Skip to content
Open
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
28 changes: 26 additions & 2 deletions src/common/git-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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()
Expand Down
17 changes: 17 additions & 0 deletions src/common/git-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,23 @@ export async function getGitConfigData(): Promise<GitConfigData | undefined> {
export async function getStoredGitRepoConfig(): Promise<GitRepoConfig> {
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()
Expand Down
3 changes: 2 additions & 1 deletion src/common/gitea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ export async function resetGiteaPasswordValidity() {
}
}

export const waitTillGitRepoAvailable = async (repoUrl: string): Promise<void> => {
export const waitTillGitRepoAvailable = async (repoUrlOrGetter: string | (() => Promise<string>)): Promise<void> => {
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
Expand Down
4 changes: 2 additions & 2 deletions src/operator/apl-operator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand Down
8 changes: 7 additions & 1 deletion src/operator/apl-operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading