Skip to content
Draft
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
77 changes: 77 additions & 0 deletions src/commands/data/pg/upgrade/run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {color, hux, utils} from '@heroku/heroku-cli-util'
import {flags as Flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import tsheredoc from 'tsheredoc'

import BaseCommand from '../../../../lib/data/baseCommand.js'
import {InfoResponse, UpgradeResponse} from '../../../../lib/data/types.js'

const heredoc = tsheredoc.default

export default class DataPgUpgradeRun extends BaseCommand {
static args = {
database: Args.string({
description: 'database name, database attachment name, or related config var on an app',
required: true,
}),
}

static description = 'upgrade the Postgres version on a Heroku Postgres Advanced database'

static examples = [
heredoc`
# Upgrade a Heroku Postgres Advanced database to version 17
<%= config.bin %> <%= command.id %> DATABASE --version 17 --app my-app
`,
]

static flags = {
app: Flags.app({required: true}),
confirm: Flags.string({char: 'c', description: 'pass in the app name to skip confirmation prompts'}),
remote: Flags.remote(),
version: Flags.string({char: 'v', description: 'Postgres version to upgrade to'}),
}

public async run(): Promise<void> {
const {args, flags} = await this.parse(DataPgUpgradeRun)
const {app, confirm, version} = flags
const {database} = args

const dbResolver = new utils.pg.DatabaseResolver(this.heroku)
const {addon} = await dbResolver.getAttachment(app, database)

if (!utils.pg.isAdvancedDatabase(addon)) {
ux.error(
'You can only use this command on Advanced-tier databases.\n'
+ `Run ${color.code(`heroku pg:upgrade:run ${addon.name} --app ${app}`)} instead.`,
)
}

const {body: databaseInfo} = await this.dataApi.get<InfoResponse>(`/data/postgres/v1/${addon.id}/info`)
const {version: currentVersion} = databaseInfo
const newVersion = version ?? 'the latest supported Postgres version'
await hux.confirmCommand({
comparison: app,
confirmation: confirm,
warningMessage: heredoc(`
This command immediately upgrades your ${color.datastore(addon.name)} database from ${currentVersion} to ${newVersion}.
Your database will be unavailable until the upgrade is complete.`),
})

try {
ux.action.start(`Upgrading your ${color.datastore(addon.name)} database from ${currentVersion} to ${newVersion}`)
const {body: {message}} = await this.dataApi.post<UpgradeResponse>(
`/data/postgres/v1/${addon.id}/upgrade/run`,
{body: {version}},
)
ux.action.stop(heredoc(`
done

${color.info(message)}
`))
} catch (error: unknown) {
ux.action.stop(color.red('!'))
throw error
}
}
}
4 changes: 4 additions & 0 deletions src/lib/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,7 @@ export enum MaintenanceStatus {
ready = 'ready',
running = 'running',
}

export type UpgradeResponse = {
message: string
}
118 changes: 118 additions & 0 deletions test/unit/commands/data/pg/upgrade/run.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import ansis from 'ansis'
import {expect} from 'chai'
import nock from 'nock'
import {stderr, stdout} from 'stdout-stderr'
import tsheredoc from 'tsheredoc'

import DataPgUpgradeRun from '../../../../../../src/commands/data/pg/upgrade/run.js'
import {
advancedAddonAttachment,
nonAdvancedAddonAttachment,
pgInfo,
} from '../../../../../fixtures/data/pg/fixtures.js'
import runCommand from '../../../../../helpers/runCommand.js'

const heredoc = tsheredoc.default

describe('data:pg:upgrade:run', function () {
it('upgrades an advanced database to the latest version', async function () {
const resolveApi = nock('https://api.heroku.com')
.post('/actions/addon-attachments/resolve')
.reply(200, [advancedAddonAttachment])
const {addon, name: attachmentName} = advancedAddonAttachment
const dataApi = nock('https://api.data.heroku.com')
.get(`/data/postgres/v1/${addon.id}/info`)
.reply(200, {...pgInfo, version: '16.10'})
.post(`/data/postgres/v1/${addon.id}/upgrade/run`, {})
.reply(200, {message: 'Upgrade started. Monitor progress with heroku data:pg:info.'})

await runCommand(DataPgUpgradeRun, [
attachmentName,
'--app=myapp',
'--confirm=myapp',
])

resolveApi.done()
dataApi.done()

expect(ansis.strip(stderr.output)).to.equal(
heredoc(`
Upgrading your ⛁ ${addon.name} database from 16.10 to the latest supported Postgres version... done

Upgrade started. Monitor progress with heroku data:pg:info.

`),
)
expect(stdout.output).to.equal('')
})

it('upgrades an advanced database to a specific version with --version', async function () {
const resolveApi = nock('https://api.heroku.com')
.post('/actions/addon-attachments/resolve')
.reply(200, [advancedAddonAttachment])
const {addon, name: attachmentName} = advancedAddonAttachment
const dataApi = nock('https://api.data.heroku.com')
.get(`/data/postgres/v1/${addon.id}/info`)
.reply(200, {...pgInfo, version: '16.10'})
.post(`/data/postgres/v1/${addon.id}/upgrade/run`, {version: '17.5'})
.reply(200, {message: 'Upgrade to 17.5 started.'})

await runCommand(DataPgUpgradeRun, [
attachmentName,
'--app=myapp',
'--confirm=myapp',
'--version=17.5',
])

resolveApi.done()
dataApi.done()

expect(ansis.strip(stderr.output)).to.include('from 16.10 to 17.5')
expect(ansis.strip(stderr.output)).to.include('Upgrade to 17.5 started.')
})

it('errors if database is not Advanced-tier', async function () {
const resolveApi = nock('https://api.heroku.com')
.post('/actions/addon-attachments/resolve')
.reply(200, [nonAdvancedAddonAttachment])
const {addon, name: attachmentName} = nonAdvancedAddonAttachment

try {
await runCommand(DataPgUpgradeRun, [
attachmentName,
'--app=myapp',
'--confirm=myapp',
])
} catch (error: unknown) {
resolveApi.done()
expect(ansis.strip((error as Error).message)).to.equal(
'You can only use this command on Advanced-tier databases.\n'
+ `Run heroku pg:upgrade:run ${addon.name} --app myapp instead.`,
)
}
})

it('displays the correct error when the upgrade API fails', async function () {
const resolveApi = nock('https://api.heroku.com')
.post('/actions/addon-attachments/resolve')
.reply(200, [advancedAddonAttachment])
const {addon, name: attachmentName} = advancedAddonAttachment
const dataApi = nock('https://api.data.heroku.com')
.get(`/data/postgres/v1/${addon.id}/info`)
.reply(200, {...pgInfo, version: '16.10'})
.post(`/data/postgres/v1/${addon.id}/upgrade/run`)
.reply(422, {message: 'Database is not ready for upgrade.'})

try {
await runCommand(DataPgUpgradeRun, [
attachmentName,
'--app=myapp',
'--confirm=myapp',
])
} catch (error: unknown) {
resolveApi.done()
dataApi.done()
expect(ansis.strip((error as Error).message)).to.include('Database is not ready for upgrade.')
}
})
})
Loading