diff --git a/examples/resources/biganimal_project/resource.tf b/examples/resources/biganimal_project/resource.tf index 63145819..5e524ea1 100644 --- a/examples/resources/biganimal_project/resource.tf +++ b/examples/resources/biganimal_project/resource.tf @@ -17,6 +17,15 @@ resource "random_pet" "project_name" { resource "biganimal_project" "this" { project_name = format("TF %s", title(random_pet.project_name.id)) + #tags = [ + # { + # tag_name = "ex-tag-name-1" + # color = "blue" + # }, + # { + # tag_name = "ex-tag-name-2" + # }, + #] } output "project_name" { diff --git a/pkg/api/project_client.go b/pkg/api/project_client.go index eac1a965..e3ea150d 100644 --- a/pkg/api/project_client.go +++ b/pkg/api/project_client.go @@ -23,14 +23,18 @@ func NewProjectClient(api API) *ProjectClient { return &c } -func (c ProjectClient) Create(ctx context.Context, projectName string) (string, error) { +func (c ProjectClient) Create(ctx context.Context, model any) (string, error) { response := struct { Data struct { ProjectId string `json:"projectId"` } `json:"data"` }{} - project := map[string]string{"projectName": projectName} + projectRs := model.(models.Project) + project := map[string]interface{}{ + "projectName": projectRs.ProjectName, + "tags": projectRs.Tags, + } b, err := json.Marshal(project) if err != nil { @@ -83,14 +87,19 @@ func (c ProjectClient) List(ctx context.Context, query string) ([]*models.Projec return response.Data, err } -func (c ProjectClient) Update(ctx context.Context, projectId, projectName string) (string, error) { +func (c ProjectClient) Update(ctx context.Context, projectId, model any) (string, error) { response := struct { Data struct { ProjectId string `json:"projectId"` } `json:"data"` }{} - project := map[string]string{"projectName": projectName} + projectRs := model.(models.Project) + project := map[string]interface{}{ + "projectName": projectRs.ProjectName, + "tags": projectRs.Tags, + } + b, err := json.Marshal(project) if err != nil { return "", err diff --git a/pkg/models/project.go b/pkg/models/project.go index 3bedf48c..299da15d 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -1,5 +1,7 @@ package models +import commonApi "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models/common/api" + type CloudProvider struct { CloudProviderId string `json:"cloudProviderId,omitempty" tfsdk:"cloud_provider_id"` CloudProviderName string `json:"cloudProviderName,omitempty" tfsdk:"cloud_provider_name"` @@ -11,6 +13,7 @@ type Project struct { UserCount int `json:"userCount,omitempty" tfsdk:"user_count"` ClusterCount int `json:"clusterCount,omitempty" tfsdk:"cluster_count"` CloudProviders []CloudProvider `json:"cloudProviders" tfsdk:"cloud_providers"` + Tags []commonApi.Tag `json:"tags,omitempty"` } // Check the return value, if ProjectName is also needed diff --git a/pkg/plan_modifier/assign_tags.go b/pkg/plan_modifier/assign_tags.go index 9577fbb4..9a300add 100644 --- a/pkg/plan_modifier/assign_tags.go +++ b/pkg/plan_modifier/assign_tags.go @@ -36,14 +36,14 @@ func (m assignTagsModifier) PlanModifySet(ctx context.Context, req planmodifier. return } - // This is on update and tags are not set in config so just plan for state + // Below is everything else ie update with tags set in config. + + // This is on update and tags are not set in config so just use state as plan if req.PlanValue.IsUnknown() { resp.PlanValue = basetypes.NewSetValueMust(req.ConfigValue.ElementType(ctx), state.Elements()) return } - // This is for anything else ie update with tags set in config. - // merge plan into newPlan (plan is from config) and merge state in newPlan (state is from read) newPlan := state.Elements() diff --git a/pkg/provider/common.go b/pkg/provider/common.go new file mode 100644 index 00000000..212db12e --- /dev/null +++ b/pkg/provider/common.go @@ -0,0 +1,33 @@ +package provider + +import ( + commonApi "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models/common/api" + commonTerraform "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models/common/terraform" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// build tag assign terraform resource as, using api response as input +func buildTFRsrcAssignTagsAs(tfRsrcTagsOut *[]commonTerraform.Tag, apiRespTags []commonApi.Tag) { + *tfRsrcTagsOut = []commonTerraform.Tag{} + for _, v := range apiRespTags { + *tfRsrcTagsOut = append(*tfRsrcTagsOut, commonTerraform.Tag{ + TagId: types.StringValue(v.TagId), + TagName: types.StringValue(v.TagName), + Color: basetypes.NewStringPointerValue(v.Color), + }) + } +} + +// build tag assign request using terraform resource as input +func buildAPIReqAssignTags(tfRsrcTags []commonTerraform.Tag) []commonApi.Tag { + tags := []commonApi.Tag{} + for _, tag := range tfRsrcTags { + tags = append(tags, commonApi.Tag{ + Color: tag.Color.ValueStringPointer(), + TagId: tag.TagId.ValueString(), + TagName: tag.TagName.ValueString(), + }) + } + return tags +} diff --git a/pkg/provider/resource_cluster.go b/pkg/provider/resource_cluster.go index 5eb9cc0a..531469ad 100644 --- a/pkg/provider/resource_cluster.go +++ b/pkg/provider/resource_cluster.go @@ -963,7 +963,7 @@ func readCluster(ctx context.Context, client *api.ClusterClient, tfClusterResour } } - buildTFRsrcAssignTagsAs(&tfClusterResource.Tags, &responseCluster.Tags) + buildTFRsrcAssignTagsAs(&tfClusterResource.Tags, responseCluster.Tags) if responseCluster.EncryptionKeyResp != nil { tfClusterResource.TransparentDataEncryption = &TransparentDataEncryptionModel{} @@ -1221,26 +1221,3 @@ func StringSliceToSet(items *[]string) types.Set { return types.SetValueMust(types.StringType, eles) } - -func buildTFRsrcAssignTagsAs(tfRsrcTags *[]commonTerraform.Tag, apiRespTags *[]commonApi.Tag) { - *tfRsrcTags = []commonTerraform.Tag{} - for _, v := range *apiRespTags { - *tfRsrcTags = append(*tfRsrcTags, commonTerraform.Tag{ - TagId: types.StringValue(v.TagId), - TagName: types.StringValue(v.TagName), - Color: basetypes.NewStringPointerValue(v.Color), - }) - } -} - -func buildAPIReqAssignTags(tfRsrcTags []commonTerraform.Tag) []commonApi.Tag { - tags := []commonApi.Tag{} - for _, tag := range tfRsrcTags { - tags = append(tags, commonApi.Tag{ - Color: tag.Color.ValueStringPointer(), - TagId: tag.TagId.ValueString(), - TagName: tag.TagName.ValueString(), - }) - } - return tags -} diff --git a/pkg/provider/resource_pgd.go b/pkg/provider/resource_pgd.go index a9b11cdc..0191b6ae 100644 --- a/pkg/provider/resource_pgd.go +++ b/pkg/provider/resource_pgd.go @@ -857,7 +857,7 @@ func (p pgdResource) Read(ctx context.Context, req resource.ReadRequest, resp *r state.ClusterId = clusterResp.ClusterId state.ClusterName = clusterResp.ClusterName - buildTFRsrcAssignTagsAs(&state.Tags, &clusterResp.Tags) + buildTFRsrcAssignTagsAs(&state.Tags, clusterResp.Tags) buildTFGroupsAs(ctx, &resp.Diagnostics, resp.State, *clusterResp, &state) if resp.Diagnostics.HasError() { @@ -1190,7 +1190,7 @@ func (p *pgdResource) retryFuncAs(ctx context.Context, diags *diag.Diagnostics, return retry.RetryableError(errors.New("instance not yet ready")) } - buildTFRsrcAssignTagsAs(&outPgdTfResource.Tags, &pgdResp.Tags) + buildTFRsrcAssignTagsAs(&outPgdTfResource.Tags, pgdResp.Tags) return nil } diff --git a/pkg/provider/resource_project.go b/pkg/provider/resource_project.go index db9ef614..fc52372c 100644 --- a/pkg/provider/resource_project.go +++ b/pkg/provider/resource_project.go @@ -6,6 +6,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/api" + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models" + commonTerraform "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/models/common/terraform" + "github.com/EnterpriseDB/terraform-provider-biganimal/pkg/plan_modifier" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" @@ -94,6 +97,36 @@ func (p projectResource) Schema(ctx context.Context, req resource.SchemaRequest, }, }, }, + "tags": schema.SetNestedAttribute{ + Description: "Assign existing tags or create tags to assign to this resource", + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "tag_id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "tag_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "color": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + PlanModifiers: []planmodifier.Set{ + plan_modifier.CustomAssignTags(), + }, + }, }, } } @@ -113,12 +146,13 @@ type cloudProvider struct { } type Project struct { - ID *string `tfsdk:"id"` - ProjectID *string `tfsdk:"project_id"` - ProjectName *string `tfsdk:"project_name"` - UserCount *int `tfsdk:"user_count"` - ClusterCount *int `tfsdk:"cluster_count"` - CloudProviders []cloudProvider `tfsdk:"cloud_providers"` + ID *string `tfsdk:"id"` + ProjectID *string `tfsdk:"project_id"` + ProjectName *string `tfsdk:"project_name"` + UserCount *int `tfsdk:"user_count"` + ClusterCount *int `tfsdk:"cluster_count"` + CloudProviders []cloudProvider `tfsdk:"cloud_providers"` + Tags []commonTerraform.Tag `tfsdk:"tags"` } // Create creates the resource and sets the initial Terraform state. @@ -130,7 +164,12 @@ func (p projectResource) Create(ctx context.Context, req resource.CreateRequest, return } - projectId, err := p.client.Create(ctx, *config.ProjectName) + projectReqModel := models.Project{ + ProjectName: *config.ProjectName, + Tags: buildAPIReqAssignTags(config.Tags), + } + + projectId, err := p.client.Create(ctx, projectReqModel) if err != nil { resp.Diagnostics.AddError("Error creating project", "Could not create project, unexpected error: "+err.Error()) return @@ -154,6 +193,8 @@ func (p projectResource) Create(ctx context.Context, req resource.CreateRequest, }) } + buildTFRsrcAssignTagsAs(&config.Tags, project.Tags) + diags = resp.State.Set(ctx, &config) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -188,6 +229,8 @@ func (p projectResource) Read(ctx context.Context, req resource.ReadRequest, res }) } + buildTFRsrcAssignTagsAs(&state.Tags, project.Tags) + diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -204,12 +247,19 @@ func (p projectResource) Update(ctx context.Context, req resource.UpdateRequest, return } - _, err := p.client.Update(ctx, *plan.ProjectID, *plan.ProjectName) + projectReqModel := models.Project{ + ProjectName: *plan.ProjectName, + Tags: buildAPIReqAssignTags(plan.Tags), + } + + _, err := p.client.Update(ctx, *plan.ProjectID, projectReqModel) if err != nil { resp.Diagnostics.AddError("Error updating project", "Could not update project, unexpected error: "+err.Error()) return } + buildTFRsrcAssignTagsAs(&plan.Tags, projectReqModel.Tags) + diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() {