From a39fce36d2000674414d5f917a905aa0f48d42a0 Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Mon, 11 May 2026 21:49:54 +0200 Subject: [PATCH 1/9] feat: TelemetryLink resources and datasources --- .../stackit_telemetrylink_link/data-source.tf | 5 + .../stackit_telemetrylink_link/resource.tf | 24 + go.mod | 1 + go.sum | 2 + stackit/internal/core/core.go | 1 + .../services/telemetrylink/link/datasource.go | 212 ++++++ .../telemetrylink/link/datasource_test.go | 86 +++ .../services/telemetrylink/link/resource.go | 644 ++++++++++++++++++ .../telemetrylink/link/resource_test.go | 264 +++++++ .../telemetrylink/telemetrylink_acc_test.go | 346 ++++++++++ .../telemetrylink/testdata/resource-max.tf | 18 + .../telemetrylink/testdata/resource-min.tf | 16 + .../services/telemetrylink/utils/utils.go | 30 + .../telemetrylink/utils/utils_test.go | 94 +++ stackit/internal/testutil/testutil.go | 2 + stackit/provider.go | 10 + 16 files changed, 1755 insertions(+) create mode 100644 examples/data-sources/stackit_telemetrylink_link/data-source.tf create mode 100644 examples/resources/stackit_telemetrylink_link/resource.tf create mode 100644 stackit/internal/services/telemetrylink/link/datasource.go create mode 100644 stackit/internal/services/telemetrylink/link/datasource_test.go create mode 100644 stackit/internal/services/telemetrylink/link/resource.go create mode 100644 stackit/internal/services/telemetrylink/link/resource_test.go create mode 100644 stackit/internal/services/telemetrylink/telemetrylink_acc_test.go create mode 100644 stackit/internal/services/telemetrylink/testdata/resource-max.tf create mode 100644 stackit/internal/services/telemetrylink/testdata/resource-min.tf create mode 100644 stackit/internal/services/telemetrylink/utils/utils.go create mode 100644 stackit/internal/services/telemetrylink/utils/utils_test.go diff --git a/examples/data-sources/stackit_telemetrylink_link/data-source.tf b/examples/data-sources/stackit_telemetrylink_link/data-source.tf new file mode 100644 index 000000000..199951f41 --- /dev/null +++ b/examples/data-sources/stackit_telemetrylink_link/data-source.tf @@ -0,0 +1,5 @@ +data "stackit_telemetrylink_link" "link" { + resource_type = "project" + resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" +} \ No newline at end of file diff --git a/examples/resources/stackit_telemetrylink_link/resource.tf b/examples/resources/stackit_telemetrylink_link/resource.tf new file mode 100644 index 000000000..adeb60f44 --- /dev/null +++ b/examples/resources/stackit_telemetrylink_link/resource.tf @@ -0,0 +1,24 @@ +resource "stackit_telemetrylink_link" "link" { + resource_type = "project" + resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "telemetrylink-example" + access_token = "eyJxxx" + telemetry_router_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +resource "stackit_telemetrylink_link" "link2" { + resource_type = "project" + resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "telemetrylink-example" + description = "telemetrylink description" + access_token = "eyJxxx" + telemetry_router_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} + +# Only use the import statement, if you want to import an existing TelemetryLink link +import { + to = stackit_telemetrylink_link.import-example + id = "${var.resource_type},${var.resource_id},${var.region}" +} diff --git a/go.mod b/go.mod index 207f2cfd4..0c8c5105a 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/sfs v0.9.0 github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0 + github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.1.1 github.com/teambition/rrule-go v1.8.2 golang.org/x/mod v0.35.0 ) diff --git a/go.sum b/go.sum index fba53fc30..61dc7f32a 100644 --- a/go.sum +++ b/go.sum @@ -732,6 +732,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0 h1:Zy3yxmHzW+ydu1nae github.com/stackitcloud/stackit-sdk-go/services/ske v1.14.0/go.mod h1:TbqmZhLMofmfl+HhVl6oHYcI3zvXTm1vRjN3A/fOkM4= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0 h1:angvO3z0TGqZtdwTDsG/tgTw9hxB76A6leUsiUXQtME= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.10.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M= +github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.1.1 h1:+YxN37hx3bj55c/CloXOoQTYLzwLp1Cf0NvkLemZECE= +github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.1.1/go.mod h1:hgw8janWmDfP2bnuZensxqcAePr49BX5ug8Rq85o+h8= github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index f8fa8f9f0..b1d4f78a3 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -71,6 +71,7 @@ type ProviderData struct { ServiceEnablementCustomEndpoint string SfsCustomEndpoint string ServiceAccountCustomEndpoint string + TelemetryLinkCustomEndpoint string EnableBetaResources bool Experiments []string diff --git a/stackit/internal/services/telemetrylink/link/datasource.go b/stackit/internal/services/telemetrylink/link/datasource.go new file mode 100644 index 000000000..ce766d00e --- /dev/null +++ b/stackit/internal/services/telemetrylink/link/datasource.go @@ -0,0 +1,212 @@ +package link + +import ( + "context" + "errors" + "fmt" + "net/http" + + "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/core/oapierror" + + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" + + "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/services/telemetrylink/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &telemetryLinkLinkDataSource{} +) + +func NewTelemetryLinkLinkDataSource() datasource.DataSource { + return &telemetryLinkLinkDataSource{} +} + +type DataSourceModel struct { + ID types.String `tfsdk:"id"` // Required by Terraform + LinkID types.String `tfsdk:"link_id"` + Region types.String `tfsdk:"region"` + ResourceType types.String `tfsdk:"resource_type"` + ResourceID types.String `tfsdk:"resource_id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + TelemetryRouterID types.String `tfsdk:"telemetry_router_id"` + CreateTime types.String `tfsdk:"create_time"` + Status types.String `tfsdk:"status"` +} + +type telemetryLinkLinkDataSource struct { + client *telemetrylink.APIClient + providerData core.ProviderData +} + +func (d *telemetryLinkLinkDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetrylink_link" +} + +func (d *telemetryLinkLinkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + d.providerData = providerData + + apiClient := utils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + d.client = apiClient + tflog.Info(ctx, "TelemetryLink client configured") +} + +func (d *telemetryLinkLinkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryLink Link data source schema. %s", core.DatasourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + }, + "link_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_id"], + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: schemaDescriptions["region"], + // the region cannot be found, so it has to be passed + Optional: true, + }, + "resource_type": schema.StringAttribute{ + Description: schemaDescriptions["resource_type"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(resourceTypes...), + validate.NoSeparator(), + }, + }, + "resource_id": schema.StringAttribute{ + Description: schemaDescriptions["resource_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Computed: true, + Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, + }, + "description": schema.StringAttribute{ + Description: schemaDescriptions["description"], + Computed: true, + }, + "telemetry_router_id": schema.StringAttribute{ + Description: schemaDescriptions["telemetry_router_id"], + Computed: true, + }, + "create_time": schema.StringAttribute{ + Description: schemaDescriptions["create_time"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (d *telemetryLinkLinkDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model DataSourceModel + diags := req.Config.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + resourceType := model.ResourceType.ValueString() + resourceID := model.ResourceID.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "resource_type", resourceType) + ctx = tflog.SetField(ctx, "resource_id", resourceID) + ctx = tflog.SetField(ctx, "region", region) + + var response *telemetrylink.TelemetryLinkResponse + var err error + switch resourceType { + case resourceTypeOrganization: + response, err = d.client.DefaultAPI.GetOrganizationTelemetryLink(ctx, resourceID, region).Execute() + case resourceTypeFolder: + response, err = d.client.DefaultAPI.GetFolderTelemetryLink(ctx, resourceID, region).Execute() + case resourceTypeProject: + response, err = d.client.DefaultAPI.GetProjectTelemetryLink(ctx, resourceID, region).Execute() + } + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapDataSourceFields(ctx, response, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Processing response: %v", err)) + return + } + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "TelemetryLink Link read", map[string]interface{}{ + "resource_type": resourceType, + "resource_id": resourceID, + }) +} + +func mapDataSourceFields(_ context.Context, link *telemetrylink.TelemetryLinkResponse, model *DataSourceModel) error { + if link == nil { + return fmt.Errorf("link is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + var linkID string + if model.LinkID.ValueString() != "" { + linkID = model.LinkID.ValueString() + } else { + linkID = link.Id + } + + model.ID = tfutils.BuildInternalTerraformId(model.ResourceType.ValueString(), model.ResourceID.ValueString(), model.Region.ValueString()) + model.LinkID = types.StringValue(linkID) + model.DisplayName = types.StringValue(link.DisplayName) + model.Description = types.StringPointerValue(link.Description) + model.TelemetryRouterID = types.StringValue(link.TelemetryRouterId) + model.CreateTime = types.StringValue(link.CreateTime.String()) + model.Status = types.StringValue(link.Status) + + return nil +} diff --git a/stackit/internal/services/telemetrylink/link/datasource_test.go b/stackit/internal/services/telemetrylink/link/datasource_test.go new file mode 100644 index 000000000..53ad1cda7 --- /dev/null +++ b/stackit/internal/services/telemetrylink/link/datasource_test.go @@ -0,0 +1,86 @@ +package link + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" +) + +func fixtureDataSourceModel(mods ...func(model *DataSourceModel)) *DataSourceModel { + model := &DataSourceModel{ + ID: types.StringValue("rtp,rid,reg"), + LinkID: types.StringValue("lid"), + Region: types.StringValue("reg"), + ResourceType: types.StringValue("rtp"), + ResourceID: types.StringValue("rid"), + DisplayName: types.StringValue("name"), + Description: types.String{}, + TelemetryRouterID: types.StringValue("tlmrid"), + CreateTime: types.StringValue(testTime.String()), + Status: types.StringValue("active"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapDataSourceFields(t *testing.T) { + tests := []struct { + description string + input *telemetrylink.TelemetryLinkResponse + expected *DataSourceModel + wantErr bool + }{ + { + description: "min values", + input: fixtureLink(), + expected: fixtureDataSourceModel(), + }, + { + description: "max values", + input: fixtureLink(func(link *telemetrylink.TelemetryLinkResponse) { + link.Description = utils.Ptr("description") + link.DisplayName = "display-name" + link.AccessToken = utils.Ptr("access-token") + link.TelemetryRouterId = "tlmr-id" + }), + expected: fixtureDataSourceModel(func(model *DataSourceModel) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.TelemetryRouterID = types.StringValue("tlmr-id") + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureDataSourceModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &DataSourceModel{ + ResourceType: tt.expected.ResourceType, + ResourceID: tt.expected.ResourceID, + Region: tt.expected.Region, + } + err := mapDataSourceFields(context.Background(), tt.input, state) + if tt.wantErr && err == nil { + t.Fatalf("Should have failed") + } + if !tt.wantErr && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if !tt.wantErr { + diff := cmp.Diff(state, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go new file mode 100644 index 000000000..0d44b0474 --- /dev/null +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -0,0 +1,644 @@ +package link + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" + "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi/wait" + + "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/services/telemetrylink/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +const ( + resourceTypeOrganization = "organization" + resourceTypeFolder = "folder" + resourceTypeProject = "project" +) + +var ( + _ resource.Resource = &telemetryLinkInstanceResource{} + _ resource.ResourceWithConfigure = &telemetryLinkInstanceResource{} + _ resource.ResourceWithImportState = &telemetryLinkInstanceResource{} + _ resource.ResourceWithModifyPlan = &telemetryLinkInstanceResource{} + + resourceTypes = []string{resourceTypeOrganization, resourceTypeFolder, resourceTypeProject} +) + +var schemaDescriptions = map[string]string{ + "id": "Terraform's internal resource identifier. It is structured as \"`resource_type`, `resource_id`,`region`\".", + "link_id": "The TelemetryLink ID", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "resource_type": fmt.Sprintf( + "The resource type of the TelemetryLink resource, possible values: %s", + tfutils.FormatPossibleValues(resourceTypes...), + ), + "resource_id": "STACKIT project ID, folder ID, or organization ID associated with the Telemetry Link resource.", + "display_name": "The displayed name of the Telemetry Link resource.", + "description": "The description of the Telemetry Link resource.", + "telemetry_router_id": "The Telemetry Router ID.", + "access_token": "The access token of the Telemetry Router instance.", + "create_time": "The time the Telemetry Link was created.", + "status": fmt.Sprintf( + "The status of the TelemetryLink link, possible values: %s", + tfutils.FormatPossibleValues("active", "inactive", "failed"), + ), +} + +type Model struct { + ID types.String `tfsdk:"id"` // Required by Terraform + LinkID types.String `tfsdk:"link_id"` + Region types.String `tfsdk:"region"` + ResourceType types.String `tfsdk:"resource_type"` + ResourceID types.String `tfsdk:"resource_id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + TelemetryRouterID types.String `tfsdk:"telemetry_router_id"` + AccessToken types.String `tfsdk:"access_token"` + CreateTime types.String `tfsdk:"create_time"` + Status types.String `tfsdk:"status"` +} + +type telemetryLinkInstanceResource struct { + client *telemetrylink.APIClient + providerData core.ProviderData +} + +func NewTelemetryLinkResource() resource.Resource { + return &telemetryLinkInstanceResource{} +} + +func (r *telemetryLinkInstanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := utils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + r.providerData = providerData + tflog.Info(ctx, "TelemetryLink client configured") +} + +func (r *telemetryLinkInstanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel Model + if req.Config.Raw.IsNull() { + return + } + resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) + if resp.Diagnostics.HasError() { + return + } + + var planModel Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) + if resp.Diagnostics.HasError() { + return + } + + tfutils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *telemetryLinkInstanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetrylink_link" +} + +func (r *telemetryLinkInstanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryLink instance resource schema. %s", core.ResourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "link_id": schema.StringAttribute{ + Description: schemaDescriptions["link_id"], + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "region": schema.StringAttribute{ + Description: schemaDescriptions["region"], + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "resource_type": schema.StringAttribute{ + Description: schemaDescriptions["resource_type"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf(resourceTypes...), + validate.NoSeparator(), + }, + }, + "resource_id": schema.StringAttribute{ + Description: schemaDescriptions["resource_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Required: true, + }, + "description": schema.StringAttribute{ + Description: schemaDescriptions["description"], + Optional: true, + }, + "telemetry_router_id": schema.StringAttribute{ + Description: schemaDescriptions["telemetry_router_id"], + Required: true, + }, + "access_token": schema.StringAttribute{ + Description: schemaDescriptions["access_token"], + Optional: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "create_time": schema.StringAttribute{ + Description: schemaDescriptions["create_time"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + resourceType := model.ResourceType.ValueString() + resourceID := model.ResourceID.ValueString() + region := model.Region.ValueString() + ctx = tflog.SetField(ctx, "resource_type", resourceType) + ctx = tflog.SetField(ctx, "resource_id", resourceID) + ctx = tflog.SetField(ctx, "region", region) + + var response *telemetrylink.TelemetryLinkResponse + switch model.ResourceType.ValueString() { + case resourceTypeOrganization: + payload, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + response, err = wait.CreateOrUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + return + } + + case resourceTypeFolder: + payload, err := toCreateOrUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + response, err = wait.CreateOrUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + return + } + case resourceTypeProject: + payload, err := toCreateOrUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + response, err = wait.CreateOrUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + return + } + } + + err := mapFields(ctx, response, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Processing response: %v", err)) + return + } + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "TelemetryLink Link created") +} + +func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + resourceType := model.ResourceType.ValueString() + resourceID := model.ResourceID.ValueString() + region := model.Region.ValueString() + + ctx = tflog.SetField(ctx, "resource_type", resourceType) + ctx = tflog.SetField(ctx, "resource_id", resourceID) + ctx = tflog.SetField(ctx, "region", region) + + var err error + var response *telemetrylink.TelemetryLinkResponse + switch resourceType { + case resourceTypeOrganization: + response, err = r.client.DefaultAPI.GetOrganizationTelemetryLink(ctx, resourceID, region).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + return + } + case resourceTypeFolder: + response, err = r.client.DefaultAPI.GetFolderTelemetryLink(ctx, resourceID, region).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + return + } + case resourceTypeProject: + response, err = r.client.DefaultAPI.GetProjectTelemetryLink(ctx, resourceID, region).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + return + } + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, response, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Processing response: %v", err)) + return + } + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "TelemetryLink link read", map[string]interface{}{ + "resource_type": resourceType, + "resource_id": resourceID, + }) +} + +func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + resourceType := model.ResourceType.ValueString() + resourceID := model.ResourceID.ValueString() + region := model.Region.ValueString() + + ctx = tflog.SetField(ctx, "resource_type", resourceType) + ctx = tflog.SetField(ctx, "resource_id", resourceID) + ctx = tflog.SetField(ctx, "region", region) + + var response *telemetrylink.TelemetryLinkResponse + switch model.ResourceType.ValueString() { + case resourceTypeOrganization: + payload, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + response, err = wait.CreateOrUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + return + } + case resourceTypeFolder: + payload, err := toCreateOrUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + response, err = wait.CreateOrUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + return + } + case resourceTypeProject: + payload, err := toCreateOrUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + response, err = wait.CreateOrUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + return + } + } + + err := mapFields(ctx, response, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink Link", fmt.Sprintf("Processing response: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "TelemetryLink Link updated", map[string]interface{}{ + "resource_type": resourceType, + "resource_id": resourceID, + }) +} + +func (r *telemetryLinkInstanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + resourceType := model.ResourceType.ValueString() + resourceID := model.ResourceID.ValueString() + region := model.Region.ValueString() + + ctx = tflog.SetField(ctx, "resource_type", resourceType) + ctx = tflog.SetField(ctx, "resource_id", resourceID) + ctx = tflog.SetField(ctx, "region", region) + + var err error + switch resourceType { + case resourceTypeOrganization: + err = r.client.DefaultAPI.DeleteOrganizationTelemetryLink(ctx, resourceID, region).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + _, err = wait.DeleteOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, region).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) + return + } + case resourceTypeFolder: + err = r.client.DefaultAPI.DeleteFolderTelemetryLink(ctx, resourceID, region).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + _, err = wait.DeleteFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, region).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) + return + } + case resourceTypeProject: + err = r.client.DefaultAPI.DeleteProjectTelemetryLink(ctx, resourceID, region).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + _, err = wait.DeleteProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, region).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) + return + } + } + + tflog.Info(ctx, "TelemetryLink Link deleted") +} + +func (r *telemetryLinkInstanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing TelemetryLink Link", fmt.Sprintf("Invalid import ID %q: expected format is `project_id`,`region`,`instance_id`", req.ID)) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("resource_type"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("resource_id"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[2])...) + tflog.Info(ctx, "TelemetryLink Link state imported") +} + +func toCreateOrUpdateOrganizationTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload{ + DisplayName: model.DisplayName.ValueString(), + Description: model.Description.ValueStringPointer(), + TelemetryRouterId: model.TelemetryRouterID.ValueString(), + AccessToken: model.AccessToken.ValueString(), + }, nil +} + +func toCreateOrUpdateFolderTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.CreateOrUpdateFolderTelemetryLinkPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetrylink.CreateOrUpdateFolderTelemetryLinkPayload{ + DisplayName: model.DisplayName.ValueString(), + Description: model.Description.ValueStringPointer(), + TelemetryRouterId: model.TelemetryRouterID.ValueString(), + AccessToken: model.AccessToken.ValueString(), + }, nil +} + +func toCreateOrUpdateProjectTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.CreateOrUpdateProjectTelemetryLinkPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetrylink.CreateOrUpdateProjectTelemetryLinkPayload{ + DisplayName: model.DisplayName.ValueString(), + Description: model.Description.ValueStringPointer(), + TelemetryRouterId: model.TelemetryRouterID.ValueString(), + AccessToken: model.AccessToken.ValueString(), + }, nil +} + +func mapFields(_ context.Context, link *telemetrylink.TelemetryLinkResponse, model *Model) error { + if link == nil { + return fmt.Errorf("link is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + var linkID string + if model.LinkID.ValueString() != "" { + linkID = model.LinkID.ValueString() + } else { + linkID = link.Id + } + + model.ID = tfutils.BuildInternalTerraformId(model.ResourceType.ValueString(), model.ResourceID.ValueString(), model.Region.ValueString()) + model.LinkID = types.StringValue(linkID) + model.DisplayName = types.StringValue(link.DisplayName) + model.Description = types.StringPointerValue(link.Description) + model.TelemetryRouterID = types.StringValue(link.TelemetryRouterId) + model.CreateTime = types.StringValue(link.CreateTime.String()) + model.Status = types.StringValue(link.Status) + + return nil +} diff --git a/stackit/internal/services/telemetrylink/link/resource_test.go b/stackit/internal/services/telemetrylink/link/resource_test.go new file mode 100644 index 000000000..d5d736647 --- /dev/null +++ b/stackit/internal/services/telemetrylink/link/resource_test.go @@ -0,0 +1,264 @@ +package link + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" +) + +var testTime = time.Now() + +func fixtureLink(mods ...func(link *telemetrylink.TelemetryLinkResponse)) *telemetrylink.TelemetryLinkResponse { + link := &telemetrylink.TelemetryLinkResponse{ + Id: "lid", + DisplayName: "name", + TelemetryRouterId: "tlmrid", + CreateTime: testTime, + Status: "active", + } + for _, mod := range mods { + mod(link) + } + return link +} + +func fixtureModel(mods ...func(model *Model)) *Model { + model := &Model{ + ID: types.StringValue("rtp,rid,reg"), + LinkID: types.StringValue("lid"), + Region: types.StringValue("reg"), + ResourceType: types.StringValue("rtp"), + ResourceID: types.StringValue("rid"), + DisplayName: types.StringValue("name"), + Description: types.String{}, + TelemetryRouterID: types.StringValue("tlmrid"), + AccessToken: types.String{}, + CreateTime: types.StringValue(testTime.String()), + Status: types.StringValue("active"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapFields(t *testing.T) { + tests := []struct { + description string + input *telemetrylink.TelemetryLinkResponse + expected *Model + wantErr bool + }{ + { + description: "min values", + input: fixtureLink(), + expected: fixtureModel(), + }, + { + description: "max values", + input: fixtureLink(func(link *telemetrylink.TelemetryLinkResponse) { + link.Description = utils.Ptr("description") + link.DisplayName = "display-name" + link.AccessToken = utils.Ptr("access-token") + link.TelemetryRouterId = "tlmr-id" + }), + expected: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.TelemetryRouterID = types.StringValue("tlmr-id") + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ResourceType: tt.expected.ResourceType, + ResourceID: tt.expected.ResourceID, + Region: tt.expected.Region, + } + err := mapFields(context.Background(), tt.input, state) + if tt.wantErr && err == nil { + t.Fatalf("Should have failed") + } + if !tt.wantErr && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if !tt.wantErr { + diff := cmp.Diff(state, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestToCreateOrUpdateOrganizationTelemetryLinkPayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload{ + DisplayName: "name", + AccessToken: "", + TelemetryRouterId: "tlmrid", + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.AccessToken = types.StringValue("access-token") + model.TelemetryRouterID = types.StringValue("tlmr_id") + }), + expected: &telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + AccessToken: "access-token", + TelemetryRouterId: "tlmr_id", + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(t.Context(), diag.Diagnostics{}, tt.model) + if tt.wantErrMessage != "" && (err == nil || err.Error() != tt.wantErrMessage) { + t.Fatalf("Expected error: %v, got: %v", tt.wantErrMessage, err) + } + if tt.wantErrMessage == "" && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + diff := cmp.Diff(got, tt.expected) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func TestToCreateOrUpdateFolderTelemetryLinkPayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetrylink.CreateOrUpdateFolderTelemetryLinkPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetrylink.CreateOrUpdateFolderTelemetryLinkPayload{ + DisplayName: "name", + AccessToken: "", + TelemetryRouterId: "tlmrid", + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.AccessToken = types.StringValue("access-token") + model.TelemetryRouterID = types.StringValue("tlmr_id") + }), + expected: &telemetrylink.CreateOrUpdateFolderTelemetryLinkPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + AccessToken: "access-token", + TelemetryRouterId: "tlmr_id", + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreateOrUpdateFolderTelemetryLinkPayload(t.Context(), diag.Diagnostics{}, tt.model) + if tt.wantErrMessage != "" && (err == nil || err.Error() != tt.wantErrMessage) { + t.Fatalf("Expected error: %v, got: %v", tt.wantErrMessage, err) + } + if tt.wantErrMessage == "" && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + diff := cmp.Diff(got, tt.expected) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func TestToCreateOrUpdateProjectTelemetryLinkPayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetrylink.CreateOrUpdateProjectTelemetryLinkPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetrylink.CreateOrUpdateProjectTelemetryLinkPayload{ + DisplayName: "name", + AccessToken: "", + TelemetryRouterId: "tlmrid", + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.AccessToken = types.StringValue("access-token") + model.TelemetryRouterID = types.StringValue("tlmr_id") + }), + expected: &telemetrylink.CreateOrUpdateProjectTelemetryLinkPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + AccessToken: "access-token", + TelemetryRouterId: "tlmr_id", + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreateOrUpdateProjectTelemetryLinkPayload(t.Context(), diag.Diagnostics{}, tt.model) + if tt.wantErrMessage != "" && (err == nil || err.Error() != tt.wantErrMessage) { + t.Fatalf("Expected error: %v, got: %v", tt.wantErrMessage, err) + } + if tt.wantErrMessage == "" && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + diff := cmp.Diff(got, tt.expected) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} diff --git a/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go b/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go new file mode 100644 index 000000000..5bf5e10d5 --- /dev/null +++ b/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go @@ -0,0 +1,346 @@ +package telemetrylink_test + +import ( + "context" + _ "embed" + "errors" + "fmt" + "maps" + "strings" + "sync" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +var ( + //go:embed testdata/resource-min.tf + resourceMin string + + //go:embed testdata/resource-max.tf + resourceMax string +) + +var testConfigVarsMin = config.Variables{ + "resource_type": config.StringVariable("project"), + "resource_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-link-min"), + "access_token": config.StringVariable("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30"), + "telemetry_router_id": config.StringVariable("97272f10-87ec-4715-b280-195a4ab1856c"), +} + +func testConfigVarsMinUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigVarsMin)) + maps.Copy(newVars, testConfigVarsMin) + newVars["display_name"] = config.StringVariable("tf-acc-test-link-updated") + return newVars +} + +var testConfigVarsMax = config.Variables{ + "resource_type": config.StringVariable("project"), + "resource_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-link-max"), + "description": config.StringVariable("tf-acc-test-link-description"), + "access_token": config.StringVariable("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30"), + "telemetry_router_id": config.StringVariable("97272f10-87ec-4715-b280-195a4ab1856c"), +} + +func testConfigVarsMaxUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigVarsMin)) + maps.Copy(newVars, testConfigVarsMin) + newVars["display_name"] = config.StringVariable("tf-acc-test-link-updated") + newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryLink Link Updated") + return newVars +} + +func TestAccTelemetryLinkLinkMin(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigVarsMin, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMin, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMin["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMin["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMin["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMin["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMin["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMin["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigVarsMin, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMin + ` + data "stackit_telemetrylink_link" "link" { + resource_type = stackit_telemetrylink_link.link.resource_type + resource_id = stackit_telemetrylink_link.link.resource_id + region = stackit_telemetrylink_link.link.region + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMin["resource_type"])), + resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMin["resource_id"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "region", + "data.stackit_telemetrylink_link.link", "region", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "id", + "data.stackit_telemetrylink_link.link", "id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "link_id", + "data.stackit_telemetrylink_link.link", "link_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "display_name", + "data.stackit_telemetrylink_link.link", "display_name", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "create_time", + "data.stackit_telemetrylink_link.link", "create_time", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "telemetry_router_id", + "data.stackit_telemetrylink_link.link", "telemetry_router_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "status", + "data.stackit_telemetrylink_link.link", "status", + ), + ), + }, + // Import + { + ConfigVariables: testConfigVarsMin, + ResourceName: "stackit_telemetrylink_link.link", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetrylink_link.link"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetrylink_link.link") + } + resourceType, ok := rs.Primary.Attributes["resource_type"] + if !ok { + return "", fmt.Errorf("resource_type not set") + } + return fmt.Sprintf("%s,%s,%s", resourceType, testutil.ProjectId, testutil.Region), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"access_token"}, + }, + // Update + { + ConfigVariables: testConfigVarsMinUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMin, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccTelemetryLinkLinkMax(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigVarsMax, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMax, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMax["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMax["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMax["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMax["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMax["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigVarsMax, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMax + ` + data "stackit_telemetrylink_link" "link" { + resource_type = stackit_telemetrylink_link.link.resource_type + resource_id = stackit_telemetrylink_link.link.resource_id + region = stackit_telemetrylink_link.link.region + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMax["resource_type"])), + resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMax["resource_id"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "region", + "data.stackit_telemetrylink_link.link", "region", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "id", + "data.stackit_telemetrylink_link.link", "id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "link_id", + "data.stackit_telemetrylink_link.link", "link_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "display_name", + "data.stackit_telemetrylink_link.link", "display_name", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "description", + "data.stackit_telemetrylink_link.link", "description", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "create_time", + "data.stackit_telemetrylink_link.link", "create_time", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "telemetry_router_id", + "data.stackit_telemetrylink_link.link", "telemetry_router_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetrylink_link.link", "status", + "data.stackit_telemetrylink_link.link", "status", + ), + ), + }, + // Import + { + ConfigVariables: testConfigVarsMax, + ResourceName: "stackit_telemetrylink_link.link", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetrylink_link.link"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetrylink_link.link") + } + resourceType, ok := rs.Primary.Attributes["resource_type"] + if !ok { + return "", fmt.Errorf("resource_type not set") + } + return fmt.Sprintf("%s,%s,%s", resourceType, testutil.ProjectId, testutil.Region), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"access_token"}, + }, + // Update + { + ConfigVariables: testConfigVarsMaxUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMax, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "description", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["description"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func testAccCheckDestroy(s *terraform.State) error { + checkFunctions := []func(s *terraform.State) error{ + testAccCheckLogsInstanceDestroy, + } + + 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 testAccCheckLogsInstanceDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := telemetrylink.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.LogsCustomEndpoint, false)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + type link struct { + resourceType string + resourceId string + region string + } + + var linksToDestroy []link + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_telemetrylink_link" { + continue + } + parts := strings.Split(rs.Primary.ID, core.Separator) + linksToDestroy = append(linksToDestroy, link{ + resourceType: parts[0], + resourceId: parts[1], + region: parts[2], + }) + } + + for _, l := range linksToDestroy { + var err error + switch l.resourceType { + case "organization": + err = client.DefaultAPI.DeleteOrganizationTelemetryLink(ctx, l.resourceId, l.region).Execute() + case "folder": + err = client.DefaultAPI.DeleteFolderTelemetryLink(ctx, l.resourceId, l.region).Execute() + case "project": + err = client.DefaultAPI.DeleteProjectTelemetryLink(ctx, l.resourceId, l.region).Execute() + } + if err != nil { + return fmt.Errorf("deleting link %s %s: %w", l.resourceType, l.resourceId, err) + } + } + return nil +} diff --git a/stackit/internal/services/telemetrylink/testdata/resource-max.tf b/stackit/internal/services/telemetrylink/testdata/resource-max.tf new file mode 100644 index 000000000..2d753bd5e --- /dev/null +++ b/stackit/internal/services/telemetrylink/testdata/resource-max.tf @@ -0,0 +1,18 @@ + +variable "resource_type" {} +variable "resource_id" {} +variable "region" {} +variable "display_name" {} +variable "description" {} +variable "access_token" {} +variable "telemetry_router_id" {} + +resource "stackit_telemetrylink_link" "link" { + resource_type = var.resource_type + resource_id = var.resource_id + region = var.region + display_name = var.display_name + description = var.description + access_token = var.access_token + telemetry_router_id = var.telemetry_router_id +} diff --git a/stackit/internal/services/telemetrylink/testdata/resource-min.tf b/stackit/internal/services/telemetrylink/testdata/resource-min.tf new file mode 100644 index 000000000..ab458413e --- /dev/null +++ b/stackit/internal/services/telemetrylink/testdata/resource-min.tf @@ -0,0 +1,16 @@ + +variable "resource_type" {} +variable "resource_id" {} +variable "region" {} +variable "display_name" {} +variable "access_token" {} +variable "telemetry_router_id" {} + +resource "stackit_telemetrylink_link" "link" { + resource_type = var.resource_type + resource_id = var.resource_id + region = var.region + display_name = var.display_name + access_token = var.access_token + telemetry_router_id = var.telemetry_router_id +} diff --git a/stackit/internal/services/telemetrylink/utils/utils.go b/stackit/internal/services/telemetrylink/utils/utils.go new file mode 100644 index 000000000..dd43ab2a6 --- /dev/null +++ b/stackit/internal/services/telemetrylink/utils/utils.go @@ -0,0 +1,30 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *telemetrylink.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.TelemetryLinkCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.TelemetryLinkCustomEndpoint)) + } + apiClient, err := telemetrylink.NewAPIClient(apiClientConfigOptions...) + if err != nil { + core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + return nil + } + + return apiClient +} diff --git a/stackit/internal/services/telemetrylink/utils/utils_test.go b/stackit/internal/services/telemetrylink/utils/utils_test.go new file mode 100644 index 000000000..ddefaa831 --- /dev/null +++ b/stackit/internal/services/telemetrylink/utils/utils_test.go @@ -0,0 +1,94 @@ +package utils + +import ( + "context" + "os" + "reflect" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" + "github.com/stackitcloud/stackit-sdk-go/core/config" + telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +const ( + testVersion = "1.2.3" + testCustomEndpoint = "https://telemetrylink-custom-endpoint.api.stackit.cloud" +) + +func TestConfigureClient(t *testing.T) { + /* mock authentication by setting service account token env variable */ + os.Clearenv() + err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val") + if err != nil { + t.Errorf("error setting env variable: %v", err) + } + + type args struct { + providerData *core.ProviderData + } + tests := []struct { + name string + args args + wantErr bool + expected *telemetrylink.APIClient + }{ + { + name: "default endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + }, + }, + expected: func() *telemetrylink.APIClient { + apiClient, err := telemetrylink.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + { + name: "custom endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + TelemetryLinkCustomEndpoint: testCustomEndpoint, + }, + }, + expected: func() *telemetrylink.APIClient { + apiClient, err := telemetrylink.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + config.WithEndpoint(testCustomEndpoint), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + diags := diag.Diagnostics{} + + actual := ConfigureClient(ctx, tt.args.providerData, &diags) + if diags.HasError() != tt.wantErr { + t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr) + } + + if !reflect.DeepEqual(actual, tt.expected) { + t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected) + } + }) + } +} diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 5be2e7281..c15dc3dc3 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -98,6 +98,7 @@ var ( ServiceAccountCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SERVICE_ACCOUNT_CUSTOM_ENDPOINT", providerName: "service_account_custom_endpoint"} TokenCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TOKEN_CUSTOM_ENDPOINT", providerName: "token_custom_endpoint"} SKECustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_SKE_CUSTOM_ENDPOINT", providerName: "ske_custom_endpoint"} + TelemetryLinkCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYLINK_CUSTOM_ENDPOINT", providerName: "telemetrylink_custom_endpoint"} allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, @@ -131,6 +132,7 @@ var ( ServiceAccountCustomEndpoint, TokenCustomEndpoint, SKECustomEndpoint, + TelemetryLinkCustomEndpoint, } ) diff --git a/stackit/provider.go b/stackit/provider.go index 1dc8da94c..423e9db89 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -18,6 +18,7 @@ import ( sdkauth "github.com/stackitcloud/stackit-sdk-go/core/auth" "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/core/oidcadapters" + telemetryLink "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetrylink/link" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" @@ -196,6 +197,7 @@ type providerModel struct { SfsCustomEndpoint types.String `tfsdk:"sfs_custom_endpoint"` SkeCustomEndpoint types.String `tfsdk:"ske_custom_endpoint"` SqlServerFlexCustomEndpoint types.String `tfsdk:"sqlserverflex_custom_endpoint"` + TelemetryLinkCustomEndpoint types.String `tfsdk:"telemetrylink_custom_endpoint"` TokenCustomEndpoint types.String `tfsdk:"token_custom_endpoint"` OIDCTokenRequestURL types.String `tfsdk:"oidc_request_url"` OIDCTokenRequestToken types.String `tfsdk:"oidc_request_token"` @@ -252,6 +254,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "ske_custom_endpoint": "Custom endpoint for the Kubernetes Engine (SKE) service", "service_enablement_custom_endpoint": "Custom endpoint for the Service Enablement API", "sfs_custom_endpoint": "Custom endpoint for the Stackit Filestorage API", + "telemetrylink_custom_endpoint": "Custom endpoint for the Telemetry Link service", "token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow", "enable_beta_resources": "Enable beta resources. Default is false.", "experiments": fmt.Sprintf("Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: %v", strings.Join(features.AvailableExperiments, ", ")), @@ -458,6 +461,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["sfs_custom_endpoint"], }, + "telemetrylink_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["telemetrylink_custom_endpoint"], + }, "token_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["token_custom_endpoint"], @@ -544,6 +551,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.SfsCustomEndpoint, func(v string) { providerData.SfsCustomEndpoint = v }) setStringField(providerConfig.SkeCustomEndpoint, func(v string) { providerData.SKECustomEndpoint = v }) setStringField(providerConfig.SqlServerFlexCustomEndpoint, func(v string) { providerData.SQLServerFlexCustomEndpoint = v }) + setStringField(providerConfig.TelemetryLinkCustomEndpoint, func(v string) { providerData.TelemetryLinkCustomEndpoint = v }) if !(providerConfig.Experiments.IsUnknown() || providerConfig.Experiments.IsNull()) { var experimentValues []string @@ -703,6 +711,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource compliancelock.NewComplianceLockDataSource, serverBackupEnable.NewServerBackupEnableDataSource, serverUpdateEnable.NewServerUpdateEnableDataSource, + telemetryLink.NewTelemetryLinkLinkDataSource, } dataSources = append(dataSources, customRole.NewCustomRoleDataSources()...) dataSources = append(dataSources, iamRoleBindingsV1.NewRoleBindingsDatasources()...) @@ -795,6 +804,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { compliancelock.NewComplianceLockResource, serverBackupEnable.NewServerBackupEnableResource, serverUpdateEnable.NewServerUpdateEnableResource, + telemetryLink.NewTelemetryLinkResource, } resources = append(resources, roleAssignements.NewRoleAssignmentResources()...) resources = append(resources, customRole.NewCustomRoleResources()...) From db12dc1b2b0282b47b08f8657fb0a34cefb7e1ac Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Mon, 11 May 2026 21:57:12 +0200 Subject: [PATCH 2/9] chore: lint fix --- stackit/provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stackit/provider.go b/stackit/provider.go index 423e9db89..701ab9d95 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -18,6 +18,7 @@ import ( sdkauth "github.com/stackitcloud/stackit-sdk-go/core/auth" "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/core/oidcadapters" + telemetryLink "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetrylink/link" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" From a0857952775b55464cc653e5d2beb6c120602ace Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 13:21:07 +0200 Subject: [PATCH 3/9] chore: renamed stackit_telemetrylink link to stackit_telemetrylink --- .../data-source.tf | 2 +- .../resource.tf | 6 +- .../services/telemetrylink/link/datasource.go | 2 +- .../services/telemetrylink/link/resource.go | 2 +- .../telemetrylink/telemetrylink_acc_test.go | 182 +++++++++--------- .../telemetrylink/testdata/resource-max.tf | 2 +- .../telemetrylink/testdata/resource-min.tf | 2 +- 7 files changed, 99 insertions(+), 99 deletions(-) rename examples/data-sources/{stackit_telemetrylink_link => stackit_telemetrylink}/data-source.tf (72%) rename examples/resources/{stackit_telemetrylink_link => stackit_telemetrylink}/resource.tf (84%) diff --git a/examples/data-sources/stackit_telemetrylink_link/data-source.tf b/examples/data-sources/stackit_telemetrylink/data-source.tf similarity index 72% rename from examples/data-sources/stackit_telemetrylink_link/data-source.tf rename to examples/data-sources/stackit_telemetrylink/data-source.tf index 199951f41..932becea4 100644 --- a/examples/data-sources/stackit_telemetrylink_link/data-source.tf +++ b/examples/data-sources/stackit_telemetrylink/data-source.tf @@ -1,4 +1,4 @@ -data "stackit_telemetrylink_link" "link" { +data "stackit_telemetrylink" "link" { resource_type = "project" resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" region = "eu01" diff --git a/examples/resources/stackit_telemetrylink_link/resource.tf b/examples/resources/stackit_telemetrylink/resource.tf similarity index 84% rename from examples/resources/stackit_telemetrylink_link/resource.tf rename to examples/resources/stackit_telemetrylink/resource.tf index adeb60f44..c4ae9a1d6 100644 --- a/examples/resources/stackit_telemetrylink_link/resource.tf +++ b/examples/resources/stackit_telemetrylink/resource.tf @@ -1,4 +1,4 @@ -resource "stackit_telemetrylink_link" "link" { +resource "stackit_telemetrylink" "link" { resource_type = "project" resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" region = "eu01" @@ -7,7 +7,7 @@ resource "stackit_telemetrylink_link" "link" { telemetry_router_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } -resource "stackit_telemetrylink_link" "link2" { +resource "stackit_telemetrylink" "link2" { resource_type = "project" resource_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" region = "eu01" @@ -19,6 +19,6 @@ resource "stackit_telemetrylink_link" "link2" { # Only use the import statement, if you want to import an existing TelemetryLink link import { - to = stackit_telemetrylink_link.import-example + to = stackit_telemetrylink.import-example id = "${var.resource_type},${var.resource_id},${var.region}" } diff --git a/stackit/internal/services/telemetrylink/link/datasource.go b/stackit/internal/services/telemetrylink/link/datasource.go index ce766d00e..d025e00d8 100644 --- a/stackit/internal/services/telemetrylink/link/datasource.go +++ b/stackit/internal/services/telemetrylink/link/datasource.go @@ -50,7 +50,7 @@ type telemetryLinkLinkDataSource struct { } func (d *telemetryLinkLinkDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_telemetrylink_link" + resp.TypeName = req.ProviderTypeName + "_telemetrylink" } func (d *telemetryLinkLinkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go index 0d44b0474..003546084 100644 --- a/stackit/internal/services/telemetrylink/link/resource.go +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -130,7 +130,7 @@ func (r *telemetryLinkInstanceResource) ModifyPlan(ctx context.Context, req reso } func (r *telemetryLinkInstanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_telemetrylink_link" + resp.TypeName = req.ProviderTypeName + "_telemetrylink" } func (r *telemetryLinkInstanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go b/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go index 5bf5e10d5..df652191b 100644 --- a/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go +++ b/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go @@ -71,69 +71,69 @@ func TestAccTelemetryLinkLinkMin(t *testing.T) { ConfigVariables: testConfigVarsMin, Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMin, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMin["resource_type"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMin["resource_id"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMin["region"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMin["display_name"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMin["access_token"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMin["telemetry_router_id"])), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMin["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMin["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "region", testutil.ConvertConfigVariable(testConfigVarsMin["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMin["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMin["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMin["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "status"), ), }, // Datasource { ConfigVariables: testConfigVarsMin, Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMin + ` - data "stackit_telemetrylink_link" "link" { - resource_type = stackit_telemetrylink_link.link.resource_type - resource_id = stackit_telemetrylink_link.link.resource_id - region = stackit_telemetrylink_link.link.region + data "stackit_telemetrylink" "link" { + resource_type = stackit_telemetrylink.link.resource_type + resource_id = stackit_telemetrylink.link.resource_id + region = stackit_telemetrylink.link.region } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMin["resource_type"])), - resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMin["resource_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetrylink.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMin["resource_type"])), + resource.TestCheckResourceAttr("data.stackit_telemetrylink.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMin["resource_id"])), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "region", - "data.stackit_telemetrylink_link.link", "region", + "stackit_telemetrylink.link", "region", + "data.stackit_telemetrylink.link", "region", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "id", - "data.stackit_telemetrylink_link.link", "id", + "stackit_telemetrylink.link", "id", + "data.stackit_telemetrylink.link", "id", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "link_id", - "data.stackit_telemetrylink_link.link", "link_id", + "stackit_telemetrylink.link", "link_id", + "data.stackit_telemetrylink.link", "link_id", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "display_name", - "data.stackit_telemetrylink_link.link", "display_name", + "stackit_telemetrylink.link", "display_name", + "data.stackit_telemetrylink.link", "display_name", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "create_time", - "data.stackit_telemetrylink_link.link", "create_time", + "stackit_telemetrylink.link", "create_time", + "data.stackit_telemetrylink.link", "create_time", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "telemetry_router_id", - "data.stackit_telemetrylink_link.link", "telemetry_router_id", + "stackit_telemetrylink.link", "telemetry_router_id", + "data.stackit_telemetrylink.link", "telemetry_router_id", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "status", - "data.stackit_telemetrylink_link.link", "status", + "stackit_telemetrylink.link", "status", + "data.stackit_telemetrylink.link", "status", ), ), }, // Import { ConfigVariables: testConfigVarsMin, - ResourceName: "stackit_telemetrylink_link.link", + ResourceName: "stackit_telemetrylink.link", ImportStateIdFunc: func(state *terraform.State) (string, error) { - rs, ok := state.RootModule().Resources["stackit_telemetrylink_link.link"] + rs, ok := state.RootModule().Resources["stackit_telemetrylink.link"] if !ok { - return "", fmt.Errorf("not found: %s", "stackit_telemetrylink_link.link") + return "", fmt.Errorf("not found: %s", "stackit_telemetrylink.link") } resourceType, ok := rs.Primary.Attributes["resource_type"] if !ok { @@ -150,16 +150,16 @@ func TestAccTelemetryLinkLinkMin(t *testing.T) { ConfigVariables: testConfigVarsMinUpdated(), Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMin, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["resource_type"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["resource_id"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["region"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["display_name"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["access_token"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["telemetry_router_id"])), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "region", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "status"), ), }, // Deletion handled by framework @@ -177,74 +177,74 @@ func TestAccTelemetryLinkLinkMax(t *testing.T) { ConfigVariables: testConfigVarsMax, Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMax, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMax["resource_type"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMax["resource_id"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMax["region"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["display_name"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMax["access_token"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMax["telemetry_router_id"])), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMax["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMax["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "region", testutil.ConvertConfigVariable(testConfigVarsMax["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMax["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMax["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "status"), ), }, // Datasource { ConfigVariables: testConfigVarsMax, Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMax + ` - data "stackit_telemetrylink_link" "link" { - resource_type = stackit_telemetrylink_link.link.resource_type - resource_id = stackit_telemetrylink_link.link.resource_id - region = stackit_telemetrylink_link.link.region + data "stackit_telemetrylink" "link" { + resource_type = stackit_telemetrylink.link.resource_type + resource_id = stackit_telemetrylink.link.resource_id + region = stackit_telemetrylink.link.region } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMax["resource_type"])), - resource.TestCheckResourceAttr("data.stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMax["resource_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetrylink.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMax["resource_type"])), + resource.TestCheckResourceAttr("data.stackit_telemetrylink.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMax["resource_id"])), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "region", - "data.stackit_telemetrylink_link.link", "region", + "stackit_telemetrylink.link", "region", + "data.stackit_telemetrylink.link", "region", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "id", - "data.stackit_telemetrylink_link.link", "id", + "stackit_telemetrylink.link", "id", + "data.stackit_telemetrylink.link", "id", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "link_id", - "data.stackit_telemetrylink_link.link", "link_id", + "stackit_telemetrylink.link", "link_id", + "data.stackit_telemetrylink.link", "link_id", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "display_name", - "data.stackit_telemetrylink_link.link", "display_name", + "stackit_telemetrylink.link", "display_name", + "data.stackit_telemetrylink.link", "display_name", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "description", - "data.stackit_telemetrylink_link.link", "description", + "stackit_telemetrylink.link", "description", + "data.stackit_telemetrylink.link", "description", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "create_time", - "data.stackit_telemetrylink_link.link", "create_time", + "stackit_telemetrylink.link", "create_time", + "data.stackit_telemetrylink.link", "create_time", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "telemetry_router_id", - "data.stackit_telemetrylink_link.link", "telemetry_router_id", + "stackit_telemetrylink.link", "telemetry_router_id", + "data.stackit_telemetrylink.link", "telemetry_router_id", ), resource.TestCheckResourceAttrPair( - "stackit_telemetrylink_link.link", "status", - "data.stackit_telemetrylink_link.link", "status", + "stackit_telemetrylink.link", "status", + "data.stackit_telemetrylink.link", "status", ), ), }, // Import { ConfigVariables: testConfigVarsMax, - ResourceName: "stackit_telemetrylink_link.link", + ResourceName: "stackit_telemetrylink.link", ImportStateIdFunc: func(state *terraform.State) (string, error) { - rs, ok := state.RootModule().Resources["stackit_telemetrylink_link.link"] + rs, ok := state.RootModule().Resources["stackit_telemetrylink.link"] if !ok { - return "", fmt.Errorf("not found: %s", "stackit_telemetrylink_link.link") + return "", fmt.Errorf("not found: %s", "stackit_telemetrylink.link") } resourceType, ok := rs.Primary.Attributes["resource_type"] if !ok { @@ -261,17 +261,17 @@ func TestAccTelemetryLinkLinkMax(t *testing.T) { ConfigVariables: testConfigVarsMaxUpdated(), Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + resourceMax, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["resource_type"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["resource_id"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "region", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["region"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["display_name"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "description", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["description"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["access_token"])), - resource.TestCheckResourceAttr("stackit_telemetrylink_link.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["telemetry_router_id"])), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "link_id"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "create_time"), - resource.TestCheckResourceAttrSet("stackit_telemetrylink_link.link", "status"), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_type", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["resource_type"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "resource_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["resource_id"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "region", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["region"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "display_name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "description", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["description"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "access_token", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["access_token"])), + resource.TestCheckResourceAttr("stackit_telemetrylink.link", "telemetry_router_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["telemetry_router_id"])), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "link_id"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "create_time"), + resource.TestCheckResourceAttrSet("stackit_telemetrylink.link", "status"), ), }, // Deletion handled by framework @@ -317,7 +317,7 @@ func testAccCheckLogsInstanceDestroy(s *terraform.State) error { var linksToDestroy []link for _, rs := range s.RootModule().Resources { - if rs.Type != "stackit_telemetrylink_link" { + if rs.Type != "stackit_telemetrylink" { continue } parts := strings.Split(rs.Primary.ID, core.Separator) diff --git a/stackit/internal/services/telemetrylink/testdata/resource-max.tf b/stackit/internal/services/telemetrylink/testdata/resource-max.tf index 2d753bd5e..7c46035ad 100644 --- a/stackit/internal/services/telemetrylink/testdata/resource-max.tf +++ b/stackit/internal/services/telemetrylink/testdata/resource-max.tf @@ -7,7 +7,7 @@ variable "description" {} variable "access_token" {} variable "telemetry_router_id" {} -resource "stackit_telemetrylink_link" "link" { +resource "stackit_telemetrylink" "link" { resource_type = var.resource_type resource_id = var.resource_id region = var.region diff --git a/stackit/internal/services/telemetrylink/testdata/resource-min.tf b/stackit/internal/services/telemetrylink/testdata/resource-min.tf index ab458413e..b140056f4 100644 --- a/stackit/internal/services/telemetrylink/testdata/resource-min.tf +++ b/stackit/internal/services/telemetrylink/testdata/resource-min.tf @@ -6,7 +6,7 @@ variable "display_name" {} variable "access_token" {} variable "telemetry_router_id" {} -resource "stackit_telemetrylink_link" "link" { +resource "stackit_telemetrylink" "link" { resource_type = var.resource_type resource_id = var.resource_id region = var.region From 42130725ddc5b5da7e58a4df74e5ef8def33f7c0 Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 13:24:04 +0200 Subject: [PATCH 4/9] chore: rename "TelemetryLink Link" to "TelemetryLink" --- .../stackit_telemetrylink/resource.tf | 2 +- .../services/telemetrylink/link/datasource.go | 8 +- .../services/telemetrylink/link/resource.go | 74 +++++++++---------- .../telemetrylink/telemetrylink_acc_test.go | 2 +- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/examples/resources/stackit_telemetrylink/resource.tf b/examples/resources/stackit_telemetrylink/resource.tf index c4ae9a1d6..5d03433a2 100644 --- a/examples/resources/stackit_telemetrylink/resource.tf +++ b/examples/resources/stackit_telemetrylink/resource.tf @@ -17,7 +17,7 @@ resource "stackit_telemetrylink" "link2" { telemetry_router_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } -# Only use the import statement, if you want to import an existing TelemetryLink link +# Only use the import statement, if you want to import an existing TelemetryLink import { to = stackit_telemetrylink.import-example id = "${var.resource_type},${var.resource_id},${var.region}" diff --git a/stackit/internal/services/telemetrylink/link/datasource.go b/stackit/internal/services/telemetrylink/link/datasource.go index d025e00d8..e6f9fe7f3 100644 --- a/stackit/internal/services/telemetrylink/link/datasource.go +++ b/stackit/internal/services/telemetrylink/link/datasource.go @@ -70,7 +70,7 @@ func (d *telemetryLinkLinkDataSource) Configure(ctx context.Context, req datasou func (d *telemetryLinkLinkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: fmt.Sprintf("TelemetryLink Link data source schema. %s", core.DatasourceRegionFallbackDocstring), + Description: fmt.Sprintf("TelemetryLink data source schema. %s", core.DatasourceRegionFallbackDocstring), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: schemaDescriptions["id"], @@ -165,14 +165,14 @@ func (d *telemetryLinkLinkDataSource) Read(ctx context.Context, req datasource.R resp.State.RemoveResource(ctx) return } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } ctx = core.LogResponse(ctx) err = mapDataSourceFields(ctx, response, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Processing response: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Processing response: %v", err)) return } diags = resp.State.Set(ctx, model) @@ -180,7 +180,7 @@ func (d *telemetryLinkLinkDataSource) Read(ctx context.Context, req datasource.R if resp.Diagnostics.HasError() { return } - tflog.Info(ctx, "TelemetryLink Link read", map[string]interface{}{ + tflog.Info(ctx, "TelemetryLink read", map[string]interface{}{ "resource_type": resourceType, "resource_id": resourceID, }) diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go index 003546084..e1a74bb80 100644 --- a/stackit/internal/services/telemetrylink/link/resource.go +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -59,7 +59,7 @@ var schemaDescriptions = map[string]string{ "access_token": "The access token of the Telemetry Router instance.", "create_time": "The time the Telemetry Link was created.", "status": fmt.Sprintf( - "The status of the TelemetryLink link, possible values: %s", + "The status of the TelemetryLink, possible values: %s", tfutils.FormatPossibleValues("active", "inactive", "failed"), ), } @@ -240,7 +240,7 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource case resourceTypeOrganization: payload, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -248,7 +248,7 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -256,14 +256,14 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource response, err = wait.CreateOrUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeFolder: payload, err := toCreateOrUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -271,7 +271,7 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -279,13 +279,13 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource response, err = wait.CreateOrUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeProject: payload, err := toCreateOrUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -293,7 +293,7 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -301,14 +301,14 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource response, err = wait.CreateOrUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } } err := mapFields(ctx, response, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Processing response: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Processing response: %v", err)) return } diags = resp.State.Set(ctx, model) @@ -316,7 +316,7 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource if resp.Diagnostics.HasError() { return } - tflog.Info(ctx, "TelemetryLink Link created") + tflog.Info(ctx, "TelemetryLink created") } func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform @@ -349,7 +349,7 @@ func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.R resp.State.RemoveResource(ctx) return } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } case resourceTypeFolder: @@ -361,7 +361,7 @@ func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.R resp.State.RemoveResource(ctx) return } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } case resourceTypeProject: @@ -373,7 +373,7 @@ func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.R resp.State.RemoveResource(ctx) return } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } } @@ -381,7 +381,7 @@ func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.R err = mapFields(ctx, response, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink link", fmt.Sprintf("Processing response: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Processing response: %v", err)) return } diags = resp.State.Set(ctx, model) @@ -389,7 +389,7 @@ func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.R if resp.Diagnostics.HasError() { return } - tflog.Info(ctx, "TelemetryLink link read", map[string]interface{}{ + tflog.Info(ctx, "TelemetryLink read", map[string]interface{}{ "resource_type": resourceType, "resource_id": resourceID, }) @@ -418,7 +418,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource case resourceTypeOrganization: payload, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -426,7 +426,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -434,13 +434,13 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource response, err = wait.CreateOrUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeFolder: payload, err := toCreateOrUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -448,7 +448,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -456,13 +456,13 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource response, err = wait.CreateOrUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeProject: payload, err := toCreateOrUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } @@ -470,7 +470,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -478,14 +478,14 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource response, err = wait.CreateOrUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } } err := mapFields(ctx, response, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink Link", fmt.Sprintf("Processing response: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Processing response: %v", err)) return } @@ -494,7 +494,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource if resp.Diagnostics.HasError() { return } - tflog.Info(ctx, "TelemetryLink Link updated", map[string]interface{}{ + tflog.Info(ctx, "TelemetryLink updated", map[string]interface{}{ "resource_type": resourceType, "resource_id": resourceID, }) @@ -523,7 +523,7 @@ func (r *telemetryLinkInstanceResource) Delete(ctx context.Context, req resource case resourceTypeOrganization: err = r.client.DefaultAPI.DeleteOrganizationTelemetryLink(ctx, resourceID, region).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -531,13 +531,13 @@ func (r *telemetryLinkInstanceResource) Delete(ctx context.Context, req resource _, err = wait.DeleteOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, region).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) return } case resourceTypeFolder: err = r.client.DefaultAPI.DeleteFolderTelemetryLink(ctx, resourceID, region).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -545,13 +545,13 @@ func (r *telemetryLinkInstanceResource) Delete(ctx context.Context, req resource _, err = wait.DeleteFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, region).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) return } case resourceTypeProject: err = r.client.DefaultAPI.DeleteProjectTelemetryLink(ctx, resourceID, region).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -559,24 +559,24 @@ func (r *telemetryLinkInstanceResource) Delete(ctx context.Context, req resource _, err = wait.DeleteProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, region).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink Link", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become deleted: %v", err)) return } } - tflog.Info(ctx, "TelemetryLink Link deleted") + tflog.Info(ctx, "TelemetryLink deleted") } func (r *telemetryLinkInstanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { idParts := strings.Split(req.ID, core.Separator) if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing TelemetryLink Link", fmt.Sprintf("Invalid import ID %q: expected format is `project_id`,`region`,`instance_id`", req.ID)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing TelemetryLink", fmt.Sprintf("Invalid import ID %q: expected format is `project_id`,`region`,`instance_id`", req.ID)) return } resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("resource_type"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("resource_id"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[2])...) - tflog.Info(ctx, "TelemetryLink Link state imported") + tflog.Info(ctx, "TelemetryLink state imported") } func toCreateOrUpdateOrganizationTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload, error) { diff --git a/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go b/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go index df652191b..f894db58b 100644 --- a/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go +++ b/stackit/internal/services/telemetrylink/telemetrylink_acc_test.go @@ -57,7 +57,7 @@ func testConfigVarsMaxUpdated() config.Variables { newVars := make(config.Variables, len(testConfigVarsMin)) maps.Copy(newVars, testConfigVarsMin) newVars["display_name"] = config.StringVariable("tf-acc-test-link-updated") - newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryLink Link Updated") + newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryLink Updated") return newVars } From 6d6d309b190937287121fb8b070be60312a9af7f Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 13:30:27 +0200 Subject: [PATCH 5/9] chore: sorted schema by required then optional --- .../internal/services/telemetrylink/link/datasource.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stackit/internal/services/telemetrylink/link/datasource.go b/stackit/internal/services/telemetrylink/link/datasource.go index e6f9fe7f3..39d98de13 100644 --- a/stackit/internal/services/telemetrylink/link/datasource.go +++ b/stackit/internal/services/telemetrylink/link/datasource.go @@ -84,11 +84,6 @@ func (d *telemetryLinkLinkDataSource) Schema(_ context.Context, _ datasource.Sch validate.NoSeparator(), }, }, - "region": schema.StringAttribute{ - Description: schemaDescriptions["region"], - // the region cannot be found, so it has to be passed - Optional: true, - }, "resource_type": schema.StringAttribute{ Description: schemaDescriptions["resource_type"], Required: true, @@ -105,6 +100,11 @@ func (d *telemetryLinkLinkDataSource) Schema(_ context.Context, _ datasource.Sch validate.NoSeparator(), }, }, + "region": schema.StringAttribute{ + Description: schemaDescriptions["region"], + // the region cannot be found, so it has to be passed + Optional: true, + }, "display_name": schema.StringAttribute{ Description: schemaDescriptions["display_name"], Computed: true, From e573f5976a0532ec1b0fa8fd6c90cce1f9657712 Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 13:31:57 +0200 Subject: [PATCH 6/9] chore: sorted schema fields by required then optional --- .../services/telemetrylink/link/resource.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go index e1a74bb80..b97bcbf02 100644 --- a/stackit/internal/services/telemetrylink/link/resource.go +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -155,15 +155,6 @@ func (r *telemetryLinkInstanceResource) Schema(_ context.Context, _ resource.Sch stringplanmodifier.UseStateForUnknown(), }, }, - "region": schema.StringAttribute{ - Description: schemaDescriptions["region"], - Optional: true, - // must be computed to allow for storing the override value from the provider - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, "resource_type": schema.StringAttribute{ Description: schemaDescriptions["resource_type"], Required: true, @@ -198,6 +189,15 @@ func (r *telemetryLinkInstanceResource) Schema(_ context.Context, _ resource.Sch Description: schemaDescriptions["telemetry_router_id"], Required: true, }, + "region": schema.StringAttribute{ + Description: schemaDescriptions["region"], + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, "access_token": schema.StringAttribute{ Description: schemaDescriptions["access_token"], Optional: true, From d38ccae641a187a965b281316d16a8150a68b7fe Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 13:37:53 +0200 Subject: [PATCH 7/9] chore: review comments fixed --- .../services/telemetrylink/link/resource.go | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go index b97bcbf02..271951b98 100644 --- a/stackit/internal/services/telemetrylink/link/resource.go +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -235,6 +235,9 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource ctx = tflog.SetField(ctx, "resource_id", resourceID) ctx = tflog.SetField(ctx, "region", region) + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + var response *telemetrylink.TelemetryLinkResponse switch model.ResourceType.ValueString() { case resourceTypeOrganization: @@ -244,8 +247,6 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource return } - regionId := r.providerData.GetRegionWithOverride(model.Region) - ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) @@ -267,8 +268,6 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource return } - regionId := r.providerData.GetRegionWithOverride(model.Region) - ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) @@ -289,8 +288,6 @@ func (r *telemetryLinkInstanceResource) Create(ctx context.Context, req resource return } - regionId := r.providerData.GetRegionWithOverride(model.Region) - ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) @@ -413,20 +410,21 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = tflog.SetField(ctx, "resource_id", resourceID) ctx = tflog.SetField(ctx, "region", region) + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + var response *telemetrylink.TelemetryLinkResponse switch model.ResourceType.ValueString() { case resourceTypeOrganization: payload, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } - regionId := r.providerData.GetRegionWithOverride(model.Region) - ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -434,21 +432,19 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource response, err = wait.CreateOrUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeFolder: payload, err := toCreateOrUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } - regionId := r.providerData.GetRegionWithOverride(model.Region) - ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -456,21 +452,19 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource response, err = wait.CreateOrUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeProject: payload, err := toCreateOrUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } - regionId := r.providerData.GetRegionWithOverride(model.Region) - ctx = tflog.SetField(ctx, "region", regionId) _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return } @@ -478,7 +472,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource response, err = wait.CreateOrUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } } From 9db81c20da3cb58590e37c783f0aef02da48ab62 Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 13:39:41 +0200 Subject: [PATCH 8/9] chore: moved error handling out of the switch statement --- .../services/telemetrylink/link/resource.go | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go index 271951b98..cce446a68 100644 --- a/stackit/internal/services/telemetrylink/link/resource.go +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -339,40 +339,20 @@ func (r *telemetryLinkInstanceResource) Read(ctx context.Context, req resource.R switch resourceType { case resourceTypeOrganization: response, err = r.client.DefaultAPI.GetOrganizationTelemetryLink(ctx, resourceID, region).Execute() - if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - ok := errors.As(err, &oapiErr) - if ok && oapiErr.StatusCode == http.StatusNotFound { - resp.State.RemoveResource(ctx) - return - } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) - return - } case resourceTypeFolder: response, err = r.client.DefaultAPI.GetFolderTelemetryLink(ctx, resourceID, region).Execute() - if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - ok := errors.As(err, &oapiErr) - if ok && oapiErr.StatusCode == http.StatusNotFound { - resp.State.RemoveResource(ctx) - return - } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) - return - } case resourceTypeProject: response, err = r.client.DefaultAPI.GetProjectTelemetryLink(ctx, resourceID, region).Execute() - if err != nil { - var oapiErr *oapierror.GenericOpenAPIError - ok := errors.As(err, &oapiErr) - if ok && oapiErr.StatusCode == http.StatusNotFound { - resp.State.RemoveResource(ctx) - return - } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) + } + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) return } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryLink", fmt.Sprintf("Calling API: %v", err)) + return } ctx = core.LogResponse(ctx) From a5f01a53b96183f130547a53c6eb7cfa684eef1d Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Thu, 14 May 2026 14:41:16 +0200 Subject: [PATCH 9/9] chore: partial update --- .../telemetrylink/link/datasource_test.go | 5 +- .../services/telemetrylink/link/resource.go | 57 +++++- .../telemetrylink/link/resource_test.go | 170 +++++++++++++++++- 3 files changed, 214 insertions(+), 18 deletions(-) diff --git a/stackit/internal/services/telemetrylink/link/datasource_test.go b/stackit/internal/services/telemetrylink/link/datasource_test.go index 53ad1cda7..093cb116f 100644 --- a/stackit/internal/services/telemetrylink/link/datasource_test.go +++ b/stackit/internal/services/telemetrylink/link/datasource_test.go @@ -6,7 +6,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/stackitcloud/stackit-sdk-go/core/utils" telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" ) @@ -44,9 +43,9 @@ func TestMapDataSourceFields(t *testing.T) { { description: "max values", input: fixtureLink(func(link *telemetrylink.TelemetryLinkResponse) { - link.Description = utils.Ptr("description") + link.Description = new("description") link.DisplayName = "display-name" - link.AccessToken = utils.Ptr("access-token") + link.AccessToken = new("access-token") link.TelemetryRouterId = "tlmr-id" }), expected: fixtureDataSourceModel(func(model *DataSourceModel) { diff --git a/stackit/internal/services/telemetrylink/link/resource.go b/stackit/internal/services/telemetrylink/link/resource.go index cce446a68..fb596ddee 100644 --- a/stackit/internal/services/telemetrylink/link/resource.go +++ b/stackit/internal/services/telemetrylink/link/resource.go @@ -396,13 +396,13 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource var response *telemetrylink.TelemetryLinkResponse switch model.ResourceType.ValueString() { case resourceTypeOrganization: - payload, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + payload, err := toPartialUpdateOrganizationTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } - _, err = r.client.DefaultAPI.CreateOrUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateOrganizationTelemetryLinkPayload(*payload).Execute() + _, err = r.client.DefaultAPI.PartialUpdateOrganizationTelemetryLink(ctx, resourceID, regionId).PartialUpdateOrganizationTelemetryLinkPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return @@ -410,19 +410,19 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = core.LogResponse(ctx) - response, err = wait.CreateOrUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + response, err = wait.PartialUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeFolder: - payload, err := toCreateOrUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + payload, err := toPartialUpdateFolderTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } - _, err = r.client.DefaultAPI.CreateOrUpdateFolderTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateFolderTelemetryLinkPayload(*payload).Execute() + _, err = r.client.DefaultAPI.PartialUpdateFolderTelemetryLink(ctx, resourceID, regionId).PartialUpdateFolderTelemetryLinkPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return @@ -430,19 +430,19 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = core.LogResponse(ctx) - response, err = wait.CreateOrUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + response, err = wait.PartialUpdateFolderTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return } case resourceTypeProject: - payload, err := toCreateOrUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) + payload, err := toPartialUpdateProjectTelemetryLinkPayload(ctx, resp.Diagnostics, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Creating API payload: %v", err)) return } - _, err = r.client.DefaultAPI.CreateOrUpdateProjectTelemetryLink(ctx, resourceID, regionId).CreateOrUpdateProjectTelemetryLinkPayload(*payload).Execute() + _, err = r.client.DefaultAPI.PartialUpdateProjectTelemetryLink(ctx, resourceID, regionId).PartialUpdateProjectTelemetryLinkPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Calling API: %v", err)) return @@ -450,7 +450,7 @@ func (r *telemetryLinkInstanceResource) Update(ctx context.Context, req resource ctx = core.LogResponse(ctx) - response, err = wait.CreateOrUpdateOrganizationTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) + response, err = wait.PartialUpdateProjectTelemetryLinkWaitHandler(ctx, r.client.DefaultAPI, resourceID, regionId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryLink", fmt.Sprintf("Waiting for TelemetryLink to become active: %v", err)) return @@ -553,6 +553,45 @@ func (r *telemetryLinkInstanceResource) ImportState(ctx context.Context, req res tflog.Info(ctx, "TelemetryLink state imported") } +func toPartialUpdateOrganizationTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.PartialUpdateOrganizationTelemetryLinkPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetrylink.PartialUpdateOrganizationTelemetryLinkPayload{ + DisplayName: model.DisplayName.ValueStringPointer(), + Description: model.Description.ValueStringPointer(), + TelemetryRouterId: model.TelemetryRouterID.ValueStringPointer(), + AccessToken: model.AccessToken.ValueStringPointer(), + }, nil +} + +func toPartialUpdateFolderTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.PartialUpdateFolderTelemetryLinkPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetrylink.PartialUpdateFolderTelemetryLinkPayload{ + DisplayName: model.DisplayName.ValueStringPointer(), + Description: model.Description.ValueStringPointer(), + TelemetryRouterId: model.TelemetryRouterID.ValueStringPointer(), + AccessToken: model.AccessToken.ValueStringPointer(), + }, nil +} + +func toPartialUpdateProjectTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.PartialUpdateProjectTelemetryLinkPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetrylink.PartialUpdateProjectTelemetryLinkPayload{ + DisplayName: model.DisplayName.ValueStringPointer(), + Description: model.Description.ValueStringPointer(), + TelemetryRouterId: model.TelemetryRouterID.ValueStringPointer(), + AccessToken: model.AccessToken.ValueStringPointer(), + }, nil +} + func toCreateOrUpdateOrganizationTelemetryLinkPayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload, error) { if model == nil { return nil, fmt.Errorf("missing model") diff --git a/stackit/internal/services/telemetrylink/link/resource_test.go b/stackit/internal/services/telemetrylink/link/resource_test.go index d5d736647..446bd2faf 100644 --- a/stackit/internal/services/telemetrylink/link/resource_test.go +++ b/stackit/internal/services/telemetrylink/link/resource_test.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/stackitcloud/stackit-sdk-go/core/utils" telemetrylink "github.com/stackitcloud/stackit-sdk-go/services/telemetrylink/v1betaapi" ) @@ -63,9 +62,9 @@ func TestMapFields(t *testing.T) { { description: "max values", input: fixtureLink(func(link *telemetrylink.TelemetryLinkResponse) { - link.Description = utils.Ptr("description") + link.Description = new("description") link.DisplayName = "display-name" - link.AccessToken = utils.Ptr("access-token") + link.AccessToken = new("access-token") link.TelemetryRouterId = "tlmr-id" }), expected: fixtureModel(func(model *Model) { @@ -129,7 +128,7 @@ func TestToCreateOrUpdateOrganizationTelemetryLinkPayload(t *testing.T) { model.TelemetryRouterID = types.StringValue("tlmr_id") }), expected: &telemetrylink.CreateOrUpdateOrganizationTelemetryLinkPayload{ - Description: utils.Ptr("description"), + Description: new("description"), DisplayName: "display-name", AccessToken: "access-token", TelemetryRouterId: "tlmr_id", @@ -182,7 +181,7 @@ func TestToCreateOrUpdateFolderTelemetryLinkPayload(t *testing.T) { model.TelemetryRouterID = types.StringValue("tlmr_id") }), expected: &telemetrylink.CreateOrUpdateFolderTelemetryLinkPayload{ - Description: utils.Ptr("description"), + Description: new("description"), DisplayName: "display-name", AccessToken: "access-token", TelemetryRouterId: "tlmr_id", @@ -235,7 +234,7 @@ func TestToCreateOrUpdateProjectTelemetryLinkPayload(t *testing.T) { model.TelemetryRouterID = types.StringValue("tlmr_id") }), expected: &telemetrylink.CreateOrUpdateProjectTelemetryLinkPayload{ - Description: utils.Ptr("description"), + Description: new("description"), DisplayName: "display-name", AccessToken: "access-token", TelemetryRouterId: "tlmr_id", @@ -262,3 +261,162 @@ func TestToCreateOrUpdateProjectTelemetryLinkPayload(t *testing.T) { }) } } + +func TestToPartialUpdateOrganizationTelemetryLinkPayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetrylink.PartialUpdateOrganizationTelemetryLinkPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetrylink.PartialUpdateOrganizationTelemetryLinkPayload{ + DisplayName: new("name"), + AccessToken: new(""), + TelemetryRouterId: new("tlmrid"), + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.AccessToken = types.StringValue("access-token") + model.TelemetryRouterID = types.StringValue("tlmr_id") + }), + expected: &telemetrylink.PartialUpdateOrganizationTelemetryLinkPayload{ + Description: new("description"), + DisplayName: new("display-name"), + AccessToken: new("access-token"), + TelemetryRouterId: new("tlmr_id"), + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreateOrUpdateOrganizationTelemetryLinkPayload(t.Context(), diag.Diagnostics{}, tt.model) + if tt.wantErrMessage != "" && (err == nil || err.Error() != tt.wantErrMessage) { + t.Fatalf("Expected error: %v, got: %v", tt.wantErrMessage, err) + } + if tt.wantErrMessage == "" && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + diff := cmp.Diff(got, tt.expected) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func TestToPartialUpdateFolderTelemetryLinkPayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetrylink.PartialUpdateFolderTelemetryLinkPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetrylink.PartialUpdateFolderTelemetryLinkPayload{ + DisplayName: new("name"), + AccessToken: new(""), + TelemetryRouterId: new("tlmrid"), + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.AccessToken = types.StringValue("access-token") + model.TelemetryRouterID = types.StringValue("tlmr_id") + }), + expected: &telemetrylink.PartialUpdateFolderTelemetryLinkPayload{ + Description: new("description"), + DisplayName: new("display-name"), + AccessToken: new("access-token"), + TelemetryRouterId: new("tlmr_id"), + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreateOrUpdateFolderTelemetryLinkPayload(t.Context(), diag.Diagnostics{}, tt.model) + if tt.wantErrMessage != "" && (err == nil || err.Error() != tt.wantErrMessage) { + t.Fatalf("Expected error: %v, got: %v", tt.wantErrMessage, err) + } + if tt.wantErrMessage == "" && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + diff := cmp.Diff(got, tt.expected) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func TestToPartialUpdateProjectTelemetryLinkPayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetrylink.PartialUpdateProjectTelemetryLinkPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetrylink.PartialUpdateProjectTelemetryLinkPayload{ + DisplayName: new("name"), + AccessToken: new(""), + TelemetryRouterId: new("tlmrid"), + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.AccessToken = types.StringValue("access-token") + model.TelemetryRouterID = types.StringValue("tlmr_id") + }), + expected: &telemetrylink.PartialUpdateProjectTelemetryLinkPayload{ + Description: new("description"), + DisplayName: new("display-name"), + AccessToken: new("access-token"), + TelemetryRouterId: new("tlmr_id"), + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreateOrUpdateProjectTelemetryLinkPayload(t.Context(), diag.Diagnostics{}, tt.model) + if tt.wantErrMessage != "" && (err == nil || err.Error() != tt.wantErrMessage) { + t.Fatalf("Expected error: %v, got: %v", tt.wantErrMessage, err) + } + if tt.wantErrMessage == "" && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + diff := cmp.Diff(got, tt.expected) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +}