From 30156fd6e85378041de23646f76f427cdc1d9431 Mon Sep 17 00:00:00 2001 From: Patricia Salajova Date: Wed, 5 Nov 2025 11:00:41 +0100 Subject: [PATCH 1/5] Add func to construct index secret payload --- pkg/gsm-secrets/execution.go | 2 +- pkg/gsm-secrets/secrets.go | 15 ++++++++++ pkg/gsm-secrets/secrets_test.go | 49 +++++++++++++++++++++++++++++++++ pkg/gsm-secrets/types.go | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pkg/gsm-secrets/execution.go b/pkg/gsm-secrets/execution.go index 03421f3d4a..8006ee8847 100644 --- a/pkg/gsm-secrets/execution.go +++ b/pkg/gsm-secrets/execution.go @@ -266,7 +266,7 @@ func (a *Actions) CreateSecrets(ctx context.Context, secretsClient SecretManager } if s.Type == SecretTypeIndex { - s.Payload = fmt.Appendf(nil, "- updater-service-account") + s.Payload = ConstructIndexSecretContent([]string{}) a.SecretsToCreate[name] = s } diff --git a/pkg/gsm-secrets/secrets.go b/pkg/gsm-secrets/secrets.go index 14d9ed53eb..887784d475 100644 --- a/pkg/gsm-secrets/secrets.go +++ b/pkg/gsm-secrets/secrets.go @@ -3,6 +3,7 @@ package gsmsecrets import ( "fmt" "regexp" + "sort" "strings" "github.com/openshift/ci-tools/pkg/group" @@ -66,3 +67,17 @@ func VerifyIndexSecretContent(payload []byte) error { return nil } + +// ConstructIndexSecretContent constructs the index secret content from the secretsList, +// with UpdaterSASecretName included. +func ConstructIndexSecretContent(secretsList []string) []byte { + secretsList = append(secretsList, UpdaterSASecretName) + sort.Strings(secretsList) + + var formattedSecrets []string + for _, secret := range secretsList { + formattedSecrets = append(formattedSecrets, fmt.Sprintf("- %s", secret)) + } + + return []byte(strings.Join(formattedSecrets, "\n")) +} diff --git a/pkg/gsm-secrets/secrets_test.go b/pkg/gsm-secrets/secrets_test.go index 02d8ef08ab..93b422b435 100644 --- a/pkg/gsm-secrets/secrets_test.go +++ b/pkg/gsm-secrets/secrets_test.go @@ -165,6 +165,55 @@ func TestValidateSecretName(t *testing.T) { } } +func TestConstructIndexSecretContent(t *testing.T) { + testCases := []struct { + name string + secretsList []string + expectedOutput string + }{ + { + name: "empty list", + secretsList: []string{}, + expectedOutput: "- updater-service-account", + }, + { + name: "single secret", + secretsList: []string{"abc"}, + expectedOutput: "- abc\n- updater-service-account", + }, + { + name: "multiple secrets sorted", + secretsList: []string{"abc", "second-secret"}, + expectedOutput: "- abc\n- second-secret\n- updater-service-account", + }, + { + name: "multiple secrets unsorted", + secretsList: []string{"zebra", "apple", "banana"}, + expectedOutput: "- apple\n- banana\n- updater-service-account\n- zebra", + }, + { + name: "secrets with hyphens", + secretsList: []string{"my-secret", "another-secret"}, + expectedOutput: "- another-secret\n- my-secret\n- updater-service-account", + }, + { + name: "secrets that sort after updater-service-account", + secretsList: []string{"xyz", "zzz"}, + expectedOutput: "- updater-service-account\n- xyz\n- zzz", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ConstructIndexSecretContent(tc.secretsList) + actualOutput := string(result) + if actualOutput != tc.expectedOutput { + t.Errorf("Expected:\n%s\n\nGot:\n%s", tc.expectedOutput, actualOutput) + } + }) + } +} + func TestExtractCollectionFromSecretName(t *testing.T) { testCases := []struct { name string diff --git a/pkg/gsm-secrets/types.go b/pkg/gsm-secrets/types.go index dad81cd494..469d2a6155 100644 --- a/pkg/gsm-secrets/types.go +++ b/pkg/gsm-secrets/types.go @@ -15,6 +15,7 @@ const ( GCPMaxServiceAccountIDLength = 30 + UpdaterSASecretName = "updater-service-account" UpdaterSASecretSuffix = "__updater-service-account" IndexSecretSuffix = "____index" From 72a2538ff1b0839ac7ed41ebadc2826dc47e5c6d Mon Sep 17 00:00:00 2001 From: Patricia Salajova Date: Wed, 5 Nov 2025 11:44:26 +0100 Subject: [PATCH 2/5] Edit client so that *all* generated secrets are synced to GSM --- pkg/secrets/gsm.go | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/pkg/secrets/gsm.go b/pkg/secrets/gsm.go index 3cfe6d650a..db445ff532 100644 --- a/pkg/secrets/gsm.go +++ b/pkg/secrets/gsm.go @@ -3,7 +3,6 @@ package secrets import ( "context" "fmt" - "regexp" secretmanager "cloud.google.com/go/secretmanager/apiv1" "github.com/sirupsen/logrus" @@ -17,7 +16,6 @@ type gsmSyncDecorator struct { gsmClient *secretmanager.Client config gsm.Config ctx context.Context - pattern *regexp.Regexp } func NewGSMSyncDecorator(wrappedVaultClient Client, gcpProjectConfig gsm.Config, credentialsFile string) (Client, error) { @@ -33,14 +31,10 @@ func NewGSMSyncDecorator(wrappedVaultClient Client, gcpProjectConfig gsm.Config, return nil, fmt.Errorf("failed to create GSM client: %w", err) } - // Hardcoded pattern to match only the fields created for cluster-init secret - pattern := regexp.MustCompile(`^sa\.cluster-init\..*`) - return &gsmSyncDecorator{ Client: wrappedVaultClient, gsmClient: gsmClient, config: gcpProjectConfig, - pattern: pattern, ctx: ctx, }, nil } @@ -51,15 +45,12 @@ func (g *gsmSyncDecorator) SetFieldOnItem(itemName, fieldName string, fieldValue return err } - // Check if this field should sync to GSM (only cluster-init secrets) - if !g.pattern.MatchString(fieldName) { - return nil - } - // replace forbidden characters: // e.g., "sa.cluster-init.build01.config" -> "sa--dot--cluster-init--dot--build01--dot--config" fieldNameNormalized := gsm.NormalizeSecretName(fieldName) - secretName := fmt.Sprintf("%s__%s", "cluster-init", fieldNameNormalized) + + // item name will become the collection name: + secretName := fmt.Sprintf("%s__%s", itemName, fieldNameNormalized) labels := make(map[string]string) labels["jira-project"] = "dptp" @@ -71,7 +62,7 @@ func (g *gsmSyncDecorator) SetFieldOnItem(itemName, fieldName string, fieldValue logrus.WithError(err).Errorf("Failed to sync to GSM: %s", secretName) // Don't fail the Vault write } else { - logrus.Debugf("Successfully synced cluster-init secret to GSM: %s", secretName) + logrus.Debugf("Successfully synced secret '%s' to GSM", secretName) } return nil From 8556f716a36c7d1784a2004d3d8ce4952f041c60 Mon Sep 17 00:00:00 2001 From: Patricia Salajova Date: Wed, 5 Nov 2025 11:54:16 +0100 Subject: [PATCH 3/5] Add method to update index secret --- cmd/ci-secret-generator/main.go | 16 ++++++++++++++-- cmd/ci-secret-generator/main_test.go | 2 +- pkg/secrets/client.go | 1 + pkg/secrets/gsm.go | 9 +++++++++ pkg/secrets/vault.go | 9 +++++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/cmd/ci-secret-generator/main.go b/cmd/ci-secret-generator/main.go index 6428b92355..d15ff95a4f 100644 --- a/cmd/ci-secret-generator/main.go +++ b/cmd/ci-secret-generator/main.go @@ -211,10 +211,11 @@ func fmtExecCmdErr(action, cmd string, wrappedErr error, stdout, stderr []byte, stdout, stderrPreamble, stderr) } -func updateSecrets(config secretgenerator.Config, client secrets.Client, disabledClusters sets.Set[string]) error { +func updateSecrets(config secretgenerator.Config, client secrets.Client, disabledClusters sets.Set[string], GSMsyncEnabled bool) error { var errs []error for _, item := range config { logger := logrus.WithField("item", item.ItemName) + var secretsList []string for _, field := range item.Fields { logger = logger.WithFields(logrus.Fields{ "field": field.Name, @@ -239,6 +240,17 @@ func updateSecrets(config secretgenerator.Config, client secrets.Client, disable errs = append(errs, errors.New(msg)) continue } + if GSMsyncEnabled { + secretsList = append(secretsList, gsm.NormalizeSecretName(field.Name)) + } + } + + if GSMsyncEnabled { + indexPayload := gsm.ConstructIndexSecretContent(secretsList) + err := client.UpdateIndexSecret(gsm.GetIndexSecretName(item.ItemName), indexPayload) + if err != nil { + errs = append(errs, err) + } } // Adding the notes not empty check here since we dont want to overwrite any notes that might already be present @@ -326,7 +338,7 @@ func generateSecrets(o options, censor *secrets.DynamicCensor) (errs []error) { } } - if err := updateSecrets(o.config, client, o.disabledClusters); err != nil { + if err := updateSecrets(o.config, client, o.disabledClusters, o.enableGsmSync); err != nil { errs = append(errs, fmt.Errorf("failed to update secrets: %w", err)) } diff --git a/cmd/ci-secret-generator/main_test.go b/cmd/ci-secret-generator/main_test.go index 8462410992..d8ce7a7dc1 100644 --- a/cmd/ci-secret-generator/main_test.go +++ b/cmd/ci-secret-generator/main_test.go @@ -193,7 +193,7 @@ func TestVault(t *testing.T) { } } }() - if err := updateSecrets(tc.config, client, tc.disabledClusters); err != nil { + if err := updateSecrets(tc.config, client, tc.disabledClusters, false); err != nil { t.Errorf("failed to update secrets: %v", err) } list, err := vault.ListKV("secret") diff --git a/pkg/secrets/client.go b/pkg/secrets/client.go index 482cfaf7c5..70202b447e 100644 --- a/pkg/secrets/client.go +++ b/pkg/secrets/client.go @@ -18,6 +18,7 @@ type Client interface { ReadOnlyClient SetFieldOnItem(itemName, fieldName string, fieldValue []byte) error UpdateNotesOnItem(itemName string, notes string) error + UpdateIndexSecret(itemName string, payload []byte) error } type SecretUsageComparer interface { diff --git a/pkg/secrets/gsm.go b/pkg/secrets/gsm.go index db445ff532..597bcc423c 100644 --- a/pkg/secrets/gsm.go +++ b/pkg/secrets/gsm.go @@ -67,3 +67,12 @@ func (g *gsmSyncDecorator) SetFieldOnItem(itemName, fieldName string, fieldValue return nil } + +func (g *gsmSyncDecorator) UpdateIndexSecret(itemName string, payload []byte) error { + annotations := make(map[string]string) + annotations["request-information"] = "Created by periodic-ci-secret-generator." + if err := gsm.CreateOrUpdateSecret(g.ctx, g.gsmClient, g.config.ProjectIdNumber, gsm.GetIndexSecretName(itemName), payload, nil, annotations); err != nil { + return err + } + return nil +} diff --git a/pkg/secrets/vault.go b/pkg/secrets/vault.go index c82074ec28..721acbbec4 100644 --- a/pkg/secrets/vault.go +++ b/pkg/secrets/vault.go @@ -44,6 +44,10 @@ func (d dryRunClient) GetInUseInformationForAllItems(_ string) (map[string]Secre return nil, nil } +func (d dryRunClient) UpdateIndexSecret(itemName string, payload []byte) error { + return nil +} + func (d dryRunClient) GetUserSecrets() (map[types.NamespacedName]map[string]string, error) { return nil, nil } @@ -130,6 +134,11 @@ func (c *vaultClient) GetFieldOnItem(itemName, fieldName string) ([]byte, error) return c.getSecretAtPath(itemName, fieldName) } +func (c *vaultClient) UpdateIndexSecret(itemName string, payload []byte) error { + // No-op: index secrets are only relevant for GSM syncing + return nil +} + func (c *vaultClient) GetInUseInformationForAllItems(optionalSubPath string) (map[string]SecretUsageComparer, error) { prefix := c.prefix if optionalSubPath != "" { From 9b67316003c6ca09529533c3e88397b86449a500 Mon Sep 17 00:00:00 2001 From: Patricia Salajova Date: Mon, 10 Nov 2025 11:20:38 +0100 Subject: [PATCH 4/5] Add tests --- cmd/ci-secret-generator/main_test.go | 318 +++++++++++++++++++++++++++ pkg/secrets/client_mock.go | 296 +++++++++++++++++++++++++ 2 files changed, 614 insertions(+) create mode 100644 pkg/secrets/client_mock.go diff --git a/cmd/ci-secret-generator/main_test.go b/cmd/ci-secret-generator/main_test.go index d8ce7a7dc1..ba9e82ca0c 100644 --- a/cmd/ci-secret-generator/main_test.go +++ b/cmd/ci-secret-generator/main_test.go @@ -7,11 +7,13 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "go.uber.org/mock/gomock" "k8s.io/apimachinery/pkg/util/sets" "github.com/openshift/ci-tools/pkg/api/secretbootstrap" "github.com/openshift/ci-tools/pkg/api/secretgenerator" + gsm "github.com/openshift/ci-tools/pkg/gsm-secrets" "github.com/openshift/ci-tools/pkg/secrets" "github.com/openshift/ci-tools/pkg/testhelper" "github.com/openshift/ci-tools/pkg/vaultclient" @@ -435,3 +437,319 @@ func TestValidateConfig(t *testing.T) { }) } } + +func TestUpdateSecretsWithGSMIndex(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + config secretgenerator.Config + GSMsyncEnabled bool + expectedIndexCalls int + verifyIndexPayload func(t *testing.T, itemName string, payload []byte) + }{ + { + name: "GSM sync enabled - single field creates index secret", + config: secretgenerator.Config{{ + ItemName: "test-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + }, + }}, + GSMsyncEnabled: true, + expectedIndexCalls: 1, + verifyIndexPayload: func(t *testing.T, itemName string, payload []byte) { + expectedItemName := gsm.GetIndexSecretName("test-item") + if itemName != expectedItemName { + t.Errorf("expected item name %q, got %q", expectedItemName, itemName) + } + expectedPayload := string(gsm.ConstructIndexSecretContent([]string{"field1"})) + if string(payload) != expectedPayload { + t.Errorf("expected payload %q, got %q", expectedPayload, string(payload)) + } + }, + }, + { + name: "GSM sync enabled - multiple fields create index with all fields", + config: secretgenerator.Config{{ + ItemName: "multi-field-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + {Name: "field2", Cmd: "printf 'value2'"}, + {Name: "field3", Cmd: "printf 'value3'"}, + }, + }}, + GSMsyncEnabled: true, + expectedIndexCalls: 1, + verifyIndexPayload: func(t *testing.T, itemName string, payload []byte) { + expectedItemName := gsm.GetIndexSecretName("multi-field-item") + if itemName != expectedItemName { + t.Errorf("expected item name %q, got %q", expectedItemName, itemName) + } + expectedPayload := string(gsm.ConstructIndexSecretContent([]string{"field1", "field2", "field3"})) + if string(payload) != expectedPayload { + t.Errorf("expected payload %q, got %q", expectedPayload, string(payload)) + } + }, + }, + { + name: "GSM sync enabled - multiple items create multiple index secrets", + config: secretgenerator.Config{ + { + ItemName: "item1", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + }, + }, + { + ItemName: "item2", + Fields: []secretgenerator.FieldGenerator{ + {Name: "fieldA", Cmd: "printf 'valueA'"}, + {Name: "fieldB", Cmd: "printf 'valueB'"}, + }, + }, + }, + GSMsyncEnabled: true, + expectedIndexCalls: 2, + verifyIndexPayload: func(t *testing.T, itemName string, payload []byte) { + // This will be called twice, once for each item + if itemName == gsm.GetIndexSecretName("item1") { + expectedPayload := string(gsm.ConstructIndexSecretContent([]string{"field1"})) + if string(payload) != expectedPayload { + t.Errorf("expected payload for item1 %q, got %q", expectedPayload, string(payload)) + } + } else if itemName == gsm.GetIndexSecretName("item2") { + expectedPayload := string(gsm.ConstructIndexSecretContent([]string{"fieldA", "fieldB"})) + if string(payload) != expectedPayload { + t.Errorf("expected payload for item2 %q, got %q", expectedPayload, string(payload)) + } + } else { + t.Errorf("unexpected item name: %q", itemName) + } + }, + }, + { + name: "GSM sync disabled - no index secret created", + config: secretgenerator.Config{{ + ItemName: "test-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + {Name: "field2", Cmd: "printf 'value2'"}, + }, + }}, + GSMsyncEnabled: false, + expectedIndexCalls: 0, + }, + { + name: "GSM sync enabled - disabled cluster field excluded from index", + config: secretgenerator.Config{{ + ItemName: "cluster-test-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'", Cluster: "enabled-cluster"}, + {Name: "field2", Cmd: "printf 'value2'", Cluster: "disabled-cluster"}, + {Name: "field3", Cmd: "printf 'value3'", Cluster: "enabled-cluster"}, + }, + }}, + GSMsyncEnabled: true, + expectedIndexCalls: 1, + verifyIndexPayload: func(t *testing.T, itemName string, payload []byte) { + // Only field1 and field3 should be in the index (field2 is from disabled cluster) + expectedPayload := string(gsm.ConstructIndexSecretContent([]string{"field1", "field3"})) + if string(payload) != expectedPayload { + t.Errorf("expected payload %q, got %q", expectedPayload, string(payload)) + } + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockClient := secrets.NewMockClient(mockCtrl) + mockClient.EXPECT(). + SetFieldOnItem(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + + // set expectations for UpdateIndexSecret based on test case + if tc.expectedIndexCalls > 0 { + mockClient.EXPECT(). + UpdateIndexSecret(gomock.Any(), gomock.Any()). + Times(tc.expectedIndexCalls). + DoAndReturn(func(itemName string, payload []byte) error { + if tc.verifyIndexPayload != nil { + tc.verifyIndexPayload(t, itemName, payload) + } + return nil + }) + } + + // for the disabled cluster test case, pass the disabled clusters set + var disabledClusters sets.Set[string] + if tc.name == "GSM sync enabled - disabled cluster field excluded from index" { + disabledClusters = sets.New[string]("disabled-cluster") + } + + err := updateSecrets(tc.config, mockClient, disabledClusters, tc.GSMsyncEnabled) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} + +func TestUpdateSecretsWithGSMIndexErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + config secretgenerator.Config + setFieldError error + updateIndexError error + expectedErrorsLen int + partialFailure bool + partialFailureConfig map[string]error // field name -> error (nil = success) + expectedIndexFields []string // fields that should appear in index for partial failure + }{ + { + name: "UpdateIndexSecret error is aggregated", + config: secretgenerator.Config{{ + ItemName: "test-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + }, + }}, + updateIndexError: errors.New("GSM update failed"), + expectedErrorsLen: 1, + }, + { + name: "SetFieldOnItem error - all fields fail, index contains only updater-service-account", + config: secretgenerator.Config{{ + ItemName: "test-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + {Name: "field2", Cmd: "printf 'value2'"}, + }, + }}, + setFieldError: errors.New("field upload failed"), + updateIndexError: nil, // Index update should still be called + expectedErrorsLen: 2, // Both field errors + }, + { + name: "SetFieldOnItem partial failure - only successful fields in index", + config: secretgenerator.Config{{ + ItemName: "test-item", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + {Name: "field2", Cmd: "printf 'value2'"}, + {Name: "field3", Cmd: "printf 'value3'"}, + }, + }}, + partialFailure: true, + partialFailureConfig: map[string]error{ + "field1": nil, // succeeds + "field2": errors.New("field2 upload failed"), // fails + "field3": nil, // succeeds + }, + expectedIndexFields: []string{"field1", "field3"}, // only successful fields + expectedErrorsLen: 1, // only field2 error + }, + { + name: "Multiple items - index errors are aggregated", + config: secretgenerator.Config{ + { + ItemName: "item1", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field1", Cmd: "printf 'value1'"}, + }, + }, + { + ItemName: "item2", + Fields: []secretgenerator.FieldGenerator{ + {Name: "field2", Cmd: "printf 'value2'"}, + }, + }, + }, + updateIndexError: errors.New("GSM update failed"), + expectedErrorsLen: 2, // One error for each item's index update + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockClient := secrets.NewMockClient(mockCtrl) + + if tc.partialFailure { + // For partial failure, return different errors based on field name + mockClient.EXPECT(). + SetFieldOnItem(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + DoAndReturn(func(itemName, fieldName string, fieldValue []byte) error { + if err, exists := tc.partialFailureConfig[fieldName]; exists { + return err + } + return nil + }) + } else if tc.setFieldError != nil { + mockClient.EXPECT(). + SetFieldOnItem(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(tc.setFieldError) + } else { + mockClient.EXPECT(). + SetFieldOnItem(gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + } + + // Set expectations for UpdateIndexSecret - it should always be called in these tests + if tc.updateIndexError != nil { + mockClient.EXPECT(). + UpdateIndexSecret(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(tc.updateIndexError) + } else { + // Verify that when SetFieldOnItem fails, the index only contains successful fields + if tc.setFieldError != nil || tc.partialFailure { + mockClient.EXPECT(). + UpdateIndexSecret(gomock.Any(), gomock.Any()). + AnyTimes(). + DoAndReturn(func(itemName string, payload []byte) error { + var expectedPayload string + if tc.partialFailure { + // For partial failure, index should contain only successful fields + expectedPayload = string(gsm.ConstructIndexSecretContent(tc.expectedIndexFields)) + } else { + // When all fields fail, index should only contain updater-service-account + expectedPayload = string(gsm.ConstructIndexSecretContent([]string{})) + } + if string(payload) != expectedPayload { + t.Errorf("expected index payload %q, got %q", expectedPayload, string(payload)) + } + return nil + }) + } else { + mockClient.EXPECT(). + UpdateIndexSecret(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + } + } + + err := updateSecrets(tc.config, mockClient, nil, true) + if err == nil { + t.Errorf("expected error but got nil") + return + } + errStr := err.Error() + if errStr == "" { + t.Errorf("expected non-empty error message") + } + }) + } +} diff --git a/pkg/secrets/client_mock.go b/pkg/secrets/client_mock.go new file mode 100644 index 0000000000..9c38fbcf98 --- /dev/null +++ b/pkg/secrets/client_mock.go @@ -0,0 +1,296 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/secrets/client.go +// +// Generated by this command: +// +// mockgen -source=pkg/secrets/client.go -destination=pkg/secrets/client_mock.go -package=secrets +// + +// Package secrets is a generated GoMock package. +package secrets + +import ( + reflect "reflect" + time "time" + + gomock "go.uber.org/mock/gomock" + + types "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" +) + +// MockReadOnlyClient is a mock of ReadOnlyClient interface. +type MockReadOnlyClient struct { + ctrl *gomock.Controller + recorder *MockReadOnlyClientMockRecorder + isgomock struct{} +} + +// MockReadOnlyClientMockRecorder is the mock recorder for MockReadOnlyClient. +type MockReadOnlyClientMockRecorder struct { + mock *MockReadOnlyClient +} + +// NewMockReadOnlyClient creates a new mock instance. +func NewMockReadOnlyClient(ctrl *gomock.Controller) *MockReadOnlyClient { + mock := &MockReadOnlyClient{ctrl: ctrl} + mock.recorder = &MockReadOnlyClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockReadOnlyClient) EXPECT() *MockReadOnlyClientMockRecorder { + return m.recorder +} + +// GetFieldOnItem mocks base method. +func (m *MockReadOnlyClient) GetFieldOnItem(itemName, fieldName string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFieldOnItem", itemName, fieldName) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFieldOnItem indicates an expected call of GetFieldOnItem. +func (mr *MockReadOnlyClientMockRecorder) GetFieldOnItem(itemName, fieldName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFieldOnItem", reflect.TypeOf((*MockReadOnlyClient)(nil).GetFieldOnItem), itemName, fieldName) +} + +// GetInUseInformationForAllItems mocks base method. +func (m *MockReadOnlyClient) GetInUseInformationForAllItems(optionalPrefix string) (map[string]SecretUsageComparer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInUseInformationForAllItems", optionalPrefix) + ret0, _ := ret[0].(map[string]SecretUsageComparer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInUseInformationForAllItems indicates an expected call of GetInUseInformationForAllItems. +func (mr *MockReadOnlyClientMockRecorder) GetInUseInformationForAllItems(optionalPrefix any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInUseInformationForAllItems", reflect.TypeOf((*MockReadOnlyClient)(nil).GetInUseInformationForAllItems), optionalPrefix) +} + +// GetUserSecrets mocks base method. +func (m *MockReadOnlyClient) GetUserSecrets() (map[types.NamespacedName]map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserSecrets") + ret0, _ := ret[0].(map[types.NamespacedName]map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserSecrets indicates an expected call of GetUserSecrets. +func (mr *MockReadOnlyClientMockRecorder) GetUserSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserSecrets", reflect.TypeOf((*MockReadOnlyClient)(nil).GetUserSecrets)) +} + +// HasItem mocks base method. +func (m *MockReadOnlyClient) HasItem(itemname string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasItem", itemname) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasItem indicates an expected call of HasItem. +func (mr *MockReadOnlyClientMockRecorder) HasItem(itemname any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasItem", reflect.TypeOf((*MockReadOnlyClient)(nil).HasItem), itemname) +} + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder + isgomock struct{} +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// GetFieldOnItem mocks base method. +func (m *MockClient) GetFieldOnItem(itemName, fieldName string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFieldOnItem", itemName, fieldName) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFieldOnItem indicates an expected call of GetFieldOnItem. +func (mr *MockClientMockRecorder) GetFieldOnItem(itemName, fieldName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFieldOnItem", reflect.TypeOf((*MockClient)(nil).GetFieldOnItem), itemName, fieldName) +} + +// GetInUseInformationForAllItems mocks base method. +func (m *MockClient) GetInUseInformationForAllItems(optionalPrefix string) (map[string]SecretUsageComparer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInUseInformationForAllItems", optionalPrefix) + ret0, _ := ret[0].(map[string]SecretUsageComparer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetInUseInformationForAllItems indicates an expected call of GetInUseInformationForAllItems. +func (mr *MockClientMockRecorder) GetInUseInformationForAllItems(optionalPrefix any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInUseInformationForAllItems", reflect.TypeOf((*MockClient)(nil).GetInUseInformationForAllItems), optionalPrefix) +} + +// GetUserSecrets mocks base method. +func (m *MockClient) GetUserSecrets() (map[types.NamespacedName]map[string]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserSecrets") + ret0, _ := ret[0].(map[types.NamespacedName]map[string]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserSecrets indicates an expected call of GetUserSecrets. +func (mr *MockClientMockRecorder) GetUserSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserSecrets", reflect.TypeOf((*MockClient)(nil).GetUserSecrets)) +} + +// HasItem mocks base method. +func (m *MockClient) HasItem(itemname string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasItem", itemname) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasItem indicates an expected call of HasItem. +func (mr *MockClientMockRecorder) HasItem(itemname any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasItem", reflect.TypeOf((*MockClient)(nil).HasItem), itemname) +} + +// SetFieldOnItem mocks base method. +func (m *MockClient) SetFieldOnItem(itemName, fieldName string, fieldValue []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetFieldOnItem", itemName, fieldName, fieldValue) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetFieldOnItem indicates an expected call of SetFieldOnItem. +func (mr *MockClientMockRecorder) SetFieldOnItem(itemName, fieldName, fieldValue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFieldOnItem", reflect.TypeOf((*MockClient)(nil).SetFieldOnItem), itemName, fieldName, fieldValue) +} + +// UpdateIndexSecret mocks base method. +func (m *MockClient) UpdateIndexSecret(itemName string, payload []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateIndexSecret", itemName, payload) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateIndexSecret indicates an expected call of UpdateIndexSecret. +func (mr *MockClientMockRecorder) UpdateIndexSecret(itemName, payload any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIndexSecret", reflect.TypeOf((*MockClient)(nil).UpdateIndexSecret), itemName, payload) +} + +// UpdateNotesOnItem mocks base method. +func (m *MockClient) UpdateNotesOnItem(itemName, notes string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNotesOnItem", itemName, notes) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNotesOnItem indicates an expected call of UpdateNotesOnItem. +func (mr *MockClientMockRecorder) UpdateNotesOnItem(itemName, notes any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNotesOnItem", reflect.TypeOf((*MockClient)(nil).UpdateNotesOnItem), itemName, notes) +} + +// MockSecretUsageComparer is a mock of SecretUsageComparer interface. +type MockSecretUsageComparer struct { + ctrl *gomock.Controller + recorder *MockSecretUsageComparerMockRecorder + isgomock struct{} +} + +// MockSecretUsageComparerMockRecorder is the mock recorder for MockSecretUsageComparer. +type MockSecretUsageComparerMockRecorder struct { + mock *MockSecretUsageComparer +} + +// NewMockSecretUsageComparer creates a new mock instance. +func NewMockSecretUsageComparer(ctrl *gomock.Controller) *MockSecretUsageComparer { + mock := &MockSecretUsageComparer{ctrl: ctrl} + mock.recorder = &MockSecretUsageComparerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSecretUsageComparer) EXPECT() *MockSecretUsageComparerMockRecorder { + return m.recorder +} + +// LastChanged mocks base method. +func (m *MockSecretUsageComparer) LastChanged() time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LastChanged") + ret0, _ := ret[0].(time.Time) + return ret0 +} + +// LastChanged indicates an expected call of LastChanged. +func (mr *MockSecretUsageComparerMockRecorder) LastChanged() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastChanged", reflect.TypeOf((*MockSecretUsageComparer)(nil).LastChanged)) +} + +// SuperfluousFields mocks base method. +func (m *MockSecretUsageComparer) SuperfluousFields() sets.Set[string] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SuperfluousFields") + ret0, _ := ret[0].(sets.Set[string]) + return ret0 +} + +// SuperfluousFields indicates an expected call of SuperfluousFields. +func (mr *MockSecretUsageComparerMockRecorder) SuperfluousFields() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SuperfluousFields", reflect.TypeOf((*MockSecretUsageComparer)(nil).SuperfluousFields)) +} + +// UnusedFields mocks base method. +func (m *MockSecretUsageComparer) UnusedFields(inUse sets.Set[string]) sets.Set[string] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnusedFields", inUse) + ret0, _ := ret[0].(sets.Set[string]) + return ret0 +} + +// UnusedFields indicates an expected call of UnusedFields. +func (mr *MockSecretUsageComparerMockRecorder) UnusedFields(inUse any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnusedFields", reflect.TypeOf((*MockSecretUsageComparer)(nil).UnusedFields), inUse) +} From 435ae0241350a6c36e273ade25c3a3064634dee8 Mon Sep 17 00:00:00 2001 From: Patricia Salajova Date: Tue, 18 Nov 2025 10:49:32 +0100 Subject: [PATCH 5/5] Add parallel testing also within child tests --- cmd/ci-secret-generator/main_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/ci-secret-generator/main_test.go b/cmd/ci-secret-generator/main_test.go index ba9e82ca0c..eb929d3e48 100644 --- a/cmd/ci-secret-generator/main_test.go +++ b/cmd/ci-secret-generator/main_test.go @@ -679,6 +679,7 @@ func TestUpdateSecretsWithGSMIndexErrors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + t.Parallel() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish()