From 0056a6e9342f4e6df2adc6a3d5e350e396f75f04 Mon Sep 17 00:00:00 2001 From: Alexander Dahmen Date: Wed, 25 Mar 2026 08:25:23 +0100 Subject: [PATCH] feat(objectstorage): Onboard compliance lock STACKITTPR-502 Signed-off-by: Alexander Dahmen --- .../objectstorage_compliance_lock.md | 35 +++ .../objectstorage_compliance_lock.md | 35 +++ .../data-source.tf | 3 + .../resource.tf | 3 + go.mod | 2 +- go.sum | 4 +- .../compliance-lock/datasource.go | 144 +++++++++ .../objectstorage/compliance-lock/resource.go | 290 ++++++++++++++++++ .../compliance-lock/resource_test.go | 72 +++++ .../objectstorage/objectstorage_acc_test.go | 11 + .../objectstorage/testfiles/resource-min.tf | 4 + stackit/provider.go | 3 + 12 files changed, 603 insertions(+), 3 deletions(-) create mode 100644 docs/data-sources/objectstorage_compliance_lock.md create mode 100644 docs/resources/objectstorage_compliance_lock.md create mode 100644 examples/data-sources/stackit_objectstorage_compliance_lock/data-source.tf create mode 100644 examples/resources/stackit_objectstorage_compliance_lock/resource.tf create mode 100644 stackit/internal/services/objectstorage/compliance-lock/datasource.go create mode 100644 stackit/internal/services/objectstorage/compliance-lock/resource.go create mode 100644 stackit/internal/services/objectstorage/compliance-lock/resource_test.go diff --git a/docs/data-sources/objectstorage_compliance_lock.md b/docs/data-sources/objectstorage_compliance_lock.md new file mode 100644 index 000000000..ff06c77f6 --- /dev/null +++ b/docs/data-sources/objectstorage_compliance_lock.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_objectstorage_compliance_lock Data Source - stackit" +subcategory: "" +description: |- + ObjectStorage compliance lock resource schema. Must have a region specified in the provider configuration. +--- + +# stackit_objectstorage_compliance_lock (Data Source) + +ObjectStorage compliance lock resource schema. Must have a `region` specified in the provider configuration. + +## Example Usage + +```terraform +data "stackit_objectstorage_compliance_lock" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + + +## Schema + +### Required + +- `project_id` (String) STACKIT Project ID to which the compliance lock is associated. + +### Optional + +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`". +- `max_retention_days` (Number) Maximum retention period in days. diff --git a/docs/resources/objectstorage_compliance_lock.md b/docs/resources/objectstorage_compliance_lock.md new file mode 100644 index 000000000..5fd869de4 --- /dev/null +++ b/docs/resources/objectstorage_compliance_lock.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_objectstorage_compliance_lock Resource - stackit" +subcategory: "" +description: |- + ObjectStorage compliance lock resource schema. Must have a region specified in the provider configuration. Always use only one compliance lock per project. +--- + +# stackit_objectstorage_compliance_lock (Resource) + +ObjectStorage compliance lock resource schema. Must have a `region` specified in the provider configuration. Always use only one compliance lock per project. + +## Example Usage + +```terraform +resource "stackit_objectstorage_compliance_lock" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + + +## Schema + +### Required + +- `project_id` (String) STACKIT Project ID to which the compliance lock is associated. + +### Optional + +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`region`". +- `max_retention_days` (Number) Maximum retention period in days. diff --git a/examples/data-sources/stackit_objectstorage_compliance_lock/data-source.tf b/examples/data-sources/stackit_objectstorage_compliance_lock/data-source.tf new file mode 100644 index 000000000..38c7b3cf1 --- /dev/null +++ b/examples/data-sources/stackit_objectstorage_compliance_lock/data-source.tf @@ -0,0 +1,3 @@ +data "stackit_objectstorage_compliance_lock" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} \ No newline at end of file diff --git a/examples/resources/stackit_objectstorage_compliance_lock/resource.tf b/examples/resources/stackit_objectstorage_compliance_lock/resource.tf new file mode 100644 index 000000000..9c811bbf1 --- /dev/null +++ b/examples/resources/stackit_objectstorage_compliance_lock/resource.tf @@ -0,0 +1,3 @@ +resource "stackit_objectstorage_compliance_lock" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} \ No newline at end of file diff --git a/go.mod b/go.mod index f2182b747..e7f626846 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.27.1 github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.8.1 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.7.1 - github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.6.1 + github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0 github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.26.1 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.3.5 diff --git a/go.sum b/go.sum index ac1a4ccea..976240e19 100644 --- a/go.sum +++ b/go.sum @@ -181,8 +181,8 @@ github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.8.1 h1:qBPfWK6Xp github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.8.1/go.mod h1:b8L6f68HZce01y+eZ1o7KTRAkgpWhggpvakAEwnxnCs= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.7.1 h1:8HFqfUI35Uk5QHUr3+VO21KXstzUl5zDKFrMuVOx+BI= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.7.1/go.mod h1:an5vc+0PL6OBMqFl75uuQpVGyzWHBpoxc9a5p41fFA8= -github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.6.1 h1:rOf829+57quGO2x3aG4dJJFgx4ZdtWqYE+hkW1tqGSY= -github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.6.1/go.mod h1:RFL4h6JZvpsyFYbdJ3+eINEkletzJQTfrPdd+yPT/fU= +github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0 h1:UxnbsKm6PQV8Gudw/EhySaEh9q1xSaTG8mzJz1EvhnE= +github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0/go.mod h1:RFL4h6JZvpsyFYbdJ3+eINEkletzJQTfrPdd+yPT/fU= github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0 h1:LGwCvvST0fwUgZ6bOxYIfu45qqTgv421ZS07UhKjZL8= github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0/go.mod h1:9KdrXC5JS30Ay3mR0adb3vNdhca+qxiy/cPF5P4wehQ= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.26.1 h1:AO5Np67/w0AUdhb6yk+CTXMzMkGdQPudmI8ryWp94fQ= diff --git a/stackit/internal/services/objectstorage/compliance-lock/datasource.go b/stackit/internal/services/objectstorage/compliance-lock/datasource.go new file mode 100644 index 000000000..06a6c2377 --- /dev/null +++ b/stackit/internal/services/objectstorage/compliance-lock/datasource.go @@ -0,0 +1,144 @@ +package compliancelock + +import ( + "context" + "fmt" + "net/http" + + "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-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + objectstorageUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &compliancelockDataSource{} +) + +// NewComplianceLockDataSource is a helper function to simplify the provider implementation. +func NewComplianceLockDataSource() datasource.DataSource { + return &compliancelockDataSource{} +} + +// compliancelockDataSource is the data source implementation. +type compliancelockDataSource struct { + client *objectstorage.APIClient + providerData core.ProviderData +} + +// Metadata returns the data source type name. +func (d *compliancelockDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_objectstorage_compliance_lock" +} + +// Configure adds the provider configured client to the data source. +func (d *compliancelockDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + var ok bool + d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := objectstorageUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + d.client = apiClient + tflog.Info(ctx, "ObjectStorage compliance lock client configured") +} + +// Schema defines the schema for the resource. +func (d *compliancelockDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "ObjectStorage compliance lock resource schema. Must have a `region` specified in the provider configuration.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`\".", + "project_id": "STACKIT Project ID to which the compliance lock is associated.", + "region": "The resource region. If not defined, the provider region is used.", + "max_retention_days": "Maximum retention period in days.", + } + + resp.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "max_retention_days": schema.Int64Attribute{ + Description: descriptions["max_retention_days"], + Computed: true, + }, + "region": schema.StringAttribute{ + Optional: true, + // the region cannot be found automatically, so it has to be passed + Description: descriptions["region"], + }, + }, + } +} + +// Read refreshes the Terraform state with the latest data. +func (d *compliancelockDataSource) 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) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + complianceResp, err := d.client.GetComplianceLock(ctx, projectId, region).Execute() + if err != nil { + utils.LogError( + ctx, + &resp.Diagnostics, + err, + "Reading compliance lock", + fmt.Sprintf("Compliance lock does not exist in project %q.", 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) + + // Map response body to schema + err = mapFields(complianceResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading compliance lock", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set refreshed state + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "ObjectStorage compliance lock read") +} diff --git a/stackit/internal/services/objectstorage/compliance-lock/resource.go b/stackit/internal/services/objectstorage/compliance-lock/resource.go new file mode 100644 index 000000000..68c4d5fbf --- /dev/null +++ b/stackit/internal/services/objectstorage/compliance-lock/resource.go @@ -0,0 +1,290 @@ +package compliancelock + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + objectstorageUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "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" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" + "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/utils" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &compliancelockResource{} + _ resource.ResourceWithConfigure = &compliancelockResource{} +) + +type Model struct { + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + MaxRetentionDays types.Int64 `tfsdk:"max_retention_days"` +} + +// NewComplianceLockResource is a helper function to simplify the provider implementation. +func NewComplianceLockResource() resource.Resource { + return &compliancelockResource{} +} + +// compliancelockResource is the resource implementation. +type compliancelockResource struct { + client *objectstorage.APIClient + providerData core.ProviderData +} + +// ModifyPlan implements resource.ResourceWithModifyPlan. +// Use the modifier to set the effective region in the current plan. +func (r *compliancelockResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel Model + // skip initial empty configuration to avoid follow-up errors + 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 + } + + utils.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 + } +} + +// Metadata returns the resource type name. +func (r *compliancelockResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_objectstorage_compliance_lock" +} + +// Configure adds the provider configured client to the resource. +func (r *compliancelockResource) 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 + } + + apiClient := objectstorageUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "ObjectStorage client configured") +} + +// Schema defines the schema for the resource. +func (r *compliancelockResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "ObjectStorage compliance lock resource schema. Must have a `region` specified in the provider configuration. Always use only one compliance lock per project.", + "id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`region`\".", + "project_id": "STACKIT Project ID to which the compliance lock is associated.", + "region": "The resource region. If not defined, the provider region is used.", + "max_retention_days": "Maximum retention period in days.", + } + + resp.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "max_retention_days": schema.Int64Attribute{ + Description: descriptions["max_retention_days"], + Computed: true, + }, + "region": schema.StringAttribute{ + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + Description: descriptions["region"], + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (r *compliancelockResource) 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 := r.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + complianceResp, err := r.client.CreateComplianceLock(ctx, projectId, region).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + + if !(ok && oapiErr.StatusCode == http.StatusConflict) { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating compliance lock", fmt.Sprintf("Calling API: %v", err)) + return + } + + tflog.Info(ctx, "Compliance lock is already enabled for this project. Please check duplicate resources.") + complianceResp, err = r.client.GetComplianceLock(ctx, projectId, region).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading compliance lock", fmt.Sprintf("Calling API: %v", err)) + return + } + } + + // Map response body to schema + err = mapFields(complianceResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating compliance lock", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "ObjectStorage compliance lock created") +} + +// Read refreshes the Terraform state with the latest data. +func (r *compliancelockResource) 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 + } + + projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + + ctx = core.InitProviderContext(ctx) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + complianceResp, err := r.client.GetComplianceLock(ctx, projectId, region).Execute() + if err != nil { + oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading compliance lock", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + // Map response body to schema + err = mapFields(complianceResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading compliance lock", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set refreshed state + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "ObjectStorage compliance lock read") +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *compliancelockResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + // Update shouldn't be called + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating compliance lock", "Compliance lock can't be updated") +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *compliancelockResource) 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() + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + _, err := r.client.DeleteComplianceLock(ctx, projectId, region).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting compliance lock", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + tflog.Info(ctx, "ObjectStorage compliance lock deleted") +} + +func mapFields(complianceResp *objectstorage.ComplianceLockResponse, model *Model, region string) error { + if complianceResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region) + model.Region = types.StringValue(region) + model.MaxRetentionDays = types.Int64PointerValue(complianceResp.MaxRetentionDays) + return nil +} diff --git a/stackit/internal/services/objectstorage/compliance-lock/resource_test.go b/stackit/internal/services/objectstorage/compliance-lock/resource_test.go new file mode 100644 index 000000000..2cdb12a22 --- /dev/null +++ b/stackit/internal/services/objectstorage/compliance-lock/resource_test.go @@ -0,0 +1,72 @@ +package compliancelock + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/services/objectstorage" +) + +func TestMapFields(t *testing.T) { + const testRegion = "eu01" + id := fmt.Sprintf("%s,%s", "pid", testRegion) + retentionDays := int64(30) + tests := []struct { + description string + input *objectstorage.ComplianceLockResponse + expected Model + isValid bool + }{ + { + "default_values", + &objectstorage.ComplianceLockResponse{}, + Model{ + Id: types.StringValue(id), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + }, + true, + }, + { + "simple_values", + &objectstorage.ComplianceLockResponse{ + MaxRetentionDays: &retentionDays, + }, + Model{ + Id: types.StringValue(id), + ProjectId: types.StringValue("pid"), + Region: types.StringValue("eu01"), + MaxRetentionDays: types.Int64Value(retentionDays), + }, + true, + }, + { + "nil_response", + nil, + Model{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + model := &Model{ + ProjectId: tt.expected.ProjectId, + } + err := mapFields(tt.input, model, "eu01") + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(model, &tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/objectstorage/objectstorage_acc_test.go b/stackit/internal/services/objectstorage/objectstorage_acc_test.go index 4fd117255..9b69b105b 100644 --- a/stackit/internal/services/objectstorage/objectstorage_acc_test.go +++ b/stackit/internal/services/objectstorage/objectstorage_acc_test.go @@ -81,6 +81,10 @@ func TestAccObjectStorageResourceMin(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_objectstorage_credential.credential_time", "name"), resource.TestCheckResourceAttrSet("stackit_objectstorage_credential.credential_time", "access_key"), resource.TestCheckResourceAttrSet("stackit_objectstorage_credential.credential_time", "secret_access_key"), + + // compliance lock + resource.TestCheckResourceAttr("stackit_objectstorage_compliance_lock.compliance_lock", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), + resource.TestCheckResourceAttrSet("stackit_objectstorage_compliance_lock.compliance_lock", "max_retention_days"), ), }, // Data source @@ -109,6 +113,9 @@ func TestAccObjectStorageResourceMin(t *testing.T) { project_id = stackit_objectstorage_credential.credential_time.project_id credentials_group_id = stackit_objectstorage_credential.credential_time.credentials_group_id credential_id = stackit_objectstorage_credential.credential_time.credential_id + } + data "stackit_objectstorage_compliance_lock" "compliance_lock" { + project_id = stackit_objectstorage_compliance_lock.compliance_lock.project_id }`, testutil.ObjectStorageProviderConfig()+resourceMinConfig, ), @@ -186,6 +193,10 @@ func TestAccObjectStorageResourceMin(t *testing.T) { "stackit_objectstorage_credential.credential_time", "expiration_timestamp", "data.stackit_objectstorage_credential.credential_time", "expiration_timestamp", ), + + // Compliance lock + resource.TestCheckResourceAttr("data.stackit_objectstorage_compliance_lock.compliance_lock", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])), + resource.TestCheckResourceAttrSet("data.stackit_objectstorage_compliance_lock.compliance_lock", "max_retention_days"), ), }, // Import diff --git a/stackit/internal/services/objectstorage/testfiles/resource-min.tf b/stackit/internal/services/objectstorage/testfiles/resource-min.tf index db9b28faf..1b05ee14f 100644 --- a/stackit/internal/services/objectstorage/testfiles/resource-min.tf +++ b/stackit/internal/services/objectstorage/testfiles/resource-min.tf @@ -24,3 +24,7 @@ resource "stackit_objectstorage_credential" "credential_time" { credentials_group_id = stackit_objectstorage_credentials_group.credentials_group.credentials_group_id expiration_timestamp = var.expiration_timestamp } + +resource "stackit_objectstorage_compliance_lock" "compliance_lock" { + project_id = var.project_id +} \ No newline at end of file diff --git a/stackit/provider.go b/stackit/provider.go index c30c35839..7e4139d66 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -74,6 +74,7 @@ import ( mongoDBFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/instance" mongoDBFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/mongodbflex/user" objectStorageBucket "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/bucket" + compliancelock "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/compliance-lock" objecStorageCredential "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/credential" objecStorageCredentialsGroup "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/objectstorage/credentialsgroup" alertGroup "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/observability/alertgroup" @@ -682,6 +683,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource share.NewShareDataSource, exportpolicy.NewExportPolicyDataSource, snapshots.NewResourcePoolSnapshotDataSource, + compliancelock.NewComplianceLockDataSource, } dataSources = append(dataSources, customRole.NewCustomRoleDataSources()...) @@ -767,6 +769,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { resourcepool.NewResourcePoolResource, share.NewShareResource, exportpolicy.NewExportPolicyResource, + compliancelock.NewComplianceLockResource, } resources = append(resources, roleAssignements.NewRoleAssignmentResources()...) resources = append(resources, customRole.NewCustomRoleResources()...)