From 0996a87ecb6806a827b25ad960d8afd8675a0a05 Mon Sep 17 00:00:00 2001 From: Ruben Hoenle Date: Tue, 29 Jul 2025 16:20:49 +0200 Subject: [PATCH] feat(kms): add wait handler for key ring creation relates to stackitcloud/terraform-provider-stackit#897 --- CHANGELOG.md | 7 ++- services/kms/CHANGELOG.md | 3 + services/kms/VERSION | 2 +- services/kms/wait/wait.go | 23 ++++++++ services/kms/wait/wait_test.go | 102 +++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4eba25f1..73646b56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,8 +31,11 @@ - **Dependencies:** Bump `github.com/golang-jwt/jwt/v5` from `v5.2.2` to `v5.2.3` - `intake`: [v0.1.0](services/intake/CHANGELOG.md#v010) - **New**: STACKIT Intake module can be used to manage the STACKIT Intake. Manage your `IntakeRunners`, `Intakes` and `IntakeUsers` -- `kms`: [v0.3.1](services/kms/CHANGELOG.md#v031) - - **Dependencies:** Bump `github.com/golang-jwt/jwt/v5` from `v5.2.2` to `v5.2.3` +- `kms`: + - [v0.4.0](services/kms/CHANGELOG.md#v040) + - **Feature:** Add new wait handler for key ring creation (`CreateKeyRingWaitHandler`) + - [v0.3.1](services/kms/CHANGELOG.md#v031) + - **Dependencies:** Bump `github.com/golang-jwt/jwt/v5` from `v5.2.2` to `v5.2.3` - `lbapplication`: [v0.5.1](services/lbapplication/CHANGELOG.md#v051) - **Dependencies:** Bump `github.com/golang-jwt/jwt/v5` from `v5.2.2` to `v5.2.3` - `loadbalancer`: [v1.5.1](services/loadbalancer/CHANGELOG.md#v151) diff --git a/services/kms/CHANGELOG.md b/services/kms/CHANGELOG.md index 47ca3178b..bc9f37756 100644 --- a/services/kms/CHANGELOG.md +++ b/services/kms/CHANGELOG.md @@ -1,3 +1,6 @@ +## v0.4.0 +- **Feature:** Add new wait handler for key ring creation (`CreateKeyRingWaitHandler`) + ## v0.3.1 - **Dependencies:** Bump `github.com/golang-jwt/jwt/v5` from `v5.2.2` to `v5.2.3` diff --git a/services/kms/VERSION b/services/kms/VERSION index 1e66a6163..fb7a04cff 100644 --- a/services/kms/VERSION +++ b/services/kms/VERSION @@ -1 +1 @@ -v0.3.1 \ No newline at end of file +v0.4.0 diff --git a/services/kms/wait/wait.go b/services/kms/wait/wait.go index 2f9ea576d..ad00630f9 100644 --- a/services/kms/wait/wait.go +++ b/services/kms/wait/wait.go @@ -46,10 +46,33 @@ const ( type ApiKmsClient interface { GetKeyExecute(ctx context.Context, projectId string, regionId string, keyRingId string, keyId string) (*kms.Key, error) + GetKeyRingExecute(ctx context.Context, projectId string, regionId string, keyRingId string) (*kms.KeyRing, error) GetVersionExecute(ctx context.Context, projectId string, regionId string, keyRingId string, keyId string, versionNumber int64) (*kms.Version, error) GetWrappingKeyExecute(ctx context.Context, projectId string, regionId string, keyRingId string, wrappingKeyId string) (*kms.WrappingKey, error) } +func CreateKeyRingWaitHandler(ctx context.Context, client ApiKmsClient, projectId, region, keyRingId string) *wait.AsyncActionHandler[kms.KeyRing] { + handler := wait.New(func() (bool, *kms.KeyRing, error) { + response, err := client.GetKeyRingExecute(ctx, projectId, region, keyRingId) + if err != nil { + return false, nil, err + } + + if response.State != nil { + switch *response.State { + case kms.KEYRINGSTATE_CREATING: + return false, nil, nil + default: + return true, response, nil + } + } + + return false, nil, nil + }) + handler.SetTimeout(10 * time.Minute) + return handler +} + func CreateOrUpdateKeyWaitHandler(ctx context.Context, client ApiKmsClient, projectId, region, keyRingId, keyId string) *wait.AsyncActionHandler[kms.Key] { handler := wait.New(func() (bool, *kms.Key, error) { response, err := client.GetKeyExecute(ctx, projectId, region, keyRingId, keyId) diff --git a/services/kms/wait/wait_test.go b/services/kms/wait/wait_test.go index 2b122a41f..9e8c9850f 100644 --- a/services/kms/wait/wait_test.go +++ b/services/kms/wait/wait_test.go @@ -30,6 +30,11 @@ type keyResponse struct { err error } +type keyRingResponse struct { + keyRing *kms.KeyRing + err error +} + type versionResponse struct { version *kms.Version err error @@ -42,9 +47,11 @@ type wrappingKeyResponse struct { type apiKmsMocked struct { idxKeyResponse int + idxKeyRingResponse int idxVersionResponse int idxWrappingKeyResponse int keyResponses []keyResponse + keyRingResponses []keyRingResponse versionResponses []versionResponse wrappingKeyResponses []wrappingKeyResponse } @@ -73,6 +80,14 @@ func (a *apiKmsMocked) GetKeyExecute(_ context.Context, _, _, _, _ string) (*kms return resp.key, resp.err } +// GetKeyRingExecute implements ApiKmsClient. +func (a *apiKmsMocked) GetKeyRingExecute(_ context.Context, _, _, _ string) (*kms.KeyRing, error) { + resp := a.keyRingResponses[a.idxKeyRingResponse] + a.idxKeyRingResponse++ + a.idxKeyRingResponse %= len(a.keyRingResponses) + return resp.keyRing, resp.err +} + func fixtureKey(state kms.KeyState) *kms.Key { return &kms.Key{ Algorithm: kms.ALGORITHM_AES_256_GCM.Ptr(), @@ -89,6 +104,16 @@ func fixtureKey(state kms.KeyState) *kms.Key { } } +func fixtureKeyRing(state kms.KeyRingState) *kms.KeyRing { + return &kms.KeyRing{ + CreatedAt: &testDate, + Description: utils.Ptr("test-description"), + DisplayName: utils.Ptr("test-displayname"), + Id: &testKeyRingId, + State: &state, + } +} + func fixtureWrappingKey(state kms.WrappingKeyState) *kms.WrappingKey { return &kms.WrappingKey{ Algorithm: kms.WRAPPINGALGORITHM__2048_OAEP_SHA256.Ptr(), @@ -118,6 +143,83 @@ func fixtureVersion(version int, disabled bool, state kms.VersionState) *kms.Ver } } +func TestCreateKeyRingWaitHandler(t *testing.T) { + tests := []struct { + name string + responses []keyRingResponse + want *kms.KeyRing + wantErr bool + }{ + { + name: "create succeeded immediately", + responses: []keyRingResponse{ + {fixtureKeyRing(kms.KEYRINGSTATE_ACTIVE), nil}, + }, + want: fixtureKeyRing(kms.KEYRINGSTATE_ACTIVE), + wantErr: false, + }, + { + name: "create succeeded delayed", + responses: []keyRingResponse{ + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + {fixtureKeyRing(kms.KEYRINGSTATE_ACTIVE), nil}, + }, + want: fixtureKeyRing(kms.KEYRINGSTATE_ACTIVE), + wantErr: false, + }, + { + name: "create failed delayed", + responses: []keyRingResponse{ + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + {fixtureKeyRing(kms.KEYRINGSTATE_DELETED), nil}, + }, + want: fixtureKeyRing(kms.KEYRINGSTATE_DELETED), + wantErr: false, + }, + { + name: "timeout", + responses: []keyRingResponse{ + {fixtureKeyRing(kms.KEYRINGSTATE_CREATING), nil}, + }, + want: nil, + wantErr: true, + }, + { + name: "broken state", + responses: []keyRingResponse{ + {fixtureKeyRing("bogus"), nil}, + }, + want: fixtureKeyRing("bogus"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + client := &apiKmsMocked{ + keyRingResponses: tt.responses, + } + + handler := CreateKeyRingWaitHandler(ctx, client, testProject, testRegion, testKeyRingId) + got, err := handler.SetTimeout(1 * time.Second). + SetThrottle(250 * time.Millisecond). + WaitWithContext(ctx) + + if (err != nil) != tt.wantErr { + t.Fatalf("unexpected error response. want %v but got %v ", tt.wantErr, err) + } + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("differing key %s", diff) + } + }) + } +} + func TestCreateOrUpdateKeyWaitHandler(t *testing.T) { tests := []struct { name string