Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,12 @@ Terraform acceptance tests are run using the command `make test-acceptance-tf`.

Additionally:

| Env var | Value | Example value | needed for Acc tests of the following services |
|---------------------------------------------|---------------------------------------------------------------------------------------------------------|----------------------------------------|------------------------------------------------|
| `TF_ACC_ORGANIZATION_ID` | ID of the STACKIT test organization | `5353ccfa-a984-4b96-a71d-b863dd2b7087` | `authorization`, `iaas` |
| `TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_EMAIL` | Email of the STACKIT service account | `abc-serviceaccount@sa.stackit.cloud` | `authorization`, `resourcemanager` | |
| `TF_ACC_TEST_PROJECT_PARENT_CONTAINER_ID` | Container ID of the project parent container (folder within an organization or the organization itself) | `organization-d2b7087` | `resourcemanager` |
| `TF_ACC_TEST_PROJECT_PARENT_UUID` | UUID ID of the project parent container (folder within an organization or the organization itself) | `5353ccfa-a984-4b96-a71d-b863dd2b7087` | `resourcemanager` |
| Env var | Value | Example value | needed for Acc tests of the following services |
|---------------------------------------------|---------------------------------------------------------------------------------------------------------|----------------------------------------|--------------------------------------------------------------|
| `TF_ACC_ORGANIZATION_ID` | ID of the STACKIT test organization | `5353ccfa-a984-4b96-a71d-b863dd2b7087` | `authorization`, `iaas` |
| `TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_EMAIL` | Email of the STACKIT service account | `abc-serviceaccount@sa.stackit.cloud` | `authorization`, `iaas`, `resourcemanager`, `secretsmanager` |
| `TF_ACC_TEST_PROJECT_PARENT_CONTAINER_ID` | Container ID of the project parent container (folder within an organization or the organization itself) | `organization-d2b7087` | `resourcemanager` |
| `TF_ACC_TEST_PROJECT_PARENT_UUID` | UUID ID of the project parent container (folder within an organization or the organization itself) | `5353ccfa-a984-4b96-a71d-b863dd2b7087` | `resourcemanager` |

### Run Acceptance Tests of a single service

Expand Down
11 changes: 11 additions & 0 deletions docs/data-sources/secretsmanager_instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ data "stackit_secretsmanager_instance" "example" {

- `acls` (Set of String) The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`".
- `kms_key` (Attributes) The STACKIT-KMS key for secret encryption and decryption. (see [below for nested schema](#nestedatt--kms_key))
- `name` (String) Instance name.

<a id="nestedatt--kms_key"></a>
### Nested Schema for `kms_key`

Read-Only:

- `key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption.
- `key_ring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS.
- `key_version` (Number) Version of the key within the STACKIT-KMS to use for the encryption.
- `service_account_email` (String) Service-Account linked to the Key within the STACKIT-KMS.
11 changes: 11 additions & 0 deletions docs/resources/secretsmanager_instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,19 @@ import {
### Optional

- `acls` (Set of String) The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation
- `kms_key` (Attributes) The STACKIT-KMS key for secret encryption and decryption. (see [below for nested schema](#nestedatt--kms_key))

### Read-Only

- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`".
- `instance_id` (String) ID of the Secrets Manager instance.

<a id="nestedatt--kms_key"></a>
### Nested Schema for `kms_key`

Required:

- `key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption.
- `key_ring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS.
- `key_version` (Number) Version of the key within the STACKIT-KMS to use for the encryption.
- `service_account_email` (String) Service-Account linked to the Key within the STACKIT-KMS.
39 changes: 33 additions & 6 deletions stackit/internal/services/secretsmanager/instance/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,17 @@ func (r *instanceDataSource) Configure(ctx context.Context, req datasource.Confi
// Schema defines the schema for the data source.
func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "Secrets Manager instance data source schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".",
"instance_id": "ID of the Secrets Manager instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"acls": "The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation",
"main": "Secrets Manager instance data source schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".",
"instance_id": "ID of the Secrets Manager instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"acls": "The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation",
"kms_key": "The STACKIT-KMS key for secret encryption and decryption.",
"kms_key.key_id": "UUID of the key within the STACKIT-KMS to use for the encryption.",
"kms_key.key_ring_id": "UUID of the keyring where the key is located within the STACKTI-KMS.",
"kms_key.key_version": "Version of the key within the STACKIT-KMS to use for the encryption.",
"kms_key.service_account_email": "Service-Account linked to the Key within the STACKIT-KMS.",
}

resp.Schema = schema.Schema{
Expand Down Expand Up @@ -98,6 +103,28 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
ElementType: types.StringType,
Computed: true,
},
"kms_key": schema.SingleNestedAttribute{
Description: descriptions["kms_key"],
Computed: true,
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["kms_key.key_id"],
Computed: true,
},
"key_ring_id": schema.StringAttribute{
Description: descriptions["kms_key.key_ring_id"],
Computed: true,
},
"key_version": schema.Int64Attribute{
Description: descriptions["kms_key.key_version"],
Computed: true,
},
"service_account_email": schema.StringAttribute{
Description: descriptions["kms_key.service_account_email"],
Computed: true,
},
},
},
},
}
}
Expand Down
117 changes: 103 additions & 14 deletions stackit/internal/services/secretsmanager/instance/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ type Model struct {
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
ACLs types.Set `tfsdk:"acls"`
KmsKey *KmsKeyModel `tfsdk:"kms_key"`
}

type KmsKeyModel struct {
KeyId types.String `tfsdk:"key_id"`
KeyRingId types.String `tfsdk:"key_ring_id"`
KeyVersion types.Int64 `tfsdk:"key_version"`
ServiceAccountEmail types.String `tfsdk:"service_account_email"`
}

// NewInstanceResource is a helper function to simplify the provider implementation.
Expand Down Expand Up @@ -77,12 +85,17 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure
// Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"main": "Secrets Manager instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".",
"instance_id": "ID of the Secrets Manager instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"acls": "The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation",
"main": "Secrets Manager instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`instance_id`\".",
"instance_id": "ID of the Secrets Manager instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"acls": "The access control list for this instance. Each entry is an IP or IP range that is permitted to access, in CIDR notation",
"kms_key": "The STACKIT-KMS key for secret encryption and decryption.",
"kms_key.key_id": "UUID of the key within the STACKIT-KMS to use for the encryption.",
"kms_key.key_ring_id": "UUID of the keyring where the key is located within the STACKTI-KMS.",
"kms_key.key_version": "Version of the key within the STACKIT-KMS to use for the encryption.",
"kms_key.service_account_email": "Service-Account linked to the Key within the STACKIT-KMS.",
}

resp.Schema = schema.Schema{
Expand Down Expand Up @@ -118,11 +131,9 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Description: descriptions["name"],
Required: true,
PlanModifiers: []planmodifier.String{},
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
Expand All @@ -137,6 +148,28 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
),
},
},
"kms_key": schema.SingleNestedAttribute{
Description: descriptions["kms_key"],
Optional: true,
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["kms_key.key_id"],
Required: true,
},
"key_ring_id": schema.StringAttribute{
Description: descriptions["kms_key.key_ring_id"],
Required: true,
},
"key_version": schema.Int64Attribute{
Description: descriptions["kms_key.key_version"],
Required: true,
},
"service_account_email": schema.StringAttribute{
Description: descriptions["kms_key.service_account_email"],
Required: true,
},
},
},
},
}
}
Expand Down Expand Up @@ -284,6 +317,21 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)

// Generate API request body from model
payload, err := toUpdatePayload(&model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Update instance
err = r.client.UpdateInstance(ctx, projectId, instanceId).UpdateInstancePayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Calling API: %v", err))
return
}

ctx = core.LogResponse(ctx)

var acls []string
if !(model.ACLs.IsNull() || model.ACLs.IsUnknown()) {
diags = model.ACLs.ElementsAs(ctx, &acls, false)
Expand All @@ -294,7 +342,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
}

// Update ACLs
err := updateACLs(ctx, projectId, instanceId, acls, r.client)
err = updateACLs(ctx, projectId, instanceId, acls, r.client)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Updating ACLs: %v", err))
return
Expand Down Expand Up @@ -398,6 +446,15 @@ func mapFields(instance *secretsmanager.Instance, aclList *secretsmanager.ListAC
model.InstanceId = types.StringValue(instanceId)
model.Name = types.StringPointerValue(instance.Name)

if instance.KmsKey != nil {
model.KmsKey = &KmsKeyModel{
KeyId: types.StringPointerValue(instance.KmsKey.KeyId),
KeyRingId: types.StringPointerValue(instance.KmsKey.KeyRingId),
KeyVersion: types.Int64PointerValue(instance.KmsKey.KeyVersion),
ServiceAccountEmail: types.StringPointerValue(instance.KmsKey.ServiceAccountEmail),
}
}

err := mapACLs(aclList, model)
if err != nil {
return err
Expand Down Expand Up @@ -431,9 +488,41 @@ func toCreatePayload(model *Model) (*secretsmanager.CreateInstancePayload, error
if model == nil {
return nil, fmt.Errorf("nil model")
}
return &secretsmanager.CreateInstancePayload{
payload := &secretsmanager.CreateInstancePayload{
Name: conversion.StringValueToPointer(model.Name),
}

if model.KmsKey != nil {
payload.KmsKey = &secretsmanager.KmsKeyPayload{
KeyId: conversion.StringValueToPointer(model.KmsKey.KeyId),
KeyRingId: conversion.StringValueToPointer(model.KmsKey.KeyRingId),
KeyVersion: conversion.Int64ValueToPointer(model.KmsKey.KeyVersion),
ServiceAccountEmail: conversion.StringValueToPointer(model.KmsKey.ServiceAccountEmail),
}
}

return payload, nil
}

func toUpdatePayload(model *Model) (*secretsmanager.UpdateInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}

payload := &secretsmanager.UpdateInstancePayload{
Name: conversion.StringValueToPointer(model.Name),
}, nil
}

if model.KmsKey != nil {
payload.KmsKey = &secretsmanager.KmsKeyPayload{
KeyId: conversion.StringValueToPointer(model.KmsKey.KeyId),
KeyRingId: conversion.StringValueToPointer(model.KmsKey.KeyRingId),
KeyVersion: conversion.Int64ValueToPointer(model.KmsKey.KeyVersion),
ServiceAccountEmail: conversion.StringValueToPointer(model.KmsKey.ServiceAccountEmail),
}
}

return payload, nil
}

// updateACLs creates and deletes ACLs so that the instance's ACLs are the ones in the model
Expand Down
103 changes: 103 additions & 0 deletions stackit/internal/services/secretsmanager/instance/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,28 @@ func TestToCreatePayload(t *testing.T) {
},
true,
},
{
"with_kms_key",
&Model{
Name: types.StringValue("name"),
KmsKey: &KmsKeyModel{
KeyId: types.StringValue("kid"),
KeyRingId: types.StringValue("key-ring-id"),
KeyVersion: types.Int64Value(1),
ServiceAccountEmail: types.StringValue("service-account-email"),
},
},
&secretsmanager.CreateInstancePayload{
Name: utils.Ptr("name"),
KmsKey: &secretsmanager.KmsKeyPayload{
KeyId: utils.Ptr("kid"),
KeyRingId: utils.Ptr("key-ring-id"),
KeyVersion: utils.Ptr(int64(1)),
ServiceAccountEmail: utils.Ptr("service-account-email"),
},
},
true,
},
{
"null_fields_and_int_conversions",
&Model{
Expand Down Expand Up @@ -485,3 +507,84 @@ func TestUpdateACLs(t *testing.T) {
})
}
}

func TestToUpdatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
expected *secretsmanager.UpdateInstancePayload
isValid bool
}{
{
"default_values",
&Model{},
&secretsmanager.UpdateInstancePayload{},
true,
},
{
"simple_values",
&Model{
Name: types.StringValue("name"),
},
&secretsmanager.UpdateInstancePayload{
Name: utils.Ptr("name"),
},
true,
},
{
"with_kms_key",
&Model{
Name: types.StringValue("name"),
KmsKey: &KmsKeyModel{
KeyId: types.StringValue("kid"),
KeyRingId: types.StringValue("key-ring-id"),
KeyVersion: types.Int64Value(1),
ServiceAccountEmail: types.StringValue("service-account-email"),
},
},
&secretsmanager.UpdateInstancePayload{
Name: utils.Ptr("name"),
KmsKey: &secretsmanager.KmsKeyPayload{
KeyId: utils.Ptr("kid"),
KeyRingId: utils.Ptr("key-ring-id"),
KeyVersion: utils.Ptr(int64(1)),
ServiceAccountEmail: utils.Ptr("service-account-email"),
},
},
true,
},
{
"null_fields_and_int_conversions",
&Model{
Name: types.StringValue(""),
},
&secretsmanager.UpdateInstancePayload{
Name: utils.Ptr(""),
},
true,
},
{
"nil_model",
nil,
nil,
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := toUpdatePayload(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)
}
}
})
}
}
Loading
Loading