From a7259ffa763844a84d2a89ea4e6c33053bb47e5f Mon Sep 17 00:00:00 2001 From: smagulow Date: Tue, 1 Jul 2025 15:45:03 +0200 Subject: [PATCH 01/13] first steps for kms key-ring resource and datasource --- go.mod | 1 + go.sum | 2 + stackit/internal/core/core.go | 1 + .../services/kms/key-ring/datasource.go | 34 +++++++++++++ .../services/kms/key-ring/resource.go | 48 +++++++++++++++++++ stackit/internal/services/kms/utils/util.go | 29 +++++++++++ stackit/provider.go | 14 ++++++ 7 files changed, 129 insertions(+) create mode 100644 stackit/internal/services/kms/key-ring/datasource.go create mode 100644 stackit/internal/services/kms/key-ring/resource.go create mode 100644 stackit/internal/services/kms/utils/util.go diff --git a/go.mod b/go.mod index abd3a9b53..3aec45ef5 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/dns v0.16.0 github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0 github.com/stackitcloud/stackit-sdk-go/services/iaas v0.25.0 + github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0 diff --git a/go.sum b/go.sum index 356889703..0a27b3cab 100644 --- a/go.sum +++ b/go.sum @@ -162,6 +162,8 @@ github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0 h1:C+8z3MdvnTngcH9L72 github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0/go.mod h1:agI7SONeLR/IZL3TOgn1tDzfS63O2rWKQE8+huRjEzU= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.25.0 h1:K9RjMPlEK1XQegZBMIrI/KHAorzRdOt5YpftsT7pMEk= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.25.0/go.mod h1:lUGkcbyMkd4nRBDFmKohIwlgtOZqQo4Ek5S5ajw90Xg= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 h1:Fo1XCnfW47yW9zNfQgJk6/e9Z7YVoD4fay/PxYnSf4c= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0/go.mod h1:I3UOleO9ZlTYQDd5iTpomfjx7l1J7JAIj9VGmAjTMjk= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0 h1:Ef4SyTBjIkfwaws4mssa6AoK+OokHFtr7ZIflUpoXVE= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0/go.mod h1:FiVhDlw9+yuTiUmnyGLn2qpsLW26w9OC4TS1y78czvg= github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 h1:QKOfaB7EcuJmBCxpFXN2K7g2ih0gQM6cyZ1VhTmtQfI= diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index 293afa323..d84936ec4 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -26,6 +26,7 @@ type ProviderData struct { DnsCustomEndpoint string GitCustomEndpoint string IaaSCustomEndpoint string + KMSCustomEndpoint string LoadBalancerCustomEndpoint string LogMeCustomEndpoint string MariaDBCustomEndpoint string diff --git a/stackit/internal/services/kms/key-ring/datasource.go b/stackit/internal/services/kms/key-ring/datasource.go new file mode 100644 index 000000000..5569a981d --- /dev/null +++ b/stackit/internal/services/kms/key-ring/datasource.go @@ -0,0 +1,34 @@ +package kms + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/stackitcloud/stackit-sdk-go/services/kms" +) + +var ( + _ datasource.DataSource = &keyRingDataSource{} +) + +func NewKeyRingDataSource() datasource.DataSource { + return &keyRingDataSource{} +} + +type keyRingDataSource struct { + client *kms.APIClient +} + +func (k keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + //TODO implement me + panic("implement me") +} diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go new file mode 100644 index 000000000..08a9510ff --- /dev/null +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -0,0 +1,48 @@ +package kms + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/stackitcloud/stackit-sdk-go/services/kms" +) + +type Model struct { +} + +func NewKeyRingResource() resource.Resource { + return &keyRingResource{} +} + +type keyRingResource struct { + client *kms.APIClient +} + +func (k keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + //TODO implement me + panic("implement me") +} diff --git a/stackit/internal/services/kms/utils/util.go b/stackit/internal/services/kms/utils/util.go new file mode 100644 index 000000000..fd3ed3a04 --- /dev/null +++ b/stackit/internal/services/kms/utils/util.go @@ -0,0 +1,29 @@ +package utils + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *kms.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.KMSCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.KMSCustomEndpoint)) + } else { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + } + apiClient, err := kms.NewAPIClient(apiClientConfigOptions...) + if err != nil { + core.LogAndAddError(ctx, diags, "Error configurin API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + } + + return apiClient +} diff --git a/stackit/provider.go b/stackit/provider.go index a3392bdfb..b4d66d3ea 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -40,6 +40,7 @@ import ( iaasServiceAccountAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/serviceaccountattach" iaasVolume "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/volume" iaasVolumeAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/volumeattach" + kms "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key-ring" loadBalancer "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/loadbalancer" 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" @@ -119,6 +120,7 @@ type providerModel struct { DNSCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` GitCustomEndpoint types.String `tfsdk:"git_custom_endpoint"` IaaSCustomEndpoint types.String `tfsdk:"iaas_custom_endpoint"` + KMSCustomEndpoint types.List `tfsdk:"kms_custom_endpoint"` PostgresFlexCustomEndpoint types.String `tfsdk:"postgresflex_custom_endpoint"` MongoDBFlexCustomEndpoint types.String `tfsdk:"mongodbflex_custom_endpoint"` ModelServingCustomEndpoint types.String `tfsdk:"modelserving_custom_endpoint"` @@ -160,6 +162,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "dns_custom_endpoint": "Custom endpoint for the DNS service", "git_custom_endpoint": "Custom endpoint for the Git service", "iaas_custom_endpoint": "Custom endpoint for the IaaS service", + "kms_custom_endpoint": "Custom endpoint for the KMS service", "mongodbflex_custom_endpoint": "Custom endpoint for the MongoDB Flex service", "modelserving_custom_endpoint": "Custom endpoint for the AI Model Serving service", "loadbalancer_custom_endpoint": "Custom endpoint for the Load Balancer service", @@ -247,6 +250,11 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["iaas_custom_endpoint"], }, + "kms_custom_endpoint": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: descriptions["kms_custom_endpoint"], + }, "postgresflex_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["postgresflex_custom_endpoint"], @@ -412,6 +420,10 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.ServiceEnablementCustomEndpoint, func(v string) { providerData.ServiceEnablementCustomEndpoint = v }) setBoolField(providerConfig.EnableBetaResources, func(v bool) { providerData.EnableBetaResources = v }) + if !(providerConfig.KMSCustomEndpoint.IsUnknown() || providerConfig.KMSCustomEndpoint.IsNull()) { + providerData.KMSCustomEndpoint = providerConfig.KMSCustomEndpoint.String() + } + if !(providerConfig.Experiments.IsUnknown() || providerConfig.Experiments.IsNull()) { var experimentValues []string diags := providerConfig.Experiments.ElementsAs(ctx, &experimentValues, false) @@ -458,6 +470,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource iaasServer.NewServerDataSource, iaasSecurityGroup.NewSecurityGroupDataSource, iaasSecurityGroupRule.NewSecurityGroupRuleDataSource, + kms.NewKeyRingDataSource, loadBalancer.NewLoadBalancerDataSource, logMeInstance.NewInstanceDataSource, logMeCredential.NewCredentialDataSource, @@ -519,6 +532,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { iaasServer.NewServerResource, iaasSecurityGroup.NewSecurityGroupResource, iaasSecurityGroupRule.NewSecurityGroupRuleResource, + kms.NewKeyRingResource, loadBalancer.NewLoadBalancerResource, loadBalancerObservabilityCredential.NewObservabilityCredentialResource, logMeInstance.NewInstanceResource, From 6f6d063b22998605a250ee342118af1013bf331c Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 11 Jul 2025 14:16:39 +0200 Subject: [PATCH 02/13] define model, implement Metadata, Configure, Schema and Create methods + helper funcs --- .../services/kms/key-ring/resource.go | 188 ++++++++++++++++-- 1 file changed, 176 insertions(+), 12 deletions(-) diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go index 08a9510ff..1ea846572 100644 --- a/stackit/internal/services/kms/key-ring/resource.go +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -2,11 +2,30 @@ package kms import ( "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" ) type Model struct { + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + KeyRingId types.String `tfsdk:"key_ring_id"` + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + RegionId types.String `tfsdk:"region_id"` } func NewKeyRingResource() resource.Resource { @@ -17,32 +36,177 @@ type keyRingResource struct { client *kms.APIClient } -func (k keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "kms_key_ring" } -func (k keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + + apiClient := kmsUtils.ConfigureClient(ctx, &providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + k.client = apiClient } -func (k keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key Ring resource schema.", + "description": "A user chosen description to distinguish multiple key rings.", + "display_name": "The display name to distinguish multiple key rings.", + "key_ring_id": "An auto generated unique id which identifies the key ring.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["description"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "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(), + }, + }, + }, + } + +} + +func (k *keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var model Model + diags := request.Plan.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + projectId := model.ProjectId.ValueString() + regionId := model.RegionId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region_id", regionId) + + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Creating API payload: %v", err)) + return + } + createResponse, err := k.client.CreateKeyRing(ctx, projectId, regionId).CreateKeyRingPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Calling API: %v", err)) + return + } + + keyRingId := *createResponse.Id + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + + err = mapFields(createResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key Ring created") } -func (k keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { +func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { //TODO implement me panic("implement me") } -func (k keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { +func (k *keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { //TODO implement me panic("implement me") } -func (k keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { +func (k *keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { //TODO implement me panic("implement me") } + +func toCreatePayload(model *Model) (*kms.CreateKeyRingPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + return &kms.CreateKeyRingPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + }, nil +} + +func mapFields(keyRing *kms.KeyRing, model *Model) error { + if keyRing == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var keyRingId string + if model.KeyRingId.ValueString() != "" { + keyRingId = model.KeyRingId.ValueString() + } else if keyRing.Id != nil { + keyRingId = *keyRing.Id + } else { + return fmt.Errorf("keyring id not present") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), keyRingId) + model.KeyRingId = types.StringValue(keyRingId) + model.DisplayName = types.StringPointerValue(keyRing.DisplayName) + model.Description = types.StringPointerValue(keyRing.Description) + + return nil +} From 6bcc14a49cb10b013e33445cf8512958631b0b50 Mon Sep 17 00:00:00 2001 From: smagulow Date: Mon, 28 Jul 2025 14:04:14 +0200 Subject: [PATCH 03/13] PR comments, fix region logic, add example, add datasource, add example tf file --- docs/index.md | 1 + .../stackit_kms_key_ring/resource.tf | 6 + .../services/kms/key-ring/datasource.go | 141 +++++++++++++++-- .../services/kms/key-ring/resource.go | 142 +++++++++++++++--- stackit/internal/services/kms/utils/util.go | 2 +- stackit/provider.go | 4 +- 6 files changed, 258 insertions(+), 38 deletions(-) create mode 100644 examples/resources/stackit_kms_key_ring/resource.tf diff --git a/docs/index.md b/docs/index.md index b58bf66c9..0eb2774af 100644 --- a/docs/index.md +++ b/docs/index.md @@ -160,6 +160,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de - `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network - `git_custom_endpoint` (String) Custom endpoint for the Git service - `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service +- `kms_custom_endpoint` (List of String) Custom endpoint for the KMS service - `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service - `logme_custom_endpoint` (String) Custom endpoint for the LogMe service - `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service diff --git a/examples/resources/stackit_kms_key_ring/resource.tf b/examples/resources/stackit_kms_key_ring/resource.tf new file mode 100644 index 000000000..7bd5a50ed --- /dev/null +++ b/examples/resources/stackit_kms_key_ring/resource.tf @@ -0,0 +1,6 @@ +resource "stackit_kms_key_ring" "example" { + description = "example description" + display_name = "example name" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region_id = "eu01" +} \ No newline at end of file diff --git a/stackit/internal/services/kms/key-ring/datasource.go b/stackit/internal/services/kms/key-ring/datasource.go index 5569a981d..92b0aa72e 100644 --- a/stackit/internal/services/kms/key-ring/datasource.go +++ b/stackit/internal/services/kms/key-ring/datasource.go @@ -2,8 +2,19 @@ package kms import ( "context" + "fmt" + "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-log/tflog" "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" ) var ( @@ -15,20 +26,130 @@ func NewKeyRingDataSource() datasource.DataSource { } type keyRingDataSource struct { - client *kms.APIClient + client *kms.APIClient + providerData core.ProviderData } -func (k keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_kms_key_ring" } -func (k keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + var ok bool + k.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + + apiClient := kmsUtils.ConfigureClient(ctx, &k.providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + k.client = apiClient + tflog.Info(ctx, "Key ring configured") +} + +func (k *keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key Ring resource schema.", + "description": "A user chosen description to distinguish multiple key rings.", + "display_name": "The display name to distinguish multiple key rings.", + "key_ring_id": "An auto generated unique id which identifies the key ring.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["description"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Computed: false, + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "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(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + }, + }, + } } -func (k keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + var model Model + + diags := request.Config.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + keyRingResponse, err := k.client.GetKeyRing(ctx, projectId, region, keyRingId).Execute() + if err != nil { + utils.LogError( + ctx, + &response.Diagnostics, + err, + "Reading key ring", + fmt.Sprintf("Key ring with ID %q does not exist in project %q.", keyRingId, projectId), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + response.State.RemoveResource(ctx) + return + } + + err = mapFields(keyRingResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Processing API payload %v", err)) + return + } + + diags = response.State.Set(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key ring read") } diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go index 1ea846572..a433ea01a 100644 --- a/stackit/internal/services/kms/key-ring/resource.go +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -11,12 +12,21 @@ import ( "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/kms" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" + "strings" +) + +var ( + _ resource.Resource = &keyRingResource{} + _ resource.ResourceWithConfigure = &keyRingResource{} + _ resource.ResourceWithImportState = &keyRingResource{} ) type Model struct { @@ -25,7 +35,7 @@ type Model struct { KeyRingId types.String `tfsdk:"key_ring_id"` Id types.String `tfsdk:"id"` // needed by TF ProjectId types.String `tfsdk:"project_id"` - RegionId types.String `tfsdk:"region_id"` + Region types.String `tfsdk:"region"` } func NewKeyRingResource() resource.Resource { @@ -33,20 +43,22 @@ func NewKeyRingResource() resource.Resource { } type keyRingResource struct { - client *kms.APIClient + client *kms.APIClient + providerData core.ProviderData } func (k *keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = request.ProviderTypeName + "kms_key_ring" + response.TypeName = request.ProviderTypeName + "_kms_key_ring" } func (k *keyRingResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + var ok bool + k.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) if !ok { return } - apiClient := kmsUtils.ConfigureClient(ctx, &providerData, &response.Diagnostics) + apiClient := kmsUtils.ConfigureClient(ctx, &k.providerData, &response.Diagnostics) if response.Diagnostics.HasError() { return } @@ -55,7 +67,7 @@ func (k *keyRingResource) Configure(ctx context.Context, request resource.Config func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { descriptions := map[string]string{ - "main": "KMS Key Ring resource schema.", + "main": "KMS Key Ring resource schema. Must have a `region` specified in the provider configuration.", "description": "A user chosen description to distinguish multiple key rings.", "display_name": "The display name to distinguish multiple key rings.", "key_ring_id": "An auto generated unique id which identifies the key ring.", @@ -116,9 +128,16 @@ func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaReq validate.NoSeparator(), }, }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, }, } - } func (k *keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { @@ -128,17 +147,19 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq if response.Diagnostics.HasError() { return } + projectId := model.ProjectId.ValueString() - regionId := model.RegionId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) - ctx = tflog.SetField(ctx, "region_id", regionId) + ctx = tflog.SetField(ctx, "region", region) payload, err := toCreatePayload(&model) if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Creating API payload: %v", err)) return } - createResponse, err := k.client.CreateKeyRing(ctx, projectId, regionId).CreateKeyRingPayload(*payload).Execute() + createResponse, err := k.client.CreateKeyRing(ctx, projectId, region).CreateKeyRingPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Calling API: %v", err)) return @@ -147,7 +168,7 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq keyRingId := *createResponse.Id ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) - err = mapFields(createResponse, &model) + err = mapFields(createResponse, &model, region) if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Processing API payload: %v", err)) return @@ -162,31 +183,91 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq } func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - //TODO implement me - panic("implement me") + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + keyRingResponse, err := k.client.GetKeyRing(ctx, projectId, region, keyRingId).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 { + response.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(keyRingResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key ring read") } func (k *keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - //TODO implement me - panic("implement me") + // key rings cannot be updated, so we log an error. + core.LogAndAddError(ctx, &response.Diagnostics, "Error updating key ring", "Key rings can't be updated") } func (k *keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - //TODO implement me - panic("implement me") + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + err := k.client.DeleteKeyRing(ctx, projectId, region, keyRingId).Execute() + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error deleting key ring", fmt.Sprintf("Calling API: %v", err)) + } + + tflog.Info(ctx, "key ring deleted") } -func toCreatePayload(model *Model) (*kms.CreateKeyRingPayload, error) { - if model == nil { - return nil, fmt.Errorf("nil model") +func (k *keyRingResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.Split(request.ID, core.Separator) + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + core.LogAndAddError(ctx, &response.Diagnostics, + "Error importing key ring", + fmt.Sprintf("Exptected import identifier with format: [proejct_id],[instance_id], got :%q", request.ID), + ) + return } - return &kms.CreateKeyRingPayload{ - Description: conversion.StringValueToPointer(model.Description), - DisplayName: conversion.StringValueToPointer(model.DisplayName), - }, nil + + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("key_ring_id"), idParts[1])...) + tflog.Info(ctx, "key ring state imported") } -func mapFields(keyRing *kms.KeyRing, model *Model) error { +func mapFields(keyRing *kms.KeyRing, model *Model, region string) error { if keyRing == nil { return fmt.Errorf("response input is nil") } @@ -207,6 +288,17 @@ func mapFields(keyRing *kms.KeyRing, model *Model) error { model.KeyRingId = types.StringValue(keyRingId) model.DisplayName = types.StringPointerValue(keyRing.DisplayName) model.Description = types.StringPointerValue(keyRing.Description) + model.Region = types.StringValue(region) return nil } + +func toCreatePayload(model *Model) (*kms.CreateKeyRingPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + return &kms.CreateKeyRingPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + }, nil +} diff --git a/stackit/internal/services/kms/utils/util.go b/stackit/internal/services/kms/utils/util.go index fd3ed3a04..f78cb54a5 100644 --- a/stackit/internal/services/kms/utils/util.go +++ b/stackit/internal/services/kms/utils/util.go @@ -18,7 +18,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags if providerData.KMSCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.KMSCustomEndpoint)) } else { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + apiClientConfigOptions = append(apiClientConfigOptions) } apiClient, err := kms.NewAPIClient(apiClientConfigOptions...) if err != nil { diff --git a/stackit/provider.go b/stackit/provider.go index cd641d694..103eaf68d 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -45,7 +45,7 @@ import ( iaasalphaRoutingTableRoutes "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/routes" iaasalphaRoutingTable "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/table" iaasalphaRoutingTables "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/tables" - kms "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key-ring" + kms "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key-ring" loadBalancer "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/loadbalancer" 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" @@ -543,7 +543,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { iaasSecurityGroupRule.NewSecurityGroupRuleResource, iaasalphaRoutingTable.NewRoutingTableResource, iaasalphaRoutingTableRoute.NewRoutingTableRouteResource, - kms.NewKeyRingResource, + kms.NewKeyRingResource, loadBalancer.NewLoadBalancerResource, loadBalancerObservabilityCredential.NewObservabilityCredentialResource, logMeInstance.NewInstanceResource, From bcd0528be7fc3d76fa07d7ef5df39659e5b3964c Mon Sep 17 00:00:00 2001 From: smagulow Date: Tue, 1 Jul 2025 15:45:03 +0200 Subject: [PATCH 04/13] first steps for kms key-ring resource and datasource --- go.mod | 2 + go.sum | 2 + stackit/internal/core/core.go | 1 + .../services/kms/key-ring/datasource.go | 34 +++++++++++++ .../services/kms/key-ring/resource.go | 48 +++++++++++++++++++ stackit/internal/services/kms/utils/util.go | 29 +++++++++++ stackit/provider.go | 14 ++++++ 7 files changed, 130 insertions(+) create mode 100644 stackit/internal/services/kms/key-ring/datasource.go create mode 100644 stackit/internal/services/kms/key-ring/resource.go create mode 100644 stackit/internal/services/kms/utils/util.go diff --git a/go.mod b/go.mod index 2899ffd72..c3a283902 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1 github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1 github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha + github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.5.1 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.1 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.1 @@ -36,6 +37,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2 github.com/stackitcloud/stackit-sdk-go/services/ske v1.2.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1 + github.com/stackitcloud/stackit-sdk-go/core v0.17.2 github.com/teambition/rrule-go v1.8.2 golang.org/x/mod v0.26.0 ) diff --git a/go.sum b/go.sum index 190c8d8b4..c716163d1 100644 --- a/go.sum +++ b/go.sum @@ -160,6 +160,8 @@ github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1 h1:CnhAMLql0MNmAeq4r github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1/go.mod h1:7Bx85knfNSBxulPdJUFuBePXNee3cO+sOTYnUG6M+iQ= github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1 h1:hkFixFnBcQzU4BSIZFITc8N0gK0pUYk7mk0wdUu5Ki8= github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1/go.mod h1:Ng1EzrRndG3iGXGH90AZJz//wfK+2YOyDwTnTLwX3a4= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 h1:Fo1XCnfW47yW9zNfQgJk6/e9Z7YVoD4fay/PxYnSf4c= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0/go.mod h1:I3UOleO9ZlTYQDd5iTpomfjx7l1J7JAIj9VGmAjTMjk= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1 h1:0RP8DCwSCbl0KBsRI6INFy/8JW7UqlAd2Yr3dWFS3No= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1/go.mod h1:TvqL/TgVrrdDF387gAhO8QQYLxiaOZwgpmyv6s15TU0= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha h1:m1jq6a8dbUe+suFuUNdHmM/cSehpGLUtDbK1CqLqydg= diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index e477c9064..12aacb555 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -33,6 +33,7 @@ type ProviderData struct { DnsCustomEndpoint string GitCustomEndpoint string IaaSCustomEndpoint string + KMSCustomEndpoint string LoadBalancerCustomEndpoint string LogMeCustomEndpoint string MariaDBCustomEndpoint string diff --git a/stackit/internal/services/kms/key-ring/datasource.go b/stackit/internal/services/kms/key-ring/datasource.go new file mode 100644 index 000000000..5569a981d --- /dev/null +++ b/stackit/internal/services/kms/key-ring/datasource.go @@ -0,0 +1,34 @@ +package kms + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/stackitcloud/stackit-sdk-go/services/kms" +) + +var ( + _ datasource.DataSource = &keyRingDataSource{} +) + +func NewKeyRingDataSource() datasource.DataSource { + return &keyRingDataSource{} +} + +type keyRingDataSource struct { + client *kms.APIClient +} + +func (k keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + //TODO implement me + panic("implement me") +} diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go new file mode 100644 index 000000000..08a9510ff --- /dev/null +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -0,0 +1,48 @@ +package kms + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/stackitcloud/stackit-sdk-go/services/kms" +) + +type Model struct { +} + +func NewKeyRingResource() resource.Resource { + return &keyRingResource{} +} + +type keyRingResource struct { + client *kms.APIClient +} + +func (k keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + //TODO implement me + panic("implement me") +} + +func (k keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + //TODO implement me + panic("implement me") +} diff --git a/stackit/internal/services/kms/utils/util.go b/stackit/internal/services/kms/utils/util.go new file mode 100644 index 000000000..fd3ed3a04 --- /dev/null +++ b/stackit/internal/services/kms/utils/util.go @@ -0,0 +1,29 @@ +package utils + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *kms.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.KMSCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.KMSCustomEndpoint)) + } else { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + } + apiClient, err := kms.NewAPIClient(apiClientConfigOptions...) + if err != nil { + core.LogAndAddError(ctx, diags, "Error configurin API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + } + + return apiClient +} diff --git a/stackit/provider.go b/stackit/provider.go index 550410432..103eaf68d 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -45,6 +45,7 @@ import ( iaasalphaRoutingTableRoutes "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/routes" iaasalphaRoutingTable "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/table" iaasalphaRoutingTables "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/tables" + kms "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key-ring" loadBalancer "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/loadbalancer" 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" @@ -124,6 +125,7 @@ type providerModel struct { DNSCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` GitCustomEndpoint types.String `tfsdk:"git_custom_endpoint"` IaaSCustomEndpoint types.String `tfsdk:"iaas_custom_endpoint"` + KMSCustomEndpoint types.List `tfsdk:"kms_custom_endpoint"` PostgresFlexCustomEndpoint types.String `tfsdk:"postgresflex_custom_endpoint"` MongoDBFlexCustomEndpoint types.String `tfsdk:"mongodbflex_custom_endpoint"` ModelServingCustomEndpoint types.String `tfsdk:"modelserving_custom_endpoint"` @@ -165,6 +167,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "dns_custom_endpoint": "Custom endpoint for the DNS service", "git_custom_endpoint": "Custom endpoint for the Git service", "iaas_custom_endpoint": "Custom endpoint for the IaaS service", + "kms_custom_endpoint": "Custom endpoint for the KMS service", "mongodbflex_custom_endpoint": "Custom endpoint for the MongoDB Flex service", "modelserving_custom_endpoint": "Custom endpoint for the AI Model Serving service", "loadbalancer_custom_endpoint": "Custom endpoint for the Load Balancer service", @@ -252,6 +255,11 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["iaas_custom_endpoint"], }, + "kms_custom_endpoint": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: descriptions["kms_custom_endpoint"], + }, "postgresflex_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["postgresflex_custom_endpoint"], @@ -417,6 +425,10 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.ServiceEnablementCustomEndpoint, func(v string) { providerData.ServiceEnablementCustomEndpoint = v }) setBoolField(providerConfig.EnableBetaResources, func(v bool) { providerData.EnableBetaResources = v }) + if !(providerConfig.KMSCustomEndpoint.IsUnknown() || providerConfig.KMSCustomEndpoint.IsNull()) { + providerData.KMSCustomEndpoint = providerConfig.KMSCustomEndpoint.String() + } + if !(providerConfig.Experiments.IsUnknown() || providerConfig.Experiments.IsNull()) { var experimentValues []string diags := providerConfig.Experiments.ElementsAs(ctx, &experimentValues, false) @@ -467,6 +479,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource iaasalphaRoutingTables.NewRoutingTablesDataSource, iaasalphaRoutingTableRoutes.NewRoutingTableRoutesDataSource, iaasSecurityGroupRule.NewSecurityGroupRuleDataSource, + kms.NewKeyRingDataSource, loadBalancer.NewLoadBalancerDataSource, logMeInstance.NewInstanceDataSource, logMeCredential.NewCredentialDataSource, @@ -530,6 +543,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { iaasSecurityGroupRule.NewSecurityGroupRuleResource, iaasalphaRoutingTable.NewRoutingTableResource, iaasalphaRoutingTableRoute.NewRoutingTableRouteResource, + kms.NewKeyRingResource, loadBalancer.NewLoadBalancerResource, loadBalancerObservabilityCredential.NewObservabilityCredentialResource, logMeInstance.NewInstanceResource, From 99e2b7fe0839a3af8cab139a790e71bd507cee3c Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 11 Jul 2025 14:16:39 +0200 Subject: [PATCH 05/13] define model, implement Metadata, Configure, Schema and Create methods + helper funcs --- .../services/kms/key-ring/resource.go | 188 ++++++++++++++++-- 1 file changed, 176 insertions(+), 12 deletions(-) diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go index 08a9510ff..1ea846572 100644 --- a/stackit/internal/services/kms/key-ring/resource.go +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -2,11 +2,30 @@ package kms import ( "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" ) type Model struct { + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + KeyRingId types.String `tfsdk:"key_ring_id"` + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + RegionId types.String `tfsdk:"region_id"` } func NewKeyRingResource() resource.Resource { @@ -17,32 +36,177 @@ type keyRingResource struct { client *kms.APIClient } -func (k keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "kms_key_ring" } -func (k keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData, ok := conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + + apiClient := kmsUtils.ConfigureClient(ctx, &providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + k.client = apiClient } -func (k keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key Ring resource schema.", + "description": "A user chosen description to distinguish multiple key rings.", + "display_name": "The display name to distinguish multiple key rings.", + "key_ring_id": "An auto generated unique id which identifies the key ring.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["description"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "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(), + }, + }, + }, + } + +} + +func (k *keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var model Model + diags := request.Plan.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + projectId := model.ProjectId.ValueString() + regionId := model.RegionId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region_id", regionId) + + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Creating API payload: %v", err)) + return + } + createResponse, err := k.client.CreateKeyRing(ctx, projectId, regionId).CreateKeyRingPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Calling API: %v", err)) + return + } + + keyRingId := *createResponse.Id + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + + err = mapFields(createResponse, &model) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key Ring created") } -func (k keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { +func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { //TODO implement me panic("implement me") } -func (k keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { +func (k *keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { //TODO implement me panic("implement me") } -func (k keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { +func (k *keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { //TODO implement me panic("implement me") } + +func toCreatePayload(model *Model) (*kms.CreateKeyRingPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + return &kms.CreateKeyRingPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + }, nil +} + +func mapFields(keyRing *kms.KeyRing, model *Model) error { + if keyRing == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var keyRingId string + if model.KeyRingId.ValueString() != "" { + keyRingId = model.KeyRingId.ValueString() + } else if keyRing.Id != nil { + keyRingId = *keyRing.Id + } else { + return fmt.Errorf("keyring id not present") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), keyRingId) + model.KeyRingId = types.StringValue(keyRingId) + model.DisplayName = types.StringPointerValue(keyRing.DisplayName) + model.Description = types.StringPointerValue(keyRing.Description) + + return nil +} From 8c654ad5d0a97551d1d3f274fcc6777b5f6aaf1e Mon Sep 17 00:00:00 2001 From: smagulow Date: Mon, 28 Jul 2025 14:04:14 +0200 Subject: [PATCH 06/13] PR comments, fix region logic, add example, add datasource, add example tf file --- docs/index.md | 1 + .../stackit_kms_key_ring/resource.tf | 6 + .../services/kms/key-ring/datasource.go | 141 +++++++++++++++-- .../services/kms/key-ring/resource.go | 142 +++++++++++++++--- stackit/internal/services/kms/utils/util.go | 2 +- 5 files changed, 256 insertions(+), 36 deletions(-) create mode 100644 examples/resources/stackit_kms_key_ring/resource.tf diff --git a/docs/index.md b/docs/index.md index b58bf66c9..0eb2774af 100644 --- a/docs/index.md +++ b/docs/index.md @@ -160,6 +160,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de - `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network - `git_custom_endpoint` (String) Custom endpoint for the Git service - `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service +- `kms_custom_endpoint` (List of String) Custom endpoint for the KMS service - `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service - `logme_custom_endpoint` (String) Custom endpoint for the LogMe service - `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service diff --git a/examples/resources/stackit_kms_key_ring/resource.tf b/examples/resources/stackit_kms_key_ring/resource.tf new file mode 100644 index 000000000..7bd5a50ed --- /dev/null +++ b/examples/resources/stackit_kms_key_ring/resource.tf @@ -0,0 +1,6 @@ +resource "stackit_kms_key_ring" "example" { + description = "example description" + display_name = "example name" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region_id = "eu01" +} \ No newline at end of file diff --git a/stackit/internal/services/kms/key-ring/datasource.go b/stackit/internal/services/kms/key-ring/datasource.go index 5569a981d..92b0aa72e 100644 --- a/stackit/internal/services/kms/key-ring/datasource.go +++ b/stackit/internal/services/kms/key-ring/datasource.go @@ -2,8 +2,19 @@ package kms import ( "context" + "fmt" + "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-log/tflog" "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" ) var ( @@ -15,20 +26,130 @@ func NewKeyRingDataSource() datasource.DataSource { } type keyRingDataSource struct { - client *kms.APIClient + client *kms.APIClient + providerData core.ProviderData } -func (k keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_kms_key_ring" } -func (k keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + var ok bool + k.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + + apiClient := kmsUtils.ConfigureClient(ctx, &k.providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + k.client = apiClient + tflog.Info(ctx, "Key ring configured") +} + +func (k *keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key Ring resource schema.", + "description": "A user chosen description to distinguish multiple key rings.", + "display_name": "The display name to distinguish multiple key rings.", + "key_ring_id": "An auto generated unique id which identifies the key ring.", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["description"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Computed: false, + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "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(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + }, + }, + } } -func (k keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { - //TODO implement me - panic("implement me") +func (k *keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + var model Model + + diags := request.Config.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + keyRingResponse, err := k.client.GetKeyRing(ctx, projectId, region, keyRingId).Execute() + if err != nil { + utils.LogError( + ctx, + &response.Diagnostics, + err, + "Reading key ring", + fmt.Sprintf("Key ring with ID %q does not exist in project %q.", keyRingId, projectId), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + response.State.RemoveResource(ctx) + return + } + + err = mapFields(keyRingResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Processing API payload %v", err)) + return + } + + diags = response.State.Set(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key ring read") } diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go index 1ea846572..a433ea01a 100644 --- a/stackit/internal/services/kms/key-ring/resource.go +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -11,12 +12,21 @@ import ( "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/kms" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" + "strings" +) + +var ( + _ resource.Resource = &keyRingResource{} + _ resource.ResourceWithConfigure = &keyRingResource{} + _ resource.ResourceWithImportState = &keyRingResource{} ) type Model struct { @@ -25,7 +35,7 @@ type Model struct { KeyRingId types.String `tfsdk:"key_ring_id"` Id types.String `tfsdk:"id"` // needed by TF ProjectId types.String `tfsdk:"project_id"` - RegionId types.String `tfsdk:"region_id"` + Region types.String `tfsdk:"region"` } func NewKeyRingResource() resource.Resource { @@ -33,20 +43,22 @@ func NewKeyRingResource() resource.Resource { } type keyRingResource struct { - client *kms.APIClient + client *kms.APIClient + providerData core.ProviderData } func (k *keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = request.ProviderTypeName + "kms_key_ring" + response.TypeName = request.ProviderTypeName + "_kms_key_ring" } func (k *keyRingResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { - providerData, ok := conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + var ok bool + k.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) if !ok { return } - apiClient := kmsUtils.ConfigureClient(ctx, &providerData, &response.Diagnostics) + apiClient := kmsUtils.ConfigureClient(ctx, &k.providerData, &response.Diagnostics) if response.Diagnostics.HasError() { return } @@ -55,7 +67,7 @@ func (k *keyRingResource) Configure(ctx context.Context, request resource.Config func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { descriptions := map[string]string{ - "main": "KMS Key Ring resource schema.", + "main": "KMS Key Ring resource schema. Must have a `region` specified in the provider configuration.", "description": "A user chosen description to distinguish multiple key rings.", "display_name": "The display name to distinguish multiple key rings.", "key_ring_id": "An auto generated unique id which identifies the key ring.", @@ -116,9 +128,16 @@ func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaReq validate.NoSeparator(), }, }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, }, } - } func (k *keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { @@ -128,17 +147,19 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq if response.Diagnostics.HasError() { return } + projectId := model.ProjectId.ValueString() - regionId := model.RegionId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) - ctx = tflog.SetField(ctx, "region_id", regionId) + ctx = tflog.SetField(ctx, "region", region) payload, err := toCreatePayload(&model) if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Creating API payload: %v", err)) return } - createResponse, err := k.client.CreateKeyRing(ctx, projectId, regionId).CreateKeyRingPayload(*payload).Execute() + createResponse, err := k.client.CreateKeyRing(ctx, projectId, region).CreateKeyRingPayload(*payload).Execute() if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Calling API: %v", err)) return @@ -147,7 +168,7 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq keyRingId := *createResponse.Id ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) - err = mapFields(createResponse, &model) + err = mapFields(createResponse, &model, region) if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Processing API payload: %v", err)) return @@ -162,31 +183,91 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq } func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - //TODO implement me - panic("implement me") + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + keyRingResponse, err := k.client.GetKeyRing(ctx, projectId, region, keyRingId).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 { + response.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(keyRingResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key ring read") } func (k *keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - //TODO implement me - panic("implement me") + // key rings cannot be updated, so we log an error. + core.LogAndAddError(ctx, &response.Diagnostics, "Error updating key ring", "Key rings can't be updated") } func (k *keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - //TODO implement me - panic("implement me") + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + + err := k.client.DeleteKeyRing(ctx, projectId, region, keyRingId).Execute() + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error deleting key ring", fmt.Sprintf("Calling API: %v", err)) + } + + tflog.Info(ctx, "key ring deleted") } -func toCreatePayload(model *Model) (*kms.CreateKeyRingPayload, error) { - if model == nil { - return nil, fmt.Errorf("nil model") +func (k *keyRingResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.Split(request.ID, core.Separator) + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + core.LogAndAddError(ctx, &response.Diagnostics, + "Error importing key ring", + fmt.Sprintf("Exptected import identifier with format: [proejct_id],[instance_id], got :%q", request.ID), + ) + return } - return &kms.CreateKeyRingPayload{ - Description: conversion.StringValueToPointer(model.Description), - DisplayName: conversion.StringValueToPointer(model.DisplayName), - }, nil + + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("key_ring_id"), idParts[1])...) + tflog.Info(ctx, "key ring state imported") } -func mapFields(keyRing *kms.KeyRing, model *Model) error { +func mapFields(keyRing *kms.KeyRing, model *Model, region string) error { if keyRing == nil { return fmt.Errorf("response input is nil") } @@ -207,6 +288,17 @@ func mapFields(keyRing *kms.KeyRing, model *Model) error { model.KeyRingId = types.StringValue(keyRingId) model.DisplayName = types.StringPointerValue(keyRing.DisplayName) model.Description = types.StringPointerValue(keyRing.Description) + model.Region = types.StringValue(region) return nil } + +func toCreatePayload(model *Model) (*kms.CreateKeyRingPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + return &kms.CreateKeyRingPayload{ + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + }, nil +} diff --git a/stackit/internal/services/kms/utils/util.go b/stackit/internal/services/kms/utils/util.go index fd3ed3a04..f78cb54a5 100644 --- a/stackit/internal/services/kms/utils/util.go +++ b/stackit/internal/services/kms/utils/util.go @@ -18,7 +18,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags if providerData.KMSCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.KMSCustomEndpoint)) } else { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithRegion(providerData.GetRegion())) + apiClientConfigOptions = append(apiClientConfigOptions) } apiClient, err := kms.NewAPIClient(apiClientConfigOptions...) if err != nil { From 1186cee8ea0d9c9b1a71786441e4abccf804cb15 Mon Sep 17 00:00:00 2001 From: smagulow Date: Mon, 28 Jul 2025 14:18:18 +0200 Subject: [PATCH 07/13] PR comments, fix region logic, add example, add datasource, add example tf file --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index c3a283902..0271f5f98 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,6 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2 github.com/stackitcloud/stackit-sdk-go/services/ske v1.2.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1 - github.com/stackitcloud/stackit-sdk-go/core v0.17.2 github.com/teambition/rrule-go v1.8.2 golang.org/x/mod v0.26.0 ) From cb23a4e2bc3804f7156b71a20b0472c95e424ccc Mon Sep 17 00:00:00 2001 From: smagulow Date: Mon, 28 Jul 2025 14:18:18 +0200 Subject: [PATCH 08/13] PR comments, fix region logic, add example, add datasource, add example tf file --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index c3a283902..0271f5f98 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,6 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2 github.com/stackitcloud/stackit-sdk-go/services/ske v1.2.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1 - github.com/stackitcloud/stackit-sdk-go/core v0.17.2 github.com/teambition/rrule-go v1.8.2 golang.org/x/mod v0.26.0 ) From 2732c4af102953b3d984c036dd38b1ecb18716ed Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 8 Aug 2025 12:22:37 +0200 Subject: [PATCH 09/13] add missing resources and unit tests --- go.mod | 38 +- go.sum | 81 ++-- .../services/kms/key-ring/datasource.go | 2 +- .../services/kms/key-ring/resource.go | 10 +- .../services/kms/key-ring/resource_test.go | 171 +++++++++ .../internal/services/kms/key/datasource.go | 194 ++++++++++ stackit/internal/services/kms/key/resource.go | 361 ++++++++++++++++++ .../services/kms/key/resource_test.go | 175 +++++++++ .../services/kms/wrapping-key/datasource.go | 189 +++++++++ .../services/kms/wrapping-key/resource.go | 355 +++++++++++++++++ .../kms/wrapping-key/resource_test.go | 175 +++++++++ stackit/provider.go | 12 +- 12 files changed, 1713 insertions(+), 50 deletions(-) create mode 100644 stackit/internal/services/kms/key-ring/resource_test.go create mode 100644 stackit/internal/services/kms/key/datasource.go create mode 100644 stackit/internal/services/kms/key/resource.go create mode 100644 stackit/internal/services/kms/key/resource_test.go create mode 100644 stackit/internal/services/kms/wrapping-key/datasource.go create mode 100644 stackit/internal/services/kms/wrapping-key/resource.go create mode 100644 stackit/internal/services/kms/wrapping-key/resource_test.go diff --git a/go.mod b/go.mod index 0271f5f98..b66ee6d07 100644 --- a/go.mod +++ b/go.mod @@ -6,25 +6,25 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/hashicorp/terraform-plugin-framework v1.15.0 + github.com/hashicorp/terraform-plugin-framework v1.15.1 github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 github.com/hashicorp/terraform-plugin-go v0.28.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.13.2 github.com/stackitcloud/stackit-sdk-go/core v0.17.3 - github.com/stackitcloud/stackit-sdk-go/services/cdn v1.3.2 + github.com/stackitcloud/stackit-sdk-go/services/cdn v1.4.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1 github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1 - github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1 + github.com/stackitcloud/stackit-sdk-go/services/iaas v0.29.0 github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha - github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 + github.com/stackitcloud/stackit-sdk-go/services/kms v0.5.0 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.5.1 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.1 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.1 github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.1 - github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.1 + github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2 github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1 - github.com/stackitcloud/stackit-sdk-go/services/observability v0.9.1 + github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.2.1 github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.1 @@ -33,12 +33,12 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.13.1 github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.2 github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.1 - github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.9.1 + github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.0 github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2 - github.com/stackitcloud/stackit-sdk-go/services/ske v1.2.0 + github.com/stackitcloud/stackit-sdk-go/services/ske v1.3.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1 github.com/teambition/rrule-go v1.8.2 - golang.org/x/mod v0.26.0 + golang.org/x/mod v0.27.0 ) require github.com/hashicorp/go-retryablehttp v0.7.7 // indirect @@ -49,7 +49,7 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.3 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect @@ -66,7 +66,7 @@ require ( github.com/hashicorp/terraform-exec v0.23.0 // indirect github.com/hashicorp/terraform-json v0.25.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // indirect - github.com/hashicorp/terraform-registry-address v0.2.5 // indirect + github.com/hashicorp/terraform-registry-address v0.3.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -77,7 +77,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/oklog/run v1.1.0 // indirect + github.com/oklog/run v1.2.0 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/stackitcloud/stackit-sdk-go/services/authorization v0.8.1 github.com/stretchr/testify v1.8.4 // indirect @@ -85,16 +85,16 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.16.3 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.42.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/tools v0.35.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.7 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index c716163d1..822b5ee7c 100644 --- a/go.sum +++ b/go.sum @@ -30,14 +30,16 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -88,6 +90,8 @@ github.com/hashicorp/terraform-json v0.25.0 h1:rmNqc/CIfcWawGiwXmRuiXJKEiJu1ntGo github.com/hashicorp/terraform-json v0.25.0/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc= github.com/hashicorp/terraform-plugin-framework v1.15.0 h1:LQ2rsOfmDLxcn5EeIwdXFtr03FVsNktbbBci8cOKdb4= github.com/hashicorp/terraform-plugin-framework v1.15.0/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI= +github.com/hashicorp/terraform-plugin-framework v1.15.1 h1:2mKDkwb8rlx/tvJTlIcpw0ykcmvdWv+4gY3SIgk8Pq8= +github.com/hashicorp/terraform-plugin-framework v1.15.1/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI= github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw= github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA= github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA= @@ -98,8 +102,8 @@ github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsor github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= github.com/hashicorp/terraform-plugin-testing v1.13.2 h1:mSotG4Odl020vRjIenA3rggwo6Kg6XCKIwtRhYgp+/M= github.com/hashicorp/terraform-plugin-testing v1.13.2/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE= -github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= -github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= +github.com/hashicorp/terraform-registry-address v0.3.0 h1:HMpK3nqaGFPS9VmgRXrJL/dzHNdheGVKk5k7VlFxzCo= +github.com/hashicorp/terraform-registry-address v0.3.0/go.mod h1:jRGCMiLaY9zii3GLC7hqpSnwhfnCN5yzvY0hh4iCGbM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= @@ -136,8 +140,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -154,18 +158,22 @@ github.com/stackitcloud/stackit-sdk-go/core v0.17.3 h1:GsZGmRRc/3GJLmCUnsZswirr5 github.com/stackitcloud/stackit-sdk-go/core v0.17.3/go.mod h1:HBCXJGPgdRulplDzhrmwC+Dak9B/x0nzNtmOpu+1Ahg= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.8.1 h1:Kzr1G4g9PHI8ePFnHrHZEX06XtEJQYBK9JExje0aXl0= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.8.1/go.mod h1:OwQ+fYpON4WQpEinvI9lCTuuwj9UBCnPPJcnDpK803U= -github.com/stackitcloud/stackit-sdk-go/services/cdn v1.3.2 h1:E/JnmAgB9ffrqwE2JC9WTaL1+UGmnoZL03/RqDDoNQ8= -github.com/stackitcloud/stackit-sdk-go/services/cdn v1.3.2/go.mod h1:nNq+sD+jGKsR6xKCVSSxuSVmH5YYz8ZMt9QGbDPAEVU= +github.com/stackitcloud/stackit-sdk-go/services/cdn v1.4.0 h1:0R7yCI/Y5mtXi4ufMp7e4V+vfrQZXkkbSZANUheCwJ4= +github.com/stackitcloud/stackit-sdk-go/services/cdn v1.4.0/go.mod h1:lQgY1ugyG2pFzIuYTxWXjv9DFfB/sVIg7myf1tTaPYw= github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1 h1:CnhAMLql0MNmAeq4roQKN8OpSKX4FSgTU6Eu6detB4I= github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.1/go.mod h1:7Bx85knfNSBxulPdJUFuBePXNee3cO+sOTYnUG6M+iQ= github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1 h1:hkFixFnBcQzU4BSIZFITc8N0gK0pUYk7mk0wdUu5Ki8= github.com/stackitcloud/stackit-sdk-go/services/git v0.7.1/go.mod h1:Ng1EzrRndG3iGXGH90AZJz//wfK+2YOyDwTnTLwX3a4= -github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 h1:Fo1XCnfW47yW9zNfQgJk6/e9Z7YVoD4fay/PxYnSf4c= -github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0/go.mod h1:I3UOleO9ZlTYQDd5iTpomfjx7l1J7JAIj9VGmAjTMjk= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1 h1:0RP8DCwSCbl0KBsRI6INFy/8JW7UqlAd2Yr3dWFS3No= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.27.1/go.mod h1:TvqL/TgVrrdDF387gAhO8QQYLxiaOZwgpmyv6s15TU0= +github.com/stackitcloud/stackit-sdk-go/services/iaas v0.29.0 h1:j4FKFOVkcTot8xNxpvDsPzIFyjADE4GxXF0rFE6/Uo4= +github.com/stackitcloud/stackit-sdk-go/services/iaas v0.29.0/go.mod h1:b/jgJf7QHdRzU2fmZeJJtu5j0TAevDRghzcn5MyRmOI= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha h1:m1jq6a8dbUe+suFuUNdHmM/cSehpGLUtDbK1CqLqydg= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha/go.mod h1:Nu1b5Phsv8plgZ51+fkxPVsU91ZJ5Ayz+cthilxdmQ8= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 h1:Fo1XCnfW47yW9zNfQgJk6/e9Z7YVoD4fay/PxYnSf4c= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0/go.mod h1:I3UOleO9ZlTYQDd5iTpomfjx7l1J7JAIj9VGmAjTMjk= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.5.0 h1:pKxX9XcCfTqAM86xD66/iQVWdXSkAT+9AGNYD/195jA= +github.com/stackitcloud/stackit-sdk-go/services/kms v0.5.0/go.mod h1:KEPVoO21pC4bjy5l0nyhjUJ0+uVwVWb+k2TYrzJ8xYw= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.5.1 h1:OdJEs8eOfrzn9tCBDLxIyP8hX50zPfcXNYnRoQX+chs= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.5.1/go.mod h1:11uzaOPCF9SeDHXEGOPMlHDD3J5r2TnvCGUwW9Igq9c= github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.1 h1:hv5WrRU9rN6Jx4OwdOGJRyaQrfA9p1tzEoQK6/CDyoA= @@ -176,10 +184,14 @@ github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.1 h1:Fc+iVo5de github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.1/go.mod h1:84Gfz5wimt0gNyOXaAfTwVk/RFovgBxq3AOG2cdZx4Q= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.1 h1:XOpikSY2IXfBJPzUdgBk69iJXFC99xzfYtY1h4bZ5vM= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.1/go.mod h1:G7S/hGa6EyX5Avxxw/PIdbdtbFeiXL/T1vUkPOJ120w= +github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2 h1:BQ+qAkVS/aGHepE/+gVsvSg1sRkPOyIUI/jkCyUOrWg= +github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2/go.mod h1:oc8Mpwl7O6EZwG0YxfhOzNCJwNQBWK5rFh764OtxoMY= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1 h1:4jsFLbDVEosYTgQz6lPds1E9KDOiHwjuhWqcG+lo5B4= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1/go.mod h1:j1SHAS5lN8F9b/iPUOfjAl9QAA9tOT7NKOiDEzcM2zc= github.com/stackitcloud/stackit-sdk-go/services/observability v0.9.1 h1:eSiUKN61FJ4x42vgIvhVU7bgmrPbj05xR4y0nnRV5Yg= github.com/stackitcloud/stackit-sdk-go/services/observability v0.9.1/go.mod h1:oJku0heeBwsy4IToqhvSdPJI++GUNkBSESxOjiLWRVQ= +github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0 h1:SIctDqGprEoZArWaTds7hzQyh8Pqaz95Nmuj/1QuDEQ= +github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0/go.mod h1:tJEOi6L0le4yQZPGwalup/PZ13gqs1aCQDqlUs2cYW0= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1 h1:50n87uZn0EvSP9hJGLqd3Wm2hfqbyh7BMGGCk7axgqA= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1/go.mod h1:jfguuSPa56Z5Bzs/Xg/CI37XzPo5Zn5lzC5LhfuT8Qc= github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.2.1 h1:K8vXele3U6b5urcSIpq21EkVblWfPDY3eMPSuQ48TkI= @@ -198,10 +210,12 @@ github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.1 h1:hcHX2n5pU github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.1/go.mod h1:jZwTg3wU4/UxgNJ7TKlFZ3dTIlnfvppnW8kJTc4UXy8= github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.9.1 h1:6kEct2wo84DlHkKJTNcUQbKAwARh/ugOTDR3x5mYCWc= github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.9.1/go.mod h1:dScCMWYbsf3B+c6a/5CFoVFcYLqHHkEEc622cHFtGGY= +github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.0 h1:u0PjbKDuIVOMm9hyxLeqSM51ExtJXJ+TdSJT5hDW6wk= +github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.11.0/go.mod h1:QCrAW/Rmf+styT25ke8cUV6hDHpdKNmAY14kkJ3+Fd8= github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2 h1:s2iag/Gc4tuQH7x5I0n4mQWVhpfl/cj+SVNAFAB5ck0= github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.2.2/go.mod h1:DFEamKVoOjm/rjMwzfZK0Zg/hwsSkXOibdA4HcC6swk= -github.com/stackitcloud/stackit-sdk-go/services/ske v1.2.0 h1:rNlTSWShnlkW4vbBuJ3a1NPwQfN5H1+mpdjngLqFRdo= -github.com/stackitcloud/stackit-sdk-go/services/ske v1.2.0/go.mod h1:UPPntEOhriZ4dZXEkjtfkGLFKvfA7Q/JAPG/zfwcoyc= +github.com/stackitcloud/stackit-sdk-go/services/ske v1.3.0 h1:hPCpRcWEzwzGONZJsKH+j2TjN1LRTH7Tp/q0TyzmL5M= +github.com/stackitcloud/stackit-sdk-go/services/ske v1.3.0/go.mod h1:jDYRbagjOwKEVsvkxdUErXIvvTOLw9WdBVjaXr5YOD8= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1 h1:r5808lRhtY8l5anft/UwgJEaef1XsoYObmwd3FVai48= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.3.1/go.mod h1:+LYy2pB+tpF0lkkmCf524wvv2Sa49REgEaNh7JGzN6Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -226,29 +240,35 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6 github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -267,16 +287,21 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -287,14 +312,18 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/stackit/internal/services/kms/key-ring/datasource.go b/stackit/internal/services/kms/key-ring/datasource.go index 92b0aa72e..ee8242e07 100644 --- a/stackit/internal/services/kms/key-ring/datasource.go +++ b/stackit/internal/services/kms/key-ring/datasource.go @@ -142,7 +142,7 @@ func (k *keyRingDataSource) Read(ctx context.Context, request datasource.ReadReq err = mapFields(keyRingResponse, &model, region) if err != nil { - core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Processing API payload %v", err)) + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key ring", fmt.Sprintf("Processing API payload: %v", err)) return } diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go index a433ea01a..5e5dbe58f 100644 --- a/stackit/internal/services/kms/key-ring/resource.go +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/stackit-sdk-go/services/kms/wait" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" @@ -21,6 +22,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" "net/http" "strings" + "time" ) var ( @@ -168,7 +170,13 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq keyRingId := *createResponse.Id ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) - err = mapFields(createResponse, &model, region) + waitResp, err := wait.CreateKeyRingWaitHandler(ctx, k.client, projectId, region, keyRingId).SetSleepBeforeWait(5 * time.Second).WaitWithContext(ctx) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Key Ring creation waiting: %v", err)) + return + } + + err = mapFields(waitResp, &model, region) if err != nil { core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key ring", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/kms/key-ring/resource_test.go b/stackit/internal/services/kms/key-ring/resource_test.go new file mode 100644 index 000000000..550546046 --- /dev/null +++ b/stackit/internal/services/kms/key-ring/resource_test.go @@ -0,0 +1,171 @@ +package kms + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "testing" +) + +func TestMapFields(t *testing.T) { + const testRegion = "eu01" + tests := []struct { + description string + state Model + input *kms.KeyRing + expected Model + isValid bool + }{ + { + "default values", + Model{ + KeyRingId: types.StringValue("krid"), + ProjectId: types.StringValue("pid"), + }, + &kms.KeyRing{ + Id: utils.Ptr("krid"), + }, + Model{ + Description: types.StringNull(), + DisplayName: types.StringNull(), + KeyRingId: types.StringValue("krid"), + Id: types.StringValue("pid,krid"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), + }, + true, + }, + { + "values_ok", + Model{ + KeyRingId: types.StringValue("krid"), + ProjectId: types.StringValue("pid"), + }, + &kms.KeyRing{ + Description: utils.Ptr("descr"), + DisplayName: utils.Ptr("name"), + Id: utils.Ptr("krid"), + }, + Model{ + Description: types.StringValue("descr"), + DisplayName: types.StringValue("name"), + KeyRingId: types.StringValue("krid"), + Id: types.StringValue("pid,krid"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), + }, + true, + }, + { + "nil_response_field", + Model{}, + &kms.KeyRing{ + Id: nil, + }, + Model{}, + false, + }, + { + "nil_response", + Model{}, + nil, + Model{}, + false, + }, + { + "no_resource_id", + Model{ + Region: types.StringValue(testRegion), + ProjectId: types.StringValue("pid"), + }, + &kms.KeyRing{}, + Model{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ProjectId: tt.expected.ProjectId, + KeyRingId: tt.expected.KeyRingId, + } + err := mapFields(tt.input, state, testRegion) + 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(state, &tt.expected) + if diff != "" { + fmt.Println("state: ", state, " expected: ", tt.expected) + t.Fatalf("Data does not match") + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + input *Model + expected *kms.CreateKeyRingPayload + isValid bool + }{ + { + "default_values", + &Model{}, + &kms.CreateKeyRingPayload{}, + true, + }, + { + "simple_values", + &Model{ + DisplayName: types.StringValue("name"), + }, + &kms.CreateKeyRingPayload{ + DisplayName: utils.Ptr("name"), + }, + true, + }, + { + "null_fields", + &Model{ + DisplayName: types.StringValue(""), + Description: types.StringValue(""), + }, + &kms.CreateKeyRingPayload{ + DisplayName: utils.Ptr(""), + Description: utils.Ptr(""), + }, + true, + }, + { + "nil_model", + nil, + nil, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output, err := toCreatePayload(tt.input) + 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(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/kms/key/datasource.go b/stackit/internal/services/kms/key/datasource.go new file mode 100644 index 000000000..de02a70a1 --- /dev/null +++ b/stackit/internal/services/kms/key/datasource.go @@ -0,0 +1,194 @@ +package kms + +import ( + "context" + "fmt" + "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-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" +) + +var ( + _ datasource.DataSource = &keyDataSource{} +) + +func NewKeyDataSource() datasource.DataSource { + return &keyDataSource{} +} + +type keyDataSource struct { + client *kms.APIClient + providerData core.ProviderData +} + +func (k *keyDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_kms_key" +} + +func (k *keyDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + var ok bool + k.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + + apiClient := kmsUtils.ConfigureClient(ctx, &k.providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + k.client = apiClient + tflog.Info(ctx, "Key configured") +} + +func (k *keyDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", + "backend": "The backend that is used for KMS. Right now, only software is accepted.", + "algorithm": "The encryption algorithm that the key will use to encrypt data", + "description": "A user chosen description to distinguish multiple keys", + "display_name": "The display name to distinguish multiple keys", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "import_only": "Specifies if the the key should be import_only", + "key_ring_id": "The ID of the associated key ring", + "purpose": "The purpose for which the key will be used", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "algorithm": schema.StringAttribute{ + Description: descriptions["algorithm"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "backend": schema.StringAttribute{ + Description: descriptions["backend"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["display_name"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + }, + "import_only": schema.BoolAttribute{ + Description: descriptions["id"], + Computed: false, + Required: true, + }, + "key_id": schema.StringAttribute{ + Description: descriptions["key_id"], + Computed: false, + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "purpose": schema.StringAttribute{ + Description: descriptions["purpose"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + }, + }, + } +} + +func (k *keyDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + var model Model + diags := request.Config.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + keyId := model.KeyId.ValueString() + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "key_id", keyId) + + keyResponse, err := k.client.GetKey(ctx, projectId, region, keyRingId, keyId).Execute() + if err != nil { + utils.LogError( + ctx, + &response.Diagnostics, + err, + "Reading key", + fmt.Sprintf("Key with ID %q does not exist in project %q.", keyId, projectId), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + response.State.RemoveResource(ctx) + return + } + + err = mapFields(keyResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key read") +} diff --git a/stackit/internal/services/kms/key/resource.go b/stackit/internal/services/kms/key/resource.go new file mode 100644 index 000000000..b9eb5309d --- /dev/null +++ b/stackit/internal/services/kms/key/resource.go @@ -0,0 +1,361 @@ +package kms + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" + "strings" +) + +var ( + _ resource.Resource = &keyResource{} + _ resource.ResourceWithConfigure = &keyResource{} + _ resource.ResourceWithImportState = &keyResource{} +) + +type Model struct { + Algorithm types.String `tfsdk:"algorithm"` + Backend types.String `tfsdk:"backend"` + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + Id types.String `tfsdk:"id"` // needed by TF + ImportOnly types.Bool `tfsdk:"import_only"` + KeyId types.String `tfsdk:"key_id"` + KeyRingId types.String `tfsdk:"key_ring_id"` + Purpose types.String `tfsdk:"purpose"` + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` +} + +func NewKeyResource() resource.Resource { + return &keyResource{} +} + +type keyResource struct { + client *kms.APIClient + providerData core.ProviderData +} + +func (k *keyResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_kms_key" +} + +func (k *keyResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + var ok bool + k.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + apiClient := kmsUtils.ConfigureClient(ctx, &k.providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + k.client = apiClient +} + +func (k *keyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", + "backend": "The backend that is used for KMS. Right now, only software is accepted.", + "algorithm": "The encryption algorithm that the key will use to encrypt data", + "description": "A user chosen description to distinguish multiple keys", + "display_name": "The display name to distinguish multiple keys", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "import_only": "Specifies if the the key should be import_only", + "key_id": "The ID of the key", + "key_ring_id": "The ID of the associated key ring", + "purpose": "The purpose for which the key will be used", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "algorithm": schema.StringAttribute{ + Description: descriptions["algorithm"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "backend": schema.StringAttribute{ + Description: descriptions["backend"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["display_name"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "import_only": schema.BoolAttribute{ + Description: descriptions["id"], + Computed: false, + Required: true, + }, + "key_id": schema.StringAttribute{ + Description: descriptions["key_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "purpose": schema.StringAttribute{ + Description: descriptions["purpose"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (k *keyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var model Model + diags := request.Plan.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + keyRingId := model.KeyRingId.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key", fmt.Sprintf("Creating API payload: %v", err)) + return + } + createResponse, err := k.client.CreateKey(ctx, projectId, region, keyRingId).CreateKeyPayload(*payload).Execute() + + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key", fmt.Sprintf("Calling API: %v", err)) + return + } + + keyId := *createResponse.Id + ctx = tflog.SetField(ctx, "key_id", keyId) + + err = mapFields(createResponse, &model, region) + + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key created") +} + +func (k *keyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + keyId := model.KeyId.ValueString() + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "key_id", keyId) + + keyResponse, err := k.client.GetKey(ctx, projectId, region, keyRingId, keyId).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 { + response.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(keyResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading key", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key read") +} + +func (k *keyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + // keys cannot be updated, so we log an error. + core.LogAndAddError(ctx, &response.Diagnostics, "Error updating key", "Keys can't be updated") +} + +func (k *keyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := k.providerData.GetRegionWithOverride(model.Region) + keyId := model.KeyId.ValueString() + + err := k.client.DeleteKey(ctx, projectId, region, keyRingId, keyId).Execute() + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error deleting key", fmt.Sprintf("Calling API: %v", err)) + } + + tflog.Info(ctx, "key deleted") +} + +func (k *keyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.Split(request.ID, core.Separator) + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + core.LogAndAddError(ctx, &response.Diagnostics, + "Error importing key", + fmt.Sprintf("Exptected import identifier with format: [proejct_id],[instance_id], got :%q", request.ID), + ) + return + } + + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("key_id"), idParts[1])...) + tflog.Info(ctx, "key state imported") +} + +func mapFields(key *kms.Key, model *Model, region string) error { + if key == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var keyId string + if model.KeyId.ValueString() != "" { + keyId = model.KeyId.ValueString() + } else if key.Id != nil { + keyId = *key.Id + } else { + return fmt.Errorf("key id not present") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), keyId) + model.KeyId = types.StringValue(keyId) + model.DisplayName = types.StringPointerValue(key.DisplayName) + model.Description = types.StringPointerValue(key.Description) + model.Region = types.StringValue(region) + + return nil +} + +func toCreatePayload(model *Model) (*kms.CreateKeyPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + return &kms.CreateKeyPayload{ + Algorithm: kms.CreateKeyPayloadGetAlgorithmAttributeType(conversion.StringValueToPointer(model.Algorithm)), + Backend: kms.CreateKeyPayloadGetBackendAttributeType(conversion.StringValueToPointer(model.Backend)), + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + ImportOnly: conversion.BoolValueToPointer(model.ImportOnly), + Purpose: kms.CreateKeyPayloadGetPurposeAttributeType(conversion.StringValueToPointer(model.Purpose)), + }, nil +} diff --git a/stackit/internal/services/kms/key/resource_test.go b/stackit/internal/services/kms/key/resource_test.go new file mode 100644 index 000000000..ae9b88695 --- /dev/null +++ b/stackit/internal/services/kms/key/resource_test.go @@ -0,0 +1,175 @@ +package kms + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "testing" +) + +func TestMapFields(t *testing.T) { + const testRegion = "eu01" + tests := []struct { + description string + state Model + input *kms.Key + expected Model + isValid bool + }{ + { + "default values", + Model{ + KeyId: types.StringValue("kid"), + KeyRingId: types.StringValue("krid"), + ProjectId: types.StringValue("pid"), + }, + &kms.Key{ + Id: utils.Ptr("kid"), + }, + Model{ + Description: types.StringNull(), + DisplayName: types.StringNull(), + KeyRingId: types.StringValue("krid"), + KeyId: types.StringValue("kid"), + Id: types.StringValue("pid,kid"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), + }, + true, + }, + { + "values_ok", + Model{ + KeyId: types.StringValue("kid"), + KeyRingId: types.StringValue("krid"), + ProjectId: types.StringValue("pid"), + }, + &kms.Key{ + Description: utils.Ptr("descr"), + DisplayName: utils.Ptr("name"), + Id: utils.Ptr("kid"), + }, + Model{ + Description: types.StringValue("descr"), + DisplayName: types.StringValue("name"), + KeyId: types.StringValue("kid"), + KeyRingId: types.StringValue("krid"), + Id: types.StringValue("pid,kid"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), + }, + true, + }, + { + "nil_response_field", + Model{}, + &kms.Key{ + Id: nil, + }, + Model{}, + false, + }, + { + "nil_response", + Model{}, + nil, + Model{}, + false, + }, + { + "no_resource_id", + Model{ + Region: types.StringValue(testRegion), + ProjectId: types.StringValue("pid"), + }, + &kms.Key{}, + Model{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ProjectId: tt.expected.ProjectId, + KeyRingId: tt.expected.KeyRingId, + } + err := mapFields(tt.input, state, testRegion) + 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(state, &tt.expected) + if diff != "" { + fmt.Println("state: ", state, " expected: ", tt.expected) + t.Fatalf("Data does not match") + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + input *Model + expected *kms.CreateKeyPayload + isValid bool + }{ + { + "default_values", + &Model{}, + &kms.CreateKeyPayload{}, + true, + }, + { + "simple_values", + &Model{ + DisplayName: types.StringValue("name"), + }, + &kms.CreateKeyPayload{ + DisplayName: utils.Ptr("name"), + }, + true, + }, + { + "null_fields", + &Model{ + DisplayName: types.StringValue(""), + Description: types.StringValue(""), + }, + &kms.CreateKeyPayload{ + DisplayName: utils.Ptr(""), + Description: utils.Ptr(""), + }, + true, + }, + { + "nil_model", + nil, + nil, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output, err := toCreatePayload(tt.input) + 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(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/kms/wrapping-key/datasource.go b/stackit/internal/services/kms/wrapping-key/datasource.go new file mode 100644 index 000000000..476d86a2b --- /dev/null +++ b/stackit/internal/services/kms/wrapping-key/datasource.go @@ -0,0 +1,189 @@ +package kms + +import ( + "context" + "fmt" + "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-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" +) + +var ( + _ datasource.DataSource = &wrappingKeyDataSource{} +) + +func NewWrappingKeyDataSource() datasource.DataSource { + return &wrappingKeyDataSource{} +} + +type wrappingKeyDataSource struct { + client *kms.APIClient + providerData core.ProviderData +} + +func (w *wrappingKeyDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_kms_wrapping_key" +} + +func (w *wrappingKeyDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + var ok bool + w.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + + apiClient := kmsUtils.ConfigureClient(ctx, &w.providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + + w.client = apiClient + tflog.Info(ctx, "Wrapping key configured") +} + +func (w *wrappingKeyDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", + "backend": "The backend that is used for KMS. Right now, only software is accepted.", + "algorithm": "The encryption algorithm that the key will use to encrypt data", + "description": "A user chosen description to distinguish multiple keys", + "display_name": "The display name to distinguish multiple keys", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "import_only": "Specifies if the the key should be import_only", + "key_ring_id": "The ID of the associated key ring", + "purpose": "The purpose for which the key will be used", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "algorithm": schema.StringAttribute{ + Description: descriptions["algorithm"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "backend": schema.StringAttribute{ + Description: descriptions["backend"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["display_name"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "purpose": schema.StringAttribute{ + Description: descriptions["purpose"], + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + }, + "wrapping_key_id": schema.StringAttribute{ + Description: descriptions["wrapping_key_id"], + Computed: false, + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + }, + } +} + +func (w *wrappingKeyDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + var model Model + diags := request.Config.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := w.providerData.GetRegionWithOverride(model.Region) + wrappingKeyId := model.WrappingKeyId.ValueString() + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "wrapping_key_id", wrappingKeyId) + + wrappingKeyResponse, err := w.client.GetWrappingKey(ctx, projectId, region, keyRingId, wrappingKeyId).Execute() + if err != nil { + utils.LogError( + ctx, + &response.Diagnostics, + err, + "Reading wrapping key", + fmt.Sprintf("Wrapping key with ID %q does not exist in project %q.", wrappingKeyId, projectId), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + response.State.RemoveResource(ctx) + return + } + + err = mapFields(wrappingKeyResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading wrapping key", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key read") +} diff --git a/stackit/internal/services/kms/wrapping-key/resource.go b/stackit/internal/services/kms/wrapping-key/resource.go new file mode 100644 index 000000000..713728cd4 --- /dev/null +++ b/stackit/internal/services/kms/wrapping-key/resource.go @@ -0,0 +1,355 @@ +package kms + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" + "net/http" + "strings" +) + +var ( + _ resource.Resource = &wrappingKeyResource{} + _ resource.ResourceWithConfigure = &wrappingKeyResource{} + _ resource.ResourceWithImportState = &wrappingKeyResource{} +) + +type Model struct { + Algorithm types.String `tfsdk:"algorithm"` + Backend types.String `tfsdk:"backend"` + Description types.String `tfsdk:"description"` + DisplayName types.String `tfsdk:"display_name"` + Id types.String `tfsdk:"id"` // needed by TF + KeyRingId types.String `tfsdk:"key_ring_id"` + Purpose types.String `tfsdk:"purpose"` + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + WrappingKeyId types.String `tfsdk:"wrapping_key_id"` +} + +func NewWrappingKeyResource() resource.Resource { + return &wrappingKeyResource{} +} + +type wrappingKeyResource struct { + client *kms.APIClient + providerData core.ProviderData +} + +func (w *wrappingKeyResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_kms_wrapping_key" +} + +func (w *wrappingKeyResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + var ok bool + w.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics) + if !ok { + return + } + apiClient := kmsUtils.ConfigureClient(ctx, &w.providerData, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } + w.client = apiClient +} + +func (w *wrappingKeyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + descriptions := map[string]string{ + "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", + "algorithm": "The encryption algorithm that the key will use to encrypt data", + "backend": "The backend that is used for KMS. Right now, only software is accepted.", + "description": "A user chosen description to distinguish multiple keys", + "display_name": "The display name to distinguish multiple keys", + "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".", + "key_ring_id": "The ID of the associated key ring", + "purpose": "The purpose for which the key will be used", + "project_id": "STACKIT project ID to which the key ring is associated.", + "region_id": "The STACKIT region name the key ring is located in.", + "wrapping_key_id": "The ID of the wrapping key", + } + + response.Schema = schema.Schema{ + Description: descriptions["main"], + Attributes: map[string]schema.Attribute{ + "algorithm": schema.StringAttribute{ + Description: descriptions["algorithm"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "backend": schema.StringAttribute{ + Description: descriptions["backend"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["description"], + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "display_name": schema.StringAttribute{ + Description: descriptions["display_name"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "key_ring_id": schema.StringAttribute{ + Description: descriptions["key_ring_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "purpose": schema.StringAttribute{ + Description: descriptions["purpose"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The resource region. If not defined, the provider region is used.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "wrapping_key_id": schema.StringAttribute{ + Description: descriptions["wrapping_key_id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + }, + } +} + +func (w *wrappingKeyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var model Model + + diags := request.Plan.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + region := w.providerData.GetRegionWithOverride(model.Region) + keyRingId := model.KeyRingId.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + + payload, err := toCreatePayload(&model) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating wrapping key", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + createResponse, err := w.client.CreateWrappingKey(ctx, projectId, region, keyRingId).CreateWrappingKeyPayload(*payload).Execute() + + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating wrapping key", fmt.Sprintf("Calling API: %v", err)) + return + } + + wrappingKeyId := *createResponse.Id + ctx = tflog.SetField(ctx, "wrapping_key_id", wrappingKeyId) + + err = mapFields(createResponse, &model, region) + + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Key created") +} + +func (w *wrappingKeyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := w.providerData.GetRegionWithOverride(model.Region) + wrappingKeyId := model.WrappingKeyId.ValueString() + + ctx = tflog.SetField(ctx, "key_ring_id", keyRingId) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "wrapping_key_id", wrappingKeyId) + + wrappingKeyResponse, err := w.client.GetWrappingKey(ctx, projectId, region, keyRingId, wrappingKeyId).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 { + response.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading wrapping key", fmt.Sprintf("Calling API: %v", err)) + return + } + + err = mapFields(wrappingKeyResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error reading wrapping key", fmt.Sprintf("Processing API payload: %v", err)) + return + } + diags = response.State.Set(ctx, model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Wrapping key read") +} + +func (w *wrappingKeyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + // wrapping keys cannot be updated, so we log an error. + core.LogAndAddError(ctx, &response.Diagnostics, "Error updating wrapping key", "Keys can't be updated") +} + +func (w *wrappingKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var model Model + diags := request.State.Get(ctx, &model) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + keyRingId := model.KeyRingId.ValueString() + region := w.providerData.GetRegionWithOverride(model.Region) + wrappingKeyId := model.WrappingKeyId.ValueString() + + err := w.client.DeleteWrappingKey(ctx, projectId, region, keyRingId, wrappingKeyId).Execute() + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error deleting wrapping key", fmt.Sprintf("Calling API: %v", err)) + } + + tflog.Info(ctx, "wrapping key deleted") +} + +func (w *wrappingKeyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.Split(request.ID, core.Separator) + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + core.LogAndAddError(ctx, &response.Diagnostics, + "Error importing wrapping key", + fmt.Sprintf("Exptected import identifier with format: [proejct_id],[instance_id], got :%q", request.ID), + ) + return + } + + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("wrapping_key_id"), idParts[1])...) + tflog.Info(ctx, "wrapping key state imported") +} + +func mapFields(wrappingKey *kms.WrappingKey, model *Model, region string) error { + if wrappingKey == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var wrappingKeyId string + if model.WrappingKeyId.ValueString() != "" { + wrappingKeyId = model.WrappingKeyId.ValueString() + } else if wrappingKey.Id != nil { + wrappingKeyId = *wrappingKey.Id + } else { + return fmt.Errorf("key id not present") + } + + model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), wrappingKeyId) + model.WrappingKeyId = types.StringValue(wrappingKeyId) + model.DisplayName = types.StringPointerValue(wrappingKey.DisplayName) + model.Description = types.StringPointerValue(wrappingKey.Description) + model.Region = types.StringValue(region) + + return nil +} + +func toCreatePayload(model *Model) (*kms.CreateWrappingKeyPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + return &kms.CreateWrappingKeyPayload{ + Algorithm: kms.CreateWrappingKeyPayloadGetAlgorithmAttributeType(conversion.StringValueToPointer(model.Algorithm)), + Backend: kms.CreateKeyPayloadGetBackendAttributeType(conversion.StringValueToPointer(model.Backend)), + Description: conversion.StringValueToPointer(model.Description), + DisplayName: conversion.StringValueToPointer(model.DisplayName), + Purpose: kms.CreateWrappingKeyPayloadGetPurposeAttributeType(conversion.StringValueToPointer(model.Purpose)), + }, nil +} diff --git a/stackit/internal/services/kms/wrapping-key/resource_test.go b/stackit/internal/services/kms/wrapping-key/resource_test.go new file mode 100644 index 000000000..973ce903b --- /dev/null +++ b/stackit/internal/services/kms/wrapping-key/resource_test.go @@ -0,0 +1,175 @@ +package kms + +import ( + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/kms" + "testing" +) + +func TestMapFields(t *testing.T) { + const testRegion = "eu01" + tests := []struct { + description string + state Model + input *kms.WrappingKey + expected Model + isValid bool + }{ + { + "default values", + Model{ + KeyRingId: types.StringValue("krid"), + ProjectId: types.StringValue("pid"), + WrappingKeyId: types.StringValue("wid"), + }, + &kms.WrappingKey{ + Id: utils.Ptr("wid"), + }, + Model{ + Description: types.StringNull(), + DisplayName: types.StringNull(), + KeyRingId: types.StringValue("krid"), + Id: types.StringValue("pid,wid"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), + WrappingKeyId: types.StringValue("wid"), + }, + true, + }, + { + "values_ok", + Model{ + KeyRingId: types.StringValue("krid"), + ProjectId: types.StringValue("pid"), + WrappingKeyId: types.StringValue("wid"), + }, + &kms.WrappingKey{ + Description: utils.Ptr("descr"), + DisplayName: utils.Ptr("name"), + Id: utils.Ptr("wid"), + }, + Model{ + Description: types.StringValue("descr"), + DisplayName: types.StringValue("name"), + KeyRingId: types.StringValue("krid"), + Id: types.StringValue("pid,wid"), + ProjectId: types.StringValue("pid"), + Region: types.StringValue(testRegion), + WrappingKeyId: types.StringValue("wid"), + }, + true, + }, + { + "nil_response_field", + Model{}, + &kms.WrappingKey{ + Id: nil, + }, + Model{}, + false, + }, + { + "nil_response", + Model{}, + nil, + Model{}, + false, + }, + { + "no_resource_id", + Model{ + Region: types.StringValue(testRegion), + ProjectId: types.StringValue("pid"), + }, + &kms.WrappingKey{}, + Model{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + state := &Model{ + ProjectId: tt.expected.ProjectId, + KeyRingId: tt.expected.KeyRingId, + } + err := mapFields(tt.input, state, testRegion) + 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(state, &tt.expected) + if diff != "" { + fmt.Println("state: ", state, " expected: ", tt.expected) + t.Fatalf("Data does not match") + } + } + }) + } +} + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + description string + input *Model + expected *kms.CreateWrappingKeyPayload + isValid bool + }{ + { + "default_values", + &Model{}, + &kms.CreateWrappingKeyPayload{}, + true, + }, + { + "simple_values", + &Model{ + DisplayName: types.StringValue("name"), + }, + &kms.CreateWrappingKeyPayload{ + DisplayName: utils.Ptr("name"), + }, + true, + }, + { + "null_fields", + &Model{ + DisplayName: types.StringValue(""), + Description: types.StringValue(""), + }, + &kms.CreateWrappingKeyPayload{ + DisplayName: utils.Ptr(""), + Description: utils.Ptr(""), + }, + true, + }, + { + "nil_model", + nil, + nil, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output, err := toCreatePayload(tt.input) + 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(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/provider.go b/stackit/provider.go index 103eaf68d..cd39788ef 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -45,7 +45,9 @@ import ( iaasalphaRoutingTableRoutes "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/routes" iaasalphaRoutingTable "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/table" iaasalphaRoutingTables "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/routingtable/tables" - kms "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key-ring" + kmsKey "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key" + kmsKeyRing "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/key-ring" + kmsWrappingKey "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/wrapping-key" loadBalancer "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/loadbalancer/loadbalancer" 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" @@ -479,7 +481,9 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource iaasalphaRoutingTables.NewRoutingTablesDataSource, iaasalphaRoutingTableRoutes.NewRoutingTableRoutesDataSource, iaasSecurityGroupRule.NewSecurityGroupRuleDataSource, - kms.NewKeyRingDataSource, + kmsKey.NewKeyDataSource, + kmsKeyRing.NewKeyRingDataSource, + kmsWrappingKey.NewWrappingKeyDataSource, loadBalancer.NewLoadBalancerDataSource, logMeInstance.NewInstanceDataSource, logMeCredential.NewCredentialDataSource, @@ -543,7 +547,9 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { iaasSecurityGroupRule.NewSecurityGroupRuleResource, iaasalphaRoutingTable.NewRoutingTableResource, iaasalphaRoutingTableRoute.NewRoutingTableRouteResource, - kms.NewKeyRingResource, + kmsKey.NewKeyResource, + kmsKeyRing.NewKeyRingResource, + kmsWrappingKey.NewWrappingKeyResource, loadBalancer.NewLoadBalancerResource, loadBalancerObservabilityCredential.NewObservabilityCredentialResource, logMeInstance.NewInstanceResource, From 248748e82e364d020e73ad2c308f895e3c471dfc Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 8 Aug 2025 13:15:44 +0200 Subject: [PATCH 10/13] add missing examples and docs --- docs/data-sources/kms_key.md | 36 ++++++++++++++ docs/data-sources/kms_key_ring.md | 31 ++++++++++++ docs/data-sources/kms_wrapping_key.md | 35 +++++++++++++ docs/resources/kms_key.md | 49 +++++++++++++++++++ docs/resources/kms_key_ring.md | 40 +++++++++++++++ docs/resources/kms_wrapping_key.md | 47 ++++++++++++++++++ .../resources/stackit_kms_key/resource.tf | 10 ++++ .../stackit_kms_wrapping_key/resource.tf | 9 ++++ 8 files changed, 257 insertions(+) create mode 100644 docs/data-sources/kms_key.md create mode 100644 docs/data-sources/kms_key_ring.md create mode 100644 docs/data-sources/kms_wrapping_key.md create mode 100644 docs/resources/kms_key.md create mode 100644 docs/resources/kms_key_ring.md create mode 100644 docs/resources/kms_wrapping_key.md create mode 100644 examples/resources/stackit_kms_key/resource.tf create mode 100644 examples/resources/stackit_kms_wrapping_key/resource.tf diff --git a/docs/data-sources/kms_key.md b/docs/data-sources/kms_key.md new file mode 100644 index 000000000..be4e35778 --- /dev/null +++ b/docs/data-sources/kms_key.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_kms_key Data Source - stackit" +subcategory: "" +description: |- + KMS Key resource schema. Must have a region specified in the provider configuration. +--- + +# stackit_kms_key (Data Source) + +KMS Key resource schema. Must have a `region` specified in the provider configuration. + + + + +## Schema + +### Required + +- `algorithm` (String) The encryption algorithm that the key will use to encrypt data +- `backend` (String) The backend that is used for KMS. Right now, only software is accepted. +- `display_name` (String) The display name to distinguish multiple keys +- `import_only` (Boolean) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `key_id` (String) +- `key_ring_id` (String) The ID of the associated key ring +- `project_id` (String) STACKIT project ID to which the key ring is associated. +- `purpose` (String) The purpose for which the key will be used + +### Optional + +- `description` (String) A user chosen description to distinguish multiple keys +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". diff --git a/docs/data-sources/kms_key_ring.md b/docs/data-sources/kms_key_ring.md new file mode 100644 index 000000000..55f05f207 --- /dev/null +++ b/docs/data-sources/kms_key_ring.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_kms_key_ring Data Source - stackit" +subcategory: "" +description: |- + KMS Key Ring resource schema. +--- + +# stackit_kms_key_ring (Data Source) + +KMS Key Ring resource schema. + + + + +## Schema + +### Required + +- `display_name` (String) A user chosen description to distinguish multiple key rings. +- `key_ring_id` (String) An auto generated unique id which identifies the key ring. +- `project_id` (String) STACKIT project ID to which the key ring is associated. + +### Optional + +- `description` (String) A user chosen description to distinguish multiple key rings. +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". diff --git a/docs/data-sources/kms_wrapping_key.md b/docs/data-sources/kms_wrapping_key.md new file mode 100644 index 000000000..ea2952a3f --- /dev/null +++ b/docs/data-sources/kms_wrapping_key.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_kms_wrapping_key Data Source - stackit" +subcategory: "" +description: |- + KMS Key resource schema. Must have a region specified in the provider configuration. +--- + +# stackit_kms_wrapping_key (Data Source) + +KMS Key resource schema. Must have a `region` specified in the provider configuration. + + + + +## Schema + +### Required + +- `algorithm` (String) The encryption algorithm that the key will use to encrypt data +- `backend` (String) The backend that is used for KMS. Right now, only software is accepted. +- `display_name` (String) The display name to distinguish multiple keys +- `key_ring_id` (String) The ID of the associated key ring +- `project_id` (String) STACKIT project ID to which the key ring is associated. +- `purpose` (String) The purpose for which the key will be used +- `wrapping_key_id` (String) + +### Optional + +- `description` (String) A user chosen description to distinguish multiple keys +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". diff --git a/docs/resources/kms_key.md b/docs/resources/kms_key.md new file mode 100644 index 000000000..6d96c0e07 --- /dev/null +++ b/docs/resources/kms_key.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_kms_key Resource - stackit" +subcategory: "" +description: |- + KMS Key resource schema. Must have a region specified in the provider configuration. +--- + +# stackit_kms_key (Resource) + +KMS Key resource schema. Must have a `region` specified in the provider configuration. + +## Example Usage + +```terraform +resource "stackit_kms_key" "name" { + algorithm = "example algorithm" + backend = "software" + description = "new descr" + display_name = "example name" + import_only = false + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" +} +``` + + +## Schema + +### Required + +- `algorithm` (String) The encryption algorithm that the key will use to encrypt data +- `backend` (String) The backend that is used for KMS. Right now, only software is accepted. +- `display_name` (String) The display name to distinguish multiple keys +- `import_only` (Boolean) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `key_ring_id` (String) The ID of the associated key ring +- `project_id` (String) STACKIT project ID to which the key ring is associated. +- `purpose` (String) The purpose for which the key will be used + +### Optional + +- `description` (String) A user chosen description to distinguish multiple keys +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `key_id` (String) The ID of the key diff --git a/docs/resources/kms_key_ring.md b/docs/resources/kms_key_ring.md new file mode 100644 index 000000000..d616f78f1 --- /dev/null +++ b/docs/resources/kms_key_ring.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_kms_key_ring Resource - stackit" +subcategory: "" +description: |- + KMS Key Ring resource schema. Must have a region specified in the provider configuration. +--- + +# stackit_kms_key_ring (Resource) + +KMS Key Ring resource schema. Must have a `region` specified in the provider configuration. + +## Example Usage + +```terraform +resource "stackit_kms_key_ring" "example" { + description = "example description" + display_name = "example name" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region_id = "eu01" +} +``` + + +## Schema + +### Required + +- `display_name` (String) A user chosen description to distinguish multiple key rings. +- `project_id` (String) STACKIT project ID to which the key ring is associated. + +### Optional + +- `description` (String) A user chosen description to distinguish multiple key rings. +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `key_ring_id` (String) An auto generated unique id which identifies the key ring. diff --git a/docs/resources/kms_wrapping_key.md b/docs/resources/kms_wrapping_key.md new file mode 100644 index 000000000..0dfc86988 --- /dev/null +++ b/docs/resources/kms_wrapping_key.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_kms_wrapping_key Resource - stackit" +subcategory: "" +description: |- + KMS Key resource schema. Must have a region specified in the provider configuration. +--- + +# stackit_kms_wrapping_key (Resource) + +KMS Key resource schema. Must have a `region` specified in the provider configuration. + +## Example Usage + +```terraform +resource "stackit_kms_wrapping_key" "name" { + algorithm = "example algorithm" + backend = "software" + description = "new descr" + display_name = "example name" + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" +} +``` + + +## Schema + +### Required + +- `algorithm` (String) The encryption algorithm that the key will use to encrypt data +- `backend` (String) The backend that is used for KMS. Right now, only software is accepted. +- `display_name` (String) The display name to distinguish multiple keys +- `key_ring_id` (String) The ID of the associated key ring +- `project_id` (String) STACKIT project ID to which the key ring is associated. +- `purpose` (String) The purpose for which the key will be used + +### Optional + +- `description` (String) A user chosen description to distinguish multiple keys +- `region` (String) The resource region. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `wrapping_key_id` (String) The ID of the wrapping key diff --git a/examples/resources/stackit_kms_key/resource.tf b/examples/resources/stackit_kms_key/resource.tf new file mode 100644 index 000000000..0044889d3 --- /dev/null +++ b/examples/resources/stackit_kms_key/resource.tf @@ -0,0 +1,10 @@ +resource "stackit_kms_key" "name" { + algorithm = "example algorithm" + backend = "software" + description = "new descr" + display_name = "example name" + import_only = false + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" +} \ No newline at end of file diff --git a/examples/resources/stackit_kms_wrapping_key/resource.tf b/examples/resources/stackit_kms_wrapping_key/resource.tf new file mode 100644 index 000000000..fcbd20eb4 --- /dev/null +++ b/examples/resources/stackit_kms_wrapping_key/resource.tf @@ -0,0 +1,9 @@ +resource "stackit_kms_wrapping_key" "name" { + algorithm = "example algorithm" + backend = "software" + description = "new descr" + display_name = "example name" + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" +} \ No newline at end of file From 77a623e2ce104943e96facd916337be32ce898d1 Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 8 Aug 2025 14:52:16 +0200 Subject: [PATCH 11/13] fix linter findings --- .../resources/stackit_kms_key/resource.tf | 16 ++++++------- .../stackit_kms_key_ring/resource.tf | 8 +++---- .../stackit_kms_wrapping_key/resource.tf | 14 +++++------ go.sum | 23 +------------------ .../services/kms/key-ring/datasource.go | 9 ++++---- .../services/kms/key-ring/resource.go | 19 +++++++-------- .../services/kms/key-ring/resource_test.go | 3 ++- .../internal/services/kms/key/datasource.go | 9 ++++---- stackit/internal/services/kms/key/resource.go | 21 ++++++++++------- .../services/kms/key/resource_test.go | 3 ++- stackit/internal/services/kms/utils/util.go | 5 ++-- .../services/kms/wrapping-key/datasource.go | 9 ++++---- .../services/kms/wrapping-key/resource.go | 21 ++++++++++------- .../kms/wrapping-key/resource_test.go | 3 ++- 14 files changed, 79 insertions(+), 84 deletions(-) diff --git a/examples/resources/stackit_kms_key/resource.tf b/examples/resources/stackit_kms_key/resource.tf index 0044889d3..1431c9045 100644 --- a/examples/resources/stackit_kms_key/resource.tf +++ b/examples/resources/stackit_kms_key/resource.tf @@ -1,10 +1,10 @@ resource "stackit_kms_key" "name" { - algorithm = "example algorithm" - backend = "software" - description = "new descr" + algorithm = "example algorithm" + backend = "software" + description = "new descr" display_name = "example name" - import_only = false - key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - purpose = "example purpose" -} \ No newline at end of file + import_only = false + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" +} diff --git a/examples/resources/stackit_kms_key_ring/resource.tf b/examples/resources/stackit_kms_key_ring/resource.tf index 7bd5a50ed..a1a6e232e 100644 --- a/examples/resources/stackit_kms_key_ring/resource.tf +++ b/examples/resources/stackit_kms_key_ring/resource.tf @@ -1,6 +1,6 @@ resource "stackit_kms_key_ring" "example" { - description = "example description" + description = "example description" display_name = "example name" - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - region_id = "eu01" -} \ No newline at end of file + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region_id = "eu01" +} diff --git a/examples/resources/stackit_kms_wrapping_key/resource.tf b/examples/resources/stackit_kms_wrapping_key/resource.tf index fcbd20eb4..3fb55692b 100644 --- a/examples/resources/stackit_kms_wrapping_key/resource.tf +++ b/examples/resources/stackit_kms_wrapping_key/resource.tf @@ -1,9 +1,9 @@ resource "stackit_kms_wrapping_key" "name" { - algorithm = "example algorithm" - backend = "software" - description = "new descr" + algorithm = "example algorithm" + backend = "software" + description = "new descr" display_name = "example name" - key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - purpose = "example purpose" -} \ No newline at end of file + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" +} diff --git a/go.sum b/go.sum index 9ff2c963d..1de6cef16 100644 --- a/go.sum +++ b/go.sum @@ -164,8 +164,6 @@ github.com/stackitcloud/stackit-sdk-go/services/iaas v0.28.0 h1:ggnr5AD62QiP+Us+ github.com/stackitcloud/stackit-sdk-go/services/iaas v0.28.0/go.mod h1:b/jgJf7QHdRzU2fmZeJJtu5j0TAevDRghzcn5MyRmOI= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha h1:m1jq6a8dbUe+suFuUNdHmM/cSehpGLUtDbK1CqLqydg= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha/go.mod h1:Nu1b5Phsv8plgZ51+fkxPVsU91ZJ5Ayz+cthilxdmQ8= -github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0 h1:Fo1XCnfW47yW9zNfQgJk6/e9Z7YVoD4fay/PxYnSf4c= -github.com/stackitcloud/stackit-sdk-go/services/kms v0.3.0/go.mod h1:I3UOleO9ZlTYQDd5iTpomfjx7l1J7JAIj9VGmAjTMjk= github.com/stackitcloud/stackit-sdk-go/services/kms v0.5.0 h1:pKxX9XcCfTqAM86xD66/iQVWdXSkAT+9AGNYD/195jA= github.com/stackitcloud/stackit-sdk-go/services/kms v0.5.0/go.mod h1:KEPVoO21pC4bjy5l0nyhjUJ0+uVwVWb+k2TYrzJ8xYw= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.5.1 h1:OdJEs8eOfrzn9tCBDLxIyP8hX50zPfcXNYnRoQX+chs= @@ -176,14 +174,10 @@ github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.1 h1:Db/ebOL2vbpIe github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.1/go.mod h1:8jdN4v2euK3f9gfdzbRi8e4nBJ8g/Q5YF9aPB4M4fCQ= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.1 h1:Fc+iVo5de5Z1XI6nXGcuswZYkCiryr2h/Z9v2JmNk0w= github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.1/go.mod h1:84Gfz5wimt0gNyOXaAfTwVk/RFovgBxq3AOG2cdZx4Q= -github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.1 h1:XOpikSY2IXfBJPzUdgBk69iJXFC99xzfYtY1h4bZ5vM= -github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.1/go.mod h1:G7S/hGa6EyX5Avxxw/PIdbdtbFeiXL/T1vUkPOJ120w= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2 h1:BQ+qAkVS/aGHepE/+gVsvSg1sRkPOyIUI/jkCyUOrWg= github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2/go.mod h1:oc8Mpwl7O6EZwG0YxfhOzNCJwNQBWK5rFh764OtxoMY= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1 h1:4jsFLbDVEosYTgQz6lPds1E9KDOiHwjuhWqcG+lo5B4= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1/go.mod h1:j1SHAS5lN8F9b/iPUOfjAl9QAA9tOT7NKOiDEzcM2zc= -github.com/stackitcloud/stackit-sdk-go/services/observability v0.9.1 h1:eSiUKN61FJ4x42vgIvhVU7bgmrPbj05xR4y0nnRV5Yg= -github.com/stackitcloud/stackit-sdk-go/services/observability v0.9.1/go.mod h1:oJku0heeBwsy4IToqhvSdPJI++GUNkBSESxOjiLWRVQ= github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0 h1:SIctDqGprEoZArWaTds7hzQyh8Pqaz95Nmuj/1QuDEQ= github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0/go.mod h1:tJEOi6L0le4yQZPGwalup/PZ13gqs1aCQDqlUs2cYW0= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1 h1:50n87uZn0EvSP9hJGLqd3Wm2hfqbyh7BMGGCk7axgqA= @@ -244,21 +238,15 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -277,21 +265,16 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -304,16 +287,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/stackit/internal/services/kms/key-ring/datasource.go b/stackit/internal/services/kms/key-ring/datasource.go index ee8242e07..4877f10c4 100644 --- a/stackit/internal/services/kms/key-ring/datasource.go +++ b/stackit/internal/services/kms/key-ring/datasource.go @@ -3,6 +3,8 @@ package kms import ( "context" "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" @@ -14,7 +16,6 @@ import ( kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "net/http" ) var ( @@ -30,7 +31,7 @@ type keyRingDataSource struct { providerData core.ProviderData } -func (k *keyRingDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { +func (k *keyRingDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { response.TypeName = request.ProviderTypeName + "_kms_key_ring" } @@ -50,7 +51,7 @@ func (k *keyRingDataSource) Configure(ctx context.Context, request datasource.Co tflog.Info(ctx, "Key ring configured") } -func (k *keyRingDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { +func (k *keyRingDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { descriptions := map[string]string{ "main": "KMS Key Ring resource schema.", "description": "A user chosen description to distinguish multiple key rings.", @@ -108,7 +109,7 @@ func (k *keyRingDataSource) Schema(ctx context.Context, request datasource.Schem } } -func (k *keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { +func (k *keyRingDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.Config.Get(ctx, &model) diff --git a/stackit/internal/services/kms/key-ring/resource.go b/stackit/internal/services/kms/key-ring/resource.go index 5e5dbe58f..179cf1387 100644 --- a/stackit/internal/services/kms/key-ring/resource.go +++ b/stackit/internal/services/kms/key-ring/resource.go @@ -3,6 +3,10 @@ package kms import ( "context" "fmt" + "net/http" + "strings" + "time" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -20,9 +24,6 @@ import ( kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "net/http" - "strings" - "time" ) var ( @@ -49,7 +50,7 @@ type keyRingResource struct { providerData core.ProviderData } -func (k *keyRingResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { +func (k *keyRingResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = request.ProviderTypeName + "_kms_key_ring" } @@ -67,7 +68,7 @@ func (k *keyRingResource) Configure(ctx context.Context, request resource.Config k.client = apiClient } -func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { +func (k *keyRingResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { descriptions := map[string]string{ "main": "KMS Key Ring resource schema. Must have a `region` specified in the provider configuration.", "description": "A user chosen description to distinguish multiple key rings.", @@ -142,7 +143,7 @@ func (k *keyRingResource) Schema(ctx context.Context, request resource.SchemaReq } } -func (k *keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { +func (k *keyRingResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.Plan.Get(ctx, &model) response.Diagnostics.Append(diags...) @@ -190,7 +191,7 @@ func (k *keyRingResource) Create(ctx context.Context, request resource.CreateReq tflog.Info(ctx, "Key Ring created") } -func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { +func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.State.Get(ctx, &model) response.Diagnostics.Append(diags...) @@ -230,12 +231,12 @@ func (k *keyRingResource) Read(ctx context.Context, request resource.ReadRequest tflog.Info(ctx, "Key ring read") } -func (k *keyRingResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { +func (k *keyRingResource) Update(ctx context.Context, _ resource.UpdateRequest, response *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform // key rings cannot be updated, so we log an error. core.LogAndAddError(ctx, &response.Diagnostics, "Error updating key ring", "Key rings can't be updated") } -func (k *keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { +func (k *keyRingResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.State.Get(ctx, &model) response.Diagnostics.Append(diags...) diff --git a/stackit/internal/services/kms/key-ring/resource_test.go b/stackit/internal/services/kms/key-ring/resource_test.go index 550546046..e6866b2c2 100644 --- a/stackit/internal/services/kms/key-ring/resource_test.go +++ b/stackit/internal/services/kms/key-ring/resource_test.go @@ -2,11 +2,12 @@ package kms import ( "fmt" + "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/kms" - "testing" ) func TestMapFields(t *testing.T) { diff --git a/stackit/internal/services/kms/key/datasource.go b/stackit/internal/services/kms/key/datasource.go index de02a70a1..ccf1afc4e 100644 --- a/stackit/internal/services/kms/key/datasource.go +++ b/stackit/internal/services/kms/key/datasource.go @@ -3,6 +3,8 @@ package kms import ( "context" "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" @@ -14,7 +16,6 @@ import ( kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "net/http" ) var ( @@ -30,7 +31,7 @@ type keyDataSource struct { providerData core.ProviderData } -func (k *keyDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { +func (k *keyDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { response.TypeName = request.ProviderTypeName + "_kms_key" } @@ -50,7 +51,7 @@ func (k *keyDataSource) Configure(ctx context.Context, request datasource.Config tflog.Info(ctx, "Key configured") } -func (k *keyDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { +func (k *keyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { descriptions := map[string]string{ "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", "backend": "The backend that is used for KMS. Right now, only software is accepted.", @@ -146,7 +147,7 @@ func (k *keyDataSource) Schema(ctx context.Context, request datasource.SchemaReq } } -func (k *keyDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { +func (k *keyDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.Config.Get(ctx, &model) response.Diagnostics.Append(diags...) diff --git a/stackit/internal/services/kms/key/resource.go b/stackit/internal/services/kms/key/resource.go index b9eb5309d..23ab22734 100644 --- a/stackit/internal/services/kms/key/resource.go +++ b/stackit/internal/services/kms/key/resource.go @@ -3,6 +3,9 @@ package kms import ( "context" "fmt" + "net/http" + "strings" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -19,8 +22,6 @@ import ( kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "net/http" - "strings" ) var ( @@ -52,7 +53,7 @@ type keyResource struct { providerData core.ProviderData } -func (k *keyResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { +func (k *keyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = request.ProviderTypeName + "_kms_key" } @@ -69,7 +70,7 @@ func (k *keyResource) Configure(ctx context.Context, request resource.ConfigureR k.client = apiClient } -func (k *keyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { +func (k *keyResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { descriptions := map[string]string{ "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", "backend": "The backend that is used for KMS. Right now, only software is accepted.", @@ -195,7 +196,7 @@ func (k *keyResource) Schema(ctx context.Context, request resource.SchemaRequest } } -func (k *keyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { +func (k *keyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.Plan.Get(ctx, &model) response.Diagnostics.Append(diags...) @@ -227,6 +228,10 @@ func (k *keyResource) Create(ctx context.Context, request resource.CreateRequest ctx = tflog.SetField(ctx, "key_id", keyId) err = mapFields(createResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating key", fmt.Sprintf("Processing API payload: %v", err)) + return + } diags = response.State.Set(ctx, model) response.Diagnostics.Append(diags...) @@ -236,7 +241,7 @@ func (k *keyResource) Create(ctx context.Context, request resource.CreateRequest tflog.Info(ctx, "Key created") } -func (k *keyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { +func (k *keyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.State.Get(ctx, &model) response.Diagnostics.Append(diags...) @@ -278,12 +283,12 @@ func (k *keyResource) Read(ctx context.Context, request resource.ReadRequest, re tflog.Info(ctx, "Key read") } -func (k *keyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { +func (k *keyResource) Update(ctx context.Context, _ resource.UpdateRequest, response *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform // keys cannot be updated, so we log an error. core.LogAndAddError(ctx, &response.Diagnostics, "Error updating key", "Keys can't be updated") } -func (k *keyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { +func (k *keyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.State.Get(ctx, &model) response.Diagnostics.Append(diags...) diff --git a/stackit/internal/services/kms/key/resource_test.go b/stackit/internal/services/kms/key/resource_test.go index ae9b88695..667939b45 100644 --- a/stackit/internal/services/kms/key/resource_test.go +++ b/stackit/internal/services/kms/key/resource_test.go @@ -2,11 +2,12 @@ package kms import ( "fmt" + "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/kms" - "testing" ) func TestMapFields(t *testing.T) { diff --git a/stackit/internal/services/kms/utils/util.go b/stackit/internal/services/kms/utils/util.go index f78cb54a5..bfe2ed6c9 100644 --- a/stackit/internal/services/kms/utils/util.go +++ b/stackit/internal/services/kms/utils/util.go @@ -3,6 +3,7 @@ package utils import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/services/kms" @@ -17,12 +18,10 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags } if providerData.KMSCustomEndpoint != "" { apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.KMSCustomEndpoint)) - } else { - apiClientConfigOptions = append(apiClientConfigOptions) } apiClient, err := kms.NewAPIClient(apiClientConfigOptions...) if err != nil { - core.LogAndAddError(ctx, diags, "Error configurin API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) } return apiClient diff --git a/stackit/internal/services/kms/wrapping-key/datasource.go b/stackit/internal/services/kms/wrapping-key/datasource.go index 476d86a2b..a301852f9 100644 --- a/stackit/internal/services/kms/wrapping-key/datasource.go +++ b/stackit/internal/services/kms/wrapping-key/datasource.go @@ -3,6 +3,8 @@ package kms import ( "context" "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" @@ -14,7 +16,6 @@ import ( kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "net/http" ) var ( @@ -30,7 +31,7 @@ type wrappingKeyDataSource struct { providerData core.ProviderData } -func (w *wrappingKeyDataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { +func (w *wrappingKeyDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { response.TypeName = request.ProviderTypeName + "_kms_wrapping_key" } @@ -50,7 +51,7 @@ func (w *wrappingKeyDataSource) Configure(ctx context.Context, request datasourc tflog.Info(ctx, "Wrapping key configured") } -func (w *wrappingKeyDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { +func (w *wrappingKeyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { descriptions := map[string]string{ "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", "backend": "The backend that is used for KMS. Right now, only software is accepted.", @@ -141,7 +142,7 @@ func (w *wrappingKeyDataSource) Schema(ctx context.Context, request datasource.S } } -func (w *wrappingKeyDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { +func (w *wrappingKeyDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.Config.Get(ctx, &model) response.Diagnostics.Append(diags...) diff --git a/stackit/internal/services/kms/wrapping-key/resource.go b/stackit/internal/services/kms/wrapping-key/resource.go index 713728cd4..471326a5e 100644 --- a/stackit/internal/services/kms/wrapping-key/resource.go +++ b/stackit/internal/services/kms/wrapping-key/resource.go @@ -3,6 +3,9 @@ package kms import ( "context" "fmt" + "net/http" + "strings" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -19,8 +22,6 @@ import ( kmsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "net/http" - "strings" ) var ( @@ -51,7 +52,7 @@ type wrappingKeyResource struct { providerData core.ProviderData } -func (w *wrappingKeyResource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { +func (w *wrappingKeyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = request.ProviderTypeName + "_kms_wrapping_key" } @@ -68,7 +69,7 @@ func (w *wrappingKeyResource) Configure(ctx context.Context, request resource.Co w.client = apiClient } -func (w *wrappingKeyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { +func (w *wrappingKeyResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { descriptions := map[string]string{ "main": "KMS Key resource schema. Must have a `region` specified in the provider configuration.", "algorithm": "The encryption algorithm that the key will use to encrypt data", @@ -188,7 +189,7 @@ func (w *wrappingKeyResource) Schema(ctx context.Context, request resource.Schem } } -func (w *wrappingKeyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { +func (w *wrappingKeyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.Plan.Get(ctx, &model) @@ -222,6 +223,10 @@ func (w *wrappingKeyResource) Create(ctx context.Context, request resource.Creat ctx = tflog.SetField(ctx, "wrapping_key_id", wrappingKeyId) err = mapFields(createResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &response.Diagnostics, "Error creating wrapping key", fmt.Sprintf("Processing API payload: %v", err)) + return + } diags = response.State.Set(ctx, model) response.Diagnostics.Append(diags...) @@ -231,7 +236,7 @@ func (w *wrappingKeyResource) Create(ctx context.Context, request resource.Creat tflog.Info(ctx, "Key created") } -func (w *wrappingKeyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { +func (w *wrappingKeyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.State.Get(ctx, &model) response.Diagnostics.Append(diags...) @@ -273,12 +278,12 @@ func (w *wrappingKeyResource) Read(ctx context.Context, request resource.ReadReq tflog.Info(ctx, "Wrapping key read") } -func (w *wrappingKeyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { +func (w *wrappingKeyResource) Update(ctx context.Context, _ resource.UpdateRequest, response *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform // wrapping keys cannot be updated, so we log an error. core.LogAndAddError(ctx, &response.Diagnostics, "Error updating wrapping key", "Keys can't be updated") } -func (w *wrappingKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { +func (w *wrappingKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform var model Model diags := request.State.Get(ctx, &model) response.Diagnostics.Append(diags...) diff --git a/stackit/internal/services/kms/wrapping-key/resource_test.go b/stackit/internal/services/kms/wrapping-key/resource_test.go index 973ce903b..ec46dfc84 100644 --- a/stackit/internal/services/kms/wrapping-key/resource_test.go +++ b/stackit/internal/services/kms/wrapping-key/resource_test.go @@ -2,11 +2,12 @@ package kms import ( "fmt" + "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/kms" - "testing" ) func TestMapFields(t *testing.T) { From d87d60ff6e1674e870be726e26a15e0a21d7ba77 Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 8 Aug 2025 14:55:41 +0200 Subject: [PATCH 12/13] update docs --- docs/resources/kms_key.md | 14 +++++++------- docs/resources/kms_key_ring.md | 6 +++--- docs/resources/kms_wrapping_key.md | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/resources/kms_key.md b/docs/resources/kms_key.md index 6d96c0e07..7d73d973b 100644 --- a/docs/resources/kms_key.md +++ b/docs/resources/kms_key.md @@ -14,14 +14,14 @@ KMS Key resource schema. Must have a `region` specified in the provider configur ```terraform resource "stackit_kms_key" "name" { - algorithm = "example algorithm" - backend = "software" - description = "new descr" + algorithm = "example algorithm" + backend = "software" + description = "new descr" display_name = "example name" - import_only = false - key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - purpose = "example purpose" + import_only = false + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" } ``` diff --git a/docs/resources/kms_key_ring.md b/docs/resources/kms_key_ring.md index d616f78f1..f0cd73514 100644 --- a/docs/resources/kms_key_ring.md +++ b/docs/resources/kms_key_ring.md @@ -14,10 +14,10 @@ KMS Key Ring resource schema. Must have a `region` specified in the provider con ```terraform resource "stackit_kms_key_ring" "example" { - description = "example description" + description = "example description" display_name = "example name" - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - region_id = "eu01" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + region_id = "eu01" } ``` diff --git a/docs/resources/kms_wrapping_key.md b/docs/resources/kms_wrapping_key.md index 0dfc86988..aeb39f2f9 100644 --- a/docs/resources/kms_wrapping_key.md +++ b/docs/resources/kms_wrapping_key.md @@ -14,13 +14,13 @@ KMS Key resource schema. Must have a `region` specified in the provider configur ```terraform resource "stackit_kms_wrapping_key" "name" { - algorithm = "example algorithm" - backend = "software" - description = "new descr" + algorithm = "example algorithm" + backend = "software" + description = "new descr" display_name = "example name" - key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - purpose = "example purpose" + key_ring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + purpose = "example purpose" } ``` From 889b222e04cdef9fb6a8edaeffd41945f7ceb80d Mon Sep 17 00:00:00 2001 From: smagulow Date: Fri, 8 Aug 2025 16:17:31 +0200 Subject: [PATCH 13/13] update kms client config --- stackit/internal/services/kms/utils/util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stackit/internal/services/kms/utils/util.go b/stackit/internal/services/kms/utils/util.go index bfe2ed6c9..9f6f64d81 100644 --- a/stackit/internal/services/kms/utils/util.go +++ b/stackit/internal/services/kms/utils/util.go @@ -22,6 +22,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags apiClient, err := kms.NewAPIClient(apiClientConfigOptions...) if err != nil { core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + return nil } return apiClient