From d76a4d4bd298afd5b9ccc54e83596ce8edf53e15 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 29 May 2026 11:19:25 +0200 Subject: [PATCH 1/2] feat: enhance git credential handling for apl-operator --- chart/apl/templates/git-secret.yaml | 4 ++-- src/common/git-config.test.ts | 28 ++++++++++++++++++++++++++-- src/common/git-config.ts | 17 +++++++++++++++++ src/common/gitea.ts | 3 ++- src/operator/apl-operator.test.ts | 4 ++-- src/operator/apl-operator.ts | 8 +++++++- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/chart/apl/templates/git-secret.yaml b/chart/apl/templates/git-secret.yaml index d8262fc2ca..3749e9a5ea 100644 --- a/chart/apl/templates/git-secret.yaml +++ b/chart/apl/templates/git-secret.yaml @@ -1,4 +1,5 @@ {{- $git := .Values.otomi.git | default dict }} +{{- if $git.password }} apiVersion: v1 kind: Secret metadata: @@ -9,6 +10,5 @@ stringData: {{- if $git.username }} username: {{ $git.username | quote }} {{- end }} - {{- if $git.password }} password: {{ $git.password | quote }} - {{- end }} +{{- end }} diff --git a/src/common/git-config.test.ts b/src/common/git-config.test.ts index 4941cd3a7a..14e83735d8 100644 --- a/src/common/git-config.test.ts +++ b/src/common/git-config.test.ts @@ -167,7 +167,7 @@ describe('git-config', () => { }) }) - it('should fall back to old credentials when new secret is missing', async () => { + it('should use otomi-secrets when apl-git-credentials has no password', async () => { mockGetK8sConfigMap.mockResolvedValue({ data: { repoUrl: 'https://github.com/org/repo.git', @@ -176,7 +176,31 @@ describe('git-config', () => { }, }) mockGetK8sSecret - .mockResolvedValueOnce(undefined) // getGitCredentials + .mockResolvedValueOnce(undefined) // getGitCredentials (apl-git-credentials empty) + .mockResolvedValueOnce({ git_password: 'github-token' }) // otomi-secrets fallback + + const result = await getStoredGitRepoConfig() + expect(result).toEqual({ + repoUrl: 'https://github.com/org/repo.git', + authenticatedUrl: 'https://github-token@github.com/org/repo.git', + branch: 'main', + email: 'pipeline@cluster.local', + username: undefined, + password: 'github-token', + }) + }) + + it('should fall back to old credentials when both new secrets are missing', async () => { + mockGetK8sConfigMap.mockResolvedValue({ + data: { + repoUrl: 'https://github.com/org/repo.git', + branch: 'main', + email: 'pipeline@cluster.local', + }, + }) + mockGetK8sSecret + .mockResolvedValueOnce(undefined) // getGitCredentials (apl-git-credentials empty) + .mockResolvedValueOnce(undefined) // otomi-secrets empty .mockResolvedValueOnce({ GIT_USERNAME: 'otomi-admin', GIT_PASSWORD: 'oldpass' }) // getOldGitCredentials const result = await getStoredGitRepoConfig() diff --git a/src/common/git-config.ts b/src/common/git-config.ts index 812fafcb43..366d3beca3 100644 --- a/src/common/git-config.ts +++ b/src/common/git-config.ts @@ -83,6 +83,23 @@ export async function getGitConfigData(): Promise { export async function getStoredGitRepoConfig(): Promise { let [configData, credentials] = await Promise.all([getGitConfigData(), getGitCredentials()]) + // Try the canonical secret populated by SealedSecrets/ESO (same source as getRepo()) + // This covers the window after a git provider switch where apl-git-credentials is not yet + // populated by ESO but otomi-secrets already has the real token. + if (!credentials) { + try { + const otomiSecrets = await getK8sSecret(OTOMI_SECRETS, SEALED_SECRETS_NAMESPACE) + if (otomiSecrets?.git_password) { + credentials = { + password: String(otomiSecrets.git_password), + } + d.info('Read git credentials from otomi-secrets') + } + } catch { + d.debug('Could not read git credentials from otomi-secrets') + } + } + //TODO This can be removed after BYO Git has been released if (!credentials) { credentials = await getOldGitCredentials() diff --git a/src/common/gitea.ts b/src/common/gitea.ts index 5d46cbdafd..a7c6c45203 100644 --- a/src/common/gitea.ts +++ b/src/common/gitea.ts @@ -25,10 +25,11 @@ export async function resetGiteaPasswordValidity() { } } -export const waitTillGitRepoAvailable = async (repoUrl: string): Promise => { +export const waitTillGitRepoAvailable = async (repoUrlOrGetter: string | (() => Promise)): Promise => { const d = terminal('common:gitea:waitTillGitRepoAvailable') await retry( async () => { + const repoUrl = typeof repoUrlOrGetter === 'function' ? await repoUrlOrGetter() : repoUrlOrGetter const $git = $({ cwd: env.ENV_DIR }) try { // the ls-remote exists with zero even if repo is empty diff --git a/src/operator/apl-operator.test.ts b/src/operator/apl-operator.test.ts index 55c602bb73..5183f4b926 100644 --- a/src/operator/apl-operator.test.ts +++ b/src/operator/apl-operator.test.ts @@ -116,7 +116,7 @@ describe('AplOperator', () => { await startPromise - expect(waitTillGitRepoAvailable).toHaveBeenCalledWith(mockGitRepo.authenticatedUrl) + expect(waitTillGitRepoAvailable).toHaveBeenCalledWith(expect.any(Function)) expect(mockGitRepo.clone).toHaveBeenCalled() expect(mockInfoFn).toHaveBeenCalledWith('APL operator started successfully') @@ -128,7 +128,7 @@ describe('AplOperator', () => { await expect(aplOperator.start()).rejects.toThrow('Start failed') - expect(waitTillGitRepoAvailable).toHaveBeenCalledWith(mockGitRepo.authenticatedUrl) + expect(waitTillGitRepoAvailable).toHaveBeenCalledWith(expect.any(Function)) expect(mockGitRepo.clone).toHaveBeenCalled() expect(mockErrorFn).toHaveBeenCalledWith('Failed to start APL operator:', 'Start failed') diff --git a/src/operator/apl-operator.ts b/src/operator/apl-operator.ts index 70e8f19f85..a7e7918c67 100644 --- a/src/operator/apl-operator.ts +++ b/src/operator/apl-operator.ts @@ -192,7 +192,13 @@ export class AplOperator { this.d.info('Starting APL operator') try { - await waitTillGitRepoAvailable(this.authenticatedUrl) + // Reload credentials on every retry so that an ESO sync that happens + // after construction (e.g. during a git-provider switch) is picked up + // automatically without requiring a manual pod restart. + await waitTillGitRepoAvailable(async () => { + await this.reloadGitCredentials() + return this.gitRepo.authenticatedUrl + }) await this.gitRepo.clone() this.d.info('APL operator started successfully') } catch (error) { From c0edce7adf50d93abd9a4545cd180c24da166ed5 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 29 May 2026 18:38:17 +0200 Subject: [PATCH 2/2] fix: revert git-secret change --- chart/apl/templates/git-secret.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chart/apl/templates/git-secret.yaml b/chart/apl/templates/git-secret.yaml index 3749e9a5ea..d8262fc2ca 100644 --- a/chart/apl/templates/git-secret.yaml +++ b/chart/apl/templates/git-secret.yaml @@ -1,5 +1,4 @@ {{- $git := .Values.otomi.git | default dict }} -{{- if $git.password }} apiVersion: v1 kind: Secret metadata: @@ -10,5 +9,6 @@ stringData: {{- if $git.username }} username: {{ $git.username | quote }} {{- end }} + {{- if $git.password }} password: {{ $git.password | quote }} -{{- end }} + {{- end }}