feat(resourcemanager): add folder resource/datasource (#975)
* feat(resourcemanager): add folder resource/datasource * feat(resourcemanager): add created_at and updated_at attributes to resourcemanager project/folder --------- Signed-off-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>
This commit is contained in:
parent
27e4ef0227
commit
813b8c0e81
17 changed files with 1844 additions and 171 deletions
39
docs/data-sources/resourcemanager_folder.md
Normal file
39
docs/data-sources/resourcemanager_folder.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||||
|
page_title: "stackit_resourcemanager_folder Data Source - stackit"
|
||||||
|
subcategory: ""
|
||||||
|
description: |-
|
||||||
|
Resource Manager folder data source schema. To identify the folder, you need to provide the container_id.
|
||||||
|
~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
|
||||||
|
---
|
||||||
|
|
||||||
|
# stackit_resourcemanager_folder (Data Source)
|
||||||
|
|
||||||
|
Resource Manager folder data source schema. To identify the folder, you need to provide the container_id.
|
||||||
|
|
||||||
|
~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
data "stackit_resourcemanager_folder" "example" {
|
||||||
|
container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `container_id` (String) Folder container ID. Globally unique, user-friendly identifier.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `creation_time` (String) Date-time at which the folder was created.
|
||||||
|
- `folder_id` (String) Folder UUID identifier. Globally unique folder identifier
|
||||||
|
- `id` (String) Terraform's internal resource ID. It is structured as "`container_id`".
|
||||||
|
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}.
|
||||||
|
- `name` (String) The name of the folder.
|
||||||
|
- `parent_container_id` (String) Parent resource identifier. Both container ID (user-friendly) and UUID are supported.
|
||||||
|
- `update_time` (String) Date-time at which the folder was last modified.
|
||||||
|
|
@ -29,7 +29,9 @@ data "stackit_resourcemanager_project" "example" {
|
||||||
|
|
||||||
### Read-Only
|
### Read-Only
|
||||||
|
|
||||||
|
- `creation_time` (String) Date-time at which the project was created.
|
||||||
- `id` (String) Terraform's internal data source. ID. It is structured as "`container_id`".
|
- `id` (String) Terraform's internal data source. ID. It is structured as "`container_id`".
|
||||||
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}
|
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}
|
||||||
- `name` (String) Project name.
|
- `name` (String) Project name.
|
||||||
- `parent_container_id` (String) Parent resource identifier. Both container ID (user-friendly) and UUID are supported
|
- `parent_container_id` (String) Parent resource identifier. Both container ID (user-friendly) and UUID are supported
|
||||||
|
- `update_time` (String) Date-time at which the project was last modified.
|
||||||
|
|
|
||||||
64
docs/resources/resourcemanager_folder.md
Normal file
64
docs/resources/resourcemanager_folder.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||||
|
page_title: "stackit_resourcemanager_folder Resource - stackit"
|
||||||
|
subcategory: ""
|
||||||
|
description: |-
|
||||||
|
Resource Manager folder resource schema.
|
||||||
|
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
|
||||||
|
---
|
||||||
|
|
||||||
|
# stackit_resourcemanager_folder (Resource)
|
||||||
|
|
||||||
|
Resource Manager folder resource schema.
|
||||||
|
|
||||||
|
~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
resource "stackit_resourcemanager_folder" "example" {
|
||||||
|
name = "example-folder"
|
||||||
|
owner_email = "foo.bar@stackit.cloud"
|
||||||
|
parent_container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note:
|
||||||
|
# You can add projects under folders.
|
||||||
|
# However, when deleting a project, be aware:
|
||||||
|
# - Projects may remain "invisible" for up to 7 days after deletion
|
||||||
|
# - During this time, deleting the parent folder may fail because the project is still technically linked
|
||||||
|
resource "stackit_resourcemanager_project" "example_project" {
|
||||||
|
name = "example-project"
|
||||||
|
owner_email = "foo.bar@stackit.cloud"
|
||||||
|
parent_container_id = stackit_resourcemanager_folder.example.container_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only use the import statement, if you want to import an existing resourcemanager folder
|
||||||
|
# Note: There will be a conflict which needs to be resolved manually.
|
||||||
|
# Must set a configuration value for the owner_email attribute as the provider has marked it as required.
|
||||||
|
import {
|
||||||
|
to = stackit_resourcemanager_folder.import-example
|
||||||
|
id = var.container_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `name` (String) The name of the folder.
|
||||||
|
- `owner_email` (String) Email address of the owner of the folder. This value is only considered during creation. Changing it afterwards will have no effect.
|
||||||
|
- `parent_container_id` (String) Parent resource identifier. Both container ID (user-friendly) and UUID are supported.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `container_id` (String) Folder container ID. Globally unique, user-friendly identifier.
|
||||||
|
- `creation_time` (String) Date-time at which the folder was created.
|
||||||
|
- `folder_id` (String) Folder UUID identifier. Globally unique folder identifier
|
||||||
|
- `id` (String) Terraform's internal resource ID. It is structured as "`container_id`".
|
||||||
|
- `update_time` (String) Date-time at which the folder was last modified.
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
page_title: "stackit_resourcemanager_project Resource - stackit"
|
page_title: "stackit_resourcemanager_project Resource - stackit"
|
||||||
subcategory: ""
|
subcategory: ""
|
||||||
description: |-
|
description: |-
|
||||||
Resource Manager project resource schema. To use this resource, it is required that you set the service account email in the provider configuration.
|
Resource Manager project resource schema.
|
||||||
-> In case you're getting started with an empty STACKIT organization and want to use this resource to create projects in it, check out this guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/stackit_org_service_account for how to create a service account which you can use for authentication in the STACKIT Terraform provider.
|
-> In case you're getting started with an empty STACKIT organization and want to use this resource to create projects in it, check out this guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/stackit_org_service_account for how to create a service account which you can use for authentication in the STACKIT Terraform provider.
|
||||||
---
|
---
|
||||||
|
|
||||||
# stackit_resourcemanager_project (Resource)
|
# stackit_resourcemanager_project (Resource)
|
||||||
|
|
||||||
Resource Manager project resource schema. To use this resource, it is required that you set the service account email in the provider configuration.
|
Resource Manager project resource schema.
|
||||||
|
|
||||||
-> In case you're getting started with an empty STACKIT organization and want to use this resource to create projects in it, check out [this guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/stackit_org_service_account) for how to create a service account which you can use for authentication in the STACKIT Terraform provider.
|
-> In case you're getting started with an empty STACKIT organization and want to use this resource to create projects in it, check out [this guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/stackit_org_service_account) for how to create a service account which you can use for authentication in the STACKIT Terraform provider.
|
||||||
|
|
||||||
|
|
@ -52,5 +52,7 @@ To create a project within a STACKIT Network Area, setting the label `networkAre
|
||||||
### Read-Only
|
### Read-Only
|
||||||
|
|
||||||
- `container_id` (String) Project container ID. Globally unique, user-friendly identifier.
|
- `container_id` (String) Project container ID. Globally unique, user-friendly identifier.
|
||||||
|
- `creation_time` (String) Date-time at which the project was created.
|
||||||
- `id` (String) Terraform's internal resource ID. It is structured as "`container_id`".
|
- `id` (String) Terraform's internal resource ID. It is structured as "`container_id`".
|
||||||
- `project_id` (String) Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.
|
- `project_id` (String) Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.
|
||||||
|
- `update_time` (String) Date-time at which the project was last modified.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
data "stackit_resourcemanager_folder" "example" {
|
||||||
|
container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
resource "stackit_resourcemanager_folder" "example" {
|
||||||
|
name = "example-folder"
|
||||||
|
owner_email = "foo.bar@stackit.cloud"
|
||||||
|
parent_container_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Note:
|
||||||
|
# You can add projects under folders.
|
||||||
|
# However, when deleting a project, be aware:
|
||||||
|
# - Projects may remain "invisible" for up to 7 days after deletion
|
||||||
|
# - During this time, deleting the parent folder may fail because the project is still technically linked
|
||||||
|
resource "stackit_resourcemanager_project" "example_project" {
|
||||||
|
name = "example-project"
|
||||||
|
owner_email = "foo.bar@stackit.cloud"
|
||||||
|
parent_container_id = stackit_resourcemanager_folder.example.container_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only use the import statement, if you want to import an existing resourcemanager folder
|
||||||
|
# Note: There will be a conflict which needs to be resolved manually.
|
||||||
|
# Must set a configuration value for the owner_email attribute as the provider has marked it as required.
|
||||||
|
import {
|
||||||
|
to = stackit_resourcemanager_folder.import-example
|
||||||
|
id = var.container_id
|
||||||
|
}
|
||||||
185
stackit/internal/services/resourcemanager/folder/datasource.go
Normal file
185
stackit/internal/services/resourcemanager/folder/datasource.go
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
package folder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
||||||
|
resourcemanagerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/utils"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure the implementation satisfies the expected interfaces.
|
||||||
|
var (
|
||||||
|
_ datasource.DataSource = &folderDataSource{}
|
||||||
|
_ datasource.DataSourceWithConfigure = &folderDataSource{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewFolderDataSource is a helper function to simplify the provider implementation.
|
||||||
|
func NewFolderDataSource() datasource.DataSource {
|
||||||
|
return &folderDataSource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// folderDataSource is the data source implementation.
|
||||||
|
type folderDataSource struct {
|
||||||
|
client *resourcemanager.APIClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns the data source type name.
|
||||||
|
func (d *folderDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_resourcemanager_folder"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *folderDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||||
|
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_resourcemanager_folder", "datasource")
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiClient := resourcemanagerUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.client = apiClient
|
||||||
|
tflog.Info(ctx, "Resource Manager client configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema defines the schema for the data source.
|
||||||
|
func (d *folderDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
|
descriptions := map[string]string{
|
||||||
|
"main": "Resource Manager folder data source schema. To identify the folder, you need to provide the container_id.",
|
||||||
|
"id": "Terraform's internal resource ID. It is structured as \"`container_id`\".",
|
||||||
|
"container_id": "Folder container ID. Globally unique, user-friendly identifier.",
|
||||||
|
"folder_id": "Folder UUID identifier. Globally unique folder identifier",
|
||||||
|
"parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported.",
|
||||||
|
"name": "The name of the folder.",
|
||||||
|
"labels": "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}.",
|
||||||
|
"owner_email": "Email address of the owner of the folder. This value is only considered during creation. Changing it afterwards will have no effect.",
|
||||||
|
"creation_time": "Date-time at which the folder was created.",
|
||||||
|
"update_time": "Date-time at which the folder was last modified.",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: features.AddBetaDescription(descriptions["main"], core.Datasource),
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Description: descriptions["id"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"container_id": schema.StringAttribute{
|
||||||
|
Description: descriptions["container_id"],
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.NoSeparator(),
|
||||||
|
},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"folder_id": schema.StringAttribute{
|
||||||
|
Description: descriptions["folder_id"],
|
||||||
|
Computed: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.UUID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"parent_container_id": schema.StringAttribute{
|
||||||
|
Description: descriptions["parent_container_id"],
|
||||||
|
Computed: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.NoSeparator(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Description: descriptions["name"],
|
||||||
|
Computed: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
stringvalidator.LengthAtLeast(1),
|
||||||
|
stringvalidator.LengthAtMost(63),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"labels": schema.MapAttribute{
|
||||||
|
Description: descriptions["labels"],
|
||||||
|
ElementType: types.StringType,
|
||||||
|
Computed: true,
|
||||||
|
Validators: []validator.Map{
|
||||||
|
mapvalidator.KeysAre(
|
||||||
|
stringvalidator.RegexMatches(
|
||||||
|
regexp.MustCompile(`[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`),
|
||||||
|
"must match expression"),
|
||||||
|
),
|
||||||
|
mapvalidator.ValueStringsAre(
|
||||||
|
stringvalidator.RegexMatches(
|
||||||
|
regexp.MustCompile(`[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`),
|
||||||
|
"must match expression"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"creation_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["creation_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"update_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["update_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read refreshes the Terraform state with the latest data.
|
||||||
|
func (d *folderDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
var model Model
|
||||||
|
diags := req.Config.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
containerId := model.ContainerId.ValueString()
|
||||||
|
ctx = tflog.SetField(ctx, "container_id", containerId)
|
||||||
|
|
||||||
|
folderResp, err := d.client.GetFolderDetails(ctx, containerId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
utils.LogError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
err,
|
||||||
|
"Reading folder",
|
||||||
|
fmt.Sprintf("folder with ID %q does not exist.", containerId),
|
||||||
|
map[int]string{
|
||||||
|
http.StatusForbidden: fmt.Sprintf("folder with ID %q not found or forbidden access", containerId),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapFolderFields(ctx, folderResp, &model, &resp.State)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading folder", fmt.Sprintf("Processing API response: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = resp.State.Set(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tflog.Info(ctx, "Resource Manager folder read")
|
||||||
|
}
|
||||||
510
stackit/internal/services/resourcemanager/folder/resource.go
Normal file
510
stackit/internal/services/resourcemanager/folder/resource.go
Normal file
|
|
@ -0,0 +1,510 @@
|
||||||
|
package folder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||||
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||||
|
sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
||||||
|
resourcemanagerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/utils"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure the implementation satisfies the expected interfaces.
|
||||||
|
var (
|
||||||
|
_ resource.Resource = &folderResource{}
|
||||||
|
_ resource.ResourceWithConfigure = &folderResource{}
|
||||||
|
_ resource.ResourceWithImportState = &folderResource{}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
projectOwnerRole = "owner"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Model struct {
|
||||||
|
Id types.String `tfsdk:"id"` // needed by TF
|
||||||
|
FolderId types.String `tfsdk:"folder_id"`
|
||||||
|
ContainerId types.String `tfsdk:"container_id"`
|
||||||
|
ContainerParentId types.String `tfsdk:"parent_container_id"`
|
||||||
|
Name types.String `tfsdk:"name"`
|
||||||
|
Labels types.Map `tfsdk:"labels"`
|
||||||
|
CreationTime types.String `tfsdk:"creation_time"`
|
||||||
|
UpdateTime types.String `tfsdk:"update_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceModel struct {
|
||||||
|
Model
|
||||||
|
OwnerEmail types.String `tfsdk:"owner_email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFolderResource is a helper function to simplify the provider implementation.
|
||||||
|
func NewFolderResource() resource.Resource {
|
||||||
|
return &folderResource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// folderResource is the resource implementation.
|
||||||
|
type folderResource struct {
|
||||||
|
client *resourcemanager.APIClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns the resource type name.
|
||||||
|
func (r *folderResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_resourcemanager_folder"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure adds the provider configured client to the resource.
|
||||||
|
func (r *folderResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_resourcemanager_folder", "resource")
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiClient := resourcemanagerUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.client = apiClient
|
||||||
|
tflog.Info(ctx, "Resource Manager client configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema defines the schema for the resource.
|
||||||
|
func (r *folderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
|
descriptions := map[string]string{
|
||||||
|
"main": "Resource Manager folder resource schema.",
|
||||||
|
"id": "Terraform's internal resource ID. It is structured as \"`container_id`\".",
|
||||||
|
"container_id": "Folder container ID. Globally unique, user-friendly identifier.",
|
||||||
|
"folder_id": "Folder UUID identifier. Globally unique folder identifier",
|
||||||
|
"parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported.",
|
||||||
|
"name": "The name of the folder.",
|
||||||
|
"labels": "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}.",
|
||||||
|
"owner_email": "Email address of the owner of the folder. This value is only considered during creation. Changing it afterwards will have no effect.",
|
||||||
|
"creation_time": "Date-time at which the folder was created.",
|
||||||
|
"update_time": "Date-time at which the folder was last modified.",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
Description: features.AddBetaDescription(descriptions["main"], core.Resource),
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Description: descriptions["id"],
|
||||||
|
Computed: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"container_id": schema.StringAttribute{
|
||||||
|
Description: descriptions["container_id"],
|
||||||
|
Computed: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
|
},
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.NoSeparator(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"folder_id": schema.StringAttribute{
|
||||||
|
Description: descriptions["folder_id"],
|
||||||
|
Computed: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.UseStateForUnknown(),
|
||||||
|
},
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.UUID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"parent_container_id": schema.StringAttribute{
|
||||||
|
Description: descriptions["parent_container_id"],
|
||||||
|
Required: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
validate.NoSeparator(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
Description: descriptions["name"],
|
||||||
|
Required: true,
|
||||||
|
Validators: []validator.String{
|
||||||
|
stringvalidator.LengthAtLeast(1),
|
||||||
|
stringvalidator.LengthAtMost(63),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"labels": schema.MapAttribute{
|
||||||
|
Description: descriptions["labels"],
|
||||||
|
ElementType: types.StringType,
|
||||||
|
Optional: true,
|
||||||
|
Validators: []validator.Map{
|
||||||
|
mapvalidator.KeysAre(
|
||||||
|
stringvalidator.RegexMatches(
|
||||||
|
regexp.MustCompile(`[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`),
|
||||||
|
"must match expression"),
|
||||||
|
),
|
||||||
|
mapvalidator.ValueStringsAre(
|
||||||
|
stringvalidator.RegexMatches(
|
||||||
|
regexp.MustCompile(`^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`),
|
||||||
|
"must match expression"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"owner_email": schema.StringAttribute{
|
||||||
|
Description: descriptions["owner_email"],
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"creation_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["creation_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"update_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["update_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates the resource and sets the initial Terraform state.
|
||||||
|
func (r *folderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
tflog.Info(ctx, "creating folder")
|
||||||
|
var model ResourceModel
|
||||||
|
diags := req.Plan.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
containerParentId := model.ContainerParentId.ValueString()
|
||||||
|
folderName := model.Name.ValueString()
|
||||||
|
ctx = tflog.SetField(ctx, "container_parent_id", containerParentId)
|
||||||
|
ctx = tflog.SetField(ctx, "folder_name", folderName)
|
||||||
|
|
||||||
|
// Generate API request body from model
|
||||||
|
payload, err := toCreatePayload(&model)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating folder", fmt.Sprintf("Creating API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
folderCreateResp, err := r.client.CreateFolder(ctx).CreateFolderPayload(*payload).Execute()
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating folder", fmt.Sprintf("Calling API: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if folderCreateResp.ContainerId == nil || *folderCreateResp.ContainerId == "" {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating folder", "Container ID is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This sleep is currently needed due to the IAM Cache.
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
folderGetResponse, err := r.client.GetFolderDetails(ctx, *folderCreateResp.ContainerId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating folder", fmt.Sprintf("Calling API: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapFolderFields(ctx, folderGetResponse, &model.Model, &resp.State)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "API response processing error", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
||||||
|
tflog.Info(ctx, "Folder created")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read refreshes the Terraform state with the latest data.
|
||||||
|
func (r *folderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
var model ResourceModel
|
||||||
|
diags := req.State.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
containerId := model.ContainerId.ValueString()
|
||||||
|
folderName := model.Name.ValueString()
|
||||||
|
ctx = tflog.SetField(ctx, "folder_name", folderName)
|
||||||
|
ctx = tflog.SetField(ctx, "container_id", containerId)
|
||||||
|
|
||||||
|
folderResp, err := r.client.GetFolderDetails(ctx, containerId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||||
|
if ok && oapiErr.StatusCode == http.StatusForbidden {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading folder", fmt.Sprintf("Calling API: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapFolderFields(ctx, folderResp, &model.Model, &resp.State)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading folder", fmt.Sprintf("Processing API response: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set refreshed model
|
||||||
|
diags = resp.State.Set(ctx, model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tflog.Info(ctx, "Resource Manager folder read")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the resource and sets the updated Terraform state on success.
|
||||||
|
func (r *folderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
// Retrieve values from plan
|
||||||
|
var model ResourceModel
|
||||||
|
diags := req.Plan.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
containerId := model.ContainerId.ValueString()
|
||||||
|
ctx = tflog.SetField(ctx, "container_id", containerId)
|
||||||
|
|
||||||
|
// Generate API request body from model
|
||||||
|
payload, err := toUpdatePayload(&model)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating folder", fmt.Sprintf("Creating API payload: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Update existing folder
|
||||||
|
_, err = r.client.PartialUpdateFolder(ctx, containerId).PartialUpdateFolderPayload(*payload).Execute()
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating folder", fmt.Sprintf("Calling API: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch updated folder
|
||||||
|
folderResp, err := r.client.GetFolderDetails(ctx, containerId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating folder", fmt.Sprintf("Calling API for updated data: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapFolderFields(ctx, folderResp, &model.Model, &resp.State)
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating folder", fmt.Sprintf("Processing API response: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = resp.State.Set(ctx, model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tflog.Info(ctx, "Resource Manager folder updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the resource and removes the Terraform state on success.
|
||||||
|
func (r *folderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||||
|
// Retrieve values from state
|
||||||
|
var model ResourceModel
|
||||||
|
diags := req.State.Get(ctx, &model)
|
||||||
|
resp.Diagnostics.Append(diags...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
containerId := model.ContainerId.ValueString()
|
||||||
|
ctx = tflog.SetField(ctx, "container_id", containerId)
|
||||||
|
|
||||||
|
// Delete existing folder
|
||||||
|
err := r.client.DeleteFolder(ctx, containerId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
core.LogAndAddError(
|
||||||
|
ctx,
|
||||||
|
&resp.Diagnostics,
|
||||||
|
"Error deleting folder. Deletion may fail because associated projects remain hidden for up to 7 days after user deletion due to technical requirements.",
|
||||||
|
fmt.Sprintf("Calling API: %v", err),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tflog.Info(ctx, "Resource Manager folder deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportState imports a resource into the Terraform state on success.
|
||||||
|
// The expected format of the resource import identifier is: container_id
|
||||||
|
func (r *folderResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
idParts := strings.Split(req.ID, core.Separator)
|
||||||
|
if len(idParts) != 1 || idParts[0] == "" {
|
||||||
|
core.LogAndAddError(ctx, &resp.Diagnostics,
|
||||||
|
"Error importing folder",
|
||||||
|
fmt.Sprintf("Expected import identifier with format: [container_id] Got: %q", req.ID),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = tflog.SetField(ctx, "container_id", req.ID)
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("container_id"), req.ID)...)
|
||||||
|
tflog.Info(ctx, "Resource Manager folder state imported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFolderFields maps folder fields from a response into the Terraform model and optionally updates state.
|
||||||
|
func mapFolderFields(
|
||||||
|
ctx context.Context,
|
||||||
|
folderGetResponse *resourcemanager.GetFolderDetailsResponse,
|
||||||
|
model *Model,
|
||||||
|
state *tfsdk.State,
|
||||||
|
) error {
|
||||||
|
if folderGetResponse == nil {
|
||||||
|
return fmt.Errorf("folder get response is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderId string
|
||||||
|
if model.FolderId.ValueString() != "" {
|
||||||
|
folderId = model.FolderId.ValueString()
|
||||||
|
} else if folderGetResponse.FolderId != nil {
|
||||||
|
folderId = *folderGetResponse.FolderId
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("folder id not present")
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerId string
|
||||||
|
if model.ContainerId.ValueString() != "" {
|
||||||
|
containerId = model.ContainerId.ValueString()
|
||||||
|
} else if folderGetResponse.ContainerId != nil {
|
||||||
|
containerId = *folderGetResponse.ContainerId
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("container id not present")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var tfLabels basetypes.MapValue
|
||||||
|
if folderGetResponse.Labels != nil && len(*folderGetResponse.Labels) > 0 {
|
||||||
|
tfLabels, err = conversion.ToTerraformStringMap(ctx, *folderGetResponse.Labels)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("converting to StringValue map: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tfLabels = types.MapNull(types.StringType)
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerParentIdTF basetypes.StringValue
|
||||||
|
if folderGetResponse.Parent != nil {
|
||||||
|
if _, err := uuid.Parse(model.ContainerParentId.ValueString()); err == nil {
|
||||||
|
// the provided containerParent is the UUID identifier
|
||||||
|
containerParentIdTF = types.StringPointerValue(folderGetResponse.Parent.Id)
|
||||||
|
} else {
|
||||||
|
// the provided containerParent is the user-friendly container id
|
||||||
|
containerParentIdTF = types.StringPointerValue(folderGetResponse.Parent.ContainerId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
containerParentIdTF = types.StringNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
model.Id = types.StringValue(containerId)
|
||||||
|
model.FolderId = types.StringValue(folderId)
|
||||||
|
model.ContainerId = types.StringValue(containerId)
|
||||||
|
model.ContainerParentId = containerParentIdTF
|
||||||
|
model.Name = types.StringPointerValue(folderGetResponse.Name)
|
||||||
|
model.Labels = tfLabels
|
||||||
|
model.CreationTime = types.StringValue(folderGetResponse.CreationTime.Format(time.RFC3339))
|
||||||
|
model.UpdateTime = types.StringValue(folderGetResponse.UpdateTime.Format(time.RFC3339))
|
||||||
|
|
||||||
|
if state != nil {
|
||||||
|
diags := diag.Diagnostics{}
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("id"), model.Id)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("folder_id"), model.FolderId)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("container_id"), model.ContainerId)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("parent_container_id"), model.ContainerParentId)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("name"), model.Name)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("labels"), model.Labels)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("creation_time"), model.CreationTime)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("update_time"), model.UpdateTime)...)
|
||||||
|
if diags.HasError() {
|
||||||
|
return fmt.Errorf("update terraform state: %w", core.DiagsToError(diags))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMembersPayload(model *ResourceModel) (*[]resourcemanager.Member, error) {
|
||||||
|
if model == nil {
|
||||||
|
return nil, fmt.Errorf("nil model")
|
||||||
|
}
|
||||||
|
if model.OwnerEmail.IsNull() {
|
||||||
|
return nil, fmt.Errorf("owner_email is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &[]resourcemanager.Member{
|
||||||
|
{
|
||||||
|
Subject: model.OwnerEmail.ValueStringPointer(),
|
||||||
|
Role: sdkUtils.Ptr(projectOwnerRole),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCreatePayload(model *ResourceModel) (*resourcemanager.CreateFolderPayload, error) {
|
||||||
|
if model == nil {
|
||||||
|
return nil, fmt.Errorf("nil model")
|
||||||
|
}
|
||||||
|
|
||||||
|
members, err := toMembersPayload(model)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("processing members: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modelLabels := model.Labels.Elements()
|
||||||
|
labels, err := conversion.ToOptStringMap(modelLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting to Go map: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resourcemanager.CreateFolderPayload{
|
||||||
|
ContainerParentId: conversion.StringValueToPointer(model.ContainerParentId),
|
||||||
|
Labels: labels,
|
||||||
|
Members: members,
|
||||||
|
Name: conversion.StringValueToPointer(model.Name),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUpdatePayload(model *ResourceModel) (*resourcemanager.PartialUpdateFolderPayload, error) {
|
||||||
|
if model == nil {
|
||||||
|
return nil, fmt.Errorf("nil model")
|
||||||
|
}
|
||||||
|
|
||||||
|
modelLabels := model.Labels.Elements()
|
||||||
|
labels, err := conversion.ToOptStringMap(modelLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting to GO map: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resourcemanager.PartialUpdateFolderPayload{
|
||||||
|
ContainerParentId: conversion.StringValueToPointer(model.ContainerParentId),
|
||||||
|
Name: conversion.StringValueToPointer(model.Name),
|
||||||
|
Labels: labels,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,396 @@
|
||||||
|
package folder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||||
|
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
|
||||||
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMapFolderFields(t *testing.T) {
|
||||||
|
testUUID := uuid.New().String()
|
||||||
|
baseTime := time.Now()
|
||||||
|
createTime := baseTime
|
||||||
|
updateTime := baseTime.Add(1 * time.Hour)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
uuidContainerParentId bool
|
||||||
|
projectResp *resourcemanager.GetFolderDetailsResponse
|
||||||
|
expected Model
|
||||||
|
expectedLabels *map[string]string
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "default_ok",
|
||||||
|
uuidContainerParentId: false,
|
||||||
|
projectResp: &resourcemanager.GetFolderDetailsResponse{
|
||||||
|
ContainerId: utils.Ptr("cid"),
|
||||||
|
FolderId: utils.Ptr("fid"),
|
||||||
|
CreationTime: &createTime,
|
||||||
|
UpdateTime: &updateTime,
|
||||||
|
},
|
||||||
|
expected: Model{
|
||||||
|
Id: types.StringValue("cid"),
|
||||||
|
ContainerId: types.StringValue("cid"),
|
||||||
|
FolderId: types.StringValue("fid"),
|
||||||
|
ContainerParentId: types.StringNull(),
|
||||||
|
Name: types.StringNull(),
|
||||||
|
CreationTime: types.StringValue(createTime.Format(time.RFC3339)),
|
||||||
|
UpdateTime: types.StringValue(updateTime.Format(time.RFC3339)),
|
||||||
|
},
|
||||||
|
expectedLabels: nil,
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "container_parent_id_ok",
|
||||||
|
uuidContainerParentId: false,
|
||||||
|
projectResp: &resourcemanager.GetFolderDetailsResponse{
|
||||||
|
ContainerId: utils.Ptr("cid"),
|
||||||
|
FolderId: utils.Ptr("fid"),
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"label1": "ref1",
|
||||||
|
"label2": "ref2",
|
||||||
|
},
|
||||||
|
Parent: &resourcemanager.Parent{
|
||||||
|
ContainerId: utils.Ptr("parent_cid"),
|
||||||
|
Id: utils.Ptr(testUUID),
|
||||||
|
},
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
CreationTime: &createTime,
|
||||||
|
UpdateTime: &updateTime,
|
||||||
|
},
|
||||||
|
expected: Model{
|
||||||
|
Id: types.StringValue("cid"),
|
||||||
|
ContainerId: types.StringValue("cid"),
|
||||||
|
FolderId: types.StringValue("fid"),
|
||||||
|
ContainerParentId: types.StringValue("parent_cid"),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
CreationTime: types.StringValue(createTime.Format(time.RFC3339)),
|
||||||
|
UpdateTime: types.StringValue(updateTime.Format(time.RFC3339)),
|
||||||
|
},
|
||||||
|
expectedLabels: &map[string]string{
|
||||||
|
"label1": "ref1",
|
||||||
|
"label2": "ref2",
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "uuid_parent_id_ok",
|
||||||
|
uuidContainerParentId: true,
|
||||||
|
projectResp: &resourcemanager.GetFolderDetailsResponse{
|
||||||
|
ContainerId: utils.Ptr("cid"),
|
||||||
|
FolderId: utils.Ptr("fid"),
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"label1": "ref1",
|
||||||
|
"label2": "ref2",
|
||||||
|
},
|
||||||
|
Parent: &resourcemanager.Parent{
|
||||||
|
ContainerId: utils.Ptr("parent_cid"),
|
||||||
|
Id: utils.Ptr(testUUID), // simulate UUID logic
|
||||||
|
},
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
CreationTime: &createTime,
|
||||||
|
UpdateTime: &updateTime,
|
||||||
|
},
|
||||||
|
expected: Model{
|
||||||
|
Id: types.StringValue("cid"),
|
||||||
|
ContainerId: types.StringValue("cid"),
|
||||||
|
FolderId: types.StringValue("fid"),
|
||||||
|
ContainerParentId: types.StringValue(testUUID),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
CreationTime: types.StringValue(createTime.Format(time.RFC3339)),
|
||||||
|
UpdateTime: types.StringValue(updateTime.Format(time.RFC3339)),
|
||||||
|
},
|
||||||
|
expectedLabels: &map[string]string{
|
||||||
|
"label1": "ref1",
|
||||||
|
"label2": "ref2",
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "response_nil_fail",
|
||||||
|
uuidContainerParentId: false,
|
||||||
|
projectResp: nil,
|
||||||
|
expected: Model{},
|
||||||
|
expectedLabels: nil,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "no_resource_id",
|
||||||
|
uuidContainerParentId: false,
|
||||||
|
projectResp: &resourcemanager.GetFolderDetailsResponse{},
|
||||||
|
expected: Model{},
|
||||||
|
expectedLabels: nil,
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
if tt.expectedLabels == nil {
|
||||||
|
tt.expected.Labels = types.MapNull(types.StringType)
|
||||||
|
} else {
|
||||||
|
convertedLabels, err := conversion.ToTerraformStringMap(context.Background(), *tt.expectedLabels)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error converting to terraform string map: %v", err)
|
||||||
|
}
|
||||||
|
tt.expected.Labels = convertedLabels
|
||||||
|
}
|
||||||
|
var containerParentId = types.StringNull()
|
||||||
|
if tt.uuidContainerParentId {
|
||||||
|
containerParentId = types.StringValue(testUUID)
|
||||||
|
} else if tt.projectResp != nil && tt.projectResp.Parent != nil && tt.projectResp.Parent.ContainerId != nil {
|
||||||
|
containerParentId = types.StringValue(*tt.projectResp.Parent.ContainerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
model := &Model{
|
||||||
|
ContainerId: tt.expected.ContainerId,
|
||||||
|
ContainerParentId: containerParentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mapFolderFields(context.Background(), tt.projectResp, model, nil)
|
||||||
|
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(model, &tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToCreatePayload(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
input *ResourceModel
|
||||||
|
inputLabels *map[string]string
|
||||||
|
expected *resourcemanager.CreateFolderPayload
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"mapping_with_conversions",
|
||||||
|
&ResourceModel{
|
||||||
|
Model: Model{
|
||||||
|
ContainerParentId: types.StringValue("pid"),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
},
|
||||||
|
OwnerEmail: types.StringValue("john.doe@stackit.cloud"),
|
||||||
|
},
|
||||||
|
&map[string]string{
|
||||||
|
"label1": "1",
|
||||||
|
"label2": "2",
|
||||||
|
},
|
||||||
|
&resourcemanager.CreateFolderPayload{
|
||||||
|
ContainerParentId: utils.Ptr("pid"),
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"label1": "1",
|
||||||
|
"label2": "2",
|
||||||
|
},
|
||||||
|
Members: &[]resourcemanager.Member{
|
||||||
|
{
|
||||||
|
Subject: utils.Ptr("john.doe@stackit.cloud"),
|
||||||
|
Role: utils.Ptr("owner"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no owner_email fails",
|
||||||
|
&ResourceModel{
|
||||||
|
Model: Model{
|
||||||
|
ContainerParentId: types.StringValue("pid"),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&map[string]string{},
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nil_model",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
if tt.input != nil {
|
||||||
|
if tt.inputLabels == nil {
|
||||||
|
tt.input.Labels = types.MapNull(types.StringType)
|
||||||
|
} else {
|
||||||
|
convertedLabels, err := conversion.ToTerraformStringMap(context.Background(), *tt.inputLabels)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error converting to terraform string map: %v", err)
|
||||||
|
}
|
||||||
|
tt.input.Labels = convertedLabels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output, err := toCreatePayload(tt.input)
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(output, tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToUpdatePayload(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
input *ResourceModel
|
||||||
|
inputLabels *map[string]string
|
||||||
|
expected *resourcemanager.PartialUpdateFolderPayload
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"default_ok",
|
||||||
|
&ResourceModel{},
|
||||||
|
nil,
|
||||||
|
&resourcemanager.PartialUpdateFolderPayload{
|
||||||
|
ContainerParentId: nil,
|
||||||
|
Labels: nil,
|
||||||
|
Name: nil,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mapping_with_conversions_ok",
|
||||||
|
&ResourceModel{
|
||||||
|
Model: Model{
|
||||||
|
ContainerParentId: types.StringValue("pid"),
|
||||||
|
Name: types.StringValue("name"),
|
||||||
|
},
|
||||||
|
OwnerEmail: types.StringValue("owner_email"),
|
||||||
|
},
|
||||||
|
&map[string]string{
|
||||||
|
"label1": "1",
|
||||||
|
"label2": "2",
|
||||||
|
},
|
||||||
|
&resourcemanager.PartialUpdateFolderPayload{
|
||||||
|
ContainerParentId: utils.Ptr("pid"),
|
||||||
|
Labels: &map[string]string{
|
||||||
|
"label1": "1",
|
||||||
|
"label2": "2",
|
||||||
|
},
|
||||||
|
Name: utils.Ptr("name"),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nil_model",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
if tt.input != nil {
|
||||||
|
if tt.inputLabels == nil {
|
||||||
|
tt.input.Labels = types.MapNull(types.StringType)
|
||||||
|
} else {
|
||||||
|
convertedLabels, err := conversion.ToTerraformStringMap(context.Background(), *tt.inputLabels)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error converting to terraform string map: %v", err)
|
||||||
|
}
|
||||||
|
tt.input.Labels = convertedLabels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output, err := toUpdatePayload(tt.input)
|
||||||
|
if !tt.isValid && err == nil {
|
||||||
|
t.Fatalf("Should have failed")
|
||||||
|
}
|
||||||
|
if tt.isValid && err != nil {
|
||||||
|
t.Fatalf("Should not have failed: %v", err)
|
||||||
|
}
|
||||||
|
if tt.isValid {
|
||||||
|
diff := cmp.Diff(output, tt.expected)
|
||||||
|
if diff != "" {
|
||||||
|
t.Fatalf("Data does not match: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToMembersPayload(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
model *ResourceModel
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *[]resourcemanager.Member
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing model",
|
||||||
|
args: args{},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty model",
|
||||||
|
args: args{
|
||||||
|
model: &ResourceModel{},
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
args: args{
|
||||||
|
model: &ResourceModel{
|
||||||
|
OwnerEmail: types.StringValue("john.doe@stackit.cloud"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &[]resourcemanager.Member{
|
||||||
|
{
|
||||||
|
Subject: utils.Ptr("john.doe@stackit.cloud"),
|
||||||
|
Role: utils.Ptr("owner"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := toMembersPayload(tt.args.model)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("toMembersPayload() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("toMembersPayload() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,8 @@ import (
|
||||||
|
|
||||||
// Ensure the implementation satisfies the expected interfaces.
|
// Ensure the implementation satisfies the expected interfaces.
|
||||||
var (
|
var (
|
||||||
_ datasource.DataSource = &projectDataSource{}
|
_ datasource.DataSource = &projectDataSource{}
|
||||||
|
_ datasource.DataSourceWithConfigure = &projectDataSource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewProjectDataSource is a helper function to simplify the provider implementation.
|
// NewProjectDataSource is a helper function to simplify the provider implementation.
|
||||||
|
|
@ -67,6 +68,8 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
||||||
"parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported",
|
"parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported",
|
||||||
"name": "Project name.",
|
"name": "Project name.",
|
||||||
"labels": `Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`,
|
"labels": `Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}`,
|
||||||
|
"creation_time": "Date-time at which the project was created.",
|
||||||
|
"update_time": "Date-time at which the project was last modified.",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
|
|
@ -122,6 +125,14 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"creation_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["creation_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"update_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["update_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
resourcemanagerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/utils"
|
resourcemanagerUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/utils"
|
||||||
|
|
||||||
|
|
@ -51,6 +52,8 @@ type Model struct {
|
||||||
ContainerParentId types.String `tfsdk:"parent_container_id"`
|
ContainerParentId types.String `tfsdk:"parent_container_id"`
|
||||||
Name types.String `tfsdk:"name"`
|
Name types.String `tfsdk:"name"`
|
||||||
Labels types.Map `tfsdk:"labels"`
|
Labels types.Map `tfsdk:"labels"`
|
||||||
|
CreationTime types.String `tfsdk:"creation_time"`
|
||||||
|
UpdateTime types.String `tfsdk:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceModel struct {
|
type ResourceModel struct {
|
||||||
|
|
@ -92,7 +95,7 @@ func (r *projectResource) Configure(ctx context.Context, req resource.ConfigureR
|
||||||
func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
descriptions := map[string]string{
|
descriptions := map[string]string{
|
||||||
"main": fmt.Sprintf("%s\n\n%s",
|
"main": fmt.Sprintf("%s\n\n%s",
|
||||||
"Resource Manager project resource schema. To use this resource, it is required that you set the service account email in the provider configuration.",
|
"Resource Manager project resource schema.",
|
||||||
"-> In case you're getting started with an empty STACKIT organization and want to use this resource to create projects in it, check out [this guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/stackit_org_service_account) for how to create a service account which you can use for authentication in the STACKIT Terraform provider.",
|
"-> In case you're getting started with an empty STACKIT organization and want to use this resource to create projects in it, check out [this guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/stackit_org_service_account) for how to create a service account which you can use for authentication in the STACKIT Terraform provider.",
|
||||||
),
|
),
|
||||||
"id": "Terraform's internal resource ID. It is structured as \"`container_id`\".",
|
"id": "Terraform's internal resource ID. It is structured as \"`container_id`\".",
|
||||||
|
|
@ -102,6 +105,8 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||||
"name": "Project name.",
|
"name": "Project name.",
|
||||||
"labels": "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}. \nTo create a project within a STACKIT Network Area, setting the label `networkArea=<networkAreaID>` is required. This can not be changed after project creation.",
|
"labels": "Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}. \nTo create a project within a STACKIT Network Area, setting the label `networkArea=<networkAreaID>` is required. This can not be changed after project creation.",
|
||||||
"owner_email": "Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect.",
|
"owner_email": "Email address of the owner of the project. This value is only considered during creation. Changing it afterwards will have no effect.",
|
||||||
|
"creation_time": "Date-time at which the project was created.",
|
||||||
|
"update_time": "Date-time at which the project was last modified.",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Schema = schema.Schema{
|
resp.Schema = schema.Schema{
|
||||||
|
|
@ -170,6 +175,14 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||||
Description: descriptions["owner_email"],
|
Description: descriptions["owner_email"],
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
"creation_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["creation_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"update_time": schema.StringAttribute{
|
||||||
|
Description: descriptions["update_time"],
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -409,6 +422,8 @@ func mapProjectFields(ctx context.Context, projectResp *resourcemanager.GetProje
|
||||||
model.ContainerId = types.StringValue(containerId)
|
model.ContainerId = types.StringValue(containerId)
|
||||||
model.Name = types.StringPointerValue(projectResp.Name)
|
model.Name = types.StringPointerValue(projectResp.Name)
|
||||||
model.Labels = labels
|
model.Labels = labels
|
||||||
|
model.CreationTime = types.StringValue(projectResp.CreationTime.Format(time.RFC3339))
|
||||||
|
model.UpdateTime = types.StringValue(projectResp.UpdateTime.Format(time.RFC3339))
|
||||||
|
|
||||||
if state != nil {
|
if state != nil {
|
||||||
diags := diag.Diagnostics{}
|
diags := diag.Diagnostics{}
|
||||||
|
|
@ -418,6 +433,8 @@ func mapProjectFields(ctx context.Context, projectResp *resourcemanager.GetProje
|
||||||
diags.Append(state.SetAttribute(ctx, path.Root("container_id"), model.ContainerId)...)
|
diags.Append(state.SetAttribute(ctx, path.Root("container_id"), model.ContainerId)...)
|
||||||
diags.Append(state.SetAttribute(ctx, path.Root("name"), model.Name)...)
|
diags.Append(state.SetAttribute(ctx, path.Root("name"), model.Name)...)
|
||||||
diags.Append(state.SetAttribute(ctx, path.Root("labels"), model.Labels)...)
|
diags.Append(state.SetAttribute(ctx, path.Root("labels"), model.Labels)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("creation_time"), model.CreationTime)...)
|
||||||
|
diags.Append(state.SetAttribute(ctx, path.Root("update_time"), model.UpdateTime)...)
|
||||||
if diags.HasError() {
|
if diags.HasError() {
|
||||||
return fmt.Errorf("update terraform state: %w", core.DiagsToError(diags))
|
return fmt.Errorf("update terraform state: %w", core.DiagsToError(diags))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -15,6 +16,10 @@ import (
|
||||||
|
|
||||||
func TestMapProjectFields(t *testing.T) {
|
func TestMapProjectFields(t *testing.T) {
|
||||||
testUUID := uuid.New().String()
|
testUUID := uuid.New().String()
|
||||||
|
baseTime := time.Now()
|
||||||
|
createTime := baseTime
|
||||||
|
updateTime := baseTime.Add(1 * time.Hour)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
description string
|
description string
|
||||||
uuidContainerParentId bool
|
uuidContainerParentId bool
|
||||||
|
|
@ -24,26 +29,30 @@ func TestMapProjectFields(t *testing.T) {
|
||||||
isValid bool
|
isValid bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"default_ok",
|
description: "default_ok",
|
||||||
false,
|
uuidContainerParentId: false,
|
||||||
&resourcemanager.GetProjectResponse{
|
projectResp: &resourcemanager.GetProjectResponse{
|
||||||
ContainerId: utils.Ptr("cid"),
|
ContainerId: utils.Ptr("cid"),
|
||||||
ProjectId: utils.Ptr("pid"),
|
ProjectId: utils.Ptr("pid"),
|
||||||
|
CreationTime: &createTime,
|
||||||
|
UpdateTime: &updateTime,
|
||||||
},
|
},
|
||||||
Model{
|
expected: Model{
|
||||||
Id: types.StringValue("cid"),
|
Id: types.StringValue("cid"),
|
||||||
ContainerId: types.StringValue("cid"),
|
ContainerId: types.StringValue("cid"),
|
||||||
ProjectId: types.StringValue("pid"),
|
ProjectId: types.StringValue("pid"),
|
||||||
ContainerParentId: types.StringNull(),
|
ContainerParentId: types.StringNull(),
|
||||||
Name: types.StringNull(),
|
Name: types.StringNull(),
|
||||||
|
CreationTime: types.StringValue(createTime.Format(time.RFC3339)),
|
||||||
|
UpdateTime: types.StringValue(updateTime.Format(time.RFC3339)),
|
||||||
},
|
},
|
||||||
nil,
|
expectedLabels: nil,
|
||||||
true,
|
isValid: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"container_parent_id_ok",
|
description: "container_parent_id_ok",
|
||||||
false,
|
uuidContainerParentId: false,
|
||||||
&resourcemanager.GetProjectResponse{
|
projectResp: &resourcemanager.GetProjectResponse{
|
||||||
ContainerId: utils.Ptr("cid"),
|
ContainerId: utils.Ptr("cid"),
|
||||||
ProjectId: utils.Ptr("pid"),
|
ProjectId: utils.Ptr("pid"),
|
||||||
Labels: &map[string]string{
|
Labels: &map[string]string{
|
||||||
|
|
@ -54,25 +63,29 @@ func TestMapProjectFields(t *testing.T) {
|
||||||
ContainerId: utils.Ptr("parent_cid"),
|
ContainerId: utils.Ptr("parent_cid"),
|
||||||
Id: utils.Ptr("parent_pid"),
|
Id: utils.Ptr("parent_pid"),
|
||||||
},
|
},
|
||||||
Name: utils.Ptr("name"),
|
Name: utils.Ptr("name"),
|
||||||
|
CreationTime: &createTime,
|
||||||
|
UpdateTime: &updateTime,
|
||||||
},
|
},
|
||||||
Model{
|
expected: Model{
|
||||||
Id: types.StringValue("cid"),
|
Id: types.StringValue("cid"),
|
||||||
ContainerId: types.StringValue("cid"),
|
ContainerId: types.StringValue("cid"),
|
||||||
ProjectId: types.StringValue("pid"),
|
ProjectId: types.StringValue("pid"),
|
||||||
ContainerParentId: types.StringValue("parent_cid"),
|
ContainerParentId: types.StringValue("parent_cid"),
|
||||||
Name: types.StringValue("name"),
|
Name: types.StringValue("name"),
|
||||||
|
CreationTime: types.StringValue(createTime.Format(time.RFC3339)),
|
||||||
|
UpdateTime: types.StringValue(updateTime.Format(time.RFC3339)),
|
||||||
},
|
},
|
||||||
&map[string]string{
|
expectedLabels: &map[string]string{
|
||||||
"label1": "ref1",
|
"label1": "ref1",
|
||||||
"label2": "ref2",
|
"label2": "ref2",
|
||||||
},
|
},
|
||||||
true,
|
isValid: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uuid_parent_id_ok",
|
description: "uuid_parent_id_ok",
|
||||||
true,
|
uuidContainerParentId: true,
|
||||||
&resourcemanager.GetProjectResponse{
|
projectResp: &resourcemanager.GetProjectResponse{
|
||||||
ContainerId: utils.Ptr("cid"),
|
ContainerId: utils.Ptr("cid"),
|
||||||
ProjectId: utils.Ptr("pid"),
|
ProjectId: utils.Ptr("pid"),
|
||||||
Labels: &map[string]string{
|
Labels: &map[string]string{
|
||||||
|
|
@ -81,40 +94,45 @@ func TestMapProjectFields(t *testing.T) {
|
||||||
},
|
},
|
||||||
Parent: &resourcemanager.Parent{
|
Parent: &resourcemanager.Parent{
|
||||||
ContainerId: utils.Ptr("parent_cid"),
|
ContainerId: utils.Ptr("parent_cid"),
|
||||||
Id: utils.Ptr(testUUID),
|
Id: utils.Ptr(testUUID), // simulate UUID logic
|
||||||
},
|
},
|
||||||
Name: utils.Ptr("name"),
|
Name: utils.Ptr("name"),
|
||||||
|
CreationTime: &createTime,
|
||||||
|
UpdateTime: &updateTime,
|
||||||
},
|
},
|
||||||
Model{
|
expected: Model{
|
||||||
Id: types.StringValue("cid"),
|
Id: types.StringValue("cid"),
|
||||||
ContainerId: types.StringValue("cid"),
|
ContainerId: types.StringValue("cid"),
|
||||||
ProjectId: types.StringValue("pid"),
|
ProjectId: types.StringValue("pid"),
|
||||||
ContainerParentId: types.StringValue(testUUID),
|
ContainerParentId: types.StringValue(testUUID),
|
||||||
Name: types.StringValue("name"),
|
Name: types.StringValue("name"),
|
||||||
|
CreationTime: types.StringValue(createTime.Format(time.RFC3339)),
|
||||||
|
UpdateTime: types.StringValue(updateTime.Format(time.RFC3339)),
|
||||||
},
|
},
|
||||||
&map[string]string{
|
expectedLabels: &map[string]string{
|
||||||
"label1": "ref1",
|
"label1": "ref1",
|
||||||
"label2": "ref2",
|
"label2": "ref2",
|
||||||
},
|
},
|
||||||
true,
|
isValid: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"response_nil_fail",
|
description: "response_nil_fail",
|
||||||
false,
|
uuidContainerParentId: false,
|
||||||
nil,
|
projectResp: nil,
|
||||||
Model{},
|
expected: Model{},
|
||||||
nil,
|
expectedLabels: nil,
|
||||||
false,
|
isValid: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"no_resource_id",
|
description: "no_resource_id",
|
||||||
false,
|
uuidContainerParentId: false,
|
||||||
&resourcemanager.GetProjectResponse{},
|
projectResp: &resourcemanager.GetProjectResponse{},
|
||||||
Model{},
|
expected: Model{},
|
||||||
nil,
|
expectedLabels: nil,
|
||||||
false,
|
isValid: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.description, func(t *testing.T) {
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
if tt.expectedLabels == nil {
|
if tt.expectedLabels == nil {
|
||||||
|
|
@ -129,13 +147,17 @@ func TestMapProjectFields(t *testing.T) {
|
||||||
var containerParentId = types.StringNull()
|
var containerParentId = types.StringNull()
|
||||||
if tt.uuidContainerParentId {
|
if tt.uuidContainerParentId {
|
||||||
containerParentId = types.StringValue(testUUID)
|
containerParentId = types.StringValue(testUUID)
|
||||||
|
} else if tt.projectResp != nil && tt.projectResp.Parent != nil && tt.projectResp.Parent.ContainerId != nil {
|
||||||
|
containerParentId = types.StringValue(*tt.projectResp.Parent.ContainerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
model := &Model{
|
model := &Model{
|
||||||
ContainerId: tt.expected.ContainerId,
|
ContainerId: tt.expected.ContainerId,
|
||||||
ContainerParentId: containerParentId,
|
ContainerParentId: containerParentId,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mapProjectFields(context.Background(), tt.projectResp, model, nil)
|
err := mapProjectFields(context.Background(), tt.projectResp, model, nil)
|
||||||
|
|
||||||
if !tt.isValid && err == nil {
|
if !tt.isValid && err == nil {
|
||||||
t.Fatalf("Should have failed")
|
t.Fatalf("Should have failed")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,176 +2,450 @@ package resourcemanager_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/config"
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
|
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
|
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager"
|
||||||
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager/wait"
|
"github.com/stackitcloud/stackit-sdk-go/services/resourcemanager/wait"
|
||||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
|
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Project resource data
|
//go:embed testdata/resource-project.tf
|
||||||
var projectResource = map[string]string{
|
var resourceProject string
|
||||||
"name": fmt.Sprintf("acc-pj-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)),
|
|
||||||
"parent_container_id": testutil.TestProjectParentContainerID,
|
//go:embed testdata/resource-folder.tf
|
||||||
"parent_uuid": testutil.TestProjectParentUUID,
|
var resourceFolder string
|
||||||
"billing_reference": "TEST-REF",
|
|
||||||
"new_label": "a-label",
|
var defaultLabels = config.ObjectVariable(
|
||||||
|
map[string]config.Variable{
|
||||||
|
"env": config.StringVariable("prod"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
var projectNameParentContainerId = fmt.Sprintf("tfe2e-project-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
|
||||||
|
var projectNameParentContainerIdUpdated = fmt.Sprintf("%s-updated", projectNameParentContainerId)
|
||||||
|
|
||||||
|
var projectNameParentUUID = fmt.Sprintf("tfe2e-project-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
|
||||||
|
var projectNameParentUUIDUpdated = fmt.Sprintf("%s-updated", projectNameParentUUID)
|
||||||
|
|
||||||
|
var folderNameParentContainerId = fmt.Sprintf("tfe2e-folder-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
|
||||||
|
var folderNameParentContainerIdUpdated = fmt.Sprintf("%s-updated", folderNameParentContainerId)
|
||||||
|
|
||||||
|
var folderNameParentUUID = fmt.Sprintf("tfe2e-folder-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
|
||||||
|
var folderNameParentUUIDUpdated = fmt.Sprintf("%s-updated", folderNameParentUUID)
|
||||||
|
|
||||||
|
var testConfigResourceProjectParentContainerId = config.Variables{
|
||||||
|
"name": config.StringVariable(projectNameParentContainerId),
|
||||||
|
"owner_email": config.StringVariable(testutil.TestProjectServiceAccountEmail),
|
||||||
|
"parent_container_id": config.StringVariable(testutil.TestProjectParentContainerID),
|
||||||
|
"labels": config.ObjectVariable(
|
||||||
|
map[string]config.Variable{
|
||||||
|
"env": config.StringVariable("prod"),
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceConfig(name string, label *string) string {
|
var testConfigResourceProjectParentUUID = config.Variables{
|
||||||
labelConfig := ""
|
"name": config.StringVariable(projectNameParentUUID),
|
||||||
if label != nil {
|
"owner_email": config.StringVariable(testutil.TestProjectServiceAccountEmail),
|
||||||
labelConfig = fmt.Sprintf("new_label = %q", *label)
|
"parent_container_id": config.StringVariable(testutil.TestProjectParentUUID),
|
||||||
}
|
"labels": defaultLabels,
|
||||||
return fmt.Sprintf(`
|
|
||||||
%[1]s
|
|
||||||
|
|
||||||
resource "stackit_resourcemanager_project" "parent_by_container" {
|
|
||||||
parent_container_id = "%[2]s"
|
|
||||||
name = "%[3]s"
|
|
||||||
labels = {
|
|
||||||
"billing_reference" = "%[4]s"
|
|
||||||
%[5]s
|
|
||||||
}
|
|
||||||
owner_email = "%[7]s"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "stackit_resourcemanager_project" "parent_by_uuid" {
|
|
||||||
parent_container_id = "%[6]s"
|
|
||||||
name = "%[3]s-uuid"
|
|
||||||
owner_email = "%[7]s"
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
testutil.ResourceManagerProviderConfig(),
|
|
||||||
projectResource["parent_container_id"],
|
|
||||||
name,
|
|
||||||
projectResource["billing_reference"],
|
|
||||||
labelConfig,
|
|
||||||
projectResource["parent_uuid"],
|
|
||||||
testutil.TestProjectServiceAccountEmail,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccResourceManagerResource(t *testing.T) {
|
var testConfigResourceFolderParentContainerId = config.Variables{
|
||||||
|
"name": config.StringVariable(folderNameParentContainerId),
|
||||||
|
"owner_email": config.StringVariable(testutil.TestProjectServiceAccountEmail),
|
||||||
|
"parent_container_id": config.StringVariable(testutil.TestProjectParentContainerID),
|
||||||
|
"labels": defaultLabels,
|
||||||
|
}
|
||||||
|
|
||||||
|
var testConfigResourceFolderParentUUID = config.Variables{
|
||||||
|
"name": config.StringVariable(folderNameParentUUID),
|
||||||
|
"owner_email": config.StringVariable(testutil.TestProjectServiceAccountEmail),
|
||||||
|
"parent_container_id": config.StringVariable(testutil.TestProjectParentUUID),
|
||||||
|
"labels": defaultLabels,
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigProjectNameParentContainerIdUpdated() config.Variables {
|
||||||
|
tempConfig := make(config.Variables, len(testConfigResourceProjectParentContainerId))
|
||||||
|
maps.Copy(tempConfig, testConfigResourceProjectParentContainerId)
|
||||||
|
tempConfig["name"] = config.StringVariable(projectNameParentContainerIdUpdated)
|
||||||
|
return tempConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigProjectNameParentUUIDUpdated() config.Variables {
|
||||||
|
tempConfig := make(config.Variables, len(testConfigResourceProjectParentUUID))
|
||||||
|
maps.Copy(tempConfig, testConfigResourceProjectParentUUID)
|
||||||
|
tempConfig["name"] = config.StringVariable(projectNameParentUUIDUpdated)
|
||||||
|
return tempConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigFolderNameParentContainerIdUpdated() config.Variables {
|
||||||
|
tempConfig := make(config.Variables, len(testConfigResourceFolderParentContainerId))
|
||||||
|
maps.Copy(tempConfig, testConfigResourceFolderParentContainerId)
|
||||||
|
tempConfig["name"] = config.StringVariable(folderNameParentContainerIdUpdated)
|
||||||
|
return tempConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigFolderNameParentUUIDUpdated() config.Variables {
|
||||||
|
tempConfig := make(config.Variables, len(testConfigResourceFolderParentUUID))
|
||||||
|
maps.Copy(tempConfig, testConfigResourceFolderParentUUID)
|
||||||
|
tempConfig["name"] = config.StringVariable(folderNameParentUUIDUpdated)
|
||||||
|
return tempConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccResourceManagerProjectContainerId(t *testing.T) {
|
||||||
|
t.Logf("TestAccResourceManagerProjectContainerId name: %s", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["name"]))
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||||
CheckDestroy: testAccCheckResourceManagerDestroy,
|
CheckDestroy: testAccCheckDestroy,
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
// Creation
|
// Create
|
||||||
{
|
{
|
||||||
Config: resourceConfig(projectResource["name"], nil),
|
ConfigVariables: testConfigResourceProjectParentContainerId,
|
||||||
|
Config: testutil.ResourceManagerProviderConfig() + resourceProject,
|
||||||
Check: resource.ComposeAggregateTestCheckFunc(
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
// Parent container id project data
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "name", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["name"])),
|
||||||
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.parent_by_container", "container_id"),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["parent_container_id"])),
|
||||||
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.parent_by_container", "project_id"),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "owner_email", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["owner_email"])),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "name", projectResource["name"]),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.%", "1"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "parent_container_id", projectResource["parent_container_id"]),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.env", "prod"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "labels.%", "1"),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "container_id"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "labels.billing_reference", projectResource["billing_reference"]),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "project_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "owner_email"),
|
||||||
// Parent UUID project data
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "id"),
|
||||||
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.parent_by_uuid", "container_id"),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "creation_time"),
|
||||||
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.parent_by_uuid", "project_id"),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "update_time"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_uuid", "name", fmt.Sprintf("%s-uuid", projectResource["name"])),
|
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_uuid", "parent_container_id", projectResource["parent_uuid"]),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// Data source
|
// Data Source
|
||||||
{
|
{
|
||||||
|
ConfigVariables: testConfigResourceProjectParentContainerId,
|
||||||
Config: fmt.Sprintf(`
|
Config: fmt.Sprintf(`
|
||||||
%s
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
data "stackit_resourcemanager_project" "project_by_container" {
|
data "stackit_resourcemanager_project" "example" {
|
||||||
container_id = stackit_resourcemanager_project.parent_by_container.container_id
|
project_id = stackit_resourcemanager_project.example.project_id
|
||||||
}
|
}
|
||||||
|
`, testutil.ResourceManagerProviderConfig(), resourceProject),
|
||||||
data "stackit_resourcemanager_project" "project_by_uuid" {
|
|
||||||
project_id = stackit_resourcemanager_project.parent_by_container.project_id
|
|
||||||
}
|
|
||||||
|
|
||||||
data "stackit_resourcemanager_project" "project_by_both" {
|
|
||||||
container_id = stackit_resourcemanager_project.parent_by_container.container_id
|
|
||||||
project_id = stackit_resourcemanager_project.parent_by_container.project_id
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
resourceConfig(projectResource["name"], nil),
|
|
||||||
),
|
|
||||||
Check: resource.ComposeAggregateTestCheckFunc(
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
// Container project data
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "name", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["name"])),
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_container", "id"),
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["parent_container_id"])),
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_container", "container_id"),
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_project.example", "container_id", "stackit_resourcemanager_project.example", "container_id"),
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_container", "project_id"),
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_project.example", "project_id", "stackit_resourcemanager_project.example", "project_id"),
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_container", "name", projectResource["name"]),
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "labels.%", "1"),
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_container", "parent_container_id"),
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "labels.env", "prod"),
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_container", "labels.%", "1"),
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "id"),
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_container", "labels.billing_reference", projectResource["billing_reference"]),
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "update_time"),
|
||||||
// UUID project data
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_uuid", "id"),
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_uuid", "container_id"),
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_uuid", "project_id"),
|
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_uuid", "name", projectResource["name"]),
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_uuid", "parent_container_id"),
|
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_uuid", "labels.%", "1"),
|
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_uuid", "labels.billing_reference", projectResource["billing_reference"]),
|
|
||||||
|
|
||||||
// Both project data
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_both", "id"),
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_both", "container_id"),
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_both", "project_id"),
|
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_both", "name", projectResource["name"]),
|
|
||||||
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_by_both", "parent_container_id"),
|
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_both", "labels.%", "1"),
|
|
||||||
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_by_both", "labels.billing_reference", projectResource["billing_reference"]),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// Import
|
// Import
|
||||||
{
|
{
|
||||||
ResourceName: "stackit_resourcemanager_project.parent_by_container",
|
ConfigVariables: testConfigResourceProjectParentContainerId,
|
||||||
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
ResourceName: "stackit_resourcemanager_project.example",
|
||||||
r, ok := s.RootModule().Resources["stackit_resourcemanager_project.parent_by_container"]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("couldn't find resource stackit_resourcemanager_project.parent_by_container")
|
|
||||||
}
|
|
||||||
containerId, ok := r.Primary.Attributes["container_id"]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("couldn't find attribute container_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
return containerId, nil
|
|
||||||
},
|
|
||||||
ImportState: true,
|
ImportState: true,
|
||||||
ImportStateVerify: true,
|
ImportStateVerify: true,
|
||||||
// The owner_email attributes don't exist in the
|
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||||
// API, therefore there is no value for it during import.
|
return getImportIdFromID(s, "stackit_resourcemanager_project.example", "container_id")
|
||||||
|
},
|
||||||
ImportStateVerifyIgnore: []string{"owner_email"},
|
ImportStateVerifyIgnore: []string{"owner_email"},
|
||||||
},
|
},
|
||||||
// Update
|
// Update
|
||||||
{
|
{
|
||||||
Config: resourceConfig(fmt.Sprintf("%s-new", projectResource["name"]), utils.Ptr("a-label")),
|
ConfigVariables: testConfigProjectNameParentContainerIdUpdated(),
|
||||||
|
Config: testutil.ResourceManagerProviderConfig() + resourceProject,
|
||||||
Check: resource.ComposeAggregateTestCheckFunc(
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
// Project data
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "name", testutil.ConvertConfigVariable(testConfigProjectNameParentContainerIdUpdated()["name"])),
|
||||||
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.parent_by_container", "container_id"),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["parent_container_id"])),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "name", fmt.Sprintf("%s-new", projectResource["name"])),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "owner_email", testutil.ConvertConfigVariable(testConfigResourceProjectParentContainerId["owner_email"])),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "parent_container_id", projectResource["parent_container_id"]),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.%", "1"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "labels.%", "2"),
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.env", "prod"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "labels.billing_reference", projectResource["billing_reference"]),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "container_id"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "labels.new_label", projectResource["new_label"]),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "project_id"),
|
||||||
resource.TestCheckResourceAttr("stackit_resourcemanager_project.parent_by_container", "owner_email", testutil.TestProjectServiceAccountEmail),
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "owner_email"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "update_time"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// Deletion is done by the framework implicitly
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckResourceManagerDestroy(s *terraform.State) error {
|
func TestAccResourceManagerProjectParentUUID(t *testing.T) {
|
||||||
|
t.Logf("TestAccResourceManagerProjectParentUUID name: %s", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["name"]))
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||||
|
CheckDestroy: testAccCheckDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
// Create
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceProjectParentUUID,
|
||||||
|
Config: testutil.ResourceManagerProviderConfig() + resourceProject,
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "name", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["name"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "owner_email", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["owner_email"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "project_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "owner_email"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Data Source
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceProjectParentUUID,
|
||||||
|
Config: fmt.Sprintf(`
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
data "stackit_resourcemanager_project" "example" {
|
||||||
|
project_id = stackit_resourcemanager_project.example.project_id
|
||||||
|
}
|
||||||
|
`, testutil.ResourceManagerProviderConfig(), resourceProject),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "name", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["name"])),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "parent_container_id"),
|
||||||
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_project.example", "container_id", "stackit_resourcemanager_project.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_project.example", "project_id", "stackit_resourcemanager_project.example", "project_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Import
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceProjectParentUUID,
|
||||||
|
ResourceName: "stackit_resourcemanager_project.example",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||||
|
return getImportIdFromID(s, "stackit_resourcemanager_project.example", "container_id")
|
||||||
|
},
|
||||||
|
ImportStateVerifyIgnore: []string{"owner_email", "parent_container_id"},
|
||||||
|
},
|
||||||
|
// Update
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigProjectNameParentUUIDUpdated(),
|
||||||
|
Config: testutil.ResourceManagerProviderConfig() + resourceProject,
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "name", testutil.ConvertConfigVariable(testConfigProjectNameParentUUIDUpdated()["name"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "owner_email", testutil.ConvertConfigVariable(testConfigResourceProjectParentUUID["owner_email"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_project.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "project_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "owner_email"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccResourceManagerFolderContainerId(t *testing.T) {
|
||||||
|
t.Logf("TestAccResourceManagerFolderContainerId name: %s", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["name"]))
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||||
|
CheckDestroy: testAccCheckDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
// Create
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceFolderParentContainerId,
|
||||||
|
Config: testutil.ResourceManagerProviderConfigBetaEnabled() + resourceFolder,
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "name", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["name"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "owner_email", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["owner_email"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "folder_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Data Source
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceFolderParentContainerId,
|
||||||
|
Config: fmt.Sprintf(`
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
data "stackit_resourcemanager_folder" "example" {
|
||||||
|
container_id = stackit_resourcemanager_folder.example.container_id
|
||||||
|
}
|
||||||
|
`, testutil.ResourceManagerProviderConfigBetaEnabled(), resourceFolder),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "name", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["name"])),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_folder.example", "container_id", "stackit_resourcemanager_folder.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_folder.example", "project_id", "stackit_resourcemanager_folder.example", "project_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Import
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceFolderParentContainerId,
|
||||||
|
ResourceName: "stackit_resourcemanager_folder.example",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||||
|
return getImportIdFromID(s, "stackit_resourcemanager_folder.example", "container_id")
|
||||||
|
},
|
||||||
|
ImportStateVerifyIgnore: []string{"owner_email"},
|
||||||
|
},
|
||||||
|
// Update
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigFolderNameParentContainerIdUpdated(),
|
||||||
|
Config: testutil.ResourceManagerProviderConfigBetaEnabled() + resourceFolder,
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "name", testutil.ConvertConfigVariable(testConfigFolderNameParentContainerIdUpdated()["name"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigFolderNameParentContainerIdUpdated()["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "owner_email", testutil.ConvertConfigVariable(testConfigFolderNameParentContainerIdUpdated()["owner_email"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "folder_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "owner_email"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccResourceManagerFolderParentUUID(t *testing.T) {
|
||||||
|
t.Logf("TestAccResourceManagerFolderParentUUID name: %s", testutil.ConvertConfigVariable(testConfigResourceFolderParentContainerId["name"]))
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||||
|
CheckDestroy: testAccCheckDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
// Create
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceFolderParentUUID,
|
||||||
|
Config: testutil.ResourceManagerProviderConfigBetaEnabled() + resourceFolder,
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "name", testutil.ConvertConfigVariable(testConfigResourceFolderParentUUID["name"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigResourceFolderParentUUID["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "owner_email", testutil.ConvertConfigVariable(testConfigResourceFolderParentUUID["owner_email"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "folder_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Data Source
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceFolderParentUUID,
|
||||||
|
Config: fmt.Sprintf(`
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
|
||||||
|
data "stackit_resourcemanager_folder" "example" {
|
||||||
|
container_id = stackit_resourcemanager_folder.example.container_id
|
||||||
|
}
|
||||||
|
`, testutil.ResourceManagerProviderConfigBetaEnabled(), resourceFolder),
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "name", testutil.ConvertConfigVariable(testConfigResourceFolderParentUUID["name"])),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("data.stackit_resourcemanager_folder.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "parent_container_id"),
|
||||||
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_folder.example", "container_id", "stackit_resourcemanager_folder.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_folder.example", "project_id", "stackit_resourcemanager_folder.example", "project_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_folder.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// Import
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigResourceFolderParentUUID,
|
||||||
|
ResourceName: "stackit_resourcemanager_folder.example",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||||
|
return getImportIdFromID(s, "stackit_resourcemanager_folder.example", "container_id")
|
||||||
|
},
|
||||||
|
ImportStateVerifyIgnore: []string{"owner_email", "parent_container_id"},
|
||||||
|
},
|
||||||
|
// Update
|
||||||
|
{
|
||||||
|
ConfigVariables: testConfigFolderNameParentUUIDUpdated(),
|
||||||
|
Config: testutil.ResourceManagerProviderConfigBetaEnabled() + resourceFolder,
|
||||||
|
Check: resource.ComposeAggregateTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "name", testutil.ConvertConfigVariable(testConfigFolderNameParentUUIDUpdated()["name"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "parent_container_id", testutil.ConvertConfigVariable(testConfigFolderNameParentUUIDUpdated()["parent_container_id"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "owner_email", testutil.ConvertConfigVariable(testConfigFolderNameParentUUIDUpdated()["owner_email"])),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.%", "1"),
|
||||||
|
resource.TestCheckResourceAttr("stackit_resourcemanager_folder.example", "labels.env", "prod"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "container_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "folder_id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "id"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "owner_email"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "creation_time"),
|
||||||
|
resource.TestCheckResourceAttrSet("stackit_resourcemanager_folder.example", "update_time"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDestroy(s *terraform.State) error {
|
||||||
|
checkFunctions := []func(s *terraform.State) error{
|
||||||
|
testAccCheckResourceManagerProjectsDestroy,
|
||||||
|
testAccCheckResourceManagerFoldersDestroy,
|
||||||
|
}
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(checkFunctions))
|
||||||
|
|
||||||
|
for _, f := range checkFunctions {
|
||||||
|
go func() {
|
||||||
|
err := f(s)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckResourceManagerProjectsDestroy(s *terraform.State) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
var client *resourcemanager.APIClient
|
var client *resourcemanager.APIClient
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -179,7 +453,7 @@ func testAccCheckResourceManagerDestroy(s *terraform.State) error {
|
||||||
client, err = resourcemanager.NewAPIClient()
|
client, err = resourcemanager.NewAPIClient()
|
||||||
} else {
|
} else {
|
||||||
client, err = resourcemanager.NewAPIClient(
|
client, err = resourcemanager.NewAPIClient(
|
||||||
config.WithEndpoint(testutil.ResourceManagerCustomEndpoint),
|
sdkConfig.WithEndpoint(testutil.ResourceManagerCustomEndpoint),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -196,7 +470,17 @@ func testAccCheckResourceManagerDestroy(s *terraform.State) error {
|
||||||
projectsToDestroy = append(projectsToDestroy, containerId)
|
projectsToDestroy = append(projectsToDestroy, containerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
projectsResp, err := client.ListProjects(ctx).ContainerParentId(projectResource["parent_container_id"]).Execute()
|
var containerParentId string
|
||||||
|
switch {
|
||||||
|
case testutil.TestProjectParentContainerID != "":
|
||||||
|
containerParentId = testutil.TestProjectParentContainerID
|
||||||
|
case testutil.TestProjectParentUUID != "":
|
||||||
|
containerParentId = testutil.TestProjectParentUUID
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("either TestProjectParentContainerID or TestProjectParentUUID must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectsResp, err := client.ListProjects(ctx).ContainerParentId(containerParentId).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting projectsResp: %w", err)
|
return fmt.Errorf("getting projectsResp: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -221,3 +505,69 @@ func testAccCheckResourceManagerDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAccCheckResourceManagerFoldersDestroy(s *terraform.State) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
var client *resourcemanager.APIClient
|
||||||
|
var err error
|
||||||
|
if testutil.ResourceManagerCustomEndpoint == "" {
|
||||||
|
client, err = resourcemanager.NewAPIClient()
|
||||||
|
} else {
|
||||||
|
client, err = resourcemanager.NewAPIClient(
|
||||||
|
sdkConfig.WithEndpoint(testutil.ResourceManagerCustomEndpoint),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
foldersToDestroy := []string{}
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "stackit_resourcemanager_folder" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// project terraform ID: "[container_id]"
|
||||||
|
containerId := rs.Primary.ID
|
||||||
|
foldersToDestroy = append(foldersToDestroy, containerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerParentId string
|
||||||
|
switch {
|
||||||
|
case testutil.TestProjectParentContainerID != "":
|
||||||
|
containerParentId = testutil.TestProjectParentContainerID
|
||||||
|
case testutil.TestProjectParentUUID != "":
|
||||||
|
containerParentId = testutil.TestProjectParentUUID
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("either TestProjectParentContainerID or TestProjectParentUUID must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectsResp, err := client.ListFolders(ctx).ContainerParentId(containerParentId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting projectsResp: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := *projectsResp.Items
|
||||||
|
for i := range items {
|
||||||
|
if !utils.Contains(foldersToDestroy, *items[i].ContainerId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := client.DeleteFolder(ctx, *items[i].ContainerId).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("destroying folder %s during CheckDestroy: %w", *items[i].ContainerId, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImportIdFromID(s *terraform.State, resourceName, keyName string) (string, error) {
|
||||||
|
r, ok := s.RootModule().Resources[resourceName]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("couldn't find resource %s", resourceName)
|
||||||
|
}
|
||||||
|
id, ok := r.Primary.Attributes[keyName]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("couldn't find attribute %s", keyName)
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
12
stackit/internal/services/resourcemanager/testdata/resource-folder.tf
vendored
Normal file
12
stackit/internal/services/resourcemanager/testdata/resource-folder.tf
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
variable "parent_container_id" {}
|
||||||
|
variable "name" {}
|
||||||
|
variable "labels" {}
|
||||||
|
variable "owner_email" {}
|
||||||
|
|
||||||
|
resource "stackit_resourcemanager_folder" "example" {
|
||||||
|
parent_container_id = var.parent_container_id
|
||||||
|
name = var.name
|
||||||
|
labels = var.labels
|
||||||
|
owner_email = var.owner_email
|
||||||
|
}
|
||||||
12
stackit/internal/services/resourcemanager/testdata/resource-project.tf
vendored
Normal file
12
stackit/internal/services/resourcemanager/testdata/resource-project.tf
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
variable "parent_container_id" {}
|
||||||
|
variable "name" {}
|
||||||
|
variable "labels" {}
|
||||||
|
variable "owner_email" {}
|
||||||
|
|
||||||
|
resource "stackit_resourcemanager_project" "example" {
|
||||||
|
parent_container_id = var.parent_container_id
|
||||||
|
name = var.name
|
||||||
|
labels = var.labels
|
||||||
|
owner_email = var.owner_email
|
||||||
|
}
|
||||||
|
|
@ -323,10 +323,8 @@ func ResourceManagerProviderConfig() string {
|
||||||
if ResourceManagerCustomEndpoint == "" || AuthorizationCustomEndpoint == "" {
|
if ResourceManagerCustomEndpoint == "" || AuthorizationCustomEndpoint == "" {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
provider "stackit" {
|
provider "stackit" {
|
||||||
service_account_email = "%s"
|
|
||||||
service_account_token = "%s"
|
service_account_token = "%s"
|
||||||
}`,
|
}`,
|
||||||
TestProjectServiceAccountEmail,
|
|
||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -334,12 +332,35 @@ func ResourceManagerProviderConfig() string {
|
||||||
provider "stackit" {
|
provider "stackit" {
|
||||||
resourcemanager_custom_endpoint = "%s"
|
resourcemanager_custom_endpoint = "%s"
|
||||||
authorization_custom_endpoint = "%s"
|
authorization_custom_endpoint = "%s"
|
||||||
service_account_email = "%s"
|
|
||||||
service_account_token = "%s"
|
service_account_token = "%s"
|
||||||
}`,
|
}`,
|
||||||
ResourceManagerCustomEndpoint,
|
ResourceManagerCustomEndpoint,
|
||||||
AuthorizationCustomEndpoint,
|
AuthorizationCustomEndpoint,
|
||||||
TestProjectServiceAccountEmail,
|
token,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResourceManagerProviderConfigBetaEnabled() string {
|
||||||
|
token := GetTestProjectServiceAccountToken("")
|
||||||
|
if ResourceManagerCustomEndpoint == "" || AuthorizationCustomEndpoint == "" {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "stackit" {
|
||||||
|
service_account_token = "%s"
|
||||||
|
enable_beta_resources = true
|
||||||
|
}`,
|
||||||
|
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
provider "stackit" {
|
||||||
|
resourcemanager_custom_endpoint = "%s"
|
||||||
|
authorization_custom_endpoint = "%s"
|
||||||
|
service_account_token = "%s"
|
||||||
|
enable_beta_resources = true
|
||||||
|
}`,
|
||||||
|
ResourceManagerCustomEndpoint,
|
||||||
|
AuthorizationCustomEndpoint,
|
||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ import (
|
||||||
rabbitMQInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/rabbitmq/instance"
|
rabbitMQInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/rabbitmq/instance"
|
||||||
redisCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/redis/credential"
|
redisCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/redis/credential"
|
||||||
redisInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/redis/instance"
|
redisInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/redis/instance"
|
||||||
|
resourceManagerFolder "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/folder"
|
||||||
resourceManagerProject "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/project"
|
resourceManagerProject "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/project"
|
||||||
secretsManagerInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/secretsmanager/instance"
|
secretsManagerInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/secretsmanager/instance"
|
||||||
secretsManagerUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/secretsmanager/user"
|
secretsManagerUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/secretsmanager/user"
|
||||||
|
|
@ -499,6 +500,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
|
||||||
redisInstance.NewInstanceDataSource,
|
redisInstance.NewInstanceDataSource,
|
||||||
redisCredential.NewCredentialDataSource,
|
redisCredential.NewCredentialDataSource,
|
||||||
resourceManagerProject.NewProjectDataSource,
|
resourceManagerProject.NewProjectDataSource,
|
||||||
|
resourceManagerFolder.NewFolderDataSource,
|
||||||
secretsManagerInstance.NewInstanceDataSource,
|
secretsManagerInstance.NewInstanceDataSource,
|
||||||
secretsManagerUser.NewUserDataSource,
|
secretsManagerUser.NewUserDataSource,
|
||||||
sqlServerFlexInstance.NewInstanceDataSource,
|
sqlServerFlexInstance.NewInstanceDataSource,
|
||||||
|
|
@ -565,6 +567,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
|
||||||
redisInstance.NewInstanceResource,
|
redisInstance.NewInstanceResource,
|
||||||
redisCredential.NewCredentialResource,
|
redisCredential.NewCredentialResource,
|
||||||
resourceManagerProject.NewProjectResource,
|
resourceManagerProject.NewProjectResource,
|
||||||
|
resourceManagerFolder.NewFolderResource,
|
||||||
secretsManagerInstance.NewInstanceResource,
|
secretsManagerInstance.NewInstanceResource,
|
||||||
secretsManagerUser.NewUserResource,
|
secretsManagerUser.NewUserResource,
|
||||||
sqlServerFlexInstance.NewInstanceResource,
|
sqlServerFlexInstance.NewInstanceResource,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue