Skip to content

feat(kms): add wait handler for key ring creation #3393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 1, 2025
Merged
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
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions services/kms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`

Expand Down
2 changes: 1 addition & 1 deletion services/kms/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.3.1
v0.4.0
23 changes: 23 additions & 0 deletions services/kms/wait/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
102 changes: 102 additions & 0 deletions services/kms/wait/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
Loading