From 3d9afb3058305728b9452f026f2bb48a88633ab0 Mon Sep 17 00:00:00 2001 From: GokceGK Date: Wed, 28 Jan 2026 18:31:35 +0100 Subject: [PATCH 1/3] onboard logs access token resource --- docs/data-sources/logs_access_token.md | 51 ++ docs/resources/logs_access_token.md | 72 +++ .../stackit_logs_access_token/data-source.tf | 6 + .../stackit_logs_access_token/resource.tf | 27 + go.mod | 2 +- go.sum | 2 + .../services/logs/accesstoken/datasource.go | 183 ++++++ .../services/logs/accesstoken/resource.go | 533 ++++++++++++++++++ .../logs/accesstoken/resource_test.go | 218 +++++++ .../internal/services/logs/logs_acc_test.go | 339 ++++++++++- .../logs/testdata/access-token-max.tf | 31 + .../logs/testdata/access-token-min.tf | 23 + stackit/provider.go | 3 + 13 files changed, 1483 insertions(+), 7 deletions(-) create mode 100644 docs/data-sources/logs_access_token.md create mode 100644 docs/resources/logs_access_token.md create mode 100644 examples/data-sources/stackit_logs_access_token/data-source.tf create mode 100644 examples/resources/stackit_logs_access_token/resource.tf create mode 100644 stackit/internal/services/logs/accesstoken/datasource.go create mode 100644 stackit/internal/services/logs/accesstoken/resource.go create mode 100644 stackit/internal/services/logs/accesstoken/resource_test.go create mode 100644 stackit/internal/services/logs/testdata/access-token-max.tf create mode 100644 stackit/internal/services/logs/testdata/access-token-min.tf diff --git a/docs/data-sources/logs_access_token.md b/docs/data-sources/logs_access_token.md new file mode 100644 index 000000000..4b765d56f --- /dev/null +++ b/docs/data-sources/logs_access_token.md @@ -0,0 +1,51 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_logs_access_token Data Source - stackit" +subcategory: "" +description: |- + Logs access token data source schema. + ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. +--- + +# stackit_logs_access_token (Data Source) + +Logs access token data source schema. + +~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + +## Example Usage + +```terraform +data "stackit_logs_access_token" "accessToken" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + access_token_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + + +## Schema + +### Required + +- `access_token_id` (String) The access token ID +- `instance_id` (String) The Logs instance ID associated with the access token +- `project_id` (String) STACKIT project ID associated with the Logs access token + +### Optional + +- `lifetime` (Number) A lifetime period for an access token in days. If unset the token will not expire. +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `access_token` (String, Sensitive) The generated access token +- `creator` (String) The user who created the access token +- `description` (String) The description of the access token +- `display_name` (String) The displayed name of the access token +- `expires` (Boolean) Indicates if the access token can expire +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`access_token_id`". +- `permissions` (List of String) The access permissions granted to the access token. Possible values: `read`, `write`. +- `status` (String) The status of the access token, possible values: Possible values are: `active`, `expired`. +- `valid_until` (String) The date and time until an access token is valid to (inclusively) diff --git a/docs/resources/logs_access_token.md b/docs/resources/logs_access_token.md new file mode 100644 index 000000000..96ed23e5a --- /dev/null +++ b/docs/resources/logs_access_token.md @@ -0,0 +1,72 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_logs_access_token Resource - stackit" +subcategory: "" +description: |- + Logs access token resource schema. + ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. +--- + +# stackit_logs_access_token (Resource) + +Logs access token resource schema. + +~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + +## Example Usage + +```terraform +resource "stackit_logs_access_token" "accessToken" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "logs-access-token-example" + permissions = [ + "read" + ] +} + +resource "stackit_logs_access_token" "accessToken2" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "logs-access-token-example" + lifetime = 30 + permissions = [ + "write" + ] + description = "Example description" +} + +# Only use the import statement, if you want to import an existing logs instance +import { + to = stackit_logs_access_token.import-example + id = "${var.project_id},${var.region},${var.logs_instance_id},${var.logs_access_token_id}" +} +``` + + +## Schema + +### Required + +- `display_name` (String) The displayed name of the access token +- `instance_id` (String) The Logs instance ID associated with the access token +- `permissions` (List of String) The access permissions granted to the access token. Possible values: `read`, `write`. +- `project_id` (String) STACKIT project ID associated with the Logs access token + +### Optional + +- `description` (String) The description of the access token +- `lifetime` (Number) A lifetime period for an access token in days. If unset the token will not expire. +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `access_token` (String, Sensitive) The generated access token +- `access_token_id` (String) The access token ID +- `creator` (String) The user who created the access token +- `expires` (Boolean) Indicates if the access token can expire +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`,`instance_id`,`access_token_id`". +- `status` (String) The status of the access token, possible values: Possible values are: `active`, `expired`. +- `valid_until` (String) The date and time until an access token is valid to (inclusively) diff --git a/examples/data-sources/stackit_logs_access_token/data-source.tf b/examples/data-sources/stackit_logs_access_token/data-source.tf new file mode 100644 index 000000000..22dfc7c30 --- /dev/null +++ b/examples/data-sources/stackit_logs_access_token/data-source.tf @@ -0,0 +1,6 @@ +data "stackit_logs_access_token" "accessToken" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + access_token_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} \ No newline at end of file diff --git a/examples/resources/stackit_logs_access_token/resource.tf b/examples/resources/stackit_logs_access_token/resource.tf new file mode 100644 index 000000000..828aa7985 --- /dev/null +++ b/examples/resources/stackit_logs_access_token/resource.tf @@ -0,0 +1,27 @@ +resource "stackit_logs_access_token" "accessToken" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "logs-access-token-example" + permissions = [ + "read" + ] +} + +resource "stackit_logs_access_token" "accessToken2" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region = "eu01" + display_name = "logs-access-token-example" + lifetime = 30 + permissions = [ + "write" + ] + description = "Example description" +} + +# Only use the import statement, if you want to import an existing logs instance +import { + to = stackit_logs_access_token.import-example + id = "${var.project_id},${var.region},${var.logs_instance_id},${var.logs_access_token_id}" +} diff --git a/go.mod b/go.mod index b8d3fbe65..ec8f7ba64 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/kms v1.2.0 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.7.1 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.3 - github.com/stackitcloud/stackit-sdk-go/services/logs v0.4.0 + github.com/stackitcloud/stackit-sdk-go/services/logs v0.5.0 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.3 github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.6.2 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.5 diff --git a/go.sum b/go.sum index b979df1fc..c78d074a6 100644 --- a/go.sum +++ b/go.sum @@ -171,6 +171,8 @@ github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.3 h1:fUQLWs2WsXFh+Ft github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.3/go.mod h1:305j9bvzJ+3c4csOw4SUfLSSxRbkpL0osbvqMI89FeM= github.com/stackitcloud/stackit-sdk-go/services/logs v0.4.0 h1:EOUVSKvu/m5N+psxeB69IIpANev/jw6HIw2yfh/HO7w= github.com/stackitcloud/stackit-sdk-go/services/logs v0.4.0/go.mod h1:m4IjH1/RtJOF072kjAB0E/ejoIc++myrKmIahphfO6Q= +github.com/stackitcloud/stackit-sdk-go/services/logs v0.5.0 h1:Tg7sKlkJUOcOTsO+QtAfk8cazBlOrVdcQr0NP4QIrRY= +github.com/stackitcloud/stackit-sdk-go/services/logs v0.5.0/go.mod h1:1l/ViyPvQ7xBnKp/ca3c5um8l5hZCRc7E2E9UNVrMSg= github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.3 h1:Y5Ct3Zi5UcIOwjKMWpKl0nrqiq7psTf4NJv0IKgwTkc= github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.3/go.mod h1:TMl5WcpjzUiAlLWaxMKbu9ysDzFziSPgg4xLxj9jjfY= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.6.2 h1:RKRKwSpU8spBERYNlUn9BcTL3dbTLeJM1xL2H7NexnI= diff --git a/stackit/internal/services/logs/accesstoken/datasource.go b/stackit/internal/services/logs/accesstoken/datasource.go new file mode 100644 index 000000000..ca32c7d91 --- /dev/null +++ b/stackit/internal/services/logs/accesstoken/datasource.go @@ -0,0 +1,183 @@ +package accesstoken + +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" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "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/features" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logs/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &logsAccessTokenDataSource{} +) + +func NewLogsAccessTokenDataSource() datasource.DataSource { + return &logsAccessTokenDataSource{} +} + +type logsAccessTokenDataSource struct { + client *logs.APIClient + providerData core.ProviderData +} + +func (d *logsAccessTokenDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_logs_access_token" +} + +func (d *logsAccessTokenDataSource) 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, "Logs client configured") +} + +func (d *logsAccessTokenDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: features.AddBetaDescription("Logs access token data source schema.", core.Datasource), + Description: fmt.Sprintf("Logs 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(), + }, + }, + "access_token": schema.StringAttribute{ + Description: schemaDescriptions["access_token"], + Computed: true, + Sensitive: true, + }, + "creator": schema.StringAttribute{ + Description: schemaDescriptions["creator"], + 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)}, + }, + "expires": schema.BoolAttribute{ + Description: schemaDescriptions["expires"], + Computed: true, + }, + "valid_until": schema.StringAttribute{ + Description: schemaDescriptions["valid_until"], + Computed: true, + }, + "permissions": schema.ListAttribute{ + Description: schemaDescriptions["permissions"], + ElementType: types.StringType, + Computed: true, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + "lifetime": schema.Int64Attribute{ + Description: schemaDescriptions["lifetime"], + Optional: true, + }, + }, + } +} + +func (d *logsAccessTokenDataSource) 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() + 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.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 Logs access token", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, accessTokenResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Logs 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, "Logs access token read", map[string]interface{}{ + "access_token_id": accessTokenID, + }) +} diff --git a/stackit/internal/services/logs/accesstoken/resource.go b/stackit/internal/services/logs/accesstoken/resource.go new file mode 100644 index 000000000..f1fba22cc --- /dev/null +++ b/stackit/internal/services/logs/accesstoken/resource.go @@ -0,0 +1,533 @@ +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/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "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" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/logs" + "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/features" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logs/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &logsAccessTokenResource{} + _ resource.ResourceWithConfigure = &logsAccessTokenResource{} + _ resource.ResourceWithImportState = &logsAccessTokenResource{} + _ resource.ResourceWithModifyPlan = &logsAccessTokenResource{} +) + +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 Logs 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 Logs access token", + "access_token": "The generated access token", + "creator": "The user who created the access token", + "description": "The description of the access token", + "display_name": "The displayed name of the access token", + "expires": "Indicates if the access token can expire", + "valid_until": "The date and time until an access token is valid to (inclusively)", + "lifetime": "A lifetime period for an access token in days. If unset the token will not expire.", + "permissions": "The access permissions granted to the access token. Possible values: `read`, `write`.", + "status": fmt.Sprintf( + "The status of the access token, possible values: %s", + tfutils.FormatPossibleValues(sdkUtils.EnumSliceToStringSlice(logs.AllowedAccessTokenStatusEnumValues)...), + ), +} + +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"` + AccessToken types.String `tfsdk:"access_token"` + Creator types.String `tfsdk:"creator"` + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + Expires types.Bool `tfsdk:"expires"` + ValidUntil types.String `tfsdk:"valid_until"` + Lifetime types.Int64 `tfsdk:"lifetime"` + Permissions types.List `tfsdk:"permissions"` + Status types.String `tfsdk:"status"` +} + +type logsAccessTokenResource struct { + client *logs.APIClient + providerData core.ProviderData +} + +func NewLogsAccessTokenResource() resource.Resource { + return &logsAccessTokenResource{} +} + +func (r *logsAccessTokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + features.CheckBetaResourcesEnabled(ctx, &providerData, &resp.Diagnostics, "stackit_logs_access_token", "resource") + if resp.Diagnostics.HasError() { + return + } + + apiClient := utils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + r.providerData = providerData + tflog.Info(ctx, "Logs client configured") +} + +func (r *logsAccessTokenResource) 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 *logsAccessTokenResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_logs_access_token" +} + +func (r *logsAccessTokenResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: features.AddBetaDescription("Logs access token resource schema.", core.Resource), + Description: fmt.Sprintf("Logs 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(), + }, + }, + "access_token": schema.StringAttribute{ + Description: schemaDescriptions["access_token"], + Computed: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "creator": schema.StringAttribute{ + Description: schemaDescriptions["creator"], + Computed: true, + }, + "description": schema.StringAttribute{ + Description: schemaDescriptions["description"], + Optional: true, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Required: true, + }, + "expires": schema.BoolAttribute{ + Description: schemaDescriptions["expires"], + Computed: true, + }, + "valid_until": schema.StringAttribute{ + Description: schemaDescriptions["valid_until"], + Computed: true, + }, + "lifetime": schema.Int64Attribute{ + Description: schemaDescriptions["lifetime"], + Optional: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "permissions": schema.ListAttribute{ + Description: schemaDescriptions["permissions"], + ElementType: types.StringType, + Required: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + }, + "status": schema.StringAttribute{ + Description: schemaDescriptions["status"], + Computed: true, + }, + }, + } +} + +func (r *logsAccessTokenResource) 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 Logs access token", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + regionId := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", regionId) + createResp, err := r.client.CreateAccessToken(ctx, projectId, regionId, instanceId).CreateAccessTokenPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Logs access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + if createResp.Id == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Logs access token", "Got empty credential id") + return + } + accessTokenId := *createResp.Id + ctx = tflog.SetField(ctx, "access_token_id", accessTokenId) + + err = mapFields(ctx, createResp, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating Logs 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, "Logs instance created") +} + +func (r *logsAccessTokenResource) 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.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 Logs access token", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, accessTokenResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading Logs 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, "Logs access token read", map[string]interface{}{ + "access_token_id": accessTokenID, + }) +} + +func (r *logsAccessTokenResource) 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 Logs access token", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + err = r.client.UpdateAccessToken(ctx, projectID, region, instanceID, accessTokenID).UpdateAccessTokenPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Logs access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + accessTokenResponse, err := r.client.GetAccessToken(ctx, projectID, region, instanceID, accessTokenID).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Logs access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(ctx, accessTokenResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating Logs 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, "Logs access token updated", map[string]interface{}{ + "access_token_id": accessTokenID, + }) +} + +func (r *logsAccessTokenResource) 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.DeleteAccessToken(ctx, projectID, region, instanceID, accessTokenID).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting Logs access token", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + tflog.Info(ctx, "Logs access token deleted") +} + +func (r *logsAccessTokenResource) 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 Logs 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, + "Logs 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, "Logs access token state imported") +} + +func toCreatePayload(ctx context.Context, diagnostics diag.Diagnostics, model *Model) (*logs.CreateAccessTokenPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + payload := &logs.CreateAccessTokenPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + Lifetime: conversion.Int64ValueToPointer(model.Lifetime), + } + + if !(model.Permissions.IsNull() || model.Permissions.IsUnknown()) { + var permissions []string + permissionDiags := model.Permissions.ElementsAs(ctx, &permissions, false) + diagnostics.Append(permissionDiags...) + if !permissionDiags.HasError() { + payload.Permissions = &permissions + } + } + + return payload, nil +} + +func mapFields(ctx context.Context, accessToken *logs.AccessToken, 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 != nil { + 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.Creator = types.StringPointerValue(accessToken.Creator) + model.Description = types.StringPointerValue(accessToken.Description) + model.DisplayName = types.StringPointerValue(accessToken.DisplayName) + model.Expires = types.BoolPointerValue(accessToken.Expires) + model.Status = types.StringValue(string(*accessToken.Status)) + + model.ValidUntil = types.StringNull() + if accessToken.ValidUntil != nil { + model.ValidUntil = types.StringValue(accessToken.ValidUntil.Format(time.RFC3339)) + } + + if accessToken.AccessToken != nil { + model.AccessToken = types.StringValue(*accessToken.AccessToken) + } + + permissionList := types.ListNull(types.StringType) + var diags diag.Diagnostics + if accessToken.Permissions != nil && len(*accessToken.Permissions) > 0 { + permissionList, diags = types.ListValueFrom(ctx, types.StringType, accessToken.Permissions) + if diags.HasError() { + return fmt.Errorf("mapping permissions: %w", core.DiagsToError(diags)) + } + } + model.Permissions = permissionList + + return nil +} + +func toUpdatePayload(model *Model) (*logs.UpdateAccessTokenPayload, error) { + if model == nil { + return nil, fmt.Errorf("missing model") + } + + payload := &logs.UpdateAccessTokenPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + } + + return payload, nil +} diff --git a/stackit/internal/services/logs/accesstoken/resource_test.go b/stackit/internal/services/logs/accesstoken/resource_test.go new file mode 100644 index 000000000..213c44cc1 --- /dev/null +++ b/stackit/internal/services/logs/accesstoken/resource_test.go @@ -0,0 +1,218 @@ +package accesstoken + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/logs" +) + +var testTime = time.Now() + +func fixtureAccessToken(mods ...func(accessToken *logs.AccessToken)) *logs.AccessToken { + accessToken := &logs.AccessToken{ + Id: utils.Ptr("atid"), + Status: utils.Ptr(logs.ACCESSTOKENSTATUS_ACTIVE), + } + 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"), + AccessToken: types.String{}, + Creator: types.String{}, + Description: types.String{}, + DisplayName: types.String{}, + Expires: types.Bool{}, + ValidUntil: types.String{}, + Lifetime: types.Int64{}, + Permissions: types.ListNull(types.StringType), + Status: types.StringValue(string(logs.ACCESSTOKENSTATUS_ACTIVE)), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func TestMapFields(t *testing.T) { + tests := []struct { + description string + input *logs.AccessToken + expected *Model + wantErr bool + }{ + { + description: "min values", + input: fixtureAccessToken(), + expected: fixtureModel(), + }, + { + description: "max values", + input: fixtureAccessToken(func(accessToken *logs.AccessToken) { + accessToken.Permissions = &[]string{"write"} + accessToken.AccessToken = utils.Ptr("") + accessToken.Description = utils.Ptr("description") + accessToken.DisplayName = utils.Ptr("display-name") + accessToken.Creator = utils.Ptr("testUser") + accessToken.Expires = utils.Ptr(false) + accessToken.ValidUntil = utils.Ptr(testTime) + }), + expected: fixtureModel(func(model *Model) { + model.Permissions = types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("write"), + }) + model.AccessToken = types.StringValue("") + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.Creator = types.StringValue("testUser") + model.Expires = types.BoolValue(false) + model.ValidUntil = types.StringValue(testTime.Format(time.RFC3339)) + }), + }, + { + description: "nil input", + wantErr: true, + expected: fixtureModel(), + }, + { + description: "nil access token id", + input: &logs.AccessToken{}, + 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 := 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 *logs.CreateAccessTokenPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &logs.CreateAccessTokenPayload{}, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Permissions = types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("read"), + types.StringValue("write"), + }) + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + model.Lifetime = types.Int64Value(7) + }), + expected: &logs.CreateAccessTokenPayload{ + Permissions: &[]string{"read", "write"}, + Description: utils.Ptr("description"), + DisplayName: utils.Ptr("display-name"), + Lifetime: utils.Ptr(int64(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) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} + +func TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + model *Model + expected *logs.UpdateAccessTokenPayload + wantErrMessage string + }{ + { + description: "min values", + model: fixtureModel(), + expected: &logs.UpdateAccessTokenPayload{}, + }, + { + description: "max values", + model: fixtureModel(func(model *Model) { + model.Description = types.StringValue("description") + model.DisplayName = types.StringValue("display-name") + }), + expected: &logs.UpdateAccessTokenPayload{ + Description: utils.Ptr("description"), + DisplayName: 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) + if diff != "" { + t.Fatalf("Payload does not match: %s", diff) + } + }) + } +} diff --git a/stackit/internal/services/logs/logs_acc_test.go b/stackit/internal/services/logs/logs_acc_test.go index 4ddf44f91..88cbab67e 100644 --- a/stackit/internal/services/logs/logs_acc_test.go +++ b/stackit/internal/services/logs/logs_acc_test.go @@ -3,26 +3,38 @@ package logs_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" coreConfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/services/logs" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) -//go:embed testdata/resource-min.tf -var resourceMin string +var ( + //go:embed testdata/resource-min.tf + resourceMin string -//go:embed testdata/resource-max.tf -var resourceMax string + //go:embed testdata/resource-max.tf + resourceMax string + + //go:embed testdata/access-token-min.tf + accessTokenMinConfig string + + //go:embed testdata/access-token-max.tf + accessTokenMaxConfig string +) var testConfigVarsMin = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), @@ -58,10 +70,44 @@ func testConfigVarsMaxUpdated() config.Variables { 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"), + "retention_days": config.IntegerVariable(7), + "permissions": config.StringVariable("read"), +} + +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"), + "retention_days": config.IntegerVariable(7), + "acl": config.StringVariable("192.168.0.1/24"), + "permissions": config.StringVariable("write"), + "description": config.StringVariable("Terraform Acceptance Test Logs Access Token"), + "lifetime": config.IntegerVariable(7), +} + +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 +} + func TestAccLogsInstanceMin(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckLogsInstanceDestroy, + CheckDestroy: testAccCheckDestroy, Steps: []resource.TestStep{ // Create { @@ -191,7 +237,7 @@ data "stackit_logs_instance" "logs" { func TestAccLogsInstanceMax(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckLogsInstanceDestroy, + CheckDestroy: testAccCheckDestroy, Steps: []resource.TestStep{ // Create { @@ -330,6 +376,247 @@ data "stackit_logs_instance" "logs" { }) } +func TestAccLogsAccessTokenMin(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigAccessTokenVarsMin, + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "retention_days", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["retention_days"])), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "instance_id"), + + // Access token data + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttrPair( + "stackit_logs_access_token.accessToken", "instance_id", + "stackit_logs_instance.logs", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["permissions"])), + ), + }, + // Datasource + { + ConfigVariables: testConfigVarsMin, + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig + ` + data "stackit_logs_access_token" "accessToken" { + project_id = stackit_logs_access_token.accessToken.project_id + region = stackit_logs_access_token.accessToken.region + instance_id = stackit_logs_access_token.accessToken.instance_id + access_token_id = stackit_logs_access_token.accessToken.access_token_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttrPair( + "stackit_logs_access_token.accessToken", "instance_id", + "data.stackit_logs_access_token.accessToken", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["permissions"])), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "creator"), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "expires"), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "status"), + ), + }, + // Import + { + ConfigVariables: testConfigAccessTokenVarsMin, + ResourceName: "stackit_logs_access_token.accessToken", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_logs_access_token.accessToken"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_logs_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, + }, + // Update + { + ConfigVariables: testConfigAccessTokenVarsMinUpdated(), + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_logs_access_token.accessToken", "instance_id", + "stackit_logs_instance.logs", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "retention_days", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["retention_days"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["permissions"])), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "creator"), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "expires"), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "status"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func TestAccLogsAccessTokenMax(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Create + { + ConfigVariables: testConfigAccessTokenVarsMax, + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMaxConfig, + Check: resource.ComposeTestCheckFunc( + // Instance data + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["project_id"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["region"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["display_name"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "retention_days", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["retention_days"])), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "instance_id"), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "acl.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["acl"])), + resource.TestCheckResourceAttr("stackit_logs_instance.logs", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["description"])), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "created"), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "datasource_url"), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "ingest_otlp_url"), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "ingest_url"), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "query_range_url"), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "query_url"), + resource.TestCheckResourceAttrSet("stackit_logs_instance.logs", "status"), + + // Access token data + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["project_id"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["region"])), + resource.TestCheckResourceAttrPair( + "stackit_logs_access_token.accessToken", "instance_id", + "stackit_logs_instance.logs", "instance_id", + ), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["display_name"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["permissions"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["description"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "lifetime", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["lifetime"])), + ), + }, + // Datasource + { + ConfigVariables: testConfigVarsMin, + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig + ` + data "stackit_logs_access_token" "accessToken" { + project_id = stackit_logs_access_token.accessToken.project_id + region = stackit_logs_access_token.accessToken.region + instance_id = stackit_logs_access_token.accessToken.instance_id + access_token_id = stackit_logs_access_token.accessToken.access_token_id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttrPair( + "stackit_logs_access_token.accessToken", "instance_id", + "data.stackit_logs_access_token.accessToken", "instance_id", + ), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["permissions"])), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "creator"), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "expires"), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "status"), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "valid_until"), + resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "description"), + ), + }, + // Import + { + ConfigVariables: testConfigAccessTokenVarsMin, + ResourceName: "stackit_logs_access_token.accessToken", + ImportStateIdFunc: func(state *terraform.State) (string, error) { + rs, ok := state.RootModule().Resources["stackit_logs_access_token.accessToken"] + if !ok { + return "", fmt.Errorf("not found: %s", "stackit_logs_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, + }, + // Update + { + ConfigVariables: testConfigAccessTokenVarsMaxUpdated(), + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["project_id"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["region"])), + resource.TestCheckResourceAttrPair( + "stackit_logs_access_token.accessToken", "instance_id", + "stackit_logs_instance.logs", "instance_id", + ), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["display_name"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["description"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "retention_days", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["retention_days"])), + resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["permissions"])), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "access_token_id"), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "creator"), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "expires"), + resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "status"), + ), + }, + // Deletion handled by framework + }, + }) +} + +func testAccCheckDestroy(s *terraform.State) error { + checkFunctions := []func(s *terraform.State) error{ + testAccCheckLogsInstanceDestroy, + testAccCheckLogsAccessTokenDestroy, + } + + var errs []error + + wg := sync.WaitGroup{} + wg.Add(len(checkFunctions)) + + for _, f := range checkFunctions { + go func() { + err := f(s) + if err != nil { + errs = append(errs, err) + } + wg.Done() + }() + } + wg.Wait() + return errors.Join(errs...) +} + func testAccCheckLogsInstanceDestroy(s *terraform.State) error { ctx := context.Background() var client *logs.APIClient @@ -369,3 +656,43 @@ func testAccCheckLogsInstanceDestroy(s *terraform.State) error { } return nil } + +func testAccCheckLogsAccessTokenDestroy(s *terraform.State) error { + ctx := context.Background() + var client *logs.APIClient + var err error + if testutil.LogsCustomEndpoint == "" { + client, err = logs.NewAPIClient() + } else { + client, err = logs.NewAPIClient( + coreConfig.WithEndpoint(testutil.LogsCustomEndpoint), + ) + } + 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_logs_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.DeleteAccessTokenExecute(ctx, testutil.ProjectId, region, instanceId, accessTokenId) + 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...) +} diff --git a/stackit/internal/services/logs/testdata/access-token-max.tf b/stackit/internal/services/logs/testdata/access-token-max.tf new file mode 100644 index 000000000..348f68044 --- /dev/null +++ b/stackit/internal/services/logs/testdata/access-token-max.tf @@ -0,0 +1,31 @@ +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "retention_days" {} +variable "acl" {} +variable "description" {} +variable "permissions" {} +variable "lifetime" {} + +resource "stackit_logs_instance" "logs" { + project_id = var.project_id + region = var.region + display_name = var.display_name + retention_days = var.retention_days + acl = [ + var.acl + ] + description = var.description +} + +resource "stackit_logs_access_token" "accessToken" { + project_id = var.project_id + instance_id = stackit_logs_instance.logs.instance_id + region = var.region + display_name = var.display_name + permissions = [ + var.permissions + ] + description = var.description + lifetime = var.lifetime +} \ No newline at end of file diff --git a/stackit/internal/services/logs/testdata/access-token-min.tf b/stackit/internal/services/logs/testdata/access-token-min.tf new file mode 100644 index 000000000..f2a50f213 --- /dev/null +++ b/stackit/internal/services/logs/testdata/access-token-min.tf @@ -0,0 +1,23 @@ +variable "project_id" {} +variable "region" {} +variable "display_name" {} +variable "retention_days" {} +variable "permissions" {} + +resource "stackit_logs_instance" "logs" { + project_id = var.project_id + region = var.region + display_name = var.display_name + retention_days = var.retention_days +} + +resource "stackit_logs_access_token" "accessToken" { + project_id = var.project_id + instance_id = stackit_logs_instance.logs.instance_id + region = var.region + display_name = var.display_name + permissions = [ + var.permissions + ] +} + diff --git a/stackit/provider.go b/stackit/provider.go index e62247ecf..b196a79ce 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -63,6 +63,7 @@ import ( loadBalancerObservabilityCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/observability-credential" logMeCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/credential" logMeInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logme/instance" + logsAccessToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logs/accesstoken" logsInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/logs/instance" mariaDBCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/credential" mariaDBInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mariadb/instance" @@ -558,6 +559,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource logMeInstance.NewInstanceDataSource, logMeCredential.NewCredentialDataSource, logsInstance.NewLogsInstanceDataSource, + logsAccessToken.NewLogsAccessTokenDataSource, logAlertGroup.NewLogAlertGroupDataSource, machineType.NewMachineTypeDataSource, mariaDBInstance.NewInstanceDataSource, @@ -640,6 +642,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { logMeCredential.NewCredentialResource, logAlertGroup.NewLogAlertGroupResource, logsInstance.NewLogsInstanceResource, + logsAccessToken.NewLogsAccessTokenResource, mariaDBInstance.NewInstanceResource, mariaDBCredential.NewCredentialResource, modelServingToken.NewTokenResource, From d7ab784e5a630fc661e7d69bdac44fcc8f7fd4ef Mon Sep 17 00:00:00 2001 From: GokceGK Date: Wed, 28 Jan 2026 19:17:15 +0100 Subject: [PATCH 2/3] fixes in acceptance test --- .../internal/services/logs/logs_acc_test.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/stackit/internal/services/logs/logs_acc_test.go b/stackit/internal/services/logs/logs_acc_test.go index 88cbab67e..c168c540e 100644 --- a/stackit/internal/services/logs/logs_acc_test.go +++ b/stackit/internal/services/logs/logs_acc_test.go @@ -464,7 +464,6 @@ func TestAccLogsAccessTokenMin(t *testing.T) { "stackit_logs_instance.logs", "instance_id", ), resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["display_name"])), - resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "retention_days", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["retention_days"])), resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMinUpdated()["permissions"])), resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "access_token_id"), resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "creator"), @@ -519,8 +518,8 @@ func TestAccLogsAccessTokenMax(t *testing.T) { }, // Datasource { - ConfigVariables: testConfigVarsMin, - Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig + ` + ConfigVariables: testConfigAccessTokenVarsMax, + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMaxConfig + ` data "stackit_logs_access_token" "accessToken" { project_id = stackit_logs_access_token.accessToken.project_id region = stackit_logs_access_token.accessToken.region @@ -529,15 +528,15 @@ func TestAccLogsAccessTokenMax(t *testing.T) { } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["project_id"])), - resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["region"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["project_id"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["region"])), resource.TestCheckResourceAttrPair( "stackit_logs_access_token.accessToken", "instance_id", "data.stackit_logs_access_token.accessToken", "instance_id", ), resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "access_token_id"), - resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["display_name"])), - resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMin["permissions"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["display_name"])), + resource.TestCheckResourceAttr("data.stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMax["permissions"])), resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "creator"), resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "expires"), resource.TestCheckResourceAttrSet("data.stackit_logs_access_token.accessToken", "status"), @@ -547,7 +546,7 @@ func TestAccLogsAccessTokenMax(t *testing.T) { }, // Import { - ConfigVariables: testConfigAccessTokenVarsMin, + ConfigVariables: testConfigAccessTokenVarsMax, ResourceName: "stackit_logs_access_token.accessToken", ImportStateIdFunc: func(state *terraform.State) (string, error) { rs, ok := state.RootModule().Resources["stackit_logs_access_token.accessToken"] @@ -570,7 +569,7 @@ func TestAccLogsAccessTokenMax(t *testing.T) { // Update { ConfigVariables: testConfigAccessTokenVarsMaxUpdated(), - Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig, + Config: testutil.LogsProviderConfig() + "\n" + accessTokenMaxConfig, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "project_id", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["project_id"])), resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "region", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["region"])), @@ -580,7 +579,6 @@ func TestAccLogsAccessTokenMax(t *testing.T) { ), resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "display_name", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["display_name"])), resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "description", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["description"])), - resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "retention_days", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["retention_days"])), resource.TestCheckResourceAttr("stackit_logs_access_token.accessToken", "permissions.0", testutil.ConvertConfigVariable(testConfigAccessTokenVarsMaxUpdated()["permissions"])), resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "access_token_id"), resource.TestCheckResourceAttrSet("stackit_logs_access_token.accessToken", "creator"), From 16d90a893460b3715f443092b8804152f302a5af Mon Sep 17 00:00:00 2001 From: GokceGK Date: Wed, 28 Jan 2026 19:23:04 +0100 Subject: [PATCH 3/3] use correct config variable in test --- stackit/internal/services/logs/logs_acc_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackit/internal/services/logs/logs_acc_test.go b/stackit/internal/services/logs/logs_acc_test.go index c168c540e..a4a09310d 100644 --- a/stackit/internal/services/logs/logs_acc_test.go +++ b/stackit/internal/services/logs/logs_acc_test.go @@ -406,7 +406,7 @@ func TestAccLogsAccessTokenMin(t *testing.T) { }, // Datasource { - ConfigVariables: testConfigVarsMin, + ConfigVariables: testConfigAccessTokenVarsMin, Config: testutil.LogsProviderConfig() + "\n" + accessTokenMinConfig + ` data "stackit_logs_access_token" "accessToken" { project_id = stackit_logs_access_token.accessToken.project_id