Skip to content

Commit 3653e0e

Browse files
committed
review findings
1 parent d667641 commit 3653e0e

File tree

6 files changed

+141
-15
lines changed

6 files changed

+141
-15
lines changed

docs/data-sources/kms_keyring.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
page_title: "stackit_kms_keyring Data Source - stackit"
44
subcategory: ""
55
description: |-
6-
KMS Keyring resource schema.
6+
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.
77
---
88

99
# stackit_kms_keyring (Data Source)
1010

11-
KMS Keyring resource schema.
11+
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.
1212

1313
## Example Usage
1414

@@ -27,9 +27,12 @@ data "stackit_kms_keyring" "example" {
2727
- `keyring_id` (String) An auto generated unique id which identifies the keyring.
2828
- `project_id` (String) STACKIT project ID to which the keyring is associated.
2929

30+
### Optional
31+
32+
- `region` (String) The resource region. If not defined, the provider region is used.
33+
3034
### Read-Only
3135

3236
- `description` (String) A user chosen description to distinguish multiple keyrings.
3337
- `display_name` (String) The display name to distinguish multiple keyrings.
3438
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`keyring_id`".
35-
- `region` (String) The resource region. If not defined, the provider region is used.

docs/resources/kms_keyring.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
page_title: "stackit_kms_keyring Resource - stackit"
44
subcategory: ""
55
description: |-
6-
KMS Keyring resource schema.
6+
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.
77
~> 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.
88
---
99

1010
# stackit_kms_keyring (Resource)
1111

12-
KMS Keyring resource schema.
12+
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.
1313

1414
~> 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.**
1515

stackit/internal/core/core.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ import (
1212
"github.com/hashicorp/terraform-plugin-log/tflog"
1313
)
1414

15-
// Separator used for concatenation of TF-internal resource ID
16-
const Separator = ","
17-
1815
type ResourceType string
1916

2017
const (
2118
Resource ResourceType = "resource"
2219
Datasource ResourceType = "datasource"
20+
21+
// Separator used for concatenation of TF-internal resource ID
22+
Separator = ","
23+
24+
ResourceRegionFallbackDocstring = "Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level."
25+
DatasourceRegionFallbackDocstring = "Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level."
2326
)
2427

2528
type ProviderData struct {

stackit/internal/services/kms/keyring/datasource.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ func (k *keyRingDataSource) Configure(ctx context.Context, request datasource.Co
4747
}
4848

4949
k.client = apiClient
50-
tflog.Info(ctx, "Keyring configured")
50+
tflog.Info(ctx, "KMS client configured")
5151
}
5252

5353
func (k *keyRingDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) {
5454
response.Schema = schema.Schema{
55-
Description: "KMS Keyring resource schema.",
55+
Description: fmt.Sprintf("KMS Keyring datasource schema. %s", core.DatasourceRegionFallbackDocstring),
5656
Attributes: map[string]schema.Attribute{
5757
"description": schema.StringAttribute{
5858
Description: "A user chosen description to distinguish multiple keyrings.",
@@ -64,7 +64,6 @@ func (k *keyRingDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
6464
},
6565
"keyring_id": schema.StringAttribute{
6666
Description: "An auto generated unique id which identifies the keyring.",
67-
Computed: false,
6867
Required: true,
6968
Validators: []validator.String{
7069
validate.UUID(),
@@ -84,6 +83,8 @@ func (k *keyRingDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
8483
},
8584
},
8685
"region": schema.StringAttribute{
86+
Optional: true,
87+
// must be computed to allow for storing the override value from the provider
8788
Computed: true,
8889
Description: "The resource region. If not defined, the provider region is used.",
8990
},

stackit/internal/services/kms/keyring/resource.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package kms
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"net/http"
78
"strings"
@@ -65,11 +66,12 @@ func (r *keyRingResource) Configure(ctx context.Context, request resource.Config
6566
return
6667
}
6768

68-
apiClient := kmsUtils.ConfigureClient(ctx, &r.providerData, &response.Diagnostics)
69+
r.client = kmsUtils.ConfigureClient(ctx, &r.providerData, &response.Diagnostics)
6970
if response.Diagnostics.HasError() {
7071
return
7172
}
72-
r.client = apiClient
73+
74+
tflog.Info(ctx, "KMS client configured")
7375
}
7476

7577
// ModifyPlan implements resource.ResourceWithModifyPlan.
@@ -103,7 +105,7 @@ func (r *keyRingResource) ModifyPlan(ctx context.Context, req resource.ModifyPla
103105
}
104106

105107
func (r *keyRingResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
106-
description := "KMS Keyring resource schema."
108+
description := fmt.Sprintf("KMS Keyring resource schema. %s", core.ResourceRegionFallbackDocstring)
107109

108110
response.Schema = schema.Schema{
109111
Description: description,
@@ -193,6 +195,11 @@ func (r *keyRingResource) Create(ctx context.Context, req resource.CreateRequest
193195
return
194196
}
195197

198+
if createResponse == nil || createResponse.Id == nil {
199+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating keyring", fmt.Sprintf("API returned empty response"))
200+
return
201+
}
202+
196203
keyRingId := *createResponse.Id
197204
// Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler
198205
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
@@ -239,7 +246,8 @@ func (r *keyRingResource) Read(ctx context.Context, req resource.ReadRequest, re
239246

240247
keyRingResponse, err := r.client.GetKeyRing(ctx, projectId, region, keyRingId).Execute()
241248
if err != nil {
242-
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
249+
var oapiErr *oapierror.GenericOpenAPIError
250+
ok := errors.As(err, &oapiErr)
243251
if ok && oapiErr.StatusCode == http.StatusNotFound {
244252
resp.State.RemoveResource(ctx)
245253
return

stackit/internal/services/kms/kms_acc_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package kms_test
22

33
import (
4+
"context"
45
_ "embed"
6+
"errors"
57
"fmt"
68
"maps"
9+
"net/http"
10+
"strings"
11+
"sync"
712
"testing"
813

14+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
15+
coreConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
16+
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
17+
"github.com/stackitcloud/stackit-sdk-go/services/kms"
18+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
19+
920
"github.com/hashicorp/terraform-plugin-testing/config"
1021
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
1122
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -50,11 +61,17 @@ var testConfigKeyRingVarsMaxUpdated = func() config.Variables {
5061
func TestAccKeyRingMin(t *testing.T) {
5162
resource.Test(t, resource.TestCase{
5263
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
64+
CheckDestroy: testAccCheckDestroy,
5365
Steps: []resource.TestStep{
5466
// Creation
5567
{
5668
ConfigVariables: testConfigKeyRingVarsMin,
5769
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMinConfig),
70+
ConfigPlanChecks: resource.ConfigPlanChecks{
71+
PreApply: []plancheck.PlanCheck{
72+
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionCreate),
73+
},
74+
},
5875
Check: resource.ComposeAggregateTestCheckFunc(
5976
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
6077
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
@@ -77,6 +94,11 @@ func TestAccKeyRingMin(t *testing.T) {
7794
`,
7895
testutil.KMSProviderConfig(), resourceKeyRingMinConfig,
7996
),
97+
ConfigPlanChecks: resource.ConfigPlanChecks{
98+
PreApply: []plancheck.PlanCheck{
99+
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionNoop),
100+
},
101+
},
80102
Check: resource.ComposeAggregateTestCheckFunc(
81103
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
82104
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "region", testutil.Region),
@@ -111,6 +133,11 @@ func TestAccKeyRingMin(t *testing.T) {
111133
{
112134
ConfigVariables: testConfigKeyRingVarsMinUpdated(),
113135
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMinConfig),
136+
ConfigPlanChecks: resource.ConfigPlanChecks{
137+
PreApply: []plancheck.PlanCheck{
138+
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionReplace),
139+
},
140+
},
114141
Check: resource.ComposeAggregateTestCheckFunc(
115142
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
116143
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
@@ -127,11 +154,17 @@ func TestAccKeyRingMin(t *testing.T) {
127154
func TestAccKeyRingMax(t *testing.T) {
128155
resource.Test(t, resource.TestCase{
129156
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
157+
CheckDestroy: testAccCheckDestroy,
130158
Steps: []resource.TestStep{
131159
// Creation
132160
{
133161
ConfigVariables: testConfigKeyRingVarsMax,
134162
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMaxConfig),
163+
ConfigPlanChecks: resource.ConfigPlanChecks{
164+
PreApply: []plancheck.PlanCheck{
165+
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionCreate),
166+
},
167+
},
135168
Check: resource.ComposeAggregateTestCheckFunc(
136169
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
137170
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
@@ -154,6 +187,11 @@ func TestAccKeyRingMax(t *testing.T) {
154187
`,
155188
testutil.KMSProviderConfig(), resourceKeyRingMaxConfig,
156189
),
190+
ConfigPlanChecks: resource.ConfigPlanChecks{
191+
PreApply: []plancheck.PlanCheck{
192+
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionNoop),
193+
},
194+
},
157195
Check: resource.ComposeAggregateTestCheckFunc(
158196
resource.ComposeAggregateTestCheckFunc(
159197
resource.TestCheckResourceAttr("data.stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
@@ -190,6 +228,11 @@ func TestAccKeyRingMax(t *testing.T) {
190228
{
191229
ConfigVariables: testConfigKeyRingVarsMaxUpdated(),
192230
Config: fmt.Sprintf("%s\n%s", testutil.KMSProviderConfig(), resourceKeyRingMaxConfig),
231+
ConfigPlanChecks: resource.ConfigPlanChecks{
232+
PreApply: []plancheck.PlanCheck{
233+
plancheck.ExpectResourceAction("stackit_kms_keyring.keyring", plancheck.ResourceActionReplace),
234+
},
235+
},
193236
Check: resource.ComposeAggregateTestCheckFunc(
194237
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "project_id", testutil.ProjectId),
195238
resource.TestCheckResourceAttr("stackit_kms_keyring.keyring", "region", testutil.Region),
@@ -202,3 +245,71 @@ func TestAccKeyRingMax(t *testing.T) {
202245
},
203246
})
204247
}
248+
249+
func testAccCheckDestroy(s *terraform.State) error {
250+
checkFunctions := []func(s *terraform.State) error{
251+
testAccCheckKeyRingDestroy,
252+
}
253+
254+
var errs []error
255+
256+
wg := sync.WaitGroup{}
257+
wg.Add(len(checkFunctions))
258+
259+
for _, f := range checkFunctions {
260+
go func() {
261+
err := f(s)
262+
if err != nil {
263+
errs = append(errs, err)
264+
}
265+
wg.Done()
266+
}()
267+
}
268+
wg.Wait()
269+
return errors.Join(errs...)
270+
}
271+
272+
func testAccCheckKeyRingDestroy(s *terraform.State) error {
273+
ctx := context.Background()
274+
var client *kms.APIClient
275+
var err error
276+
if testutil.KMSCustomEndpoint == "" {
277+
client, err = kms.NewAPIClient()
278+
} else {
279+
client, err = kms.NewAPIClient(
280+
coreConfig.WithEndpoint(testutil.KMSCustomEndpoint),
281+
)
282+
}
283+
if err != nil {
284+
return fmt.Errorf("creating client: %w", err)
285+
}
286+
287+
var errs []error
288+
289+
for _, rs := range s.RootModule().Resources {
290+
if rs.Type != "stackit_kms_keyring" {
291+
continue
292+
}
293+
keyRingId := strings.Split(rs.Primary.ID, core.Separator)[2]
294+
err := client.DeleteKeyRingExecute(ctx, testutil.ProjectId, testutil.Region, keyRingId)
295+
if err != nil {
296+
var oapiErr *oapierror.GenericOpenAPIError
297+
if errors.As(err, &oapiErr) {
298+
if oapiErr.StatusCode == http.StatusNotFound {
299+
continue
300+
}
301+
302+
// Workaround: when the delete endpoint is called for a keyring which has keys inside it (no matter if
303+
// they are scheduled for deletion or not, it will throw an HTTP 400 error and the keyring can't be
304+
// deleted then).
305+
// But at least we can delete all empty keyrings created by the keyring acc tests this way.
306+
if oapiErr.StatusCode == http.StatusBadRequest {
307+
continue
308+
}
309+
}
310+
errs = append(errs, fmt.Errorf("cannot trigger keyring deletion %q: %w", keyRingId, err))
311+
}
312+
}
313+
314+
return errors.Join(errs...)
315+
}

0 commit comments

Comments
 (0)