Skip to content

Commit 224f26e

Browse files
authored
fix: do not perform runtimeUpgrades if deployingVersion and version are the same (#2742)
1 parent 26f7d92 commit 224f26e

23 files changed

+117
-355
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@
163163
"validate-templates": "ENV_DIR=$PWD/tests/fixtures NODE_ENV=test binzx/otomi validate-templates",
164164
"validate-templates:all": "set -e; i=31; while [ $i -le 34 ]; do NODE_ENV=test binzx/otomi validate-templates -k 1.$i; i=$(($i+1)); done",
165165
"validate-values": "ENV_DIR=$PWD/tests/fixtures NODE_ENV=test binzx/otomi validate-values",
166-
"bootstrap-dev": "rm -rf /tmp/otomi-bootstrap-dev; CI=1 VALUES_INPUT=$PWD/tests/bootstrap/input-local-dev.yaml ENV_DIR=/tmp/otomi-bootstrap-dev binzx/otomi bootstrap",
167-
"bootstrap-dev-with-repo": "CI=1 ENV_DIR=/tmp/otomi-bootstrap-dev binzx/otomi bootstrap",
168-
"bootstrap-tests-fixtures": "CI=1 ENV_DIR=$PWD/tests/fixtures binzx/otomi bootstrap"
166+
"bootstrap-dev": "rm -rf /tmp/otomi-bootstrap-dev; VALUES_INPUT=$PWD/tests/bootstrap/input-local-dev.yaml ENV_DIR=/tmp/otomi-bootstrap-dev binzx/otomi bootstrap",
167+
"bootstrap-dev-with-repo": "ENV_DIR=/tmp/otomi-bootstrap-dev binzx/otomi bootstrap",
168+
"bootstrap-tests-fixtures": "ENV_DIR=$PWD/tests/fixtures binzx/otomi bootstrap"
169169
},
170170
"standard-version": {
171171
"skip": {

src/cmd/apply-as-apps.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { logLevelString, terminal } from 'src/common/debug'
77
import { hf } from 'src/common/hf'
88
import { appRevisionMatches, k8s, patchArgoCdApp, patchContainerResourcesOfSts } from 'src/common/k8s'
99
import { getFilename, loadYaml } from 'src/common/utils'
10-
import { getImageTag, objectToYaml } from 'src/common/values'
10+
import { getImageTagFromValues, objectToYaml } from 'src/common/values'
1111
import { getParsedArgs, HelmArguments, helmOptions, setParsedArgs } from 'src/common/yargs'
1212
import { Argv, CommandModule } from 'yargs'
1313
import { $ } from 'zx'
14-
import { env } from '../common/envalid'
1514
import { ARGOCD_APP_DEFAULT_SYNC_POLICY } from '../common/constants'
15+
import { env } from '../common/envalid'
1616

1717
const cmdName = getFilename(__filename)
1818
const dir = '/tmp/otomi'
@@ -171,7 +171,7 @@ export const applyAsApps = async (argv: HelmArguments): Promise<boolean> => {
171171
const helmfileSource = argv.file?.toString() || 'helmfile.d/'
172172
d.info(`Parsing helm releases defined in ${helmfileSource}`)
173173
setup()
174-
const otomiVersion = await getImageTag()
174+
const otomiVersion = await getImageTagFromValues()
175175
try {
176176
const expectedRevision = env.APPS_REVISION || otomiVersion
177177
d.info('Checking running revision of apl-operator...')

src/cmd/apply.test.ts

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ jest.mock('src/common/k8s', () => ({
2020
}))
2121

2222
jest.mock('src/common/values', () => ({
23-
getCurrentVersion: jest.fn(),
24-
getImageTag: jest.fn(),
23+
getImageTagFromValues: jest.fn(),
24+
getPackageVersion: jest.fn(),
2525
}))
2626

2727
jest.mock('zx', () => ({
@@ -40,10 +40,6 @@ jest.mock('./commit', () => ({
4040
commit: jest.fn(),
4141
}))
4242

43-
jest.mock('./upgrade', () => ({
44-
upgrade: jest.fn(),
45-
}))
46-
4743
jest.mock('src/common/runtime-upgrade', () => ({
4844
runtimeUpgrade: jest.fn(),
4945
}))
@@ -92,23 +88,21 @@ describe('Apply command', () => {
9288
mockDeps = {
9389
getDeploymentState: require('src/common/k8s').getDeploymentState,
9490
setDeploymentState: require('src/common/k8s').setDeploymentState,
95-
getCurrentVersion: require('src/common/values').getCurrentVersion,
96-
getImageTag: require('src/common/values').getImageTag,
91+
getImageTagFromValues: require('src/common/values').getImageTagFromValues,
92+
getPackageVersion: require('src/common/values').getPackageVersion,
9793
applyAsApps: require('./apply-as-apps').applyAsApps,
9894
commit: require('./commit').commit,
99-
upgrade: require('./upgrade').upgrade,
10095
runtimeUpgrade: require('src/common/runtime-upgrade').runtimeUpgrade,
10196
cd: require('zx').cd,
10297
getParsedArgs: require('src/common/yargs').getParsedArgs,
10398
}
10499

105100
// Set up default mock return values
106101
mockDeps.getDeploymentState.mockResolvedValue({ status: 'deployed' })
107-
mockDeps.getCurrentVersion.mockResolvedValue('1.0.0')
108-
mockDeps.getImageTag.mockResolvedValue('v1.0.0')
102+
mockDeps.getImageTagFromValues.mockResolvedValue('v1.0.0')
103+
mockDeps.getPackageVersion.mockReturnValue('1.0.0')
109104
mockDeps.applyAsApps.mockResolvedValue(true)
110105
mockDeps.commit.mockResolvedValue(undefined)
111-
mockDeps.upgrade.mockResolvedValue(undefined)
112106
mockDeps.runtimeUpgrade.mockResolvedValue(undefined)
113107
mockDeps.getParsedArgs.mockReturnValue({})
114108
})
@@ -151,13 +145,11 @@ describe('Apply command', () => {
151145
await applyAll()
152146

153147
// Verify pre-upgrade steps
154-
expect(mockDeps.upgrade).toHaveBeenCalledWith({ when: 'pre' })
155148
expect(mockDeps.runtimeUpgrade).toHaveBeenCalledWith({ when: 'pre' })
156149

157150
// Verify deployment state management
158151
expect(mockDeps.getDeploymentState).toHaveBeenCalled()
159-
expect(mockDeps.getImageTag).toHaveBeenCalled()
160-
expect(mockDeps.getCurrentVersion).toHaveBeenCalled()
152+
expect(mockDeps.getImageTagFromValues).toHaveBeenCalled()
161153
expect(mockDeps.setDeploymentState).toHaveBeenCalledWith({
162154
status: 'deploying',
163155
deployingTag: 'v1.0.0',
@@ -168,7 +160,6 @@ describe('Apply command', () => {
168160
expect(mockDeps.applyAsApps).toHaveBeenCalled()
169161

170162
// Verify post-upgrade steps
171-
expect(mockDeps.upgrade).toHaveBeenCalledWith({ when: 'post' })
172163
expect(mockDeps.runtimeUpgrade).toHaveBeenCalledWith({ when: 'post' })
173164

174165
// Verify final state
@@ -260,7 +251,6 @@ describe('Apply command', () => {
260251

261252
expect(mockDeps.applyAsApps).toHaveBeenCalledWith(testArgs)
262253
// Should not go through the full applyAll flow
263-
expect(mockDeps.upgrade).not.toHaveBeenCalled()
264254
})
265255

266256
test('should call applyAsApps directly when file specified', async () => {
@@ -271,7 +261,6 @@ describe('Apply command', () => {
271261

272262
expect(mockDeps.applyAsApps).toHaveBeenCalledWith(testArgs)
273263
// Should not go through the full applyAll flow
274-
expect(mockDeps.upgrade).not.toHaveBeenCalled()
275264
})
276265

277266
test('should call applyAsApps directly when both label and file specified', async () => {
@@ -282,7 +271,6 @@ describe('Apply command', () => {
282271

283272
expect(mockDeps.applyAsApps).toHaveBeenCalledWith(testArgs)
284273
// Should not go through the full applyAll flow
285-
expect(mockDeps.upgrade).not.toHaveBeenCalled()
286274
})
287275

288276
test('should handle retry logic when applyAll fails', async () => {
@@ -310,7 +298,7 @@ describe('Apply command', () => {
310298

311299
test('should handle image tag retrieval errors', async () => {
312300
const error = new Error('Failed to get image tag')
313-
mockDeps.getImageTag.mockRejectedValueOnce(error)
301+
mockDeps.getImageTagFromValues.mockRejectedValueOnce(error)
314302

315303
await expect(applyAll()).rejects.toThrow('Failed to get image tag')
316304
})
@@ -322,13 +310,6 @@ describe('Apply command', () => {
322310
await expect(applyAll()).rejects.toThrow('ApplyAsApps failed')
323311
})
324312

325-
test('should handle upgrade errors', async () => {
326-
const error = new Error('Pre-upgrade failed')
327-
mockDeps.upgrade.mockRejectedValueOnce(error)
328-
329-
await expect(applyAll()).rejects.toThrow('Pre-upgrade failed')
330-
})
331-
332313
test('should handle runtime upgrade errors', async () => {
333314
const error = new Error('Runtime upgrade failed')
334315
mockDeps.runtimeUpgrade.mockRejectedValueOnce(error)

src/cmd/apply.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { terminal } from 'src/common/debug'
66
import { env } from 'src/common/envalid'
77
import { getDeploymentState, setDeploymentState } from 'src/common/k8s'
88
import { getFilename, rootDir } from 'src/common/utils'
9-
import { getCurrentVersion, getImageTag } from 'src/common/values'
9+
import { getImageTagFromValues, getPackageVersion } from 'src/common/values'
1010
import { getParsedArgs, HelmArguments, helmOptions, setParsedArgs } from 'src/common/yargs'
1111
import { Argv, CommandModule } from 'yargs'
1212
import { cd } from 'zx'
@@ -15,7 +15,6 @@ import { applyAsApps } from './apply-as-apps'
1515
import { applyTeams } from './apply-teams'
1616
import { commit } from './commit'
1717
import { collectTraces } from './traces'
18-
import { upgrade } from './upgrade'
1918

2019
const cmdName = getFilename(__filename)
2120
const dir = '/tmp/otomi/'
@@ -36,14 +35,13 @@ export const applyAll = async () => {
3635
const prevState = await getDeploymentState()
3736
const argv: HelmArguments = getParsedArgs()
3837

39-
await upgrade({ when: 'pre' })
4038
await runtimeUpgrade({ when: 'pre' })
4139

4240
d.info('Start apply all')
4341
d.info(`Deployment state: ${JSON.stringify(prevState)}`)
44-
const tag = await getImageTag()
45-
const version = await getCurrentVersion()
46-
await setDeploymentState({ status: 'deploying', deployingTag: tag, deployingVersion: version })
42+
const tag = await getImageTagFromValues()
43+
const deployingVersion = getPackageVersion()
44+
await setDeploymentState({ status: 'deploying', deployingTag: tag, deployingVersion })
4745

4846
// We still need to deploy all teams because some settings depend on platform apps.
4947
const teamsApplyCompleted = await applyTeams()
@@ -55,7 +53,6 @@ export const applyAll = async () => {
5553
const appsApplyCompleted = await applyAsApps(params)
5654

5755
if (appsApplyCompleted) {
58-
await upgrade({ when: 'post' })
5956
await runtimeUpgrade({ when: 'post' })
6057
} else {
6158
d.info('Apps apply step not completed, skipping upgrade checks')
@@ -65,7 +62,7 @@ export const applyAll = async () => {
6562
await commit(false)
6663
}
6764
if (appsApplyCompleted) {
68-
await setDeploymentState({ status: 'deployed', version })
65+
await setDeploymentState({ status: 'deployed', version: deployingVersion })
6966
d.info('Deployment completed')
7067
} else {
7168
d.info('Deployment finished with actions pending')

src/cmd/bootstrap.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { cloneDeep, merge } from 'lodash'
2+
import { pki } from 'node-forge'
23
import { env } from 'process'
34
import stubs from 'src/test-stubs'
45
import {
@@ -11,7 +12,6 @@ import {
1112
handleFileEntry,
1213
processValues,
1314
} from './bootstrap'
14-
import { pki } from 'node-forge'
1515

1616
const { terminal } = stubs
1717

@@ -39,9 +39,8 @@ describe('Bootstrapping values', () => {
3939
encrypt: jest.fn(),
4040
existsSync: jest.fn(),
4141
genSops: jest.fn(),
42-
getCurrentVersion: jest.fn(),
4342
getDeploymentState: jest.fn().mockReturnValue({}),
44-
getImageTag: jest.fn(),
43+
getImageTagFromValues: jest.fn(),
4544
getK8sSecret: jest.fn(),
4645
hfValues: jest.fn(),
4746
isCli: true,
@@ -59,7 +58,6 @@ describe('Bootstrapping values', () => {
5958
await bootstrap(deps)
6059
expect(deps.copyBasicFiles).toHaveBeenCalled()
6160
expect(deps.bootstrapSops).toHaveBeenCalled()
62-
expect(deps.getImageTag).toHaveBeenCalled()
6361
})
6462
it('should copy only skeleton files to env dir if it is empty or nonexisting', async () => {
6563
deps.processValues.mockReturnValue(undefined)

src/cmd/bootstrap.ts

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import { prepareEnvironment } from 'src/common/cli'
1010
import { DEPLOYMENT_PASSWORDS_SECRET } from 'src/common/constants'
1111
import { decrypt, encrypt } from 'src/common/crypt'
1212
import { terminal } from 'src/common/debug'
13-
import { env, isChart, isCi, isCli } from 'src/common/envalid'
13+
import { env, isChart, isCli } from 'src/common/envalid'
1414
import { hfValues } from 'src/common/hf'
1515
import { createK8sSecret, getDeploymentState, getK8sSecret, secretId } from 'src/common/k8s'
1616
import { getKmsSettings } from 'src/common/repo'
1717
import { ensureTeamGitOpsDirectories, getFilename, gucci, isCore, loadYaml, rootDir } from 'src/common/utils'
18-
import { generateSecrets, getCurrentVersion, getImageTag, writeValues } from 'src/common/values'
18+
import { generateSecrets, writeValues } from 'src/common/values'
1919
import { BasicArguments, setParsedArgs } from 'src/common/yargs'
2020
import { Argv } from 'yargs'
2121
import { $ } from 'zx'
@@ -292,7 +292,7 @@ export const processValues = async (
292292
addInitialPasswords,
293293
addPlatformAdmin,
294294
},
295-
): Promise<Record<string, any> | undefined> => {
295+
): Promise<Record<string, any>> => {
296296
const d = deps.terminal(`cmd:${cmdName}:processValues`)
297297
const { ENV_DIR, VALUES_INPUT } = env
298298
let originalInput: Record<string, any> = {}
@@ -436,8 +436,6 @@ export const bootstrap = async (
436436
deps = {
437437
pathExists: existsSync,
438438
getDeploymentState,
439-
getImageTag,
440-
getCurrentVersion,
441439
terminal,
442440
copyBasicFiles,
443441
processValues,
@@ -453,37 +451,14 @@ export const bootstrap = async (
453451
},
454452
): Promise<void> => {
455453
const d = deps.terminal(`cmd:${cmdName}:bootstrap`)
456-
457-
// if CI: we are called from pipeline on each deployment, which is costly
458-
// so run bootstrap only when no previous deployment was done or version or tag of otomi changed
459-
const tag = await deps.getImageTag()
460-
const version = await deps.getCurrentVersion()
461-
if (isCi) {
462-
const { version: prevVersion, tag: prevTag } = await deps.getDeploymentState()
463-
if (prevVersion && prevTag && version === prevVersion && tag === prevTag) return
464-
}
465454
const { ENV_DIR } = env
466-
const hasOtomi = deps.pathExists(`${ENV_DIR}/bin/otomi`)
467455

468-
const otomiImage = `linode/apl-core:${tag}`
469-
d.log(`Installing artifacts from ${otomiImage}`)
470456
await deps.copyBasicFiles()
471457
await deps.migrate()
472458
const originalValues = await deps.processValues()
473-
// exit early if `isCli` and `ENV_DIR` were empty, and let the user provide valid values first:
474-
475-
if (!originalValues) {
476-
// FIXME what is the use case to enter this
477-
d.log('A new values repo has been created. For next steps follow documentation at https://apl-docs.net')
478-
return
479-
}
480-
481459
await deps.handleFileEntry()
482460
await deps.bootstrapSops()
483461
await ensureTeamGitOpsDirectories(ENV_DIR, originalValues)
484-
if (!hasOtomi) {
485-
d.log('You can now use the otomi CLI')
486-
}
487462
d.log(`Done bootstrapping values`)
488463
}
489464

src/cmd/commit.ts

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import retry from 'async-retry'
2-
import { existsSync } from 'fs'
3-
import { rm } from 'fs/promises'
42
import { bootstrapGit, setIdentity } from 'src/common/bootstrap'
53
import { prepareEnvironment } from 'src/common/cli'
64
import { encrypt } from 'src/common/crypt'
75
import { terminal } from 'src/common/debug'
8-
import { env, isCi } from 'src/common/envalid'
6+
import { env } from 'src/common/envalid'
97
import { hfValues } from 'src/common/hf'
108
import { createUpdateConfigMap, createUpdateGenericSecret, k8s, waitTillGitRepoAvailable } from 'src/common/k8s'
119
import { getFilename, loadYaml } from 'src/common/utils'
1210
import { getRepo } from 'src/common/values'
13-
import { getParsedArgs, HelmArguments, setParsedArgs } from 'src/common/yargs'
11+
import { HelmArguments, setParsedArgs } from 'src/common/yargs'
1412
import { Argv } from 'yargs'
1513
import { $, cd } from 'zx'
1614
import { validateValues } from './validate-values'
@@ -55,12 +53,10 @@ const cleanupGitState = async (d: any): Promise<void> => {
5553
}
5654
}
5755

58-
const commitAndPush = async (values: Record<string, any>, branch: string): Promise<void> => {
56+
const commitAndPush = async (values: Record<string, any>, branch: string, initialInstall = false): Promise<void> => {
5957
const d = terminal(`cmd:${cmdName}:commitAndPush`)
6058
d.info('Committing values')
61-
const argv = getParsedArgs()
62-
const rerunRequested = existsSync(`${env.ENV_DIR}/.rerun`)
63-
const message = isCi ? 'updated values [ci skip]' : argv.message || 'otomi commit'
59+
const message = initialInstall ? 'otomi commit' : 'updated values [ci skip]'
6460
const { password } = getRepo(values)
6561
cd(env.ENV_DIR)
6662
try {
@@ -73,18 +69,14 @@ const commitAndPush = async (values: Record<string, any>, branch: string): Promi
7369
await $`git commit -m ${message} --no-verify`.quiet()
7470
}
7571
await $`git add -A`
76-
if (isCi && rerunRequested) {
77-
d.log('Committing changes and triggering pipeline run')
78-
await $`git commit -m "[apl-trigger]" --no-verify --allow-empty`.quiet()
79-
} else {
80-
// The below 'git status' command will always return at least single new line
81-
const filesChangedCount = (await $`git status --untracked-files=no --porcelain`).toString().split('\n').length - 1
82-
if (filesChangedCount === 0) {
83-
d.log('Nothing to commit')
84-
return
85-
}
86-
await $`git commit -m ${message} --no-verify`.quiet()
72+
73+
// The below 'git status' command will always return at least single new line
74+
const filesChangedCount = (await $`git status --untracked-files=no --porcelain`).toString().split('\n').length - 1
75+
if (filesChangedCount === 0) {
76+
d.log('Nothing to commit')
77+
return
8778
}
79+
await $`git commit -m ${message} --no-verify`.quiet()
8880
} catch (e) {
8981
d.log('commitAndPush error ', e?.message?.replace(password, '****'))
9082
return
@@ -132,9 +124,6 @@ const commitAndPush = async (values: Record<string, any>, branch: string): Promi
132124
maxTimeout: 30000,
133125
},
134126
)
135-
if (rerunRequested) {
136-
await rm(`${env.ENV_DIR}/.rerun`, { force: true })
137-
}
138127
d.log('Successfully pushed the updated values')
139128
}
140129

@@ -159,7 +148,7 @@ export const commit = async (initialInstall: boolean, overrideArgs?: HelmArgumen
159148
}
160149
// continue
161150
await encrypt()
162-
await commitAndPush(values, branch)
151+
await commitAndPush(values, branch, initialInstall)
163152
}
164153

165154
export const cloneOtomiChartsInGitea = async (): Promise<void> => {

0 commit comments

Comments
 (0)