From e72be7fe5deca4199372ba06922979f5360948ad Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Thu, 28 May 2026 18:38:09 +0200 Subject: [PATCH 1/2] feat(resource manager): fix labels relates to STACKITTPR-660 --- .../resourcemanager/folder/resource.go | 37 +++++++------------ .../resourcemanager/folder/resource_test.go | 4 +- .../resourcemanager/project/resource.go | 34 +++++++---------- .../resourcemanager/project/resource_test.go | 4 +- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/stackit/internal/services/resourcemanager/folder/resource.go b/stackit/internal/services/resourcemanager/folder/resource.go index f1109e451..f062c6972 100644 --- a/stackit/internal/services/resourcemanager/folder/resource.go +++ b/stackit/internal/services/resourcemanager/folder/resource.go @@ -21,7 +21,6 @@ import ( "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" resourcemanager "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager/v0api" @@ -202,7 +201,7 @@ func (r *folderResource) Create(ctx context.Context, req resource.CreateRequest, ctx = tflog.SetField(ctx, "folder_name", folderName) // Generate API request body from model - payload, err := toCreatePayload(&model) + payload, err := toCreatePayload(ctx, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating folder", fmt.Sprintf("Creating API payload: %v", err)) return @@ -313,7 +312,7 @@ func (r *folderResource) Update(ctx context.Context, req resource.UpdateRequest, ctx = tflog.SetField(ctx, "container_id", containerId) // Generate API request body from model - payload, err := toUpdatePayload(&model) + payload, err := toUpdatePayload(ctx, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating folder", fmt.Sprintf("Creating API payload: %v", err)) return @@ -432,15 +431,9 @@ func mapFolderFields( 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) + labels, err := utils.MapLabels(ctx, folderGetResponse.Labels, model.Labels) + if err != nil { + return err } var containerParentIdTF types.String @@ -457,7 +450,7 @@ func mapFolderFields( model.ContainerId = types.StringValue(containerId) model.ContainerParentId = containerParentIdTF model.Name = types.StringValue(folderGetResponse.Name) - model.Labels = tfLabels + model.Labels = labels model.CreationTime = types.StringValue(folderGetResponse.CreationTime.Format(time.RFC3339)) model.UpdateTime = types.StringValue(folderGetResponse.UpdateTime.Format(time.RFC3339)) @@ -495,7 +488,7 @@ func toMembersPayload(model *ResourceModel) ([]resourcemanager.Member, error) { }, nil } -func toCreatePayload(model *ResourceModel) (*resourcemanager.CreateFolderPayload, error) { +func toCreatePayload(ctx context.Context, model *ResourceModel) (*resourcemanager.CreateFolderPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } @@ -505,34 +498,32 @@ func toCreatePayload(model *ResourceModel) (*resourcemanager.CreateFolderPayload return nil, fmt.Errorf("processing members: %w", err) } - modelLabels := model.Labels.Elements() - labels, err := conversion.ToOptStringMap(modelLabels) + labels, err := utils.LabelsToPayload(ctx, model.Labels) if err != nil { - return nil, fmt.Errorf("converting to Go map: %w", err) + return nil, err } return &resourcemanager.CreateFolderPayload{ ContainerParentId: model.ContainerParentId.ValueString(), - Labels: labels, + Labels: &labels, Members: members, Name: model.Name.ValueString(), }, nil } -func toUpdatePayload(model *ResourceModel) (*resourcemanager.PartialUpdateFolderPayload, error) { +func toUpdatePayload(ctx context.Context, model *ResourceModel) (*resourcemanager.PartialUpdateFolderPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } - modelLabels := model.Labels.Elements() - labels, err := conversion.ToOptStringMap(modelLabels) + labels, err := utils.LabelsToPayload(ctx, model.Labels) if err != nil { - return nil, fmt.Errorf("converting to GO map: %w", err) + return nil, err } return &resourcemanager.PartialUpdateFolderPayload{ ContainerParentId: conversion.StringValueToPointer(model.ContainerParentId), Name: conversion.StringValueToPointer(model.Name), - Labels: labels, + Labels: &labels, }, nil } diff --git a/stackit/internal/services/resourcemanager/folder/resource_test.go b/stackit/internal/services/resourcemanager/folder/resource_test.go index 91d7efc98..c6f14a1e1 100644 --- a/stackit/internal/services/resourcemanager/folder/resource_test.go +++ b/stackit/internal/services/resourcemanager/folder/resource_test.go @@ -248,7 +248,7 @@ func TestToCreatePayload(t *testing.T) { tt.input.Labels = convertedLabels } } - output, err := toCreatePayload(tt.input) + output, err := toCreatePayload(context.Background(), tt.input) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } @@ -328,7 +328,7 @@ func TestToUpdatePayload(t *testing.T) { tt.input.Labels = convertedLabels } } - output, err := toUpdatePayload(tt.input) + output, err := toUpdatePayload(context.Background(), tt.input) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/resourcemanager/project/resource.go b/stackit/internal/services/resourcemanager/project/resource.go index a7c0ead2e..12697bbbe 100644 --- a/stackit/internal/services/resourcemanager/project/resource.go +++ b/stackit/internal/services/resourcemanager/project/resource.go @@ -18,7 +18,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -204,7 +203,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest ctx = tflog.SetField(ctx, "project_container_id", containerId) // Generate API request body from model - payload, err := toCreatePayload(&model) + payload, err := toCreatePayload(ctx, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating project", fmt.Sprintf("Creating API payload: %v", err)) return @@ -312,7 +311,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest ctx = tflog.SetField(ctx, "container_id", containerId) // Generate API request body from model - payload, err := toUpdatePayload(&model) + payload, err := toUpdatePayload(ctx, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating project", fmt.Sprintf("Creating API payload: %v", err)) return @@ -430,14 +429,9 @@ func mapProjectFields(ctx context.Context, projectResp *resourcemanager.GetProje return fmt.Errorf("container id not present") } - var labels basetypes.MapValue - if projectResp.Labels != nil && len(*projectResp.Labels) != 0 { - labels, err = conversion.ToTerraformStringMap(ctx, *projectResp.Labels) - if err != nil { - return fmt.Errorf("converting to StringValue map: %w", err) - } - } else { - labels = types.MapNull(types.StringType) + labels, err := utils.MapLabels(ctx, projectResp.Labels, model.Labels) + if err != nil { + return err } var containerParentIdTF types.String @@ -492,7 +486,7 @@ func toMembersPayload(model *ResourceModel) ([]resourcemanager.Member, error) { }, nil } -func toCreatePayload(model *ResourceModel) (*resourcemanager.CreateProjectPayload, error) { +func toCreatePayload(ctx context.Context, model *ResourceModel) (*resourcemanager.CreateProjectPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } @@ -502,34 +496,32 @@ func toCreatePayload(model *ResourceModel) (*resourcemanager.CreateProjectPayloa return nil, fmt.Errorf("processing members: %w", err) } - modelLabels := model.Labels.Elements() - labels, err := conversion.ToOptStringMap(modelLabels) + labels, err := utils.LabelsToPayload(ctx, model.Labels) if err != nil { - return nil, fmt.Errorf("converting to Go map: %w", err) + return nil, err } return &resourcemanager.CreateProjectPayload{ ContainerParentId: model.ContainerParentId.ValueString(), - Labels: labels, + Labels: &labels, Members: members, Name: model.Name.ValueString(), }, nil } -func toUpdatePayload(model *ResourceModel) (*resourcemanager.PartialUpdateProjectPayload, error) { +func toUpdatePayload(ctx context.Context, model *ResourceModel) (*resourcemanager.PartialUpdateProjectPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } - modelLabels := model.Labels.Elements() - labels, err := conversion.ToOptStringMap(modelLabels) + labels, err := utils.LabelsToPayload(ctx, model.Labels) if err != nil { - return nil, fmt.Errorf("converting to GO map: %w", err) + return nil, err } return &resourcemanager.PartialUpdateProjectPayload{ ContainerParentId: conversion.StringValueToPointer(model.ContainerParentId), Name: conversion.StringValueToPointer(model.Name), - Labels: labels, + Labels: &labels, }, nil } diff --git a/stackit/internal/services/resourcemanager/project/resource_test.go b/stackit/internal/services/resourcemanager/project/resource_test.go index c082bfcad..0e9be99cc 100644 --- a/stackit/internal/services/resourcemanager/project/resource_test.go +++ b/stackit/internal/services/resourcemanager/project/resource_test.go @@ -248,7 +248,7 @@ func TestToCreatePayload(t *testing.T) { tt.input.Labels = convertedLabels } } - output, err := toCreatePayload(tt.input) + output, err := toCreatePayload(context.Background(), tt.input) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } @@ -328,7 +328,7 @@ func TestToUpdatePayload(t *testing.T) { tt.input.Labels = convertedLabels } } - output, err := toUpdatePayload(tt.input) + output, err := toUpdatePayload(context.Background(), tt.input) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } From 089b34ea450539a1055054e25fc4569f6b013816 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Thu, 28 May 2026 19:14:47 +0200 Subject: [PATCH 2/2] feat(intake): fix runner labels relates to STACKITTPR-660 --- .../services/intake/runner/datasource.go | 2 +- .../services/intake/runner/resource.go | 53 +++++++------------ .../services/intake/runner/resource_test.go | 7 +-- 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/stackit/internal/services/intake/runner/datasource.go b/stackit/internal/services/intake/runner/datasource.go index f0f92ede9..2f3197b46 100644 --- a/stackit/internal/services/intake/runner/datasource.go +++ b/stackit/internal/services/intake/runner/datasource.go @@ -155,7 +155,7 @@ func (r *runnerDataSource) Read(ctx context.Context, req datasource.ReadRequest, ctx = core.LogResponse(ctx) - err = mapFields(runnerResp, &model, region) + err = mapFields(ctx, runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/intake/runner/resource.go b/stackit/internal/services/intake/runner/resource.go index 98459c5d1..e6cb5c889 100644 --- a/stackit/internal/services/intake/runner/resource.go +++ b/stackit/internal/services/intake/runner/resource.go @@ -209,7 +209,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, ctx = tflog.SetField(ctx, "region", region) // prepare the payload struct for the create bar request - payload, err := toCreatePayload(&model) + payload, err := toCreatePayload(ctx, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Creating API payload: %v", err)) return @@ -240,7 +240,7 @@ func (r *runnerResource) Create(ctx context.Context, req resource.CreateRequest, return } - err = mapFields(runnerResp, &model, region) + err = mapFields(ctx, runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating runner", fmt.Sprintf("Processing API payload: %v", err)) return @@ -285,7 +285,7 @@ func (r *runnerResource) Read(ctx context.Context, req resource.ReadRequest, res ctx = core.LogResponse(ctx) // Map response body to schema - err = mapFields(runnerResp, &model, region) + err = mapFields(ctx, runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading runner", fmt.Sprintf("Processing API payload: %v", err)) return @@ -317,7 +317,7 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, ctx = tflog.SetField(ctx, "runner_id", runnerId) ctx = tflog.SetField(ctx, "region", region) - payload, err := toUpdatePayload(&model, &state) + payload, err := toUpdatePayload(ctx, &model, &state) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Creating API payload: %v", err)) return @@ -340,7 +340,7 @@ func (r *runnerResource) Update(ctx context.Context, req resource.UpdateRequest, } // Map response body to schema - err = mapFields(runnerResp, &model, region) + err = mapFields(ctx, runnerResp, &model, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating runner", fmt.Sprintf("Processing API response: %v", err)) return @@ -417,7 +417,7 @@ func (r *runnerResource) ImportState(ctx context.Context, req resource.ImportSta } // Maps runner fields to the provider internal model -func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region string) error { +func mapFields(ctx context.Context, runnerResp *intake.IntakeRunnerResponse, model *Model, region string) error { if runnerResp == nil { return fmt.Errorf("response input is nil") } @@ -431,23 +431,16 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str runnerResp.Id, ) - model.RunnerId = types.StringValue(runnerResp.Id) - - if len(runnerResp.Labels) == 0 { - model.Labels = types.MapNull(types.StringType) - } else { - labels, diags := types.MapValueFrom(context.Background(), types.StringType, runnerResp.Labels) - if diags.HasError() { - return fmt.Errorf("converting labels: %w", core.DiagsToError(diags)) - } - model.Labels = labels + labels, err := utils.MapLabels(ctx, &runnerResp.Labels, model.Labels) + if err != nil { + return err } + model.RunnerId = types.StringValue(runnerResp.Id) model.Name = types.StringValue(runnerResp.DisplayName) - + model.Labels = labels model.Description = types.StringPointerValue(runnerResp.Description) model.Region = types.StringValue(region) - model.MaxMessageSizeKiB = types.Int32Value(runnerResp.MaxMessageSizeKiB) model.MaxMessagesPerHour = types.Int32Value(runnerResp.MaxMessagesPerHour) @@ -455,17 +448,14 @@ func mapFields(runnerResp *intake.IntakeRunnerResponse, model *Model, region str } // Build CreateIntakeRunnerPayload from provider's model -func toCreatePayload(model *Model) (*intake.CreateIntakeRunnerPayload, error) { +func toCreatePayload(ctx context.Context, model *Model) (*intake.CreateIntakeRunnerPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } - var labels map[string]string - if !model.Labels.IsNull() && !model.Labels.IsUnknown() { - diags := model.Labels.ElementsAs(context.Background(), &labels, false) - if diags.HasError() { - return nil, fmt.Errorf("converting labels: %w", core.DiagsToError(diags)) - } + labels, err := utils.LabelsToPayload(ctx, model.Labels) + if err != nil { + return nil, err } return &intake.CreateIntakeRunnerPayload{ @@ -478,7 +468,7 @@ func toCreatePayload(model *Model) (*intake.CreateIntakeRunnerPayload, error) { } // Build UpdateIntakeRunnerPayload from provider's model -func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, error) { +func toUpdatePayload(ctx context.Context, model, state *Model) (*intake.UpdateIntakeRunnerPayload, error) { if model == nil { return nil, fmt.Errorf("model is nil") } @@ -499,14 +489,11 @@ func toUpdatePayload(model, state *Model) (*intake.UpdateIntakeRunnerPayload, er payload.DisplayName = conversion.StringValueToPointer(model.Name) payload.Description = conversion.StringValueToPointer(model.Description) - var labels map[string]string - if !model.Labels.IsNull() && !model.Labels.IsUnknown() { - diags := model.Labels.ElementsAs(context.Background(), &labels, false) - if diags.HasError() { - return nil, fmt.Errorf("failed to convert labels: %w", core.DiagsToError(diags)) - } - payload.Labels = labels + labels, err := utils.LabelsToPayload(ctx, model.Labels) + if err != nil { + return nil, err } + payload.Labels = labels return payload, nil } diff --git a/stackit/internal/services/intake/runner/resource_test.go b/stackit/internal/services/intake/runner/resource_test.go index e8e1f869b..618f56dde 100644 --- a/stackit/internal/services/intake/runner/resource_test.go +++ b/stackit/internal/services/intake/runner/resource_test.go @@ -1,6 +1,7 @@ package runner import ( + "context" "fmt" "testing" @@ -93,7 +94,7 @@ func TestMapFields(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - err := mapFields(tt.input, tt.model, tt.region) + err := mapFields(context.Background(), tt.input, tt.model, tt.region) if (err != nil) != tt.wantErr { t.Errorf("mapFields error = %v, wantErr %v", err, tt.wantErr) return @@ -153,7 +154,7 @@ func TestToCreatePayload(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - payload, err := toCreatePayload(tt.model) + payload, err := toCreatePayload(context.Background(), tt.model) if (err != nil) != tt.wantErr { t.Errorf("toCreatePayload error = %v, wantErr %v", err, tt.wantErr) return @@ -231,7 +232,7 @@ func TestToUpdatePayload(t *testing.T) { } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - payload, err := toUpdatePayload(tt.model, tt.state) + payload, err := toUpdatePayload(context.Background(), tt.model, tt.state) if (err != nil) != tt.wantErr { t.Errorf("toUpdatePayload error = %v, wantErr %v", err, tt.wantErr) return