From f21e2369aafa98777b1c04108ffbb16019460026 Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Mon, 27 Apr 2026 09:40:34 +0200 Subject: [PATCH 1/4] feat: TelemetryRouter resources and datasources --- go.mod | 1 + go.sum | 2 + stackit/internal/core/core.go | 1 + .../telemetryrouter/accesstoken/datasource.go | 216 +++ .../accesstoken/datasource_test.go | 94 ++ .../telemetryrouter/accesstoken/resource.go | 547 ++++++++ .../accesstoken/resource_test.go | 245 ++++ .../telemetryrouter/destination/datasource.go | 467 +++++++ .../destination/datasource_test.go | 191 +++ .../telemetryrouter/destination/resource.go | 1067 +++++++++++++++ .../destination/resource_test.go | 597 +++++++++ .../telemetryrouter/instance/datasource.go | 190 +++ .../telemetryrouter/instance/resource.go | 588 +++++++++ .../telemetryrouter/instance/resource_test.go | 266 ++++ .../telemetryrouter_acc_test.go | 1165 +++++++++++++++++ .../testdata/access-token-max.tf | 22 + .../testdata/access-token-min.tf | 17 + .../testdata/destination-otlp-basic-auth.tf | 50 + .../testdata/destination-otlp-bearer-token.tf | 46 + .../testdata/destination-s3.tf | 52 + .../telemetryrouter/testdata/instance-max.tf | 30 + .../telemetryrouter/testdata/instance-min.tf | 10 + .../services/telemetryrouter/utils/utils.go | 29 + .../telemetryrouter/utils/utils_test.go | 93 ++ stackit/internal/testutil/testutil.go | 2 + stackit/provider.go | 16 + 26 files changed, 6004 insertions(+) create mode 100644 stackit/internal/services/telemetryrouter/accesstoken/datasource.go create mode 100644 stackit/internal/services/telemetryrouter/accesstoken/datasource_test.go create mode 100644 stackit/internal/services/telemetryrouter/accesstoken/resource.go create mode 100644 stackit/internal/services/telemetryrouter/accesstoken/resource_test.go create mode 100644 stackit/internal/services/telemetryrouter/destination/datasource.go create mode 100644 stackit/internal/services/telemetryrouter/destination/datasource_test.go create mode 100644 stackit/internal/services/telemetryrouter/destination/resource.go create mode 100644 stackit/internal/services/telemetryrouter/destination/resource_test.go create mode 100644 stackit/internal/services/telemetryrouter/instance/datasource.go create mode 100644 stackit/internal/services/telemetryrouter/instance/resource.go create mode 100644 stackit/internal/services/telemetryrouter/instance/resource_test.go create mode 100644 stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go create mode 100644 stackit/internal/services/telemetryrouter/testdata/access-token-max.tf create mode 100644 stackit/internal/services/telemetryrouter/testdata/access-token-min.tf create mode 100644 stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf create mode 100644 stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf create mode 100644 stackit/internal/services/telemetryrouter/testdata/destination-s3.tf create mode 100644 stackit/internal/services/telemetryrouter/testdata/instance-max.tf create mode 100644 stackit/internal/services/telemetryrouter/testdata/instance-min.tf create mode 100644 stackit/internal/services/telemetryrouter/utils/utils.go create mode 100644 stackit/internal/services/telemetryrouter/utils/utils_test.go diff --git a/go.mod b/go.mod index e68fae315..939d7534c 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/sfs v0.7.0 github.com/stackitcloud/stackit-sdk-go/services/ske v1.12.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.9.0 + github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.1.0 github.com/teambition/rrule-go v1.8.2 golang.org/x/mod v0.35.0 ) diff --git a/go.sum b/go.sum index ac675d7c3..0a55c07af 100644 --- a/go.sum +++ b/go.sum @@ -732,6 +732,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.12.0 h1:G6iUFDlrwCkCkwSV3 github.com/stackitcloud/stackit-sdk-go/services/ske v1.12.0/go.mod h1:cSRF2ARIB6dKmvZ12Z5h1usKQligeZJ1JOiJk6Ds3wE= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.9.0 h1:dv+8GYcI2G0BQvMN5Qb4kaCREaj9n2lRzj9sb29NEZg= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.9.0/go.mod h1:HTpVer71ZfpKozpzWVv7EbCPB5ko8vgw4VmFE38lB9I= +github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.1.0 h1:0WtujHIj8cPZhf03IuLhAzl/WOf8CQJWWyo0Qm/FNwI= +github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.1.0/go.mod h1:zdOIf7vz4Ay7DmxGO7UoUUd161WcozixrBemBq0aSyE= 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..712e27186 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 + TelemetryRouterCustomEndpoint string EnableBetaResources bool Experiments []string diff --git a/stackit/internal/services/telemetryrouter/accesstoken/datasource.go b/stackit/internal/services/telemetryrouter/accesstoken/datasource.go new file mode 100644 index 000000000..005b4aaef --- /dev/null +++ b/stackit/internal/services/telemetryrouter/accesstoken/datasource.go @@ -0,0 +1,216 @@ +package accesstoken + +import ( + "context" + "fmt" + "net/http" + "time" + + "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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &telemetryRouterAccessTokenDataSource{} + _ datasource.DataSourceWithConfigure = &telemetryRouterAccessTokenDataSource{} +) + +type DataSourceModel struct { + ID types.String `tfsdk:"id"` // Required by Terraform + AccessTokenID types.String `tfsdk:"access_token_id"` + InstanceID types.String `tfsdk:"instance_id"` + Region types.String `tfsdk:"region"` + ProjectID types.String `tfsdk:"project_id"` + CreatorID types.String `tfsdk:"creator_id"` + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + ExpirationTime types.String `tfsdk:"expiration_time"` + Status types.String `tfsdk:"status"` +} + +func NewTelemetryRouterAccessTokenDataSource() datasource.DataSource { + return &telemetryRouterAccessTokenDataSource{} +} + +type telemetryRouterAccessTokenDataSource struct { + client *telemetryrouter.APIClient + providerData core.ProviderData +} + +func (d *telemetryRouterAccessTokenDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetryrouter_access_token" +} + +func (d *telemetryRouterAccessTokenDataSource) 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, "TelemetryRouter client configured") +} + +func (d *telemetryRouterAccessTokenDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryRouter access token data source schema. %s", core.DatasourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + }, + "access_token_id": schema.StringAttribute{ + Description: schemaDescriptions["access_token_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_id"], + Required: 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, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "creator_id": schema.StringAttribute{ + Description: schemaDescriptions["creator_id"], + Computed: true, + }, + "description": schema.StringAttribute{ + Description: schemaDescriptions["description"], + Computed: true, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Computed: true, + Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, + }, + "expiration_time": schema.StringAttribute{ + Description: schemaDescriptions["expiration_time"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (d *telemetryRouterAccessTokenDataSource) 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) + + projectID := model.ProjectID.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) + instanceID := model.InstanceID.ValueString() + accessTokenID := model.AccessTokenID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "access_token_id", accessTokenID) + + accessTokenResponse, err := d.client.DefaultAPI.GetAccessToken(ctx, projectID, region, instanceID, accessTokenID).Execute() + if err != nil { + tfutils.LogError( + ctx, + &resp.Diagnostics, + err, + "Reading TelemetryRouter access token", + fmt.Sprintf("Access token with ID %q does not exist in project %q.", accessTokenID, projectID), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectID), + }, + ) + resp.State.RemoveResource(ctx) + return + } + ctx = core.LogResponse(ctx) + + err = mapDataSourceFields(ctx, accessTokenResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter access token", 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, "TelemetryRouter access token read", map[string]interface{}{ + "access_token_id": accessTokenID, + }) +} + +func mapDataSourceFields(ctx context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *DataSourceModel) error { + if accessToken == nil { + return fmt.Errorf("access token is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + + var accessTokenID string + if model.AccessTokenID.ValueString() != "" { + accessTokenID = model.AccessTokenID.ValueString() + } else if accessToken.Id != "" { + accessTokenID = accessToken.Id + } else { + return fmt.Errorf("access token id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), accessTokenID) + model.AccessTokenID = types.StringValue(accessTokenID) + model.Region = types.StringValue(model.Region.ValueString()) + model.CreatorID = types.StringValue(accessToken.CreatorId) + if accessToken.Description != nil && *accessToken.Description != "" { + model.Description = types.StringPointerValue(accessToken.Description) + } + model.DisplayName = types.StringValue(accessToken.DisplayName) + model.Status = types.StringValue(accessToken.Status) + + model.ExpirationTime = types.StringNull() + if accessToken.HasExpirationTime() && accessToken.ExpirationTime.Get() != nil { + model.ExpirationTime = types.StringValue(accessToken.ExpirationTime.Get().Format(time.RFC3339)) + } + + return nil +} diff --git a/stackit/internal/services/telemetryrouter/accesstoken/datasource_test.go b/stackit/internal/services/telemetryrouter/accesstoken/datasource_test.go new file mode 100644 index 000000000..2a5966f91 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/accesstoken/datasource_test.go @@ -0,0 +1,94 @@ +package accesstoken + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" +) + +func fixtureDataSourceModel(mods ...func(model *DataSourceModel)) *DataSourceModel { + model := &DataSourceModel{ + ID: types.StringValue("pid,rid,iid,atid"), + AccessTokenID: types.StringValue("atid"), + InstanceID: types.StringValue("iid"), + Region: types.StringValue("rid"), + ProjectID: types.StringValue("pid"), + CreatorID: types.StringValue(""), + Description: types.String{}, + DisplayName: types.StringValue(""), + ExpirationTime: types.String{}, + Status: types.StringValue("active"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapDataSourceFields(t *testing.T) { + tests := []struct { + description string + input *telemetryrouter.GetAccessTokenResponse + expected *DataSourceModel + wantErr bool + }{ + { + description: "min values", + input: fixtureGetAccessToken(), + expected: fixtureDataSourceModel(), + }, + { + description: "max values", + input: fixtureGetAccessToken(func(accessToken *telemetryrouter.GetAccessTokenResponse) { + accessToken.Description = utils.Ptr("description") + accessToken.DisplayName = "display-name" + accessToken.CreatorId = "testUser" + accessToken.ExpirationTime = *telemetryrouter.NewNullableTime(&testTime) + }), + expected: fixtureDataSourceModel(func(model *DataSourceModel) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CreatorID = types.StringValue("testUser") + model.ExpirationTime = types.StringValue(testTime.Format(time.RFC3339)) + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureDataSourceModel(), + }, + { + description: "nil access token id", + input: &telemetryrouter.GetAccessTokenResponse{}, + wantErr: true, + expected: fixtureDataSourceModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &DataSourceModel{ + ProjectID: tt.expected.ProjectID, + Region: tt.expected.Region, + InstanceID: tt.expected.InstanceID, + } + 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/telemetryrouter/accesstoken/resource.go b/stackit/internal/services/telemetryrouter/accesstoken/resource.go new file mode 100644 index 000000000..588b751bf --- /dev/null +++ b/stackit/internal/services/telemetryrouter/accesstoken/resource.go @@ -0,0 +1,547 @@ +package accesstoken + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "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/int32planmodifier" + "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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &telemetryRouterAccessTokenResource{} + _ resource.ResourceWithConfigure = &telemetryRouterAccessTokenResource{} + _ resource.ResourceWithImportState = &telemetryRouterAccessTokenResource{} + _ resource.ResourceWithModifyPlan = &telemetryRouterAccessTokenResource{} +) + +const ( + AccessTokenStatusActive = "active" + AccessTokenStatusExpired = "expired" + AccessTokenStatusDeleting = "deleting" +) + +var schemaDescriptions = map[string]string{ + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`access_token_id`\".", + "access_token_id": "The access token ID", + "instance_id": "The TelemetryRouter instance ID associated with the access token", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "project_id": "STACKIT project ID associated with the TelemetryRouter access token", + "display_name": "The displayed name of the access token", + "description": "The description of the access token", + "access_token": "The generated access token", + "creator_id": "The user who created the access token", + "expiration_time": "The date and time an access token will expire at (inclusively)", + "ttl": "The time-to-live (TTL) in days for the access token. If not set, token will not expire", + "status": fmt.Sprintf( + "The status of the access token. %s", + tfutils.FormatPossibleValues(AccessTokenStatusActive, AccessTokenStatusExpired, AccessTokenStatusDeleting), + ), +} + +type Model struct { + ID types.String `tfsdk:"id"` // Required by Terraform + AccessTokenID types.String `tfsdk:"access_token_id"` + InstanceID types.String `tfsdk:"instance_id"` + Region types.String `tfsdk:"region"` + ProjectID types.String `tfsdk:"project_id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + AccessToken types.String `tfsdk:"access_token"` + CreatorID types.String `tfsdk:"creator_id"` + ExpirationTime types.String `tfsdk:"expiration_time"` + Ttl types.Int32 `tfsdk:"ttl"` + Status types.String `tfsdk:"status"` +} + +type telemetryRouterAccessTokenResource struct { + client *telemetryrouter.APIClient + providerData core.ProviderData +} + +func NewTelemetryRouterAccessTokenResource() resource.Resource { + return &telemetryRouterAccessTokenResource{} +} + +func (r *telemetryRouterAccessTokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + r.client = utils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "TelemetryRouter client configured") +} + +func (r *telemetryRouterAccessTokenResource) 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 *telemetryRouterAccessTokenResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetryrouter_access_token" +} + +func (r *telemetryRouterAccessTokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryRouter access token resource schema. %s", core.ResourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "access_token_id": schema.StringAttribute{ + Description: schemaDescriptions["access_token_id"], + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + 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(), + }, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "creator_id": schema.StringAttribute{ + Description: schemaDescriptions["creator_id"], + Computed: true, + }, + "access_token": schema.StringAttribute{ + Description: schemaDescriptions["access_token"], + Computed: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "description": schema.StringAttribute{ + Description: schemaDescriptions["description"], + Optional: true, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Required: true, + }, + "expiration_time": schema.StringAttribute{ + Description: schemaDescriptions["expiration_time"], + Computed: true, + }, + "ttl": schema.Int32Attribute{ + Description: schemaDescriptions["ttl"], + Optional: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.RequiresReplace(), + }, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (r *telemetryRouterAccessTokenResource) 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) + + instanceId := model.InstanceID.ValueString() + projectId := model.ProjectID.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toCreatePayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter access token", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + createResp, err := r.client.DefaultAPI.CreateAccessToken(ctx, projectId, region, instanceId).CreateAccessTokenPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + if createResp.Id == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter access token", "Got empty credential id") + return + } + accessTokenId := createResp.Id + ctx = tflog.SetField(ctx, "access_token_id", accessTokenId) + + err = mapCreateFields(ctx, createResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter access token", 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, "TelemetryRouter instance created") +} + +func (r *telemetryRouterAccessTokenResource) 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) + + projectID := model.ProjectID.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + instanceID := model.InstanceID.ValueString() + accessTokenID := model.AccessTokenID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "access_token_id", accessTokenID) + + accessTokenResponse, err := r.client.DefaultAPI.GetAccessToken(ctx, projectID, region, instanceID, accessTokenID).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 TelemetryRouter access token", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapGetFields(ctx, accessTokenResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter access token", 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, "TelemetryRouter access token read", map[string]interface{}{ + "access_token_id": accessTokenID, + }) +} + +func (r *telemetryRouterAccessTokenResource) 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) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + accessTokenID := model.AccessTokenID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "access_token_id", accessTokenID) + + payload, err := toUpdatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter access token", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + accessTokenResponse, err := r.client.DefaultAPI.UpdateAccessToken(ctx, projectID, region, instanceID, accessTokenID).UpdateAccessTokenPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapUpdateFields(ctx, accessTokenResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter access token", 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, "TelemetryRouter access token updated", map[string]interface{}{ + "access_token_id": accessTokenID, + }) +} + +func (r *telemetryRouterAccessTokenResource) 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) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + accessTokenID := model.AccessTokenID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "access_token_id", accessTokenID) + + err := r.client.DefaultAPI.DeleteAccessToken(ctx, projectID, region, instanceID, accessTokenID).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryRouter access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + tflog.Info(ctx, "TelemetryRouter access token deleted") +} + +func (r *telemetryRouterAccessTokenResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing TelemetryRouter access token", fmt.Sprintf("Invalid import ID %q: expected format is `project_id`,`region`,`instance_id`,`access_token_id`", req.ID)) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("access_token_id"), idParts[3])...) + + core.LogAndAddWarning(ctx, &resp.Diagnostics, + "TelemetryRouter access token imported with empty token", + "The token is not imported as they are only available upon creation of a new access token. The token field will be empty.", + ) + + tflog.Info(ctx, "TelemetryRouter access token state imported") +} + +func toCreatePayload(_ context.Context, _ diag.Diagnostics, model *Model) (*telemetryrouter.CreateAccessTokenPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetryrouter.CreateAccessTokenPayload{ + Description: model.Description.ValueStringPointer(), + DisplayName: model.DisplayName.ValueString(), + Ttl: *telemetryrouter.NewNullableInt32(model.Ttl.ValueInt32Pointer()), + }, nil +} + +func mapCreateFields(ctx context.Context, accessToken *telemetryrouter.CreateAccessTokenResponse, model *Model) error { + if accessToken == nil { + return fmt.Errorf("access token is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + + var accessTokenID string + if model.AccessTokenID.ValueString() != "" { + accessTokenID = model.AccessTokenID.ValueString() + } else if accessToken.Id != "" { + accessTokenID = accessToken.Id + } else { + return fmt.Errorf("access token id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), accessTokenID) + model.AccessTokenID = types.StringValue(accessTokenID) + model.Region = types.StringValue(model.Region.ValueString()) + model.CreatorID = types.StringValue(accessToken.CreatorId) + model.Description = types.StringPointerValue(accessToken.Description) + model.DisplayName = types.StringValue(accessToken.DisplayName) + model.Status = types.StringValue(accessToken.Status) + + model.ExpirationTime = types.StringNull() + if accessToken.HasExpirationTime() && accessToken.ExpirationTime.Get() != nil { + model.ExpirationTime = types.StringValue(accessToken.ExpirationTime.Get().Format(time.RFC3339)) + } + + if accessToken.AccessToken != "" { + model.AccessToken = types.StringValue(accessToken.AccessToken) + } + + return nil +} + +func mapGetFields(ctx context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *Model) error { + if accessToken == nil { + return fmt.Errorf("access token is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + + var accessTokenID string + if model.AccessTokenID.ValueString() != "" { + accessTokenID = model.AccessTokenID.ValueString() + } else if accessToken.Id != "" { + accessTokenID = accessToken.Id + } else { + return fmt.Errorf("access token id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), accessTokenID) + model.AccessTokenID = types.StringValue(accessTokenID) + model.Region = types.StringValue(model.Region.ValueString()) + model.CreatorID = types.StringValue(accessToken.CreatorId) + model.Description = types.StringPointerValue(accessToken.Description) + model.DisplayName = types.StringValue(accessToken.DisplayName) + model.Status = types.StringValue(accessToken.Status) + + model.ExpirationTime = types.StringNull() + if accessToken.HasExpirationTime() && accessToken.ExpirationTime.Get() != nil { + model.ExpirationTime = types.StringValue(accessToken.ExpirationTime.Get().Format(time.RFC3339)) + } + + return nil +} + +func mapUpdateFields(ctx context.Context, accessToken *telemetryrouter.UpdateAccessTokenResponse, model *Model) error { + if accessToken == nil { + return fmt.Errorf("access token is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + + var accessTokenID string + if model.AccessTokenID.ValueString() != "" { + accessTokenID = model.AccessTokenID.ValueString() + } else if accessToken.Id != "" { + accessTokenID = accessToken.Id + } else { + return fmt.Errorf("access token id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), accessTokenID) + model.AccessTokenID = types.StringValue(accessTokenID) + model.Region = types.StringValue(model.Region.ValueString()) + model.CreatorID = types.StringValue(accessToken.CreatorId) + if accessToken.Description != nil && *accessToken.Description != "" { + model.Description = types.StringPointerValue(accessToken.Description) + } + model.DisplayName = types.StringValue(accessToken.DisplayName) + model.Status = types.StringValue(accessToken.Status) + + model.ExpirationTime = types.StringNull() + if accessToken.HasExpirationTime() && accessToken.ExpirationTime.Get() != nil { + model.ExpirationTime = types.StringValue(accessToken.ExpirationTime.Get().Format(time.RFC3339)) + } + + return nil +} + +func toUpdatePayload(model *Model) (*telemetryrouter.UpdateAccessTokenPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + return &telemetryrouter.UpdateAccessTokenPayload{ + Description: *telemetryrouter.NewNullableString(conversion.StringValueToPointer(model.Description)), + DisplayName: *telemetryrouter.NewNullableString(conversion.StringValueToPointer(model.DisplayName)), + }, nil +} diff --git a/stackit/internal/services/telemetryrouter/accesstoken/resource_test.go b/stackit/internal/services/telemetryrouter/accesstoken/resource_test.go new file mode 100644 index 000000000..6acc2a57c --- /dev/null +++ b/stackit/internal/services/telemetryrouter/accesstoken/resource_test.go @@ -0,0 +1,245 @@ +package accesstoken + +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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" +) + +var testTime = time.Now() + +func fixtureGetAccessToken(mods ...func(accessToken *telemetryrouter.GetAccessTokenResponse)) *telemetryrouter.GetAccessTokenResponse { + accessToken := &telemetryrouter.GetAccessTokenResponse{ + Id: "atid", + Status: AccessTokenStatusActive, + } + for _, mod := range mods { + mod(accessToken) + } + return accessToken +} + +func fixtureModel(mods ...func(model *Model)) *Model { + model := &Model{ + ID: types.StringValue("pid,rid,iid,atid"), + AccessTokenID: types.StringValue("atid"), + InstanceID: types.StringValue("iid"), + Region: types.StringValue("rid"), + ProjectID: types.StringValue("pid"), + CreatorID: types.StringValue(""), + Description: types.String{}, + DisplayName: types.StringValue(""), + ExpirationTime: types.String{}, + Ttl: types.Int32{}, + Status: types.StringValue(AccessTokenStatusActive), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapGetFields(t *testing.T) { + tests := []struct { + description string + input *telemetryrouter.GetAccessTokenResponse + expected *Model + wantErr bool + }{ + { + description: "min values", + input: fixtureGetAccessToken(), + expected: fixtureModel(), + }, + { + description: "max values", + input: fixtureGetAccessToken(func(accessToken *telemetryrouter.GetAccessTokenResponse) { + accessToken.Description = utils.Ptr("description") + accessToken.DisplayName = "display-name" + accessToken.CreatorId = "testUser" + accessToken.ExpirationTime = *telemetryrouter.NewNullableTime(&testTime) + }), + expected: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CreatorID = types.StringValue("testUser") + model.ExpirationTime = types.StringValue(testTime.Format(time.RFC3339)) + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureModel(), + }, + { + description: "nil access token id", + input: &telemetryrouter.GetAccessTokenResponse{}, + wantErr: true, + expected: fixtureModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ProjectID: tt.expected.ProjectID, + Region: tt.expected.Region, + InstanceID: tt.expected.InstanceID, + } + err := mapGetFields(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 TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetryrouter.CreateAccessTokenPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetryrouter.CreateAccessTokenPayload{ + Ttl: *telemetryrouter.NewNullableInt32(nil), + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.Ttl = types.Int32Value(7) + }), + expected: &telemetryrouter.CreateAccessTokenPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + Ttl: *telemetryrouter.NewNullableInt32(utils.Ptr(int32(7))), + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreatePayload(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, cmp.Comparer(compareNullableString), cmp.Comparer(compareNullableInt32)) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetryrouter.UpdateAccessTokenPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetryrouter.UpdateAccessTokenPayload{ + DisplayName: *telemetryrouter.NewNullableString(utils.Ptr("")), + Description: *telemetryrouter.NewNullableString(nil), + }, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + }), + expected: &telemetryrouter.UpdateAccessTokenPayload{ + Description: *telemetryrouter.NewNullableString(utils.Ptr("description")), + DisplayName: *telemetryrouter.NewNullableString(utils.Ptr("display-name")), + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toUpdatePayload(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, cmp.Comparer(compareNullableString), cmp.Comparer(compareNullableInt32)) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func compareNullableString(x, y telemetryrouter.NullableString) bool { + if x.IsSet() != y.IsSet() { + return false + } + + if !x.IsSet() && !y.IsSet() { + return true + } + + valX := x.Get() + valY := y.Get() + + if valX == nil || valY == nil { + return valX == valY + } + + return *valX == *valY +} + +func compareNullableInt32(x, y telemetryrouter.NullableInt32) bool { + if x.IsSet() != y.IsSet() { + return false + } + + if !x.IsSet() && !y.IsSet() { + return true + } + + valX := x.Get() + valY := y.Get() + + if valX == nil || valY == nil { + return valX == valY + } + + return *valX == *valY +} diff --git a/stackit/internal/services/telemetryrouter/destination/datasource.go b/stackit/internal/services/telemetryrouter/destination/datasource.go new file mode 100644 index 000000000..64b34ce13 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/destination/datasource.go @@ -0,0 +1,467 @@ +package destination + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &telemetryRouterDestinationDataSource{} +) + +func NewTelemetryRouterDestinationDataSource() datasource.DataSource { + return &telemetryRouterDestinationDataSource{} +} + +type DatasourceModel struct { + ID types.String `tfsdk:"id"` // Required by Terraform + InstanceID types.String `tfsdk:"instance_id"` + DestinationID types.String `tfsdk:"destination_id"` + Region types.String `tfsdk:"region"` + ProjectID types.String `tfsdk:"project_id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + Config types.Object `tfsdk:"config"` + CreationTime types.String `tfsdk:"creation_time"` + CredentialType types.String `tfsdk:"credential_type"` + Status types.String `tfsdk:"status"` +} + +// Struct corresponding to DatasourceModel.Config +type datasourceConfig struct { + ConfigType types.String `tfsdk:"config_type"` + Filter types.Object `tfsdk:"filter"` + OpenTelemetry types.Object `tfsdk:"opentelemetry"` + S3 types.Object `tfsdk:"s3"` +} + +// Types corresponding to datasourceConfig +var datasourceConfigTypes = map[string]attr.Type{ + "config_type": basetypes.StringType{}, + "filter": basetypes.ObjectType{datasourceFilterTypes}, + "opentelemetry": basetypes.ObjectType{datasourceOpenTelemetryTypes}, + "s3": basetypes.ObjectType{datasourceS3Types}, +} + +// Struct corresponding to datasourceFilter +type datasourceFilter struct { + Attributes types.List `tfsdk:"attributes"` +} + +// Types corresponding to datasourceFilter +var datasourceFilterTypes = map[string]attr.Type{ + "attributes": basetypes.ListType{ElemType: types.ObjectType{AttrTypes: datasourceAttributeTypes}}, +} + +// Struct corresponding to a single attribute +type datasourceAttribute struct { + Key types.String `tfsdk:"key"` + Level types.String `tfsdk:"level"` + Matcher types.String `tfsdk:"matcher"` + Values types.List `tfsdk:"values"` +} + +// Types corresponding to attributes +var datasourceAttributeTypes = map[string]attr.Type{ + "key": basetypes.StringType{}, + "level": basetypes.StringType{}, + "matcher": basetypes.StringType{}, + "values": basetypes.ListType{ElemType: types.StringType}, +} + +// Struct corresponding to opentelemetry +type datasourceOpenTelemetry struct { + Uri types.String `tfsdk:"uri"` +} + +// Types corresponding to opentelemetry +var datasourceOpenTelemetryTypes = map[string]attr.Type{ + "uri": basetypes.StringType{}, +} + +// Struct corresponding to s3 +type datasourceS3 struct { + Bucket types.String `tfsdk:"bucket"` + Endpoint types.String `tfsdk:"endpoint"` +} + +// Types corresponding to s3 +var datasourceS3Types = map[string]attr.Type{ + "bucket": basetypes.StringType{}, + "endpoint": basetypes.StringType{}, +} + +type telemetryRouterDestinationDataSource struct { + client *telemetryrouter.APIClient + providerData core.ProviderData +} + +func (d *telemetryRouterDestinationDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetryrouter_destination" +} + +func (d *telemetryRouterDestinationDataSource) 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, "TelemetryRouter client configured") +} + +func (d *telemetryRouterDestinationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryRouter destination data source schema. %s", core.DatasourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + }, + "destination_id": schema.StringAttribute{ + Description: schemaDescriptions["destination_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: schemaDescriptions["region"], + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Computed: true, + }, + "description": schema.StringAttribute{ + Description: schemaDescriptions["description"], + Optional: true, + Computed: true, + }, + "config": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "config_type": schema.StringAttribute{ + Description: schemaDescriptions["config.config_type"], + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("OpenTelemetry", "S3"), + }, + }, + "filter": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.filter"], + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "attributes": schema.ListNestedAttribute{ + Description: schemaDescriptions["config.filter.attributes"], + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: schemaDescriptions["config.filter.attributes.key"], + Computed: true, + }, + "level": schema.StringAttribute{ + Description: schemaDescriptions["config.filter.attributes.level"], + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("resource", "scope", "logRecord"), + }, + }, + "matcher": schema.StringAttribute{ + Description: schemaDescriptions["config.filter.attributes.matcher"], + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("=", "!="), + }, + }, + "values": schema.ListAttribute{ + Description: schemaDescriptions["config.filter.attributes.values"], + ElementType: types.StringType, + Computed: true, + }, + }, + }, + }, + }, + }, + "opentelemetry": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.opentelemetry"], + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "uri": schema.StringAttribute{ + Description: schemaDescriptions["config.opentelemetry.uri"], + Computed: true, + }, + }, + }, + "s3": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.s3"], + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "bucket": schema.StringAttribute{ + Description: schemaDescriptions["config.s3.bucket"], + Computed: true, + }, + "endpoint": schema.StringAttribute{ + Description: schemaDescriptions["config.s3.endpoint"], + Computed: true, + }, + }, + }, + }, + }, + "creation_time": schema.StringAttribute{ + Description: schemaDescriptions["creation_time"], + Computed: true, + }, + "credential_type": schema.StringAttribute{ + Description: schemaDescriptions["credential_type"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (d *telemetryRouterDestinationDataSource) 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) + + projectID := model.ProjectID.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) + instanceID := model.InstanceID.ValueString() + destinationID := model.DestinationID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "destination_id", destinationID) + + destinationResponse, err := d.client.DefaultAPI.GetDestination(ctx, projectID, region, instanceID, destinationID).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 TelemetryRouter destination", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapDatasourceFields(ctx, destinationResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter destination", 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, "TelemetryRouter Destination read", map[string]interface{}{ + "instance_id": instanceID, + "destination_id": destinationID, + }) +} + +func mapDatasourceFields(ctx context.Context, destination *telemetryrouter.DestinationResponse, model *DatasourceModel) error { + if destination == nil { + return fmt.Errorf("destination is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + var destinationID string + if model.DestinationID.ValueString() != "" { + destinationID = model.DestinationID.ValueString() + } else if destination.Id != "" { + destinationID = destination.Id + } else { + return fmt.Errorf("destination id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), destinationID) + model.DestinationID = types.StringValue(destinationID) + model.DisplayName = types.StringValue(destination.DisplayName) + model.Description = types.StringPointerValue(destination.Description) + model.CredentialType = types.StringValue(destination.CredentialType) + model.CreationTime = types.StringValue(destination.CreationTime.Format(time.RFC3339)) + model.Status = types.StringValue(destination.Status) + + if err := mapDatasourceConfig(ctx, destination, model); err != nil { + return fmt.Errorf("map config: %w", err) + } + + return nil +} + +func mapDatasourceConfig(ctx context.Context, destination *telemetryrouter.DestinationResponse, model *DatasourceModel) error { + var conf datasourceConfig + + conf.ConfigType = types.StringValue(string(destination.Config.ConfigType)) + + if err := mapDatasourceFilter(ctx, &destination.Config, &conf); err != nil { + return err + } + + if err := mapDatasourceOpenTelemetry(ctx, &destination.Config, &conf); err != nil { + return err + } + + if err := mapDatasourceS3(ctx, &destination.Config, &conf); err != nil { + return err + } + + configValue, diags := types.ObjectValueFrom(ctx, datasourceConfigTypes, conf) + if diags.HasError() { + return fmt.Errorf("mapping config: %w", core.DiagsToError(diags)) + } + model.Config = configValue + + return nil +} + +func mapDatasourceFilter(ctx context.Context, apiConf *telemetryrouter.DestinationConfig, conf *datasourceConfig) error { + if apiConf.Filter == nil { + conf.Filter = types.ObjectNull(datasourceFilterTypes) + return nil + } + + attrList := []attr.Value{} + for _, currentAttr := range apiConf.Filter.Attributes { + values, diags := types.ListValueFrom(ctx, types.StringType, currentAttr.Values) + if diags.HasError() { + return fmt.Errorf("mapping filter values: %w", core.DiagsToError(diags)) + } + attrModel, diags := types.ObjectValueFrom(ctx, datasourceAttributeTypes, datasourceAttribute{ + Key: types.StringValue(currentAttr.Key), + Level: types.StringValue(string(currentAttr.Level)), + Matcher: types.StringValue(string(currentAttr.Matcher)), + Values: values, + }) + if diags.HasError() { + return fmt.Errorf("mapping filter config: %w", core.DiagsToError(diags)) + } + attrList = append(attrList, attrModel) + } + + var attrConfigs basetypes.ListValue + var diags diag.Diagnostics + if len(attrList) == 0 { + attrConfigs = types.ListNull(types.ObjectType{AttrTypes: datasourceAttributeTypes}) + } else { + attrConfigs, diags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: datasourceAttributeTypes}, attrList) + if diags.HasError() { + return fmt.Errorf("mapping attributes: %w", core.DiagsToError(diags)) + } + } + + filterValue, diags := types.ObjectValueFrom(ctx, datasourceFilterTypes, filter{ + Attributes: attrConfigs, + }) + if diags.HasError() { + return fmt.Errorf("mapping filter: %w", core.DiagsToError(diags)) + } + conf.Filter = filterValue + + return nil +} + +func mapDatasourceOpenTelemetry(ctx context.Context, apiConf *telemetryrouter.DestinationConfig, conf *datasourceConfig) error { + if apiConf.OpenTelemetry == nil { + conf.OpenTelemetry = types.ObjectNull(datasourceOpenTelemetryTypes) + return nil + } + + var ot datasourceOpenTelemetry + ot.Uri = types.StringValue(apiConf.OpenTelemetry.Uri) + + otModel, diags := types.ObjectValueFrom(ctx, datasourceOpenTelemetryTypes, ot) + if diags.HasError() { + return fmt.Errorf("mapping open telemetry: %w", core.DiagsToError(diags)) + } + + conf.OpenTelemetry = otModel + + return nil +} + +func mapDatasourceS3(ctx context.Context, apiConf *telemetryrouter.DestinationConfig, conf *datasourceConfig) error { + if apiConf.S3 == nil { + conf.S3 = types.ObjectNull(datasourceS3Types) + return nil + } + + var s3Struct datasourceS3 + s3Struct.Bucket = types.StringValue(apiConf.S3.Bucket) + s3Struct.Endpoint = types.StringValue(apiConf.S3.Endpoint) + + s3Model, diags := types.ObjectValueFrom(ctx, datasourceS3Types, s3Struct) + if diags.HasError() { + return fmt.Errorf("mapping s3: %w", core.DiagsToError(diags)) + } + + conf.S3 = s3Model + + return nil +} diff --git a/stackit/internal/services/telemetryrouter/destination/datasource_test.go b/stackit/internal/services/telemetryrouter/destination/datasource_test.go new file mode 100644 index 000000000..a2ef0e152 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/destination/datasource_test.go @@ -0,0 +1,191 @@ +package destination + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" +) + +func fixtureDatasourceModel(mods ...func(model *DatasourceModel)) *DatasourceModel { + cfg, _ := types.ObjectValueFrom(context.Background(), datasourceConfigTypes, datasourceConfig{ + ConfigType: types.StringValue(""), + Filter: types.ObjectNull(datasourceFilterTypes), + OpenTelemetry: types.ObjectNull(datasourceOpenTelemetryTypes), + S3: types.ObjectNull(datasourceS3Types), + }) + model := &DatasourceModel{ + ID: types.StringValue("pid,rid,iid,dsid"), + DestinationID: types.StringValue("dsid"), + InstanceID: types.StringValue("iid"), + Region: types.StringValue("rid"), + ProjectID: types.StringValue("pid"), + Description: types.String{}, + DisplayName: types.StringValue("test"), + Config: cfg, + CredentialType: types.StringValue(""), + CreationTime: types.StringValue(testTime.Format(time.RFC3339)), + Status: types.StringValue("active"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapDataSourceFields(t *testing.T) { + tests := []struct { + description string + input *telemetryrouter.DestinationResponse + expected *DatasourceModel + wantErr bool + }{ + { + description: "min values", + input: fixtureDestinationResponse(), + expected: fixtureDatasourceModel(), + }, + { + description: "OpenTelemetry with filter", + input: fixtureDestinationResponse(func(destination *telemetryrouter.DestinationResponse) { + destination.Description = utils.Ptr("description") + destination.DisplayName = "display-name" + destination.CredentialType = "bearerToken" + destination.Config = telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "logRecord", + Matcher: "=", + Values: []string{"a", "b"}, + }, + }, + }, + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BearerToken: utils.Ptr("bearer-token"), + Uri: "https://example.test", + }, + } + }), + expected: fixtureDatasourceModel(func(model *DatasourceModel) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []datasourceAttribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("logRecord"), + Matcher: types.StringValue("="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, datasourceFilterTypes, datasourceFilter{ + Attributes: attrs, + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, datasourceOpenTelemetryTypes, datasourceOpenTelemetry{ + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), datasourceConfigTypes, datasourceConfig{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: fltr, + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(datasourceS3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + }, + { + description: "S3 with filter", + input: fixtureDestinationResponse(func(destination *telemetryrouter.DestinationResponse) { + destination.Description = utils.Ptr("description") + destination.DisplayName = "display-name" + destination.CredentialType = "accessKey" + destination.Config = telemetryrouter.DestinationConfig{ + ConfigType: "S3", + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "logRecord", + Matcher: "=", + Values: []string{"a", "b"}, + }, + }, + }, + S3: &telemetryrouter.DestinationConfigS3{ + AccessKey: &telemetryrouter.DestinationConfigS3AccessKey{ + Id: "id", + Secret: "secret", + }, + Bucket: "bucket", + Endpoint: "https://example.test", + }, + } + }), + expected: fixtureDatasourceModel(func(model *DatasourceModel) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []datasourceAttribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("logRecord"), + Matcher: types.StringValue("="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, datasourceFilterTypes, datasourceFilter{ + Attributes: attrs, + }) + s3Val, _ := types.ObjectValueFrom(ctx, datasourceS3Types, datasourceS3{ + Bucket: types.StringValue("bucket"), + Endpoint: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), datasourceConfigTypes, datasourceConfig{ + ConfigType: types.StringValue("S3"), + Filter: fltr, + OpenTelemetry: types.ObjectNull(datasourceOpenTelemetryTypes), + S3: s3Val, + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("accessKey") + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureDatasourceModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &DatasourceModel{ + ProjectID: tt.expected.ProjectID, + Region: tt.expected.Region, + InstanceID: tt.expected.InstanceID, + } + 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/telemetryrouter/destination/resource.go b/stackit/internal/services/telemetryrouter/destination/resource.go new file mode 100644 index 000000000..25283efad --- /dev/null +++ b/stackit/internal/services/telemetryrouter/destination/resource.go @@ -0,0 +1,1067 @@ +package destination + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &telemetryRouterDestinationResource{} + _ resource.ResourceWithConfigure = &telemetryRouterDestinationResource{} + _ resource.ResourceWithImportState = &telemetryRouterDestinationResource{} + _ resource.ResourceWithModifyPlan = &telemetryRouterDestinationResource{} +) + +var schemaDescriptions = map[string]string{ + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`,`destionation_id`\".", + "instance_id": "The TelemetryRouter instance ID", + "destination_id": "The TelemetryRouter destination ID", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "project_id": "STACKIT project ID associated with the TelemetryRouter instance", + "display_name": "The displayed name of the TelemetryRouter destination", + "description": "The description of the TelemetryRouter destination", + "config": "The configuration of the TelemetryRouter destination", + "config.filter": "The TelemetryRouter destination's filter settings", + "config.filter.attributes": "The TelemetryRouter destination's filter attributes", + "config.filter.attributes.key": "The TelemetryRouter destination's filter attribute key", + "config.filter.attributes.level": fmt.Sprintf( + "The TelemetryRouter destination's filter attribute level, possible values: %s", + tfutils.FormatPossibleValues("resource", "scope", "logRecord"), + ), + "config.filter.attributes.matcher": fmt.Sprintf( + "The TelemetryRouter destination's filter attribute matcher, possible values: %s", + tfutils.FormatPossibleValues("=", "!="), + ), + "config.filter.attributes.values": "The TelemetryRouter destination's filter attribute values", + "config.config_type": fmt.Sprintf( + "The TelemetryRouter destinations's configuration type, possible values: %s", + tfutils.FormatPossibleValues("OpenTelemetry", "S3"), + ), + "config.opentelemetry": "OpenTelemetry configuration", + "config.opentelemetry.basic_auth": "OpenTelemetry basic auth configuration", + "config.opentelemetry.basic_auth.username": "OpenTelemetry basic auth username", + "config.opentelemetry.basic_auth.password": "OpenTelemetry basic auth password", + "config.opentelemetry.bearer_token": "OpenTelemetry bearer token", + "config.opentelemetry.uri": "OpenTelemetry destination URI", + "config.s3": "S3 configuration", + "config.s3.access_key": "S3 access key configuration", + "config.s3.access_key.id": "S3 access key ID", + "config.s3.access_key.secret": "S3 access key secret", + "config.s3.bucket": "S3 bucket name", + "config.s3.endpoint": "S3 endpoint", + "creation_time": "The date and time the creation of the TelemetryRouter destination was initiated", + "credential_type": "The TelemetryRouter destination's credential type", + "status": fmt.Sprintf( + "The status of the TelemetryRouter destination, possible values: %s", + tfutils.FormatPossibleValues("active", "deleting", "reconciling"), + ), +} + +type Model struct { + ID types.String `tfsdk:"id"` // Required by Terraform + InstanceID types.String `tfsdk:"instance_id"` + DestinationID types.String `tfsdk:"destination_id"` + Region types.String `tfsdk:"region"` + ProjectID types.String `tfsdk:"project_id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + Config types.Object `tfsdk:"config"` + CreationTime types.String `tfsdk:"creation_time"` + CredentialType types.String `tfsdk:"credential_type"` + Status types.String `tfsdk:"status"` +} + +// Struct corresponding to Model.Config +type config struct { + ConfigType types.String `tfsdk:"config_type"` + Filter types.Object `tfsdk:"filter"` + OpenTelemetry types.Object `tfsdk:"opentelemetry"` + S3 types.Object `tfsdk:"s3"` +} + +// Types corresponding to config +var configTypes = map[string]attr.Type{ + "config_type": basetypes.StringType{}, + "filter": basetypes.ObjectType{filterTypes}, + "opentelemetry": basetypes.ObjectType{openTelemetryTypes}, + "s3": basetypes.ObjectType{s3Types}, +} + +// Struct corresponding to filter +type filter struct { + Attributes types.List `tfsdk:"attributes"` +} + +// Types corresponding to filter +var filterTypes = map[string]attr.Type{ + "attributes": basetypes.ListType{ElemType: types.ObjectType{AttrTypes: attributeTypes}}, +} + +// Struct corresponding to a single attribute +type attribute struct { + Key types.String `tfsdk:"key"` + Level types.String `tfsdk:"level"` + Matcher types.String `tfsdk:"matcher"` + Values types.List `tfsdk:"values"` +} + +// Types corresponding to attributes +var attributeTypes = map[string]attr.Type{ + "key": basetypes.StringType{}, + "level": basetypes.StringType{}, + "matcher": basetypes.StringType{}, + "values": basetypes.ListType{ElemType: types.StringType}, +} + +// Struct corresponding to opentelemetry +type openTelemetry struct { + BasicAuth types.Object `tfsdk:"basic_auth"` + BearerToken types.String `tfsdk:"bearer_token"` + Uri types.String `tfsdk:"uri"` +} + +// Types corresponding to opentelemetry +var openTelemetryTypes = map[string]attr.Type{ + "basic_auth": basetypes.ObjectType{basicAuthTypes}, + "bearer_token": basetypes.StringType{}, + "uri": basetypes.StringType{}, +} + +// Struct corresponding to s3 +type s3 struct { + AccessKey types.Object `tfsdk:"access_key"` + Bucket types.String `tfsdk:"bucket"` + Endpoint types.String `tfsdk:"endpoint"` +} + +// Types corresponding to s3 +var s3Types = map[string]attr.Type{ + "access_key": basetypes.ObjectType{accessKeyTypes}, + "bucket": basetypes.StringType{}, + "endpoint": basetypes.StringType{}, +} + +// Struct corresponding to basicAuth +type basicAuth struct { + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` +} + +// Types corresponding to filter +var basicAuthTypes = map[string]attr.Type{ + "username": basetypes.StringType{}, + "password": basetypes.StringType{}, +} + +// Struct corresponding to accessKey +type accessKey struct { + ID types.String `tfsdk:"id"` + Secret types.String `tfsdk:"secret"` +} + +// Types corresponding to filter +var accessKeyTypes = map[string]attr.Type{ + "id": basetypes.StringType{}, + "secret": basetypes.StringType{}, +} + +type telemetryRouterDestinationResource struct { + client *telemetryrouter.APIClient + providerData core.ProviderData +} + +func NewTelemetryRouterDestinationResource() resource.Resource { + return &telemetryRouterDestinationResource{} +} + +func (r *telemetryRouterDestinationResource) 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, "TelemetryRouter client configured") +} + +func (r *telemetryRouterDestinationResource) 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 *telemetryRouterDestinationResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetryrouter_destination" +} + +func (r *telemetryRouterDestinationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryRouter destination resource schema. %s", core.ResourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "destination_id": schema.StringAttribute{ + Description: schemaDescriptions["destination_id"], + Computed: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "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(), + }, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_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, + }, + "config": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config"], + Required: true, + Attributes: map[string]schema.Attribute{ + "config_type": schema.StringAttribute{ + Description: schemaDescriptions["config.config_type"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf("OpenTelemetry", "S3"), + }, + }, + "filter": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.filter"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "attributes": schema.ListNestedAttribute{ + Description: schemaDescriptions["config.filter.attributes"], + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: schemaDescriptions["config.filter.attributes.key"], + Required: true, + }, + "level": schema.StringAttribute{ + Description: schemaDescriptions["config.filter.attributes.level"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("resource", "scope", "logRecord"), + }, + }, + "matcher": schema.StringAttribute{ + Description: schemaDescriptions["config.filter.attributes.matcher"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("=", "!="), + }, + }, + "values": schema.ListAttribute{ + Description: schemaDescriptions["config.filter.attributes.values"], + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "opentelemetry": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.opentelemetry"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "basic_auth": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.opentelemetry.basic_auth"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "username": schema.StringAttribute{ + Description: schemaDescriptions["config.opentelemetry.basic_auth.username"], + Required: true, + }, + "password": schema.StringAttribute{ + Description: schemaDescriptions["config.opentelemetry.basic_auth.username"], + Required: true, + Sensitive: true, + }, + }, + }, + "bearer_token": schema.StringAttribute{ + Description: schemaDescriptions["config.opentelemetry.bearer_token"], + Optional: true, + Sensitive: true, + }, + "uri": schema.StringAttribute{ + Description: schemaDescriptions["config.opentelemetry.uri"], + Required: true, + }, + }, + }, + "s3": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.s3"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "access_key": schema.SingleNestedAttribute{ + Description: schemaDescriptions["config.s3.access_key"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["config.s3.access_key.id"], + Required: true, + }, + "secret": schema.StringAttribute{ + Description: schemaDescriptions["config.s3.access_key.secret"], + Required: true, + Sensitive: true, + }, + }, + }, + "bucket": schema.StringAttribute{ + Description: schemaDescriptions["config.s3.bucket"], + Required: true, + }, + "endpoint": schema.StringAttribute{ + Description: schemaDescriptions["config.s3.endpoint"], + Required: true, + }, + }, + }, + }, + }, + "creation_time": schema.StringAttribute{ + Description: schemaDescriptions["creation_time"], + Computed: true, + }, + "credential_type": schema.StringAttribute{ + Description: schemaDescriptions["credential_type"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (r *telemetryRouterDestinationResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var resourceModel Model + resp.Diagnostics.Append(req.Config.Get(ctx, &resourceModel)...) + if resp.Diagnostics.HasError() { + return + } + + // function is used in order to be able to write easier unit tests + validateConfig(ctx, &resp.Diagnostics, &resourceModel) +} + +func validateConfig(ctx context.Context, respDiags *diag.Diagnostics, model *Model) { + conf := &config{} + diags := model.Config.As(ctx, conf, basetypes.ObjectAsOptions{}) + respDiags.Append(diags...) + if respDiags.HasError() { + return + } + + if tfutils.IsUndefined(conf.ConfigType) { + core.LogAndAddError(ctx, respDiags, "Error configuring destination", "config.config_type cannot be empty") + } + + switch conf.ConfigType.ValueString() { + case "OpenTelemetry": + if !tfutils.IsUndefined(conf.S3) { + core.LogAndAddError( + ctx, + respDiags, + "Error configuring destination", + "S3 configuration is not supported for OpenTelemetry destination", + ) + } + if tfutils.IsUndefined(conf.OpenTelemetry) { + core.LogAndAddError( + ctx, + respDiags, + "Error configuring destination", + "OpenTelemetry configuration is required", + ) + } + + ot := &openTelemetry{} + diags = conf.OpenTelemetry.As(ctx, ot, basetypes.ObjectAsOptions{}) + respDiags.Append(diags...) + if respDiags.HasError() { + return + } + + if !tfutils.IsUndefined(ot.BasicAuth) && !tfutils.IsUndefined(ot.BearerToken) { + core.LogAndAddError( + ctx, + respDiags, + "Error configuring destination", + "Basic Auth and Bearer Token can't be used at the same time with OpenTelemetry destination", + ) + } + case "S3": + if !tfutils.IsUndefined(conf.OpenTelemetry) { + core.LogAndAddError( + ctx, + respDiags, + "Error configuring destination", + "OpenTelemetry configuration is not supported for S3 destination", + ) + } + if tfutils.IsUndefined(conf.S3) { + core.LogAndAddError( + ctx, + respDiags, + "Error configuring destination", + "S3 configuration is required", + ) + } + default: + core.LogAndAddError(ctx, respDiags, "Error configuring destination", "unknown config.config_type") + } +} + +func (r *telemetryRouterDestinationResource) 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) + + instanceId := model.InstanceID.ValueString() + projectId := model.ProjectID.ValueString() + region := model.Region.ValueString() + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toCreatePayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter destination", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + createResp, err := r.client.DefaultAPI.CreateDestination(ctx, projectId, regionId, instanceId).CreateDestinationPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter destination", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(ctx, createResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter destination", 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, "TelemetryRouter destination created") +} + +func (r *telemetryRouterDestinationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + destinationID := model.DestinationID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "destination_id", destinationID) + + instanceResponse, err := r.client.DefaultAPI.GetDestination(ctx, projectID, region, instanceID, destinationID).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 TelemetryRouter destination", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, instanceResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter destination", 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, "TelemetryRouter destination read", map[string]any{ + "instance_id": instanceID, + "destination_id": destinationID, + }) +} + +func (r *telemetryRouterDestinationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + destinationID := model.DestinationID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "destination_id", destinationID) + + payload, err := toUpdatePayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter destination", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + updateResp, err := r.client.DefaultAPI.UpdateDestination(ctx, projectID, region, instanceID, destinationID).UpdateDestinationPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter destination", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, updateResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter destination", 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, "TelemetryRouter destination updated", map[string]any{ + "instance_id": instanceID, + "destination_id": destinationID, + }) +} + +func (r *telemetryRouterDestinationResource) 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) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + destinationID := model.DestinationID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + ctx = tflog.SetField(ctx, "destination_id", destinationID) + + err := r.client.DefaultAPI.DeleteDestination(ctx, projectID, region, instanceID, destinationID).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryRouter destination", fmt.Sprintf("Calling API: %v", err)) + return + } + + tflog.Info(ctx, "TelemetryRouter destination deleted") +} + +func (r *telemetryRouterDestinationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing TelemetryRouter destination", fmt.Sprintf("Invalid import ID %q: expected format is `project_id`,`region`,`instance_id`,`destination_id`", req.ID)) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("destination_id"), idParts[3])...) + tflog.Info(ctx, "TelemetryRouter Destination state imported") +} + +func toCreatePayload(ctx context.Context, diags diag.Diagnostics, model *Model) (*telemetryrouter.CreateDestinationPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + payload := &telemetryrouter.CreateDestinationPayload{ + DisplayName: model.DisplayName.ValueString(), + Description: conversion.StringValueToPointer(model.Description), + } + + conf, err := toConfig(ctx, diags, model) + if err != nil { + return nil, err + } + payload.Config = *conf + + return payload, nil +} + +func toUpdatePayload(ctx context.Context, diags diag.Diagnostics, model *Model) (*telemetryrouter.UpdateDestinationPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + payload := &telemetryrouter.UpdateDestinationPayload{ + DisplayName: conversion.StringValueToPointer(model.DisplayName), + Description: conversion.StringValueToPointer(model.Description), + } + + conf, err := toConfig(ctx, diags, model) + if err != nil { + return nil, err + } + payload.Config = conf + + return payload, nil +} + +func toConfig(ctx context.Context, diags diag.Diagnostics, model *Model) (*telemetryrouter.DestinationConfig, error) { + result := telemetryrouter.DestinationConfig{} + var conf config + diags.Append(model.Config.As(ctx, &conf, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting config object: %v", diags.Errors()) + } + + result.ConfigType = telemetryrouter.DestinationConfigType(conf.ConfigType.ValueString()) + switch result.ConfigType { + case telemetryrouter.DESTINATIONCONFIGTYPE_OPEN_TELEMETRY: + destinationConfigOpenTelemetry, err := toOpenTelemetry(ctx, diags, &conf) + if err != nil { + return nil, err + } + result.OpenTelemetry = destinationConfigOpenTelemetry + case telemetryrouter.DESTINATIONCONFIGTYPE_S3: + destinationConfigS3, err := toS3(ctx, diags, &conf) + if err != nil { + return nil, err + } + result.S3 = destinationConfigS3 + } + + configFilter, err := toConfigFilter(ctx, diags, &conf) + if err != nil { + return nil, err + } + + result.Filter = configFilter + + return &result, nil +} + +func toConfigFilter(ctx context.Context, diags diag.Diagnostics, conf *config) (*telemetryrouter.ConfigFilter, error) { + if !conf.Filter.IsNull() && !conf.Filter.IsUnknown() { + var fltr filter + diags.Append(conf.Filter.As(ctx, &fltr, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting filter object: %v", diags.Errors()) + } + + var attributes []attribute + diags.Append(fltr.Attributes.ElementsAs(ctx, &attributes, false)...) + if diags.HasError() { + return nil, fmt.Errorf("converting attributes list: %v", diags.Errors()) + } + + configFilterAttributes := make([]telemetryrouter.ConfigFilterAttributes, 0, len(attributes)) + for _, item := range attributes { + var values []string + valuesDiags := item.Values.ElementsAs(ctx, &values, false) + diags.Append(valuesDiags...) + if !valuesDiags.HasError() { + configFilterAttributes = append(configFilterAttributes, telemetryrouter.ConfigFilterAttributes{ + Key: item.Key.ValueString(), + Level: telemetryrouter.ConfigFilterLevel(item.Level.ValueString()), + Matcher: telemetryrouter.ConfigFilterMatcher(item.Matcher.ValueString()), + Values: values, + }) + } + } + if len(configFilterAttributes) > 0 { + return telemetryrouter.NewConfigFilter( + configFilterAttributes, + ), nil + } + } + + return nil, nil +} + +func toOpenTelemetry(ctx context.Context, diags diag.Diagnostics, conf *config) (*telemetryrouter.DestinationConfigOpenTelemetry, error) { + if !conf.OpenTelemetry.IsNull() && !conf.OpenTelemetry.IsUnknown() { + var ot openTelemetry + var result telemetryrouter.DestinationConfigOpenTelemetry + diags.Append(conf.OpenTelemetry.As(ctx, &ot, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting opentelemetry object: %v", diags.Errors()) + } + + if !tfutils.IsUndefined(ot.BasicAuth) { + var basicAuthVal basicAuth + diags.Append(ot.BasicAuth.As(ctx, &basicAuthVal, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting basic_auth object: %v", diags.Errors()) + } + result.BasicAuth = telemetryrouter.NewDestinationConfigOpenTelemetryBasicAuth( + basicAuthVal.Password.ValueString(), + basicAuthVal.Username.ValueString(), + ) + } + result.BearerToken = ot.BearerToken.ValueStringPointer() + result.Uri = ot.Uri.ValueString() + + return &result, nil + } + + return nil, nil +} + +func toS3(ctx context.Context, diags diag.Diagnostics, conf *config) (*telemetryrouter.DestinationConfigS3, error) { + if !conf.S3.IsNull() && !conf.S3.IsUnknown() { + var s3Inst s3 + var result telemetryrouter.DestinationConfigS3 + diags.Append(conf.S3.As(ctx, &s3Inst, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting s3 object: %v", diags.Errors()) + } + + if !tfutils.IsUndefined(s3Inst.AccessKey) { + var accKey accessKey + diags.Append(s3Inst.AccessKey.As(ctx, &accKey, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting access_key object: %v", diags.Errors()) + } + result.AccessKey = telemetryrouter.NewDestinationConfigS3AccessKey( + accKey.ID.ValueString(), + accKey.Secret.ValueString(), + ) + } + result.Bucket = s3Inst.Bucket.ValueString() + result.Endpoint = s3Inst.Endpoint.ValueString() + + return &result, nil + } + + return nil, nil +} + +func mapFields(ctx context.Context, destination *telemetryrouter.DestinationResponse, model *Model) error { + if destination == nil { + return fmt.Errorf("destination is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + var destinationID string + if model.DestinationID.ValueString() != "" { + destinationID = model.DestinationID.ValueString() + } else if destination.Id != "" { + destinationID = destination.Id + } else { + return fmt.Errorf("destination id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), model.InstanceID.ValueString(), destinationID) + model.DestinationID = types.StringValue(destinationID) + model.DisplayName = types.StringValue(destination.DisplayName) + model.Description = types.StringPointerValue(destination.Description) + model.CredentialType = types.StringValue(destination.CredentialType) + model.CreationTime = types.StringValue(destination.CreationTime.Format(time.RFC3339)) + model.Status = types.StringValue(destination.Status) + + if err := mapConfig(ctx, destination, model); err != nil { + return fmt.Errorf("map config: %w", err) + } + + return nil +} + +func mapConfig(ctx context.Context, destination *telemetryrouter.DestinationResponse, model *Model) error { + var conf config + confIsEmpty := true + if !model.Config.IsNull() { + diags := model.Config.As(ctx, &conf, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fmt.Errorf("converting config object: %v", diags.Errors()) + } + confIsEmpty = false + } + + conf.ConfigType = types.StringValue(string(destination.Config.ConfigType)) + + if err := mapFilter(ctx, &destination.Config, &conf); err != nil { + return err + } + + if err := mapOpenTelemetry(ctx, &destination.Config, &conf); err != nil { + return err + } + + if err := mapS3(ctx, &destination.Config, &conf); err != nil { + return err + } + + if confIsEmpty { + confModel, diags := types.ObjectValueFrom(ctx, configTypes, conf) + if diags.HasError() { + return fmt.Errorf("mapping config: %w", core.DiagsToError(diags)) + } + + model.Config = confModel + } + + return nil +} + +func mapFilter(ctx context.Context, apiConf *telemetryrouter.DestinationConfig, conf *config) error { + if apiConf.Filter == nil { + conf.Filter = types.ObjectNull(filterTypes) + return nil + } + + attrList := []attr.Value{} + for _, currentAttr := range apiConf.Filter.Attributes { + values, diags := types.ListValueFrom(ctx, types.StringType, currentAttr.Values) + if diags.HasError() { + return fmt.Errorf("mapping filter values: %w", core.DiagsToError(diags)) + } + attrModel, diags := types.ObjectValueFrom(ctx, attributeTypes, attribute{ + Key: types.StringValue(currentAttr.Key), + Level: types.StringValue(string(currentAttr.Level)), + Matcher: types.StringValue(string(currentAttr.Matcher)), + Values: values, + }) + if diags.HasError() { + return fmt.Errorf("mapping filter config: %w", core.DiagsToError(diags)) + } + attrList = append(attrList, attrModel) + } + + var attrConfigs basetypes.ListValue + var diags diag.Diagnostics + if len(attrList) == 0 { + attrConfigs = types.ListNull(types.ObjectType{AttrTypes: attributeTypes}) + } else { + attrConfigs, diags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, attrList) + if diags.HasError() { + return fmt.Errorf("mapping attributes: %w", core.DiagsToError(diags)) + } + } + + filterValue, diags := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrConfigs, + }) + if diags.HasError() { + return fmt.Errorf("mapping filter: %w", core.DiagsToError(diags)) + } + conf.Filter = filterValue + + return nil +} + +func mapOpenTelemetry(ctx context.Context, apiConf *telemetryrouter.DestinationConfig, conf *config) error { + if apiConf.OpenTelemetry == nil { + conf.OpenTelemetry = types.ObjectNull(openTelemetryTypes) + return nil + } + + var ot openTelemetry + diags := conf.OpenTelemetry.As(ctx, &ot, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + }) + if diags.HasError() { + return fmt.Errorf("converting opentelemetry object: %v", diags.Errors()) + } + ot.Uri = types.StringValue(apiConf.OpenTelemetry.Uri) + if tfutils.IsUndefined(ot.BearerToken) { + ot.BearerToken = types.StringNull() + } + + if apiConf.OpenTelemetry.BasicAuth == nil { + ot.BasicAuth = types.ObjectNull(basicAuthTypes) + } else { + var ba basicAuth + diags = ot.BasicAuth.As(ctx, &ba, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + }) + if diags.HasError() { + return fmt.Errorf("converting basic auth object: %v", diags.Errors()) + } + ba.Username = types.StringValue(apiConf.OpenTelemetry.BasicAuth.Username) + if tfutils.IsUndefined(ba.Password) { + ba.Password = types.StringValue("") + } + ot.BasicAuth, diags = types.ObjectValueFrom(ctx, basicAuthTypes, ba) + if diags.HasError() { + return fmt.Errorf("mapping basic auth: %w", core.DiagsToError(diags)) + } + } + + otModel, diags := types.ObjectValueFrom(ctx, openTelemetryTypes, ot) + if diags.HasError() { + return fmt.Errorf("mapping open telemetry: %w", core.DiagsToError(diags)) + } + + conf.OpenTelemetry = otModel + + return nil +} + +func mapS3(ctx context.Context, apiConf *telemetryrouter.DestinationConfig, conf *config) error { + if apiConf.S3 == nil { + conf.S3 = types.ObjectNull(s3Types) + return nil + } + + var s3Struct s3 + diags := conf.S3.As(ctx, &s3Struct, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + }) + if diags.HasError() { + return fmt.Errorf("converting s3 object: %v", diags.Errors()) + } + s3Struct.Bucket = types.StringValue(apiConf.S3.Bucket) + s3Struct.Endpoint = types.StringValue(apiConf.S3.Endpoint) + + if apiConf.S3.AccessKey == nil { + s3Struct.AccessKey = types.ObjectNull(accessKeyTypes) + } else { + var ak accessKey + diags = s3Struct.AccessKey.As(ctx, &ak, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return fmt.Errorf("converting access key object: %v", diags.Errors()) + } + ak.ID = types.StringValue(apiConf.S3.AccessKey.Id) + s3Struct.AccessKey, diags = types.ObjectValueFrom(ctx, accessKeyTypes, ak) + if diags.HasError() { + return fmt.Errorf("mapping access key: %w", core.DiagsToError(diags)) + } + } + + s3Model, diags := types.ObjectValueFrom(ctx, s3Types, s3Struct) + if diags.HasError() { + return fmt.Errorf("mapping s3: %w", core.DiagsToError(diags)) + } + + conf.S3 = s3Model + + return nil +} diff --git a/stackit/internal/services/telemetryrouter/destination/resource_test.go b/stackit/internal/services/telemetryrouter/destination/resource_test.go new file mode 100644 index 000000000..c1e735e7f --- /dev/null +++ b/stackit/internal/services/telemetryrouter/destination/resource_test.go @@ -0,0 +1,597 @@ +package destination + +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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" +) + +var testTime = time.Now() + +func fixtureDestinationResponse(mods ...func(destination *telemetryrouter.DestinationResponse)) *telemetryrouter.DestinationResponse { + destination := &telemetryrouter.DestinationResponse{ + Id: "dsid", + DisplayName: "test", + CreationTime: testTime, + Status: "active", + } + for _, mod := range mods { + mod(destination) + } + return destination +} + +func fixtureModel(mods ...func(model *Model)) *Model { + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue(""), + Filter: types.ObjectNull(filterTypes), + OpenTelemetry: types.ObjectNull(openTelemetryTypes), + S3: types.ObjectNull(s3Types), + }) + model := &Model{ + ID: types.StringValue("pid,rid,iid,dsid"), + DestinationID: types.StringValue("dsid"), + InstanceID: types.StringValue("iid"), + Region: types.StringValue("rid"), + ProjectID: types.StringValue("pid"), + Description: types.String{}, + DisplayName: types.StringValue("test"), + Config: cfg, + CredentialType: types.StringValue(""), + CreationTime: types.StringValue(testTime.Format(time.RFC3339)), + Status: types.StringValue("active"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapFields(t *testing.T) { + tests := []struct { + description string + input *telemetryrouter.DestinationResponse + expected *Model + wantErr bool + }{ + { + description: "min values", + input: fixtureDestinationResponse(), + expected: fixtureModel(), + }, + { + description: "OpenTelemetry bearer token with filter", + input: fixtureDestinationResponse(func(destination *telemetryrouter.DestinationResponse) { + destination.Description = utils.Ptr("description") + destination.DisplayName = "display-name" + destination.CredentialType = "bearerToken" + destination.Config = telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "logRecord", + Matcher: "=", + Values: []string{"a", "b"}, + }, + }, + }, + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BearerToken: utils.Ptr("bearer-token"), + Uri: "https://example.test", + }, + } + }), + expected: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("logRecord"), + Matcher: types.StringValue("="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, openTelemetryTypes, openTelemetry{ + BasicAuth: types.ObjectNull(basicAuthTypes), + BearerToken: types.StringPointerValue(utils.Ptr("bearer-token")), + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: fltr, + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(s3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + }, + { + description: "OpenTelemetry basic auth", + input: fixtureDestinationResponse(func(destination *telemetryrouter.DestinationResponse) { + destination.Description = utils.Ptr("description") + destination.DisplayName = "display-name" + destination.CredentialType = "bearerToken" + destination.Config = telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BasicAuth: &telemetryrouter.DestinationConfigOpenTelemetryBasicAuth{ + Password: "pass", + Username: "user", + }, + Uri: "https://example.test", + }, + } + }), + expected: fixtureModel(func(model *Model) { + ctx := context.Background() + basicAuthVal, _ := types.ObjectValueFrom(ctx, basicAuthTypes, basicAuth{ + Username: types.StringValue("user"), + Password: types.StringValue("pass"), + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, openTelemetryTypes, openTelemetry{ + BasicAuth: basicAuthVal, + BearerToken: types.StringNull(), + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: types.ObjectNull(filterTypes), + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(s3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + }, + { + description: "S3 with filter", + input: fixtureDestinationResponse(func(destination *telemetryrouter.DestinationResponse) { + destination.Description = utils.Ptr("description") + destination.DisplayName = "display-name" + destination.CredentialType = "accessKey" + destination.Config = telemetryrouter.DestinationConfig{ + ConfigType: "S3", + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "logRecord", + Matcher: "=", + Values: []string{"a", "b"}, + }, + }, + }, + S3: &telemetryrouter.DestinationConfigS3{ + AccessKey: &telemetryrouter.DestinationConfigS3AccessKey{ + Id: "id", + Secret: "secret", + }, + Bucket: "bucket", + Endpoint: "https://example.test", + }, + } + }), + expected: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("logRecord"), + Matcher: types.StringValue("="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + ak, _ := types.ObjectValueFrom(ctx, accessKeyTypes, accessKey{ + ID: types.StringValue("id"), + Secret: types.StringValue("secret"), + }) + s3Val, _ := types.ObjectValueFrom(ctx, s3Types, s3{ + AccessKey: ak, + Bucket: types.StringValue("bucket"), + Endpoint: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("S3"), + Filter: fltr, + OpenTelemetry: types.ObjectNull(openTelemetryTypes), + S3: s3Val, + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("accessKey") + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ProjectID: tt.expected.ProjectID, + Region: tt.expected.Region, + InstanceID: tt.expected.InstanceID, + Config: tt.expected.Config, + } + 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 TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetryrouter.CreateDestinationPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetryrouter.CreateDestinationPayload{ + DisplayName: "test", + }, + }, + { + description: "open telemetry basic auth values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + basicAuthVal, _ := types.ObjectValueFrom(ctx, basicAuthTypes, basicAuth{ + Username: types.StringValue("user"), + Password: types.StringValue("pass"), + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, openTelemetryTypes, openTelemetry{ + BasicAuth: basicAuthVal, + BearerToken: types.StringNull(), + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: types.ObjectNull(filterTypes), + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(s3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + expected: &telemetryrouter.CreateDestinationPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + Config: telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BasicAuth: &telemetryrouter.DestinationConfigOpenTelemetryBasicAuth{ + Username: "user", + Password: "pass", + }, + Uri: "https://example.test", + }, + }, + }, + }, + { + description: "open telemetry bearer token with filter values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("logRecord"), + Matcher: types.StringValue("="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, openTelemetryTypes, openTelemetry{ + BasicAuth: types.ObjectNull(basicAuthTypes), + BearerToken: types.StringValue("bearerToken"), + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: fltr, + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(s3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + expected: &telemetryrouter.CreateDestinationPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + Config: telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "logRecord", + Matcher: "=", + Values: []string{"a", "b"}, + }, + }, + }, + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BearerToken: utils.Ptr("bearerToken"), + Uri: "https://example.test", + }, + }, + }, + }, + { + description: "s3 values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + ak, _ := types.ObjectValueFrom(ctx, accessKeyTypes, accessKey{ + ID: types.StringValue("id"), + Secret: types.StringValue("secret"), + }) + s3Val, _ := types.ObjectValueFrom(ctx, s3Types, s3{ + AccessKey: ak, + Bucket: types.StringValue("bucket"), + Endpoint: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("S3"), + Filter: types.ObjectNull(filterTypes), + OpenTelemetry: types.ObjectNull(openTelemetryTypes), + S3: s3Val, + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("accessKey") + }), + expected: &telemetryrouter.CreateDestinationPayload{ + Description: utils.Ptr("description"), + DisplayName: "display-name", + Config: telemetryrouter.DestinationConfig{ + ConfigType: "S3", + S3: &telemetryrouter.DestinationConfigS3{ + AccessKey: &telemetryrouter.DestinationConfigS3AccessKey{ + Id: "id", + Secret: "secret", + }, + Bucket: "bucket", + Endpoint: "https://example.test", + }, + }, + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreatePayload(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 TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetryrouter.UpdateDestinationPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetryrouter.UpdateDestinationPayload{ + DisplayName: utils.Ptr("test"), + Config: &telemetryrouter.DestinationConfig{ + ConfigType: "", + }, + }, + }, + { + description: "open telemetry basic auth values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + basicAuthVal, _ := types.ObjectValueFrom(ctx, basicAuthTypes, basicAuth{ + Username: types.StringValue("user"), + Password: types.StringValue("pass"), + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, openTelemetryTypes, openTelemetry{ + BasicAuth: basicAuthVal, + BearerToken: types.StringNull(), + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: types.ObjectNull(filterTypes), + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(s3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + expected: &telemetryrouter.UpdateDestinationPayload{ + Description: utils.Ptr("description"), + DisplayName: utils.Ptr("display-name"), + Config: &telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BasicAuth: &telemetryrouter.DestinationConfigOpenTelemetryBasicAuth{ + Username: "user", + Password: "pass", + }, + Uri: "https://example.test", + }, + }, + }, + }, + { + description: "open telemetry bearer token with filter values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("logRecord"), + Matcher: types.StringValue("="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + openTelemetryVal, _ := types.ObjectValueFrom(ctx, openTelemetryTypes, openTelemetry{ + BasicAuth: types.ObjectNull(basicAuthTypes), + BearerToken: types.StringValue("bearerToken"), + Uri: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("OpenTelemetry"), + Filter: fltr, + OpenTelemetry: openTelemetryVal, + S3: types.ObjectNull(s3Types), + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("bearerToken") + }), + expected: &telemetryrouter.UpdateDestinationPayload{ + Description: utils.Ptr("description"), + DisplayName: utils.Ptr("display-name"), + Config: &telemetryrouter.DestinationConfig{ + ConfigType: "OpenTelemetry", + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "logRecord", + Matcher: "=", + Values: []string{"a", "b"}, + }, + }, + }, + OpenTelemetry: &telemetryrouter.DestinationConfigOpenTelemetry{ + BearerToken: utils.Ptr("bearerToken"), + Uri: "https://example.test", + }, + }, + }, + }, + { + description: "s3 values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + ak, _ := types.ObjectValueFrom(ctx, accessKeyTypes, accessKey{ + ID: types.StringValue("id"), + Secret: types.StringValue("secret"), + }) + s3Val, _ := types.ObjectValueFrom(ctx, s3Types, s3{ + AccessKey: ak, + Bucket: types.StringValue("bucket"), + Endpoint: types.StringValue("https://example.test"), + }) + cfg, _ := types.ObjectValueFrom(context.Background(), configTypes, config{ + ConfigType: types.StringValue("S3"), + Filter: types.ObjectNull(filterTypes), + OpenTelemetry: types.ObjectNull(openTelemetryTypes), + S3: s3Val, + }) + model.Config = cfg + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.CredentialType = types.StringValue("accessKey") + }), + expected: &telemetryrouter.UpdateDestinationPayload{ + Description: utils.Ptr("description"), + DisplayName: utils.Ptr("display-name"), + Config: &telemetryrouter.DestinationConfig{ + ConfigType: "S3", + S3: &telemetryrouter.DestinationConfigS3{ + AccessKey: &telemetryrouter.DestinationConfigS3AccessKey{ + Id: "id", + Secret: "secret", + }, + Bucket: "bucket", + Endpoint: "https://example.test", + }, + }, + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toUpdatePayload(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 /*, cmp.Comparer(compareNullableString), cmp.Comparer(compareNullableInt32)*/) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} diff --git a/stackit/internal/services/telemetryrouter/instance/datasource.go b/stackit/internal/services/telemetryrouter/instance/datasource.go new file mode 100644 index 000000000..dbfa26551 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/instance/datasource.go @@ -0,0 +1,190 @@ +package instance + +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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &telemetryRouterInstanceDataSource{} +) + +func NewTelemetryRouterInstanceDataSource() datasource.DataSource { + return &telemetryRouterInstanceDataSource{} +} + +type telemetryRouterInstanceDataSource struct { + client *telemetryrouter.APIClient + providerData core.ProviderData +} + +func (d *telemetryRouterInstanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetryrouter_instance" +} + +func (d *telemetryRouterInstanceDataSource) 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, "TelemetryRouter client configured") +} + +func (d *telemetryRouterInstanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryRouter instance data source schema. %s", core.DatasourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + }, + "instance_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_id"], + Required: 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, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_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, + }, + "filter": schema.SingleNestedAttribute{ + Description: schemaDescriptions["filter"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "attributes": schema.ListNestedAttribute{ + Description: schemaDescriptions["filter.attributes"], + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: schemaDescriptions["filter.attributes.key"], + Computed: true, + }, + "level": schema.StringAttribute{ + Description: schemaDescriptions["filter.attributes.level"], + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("resource", "scope", "logRecord"), + }, + }, + "matcher": schema.StringAttribute{ + Description: schemaDescriptions["filter.attributes.matcher"], + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("=", "!="), + }, + }, + "values": schema.ListAttribute{ + Description: schemaDescriptions["filter.attributes.values"], + ElementType: types.StringType, + Computed: true, + }, + }, + }, + }, + }, + }, + "creation_time": schema.StringAttribute{ + Description: schemaDescriptions["creation_time"], + Computed: true, + }, + "uri": schema.StringAttribute{ + Description: schemaDescriptions["uri"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (d *telemetryRouterInstanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Config.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectID := model.ProjectID.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) + instanceID := model.InstanceID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + + instanceResponse, err := d.client.DefaultAPI.GetTelemetryRouter(ctx, projectID, region, instanceID).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 TelemetryRouter instance", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, instanceResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter instance", 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, "TelemetryRouter Instance read", map[string]interface{}{ + "instance_id": instanceID, + }) +} diff --git a/stackit/internal/services/telemetryrouter/instance/resource.go b/stackit/internal/services/telemetryrouter/instance/resource.go new file mode 100644 index 000000000..426bde244 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/instance/resource.go @@ -0,0 +1,588 @@ +package instance + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &telemetryRouterInstanceResource{} + _ resource.ResourceWithConfigure = &telemetryRouterInstanceResource{} + _ resource.ResourceWithImportState = &telemetryRouterInstanceResource{} + _ resource.ResourceWithModifyPlan = &telemetryRouterInstanceResource{} +) + +var schemaDescriptions = map[string]string{ + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`,`instance_id`\".", + "instance_id": "The TelemetryRouter instance ID", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "project_id": "STACKIT project ID associated with the TelemetryRouter instance", + "display_name": "The displayed name of the TelemetryRouter instance", + "description": "The description of the TelemetryRouter instance", + "filter": "The TelemetryRouter global filter settings", + "filter.attributes": "The TelemetryRouter global filter attributes", + "filter.attributes.key": "The TelemetryRouter global filter attribute key", + "filter.attributes.level": fmt.Sprintf( + "The TelemetryRouter global filter attribute level, possible values: %s", + tfutils.FormatPossibleValues("resource", "scope", "logRecord"), + ), + "filter.attributes.matcher": fmt.Sprintf( + "The TelemetryRouter global filter attribute matcher, possible values: %s", + tfutils.FormatPossibleValues("=", "!="), + ), + "filter.attributes.values": "The TelemetryRouter global filter attributes", + "creation_time": "The date and time the creation of the TelemetryRouter instance was initiated", + "uri": "The TelemetryRouter instance's URI", + "status": fmt.Sprintf( + "The status of the TelemetryRouter instance, possible values: %s", + tfutils.FormatPossibleValues("active", "deleting", "reconciling"), + ), +} + +type Model struct { + ID types.String `tfsdk:"id"` // Required by Terraform + InstanceID types.String `tfsdk:"instance_id"` + Region types.String `tfsdk:"region"` + ProjectID types.String `tfsdk:"project_id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + Filter types.Object `tfsdk:"filter"` + CreationTime types.String `tfsdk:"creation_time"` + URI types.String `tfsdk:"uri"` + Status types.String `tfsdk:"status"` +} + +// Struct corresponding to Model.Filter +type filter struct { + Attributes types.List `tfsdk:"attributes"` +} + +// Types corresponding to filter +var filterTypes = map[string]attr.Type{ + "attributes": basetypes.ListType{ElemType: types.ObjectType{AttrTypes: attributeTypes}}, +} + +// Struct corresponding to a single attribute +type attribute struct { + Key types.String `tfsdk:"key"` + Level types.String `tfsdk:"level"` + Matcher types.String `tfsdk:"matcher"` + Values types.List `tfsdk:"values"` +} + +// Types coresponding to attributes +var attributeTypes = map[string]attr.Type{ + "key": basetypes.StringType{}, + "level": basetypes.StringType{}, + "matcher": basetypes.StringType{}, + "values": basetypes.ListType{ElemType: types.StringType}, +} + +type telemetryRouterInstanceResource struct { + client *telemetryrouter.APIClient + providerData core.ProviderData +} + +func NewTelemetryRouterInstanceResource() resource.Resource { + return &telemetryRouterInstanceResource{} +} + +func (r *telemetryRouterInstanceResource) 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, "TelemetryRouter client configured") +} + +func (r *telemetryRouterInstanceResource) 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 *telemetryRouterInstanceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_telemetryrouter_instance" +} + +func (r *telemetryRouterInstanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("TelemetryRouter instance resource schema. %s", core.ResourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "instance_id": schema.StringAttribute{ + Description: schemaDescriptions["instance_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(), + }, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_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, + }, + "filter": schema.SingleNestedAttribute{ + Description: schemaDescriptions["filter"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "attributes": schema.ListNestedAttribute{ + Description: schemaDescriptions["filter.attributes"], + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Description: schemaDescriptions["filter.attributes.key"], + Required: true, + }, + "level": schema.StringAttribute{ + Description: schemaDescriptions["filter.attributes.level"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("resource", "scope", "logRecord"), + }, + }, + "matcher": schema.StringAttribute{ + Description: schemaDescriptions["filter.attributes.matcher"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("=", "!="), + }, + }, + "values": schema.ListAttribute{ + Description: schemaDescriptions["filter.attributes.values"], + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + "creation_time": schema.StringAttribute{ + Description: schemaDescriptions["creation_time"], + Computed: true, + }, + "uri": schema.StringAttribute{ + Description: schemaDescriptions["uri"], + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (r *telemetryRouterInstanceResource) 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) + + projectId := model.ProjectID.ValueString() + region := model.Region.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toCreatePayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter Instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + createResp, err := r.client.DefaultAPI.CreateTelemetryRouter(ctx, projectId, regionId).CreateTelemetryRouterPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter Instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(ctx, createResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter Instance", 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, "TelemetryRouter instance created") +} + +func (r *telemetryRouterInstanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + + instanceResponse, err := r.client.DefaultAPI.GetTelemetryRouter(ctx, projectID, region, instanceID).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 TelemetryRouter instance", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, instanceResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading TelemetryRouter instance", 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, "TelemetryRouter Instance read", map[string]any{ + "instance_id": instanceID, + }) +} + +func (r *telemetryRouterInstanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + + payload, err := toUpdatePayload(ctx, resp.Diagnostics, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter Instance", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + updateResp, err := r.client.DefaultAPI.UpdateTelemetryRouter(ctx, projectID, region, instanceID).UpdateTelemetryRouterPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter Instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, updateResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter Instance", 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, "TelemetryRouter Instance updated", map[string]any{ + "instance_id": instanceID, + }) +} + +func (r *telemetryRouterInstanceResource) 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) + + projectID := model.ProjectID.ValueString() + region := model.Region.ValueString() + instanceID := model.InstanceID.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectID) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "instance_id", instanceID) + + err := r.client.DefaultAPI.DeleteTelemetryRouter(ctx, projectID, region, instanceID).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryRouter Instance", fmt.Sprintf("Calling API: %v", err)) + return + } + + tflog.Info(ctx, "TelemetryRouter Instance deleted") +} + +func (r *telemetryRouterInstanceResource) 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 TelemetryRouter Instance", 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("project_id"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) + tflog.Info(ctx, "TelemetryRouter Instance state imported") +} + +func toCreatePayload(ctx context.Context, diags diag.Diagnostics, model *Model) (*telemetryrouter.CreateTelemetryRouterPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + payload := &telemetryrouter.CreateTelemetryRouterPayload{ + DisplayName: model.DisplayName.ValueString(), + Description: conversion.StringValueToPointer(model.Description), + } + + configFilter, err := toConfigFilter(ctx, diags, model) + if err != nil { + return nil, err + } + payload.Filter = configFilter + + return payload, nil +} + +func toConfigFilter(ctx context.Context, diags diag.Diagnostics, model *Model) (*telemetryrouter.ConfigFilter, error) { + if !model.Filter.IsNull() && !model.Filter.IsUnknown() { + var fltr filter + diags.Append(model.Filter.As(ctx, &fltr, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return nil, fmt.Errorf("converting filter object: %v", diags.Errors()) + } + + var attributes []attribute + diags.Append(fltr.Attributes.ElementsAs(ctx, &attributes, false)...) + if diags.HasError() { + return nil, fmt.Errorf("converting attributes list: %v", diags.Errors()) + } + + configFilterAttributes := make([]telemetryrouter.ConfigFilterAttributes, 0, len(attributes)) + for _, item := range attributes { + var values []string + valuesDiags := item.Values.ElementsAs(ctx, &values, false) + diags.Append(valuesDiags...) + if !valuesDiags.HasError() { + configFilterAttributes = append(configFilterAttributes, telemetryrouter.ConfigFilterAttributes{ + Key: item.Key.ValueString(), + Level: telemetryrouter.ConfigFilterLevel(item.Level.ValueString()), + Matcher: telemetryrouter.ConfigFilterMatcher(item.Matcher.ValueString()), + Values: values, + }) + } + } + if len(configFilterAttributes) > 0 { + return telemetryrouter.NewConfigFilter( + configFilterAttributes, + ), nil + } + } + + return nil, nil +} + +func mapFields(ctx context.Context, instance *telemetryrouter.TelemetryRouterResponse, model *Model) error { + if instance == nil { + return fmt.Errorf("instance is nil") + } + if model == nil { + return fmt.Errorf("model is nil") + } + var instanceID string + if model.InstanceID.ValueString() != "" { + instanceID = model.InstanceID.ValueString() + } else if instance.Id != "" { + instanceID = instance.Id + } else { + return fmt.Errorf("instance id not present") + } + + model.ID = tfutils.BuildInternalTerraformId(model.ProjectID.ValueString(), model.Region.ValueString(), instanceID) + model.InstanceID = types.StringValue(instanceID) + model.DisplayName = types.StringValue(instance.DisplayName) + model.Description = types.StringPointerValue(instance.Description) + model.CreationTime = types.StringValue(instance.CreationTime.String()) + model.URI = types.StringValue(instance.Uri) + model.Status = types.StringValue(instance.Status) + + if err := mapFilter(ctx, instance, model); err != nil { + return fmt.Errorf("map filter: %w", err) + } + + return nil +} + +func mapFilter(ctx context.Context, instance *telemetryrouter.TelemetryRouterResponse, model *Model) error { + if instance.Filter == nil { + model.Filter = types.ObjectNull(filterTypes) + return nil + } + + attrList := []attr.Value{} + for _, currentAttr := range instance.Filter.Attributes { + values, diags := types.ListValueFrom(ctx, types.StringType, currentAttr.Values) + if diags.HasError() { + return fmt.Errorf("mapping filter values: %w", core.DiagsToError(diags)) + } + attrModel, diags := types.ObjectValueFrom(ctx, attributeTypes, attribute{ + Key: types.StringValue(currentAttr.Key), + Level: types.StringValue(string(currentAttr.Level)), + Matcher: types.StringValue(string(currentAttr.Matcher)), + Values: values, + }) + if diags.HasError() { + return fmt.Errorf("mapping opsgenie config: %w", core.DiagsToError(diags)) + } + attrList = append(attrList, attrModel) + } + + var attrConfigs basetypes.ListValue + var diags diag.Diagnostics + if len(attrList) == 0 { + attrConfigs = types.ListNull(types.ObjectType{AttrTypes: attributeTypes}) + } else { + attrConfigs, diags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, attrList) + if diags.HasError() { + return fmt.Errorf("mapping attributes: %w", core.DiagsToError(diags)) + } + } + + filterValue, diags := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrConfigs, + }) + if diags.HasError() { + return fmt.Errorf("mapping filter: %w", core.DiagsToError(diags)) + } + model.Filter = filterValue + + return nil +} + +func toUpdatePayload(ctx context.Context, diags diag.Diagnostics, model *Model) (*telemetryrouter.UpdateTelemetryRouterPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + payload := &telemetryrouter.UpdateTelemetryRouterPayload{ + DisplayName: conversion.StringValueToPointer(model.DisplayName), + Description: conversion.StringValueToPointer(model.Description), + } + + configFilter, err := toConfigFilter(ctx, diags, model) + if err != nil { + return nil, err + } + payload.Filter = configFilter + + return payload, nil +} diff --git a/stackit/internal/services/telemetryrouter/instance/resource_test.go b/stackit/internal/services/telemetryrouter/instance/resource_test.go new file mode 100644 index 000000000..e8f7026c8 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/instance/resource_test.go @@ -0,0 +1,266 @@ +package instance + +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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" +) + +var testTime = time.Now() + +func fixtureInstance(mods ...func(instance *telemetryrouter.TelemetryRouterResponse)) *telemetryrouter.TelemetryRouterResponse { + instance := &telemetryrouter.TelemetryRouterResponse{ + Id: "iid", + CreationTime: testTime, + Uri: "uri", + Status: "active", + } + for _, mod := range mods { + mod(instance) + } + return instance +} + +func fixtureModel(mods ...func(model *Model)) *Model { + model := &Model{ + ID: types.StringValue("pid,rid,iid"), + InstanceID: types.StringValue("iid"), + Region: types.StringValue("rid"), + ProjectID: types.StringValue("pid"), + DisplayName: types.String{}, + Description: types.String{}, + CreationTime: types.StringValue(testTime.String()), + URI: types.StringValue("uri"), + Status: types.StringValue("active"), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapFields(t *testing.T) { + tests := []struct { + description string + input *telemetryrouter.TelemetryRouterResponse + expected *Model + wantErr bool + }{ + { + description: "min values", + input: fixtureInstance(func(instance *telemetryrouter.TelemetryRouterResponse) { + instance.DisplayName = "display-name" + }), + expected: fixtureModel(func(model *Model) { + model.DisplayName = types.StringValue("display-name") + }), + }, + { + description: "max values", + input: fixtureInstance(func(instance *telemetryrouter.TelemetryRouterResponse) { + instance.DisplayName = "display-name" + instance.Description = utils.Ptr("description") + instance.Uri = "query-url" + instance.Filter = &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "resource", + Matcher: "!=", + Values: []string{"a", "b"}, + }, + }, + } + }), + expected: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("resource"), + Matcher: types.StringValue("!="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + model.Filter = fltr + model.DisplayName = types.StringValue("display-name") + model.Description = types.StringValue("description") + model.URI = types.StringValue("query-url") + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureModel(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ProjectID: tt.expected.ProjectID, + 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 TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetryrouter.CreateTelemetryRouterPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetryrouter.CreateTelemetryRouterPayload{}, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("resource"), + Matcher: types.StringValue("!="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + model.Filter = fltr + model.DisplayName = types.StringValue("display-name") + model.Description = types.StringValue("description") + }), + expected: &telemetryrouter.CreateTelemetryRouterPayload{ + DisplayName: "display-name", + Description: utils.Ptr("description"), + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "resource", + Matcher: "!=", + Values: []string{"a", "b"}, + }, + }, + }, + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toCreatePayload(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 TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *telemetryrouter.UpdateTelemetryRouterPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &telemetryrouter.UpdateTelemetryRouterPayload{}, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + ctx := context.Background() + vals, _ := types.ListValueFrom(ctx, types.StringType, []string{"a", "b"}) + attrs, _ := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributeTypes}, []attribute{ + { + Key: types.StringValue("test"), + Level: types.StringValue("resource"), + Matcher: types.StringValue("!="), + Values: vals, + }, + }) + fltr, _ := types.ObjectValueFrom(ctx, filterTypes, filter{ + Attributes: attrs, + }) + model.Filter = fltr + model.DisplayName = types.StringValue("display-name") + model.Description = types.StringValue("description") + }), + expected: &telemetryrouter.UpdateTelemetryRouterPayload{ + DisplayName: utils.Ptr("display-name"), + Description: utils.Ptr("description"), + Filter: &telemetryrouter.ConfigFilter{ + Attributes: []telemetryrouter.ConfigFilterAttributes{ + { + Key: "test", + Level: "resource", + Matcher: "!=", + Values: []string{"a", "b"}, + }, + }, + }, + }, + }, + { + description: "nil model", + wantErrMessage: "missing model", + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + got, err := toUpdatePayload(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/telemetryrouter/telemetryrouter_acc_test.go b/stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go new file mode 100644 index 000000000..26071f992 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go @@ -0,0 +1,1165 @@ +package telemetryrouter_test + +import ( + "context" + _ "embed" + "errors" + "fmt" + "maps" + "net/http" + "slices" + "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" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +var ( + //go:embed testdata/instance-min.tf + instanceMin string + + //go:embed testdata/instance-max.tf + instanceMax string + + //go:embed testdata/access-token-min.tf + accessTokenMinConfig string + + //go:embed testdata/access-token-max.tf + accessTokenMaxConfig string + + //go:embed testdata/destination-otlp-basic-auth.tf + destinationOTLPBasicAuthConfig string + + //go:embed testdata/destination-otlp-bearer-token.tf + destinationOTLPBearerTokenConfig string + + //go:embed testdata/destination-s3.tf + destinationS3Config string +) + +var testConfigVarsMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-telemetryrouter-min"), +} + +func testConfigVarsMinUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigVarsMin)) + maps.Copy(newVars, testConfigVarsMin) + newVars["display_name"] = config.StringVariable("tf-acc-test-telemetryrouter-upd") + return newVars +} + +var testConfigVarsMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-telemetryrouter-max"), + "description": config.StringVariable("Terraform Acceptance Test TelemetryRouter Instance"), + "filter_key": config.StringVariable("key"), + "filter_level": config.StringVariable("logRecord"), + "filter_matcher": config.StringVariable("="), + "filter_value0": config.StringVariable("value1"), + "filter_value1": config.StringVariable("value2"), +} + +func testConfigVarsMaxUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigVarsMin)) + maps.Copy(newVars, testConfigVarsMin) + newVars["display_name"] = config.StringVariable("tf-acc-test-telemetryrouter-upd") + newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryRouter Instance Updated") + newVars["filter_key"] = config.StringVariable("other") + newVars["filter_level"] = config.StringVariable("resource") + newVars["filter_matcher"] = config.StringVariable("!=") + newVars["filter_value0"] = config.StringVariable("value3") + newVars["filter_value1"] = config.StringVariable("value4") + + return newVars +} + +var testConfigAccessTokenVarsMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-acc-token-min"), + "status": config.StringVariable("active"), +} + +func testConfigAccessTokenVarsMinUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigAccessTokenVarsMin)) + maps.Copy(newVars, testConfigAccessTokenVarsMin) + newVars["display_name"] = config.StringVariable("tf-acc-test-token-updated") + return newVars +} + +var testConfigAccessTokenVarsMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-acc-token-max"), + "description": config.StringVariable("Terraform Acceptance Test TelemetryRouter Access Token"), + "ttl": config.IntegerVariable(7), + "status": config.StringVariable("active"), +} + +func testConfigAccessTokenVarsMaxUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigAccessTokenVarsMax)) + maps.Copy(newVars, testConfigAccessTokenVarsMax) + newVars["display_name"] = config.StringVariable("tf-acc-test-token-updated") + newVars["description"] = config.StringVariable("tf-acc-test-token-decription-updated") + return newVars +} + +var testConfigDestinationVarsOTLPBasicAuth = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-tlmr-dest"), + "description": config.StringVariable("Terraform Acceptance Test TelemetryRouter OTLP Destination"), + "config_filter_key": config.StringVariable("key"), + "config_filter_level": config.StringVariable("logRecord"), + "config_filter_matcher": config.StringVariable("="), + "config_filter_value0": config.StringVariable("value1"), + "config_filter_value1": config.StringVariable("value2"), + "config_opentelemetry_username": config.StringVariable("user"), + "config_opentelemetry_password": config.StringVariable("password"), + "config_opentelemetry_uri": config.StringVariable("https://localhost:8116"), +} + +func testConfigDestinationVarsOTLPBasicAuthUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigDestinationVarsOTLPBasicAuth)) + maps.Copy(newVars, testConfigDestinationVarsOTLPBasicAuth) + newVars["display_name"] = config.StringVariable("tf-acc-test-tlmr-dest-upd") + newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryRouter Destination Updated") + newVars["config_filter_key"] = config.StringVariable("other") + newVars["config_filter_level"] = config.StringVariable("resource") + newVars["config_filter_matcher"] = config.StringVariable("!=") + newVars["config_filter_value0"] = config.StringVariable("value3") + newVars["config_filter_value1"] = config.StringVariable("value4") + newVars["config_opentelemetry_username"] = config.StringVariable("user1") + newVars["config_opentelemetry_password"] = config.StringVariable("pass1") + newVars["config_opentelemetry_uri"] = config.StringVariable("https://localhost:8117") + + return newVars +} + +var testConfigDestinationVarsOTLPBearerToken = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-tlmr-dest"), + "description": config.StringVariable("Terraform Acceptance Test TelemetryRouter OTLP Destination"), + "config_filter_key": config.StringVariable("key"), + "config_filter_level": config.StringVariable("logRecord"), + "config_filter_matcher": config.StringVariable("="), + "config_filter_value0": config.StringVariable("value1"), + "config_filter_value1": config.StringVariable("value2"), + "config_opentelemetry_bearer_token": config.StringVariable("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30"), + "config_opentelemetry_uri": config.StringVariable("https://localhost:8116"), +} + +func testConfigDestinationVarsOTLPBearerTokenUpdated() config.Variables { + newVars := make(config.Variables, len(testConfigDestinationVarsOTLPBearerToken)) + maps.Copy(newVars, testConfigDestinationVarsOTLPBearerToken) + newVars["display_name"] = config.StringVariable("tf-acc-test-tlmr-dest-upd") + newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryRouter Destination Updated") + newVars["config_filter_key"] = config.StringVariable("other") + newVars["config_filter_level"] = config.StringVariable("resource") + newVars["config_filter_matcher"] = config.StringVariable("!=") + newVars["config_filter_value0"] = config.StringVariable("value3") + newVars["config_filter_value1"] = config.StringVariable("value4") + newVars["config_opentelemetry_bearer_token"] = config.StringVariable("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV31") + newVars["config_opentelemetry_uri"] = config.StringVariable("https://localhost:8117") + + return newVars +} + +var testConfigDestinationVarsS3 = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "region": config.StringVariable(testutil.Region), + "display_name": config.StringVariable("tf-acc-test-tlmr-dest"), + "description": config.StringVariable("Terraform Acceptance Test TelemetryRouter OTLP Destination"), + "config_filter_key": config.StringVariable("key"), + "config_filter_level": config.StringVariable("logRecord"), + "config_filter_matcher": config.StringVariable("="), + "config_filter_value0": config.StringVariable("value1"), + "config_filter_value1": config.StringVariable("value2"), + "config_s3_id": config.StringVariable("id"), + "config_s3_secret": config.StringVariable("secret"), + "config_s3_bucket": config.StringVariable("bucket"), + "config_s3_endpoint": config.StringVariable("https://localhost:8116"), +} + +func testConfigDestinationVarsS3Updated() config.Variables { + newVars := make(config.Variables, len(testConfigDestinationVarsS3)) + maps.Copy(newVars, testConfigDestinationVarsS3) + newVars["display_name"] = config.StringVariable("tf-acc-test-tlmr-dest-upd") + newVars["description"] = config.StringVariable("Terraform Acceptance Test TelemetryRouter Destination Updated") + newVars["config_filter_key"] = config.StringVariable("other") + newVars["config_filter_level"] = config.StringVariable("resource") + newVars["config_filter_matcher"] = config.StringVariable("!=") + newVars["config_filter_value0"] = config.StringVariable("value3") + newVars["config_filter_value1"] = config.StringVariable("value4") + newVars["config_s3_id"] = config.StringVariable("id1") + newVars["config_s3_secret"] = config.StringVariable("secret1") + newVars["config_s3_bucket"] = config.StringVariable("bucket1") + newVars["config_s3_endpoint"] = config.StringVariable("https://localhost:8117") + + return newVars +} + +func TestTelemetryRouterInstanceMin(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() + instanceMin, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigVarsMin["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigVarsMin["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigVarsMin, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + instanceMin + ` + data "stackit_telemetryrouter_instance" "router" { + project_id = stackit_telemetryrouter_instance.router.project_id + region = stackit_telemetryrouter_instance.router.region + instance_id = stackit_telemetryrouter_instance.router.instance_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "region", + "data.stackit_telemetryrouter_instance.router", "region", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "display_name", + "data.stackit_telemetryrouter_instance.router", "display_name", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "id", + "data.stackit_telemetryrouter_instance.router", "id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "instance_id", + "data.stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "creation_time", + "data.stackit_telemetryrouter_instance.router", "creation_time", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "uri", + "data.stackit_telemetryrouter_instance.router", "uri", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "status", + "data.stackit_telemetryrouter_instance.router", "status", + ), + ), + }, + // Import + { + ConfigVariables: testConfigVarsMin, + ResourceName: "stackit_telemetryrouter_instance.router", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_instance.router"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_instance.router") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + // Update + { + ConfigVariables: testConfigVarsMinUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + instanceMin, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestTelemetryRouterInstanceMax(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() + instanceMax, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigVarsMax["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigVarsMax["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigVarsMax["filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigVarsMax["filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigVarsMax["filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigVarsMax["filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigVarsMax["filter_value1"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigVarsMax, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + instanceMax + ` + data "stackit_telemetryrouter_instance" "router" { + project_id = stackit_telemetryrouter_instance.router.project_id + region = stackit_telemetryrouter_instance.router.region + instance_id = stackit_telemetryrouter_instance.router.instance_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "region", + "data.stackit_telemetryrouter_instance.router", "region", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "display_name", + "data.stackit_telemetryrouter_instance.router", "display_name", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "id", + "data.stackit_telemetryrouter_instance.router", "id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "instance_id", + "data.stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "creation_time", + "data.stackit_telemetryrouter_instance.router", "creation_time", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "uri", + "data.stackit_telemetryrouter_instance.router", "uri", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "status", + "data.stackit_telemetryrouter_instance.router", "status", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "description", + "data.stackit_telemetryrouter_instance.router", "description", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "filter.attributes.0.key", + "data.stackit_telemetryrouter_instance.router", "filter.attributes.0.key", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "filter.attributes.0.level", + "data.stackit_telemetryrouter_instance.router", "filter.attributes.0.level", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "filter.attributes.0.matcher", + "data.stackit_telemetryrouter_instance.router", "filter.attributes.0.matcher", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "filter.attributes.0.value.0", + "data.stackit_telemetryrouter_instance.router", "filter.attributes.0.value.0", + ), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_instance.router", "filter.attributes.0.value.1", + "data.stackit_telemetryrouter_instance.router", "filter.attributes.0.value.1", + ), + ), + }, + // Import + { + ConfigVariables: testConfigVarsMax, + ResourceName: "stackit_telemetryrouter_instance.router", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_instance.router"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_instance.router") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + // Update + { + ConfigVariables: testConfigVarsMaxUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + instanceMax, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "description", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["filter_value1"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccTelemetryRouterAccessTokenMin(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigAccessTokenVarsMin, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + accessTokenMinConfig, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + + // Access token data + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_access_token.accessToken", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "creator_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "status", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["status"])), + resource.TestCheckNoResourceAttr("stackit_telemetryrouter_access_token.accessToken", "expiration_time"), + ), + }, + // Datasource + { + ConfigVariables: testConfigAccessTokenVarsMin, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + accessTokenMinConfig + ` + data "stackit_telemetryrouter_access_token" "accessToken" { + project_id = stackit_telemetryrouter_access_token.accessToken.project_id + region = stackit_telemetryrouter_access_token.accessToken.region + instance_id = stackit_telemetryrouter_access_token.accessToken.instance_id + access_token_id = stackit_telemetryrouter_access_token.accessToken.access_token_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_access_token.accessToken", "instance_id", + "data.stackit_telemetryrouter_access_token.accessToken", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_access_token.accessToken", "creator_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "status", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["status"])), + ), + }, + // Import + { + ConfigVariables: testConfigAccessTokenVarsMin, + ResourceName: "stackit_telemetryrouter_access_token.accessToken", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_access_token.accessToken"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_access_token.accessToken") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + tokenId, ok := rs.Primary.Attributes["access_token_id"] + if !ok { + return "", fmt.Errorf("access_token_id not set") + } + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, tokenId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ttl", "access_token"}, + }, + // Update + { + ConfigVariables: testConfigAccessTokenVarsMinUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + accessTokenMinConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_access_token.accessToken", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "creator_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "status"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccTelemetryRouterAccessTokenMax(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigAccessTokenVarsMax, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + accessTokenMaxConfig, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["description"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + + // Access token data + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_access_token.accessToken", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "ttl", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["ttl"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "creator_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "status", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["status"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "expiration_time"), + ), + }, + // Datasource + { + ConfigVariables: testConfigAccessTokenVarsMax, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + accessTokenMaxConfig + ` + data "stackit_telemetryrouter_access_token" "accessToken" { + project_id = stackit_telemetryrouter_access_token.accessToken.project_id + region = stackit_telemetryrouter_access_token.accessToken.region + instance_id = stackit_telemetryrouter_access_token.accessToken.instance_id + access_token_id = stackit_telemetryrouter_access_token.accessToken.access_token_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["project_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_access_token.accessToken", "instance_id", + "data.stackit_telemetryrouter_access_token.accessToken", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["display_name"])), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_access_token.accessToken", "creator_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_access_token.accessToken", "status", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["status"])), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_access_token.accessToken", "expiration_time"), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_access_token.accessToken", "description"), + ), + }, + // Import + { + ConfigVariables: testConfigAccessTokenVarsMax, + ResourceName: "stackit_telemetryrouter_access_token.accessToken", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_access_token.accessToken"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_access_token.accessToken") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + tokenId, ok := rs.Primary.Attributes["access_token_id"] + if !ok { + return "", fmt.Errorf("access_token_id not set") + } + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, tokenId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ttl", "access_token"}, + }, + // Update + { + ConfigVariables: testConfigAccessTokenVarsMaxUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + accessTokenMaxConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_access_token.accessToken", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_access_token.accessToken", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["description"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "creator_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "status"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_access_token.accessToken", "access_token"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccTelemetryRouterDestinationOTLPBasicAuth(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigDestinationVarsOTLPBasicAuth, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationOTLPBasicAuthConfig, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + + // Destination data + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_value1"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.basic_auth.username", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_opentelemetry_username"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.basic_auth.password", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_opentelemetry_password"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.uri", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_opentelemetry_uri"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.config_type", "OpenTelemetry"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigDestinationVarsOTLPBasicAuth, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationOTLPBasicAuthConfig + ` + data "stackit_telemetryrouter_destination" "destination" { + project_id = stackit_telemetryrouter_destination.destination.project_id + region = stackit_telemetryrouter_destination.destination.region + instance_id = stackit_telemetryrouter_destination.destination.instance_id + destination_id = stackit_telemetryrouter_destination.destination.destination_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["project_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "data.stackit_telemetryrouter_destination.destination", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["display_name"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["description"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_key"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_level"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_matcher"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_value0"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_filter_value1"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.opentelemetry.uri", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuth["config_opentelemetry_uri"])), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_destination.destination", "status"), + ), + }, + // Import + { + ConfigVariables: testConfigDestinationVarsOTLPBasicAuth, + ResourceName: "stackit_telemetryrouter_destination.destination", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_destination.destination"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_destination.destination") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + destinationId, ok := rs.Primary.Attributes["destination_id"] + if !ok { + return "", fmt.Errorf("destination_id not set") + } + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, destinationId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config.opentelemetry.basic_auth.password"}, + }, + // Update + { + ConfigVariables: testConfigDestinationVarsOTLPBasicAuthUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationOTLPBasicAuthConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_filter_value1"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.basic_auth.username", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_opentelemetry_username"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.basic_auth.password", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_opentelemetry_password"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.uri", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBasicAuthUpdated()["config_opentelemetry_uri"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "status"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.config_type", "OpenTelemetry"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccTelemetryRouterDestinationOTLPBearerToken(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigDestinationVarsOTLPBearerToken, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationOTLPBearerTokenConfig, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + + // Destination data + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_value1"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.bearer_token", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_opentelemetry_bearer_token"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.uri", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_opentelemetry_uri"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.config_type", "OpenTelemetry"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigDestinationVarsOTLPBearerToken, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationOTLPBearerTokenConfig + ` + data "stackit_telemetryrouter_destination" "destination" { + project_id = stackit_telemetryrouter_destination.destination.project_id + region = stackit_telemetryrouter_destination.destination.region + instance_id = stackit_telemetryrouter_destination.destination.instance_id + destination_id = stackit_telemetryrouter_destination.destination.destination_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["project_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "data.stackit_telemetryrouter_destination.destination", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["display_name"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["description"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_key"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_level"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_matcher"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_value0"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_filter_value1"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.opentelemetry.uri", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerToken["config_opentelemetry_uri"])), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_destination.destination", "status"), + ), + }, + // Import + { + ConfigVariables: testConfigDestinationVarsOTLPBearerToken, + ResourceName: "stackit_telemetryrouter_destination.destination", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_destination.destination"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_destination.destination") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + destinationId, ok := rs.Primary.Attributes["destination_id"] + if !ok { + return "", fmt.Errorf("destination_id not set") + } + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, destinationId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config.opentelemetry.bearer_token"}, + }, + // Update + { + ConfigVariables: testConfigDestinationVarsOTLPBearerTokenUpdated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationOTLPBearerTokenConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_filter_value1"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.bearer_token", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_opentelemetry_bearer_token"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.opentelemetry.uri", testutil.ConvertConfigVariable(testConfigDestinationVarsOTLPBearerTokenUpdated()["config_opentelemetry_uri"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "status"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.config_type", "OpenTelemetry"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccTelemetryRouterDestinationS3(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigDestinationVarsS3, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationS3Config, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["region"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_instance.router", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["display_name"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "instance_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "creation_time"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "uri"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_instance.router", "status"), + + // Destination data + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_value1"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.access_key.id", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_s3_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.access_key.secret", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_s3_secret"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.bucket", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_s3_bucket"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.endpoint", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_s3_endpoint"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.config_type", "S3"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "status"), + ), + }, + // Datasource + { + ConfigVariables: testConfigDestinationVarsS3, + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationS3Config + ` + data "stackit_telemetryrouter_destination" "destination" { + project_id = stackit_telemetryrouter_destination.destination.project_id + region = stackit_telemetryrouter_destination.destination.region + instance_id = stackit_telemetryrouter_destination.destination.instance_id + destination_id = stackit_telemetryrouter_destination.destination.destination_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["project_id"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "data.stackit_telemetryrouter_destination.destination", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["display_name"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["description"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_key"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_level"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_matcher"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_value0"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_filter_value1"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.s3.bucket", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_s3_bucket"])), + resource.TestCheckResourceAttr("data.stackit_telemetryrouter_destination.destination", "config.s3.endpoint", testutil.ConvertConfigVariable(testConfigDestinationVarsS3["config_s3_endpoint"])), + resource.TestCheckResourceAttrSet("data.stackit_telemetryrouter_destination.destination", "status"), + ), + }, + // Import + { + ConfigVariables: testConfigDestinationVarsS3, + ResourceName: "stackit_telemetryrouter_destination.destination", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_telemetryrouter_destination.destination"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_telemetryrouter_destination.destination") + } + instanceId, ok := rs.Primary.Attributes["instance_id"] + if !ok { + return "", fmt.Errorf("instance_id not set") + } + destinationId, ok := rs.Primary.Attributes["destination_id"] + if !ok { + return "", fmt.Errorf("destination_id not set") + } + return fmt.Sprintf("%s,%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId, destinationId), nil + }, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config.s3.access_key"}, + }, + // Update + { + ConfigVariables: testConfigDestinationVarsS3Updated(), + Config: testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig() + destinationS3Config, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "project_id", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["project_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "region", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_telemetryrouter_destination.destination", "instance_id", + "stackit_telemetryrouter_instance.router", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "display_name", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["display_name"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "description", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["description"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.key", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_filter_key"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.level", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_filter_level"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.matcher", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_filter_matcher"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.0", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_filter_value0"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.filter.attributes.0.values.1", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_filter_value1"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.access_key.id", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_s3_id"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.access_key.secret", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_s3_secret"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.bucket", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_s3_bucket"])), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.s3.endpoint", testutil.ConvertConfigVariable(testConfigDestinationVarsS3Updated()["config_s3_endpoint"])), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "destination_id"), + resource.TestCheckResourceAttrSet("stackit_telemetryrouter_destination.destination", "status"), + resource.TestCheckResourceAttr("stackit_telemetryrouter_destination.destination", "config.config_type", "S3"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func testAccCheckDestroy(s *terraform.State) error { + checkFunctions := []func(s *terraform.State) error{ + testAccCheckTelemetryRouterInstanceDestroy, + testAccCheckTelemetryRouterAccessTokenDestroy, + testAccCheckTelemetryRouterDestinationDestroy, + } + + 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 testAccCheckTelemetryRouterInstanceDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := telemetryrouter.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.TelemetryRouterCustomEndpoint, false)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + var instancesToDestroy []string + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_telemetryrouter_instance" { + continue + } + instanceId := strings.Split(rs.Primary.ID, core.Separator)[2] + instancesToDestroy = append(instancesToDestroy, instanceId) + } + + response, err := client.DefaultAPI.ListTelemetryRouters(ctx, testutil.ProjectId, "eu01").Execute() + if err != nil { + return fmt.Errorf("getting instances: %w", err) + } + for i := range response.TelemetryRouters { + if !slices.Contains(instancesToDestroy, response.TelemetryRouters[i].Id) { + continue + } + + err := client.DefaultAPI.DeleteTelemetryRouter(ctx, testutil.ProjectId, "eu01", response.TelemetryRouters[i].Id).Execute() + if err != nil { + return fmt.Errorf("deleting instance %s: %w", response.TelemetryRouters[i].Id, err) + } + } + return nil +} + +func testAccCheckTelemetryRouterAccessTokenDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := telemetryrouter.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.TelemetryRouterCustomEndpoint, false)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + var errs []error + // access tokens + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_telemetryrouter_access_token" { + continue + } + accessTokenId := strings.Split(rs.Primary.ID, core.Separator)[3] + instanceId := strings.Split(rs.Primary.ID, core.Separator)[2] + region := strings.Split(rs.Primary.ID, core.Separator)[1] + + err := client.DefaultAPI.DeleteAccessToken(ctx, testutil.ProjectId, region, instanceId, accessTokenId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + continue + } + } + errs = append(errs, fmt.Errorf("cannot trigger access token deletion %q: %w", accessTokenId, err)) + } + } + + return errors.Join(errs...) +} + +func testAccCheckTelemetryRouterDestinationDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := telemetryrouter.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.TelemetryRouterCustomEndpoint, false)...) + if err != nil { + return fmt.Errorf("creating client: %w", err) + } + + var errs []error + // access tokens + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_telemetryrouter_destination" { + continue + } + destinationId := strings.Split(rs.Primary.ID, core.Separator)[3] + instanceId := strings.Split(rs.Primary.ID, core.Separator)[2] + region := strings.Split(rs.Primary.ID, core.Separator)[1] + + err := client.DefaultAPI.DeleteDestination(ctx, testutil.ProjectId, region, instanceId, destinationId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + if oapiErr.StatusCode == http.StatusNotFound { + continue + } + } + errs = append(errs, fmt.Errorf("cannot trigger destination deletion %q: %w", destinationId, err)) + } + } + + return errors.Join(errs...) +} diff --git a/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf b/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf new file mode 100644 index 000000000..961d54c65 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf @@ -0,0 +1,22 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "description" {} +variable "ttl" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name + description = var.description +} + +resource "stackit_telemetryrouter_access_token" "accessToken" { + project_id = var.project_id + instance_id = stackit_telemetryrouter_instance.router.instance_id + region = var.region + display_name = var.display_name + description = var.description + ttl = var.ttl +} \ No newline at end of file diff --git a/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf b/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf new file mode 100644 index 000000000..154503930 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf @@ -0,0 +1,17 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name +} + +resource "stackit_telemetryrouter_access_token" "accessToken" { + project_id = var.project_id + instance_id = stackit_telemetryrouter_instance.router.instance_id + region = var.region + display_name = var.display_name +} \ No newline at end of file diff --git a/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf new file mode 100644 index 000000000..67feef4c7 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf @@ -0,0 +1,50 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "description" {} +variable "config_filter_key" {} +variable "config_filter_level" {} +variable "config_filter_matcher" {} +variable "config_filter_value0" {} +variable "config_filter_value1" {} +variable "config_opentelemetry_username" {} +variable "config_opentelemetry_password" {} +variable "config_opentelemetry_uri" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name +} + +resource "stackit_telemetryrouter_destination" "destination" { + project_id = var.project_id + region = var.region + instance_id = stackit_telemetryrouter_instance.router.instance_id + display_name = var.display_name + description = var.description + config = { + filter = { + attributes = [ + { + key = var.config_filter_key + level = var.config_filter_level + matcher = var.config_filter_matcher + values = [ + var.config_filter_value0, + var.config_filter_value1 + ] + } + ] + } + config_type = "OpenTelemetry" + opentelemetry = { + basic_auth = { + username = var.config_opentelemetry_username + password = var.config_opentelemetry_password + } + uri = var.config_opentelemetry_uri + } + } +} diff --git a/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf new file mode 100644 index 000000000..7dce176da --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf @@ -0,0 +1,46 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "description" {} +variable "config_filter_key" {} +variable "config_filter_level" {} +variable "config_filter_matcher" {} +variable "config_filter_value0" {} +variable "config_filter_value1" {} +variable "config_opentelemetry_bearer_token" {} +variable "config_opentelemetry_uri" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name +} + +resource "stackit_telemetryrouter_destination" "destination" { + project_id = var.project_id + region = var.region + instance_id = stackit_telemetryrouter_instance.router.instance_id + display_name = var.display_name + description = var.description + config = { + filter = { + attributes = [ + { + key = var.config_filter_key + level = var.config_filter_level + matcher = var.config_filter_matcher + values = [ + var.config_filter_value0, + var.config_filter_value1 + ] + } + ] + } + config_type = "OpenTelemetry" + opentelemetry = { + bearer_token = var.config_opentelemetry_bearer_token + uri = var.config_opentelemetry_uri + } + } +} diff --git a/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf b/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf new file mode 100644 index 000000000..090c59511 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf @@ -0,0 +1,52 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "description" {} +variable "config_filter_key" {} +variable "config_filter_level" {} +variable "config_filter_matcher" {} +variable "config_filter_value0" {} +variable "config_filter_value1" {} +variable "config_s3_id" {} +variable "config_s3_secret" {} +variable "config_s3_bucket" {} +variable "config_s3_endpoint" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name +} + +resource "stackit_telemetryrouter_destination" "destination" { + project_id = var.project_id + region = var.region + instance_id = stackit_telemetryrouter_instance.router.instance_id + display_name = var.display_name + description = var.description + config = { + filter = { + attributes = [ + { + key = var.config_filter_key + level = var.config_filter_level + matcher = var.config_filter_matcher + values = [ + var.config_filter_value0, + var.config_filter_value1 + ] + } + ] + } + config_type = "S3" + s3 = { + access_key = { + id = var.config_s3_id + secret = var.config_s3_secret + } + bucket = var.config_s3_bucket + endpoint = var.config_s3_endpoint + } + } +} diff --git a/stackit/internal/services/telemetryrouter/testdata/instance-max.tf b/stackit/internal/services/telemetryrouter/testdata/instance-max.tf new file mode 100644 index 000000000..366e9fa55 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/instance-max.tf @@ -0,0 +1,30 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "description" {} +variable "filter_key" {} +variable "filter_level" {} +variable "filter_matcher" {} +variable "filter_value0" {} +variable "filter_value1" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name + description = var.description + filter = { + attributes = [ + { + key = var.filter_key + level = var.filter_level + matcher = var.filter_matcher + values = [ + var.filter_value0, + var.filter_value1 + ] + } + ] + } +} diff --git a/stackit/internal/services/telemetryrouter/testdata/instance-min.tf b/stackit/internal/services/telemetryrouter/testdata/instance-min.tf new file mode 100644 index 000000000..e9081945c --- /dev/null +++ b/stackit/internal/services/telemetryrouter/testdata/instance-min.tf @@ -0,0 +1,10 @@ + +variable "project_id" {} +variable "region" {} +variable "display_name" {} + +resource "stackit_telemetryrouter_instance" "router" { + project_id = var.project_id + region = var.region + display_name = var.display_name +} diff --git a/stackit/internal/services/telemetryrouter/utils/utils.go b/stackit/internal/services/telemetryrouter/utils/utils.go new file mode 100644 index 000000000..c4155f9ff --- /dev/null +++ b/stackit/internal/services/telemetryrouter/utils/utils.go @@ -0,0 +1,29 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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) *telemetryrouter.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.TelemetryRouterCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.TelemetryRouterCustomEndpoint)) + } + apiClient, err := telemetryrouter.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/telemetryrouter/utils/utils_test.go b/stackit/internal/services/telemetryrouter/utils/utils_test.go new file mode 100644 index 000000000..712268f33 --- /dev/null +++ b/stackit/internal/services/telemetryrouter/utils/utils_test.go @@ -0,0 +1,93 @@ +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" + telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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://telemetry-router-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 *telemetryrouter.APIClient + }{ + { + name: "default endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + }, + }, + expected: func() *telemetryrouter.APIClient { + apiClient, err := telemetryrouter.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, + TelemetryRouterCustomEndpoint: testCustomEndpoint, + }, + }, + expected: func() *telemetryrouter.APIClient { + apiClient, err := telemetryrouter.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..edbf04cf1 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"} + TelemetryRouterCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_TELEMETRYROUTER_CUSTOM_ENDPOINT", providerName: "telemetryrouter_custom_endpoint"} allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, @@ -131,6 +132,7 @@ var ( ServiceAccountCustomEndpoint, TokenCustomEndpoint, SKECustomEndpoint, + TelemetryRouterCustomEndpoint, } ) diff --git a/stackit/provider.go b/stackit/provider.go index 3abeb1167..c4407bf1b 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -118,6 +118,9 @@ import ( skeMachineImages "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/provideroptions/machineimages" sqlServerFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/instance" sqlServerFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/user" + telemetryRouterAccessToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetryrouter/accesstoken" + telemetryRouterDestination "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetryrouter/destination" + telemetryRouterInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetryrouter/instance" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) @@ -194,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"` + TelemetryRouterCustomEndpoint types.String `tfsdk:"telemetryrouter_custom_endpoint"` TokenCustomEndpoint types.String `tfsdk:"token_custom_endpoint"` OIDCTokenRequestURL types.String `tfsdk:"oidc_request_url"` OIDCTokenRequestToken types.String `tfsdk:"oidc_request_token"` @@ -248,6 +252,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "secretsmanager_custom_endpoint": "Custom endpoint for the Secrets Manager service", "sqlserverflex_custom_endpoint": "Custom endpoint for the SQL Server Flex service", "ske_custom_endpoint": "Custom endpoint for the Kubernetes Engine (SKE) service", + "telemetryrouter_custom_endpoint": "Custom endpoint for the Telemetry Router service", "service_enablement_custom_endpoint": "Custom endpoint for the Service Enablement API", "sfs_custom_endpoint": "Custom endpoint for the Stackit Filestorage API", "token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow", @@ -456,6 +461,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["sfs_custom_endpoint"], }, + "telemetryrouter_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["telemetryrouter_custom_endpoint"], + }, "token_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["token_custom_endpoint"], @@ -542,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.TelemetryRouterCustomEndpoint, func(v string) { providerData.TelemetryRouterCustomEndpoint = v }) if !(providerConfig.Experiments.IsUnknown() || providerConfig.Experiments.IsNull()) { var experimentValues []string @@ -699,6 +709,9 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource compliancelock.NewComplianceLockDataSource, serverBackupEnable.NewServerBackupEnableDataSource, serverUpdateEnable.NewServerUpdateEnableDataSource, + telemetryRouterAccessToken.NewTelemetryRouterAccessTokenDataSource, + telemetryRouterInstance.NewTelemetryRouterInstanceDataSource, + telemetryRouterDestination.NewTelemetryRouterDestinationDataSource, } dataSources = append(dataSources, customRole.NewCustomRoleDataSources()...) @@ -788,6 +801,9 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { compliancelock.NewComplianceLockResource, serverBackupEnable.NewServerBackupEnableResource, serverUpdateEnable.NewServerUpdateEnableResource, + telemetryRouterAccessToken.NewTelemetryRouterAccessTokenResource, + telemetryRouterInstance.NewTelemetryRouterInstanceResource, + telemetryRouterDestination.NewTelemetryRouterDestinationResource, } resources = append(resources, roleAssignements.NewRoleAssignmentResources()...) resources = append(resources, customRole.NewCustomRoleResources()...) From 3b1b2b08f53e3807ec5b8cd2f5a18ac73eefb31c Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Mon, 27 Apr 2026 09:58:36 +0200 Subject: [PATCH 2/4] chore: make fmt --- .../telemetryrouter/accesstoken/datasource.go | 1 + .../telemetryrouter/accesstoken/resource.go | 1 + .../telemetryrouter/destination/datasource.go | 1 + .../telemetryrouter/destination/resource.go | 1 + .../telemetryrouter/instance/datasource.go | 1 + .../telemetryrouter/instance/resource.go | 1 + .../telemetryrouter_acc_test.go | 1 + .../testdata/access-token-max.tf | 8 +++---- .../testdata/access-token-min.tf | 6 ++--- .../testdata/destination-otlp-basic-auth.tf | 20 ++++++++-------- .../testdata/destination-otlp-bearer-token.tf | 22 ++++++++--------- .../testdata/destination-s3.tf | 24 +++++++++---------- .../telemetryrouter/testdata/instance-max.tf | 14 +++++------ .../telemetryrouter/testdata/instance-min.tf | 6 ++--- .../services/telemetryrouter/utils/utils.go | 1 + .../telemetryrouter/utils/utils_test.go | 1 + 16 files changed, 59 insertions(+), 50 deletions(-) diff --git a/stackit/internal/services/telemetryrouter/accesstoken/datasource.go b/stackit/internal/services/telemetryrouter/accesstoken/datasource.go index 005b4aaef..2e26d0ba7 100644 --- a/stackit/internal/services/telemetryrouter/accesstoken/datasource.go +++ b/stackit/internal/services/telemetryrouter/accesstoken/datasource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" diff --git a/stackit/internal/services/telemetryrouter/accesstoken/resource.go b/stackit/internal/services/telemetryrouter/accesstoken/resource.go index 588b751bf..786eb26b2 100644 --- a/stackit/internal/services/telemetryrouter/accesstoken/resource.go +++ b/stackit/internal/services/telemetryrouter/accesstoken/resource.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" diff --git a/stackit/internal/services/telemetryrouter/destination/datasource.go b/stackit/internal/services/telemetryrouter/destination/datasource.go index 64b34ce13..2bd38040d 100644 --- a/stackit/internal/services/telemetryrouter/destination/datasource.go +++ b/stackit/internal/services/telemetryrouter/destination/datasource.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" diff --git a/stackit/internal/services/telemetryrouter/destination/resource.go b/stackit/internal/services/telemetryrouter/destination/resource.go index 25283efad..20b990339 100644 --- a/stackit/internal/services/telemetryrouter/destination/resource.go +++ b/stackit/internal/services/telemetryrouter/destination/resource.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" diff --git a/stackit/internal/services/telemetryrouter/instance/datasource.go b/stackit/internal/services/telemetryrouter/instance/datasource.go index dbfa26551..4998e328b 100644 --- a/stackit/internal/services/telemetryrouter/instance/datasource.go +++ b/stackit/internal/services/telemetryrouter/instance/datasource.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" diff --git a/stackit/internal/services/telemetryrouter/instance/resource.go b/stackit/internal/services/telemetryrouter/instance/resource.go index 426bde244..175d3292b 100644 --- a/stackit/internal/services/telemetryrouter/instance/resource.go +++ b/stackit/internal/services/telemetryrouter/instance/resource.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/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/telemetryrouter/utils" diff --git a/stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go b/stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go index 26071f992..a837107ad 100644 --- a/stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go +++ b/stackit/internal/services/telemetryrouter/telemetryrouter_acc_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) diff --git a/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf b/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf index 961d54c65..0b0edd037 100644 --- a/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf +++ b/stackit/internal/services/telemetryrouter/testdata/access-token-max.tf @@ -6,10 +6,10 @@ variable "description" {} variable "ttl" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name - description = var.description + project_id = var.project_id + region = var.region + display_name = var.display_name + description = var.description } resource "stackit_telemetryrouter_access_token" "accessToken" { diff --git a/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf b/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf index 154503930..5174ca692 100644 --- a/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf +++ b/stackit/internal/services/telemetryrouter/testdata/access-token-min.tf @@ -4,9 +4,9 @@ variable "region" {} variable "display_name" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name + project_id = var.project_id + region = var.region + display_name = var.display_name } resource "stackit_telemetryrouter_access_token" "accessToken" { diff --git a/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf index 67feef4c7..8623353cd 100644 --- a/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf +++ b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-basic-auth.tf @@ -13,25 +13,25 @@ variable "config_opentelemetry_password" {} variable "config_opentelemetry_uri" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name + project_id = var.project_id + region = var.region + display_name = var.display_name } resource "stackit_telemetryrouter_destination" "destination" { - project_id = var.project_id - region = var.region + project_id = var.project_id + region = var.region instance_id = stackit_telemetryrouter_instance.router.instance_id - display_name = var.display_name - description = var.description + display_name = var.display_name + description = var.description config = { - filter = { - attributes = [ + filter = { + attributes = [ { key = var.config_filter_key level = var.config_filter_level matcher = var.config_filter_matcher - values = [ + values = [ var.config_filter_value0, var.config_filter_value1 ] diff --git a/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf index 7dce176da..b3a39df77 100644 --- a/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf +++ b/stackit/internal/services/telemetryrouter/testdata/destination-otlp-bearer-token.tf @@ -12,25 +12,25 @@ variable "config_opentelemetry_bearer_token" {} variable "config_opentelemetry_uri" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name + project_id = var.project_id + region = var.region + display_name = var.display_name } resource "stackit_telemetryrouter_destination" "destination" { - project_id = var.project_id - region = var.region + project_id = var.project_id + region = var.region instance_id = stackit_telemetryrouter_instance.router.instance_id - display_name = var.display_name - description = var.description + display_name = var.display_name + description = var.description config = { - filter = { - attributes = [ + filter = { + attributes = [ { key = var.config_filter_key level = var.config_filter_level matcher = var.config_filter_matcher - values = [ + values = [ var.config_filter_value0, var.config_filter_value1 ] @@ -40,7 +40,7 @@ resource "stackit_telemetryrouter_destination" "destination" { config_type = "OpenTelemetry" opentelemetry = { bearer_token = var.config_opentelemetry_bearer_token - uri = var.config_opentelemetry_uri + uri = var.config_opentelemetry_uri } } } diff --git a/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf b/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf index 090c59511..e533b793d 100644 --- a/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf +++ b/stackit/internal/services/telemetryrouter/testdata/destination-s3.tf @@ -14,25 +14,25 @@ variable "config_s3_bucket" {} variable "config_s3_endpoint" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name + project_id = var.project_id + region = var.region + display_name = var.display_name } resource "stackit_telemetryrouter_destination" "destination" { - project_id = var.project_id - region = var.region + project_id = var.project_id + region = var.region instance_id = stackit_telemetryrouter_instance.router.instance_id - display_name = var.display_name - description = var.description + display_name = var.display_name + description = var.description config = { - filter = { - attributes = [ + filter = { + attributes = [ { key = var.config_filter_key level = var.config_filter_level matcher = var.config_filter_matcher - values = [ + values = [ var.config_filter_value0, var.config_filter_value1 ] @@ -42,10 +42,10 @@ resource "stackit_telemetryrouter_destination" "destination" { config_type = "S3" s3 = { access_key = { - id = var.config_s3_id + id = var.config_s3_id secret = var.config_s3_secret } - bucket = var.config_s3_bucket + bucket = var.config_s3_bucket endpoint = var.config_s3_endpoint } } diff --git a/stackit/internal/services/telemetryrouter/testdata/instance-max.tf b/stackit/internal/services/telemetryrouter/testdata/instance-max.tf index 366e9fa55..2b61e17f7 100644 --- a/stackit/internal/services/telemetryrouter/testdata/instance-max.tf +++ b/stackit/internal/services/telemetryrouter/testdata/instance-max.tf @@ -10,17 +10,17 @@ variable "filter_value0" {} variable "filter_value1" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name - description = var.description - filter = { - attributes = [ + project_id = var.project_id + region = var.region + display_name = var.display_name + description = var.description + filter = { + attributes = [ { key = var.filter_key level = var.filter_level matcher = var.filter_matcher - values = [ + values = [ var.filter_value0, var.filter_value1 ] diff --git a/stackit/internal/services/telemetryrouter/testdata/instance-min.tf b/stackit/internal/services/telemetryrouter/testdata/instance-min.tf index e9081945c..fcae32be1 100644 --- a/stackit/internal/services/telemetryrouter/testdata/instance-min.tf +++ b/stackit/internal/services/telemetryrouter/testdata/instance-min.tf @@ -4,7 +4,7 @@ variable "region" {} variable "display_name" {} resource "stackit_telemetryrouter_instance" "router" { - project_id = var.project_id - region = var.region - display_name = var.display_name + project_id = var.project_id + region = var.region + display_name = var.display_name } diff --git a/stackit/internal/services/telemetryrouter/utils/utils.go b/stackit/internal/services/telemetryrouter/utils/utils.go index c4155f9ff..18f837c77 100644 --- a/stackit/internal/services/telemetryrouter/utils/utils.go +++ b/stackit/internal/services/telemetryrouter/utils/utils.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) diff --git a/stackit/internal/services/telemetryrouter/utils/utils_test.go b/stackit/internal/services/telemetryrouter/utils/utils_test.go index 712268f33..fbd476883 100644 --- a/stackit/internal/services/telemetryrouter/utils/utils_test.go +++ b/stackit/internal/services/telemetryrouter/utils/utils_test.go @@ -10,6 +10,7 @@ import ( sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" "github.com/stackitcloud/stackit-sdk-go/core/config" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) From 0f2348bdad24a650e26d6af2edb3c70acad544ce Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Mon, 27 Apr 2026 10:23:17 +0200 Subject: [PATCH 3/4] chore: fixed lint errors --- .../telemetryrouter/accesstoken/datasource.go | 2 +- .../telemetryrouter/accesstoken/resource.go | 6 +++--- .../telemetryrouter/destination/datasource.go | 6 +++--- .../telemetryrouter/destination/resource.go | 14 +++++++------- .../services/telemetryrouter/instance/resource.go | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/stackit/internal/services/telemetryrouter/accesstoken/datasource.go b/stackit/internal/services/telemetryrouter/accesstoken/datasource.go index 2e26d0ba7..369f35f14 100644 --- a/stackit/internal/services/telemetryrouter/accesstoken/datasource.go +++ b/stackit/internal/services/telemetryrouter/accesstoken/datasource.go @@ -181,7 +181,7 @@ func (d *telemetryRouterAccessTokenDataSource) Read(ctx context.Context, req dat }) } -func mapDataSourceFields(ctx context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *DataSourceModel) error { +func mapDataSourceFields(_ context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *DataSourceModel) error { if accessToken == nil { return fmt.Errorf("access token is nil") } diff --git a/stackit/internal/services/telemetryrouter/accesstoken/resource.go b/stackit/internal/services/telemetryrouter/accesstoken/resource.go index 786eb26b2..94410ca17 100644 --- a/stackit/internal/services/telemetryrouter/accesstoken/resource.go +++ b/stackit/internal/services/telemetryrouter/accesstoken/resource.go @@ -431,7 +431,7 @@ func toCreatePayload(_ context.Context, _ diag.Diagnostics, model *Model) (*tele }, nil } -func mapCreateFields(ctx context.Context, accessToken *telemetryrouter.CreateAccessTokenResponse, model *Model) error { +func mapCreateFields(_ context.Context, accessToken *telemetryrouter.CreateAccessTokenResponse, model *Model) error { if accessToken == nil { return fmt.Errorf("access token is nil") } @@ -468,7 +468,7 @@ func mapCreateFields(ctx context.Context, accessToken *telemetryrouter.CreateAcc return nil } -func mapGetFields(ctx context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *Model) error { +func mapGetFields(_ context.Context, accessToken *telemetryrouter.GetAccessTokenResponse, model *Model) error { if accessToken == nil { return fmt.Errorf("access token is nil") } @@ -501,7 +501,7 @@ func mapGetFields(ctx context.Context, accessToken *telemetryrouter.GetAccessTok return nil } -func mapUpdateFields(ctx context.Context, accessToken *telemetryrouter.UpdateAccessTokenResponse, model *Model) error { +func mapUpdateFields(_ context.Context, accessToken *telemetryrouter.UpdateAccessTokenResponse, model *Model) error { if accessToken == nil { return fmt.Errorf("access token is nil") } diff --git a/stackit/internal/services/telemetryrouter/destination/datasource.go b/stackit/internal/services/telemetryrouter/destination/datasource.go index 2bd38040d..445b32da3 100644 --- a/stackit/internal/services/telemetryrouter/destination/datasource.go +++ b/stackit/internal/services/telemetryrouter/destination/datasource.go @@ -59,9 +59,9 @@ type datasourceConfig struct { // Types corresponding to datasourceConfig var datasourceConfigTypes = map[string]attr.Type{ "config_type": basetypes.StringType{}, - "filter": basetypes.ObjectType{datasourceFilterTypes}, - "opentelemetry": basetypes.ObjectType{datasourceOpenTelemetryTypes}, - "s3": basetypes.ObjectType{datasourceS3Types}, + "filter": basetypes.ObjectType{AttrTypes: datasourceFilterTypes}, + "opentelemetry": basetypes.ObjectType{AttrTypes: datasourceOpenTelemetryTypes}, + "s3": basetypes.ObjectType{AttrTypes: datasourceS3Types}, } // Struct corresponding to datasourceFilter diff --git a/stackit/internal/services/telemetryrouter/destination/resource.go b/stackit/internal/services/telemetryrouter/destination/resource.go index 20b990339..19fca9b34 100644 --- a/stackit/internal/services/telemetryrouter/destination/resource.go +++ b/stackit/internal/services/telemetryrouter/destination/resource.go @@ -107,9 +107,9 @@ type config struct { // Types corresponding to config var configTypes = map[string]attr.Type{ "config_type": basetypes.StringType{}, - "filter": basetypes.ObjectType{filterTypes}, - "opentelemetry": basetypes.ObjectType{openTelemetryTypes}, - "s3": basetypes.ObjectType{s3Types}, + "filter": basetypes.ObjectType{AttrTypes: filterTypes}, + "opentelemetry": basetypes.ObjectType{AttrTypes: openTelemetryTypes}, + "s3": basetypes.ObjectType{AttrTypes: s3Types}, } // Struct corresponding to filter @@ -147,7 +147,7 @@ type openTelemetry struct { // Types corresponding to opentelemetry var openTelemetryTypes = map[string]attr.Type{ - "basic_auth": basetypes.ObjectType{basicAuthTypes}, + "basic_auth": basetypes.ObjectType{AttrTypes: basicAuthTypes}, "bearer_token": basetypes.StringType{}, "uri": basetypes.StringType{}, } @@ -161,7 +161,7 @@ type s3 struct { // Types corresponding to s3 var s3Types = map[string]attr.Type{ - "access_key": basetypes.ObjectType{accessKeyTypes}, + "access_key": basetypes.ObjectType{AttrTypes: accessKeyTypes}, "bucket": basetypes.StringType{}, "endpoint": basetypes.StringType{}, } @@ -554,7 +554,7 @@ func (r *telemetryRouterDestinationResource) Create(ctx context.Context, req res tflog.Info(ctx, "TelemetryRouter destination created") } -func (r *telemetryRouterDestinationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *telemetryRouterDestinationResource) 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...) @@ -603,7 +603,7 @@ func (r *telemetryRouterDestinationResource) Read(ctx context.Context, req resou }) } -func (r *telemetryRouterDestinationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *telemetryRouterDestinationResource) 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...) diff --git a/stackit/internal/services/telemetryrouter/instance/resource.go b/stackit/internal/services/telemetryrouter/instance/resource.go index 175d3292b..b347b0513 100644 --- a/stackit/internal/services/telemetryrouter/instance/resource.go +++ b/stackit/internal/services/telemetryrouter/instance/resource.go @@ -302,7 +302,7 @@ func (r *telemetryRouterInstanceResource) Create(ctx context.Context, req resour tflog.Info(ctx, "TelemetryRouter instance created") } -func (r *telemetryRouterInstanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *telemetryRouterInstanceResource) 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...) @@ -348,7 +348,7 @@ func (r *telemetryRouterInstanceResource) Read(ctx context.Context, req resource }) } -func (r *telemetryRouterInstanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *telemetryRouterInstanceResource) 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...) From ef9fd7d60e70fc3bd3443ad35122df12a7e94f5e Mon Sep 17 00:00:00 2001 From: Oleksandr Zanichkovskyi Date: Tue, 5 May 2026 15:08:41 +0200 Subject: [PATCH 4/4] feat: added TelemetryRouter waiters --- go.mod | 2 +- go.sum | 4 ++-- .../telemetryrouter/accesstoken/resource.go | 23 +++++++++++++++++-- .../telemetryrouter/destination/resource.go | 23 +++++++++++++++++-- .../telemetryrouter/instance/resource.go | 23 +++++++++++++++++-- 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 939d7534c..634649ae3 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/sfs v0.7.0 github.com/stackitcloud/stackit-sdk-go/services/ske v1.12.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.9.0 - github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.1.0 + github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.2.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 0a55c07af..060f248b4 100644 --- a/go.sum +++ b/go.sum @@ -732,8 +732,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.12.0 h1:G6iUFDlrwCkCkwSV3 github.com/stackitcloud/stackit-sdk-go/services/ske v1.12.0/go.mod h1:cSRF2ARIB6dKmvZ12Z5h1usKQligeZJ1JOiJk6Ds3wE= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.9.0 h1:dv+8GYcI2G0BQvMN5Qb4kaCREaj9n2lRzj9sb29NEZg= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.9.0/go.mod h1:HTpVer71ZfpKozpzWVv7EbCPB5ko8vgw4VmFE38lB9I= -github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.1.0 h1:0WtujHIj8cPZhf03IuLhAzl/WOf8CQJWWyo0Qm/FNwI= -github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.1.0/go.mod h1:zdOIf7vz4Ay7DmxGO7UoUUd161WcozixrBemBq0aSyE= +github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.2.1 h1:Tc5gEuHRXbd+r6OlVh9LZFSDKXftD0ChF529FiGG7Wg= +github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.2.1/go.mod h1:WUmgKtwpe90Yq3YbgNxc2clTTULVxCu0ha6lMTjUnII= 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/services/telemetryrouter/accesstoken/resource.go b/stackit/internal/services/telemetryrouter/accesstoken/resource.go index 94410ca17..5f0f6ce43 100644 --- a/stackit/internal/services/telemetryrouter/accesstoken/resource.go +++ b/stackit/internal/services/telemetryrouter/accesstoken/resource.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -253,11 +254,17 @@ func (r *telemetryRouterAccessTokenResource) Create(ctx context.Context, req res ctx = core.LogResponse(ctx) - if createResp.Id == "" { + waitResp, err := wait.CreateAccessTokenWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, createResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter access token", fmt.Sprintf("Waiting for TelemetryRouter access token to become active: %v", err)) + return + } + + if waitResp.Id == "" { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter access token", "Got empty credential id") return } - accessTokenId := createResp.Id + accessTokenId := waitResp.Id ctx = tflog.SetField(ctx, "access_token_id", accessTokenId) err = mapCreateFields(ctx, createResp, &model) @@ -353,6 +360,12 @@ func (r *telemetryRouterAccessTokenResource) Update(ctx context.Context, req res return } + _, err = wait.UpdateAccessTokenWaitHandler(ctx, r.client.DefaultAPI, projectID, region, instanceID, accessTokenResponse.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter access token", fmt.Sprintf("Waiting for TelemetryRouter access token to become active: %v", err)) + return + } + err = mapUpdateFields(ctx, accessTokenResponse, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter access token", fmt.Sprintf("Processing response: %v", err)) @@ -397,6 +410,12 @@ func (r *telemetryRouterAccessTokenResource) Delete(ctx context.Context, req res ctx = core.LogResponse(ctx) + _, err = wait.DeleteAccessTokenWaitHandler(ctx, r.client.DefaultAPI, projectID, region, instanceID, accessTokenID).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryRouter access token", fmt.Sprintf("Waiting for TelemetryRouter access token to become deleted: %v", err)) + return + } + tflog.Info(ctx, "TelemetryRouter access token deleted") } diff --git a/stackit/internal/services/telemetryrouter/destination/resource.go b/stackit/internal/services/telemetryrouter/destination/resource.go index 19fca9b34..330075b05 100644 --- a/stackit/internal/services/telemetryrouter/destination/resource.go +++ b/stackit/internal/services/telemetryrouter/destination/resource.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -541,7 +542,13 @@ func (r *telemetryRouterDestinationResource) Create(ctx context.Context, req res return } - err = mapFields(ctx, createResp, &model) + waitResp, err := wait.CreateDestinationWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, createResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter destination", fmt.Sprintf("Waiting for TelemetryRouter destination to become active: %v", err)) + return + } + + err = mapFields(ctx, waitResp, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter destination", fmt.Sprintf("Processing response: %v", err)) return @@ -637,7 +644,13 @@ func (r *telemetryRouterDestinationResource) Update(ctx context.Context, req res ctx = core.LogResponse(ctx) - err = mapFields(ctx, updateResp, &model) + waitResp, err := wait.UpdateDestinationWaitHandler(ctx, r.client.DefaultAPI, projectID, region, instanceID, updateResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter destination", fmt.Sprintf("Waiting for TelemetryRouter destination to become active: %v", err)) + return + } + + err = mapFields(ctx, waitResp, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter destination", fmt.Sprintf("Processing response: %v", err)) return @@ -680,6 +693,12 @@ func (r *telemetryRouterDestinationResource) Delete(ctx context.Context, req res return } + _, err = wait.DeleteDestinationWaitHandler(ctx, r.client.DefaultAPI, projectID, region, instanceID, destinationID).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryRouter destination", fmt.Sprintf("Waiting for TelemetryRouter destination to become deleted: %v", err)) + return + } + tflog.Info(ctx, "TelemetryRouter destination deleted") } diff --git a/stackit/internal/services/telemetryrouter/instance/resource.go b/stackit/internal/services/telemetryrouter/instance/resource.go index b347b0513..2f039f5f3 100644 --- a/stackit/internal/services/telemetryrouter/instance/resource.go +++ b/stackit/internal/services/telemetryrouter/instance/resource.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" telemetryrouter "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi" + "github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter/v1betaapi/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" @@ -289,7 +290,13 @@ func (r *telemetryRouterInstanceResource) Create(ctx context.Context, req resour return } - err = mapFields(ctx, createResp, &model) + waitResp, err := wait.CreateTelemetryRouterWaitHandler(ctx, r.client.DefaultAPI, projectId, regionId, createResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter Instance", fmt.Sprintf("Waiting for TelemetryRouter Instance to become active: %v", err)) + return + } + + err = mapFields(ctx, waitResp, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating TelemetryRouter Instance", fmt.Sprintf("Processing response: %v", err)) return @@ -380,7 +387,13 @@ func (r *telemetryRouterInstanceResource) Update(ctx context.Context, req resour ctx = core.LogResponse(ctx) - err = mapFields(ctx, updateResp, &model) + waitResp, err := wait.UpdateTelemetryRouterWaitHandler(ctx, r.client.DefaultAPI, projectID, region, updateResp.Id).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter Instance", fmt.Sprintf("Waiting for TelemetryRouter Instance to become active: %v", err)) + return + } + + err = mapFields(ctx, waitResp, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating TelemetryRouter Instance", fmt.Sprintf("Processing response: %v", err)) return @@ -420,6 +433,12 @@ func (r *telemetryRouterInstanceResource) Delete(ctx context.Context, req resour return } + _, err = wait.DeleteTelemetryRouterWaitHandler(ctx, r.client.DefaultAPI, projectID, region, instanceID).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting TelemetryRouter Instance", fmt.Sprintf("Waiting for TelemetryRouter Instance to become deleted: %v", err)) + return + } + tflog.Info(ctx, "TelemetryRouter Instance deleted") }