Skip to content

Conversation

@psalajova
Copy link
Contributor

@psalajova psalajova commented Nov 5, 2025

Extends ci-secret-generator to sync all generated secrets to GSM (previously only cluster-init secrets), and generates index secrets to track collection membership.
Part of the Vault-to-GSM migration (see design doc).

What Changed

  1. Removed pattern filter - All (~460) secrets generated by ci-secret-generator will now also sync to GSM as well as to Vault
  2. Added index secret generation - Creates {collection}____index containing sorted list of secrets per collection

Why

  • We need all secrets generated by ci-secret-generator to be available in GSM
  • GSM doesn't support multi-key secrets like Vault - each key becomes a separate secret
    • Example: Vault's build_farm item (378 keys) → 378 GSM secrets + 1 index secret
  • Keeps indexes up-to-date as config changes

Structure in GSM
For each collection (e.g., build_farm):

build_farm__sa--dot--deck--dot--build01--dot--config
build_farm__sa--dot--sinker--dot--build02--dot--token--dot--txt
...
build_farm____index

Index content:

- sa--dot--deck--dot--build01--dot--config
- sa--dot--sinker--dot--build02--dot--token--dot--txt
- updater-service-account

Tests created with the help of cursor AI.

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds GSM index-secret support: new client method UpdateIndexSecret, GSM sync logic accumulates and writes index payloads per item, index-content constructor and UpdaterSASecretName constant added, call sites extended to pass a GSM-sync flag, and tests plus GoMock mocks updated to exercise index behaviour.

Changes

Cohort / File(s) Summary
CLI Secret Generator
cmd/ci-secret-generator/main.go, cmd/ci-secret-generator/main_test.go
updateSecrets signature extended to accept a GSM sync boolean (GSMsyncEnabled / gsmSyncEnabled); call sites updated (e.g., generateSecrets); tests updated and new tests added to exercise GSM index behaviour and mock expectations.
GSM index content & types
pkg/gsm-secrets/types.go, pkg/gsm-secrets/secrets.go, pkg/gsm-secrets/secrets_test.go
Added exported constant UpdaterSASecretName = "updater-service-account" and new function ConstructIndexSecretContent(secretsList []string) []byte which appends the updater SA, sorts entries, and formats them as - <secret> lines; added tests for the constructor (duplicate test function present).
GSM execution
pkg/gsm-secrets/execution.go
Replaced prior hardcoded index payload generation with ConstructIndexSecretContent([]string{}) for SecretTypeIndex secrets before create/update.
Secrets client API & mocks
pkg/secrets/client.go, pkg/secrets/client_mock.go
Added UpdateIndexSecret(itemName string, payload []byte) error to the Client interface; generated GoMock file added/updated to include mocks and recorder helpers for the new method.
GSM sync implementation
pkg/secrets/gsm.go
Removed pattern/regexp filtering from gsmSyncDecorator; changed per-field secret naming from a fixed prefix to "<itemName>__<fieldNormalized>"; added UpdateIndexSecret(itemName string, payload []byte) method to create/update item index secrets with annotations.
Vault/dry-run clients
pkg/secrets/vault.go
Added no-op UpdateIndexSecret implementations to dryRunClient and vaultClient (return nil).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Check the duplicated test TestConstructIndexSecretContent in pkg/gsm-secrets/secrets_test.go (remove one duplicate).
  • Verify all implementations and usages of the updated Client interface are compiled/updated (real clients, dry-run, and mocks).
  • Review removal of the regexp/pattern filter in pkg/secrets/gsm.go to ensure GSM sync scope is intentional.
  • Confirm the new dynamic secret naming "<itemName>__<fieldNormalized>" and index secret naming align with existing conventions and won't collide with other secrets.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 9b67316 and 435ae02.

📒 Files selected for processing (1)
  • cmd/ci-secret-generator/main_test.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/ci-secret-generator/main_test.go

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci bot requested review from Prucek and bear-redhat November 5, 2025 11:01
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 5, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: psalajova

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Nov 5, 2025
@psalajova
Copy link
Contributor Author

/cc @danilo-gemoli

@openshift-ci openshift-ci bot requested a review from danilo-gemoli November 5, 2025 11:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/gsm-secrets/secrets.go (1)

71-83: Function logic is correct.

The ConstructIndexSecretContent function properly:

  • Includes the updater service account via UpdaterSASecretName
  • Sorts the list for deterministic output
  • Formats each entry as "- "

Note: The function modifies the input slice by appending. While this is not currently an issue since callers pass a new slice, consider documenting this behavior or creating a copy internally for safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 5280cee and 8556f71.

📒 Files selected for processing (9)
  • cmd/ci-secret-generator/main.go (3 hunks)
  • cmd/ci-secret-generator/main_test.go (1 hunks)
  • pkg/gsm-secrets/execution.go (1 hunks)
  • pkg/gsm-secrets/secrets.go (2 hunks)
  • pkg/gsm-secrets/secrets_test.go (1 hunks)
  • pkg/gsm-secrets/types.go (1 hunks)
  • pkg/secrets/client.go (1 hunks)
  • pkg/secrets/gsm.go (2 hunks)
  • pkg/secrets/vault.go (2 hunks)
🔇 Additional comments (12)
pkg/gsm-secrets/types.go (1)

18-18: LGTM! Clear constant definition.

The new UpdaterSASecretName constant provides a centralized definition for the updater service account secret name, which is used in index secret content construction. This improves maintainability.

pkg/gsm-secrets/execution.go (1)

268-271: Good refactoring to use centralized index content construction.

The change from inline formatting to ConstructIndexSecretContent([]string{}) centralizes the index secret payload logic, improving consistency across the codebase.

pkg/secrets/client.go (1)

21-21: LGTM! Clean interface extension.

The new UpdateIndexSecret method cleanly extends the Client interface to support index secret updates. All implementations properly satisfy this interface requirement.

pkg/secrets/vault.go (2)

47-49: LGTM! Appropriate no-op implementation for dry-run client.

The no-op implementation is correct for the dry-run client, which doesn't need to perform actual index secret operations.


137-140: LGTM! Clear no-op with good documentation.

The no-op implementation is appropriate since index secrets are GSM-specific and not needed for Vault operations. The comment clearly explains this rationale.

cmd/ci-secret-generator/main_test.go (1)

196-196: LGTM! Test correctly updated for new signature.

The test properly passes false for the new GSMsyncEnabled parameter, maintaining the existing test behavior while adapting to the extended function signature.

cmd/ci-secret-generator/main.go (4)

214-214: LGTM! Clean signature extension.

The addition of the GSMsyncEnabled parameter cleanly extends the function to support conditional GSM index secret updates without breaking existing behavior.


218-246: Good implementation of index secret tracking.

The logic correctly:

  • Initializes secretsList per item (one index per collection)
  • Only adds secrets that were successfully processed (after SetFieldOnItem succeeds)
  • Normalizes secret names using gsm.NormalizeSecretName
  • Skips disabled clusters

248-254: Index secret update logic is correct.

The implementation properly:

  • Constructs the index payload using the accumulated secrets list
  • Uses the correct index secret name via gsm.GetIndexSecretName
  • Handles errors by adding them to the error slice

341-341: LGTM! Proper propagation of GSM sync flag.

The flag is correctly propagated from the options to the updateSecrets function.

pkg/secrets/gsm.go (2)

52-53: Good change to support syncing all secrets.

The dynamic secret naming using itemName correctly removes the hardcoded "cluster-init" pattern, enabling GSM sync for all collections as per the PR objectives.


71-78: LGTM! Index secret update implementation is correct.

The UpdateIndexSecret implementation properly:

  • Uses gsm.GetIndexSecretName for consistent naming
  • Applies appropriate annotations
  • Returns errors directly for proper error propagation

Note: This differs from SetFieldOnItem which logs GSM errors but doesn't fail the Vault write. This is appropriate since UpdateIndexSecret is a GSM-only operation without a dual-write concern.

Comment on lines +168 to +215
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)
}
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Consider additional edge case tests.

The test cases are well-structured and correctly validate the sorting and formatting behavior. However, consider adding test cases for these edge cases:

  1. Duplicate handling: What happens if the input already contains "updater-service-account"? Should it be deduplicated or appear twice?
  2. Empty strings: How should empty strings in the input be handled?
  3. Invalid secret names: Should the function filter out or reject names that fail ValidateSecretName?
  4. Case sensitivity: How should secrets that differ only in case be sorted (e.g., "ABC" vs "abc")?

Add these test cases to improve coverage:

{
	name:           "input already contains updater-service-account",
	secretsList:    []string{"abc", "updater-service-account", "xyz"},
	expectedOutput: "- abc\n- updater-service-account\n- xyz", // or should it deduplicate?
},
{
	name:           "input contains empty string",
	secretsList:    []string{"abc", "", "xyz"},
	expectedOutput: "- \n- abc\n- updater-service-account\n- xyz", // or should empty be filtered?
},
{
	name:           "case sensitivity",
	secretsList:    []string{"ABC", "abc", "Abc"},
	expectedOutput: "- ABC\n- Abc\n- abc\n- updater-service-account", // verify actual sort behavior
},

Run this script to verify the implementation's behavior for these edge cases:


🏁 Script executed:

#!/bin/bash
# Description: Check the ConstructIndexSecretContent implementation for edge case handling

# Find and examine the ConstructIndexSecretContent function implementation
ast-grep --pattern 'func ConstructIndexSecretContent($_, $_) $_ {
  $$$
}'

# Also check if there's any deduplication logic
rg -nP -A 10 'func ConstructIndexSecretContent' pkg/gsm-secrets/

Length of output: 680


🏁 Script executed:

#!/bin/bash
# Find UpdaterSASecretName constant definition
rg -nP 'const.*UpdaterSASecretName|UpdaterSASecretName\s*=' pkg/gsm-secrets/ -B2 -A2

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Find UpdaterSASecretName constant definition
rg -nP 'const.*UpdaterSASecretName|var.*UpdaterSASecretName' -A1 -B1

# Also search for its value assignment
rg -nP 'UpdaterSASecretName.*=' -A1

Length of output: 347


Add critical edge case tests for duplicate handling, empty strings, and case sensitivity.

The test cases validate basic sorting and formatting correctly, but miss critical edge cases that the current implementation handles without proper safeguards:

  1. Duplicate handling: The implementation unconditionally appends "updater-service-account" without checking if it already exists in the input. If the input contains this secret, it will appear twice in the output.

  2. Empty strings: Empty strings in the input are not filtered and will appear as "- " in the output.

  3. Case sensitivity: Go's sort.Strings uses lexicographic ordering where uppercase letters sort before lowercase (e.g., "ABC" < "abc").

Add these test cases:

{
	name:           "input already contains updater-service-account",
	secretsList:    []string{"abc", "updater-service-account", "xyz"},
	expectedOutput: "- abc\n- updater-service-account\n- updater-service-account\n- xyz",
},
{
	name:           "input contains empty string",
	secretsList:    []string{"abc", "", "xyz"},
	expectedOutput: "- \n- abc\n- updater-service-account\n- xyz",
},
{
	name:           "case sensitivity",
	secretsList:    []string{"ABC", "abc", "Abc"},
	expectedOutput: "- ABC\n- Abc\n- abc\n- updater-service-account",
},
🤖 Prompt for AI Agents
In pkg/gsm-secrets/secrets_test.go around lines 168 to 215, the test suite is
missing edge-case coverage for duplicate handling, empty strings, and case
sensitivity; add three new table-driven tests to
TestConstructIndexSecretContent: one named "input already contains
updater-service-account" with secretsList
[]string{"abc","updater-service-account","xyz"} and expectedOutput "- abc\n-
updater-service-account\n- updater-service-account\n- xyz", one named "input
contains empty string" with secretsList []string{"abc","","xyz"} and
expectedOutput "- \n- abc\n- updater-service-account\n- xyz", and one named
"case sensitivity" with secretsList []string{"ABC","abc","Abc"} and
expectedOutput "- ABC\n- Abc\n- abc\n- updater-service-account"; insert them
into the testCases slice so they run with the other cases and assert equality as
existing tests do.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
cmd/ci-secret-generator/main_test.go (3)

589-593: Refactor string-based test case identification to use a dedicated field.

The string comparison tc.name == "GSM sync enabled - disabled cluster field excluded from index" is fragile. If the test name changes, this logic silently breaks.

Add a dedicated field to the test case struct:

 	testCases := []struct {
 		name               string
 		config             secretgenerator.Config
 		GSMsyncEnabled     bool
+		hasDisabledClusters bool
 		expectedIndexCalls int
 		verifyIndexPayload func(t *testing.T, itemName string, payload []byte)
 	}{

Then update the test case definition:

 		{
 			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,
+			hasDisabledClusters: true,
 			expectedIndexCalls: 1,

And use it in the conditional:

 			// 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" {
+			if tc.hasDisabledClusters {
 				disabledClusters = sets.New[string]("disabled-cluster")
 			}

628-639: Clarify comment about index content when all fields fail.

The comment states "index contains only updater-service-account" but the code at line 729 passes an empty slice []string{} to ConstructIndexSecretContent. This suggests that updater-service-account is automatically added by the ConstructIndexSecretContent function rather than explicitly included in the slice.

Consider clarifying the comment to make this explicit:

 		{
-			name: "SetFieldOnItem error - all fields fail, index contains only updater-service-account",
+			name: "SetFieldOnItem error - all fields fail, index contains only updater-service-account (auto-added by ConstructIndexSecretContent)",
 			config: secretgenerator.Config{{

Or update the comment at line 729:

 						} else {
-							// When all fields fail, index should only contain updater-service-account
+							// When all fields fail, ConstructIndexSecretContent adds only updater-service-account
 							expectedPayload = string(gsm.ConstructIndexSecretContent([]string{}))
 						}

744-753: Verify that the actual error count matches the expected count.

The test defines expectedErrorsLen but never verifies that the actual number of errors matches this expectation, making the test less precise.

While parsing and counting individual errors from an aggregated error message can be complex, consider at minimum verifying that the error message contains expected substrings or error indicators. For example:

 			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")
-			}
+			
+			// Verify error message contains expected failure indicators
+			errStr := err.Error()
+			if errStr == "" {
+				t.Errorf("expected non-empty error message")
+			}
+			// Could add more specific validation based on tc.expectedErrorsLen
+			// e.g., checking for expected error keywords or patterns
 		})
 	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 8556f71 and 9b67316.

📒 Files selected for processing (2)
  • cmd/ci-secret-generator/main_test.go (3 hunks)
  • pkg/secrets/client_mock.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • cmd/ci-secret-generator/main_test.go
  • pkg/secrets/client_mock.go
🔇 Additional comments (3)
cmd/ci-secret-generator/main_test.go (2)

10-10: LGTM!

The gomock and gsm package imports are appropriately added to support the new GSM index testing functionality.

Also applies to: 16-16


198-198: LGTM!

The addition of the false parameter correctly maintains backward compatibility for existing tests by disabling GSM sync.

pkg/secrets/client_mock.go (1)

1-296: LGTM!

This is correctly generated gomock mock code. The mock implementations for ReadOnlyClient, Client, and SecretUsageComparer interfaces follow standard gomock patterns. The new UpdateIndexSecret mock method (lines 204-216) properly supports the GSM index functionality tested in main_test.go.

}

func TestUpdateSecretsWithGSMIndex(t *testing.T) {
t.Parallel()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beware that child tests spawned by a "parallel" parent don't run in parallel each other.
An example is worth more:

func Test1(t *testing.T) {
  t.Parallel()
  t.Run("subtest1", func(t *testing.T) {
    // ...
  })
}

func Test2(t *testing.T) {
  t.Parallel()
  t.Run("subtest2", func(t *testing.T) {
    // ...
  })
}

subtest1 and subtest2 do run in parallel but:

func Test1(t *testing.T) {
  t.Parallel()
  for i := range 2 {
    t.Run(fmt.Sprintf("subtest1.%d", i), func(t *testing.T){
          // ...
    })
  }
}

subtest1.1 and subtest1.2 don't. In order to achieve that you have to move t.Parallel() within t.Run(...), see here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, today I learned 😄 Do you think I should change it? I was following the pattern of other tests in ci-secret-generator/main_test.go

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on what the test is doing. If you won't have any race condition then you can increase the parallelism, otherwise leave it as it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the parallelism within the subtests in the newest commit

@psalajova
Copy link
Contributor Author

/retest-required

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Nov 18, 2025

@psalajova: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/breaking-changes 435ae02 link false /test breaking-changes

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@psalajova
Copy link
Contributor Author

/hold
just so that I can control when this merges

@openshift-ci openshift-ci bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Nov 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants