Skip to content
Merged
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
35 changes: 30 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/common/k8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { resolveAny } from 'dns/promises'
import { access, mkdir, writeFile } from 'fs/promises'
import { isEmpty, isEqual, map, mapValues } from 'lodash'
import { dirname, join } from 'path'
import { Writable } from 'stream'
import { parse, stringify } from 'yaml'
import { $, cd, sleep } from 'zx'
import { ARGOCD_APP_PARAMS, DEPLOYMENT_PASSWORDS_SECRET, DEPLOYMENT_STATUS_CONFIGMAP } from './constants'
Expand All @@ -28,7 +29,6 @@ import { env } from './envalid'
import { hfValues } from './hf'
import { parser } from './yargs'
import { askYesNo } from './zx-enhance'
import { Writable } from 'stream'

export const secretId = `secret/otomi/${DEPLOYMENT_PASSWORDS_SECRET}`

Expand Down Expand Up @@ -124,8 +124,11 @@ export const getK8sSecret = async (name: string, namespace: string): Promise<Rec
export interface DeploymentState {
status?: 'deploying' | 'deployed'
tag?: string
// semantic version string (without 'v' prefix)
version?: string
// container image tag (can be an arbitrary name)
deployingTag?: string
// semantic version string (without 'v' prefix)
deployingVersion?: string
}

Expand Down
35 changes: 12 additions & 23 deletions src/common/runtime-upgrade.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getApplications } from 'src/cmd/apply-as-apps'
import { terminal } from './debug'
import { deployEssential } from './hf'
import { getDeploymentState, k8s, waitForArgoCDAppHealthy, waitForArgoCDAppSync } from './k8s'
import { filterRuntimeUpgrades, runtimeUpgrade } from './runtime-upgrade'
import { RuntimeUpgrades } from './runtime-upgrades/runtime-upgrades'
import { getCurrentVersion } from './values'
import { deployEssential } from './hf'

jest.mock('./k8s')
jest.mock('./hf')
Expand Down Expand Up @@ -43,7 +43,7 @@ describe('runtimeUpgrade', () => {
info: jest.fn(),
error: jest.fn(),
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument

mockTerminal.mockReturnValue(mockDebugger as any)
})

Expand All @@ -61,7 +61,6 @@ describe('runtimeUpgrade', () => {
})

it('should skip runtime upgrade for null deployment state', async () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
mockGetDeploymentState.mockResolvedValue(null as any)
mockGetCurrentVersion.mockResolvedValue('1.0.0')

Expand All @@ -80,17 +79,17 @@ describe('runtimeUpgrade', () => {

await runtimeUpgrade({ when: 'pre' })

expect(mockDebugger.info).toHaveBeenCalledWith('Current version of otomi: 2.0.0')
expect(mockDebugger.info).toHaveBeenCalledWith('The current version of the Akamai App Platform: 2.0.0')
expect(mockDebugger.info).toHaveBeenCalledWith('Deploying essential manifests')
expect(mockDebugger.info).toHaveBeenCalledWith('No runtime upgrade operations detected, skipping')
})

it('should use current version when deployment state has no version', async () => {
mockGetDeploymentState.mockResolvedValue({ status: 'deployed' })
mockGetCurrentVersion.mockResolvedValue('1.0.0')
mockGetDeploymentState.mockResolvedValue({ status: 'deployed', version: '1.0.0' })

await runtimeUpgrade({ when: 'pre' })

expect(mockDebugger.info).toHaveBeenCalledWith('Current version of otomi: 1.0.0')
expect(mockDebugger.info).toHaveBeenCalledWith('The current version of the Akamai App Platform: 1.0.0')
})
})

Expand Down Expand Up @@ -231,26 +230,16 @@ describe('runtimeUpgrade', () => {
})

describe('filterRuntimeUpgrades', () => {
const sampleUpgrades: RuntimeUpgrades = [
{ version: '1.0.0' },
{ version: '1.5.0' },
{ version: '2.0.0' },
{ version: 'dev' },
]
const sampleUpgrades: RuntimeUpgrades = [{ version: '1.0.0' }, { version: '1.5.0' }, { version: '2.0.0' }]

it('should filter upgrades newer than current version', () => {
const result = filterRuntimeUpgrades('1.2.0', sampleUpgrades)
expect(result).toEqual([{ version: '1.5.0' }, { version: '2.0.0' }, { version: 'dev' }])
})

it('should include dev version regardless of current version', () => {
const result = filterRuntimeUpgrades('99.0.0', sampleUpgrades)
expect(result).toEqual([{ version: 'dev' }])
expect(result).toEqual([{ version: '1.5.0' }, { version: '2.0.0' }])
})

it('should return empty array when no upgrades are newer', () => {
const result = filterRuntimeUpgrades('3.0.0', sampleUpgrades)
expect(result).toEqual([{ version: 'dev' }])
expect(result).toEqual([])
})

it('should handle prerelease versions correctly', () => {
Expand All @@ -271,16 +260,16 @@ describe('filterRuntimeUpgrades', () => {

it('should not run with prereleases = version', () => {
const result = filterRuntimeUpgrades('2.0.0-rc.2', sampleUpgrades)
expect(result).toEqual([{ version: 'dev' }])
expect(result).toEqual([])
})

it('should not modify valid semantic versions', () => {
const result = filterRuntimeUpgrades('1.0.0', sampleUpgrades)
expect(result).toEqual([{ version: '1.5.0' }, { version: '2.0.0' }, { version: 'dev' }])
expect(result).toEqual([{ version: '1.5.0' }, { version: '2.0.0' }])
})

it('should handle edge case with exact version match', () => {
const result = filterRuntimeUpgrades('1.5.0', sampleUpgrades)
expect(result).toEqual([{ version: '2.0.0' }, { version: 'dev' }])
expect(result).toEqual([{ version: '2.0.0' }])
})
})
13 changes: 4 additions & 9 deletions src/common/runtime-upgrade.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { isEmpty } from 'lodash'
import semver from 'semver'
import { getApplications } from 'src/cmd/apply-as-apps'
import { terminal } from './debug'
import { getDeploymentState, k8s, waitForArgoCDAppHealthy, waitForArgoCDAppSync } from './k8s'
import { RuntimeUpgradeContext, RuntimeUpgrades, runtimeUpgrades } from './runtime-upgrades/runtime-upgrades'
import { getCurrentVersion } from './values'
import { deployEssential } from './hf'

interface RuntimeUpgradeArgs {
Expand All @@ -15,23 +13,20 @@ export async function runtimeUpgrade({ when }: RuntimeUpgradeArgs): Promise<void
const d = terminal('cmd:upgrade:runtimeUpgrade')
const deploymentState = await getDeploymentState()

if (isEmpty(deploymentState)) {
if (!deploymentState?.version) {
d.info('Skipping the runtime upgrade procedure as this is the very first installation')
return
}
const deployedVersion: string = deploymentState.version
d.info(`The current version of the Akamai App Platform: ${deployedVersion}`)

d.info('Deploying essential manifests')
const essentialDeployResult = await deployEssential(['upgrade=true'], true)
if (!essentialDeployResult) {
throw new Error('Failed to update namespaces')
}

const declaredVersion = await getCurrentVersion()
const deployedVersion: string = deploymentState.version ?? declaredVersion
const apps = await getApplications()

d.info(`Current version of otomi: ${deployedVersion}`)

const filteredUpgrades = filterRuntimeUpgrades(deployedVersion, runtimeUpgrades)

if (filteredUpgrades.length === 0) {
Expand Down Expand Up @@ -85,5 +80,5 @@ export function filterRuntimeUpgrades(version: string, rUpgrades: RuntimeUpgrade
if (!currentVersion) {
throw new Error(`Unsupported version format: ${version}`)
}
return rUpgrades.filter((rUpgrade) => rUpgrade.version === 'dev' || semver.gt(rUpgrade.version, currentVersion))
return rUpgrades.filter((rUpgrade) => semver.gt(rUpgrade.version, currentVersion))
}
Loading