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) {