Skip to content

Commit d9d7292

Browse files
authored
refactor: Reconciliation for 'create_addon' and 'destroy_addon' (W-20610672) (#3497)
CLI reconciliation for 'create_addon' and 'destroy_addon' for NGPG migration
1 parent e52168b commit d9d7292

6 files changed

Lines changed: 249 additions & 142 deletions

File tree

packages/cli/src/commands/addons/create.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
21
import {color} from '@heroku/heroku-cli-util'
32
import {Command, flags} from '@heroku-cli/command'
3+
import * as Heroku from '@heroku-cli/schema'
44
import {Args, ux} from '@oclif/core'
55
import tsheredoc from 'tsheredoc'
66

77
import createAddon from '../../lib/addons/create_addon.js'
8+
import * as util from '../../lib/addons/util.js'
89
import notify from '../../lib/notify.js'
10+
911
const heredoc = tsheredoc.default
1012

1113
function parseConfig(args: string[]) {
@@ -46,9 +48,9 @@ export default class Create extends Command {
4648
}
4749

4850
static description = heredoc`
49-
Create a new add-on resource.
51+
Create a new add-on resource.
5052
51-
In order to add additional config items, please place them at the end of the command after a double-dash (--).
53+
In order to add additional config items, please place them at the end of the command after a double-dash (--).
5254
`
5355

5456
static examples = [
@@ -76,15 +78,13 @@ export default class Create extends Command {
7678

7779
static strict = false
7880

79-
static topic = 'addons'
80-
8181
public async run(): Promise<void> {
8282
this.allowArbitraryFlags = true
8383
const {args, flags, ...restParse} = await this.parse(Create)
8484
const {app, as, confirm, name, wait} = flags
8585
const servicePlan = args['service:plan']
86-
const argv = (restParse.argv as string[])
8786
// oclif duplicates specified args in argv
87+
const argv = (restParse.argv as string[])
8888
.filter(arg => arg !== servicePlan)
8989

9090
if (restParse.nonExistentFlags && restParse.nonExistentFlags.length > 0) {
@@ -93,9 +93,9 @@ export default class Create extends Command {
9393
}
9494

9595
const config = parseConfig(argv)
96-
let addon
96+
let addon: Heroku.AddOn
9797
try {
98-
addon = await createAddon(this.heroku, app, servicePlan, confirm, wait, {config, name, as})
98+
addon = await createAddon(this.heroku, app, servicePlan, confirm, wait, {as, config, name})
9999
if (wait) {
100100
Create.notifier(`heroku addons:create ${addon.name}`, 'Add-on successfully provisioned')
101101
}
@@ -109,6 +109,6 @@ export default class Create extends Command {
109109

110110
await this.config.runHook('recache', {addon, app, type: 'addon'})
111111
// eslint-disable-next-line no-unsafe-optional-chaining
112-
ux.stdout(`Use ${color.code('heroku addons:docs ' + addon?.addon_service?.name || '')} to view documentation`)
112+
ux.stdout(`Run ${color.code('heroku addons:docs ' + addon?.addon_service?.name || '')} to view documentation.`)
113113
}
114114
}

packages/cli/src/commands/addons/destroy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import {color} from '@heroku/heroku-cli-util'
1+
import {color, utils} from '@heroku/heroku-cli-util'
22
import {Command, flags} from '@heroku-cli/command'
33
import * as Heroku from '@heroku-cli/schema'
44
import {Args} from '@oclif/core'
55
import _ from 'lodash'
66

77
import destroyAddon from '../../lib/addons/destroy_addon.js'
8-
import {resolveAddon} from '../../lib/addons/resolve.js'
98
import ConfirmCommand from '../../lib/confirmCommand.js'
109
import notify from '../../lib/notify.js'
1110

@@ -36,8 +35,9 @@ export default class Destroy extends Command {
3635
const {argv, flags} = await this.parse(Destroy)
3736
const {app, confirm, wait} = flags
3837
const force = flags.force || process.env.HEROKU_FORCE === '1'
38+
const addonResolver = new utils.AddonResolver(this.heroku)
3939

40-
const addons = await Promise.all(argv.map((name: string) => resolveAddon(this.heroku, app, name as string)))
40+
const addons = await Promise.all(argv.map((name: string) => addonResolver.resolve(name as string, app)))
4141
for (const addon of addons) {
4242
// prevent deletion of add-on when context.app is set but the addon is attached to a different app
4343
const addonApp = addon.app?.name

packages/cli/src/lib/addons/create_addon.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {color} from '@heroku/heroku-cli-util'
1+
import {color, utils} from '@heroku/heroku-cli-util'
22
import {APIClient} from '@heroku-cli/command'
33
import * as Heroku from '@heroku-cli/schema'
44
import {ux} from '@oclif/core'
@@ -16,14 +16,19 @@ function formatConfigVarsMessage(addon: Heroku.AddOn) {
1616
return `Created ${color.addon(addon.name || '')}`
1717
}
1818

19-
// eslint-disable-next-line max-params
2019
export default async function (
2120
heroku: APIClient,
2221
app: string,
2322
plan: string,
2423
confirm: string | undefined,
2524
wait: boolean,
26-
options: {as?: string, config: Record<string, boolean | string>, name?: string},
25+
options: {
26+
actionStartMessage?: string,
27+
actionStopMessage?: string,
28+
as?: string,
29+
config: Record<string, boolean | string>,
30+
name?: string,
31+
},
2732
) {
2833
async function createAddonRequest(confirmed?: string) {
2934
const body = {
@@ -34,7 +39,7 @@ export default async function (
3439
plan: {name: plan},
3540
}
3641

37-
ux.action.start(`Creating ${plan} on ${color.app(app)}`)
42+
ux.action.start(options.actionStartMessage || `Creating ${plan} on ${color.app(app)}`)
3843
const {body: addon} = await heroku.post<Heroku.AddOn>(`/apps/${app}/addons`, {
3944
body,
4045
headers: {
@@ -43,7 +48,7 @@ export default async function (
4348
},
4449
})
4550

46-
ux.action.stop(color.green(util.formatPriceText(addon.plan?.price || '')))
51+
ux.action.stop(options.actionStopMessage || color.green(util.formatPriceText(addon.plan?.price || '')))
4752

4853
return addon
4954
}
@@ -61,7 +66,10 @@ export default async function (
6166
ux.stdout(formatConfigVarsMessage(addon))
6267
} else {
6368
ux.stdout(`${color.addon(addon.name || '')} is being created in the background. The app will restart when complete...`)
64-
ux.stdout(`Use ${color.code('heroku addons:info ' + addon.name)} to check creation progress`)
69+
if (utils.pg.isAdvancedDatabase(addon))
70+
ux.stdout(`Run ${color.code('heroku data:pg:info ' + addon.name + ' -a ' + addon.app!.name)} to check creation progress.`)
71+
else
72+
ux.stdout(`Run ${color.code('heroku addons:info ' + addon.name)} to check creation progress.`)
6573
}
6674
} else if (addon.state === 'deprovisioned') {
6775
throw new Error(`The add-on was unable to be created, with status ${addon.state}`)

packages/cli/src/lib/addons/destroy_addon.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {color} from '@heroku/heroku-cli-util'
1+
import {color, utils} from '@heroku/heroku-cli-util'
22
import {APIClient} from '@heroku-cli/command'
33
import * as Heroku from '@heroku-cli/schema'
44
import {ux} from '@oclif/core'
@@ -15,10 +15,11 @@ export default async function (heroku: APIClient, addon: Heroku.AddOn, force = f
1515
body: {force},
1616
headers: {'Accept-Expansion': 'plan'},
1717
}).catch(error => {
18-
if (error.body && error.body.message) {
19-
throw new Error(`The add-on was unable to be destroyed: ${error.body.message}.`)
18+
const errorMessage = error.body?.message || error
19+
if (utils.pg.isAdvancedDatabase(addon)) {
20+
throw new Error(`We can't destroy your database due to an error: ${errorMessage}. Try again or open a ticket with Heroku Support: https://help.heroku.com/`)
2021
} else {
21-
throw new Error(`The add-on was unable to be destroyed: ${error}.`)
22+
throw new Error(`The add-on was unable to be destroyed: ${errorMessage}.`)
2223
}
2324
})
2425

@@ -41,10 +42,14 @@ export default async function (heroku: APIClient, addon: Heroku.AddOn, force = f
4142
addonResponse = await waitForAddonDeprovisioning(heroku, addonResponse, 5)
4243
} else {
4344
ux.stdout(`${color.addon(addonName)} is being destroyed in the background. The app will restart when complete...`)
44-
ux.stdout(`Use ${color.code('heroku addons:info ' + addonName)} to check destruction progress`)
45+
ux.stdout(`Run ${color.code('heroku addons:info ' + addonName)} to check destruction progress`)
4546
}
4647
} else if (addonResponse.state !== 'deprovisioned') {
47-
throw new Error(`The add-on was unable to be destroyed, with status ${addonResponse.state}.`)
48+
if (utils.pg.isAdvancedDatabase(addonResponse)) {
49+
throw new Error(`You can't destroy a database with a ${addonResponse.state} status.`)
50+
} else {
51+
throw new Error(`The add-on was unable to be destroyed, with status ${addonResponse.state}.`)
52+
}
4853
}
4954

5055
return addonResponse

0 commit comments

Comments
 (0)