feat(kms): add keyring resource and datasource (#1049)

relates to STACKITTPR-410
This commit is contained in:
Ruben Hönle 2025-11-12 14:10:58 +01:00 committed by GitHub
parent c6e1c3d3a8
commit edf22a6193
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1157 additions and 9 deletions

View file

@ -80,9 +80,7 @@ If you want to onboard resources of a STACKIT service `foo` that was not yet in
2. Add a `foo_custom_endpoint` attribute to the provider's `Schema`, in `stackit/provider.go`
3. Check if the custom endpoint is defined and, if yes, use it. In the `Configure` method, add:
```go
if !(providerConfig.FooCustomEndpoint.IsUnknown() || providerConfig.FooCustomEndpoint.IsNull()) {
providerData.FooCustomEndpoint = providerConfig.FooCustomEndpoint.ValueString()
}
setStringField(providerConfig.FooCustomEndpoint, func(v string) { providerData.FooCustomEndpoint = v })
```
4. Create a utils package, for service `foo` it would be `stackit/internal/foo/utils`. Add a `ConfigureClient()` func and use it in your resource and datasource implementations.

View file

@ -0,0 +1,38 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_kms_keyring Data Source - stackit"
subcategory: ""
description: |-
KMS Keyring datasource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on datasource level.
---
# stackit_kms_keyring (Data Source)
KMS Keyring datasource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level.
## Example Usage
```terraform
data "stackit_kms_keyring" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
keyring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `keyring_id` (String) An auto generated unique id which identifies the keyring.
- `project_id` (String) STACKIT project ID to which the keyring is associated.
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only
- `description` (String) A user chosen description to distinguish multiple keyrings.
- `display_name` (String) The display name to distinguish multiple keyrings.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`keyring_id`".

View file

@ -162,6 +162,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` (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

View file

@ -0,0 +1,42 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_kms_keyring Resource - stackit"
subcategory: ""
description: |-
KMS Keyring resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level.
~> Keyrings will not be destroyed by terraform during a terraform destroy. They will just be thrown out of the Terraform state and not deleted on API side. This way we can ensure no keyring setups are deleted by accident and it gives you the option to recover your keys within the grace period.
---
# stackit_kms_keyring (Resource)
KMS Keyring resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level.
~> Keyrings will **not** be destroyed by terraform during a `terraform destroy`. They will just be thrown out of the Terraform state and not deleted on API side. **This way we can ensure no keyring setups are deleted by accident and it gives you the option to recover your keys within the grace period.**
## Example Usage
```terraform
resource "stackit_kms_keyring" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
display_name = "example name"
description = "example description"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `display_name` (String) The display name to distinguish multiple keyrings.
- `project_id` (String) STACKIT project ID to which the keyring is associated.
### Optional
- `description` (String) A user chosen description to distinguish multiple keyrings.
- `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`,`region`,`keyring_id`".
- `keyring_id` (String) An auto generated unique id which identifies the keyring.

View file

@ -0,0 +1,4 @@
data "stackit_kms_keyring" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
keyring_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

View file

@ -0,0 +1,5 @@
resource "stackit_kms_keyring" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
display_name = "example name"
description = "example description"
}

3
go.mod
View file

@ -17,6 +17,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/git v0.8.0
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.31.0
github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha
github.com/stackitcloud/stackit-sdk-go/services/kms v1.0.0
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.6.0
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.1
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.1
@ -80,7 +81,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.9.0
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect

6
go.sum
View file

@ -137,8 +137,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=
@ -166,6 +166,8 @@ github.com/stackitcloud/stackit-sdk-go/services/iaas v0.31.0 h1:dnEjyapuv8WwRN5v
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.31.0/go.mod h1:854gnLR92NvAbJAA1xZEumrtNh1DoBP1FXTMvhwYA6w=
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 v1.0.0 h1:zxoOv7Fu+FmdsvTKiKkbmLItrMKfL+QoVtz9ReEF30E=
github.com/stackitcloud/stackit-sdk-go/services/kms v1.0.0/go.mod h1:KEPVoO21pC4bjy5l0nyhjUJ0+uVwVWb+k2TYrzJ8xYw=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.6.0 h1:q33ZaCBVEBUsnMDxYyuJKtJvGcE5nKgvuPed3s8zXNI=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.6.0/go.mod h1:20QOZ3rBC9wTGgzXzLz9M6YheX0VaxWE0/JI+s8On7k=
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.1 h1:hv5WrRU9rN6Jx4OwdOGJRyaQrfA9p1tzEoQK6/CDyoA=

View file

@ -12,14 +12,17 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"
)
// Separator used for concatenation of TF-internal resource ID
const Separator = ","
type ResourceType string
const (
Resource ResourceType = "resource"
Datasource ResourceType = "datasource"
// Separator used for concatenation of TF-internal resource ID
Separator = ","
ResourceRegionFallbackDocstring = "Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level."
DatasourceRegionFallbackDocstring = "Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level."
)
type ProviderData struct {
@ -33,6 +36,7 @@ type ProviderData struct {
DnsCustomEndpoint string
GitCustomEndpoint string
IaaSCustomEndpoint string
KMSCustomEndpoint string
LoadBalancerCustomEndpoint string
LogMeCustomEndpoint string
MariaDBCustomEndpoint string

View file

@ -0,0 +1,139 @@
package kms
import (
"context"
"fmt"
"net/http"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/services/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"
)
var (
_ datasource.DataSource = &keyRingDataSource{}
)
func NewKeyRingDataSource() datasource.DataSource {
return &keyRingDataSource{}
}
type keyRingDataSource struct {
client *kms.APIClient
providerData core.ProviderData
}
func (k *keyRingDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) {
response.TypeName = request.ProviderTypeName + "_kms_keyring"
}
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, "KMS client configured")
}
func (k *keyRingDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) {
response.Schema = schema.Schema{
Description: fmt.Sprintf("KMS Keyring datasource schema. %s", core.DatasourceRegionFallbackDocstring),
Attributes: map[string]schema.Attribute{
"description": schema.StringAttribute{
Description: "A user chosen description to distinguish multiple keyrings.",
Computed: true,
},
"display_name": schema.StringAttribute{
Description: "The display name to distinguish multiple keyrings.",
Computed: true,
},
"keyring_id": schema.StringAttribute{
Description: "An auto generated unique id which identifies the keyring.",
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"id": schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`keyring_id`\".",
Computed: true,
},
"project_id": schema.StringAttribute{
Description: "STACKIT project ID to which the keyring is associated.",
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
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) { // nolint:gocritic // function signature required by Terraform
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, "keyring_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 keyring",
fmt.Sprintf("Keyring 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 keyring", 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")
}

View file

@ -0,0 +1,353 @@
package kms
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
"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/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"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)
const (
deletionWarning = "Keyrings will **not** be destroyed by terraform during a `terraform destroy`. They will just be thrown out of the Terraform state and not deleted on API side. **This way we can ensure no keyring setups are deleted by accident and it gives you the option to recover your keys within the grace period.**"
)
var (
_ resource.Resource = &keyRingResource{}
_ resource.ResourceWithConfigure = &keyRingResource{}
_ resource.ResourceWithImportState = &keyRingResource{}
_ resource.ResourceWithModifyPlan = &keyRingResource{}
)
type Model struct {
Description types.String `tfsdk:"description"`
DisplayName types.String `tfsdk:"display_name"`
KeyRingId types.String `tfsdk:"keyring_id"`
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
}
func NewKeyRingResource() resource.Resource {
return &keyRingResource{}
}
type keyRingResource struct {
client *kms.APIClient
providerData core.ProviderData
}
func (r *keyRingResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = request.ProviderTypeName + "_kms_keyring"
}
func (r *keyRingResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics)
if !ok {
return
}
r.client = kmsUtils.ConfigureClient(ctx, &r.providerData, &response.Diagnostics)
if response.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "KMS client configured")
}
// ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan.
func (r *keyRingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
}
var planModel Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
}
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *keyRingResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
description := fmt.Sprintf("KMS Keyring resource schema. %s", core.ResourceRegionFallbackDocstring)
response.Schema = schema.Schema{
Description: description,
MarkdownDescription: fmt.Sprintf("%s\n\n ~> %s", description, deletionWarning),
Attributes: map[string]schema.Attribute{
"description": schema.StringAttribute{
Description: "A user chosen description to distinguish multiple keyrings.",
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"display_name": schema.StringAttribute{
Description: "The display name to distinguish multiple keyrings.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"keyring_id": schema.StringAttribute{
Description: "An auto generated unique id which identifies the keyring.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"id": schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`keyring_id`\".",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"project_id": schema.StringAttribute{
Description: "STACKIT project ID to which the keyring is associated.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: "The resource region. If not defined, the provider region is used.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}
func (r *keyRingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
payload, err := toCreatePayload(&model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating keyring", fmt.Sprintf("Creating API payload: %v", err))
return
}
createResponse, err := r.client.CreateKeyRing(ctx, projectId, region).CreateKeyRingPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating keyring", fmt.Sprintf("Calling API: %v", err))
return
}
if createResponse == nil || createResponse.Id == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating keyring", "API returned empty response")
return
}
keyRingId := *createResponse.Id
// Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"project_id": projectId,
"region": region,
"keyring_id": keyRingId,
})
waitResp, err := wait.CreateKeyRingWaitHandler(ctx, r.client, projectId, region, keyRingId).SetSleepBeforeWait(5 * time.Second).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating keyring", fmt.Sprintf("Key Ring creation waiting: %v", err))
return
}
err = mapFields(waitResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating keyring", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Key Ring created")
}
func (r *keyRingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
keyRingId := model.KeyRingId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "keyring_id", keyRingId)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
keyRingResponse, err := r.client.GetKeyRing(ctx, projectId, region, keyRingId).Execute()
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading keyring", fmt.Sprintf("Calling API: %v", err))
return
}
err = mapFields(keyRingResponse, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading keyring", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Key ring read")
}
func (r *keyRingResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
// keyrings cannot be updated, so we log an error.
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating keyring", "Keyrings can't be updated")
}
func (r *keyRingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// The keyring can't be deleted by Terraform because it potentially has still keys inside it.
// These keys might be *scheduled* for deletion, but aren't deleted completely, so the delete request would fail.
core.LogAndAddWarning(ctx, &resp.Diagnostics, "Keyring not deleted on API side", deletionWarning)
tflog.Info(ctx, "keyring deleted")
}
func (r *keyRingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing keyring",
fmt.Sprintf("Exptected import identifier with format: [project_id],[region],[keyring_id], got :%q", req.ID),
)
return
}
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"project_id": idParts[0],
"region": idParts[1],
"keyring_id": idParts[2],
})
tflog.Info(ctx, "keyring state imported")
}
func mapFields(keyRing *kms.KeyRing, model *Model, region string) 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(), region, keyRingId)
model.KeyRingId = types.StringValue(keyRingId)
model.DisplayName = types.StringPointerValue(keyRing.DisplayName)
model.Region = types.StringValue(region)
// TODO: workaround - remove once STACKITKMS-377 is resolved (just write the return value from the API to the state then)
if !(model.Description.IsNull() && keyRing.Description != nil && *keyRing.Description == "") {
model.Description = types.StringPointerValue(keyRing.Description)
} else {
model.Description = types.StringNull()
}
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
}

View file

@ -0,0 +1,173 @@
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"
)
const testRegion = "eu01"
func TestMapFields(t *testing.T) {
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,eu01,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,eu01,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)
}
}
})
}
}

View file

@ -0,0 +1,315 @@
package kms_test
import (
"context"
_ "embed"
"errors"
"fmt"
"maps"
"net/http"
"strings"
"sync"
"testing"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
coreConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/kms"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
)
var (
//go:embed testdata/keyring-min.tf
resourceKeyRingMinConfig string
//go:embed testdata/keyring-max.tf
resourceKeyRingMaxConfig string
)
var testConfigKeyRingVarsMin = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
}
var testConfigKeyRingVarsMinUpdated = func() config.Variables {
updatedConfig := config.Variables{}
maps.Copy(updatedConfig, testConfigKeyRingVarsMin)
updatedConfig["display_name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["display_name"])))
return updatedConfig
}
var testConfigKeyRingVarsMax = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"description": config.StringVariable("description"),
"display_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
}
var testConfigKeyRingVarsMaxUpdated = func() config.Variables {
updatedConfig := config.Variables{}
maps.Copy(updatedConfig, testConfigKeyRingVarsMax)
updatedConfig["description"] = config.StringVariable("updated description")
updatedConfig["display_name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["display_name"])))
return updatedConfig
}
func TestAccKeyRingMin(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckDestroy,
Steps: []resource.TestStep{
// Creation
{
ConfigVariables: testConfigKeyRingVarsMin,
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMinConfig),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionCreate),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigKeyRingVarsMin["display_name"])),
resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"),
resource.TestCheckNoResourceAttr("stackit_kms_keyring.keyring", "description"),
),
},
// Data source
{
ConfigVariables: testConfigKeyRingVarsMin,
Config: fmt.Sprintf(`
%s
%s
data "stackit_kms_keyring" "keyring" {
project_id = stackit_kms_keyring.keyring.project_id
keyring_id = stackit_kms_keyring.keyring.keyring_id
}
`,
testutil.KMSProviderConfig(), resourceKeyRingMinConfig,
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionNoop),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "region", testutil.Region),
resource.TestCheckResourceAttrPair(
"stackit_kms_keyring.keyring", "keyring_id",
"data.stackit_kms_keyring.keyring", "keyring_id",
),
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigKeyRingVarsMin["display_name"])),
resource.TestCheckNoResourceAttr("data.stackit_kms_keyring.keyring", "description"),
),
},
// Import
{
ConfigVariables: testConfigKeyRingVarsMin,
ResourceName: "stackit_kms_keyring.keyring",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_kms_keyring.keyring"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_kms_keyring.keyring")
}
keyRingId, ok := r.Primary.Attributes["keyring_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute keyring_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, keyRingId), nil
},
ImportState: true,
ImportStateVerify: true,
},
// Update
{
ConfigVariables: testConfigKeyRingVarsMinUpdated(),
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMinConfig),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionReplace),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigKeyRingVarsMinUpdated()["display_name"])),
resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"),
resource.TestCheckNoResourceAttr("stackit_kms_keyring.keyring", "description"),
),
},
// Deletion is done by the framework implicitly
},
})
}
func TestAccKeyRingMax(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckDestroy,
Steps: []resource.TestStep{
// Creation
{
ConfigVariables: testConfigKeyRingVarsMax,
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMaxConfig),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionCreate),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "description", testutil.ConvertConfigVariable(testConfigKeyRingVarsMax["description"])),
resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigKeyRingVarsMax["display_name"])),
),
},
// Data Source
{
ConfigVariables: testConfigKeyRingVarsMax,
Config: fmt.Sprintf(`
%s
%s
data "stackit_kms_keyring" "keyring" {
project_id = stackit_kms_keyring.keyring.project_id
keyring_id = stackit_kms_keyring.keyring.keyring_id
}
`,
testutil.KMSProviderConfig(), resourceKeyRingMaxConfig,
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionNoop),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "region", testutil.Region),
resource.TestCheckResourceAttrPair(
"stackit_kms_keyring.keyring", "keyring_id",
"data.stackit_kms_keyring.keyring", "keyring_id",
),
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "description", testutil.ConvertConfigVariable(testConfigKeyRingVarsMax["description"])),
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigKeyRingVarsMax["display_name"])),
),
),
},
// Import
{
ConfigVariables: testConfigKeyRingVarsMax,
ResourceName: "stackit_kms_keyring.keyring",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_kms_keyring.keyring"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_kms_keyring.keyring")
}
keyRingId, ok := r.Primary.Attributes["keyring_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute keyring_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, keyRingId), nil
},
ImportState: true,
ImportStateVerify: true,
},
// Update
{
ConfigVariables: testConfigKeyRingVarsMaxUpdated(),
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMaxConfig),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionReplace),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "display_name", testutil.ConvertConfigVariable(testConfigKeyRingVarsMaxUpdated()["display_name"])),
resource.TestCheckResourceAttrSet("stackit_kms_keyring.keyring", "keyring_id"),
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "description", testutil.ConvertConfigVariable(testConfigKeyRingVarsMaxUpdated()["description"])),
),
},
// Deletion is done by the framework implicitly
},
})
}
func testAccCheckDestroy(s *terraform.State) error {
checkFunctions := []func(s *terraform.State) error{
testAccCheckKeyRingDestroy,
}
var errs []error
wg := sync.WaitGroup{}
wg.Add(len(checkFunctions))
for _, f := range checkFunctions {
go func() {
err := f(s)
if err != nil {
errs = append(errs, err)
}
wg.Done()
}()
}
wg.Wait()
return errors.Join(errs...)
}
func testAccCheckKeyRingDestroy(s *terraform.State) error {
ctx := context.Background()
var client *kms.APIClient
var err error
if testutil.KMSCustomEndpoint == "" {
client, err = kms.NewAPIClient()
} else {
client, err = kms.NewAPIClient(
coreConfig.WithEndpoint(testutil.KMSCustomEndpoint),
)
}
if err != nil {
return fmt.Errorf("creating client: %w", err)
}
var errs []error
for _, rs := range s.RootModule().Resources {
if rs.Type != "stackit_kms_keyring" {
continue
}
keyRingId := strings.Split(rs.Primary.ID, core.Separator)[2]
err := client.DeleteKeyRingExecute(ctx, testutil.ProjectId, testutil.Region, keyRingId)
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
if errors.As(err, &oapiErr) {
if oapiErr.StatusCode == http.StatusNotFound {
continue
}
// Workaround: when the delete endpoint is called for a keyring which has keys inside it (no matter if
// they are scheduled for deletion or not, it will throw an HTTP 400 error and the keyring can't be
// deleted then).
// But at least we can delete all empty keyrings created by the keyring acc tests this way.
if oapiErr.StatusCode == http.StatusBadRequest {
continue
}
}
errs = append(errs, fmt.Errorf("cannot trigger keyring deletion %q: %w", keyRingId, err))
}
}
return errors.Join(errs...)
}

View file

@ -0,0 +1,10 @@
variable "project_id" {}
variable "display_name" {}
variable "description" {}
resource "stackit_kms_keyring" "keyring" {
project_id = var.project_id
display_name = var.display_name
description = var.description
}

View file

@ -0,0 +1,8 @@
variable "project_id" {}
variable "display_name" {}
resource "stackit_kms_keyring" "keyring" {
project_id = var.project_id
display_name = var.display_name
}

View file

@ -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))
}
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
}

View file

@ -55,6 +55,7 @@ var (
DnsCustomEndpoint = os.Getenv("TF_ACC_DNS_CUSTOM_ENDPOINT")
GitCustomEndpoint = os.Getenv("TF_ACC_GIT_CUSTOM_ENDPOINT")
IaaSCustomEndpoint = os.Getenv("TF_ACC_IAAS_CUSTOM_ENDPOINT")
KMSCustomEndpoint = os.Getenv("TF_ACC_KMS_CUSTOM_ENDPOINT")
LoadBalancerCustomEndpoint = os.Getenv("TF_ACC_LOADBALANCER_CUSTOM_ENDPOINT")
LogMeCustomEndpoint = os.Getenv("TF_ACC_LOGME_CUSTOM_ENDPOINT")
MariaDBCustomEndpoint = os.Getenv("TF_ACC_MARIADB_CUSTOM_ENDPOINT")
@ -169,6 +170,21 @@ func IaaSProviderConfigWithExperiments() string {
)
}
func KMSProviderConfig() string {
if KMSCustomEndpoint == "" {
return `
provider "stackit" {
default_region = "eu01"
}`
}
return fmt.Sprintf(`
provider "stackit" {
kms_custom_endpoint = "%s"
}`,
KMSCustomEndpoint,
)
}
func LoadBalancerProviderConfig() string {
if LoadBalancerCustomEndpoint == "" {
return `

View file

@ -48,6 +48,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"
kmsKeyRing "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/kms/keyring"
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"
@ -131,6 +132,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.String `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"`
@ -173,6 +175,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",
@ -264,6 +267,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
Optional: true,
Description: descriptions["iaas_custom_endpoint"],
},
"kms_custom_endpoint": schema.StringAttribute{
Optional: true,
Description: descriptions["kms_custom_endpoint"],
},
"postgresflex_custom_endpoint": schema.StringAttribute{
Optional: true,
Description: descriptions["postgresflex_custom_endpoint"],
@ -414,6 +421,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
setStringField(providerConfig.GitCustomEndpoint, func(v string) { providerData.GitCustomEndpoint = v })
setStringField(providerConfig.IaaSCustomEndpoint, func(v string) { providerData.IaaSCustomEndpoint = v })
setStringField(providerConfig.PostgresFlexCustomEndpoint, func(v string) { providerData.PostgresFlexCustomEndpoint = v })
setStringField(providerConfig.KMSCustomEndpoint, func(v string) { providerData.KMSCustomEndpoint = v })
setStringField(providerConfig.ModelServingCustomEndpoint, func(v string) { providerData.ModelServingCustomEndpoint = v })
setStringField(providerConfig.MongoDBFlexCustomEndpoint, func(v string) { providerData.MongoDBFlexCustomEndpoint = v })
setStringField(providerConfig.LoadBalancerCustomEndpoint, func(v string) { providerData.LoadBalancerCustomEndpoint = v })
@ -486,6 +494,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
iaasalphaRoutingTables.NewRoutingTablesDataSource,
iaasalphaRoutingTableRoutes.NewRoutingTableRoutesDataSource,
iaasSecurityGroupRule.NewSecurityGroupRuleDataSource,
kmsKeyRing.NewKeyRingDataSource,
loadBalancer.NewLoadBalancerDataSource,
logMeInstance.NewInstanceDataSource,
logMeCredential.NewCredentialDataSource,
@ -554,6 +563,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
iaasSecurityGroupRule.NewSecurityGroupRuleResource,
iaasalphaRoutingTable.NewRoutingTableResource,
iaasalphaRoutingTableRoute.NewRoutingTableRouteResource,
kmsKeyRing.NewKeyRingResource,
loadBalancer.NewLoadBalancerResource,
loadBalancerObservabilityCredential.NewObservabilityCredentialResource,
logMeInstance.NewInstanceResource,