From de0635e82cc96d92513ad15d689629c838ffe6b4 Mon Sep 17 00:00:00 2001 From: vicentepinto98 Date: Tue, 12 Dec 2023 17:43:33 +0000 Subject: [PATCH] Support both uuid and container id, update acceptance tests --- Makefile | 3 + .../resourcemanager/project/datasource.go | 30 ++++-- .../resourcemanager/project/resource.go | 6 +- .../resourcemanager_acc_test.go | 94 +++++++++++++------ stackit/internal/testutil/testutil.go | 4 +- 5 files changed, 98 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index 5820843d..d233cd4c 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,9 @@ generate-docs: @echo "Generating documentation with tfplugindocs" @$(SCRIPTS_BASE)/tfplugindocs.sh +build: + @go build -o bin/terraform-provider-stackit + # TEST test: @echo "Running tests for the terraform provider" diff --git a/stackit/internal/services/resourcemanager/project/datasource.go b/stackit/internal/services/resourcemanager/project/datasource.go index d9332eff..fc6de657 100644 --- a/stackit/internal/services/resourcemanager/project/datasource.go +++ b/stackit/internal/services/resourcemanager/project/datasource.go @@ -89,11 +89,11 @@ func (d *projectDataSource) Configure(ctx context.Context, req datasource.Config // Schema defines the schema for the data source. func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ - "main": "Resource Manager project data source schema.", + "main": "Resource Manager project data source schema. To identify the project, you need to provider either project_id or container_id. If you provide both, project_id will be used.", "id": "Terraform's internal data source. ID. It is structured as \"`container_id`\".", - "project_id": "Project ID. It is an UUID.", - "container_id": "Project container ID.", - "parent_container_id": "Parent resource container ID. Both container ID (user-friendly) and UUID are supported", + "project_id": "Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.", + "container_id": "Project container ID. Globally unique, user-friendly identifier.", + "parent_container_id": "Parent resource identifier. Both container ID (user-friendly) and UUID are supported", "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}`, } @@ -108,14 +108,13 @@ func (d *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest "project_id": schema.StringAttribute{ Description: descriptions["project_id"], Optional: true, - Computed: true, Validators: []validator.String{ validate.UUID(), }, }, "container_id": schema.StringAttribute{ Description: descriptions["container_id"], - Required: true, + Optional: true, Validators: []validator.String{ validate.NoSeparator(), }, @@ -164,10 +163,25 @@ func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest if resp.Diagnostics.HasError() { return } + + projectId := state.ProjectId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + containerId := state.ContainerId.ValueString() - ctx = tflog.SetField(ctx, "project_id", containerId) + ctx = tflog.SetField(ctx, "container_id", containerId) + + if containerId == "" && projectId == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading project", "Either container_id or project_id must be set") + return + } + + // set project identifier. If projectId is provided, it takes precedence over containerId + var identifier = containerId + if projectId != "" { + identifier = projectId + } - projectResp, err := d.client.GetProject(ctx, containerId).Execute() + projectResp, err := d.client.GetProject(ctx, identifier).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading project", fmt.Sprintf("Calling API: %v", err)) return diff --git a/stackit/internal/services/resourcemanager/project/resource.go b/stackit/internal/services/resourcemanager/project/resource.go index f278afba..b0793d60 100644 --- a/stackit/internal/services/resourcemanager/project/resource.go +++ b/stackit/internal/services/resourcemanager/project/resource.go @@ -106,9 +106,9 @@ func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, re descriptions := map[string]string{ "main": "Resource Manager project resource schema. To use this resource, it is required that you set the service account email in the provider configuration.", "id": "Terraform's internal resource ID. It is structured as \"`container_id`\".", - "project_id": "Project ID. It is an UUID.", + "project_id": "Project UUID identifier. This is the ID that can be used in most of the other resources to identify the project.", "container_id": "Project container ID. Globally unique, user-friendly identifier.", - "parent_container_id": "Parent resource container ID. 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.", "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 project. This value is only considered during creation. Changing it afterwards will have no effect.", @@ -402,7 +402,7 @@ func mapFields(ctx context.Context, projectResp *resourcemanager.ProjectResponse model.ProjectId = types.StringValue(projectId) model.ContainerId = types.StringValue(containerId) if projectResp.Parent != nil { - if _, err := uuid.Parse(model.ContainerParentId.ValueString()); err != nil { + if _, err := uuid.Parse(model.ContainerParentId.ValueString()); err == nil { // the provided containerParentId is the UUID identifier model.ContainerParentId = types.StringPointerValue(projectResp.Parent.Id) } else { diff --git a/stackit/internal/services/resourcemanager/resourcemanager_acc_test.go b/stackit/internal/services/resourcemanager/resourcemanager_acc_test.go index c37631f4..81b7a333 100644 --- a/stackit/internal/services/resourcemanager/resourcemanager_acc_test.go +++ b/stackit/internal/services/resourcemanager/resourcemanager_acc_test.go @@ -19,6 +19,7 @@ import ( var projectResource = map[string]string{ "name": fmt.Sprintf("acc-pj-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)), "parent_container_id": testutil.TestProjectParentContainerID, + "parent_uuid": testutil.TestProjectParentUUID, "billing_reference": "TEST-REF", "new_label": "a-label", } @@ -29,16 +30,22 @@ func resourceConfig(name string, label *string) string { labelConfig = fmt.Sprintf("new_label = %q", *label) } return fmt.Sprintf(` - %s + %[1]s - resource "stackit_resourcemanager_project" "project" { - parent_container_id = "%s" - name = "%s" + resource "stackit_resourcemanager_project" "project_container_id" { + parent_container_id = "%[2]s" + name = "%[3]s" labels = { - "billing_reference" = "%s" - %s + "billing_reference" = "%[4]s" + %[5]s } - owner_email = "%s" + owner_email = "%[6]s" + } + + resource "stackit_resourcemanager_project" "project_uuid" { + parent_container_id = "%[7]s" + name = "%[3]s-uuid" + owner_email = "%[6]s" } `, testutil.ResourceManagerProviderConfig(), @@ -47,6 +54,7 @@ func resourceConfig(name string, label *string) string { projectResource["billing_reference"], labelConfig, testutil.TestProjectServiceAccountEmail, + projectResource["parent_uuid"], ) } @@ -59,13 +67,19 @@ func TestAccResourceManagerResource(t *testing.T) { { Config: resourceConfig(projectResource["name"], nil), Check: resource.ComposeAggregateTestCheckFunc( - // Project data - resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project", "container_id"), - resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project", "project_id"), - resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "name", projectResource["name"]), - resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "parent_container_id", projectResource["parent_container_id"]), - resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "labels.%", "1"), - resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "labels.billing_reference", projectResource["billing_reference"]), + // Parent container id project data + resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project_container_id", "container_id"), + resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project_container_id", "project_id"), + resource.TestCheckResourceAttr("stackit_resourcemanager_project.project_container_id", "name", projectResource["name"]), + resource.TestCheckResourceAttr("stackit_resourcemanager_project.project_container_id", "parent_container_id", projectResource["parent_container_id"]), + resource.TestCheckResourceAttr("stackit_resourcemanager_project.project_container_id", "labels.%", "1"), + resource.TestCheckResourceAttr("stackit_resourcemanager_project.project_container_id", "labels.billing_reference", projectResource["billing_reference"]), + + // Parent UUID project data + resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project_uuid", "container_id"), + resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project_uuid", "project_id"), + resource.TestCheckResourceAttr("stackit_resourcemanager_project.project_uuid", "name", fmt.Sprintf("%s-uuid", projectResource["name"])), + resource.TestCheckResourceAttr("stackit_resourcemanager_project.project_uuid", "parent_container_id", projectResource["parent_uuid"]), ), }, // Data source @@ -73,21 +87,48 @@ func TestAccResourceManagerResource(t *testing.T) { Config: fmt.Sprintf(` %s - data "stackit_resourcemanager_project" "project" { - container_id = stackit_resourcemanager_project.project.container_id - }`, + data "stackit_resourcemanager_project" "project_container" { + container_id = stackit_resourcemanager_project.project_container_id.container_id + } + + data "stackit_resourcemanager_project" "project_uuid" { + project_id = stackit_resourcemanager_project.project_container_id.project_id + } + + data "stackit_resourcemanager_project" "project_both" { + container_id = stackit_resourcemanager_project.project_container_id.container_id + project_id = stackit_resourcemanager_project.project_container_id.project_id + } + `, resourceConfig(projectResource["name"], nil), ), Check: resource.ComposeAggregateTestCheckFunc( - // Project data - resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project", "id"), - resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project", "container_id"), - resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project", "name", projectResource["name"]), - resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project", "parent_container_id"), - resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project", "labels.%", "1"), - resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project", "labels.billing_reference", projectResource["billing_reference"]), - resource.TestCheckResourceAttrPair("data.stackit_resourcemanager_project.project", "project_id", - "stackit_resourcemanager_project.project", "project_id"), + // Container project data + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_container", "id"), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_container", "container_id"), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_container", "project_id"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_container", "name", projectResource["name"]), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_container", "parent_container_id"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_container", "labels.%", "1"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_container", "labels.billing_reference", projectResource["billing_reference"]), + + // UUID project data + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_uuid", "id"), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_uuid", "container_id"), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_uuid", "project_id"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_uuid", "name", projectResource["name"]), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_uuid", "parent_container_id"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_uuid", "labels.%", "1"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_uuid", "labels.billing_reference", projectResource["billing_reference"]), + + // Both project data + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_both", "id"), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_both", "container_id"), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_both", "project_id"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_both", "name", projectResource["name"]), + resource.TestCheckResourceAttrSet("data.stackit_resourcemanager_project.project_both", "parent_container_id"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_both", "labels.%", "1"), + resource.TestCheckResourceAttr("data.stackit_resourcemanager_project.project_both", "labels.billing_reference", projectResource["billing_reference"]), ), }, // Import @@ -117,7 +158,6 @@ func TestAccResourceManagerResource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( // Project data resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project", "container_id"), - resource.TestCheckResourceAttrSet("stackit_resourcemanager_project.project", "project_id"), resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "name", fmt.Sprintf("%s-new", projectResource["name"])), resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "parent_container_id", projectResource["parent_container_id"]), resource.TestCheckResourceAttr("stackit_resourcemanager_project.project", "labels.%", "2"), diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 7364c1fe..d9ec893b 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -30,8 +30,10 @@ var ( // ProjectId is the id of project used for tests ProjectId = os.Getenv("TF_ACC_PROJECT_ID") - // TestProjectParentContainerID is the container id of the organization under which projects are created as part of the resource-manager acceptance tests + // TestProjectParentContainerID is the container id of the parent resource under which projects are created as part of the resource-manager acceptance tests TestProjectParentContainerID = os.Getenv("TF_ACC_TEST_PROJECT_PARENT_CONTAINER_ID") + // TestProjectParentContainerID is the uuid of the parent resource under which projects are created as part of the resource-manager acceptance tests + TestProjectParentUUID = os.Getenv("TF_ACC_TEST_PROJECT_PARENT_UUID") // TestProjectServiceAccountEmail is the e-mail of a service account with admin permissions on the organization under which projects are created as part of the resource-manager acceptance tests TestProjectServiceAccountEmail = os.Getenv("TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_EMAIL")