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: 18 additions & 17 deletions src/commands/data/maintenances/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {flags as Flags} from '@heroku-cli/command'
import {Command, flags as Flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'
import {hux} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {MaintenanceInfoByAppResult} from '@heroku/types/data'
import {ux} from '@oclif/core/ux'

import BaseCommand from '../../../lib/data/base-command.js'
import {Maintenance, MaintenanceStatus} from '../../../lib/data/types.js'
import {MaintenanceStatus} from '../../../lib/data/types.js'
import {constructSortFilterTableOptions, constructTableColumns, outputCSV} from '../../../lib/utils/table-utils.js'

export default class DataMaintenancesIndex extends BaseCommand {
type MaintenanceItem = MaintenanceInfoByAppResult['maintenances'][number]

export default class DataMaintenancesIndex extends Command {
static description = 'list maintenances for an app\'s data addons'
static examples = [
'$ heroku data:maintenances --app production-app',
Expand Down Expand Up @@ -50,38 +53,36 @@ export default class DataMaintenancesIndex extends BaseCommand {
private async fetchMaintenances(appName: string) {
ux.action.start('Fetching maintenances')
const {body: app} = await this.heroku.get<Heroku.App>(`/apps/${appName}`)
const {body: {maintenances}} = await this.dataApi.get<{maintenances: Maintenance[]}>(
`/data/maintenances/v1/apps/${app.id}`,
this.dataApi.defaults,
)
const {data} = new HerokuSDK()
const result = await data.maintenance.infoByApp(app.id!)
ux.action.stop()

return maintenances
return result.maintenances
}

private getTableColumns(extended: boolean, columns: string | undefined) {
/* eslint-disable perfectionist/sort-objects */
const allTableColumns = {
addon: {
get: (row: Maintenance) => row.addon && row.addon.name,
get: (row: MaintenanceItem) => row.addon && row.addon.name,
header: 'Addon',
},
attachments: {
get(row: Maintenance) {
get(row: MaintenanceItem) {
const attachments = (row && row.addon && row.addon.attachments) || []
return attachments.join(', ')
},
header: 'Attachments',
},
window: {
get: (row: Maintenance) => row && row.addon && row.addon.window,
get: (row: MaintenanceItem) => row && row.addon && row.addon.window,
header: 'Scheduling Window',
},
status: {
header: 'Status',
},
required_by: {
get(row: Maintenance) {
get(row: MaintenanceItem) {
if (row.status === MaintenanceStatus.completed) {
return '-'
}
Expand All @@ -91,7 +92,7 @@ export default class DataMaintenancesIndex extends BaseCommand {
header: 'Required by',
},
scheduled_for: {
get(row: Maintenance) {
get(row: MaintenanceItem) {
if (row.status === MaintenanceStatus.completed) {
return '-'
}
Expand All @@ -101,11 +102,11 @@ export default class DataMaintenancesIndex extends BaseCommand {
header: 'Scheduled for',
},
kind: {
get: (row: Maintenance) => row.addon.kind,
get: (row: MaintenanceItem) => row.addon.kind,
header: 'Kind',
},
plan: {
get: (row: Maintenance) => row.addon.plan,
get: (row: MaintenanceItem) => row.addon.plan,
header: 'Plan',
},
}
Expand All @@ -116,7 +117,7 @@ export default class DataMaintenancesIndex extends BaseCommand {
return constructTableColumns(allTableColumns, baseColumnNames, extended, columns)
}

private renderTable(maintenances: Maintenance[], tableColumns: Record<string, any>, flags: Record<string, any>) {
private renderTable(maintenances: MaintenanceItem[], tableColumns: Record<string, any>, flags: Record<string, any>) {
const tableOptions = constructSortFilterTableOptions(flags, tableColumns)
hux.table(maintenances, tableColumns, tableOptions)
}
Expand Down
55 changes: 26 additions & 29 deletions src/commands/data/maintenances/info.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import {flags} from '@heroku-cli/command'
import {Command, flags} from '@heroku-cli/command'
import {color, hux, utils} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {MaintenanceInfoResult} from '@heroku/types/data'
import {Args, ux} from '@oclif/core'

import BaseCommand from '../../../lib/data/base-command.js'
import {Maintenance} from '../../../lib/data/types.js'
import {lazyModuleLoader} from '../../../lib/lazy-module-loader.js'

interface StyledMaintenance extends Maintenance {
[key: string]: any;
type StyledMaintenance = Record<string, any> & {
duration_approximate?: string;
}

export default class DataMaintenancesInfo extends BaseCommand {
export default class DataMaintenancesInfo extends Command {
static args = {
addon: Args.string({
description: 'data addon to show maintenance for',
Expand All @@ -31,27 +30,16 @@ export default class DataMaintenancesInfo extends BaseCommand {
}

// a prettier display of the information
protected createStyledMaintenance(maintenance: Maintenance, addSeconds: any, formatDistance: any) {
// make a copy of the maintenance
protected createStyledMaintenance(maintenance: MaintenanceInfoResult, addSeconds: any, formatDistance: any) {
// make a copy of the maintenance, omitting uuids for display
const {uuid: _appUuid, ...appWithoutUuid} = maintenance.app
const {uuid: _addonUuid, ...addonWithoutUuid} = maintenance.addon

const styledMaintenance: StyledMaintenance = {
...maintenance,
addon: {
...maintenance.addon,
},
app: {
...maintenance.app,
},
}

// remove app uuid
if (styledMaintenance.app && styledMaintenance.app.uuid) {
delete styledMaintenance.app.uuid
}

// remove addon uuid
if (styledMaintenance.addon && styledMaintenance.addon.uuid) {
delete styledMaintenance.addon.uuid
}
addon: addonWithoutUuid,
app: appWithoutUuid,
};

['app', 'addon'].forEach((key: string) => {
for (const childKey of Object.keys(styledMaintenance[key])) {
Expand Down Expand Up @@ -87,10 +75,19 @@ export default class DataMaintenancesInfo extends BaseCommand {
const addon = await addonResolver.resolve(args.addon, app)

ux.action.start(`Fetching maintenance for ${color.addon(addon.name!)}`)
const {body: maintenance} = await this.dataApi.get<Maintenance>(
`/data/maintenances/v1/${addon!.id}`,
this.dataApi.defaults,
)
const {data} = new HerokuSDK()
let maintenance: MaintenanceInfoResult
try {
maintenance = await data.maintenance.info(addon.id!)
} catch (error: any) {
ux.action.stop()
if (error.statusCode === 404) {
this.error('no maintenance found for this add-on')
}

throw error
}

ux.action.stop()

if (json) {
Expand Down
7 changes: 3 additions & 4 deletions src/commands/data/maintenances/run.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {flags as Flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'
import {color, utils} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {Args, ux} from '@oclif/core'

import BaseCommand from '../../../lib/data/base-command.js'
Expand Down Expand Up @@ -63,10 +64,8 @@ export default class DataMaintenancesRun extends BaseCommand {
await this.confirmMaintenanceMode(addon, confirm, force || false)

ux.action.start('Triggering maintenance')
await this.dataApi.post(
`/data/maintenances/v1/${addon.id}/run`,
this.dataApi.defaults,
)
const {data} = new HerokuSDK()
await data.maintenance.run(addon.id!)
ux.action.stop('maintenance triggered')

if (wait) {
Expand Down
36 changes: 13 additions & 23 deletions src/commands/data/maintenances/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {flags} from '@heroku-cli/command'
import {Command, flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'
import {color, utils} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {MaintenanceInfoResult, MaintenanceScheduleResult} from '@heroku/types/data'
import {Args, ux} from '@oclif/core'

import BaseCommand from '../../../lib/data/base-command.js'
import {Maintenance} from '../../../lib/data/types.js'
import {lazyModuleLoader} from '../../../lib/lazy-module-loader.js'

export default class DataMaintenancesSchedule extends BaseCommand {
export default class DataMaintenancesSchedule extends Command {
static args = {
addon: Args.string({
description: 'addon to schedule or re-schedule maintenance for',
Expand Down Expand Up @@ -36,13 +36,10 @@ export default class DataMaintenancesSchedule extends BaseCommand {
}),
}

protected async computeDelayWeeks(addon: Heroku.AddOn, week: string, differenceInCalendarWeeks: any) {
const {body: maintenance} = await this.dataApi.get<Maintenance>(
`/data/maintenances/v1/${addon!.id}`,
this.dataApi.defaults,
)
protected async computeDelayWeeks(addon: Heroku.AddOn, week: string, differenceInCalendarWeeks: any, data: HerokuSDK['data']) {
const maintenance: MaintenanceInfoResult = await data.maintenance.info(addon.id!)

const scheduled = (maintenance.status === 'completed' || maintenance.scheduled_for === null)
const scheduled = (maintenance.status === 'completed' || !maintenance.scheduled_for)
? Date.now()
: Date.parse(maintenance.scheduled_for)

Expand All @@ -62,28 +59,21 @@ export default class DataMaintenancesSchedule extends BaseCommand {
const {app, week, weeks} = flags

const addon = await addonResolver.resolve(args.addon, app)
const {data} = new HerokuSDK()

const delayWeeks = week === undefined
? weeks
: await this.computeDelayWeeks(addon, week, differenceInCalendarWeeks)
: await this.computeDelayWeeks(addon, week, differenceInCalendarWeeks, data)

await this.scheduleMaintenance(addon, delayWeeks)
await this.scheduleMaintenance(addon, delayWeeks, data)
}

protected async scheduleMaintenance(addon: Heroku.AddOn, delayWeeks: string) {
protected async scheduleMaintenance(addon: Heroku.AddOn, delayWeeks: string, data: HerokuSDK['data']) {
ux.action.start(`Scheduling maintenance for ${color.addon(addon.name!)}`)
const {body: schedule} = await this.dataApi.post<Maintenance>(
`/data/maintenances/v1/${addon.id}/schedule`,
{
...this.dataApi.defaults,
body: {
delay_weeks: delayWeeks,
},
},
)
const schedule: MaintenanceScheduleResult = await data.maintenance.schedule(addon.id!, {delay_weeks: delayWeeks})
ux.action.stop('maintenance scheduled')

const alreadyScheduled = schedule.previously_scheduled_for !== null
const alreadyScheduled = !!schedule.previously_scheduled_for

if (alreadyScheduled) {
this.log(`Scheduled maintenance for ${color.addon(addon.name!)} changed from ${schedule.previously_scheduled_for} to ${schedule.scheduled_for}`)
Expand Down
9 changes: 4 additions & 5 deletions src/commands/data/maintenances/wait.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {flags as Flags} from '@heroku-cli/command'
import {color, utils} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {Args, ux} from '@oclif/core'

import BaseCommand from '../../../lib/data/base-command.js'
import {Maintenance, MaintenanceStatus} from '../../../lib/data/types.js'
import {MaintenanceStatus} from '../../../lib/data/types.js'
import {waitUntilMaintenanceComplete} from '../../../lib/data/utils.js'

export default class DataMaintenancesWait extends BaseCommand {
Expand All @@ -30,10 +31,8 @@ export default class DataMaintenancesWait extends BaseCommand {
this.error('You can\'t await maintenance on an Essential tier database.')
}

const {body: maintenance} = await this.dataApi.get<Maintenance>(
`/data/maintenances/v1/${addon.id}`,
this.dataApi.defaults,
)
const {data} = new HerokuSDK()
const maintenance = await data.maintenance.info(addon.id!)

if (maintenance.status !== MaintenanceStatus.running) {
this.error(`There currently isn't any maintenance in progress for ${color.addon(addon.name!)}`)
Expand Down
14 changes: 5 additions & 9 deletions src/commands/data/maintenances/window/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {flags as Flags} from '@heroku-cli/command'
import {Command, flags as Flags} from '@heroku-cli/command'
import {color, hux, utils} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {Args, ux} from '@oclif/core'

import BaseCommand from '../../../../lib/data/base-command.js'
import {Window} from '../../../../lib/data/types.js'

export default class DataMaintenancesWindow extends BaseCommand {
export default class DataMaintenancesWindow extends Command {
static args = {
addon: Args.string({
description: 'addon to show window for',
Expand All @@ -29,10 +27,8 @@ export default class DataMaintenancesWindow extends BaseCommand {
const addon = await addonResolver.resolve(args.addon, flags.app)

ux.action.start(`Fetching maintenance window for ${color.addon(addon.name!)}`)
const {body: window} = await this.dataApi.get<Window>(
`/data/maintenances/v1/${addon.id}/window`,
this.dataApi.defaults,
)
const {data} = new HerokuSDK()
const window = await data.maintenance.window(addon.id!)
ux.action.stop()

if (flags.json) {
Expand Down
23 changes: 8 additions & 15 deletions src/commands/data/maintenances/window/update.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {flags as Flags} from '@heroku-cli/command'
import {Command, flags as Flags} from '@heroku-cli/command'
import {color, hux, utils} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {Args, ux} from '@oclif/core'

import BaseCommand from '../../../../lib/data/base-command.js'
import {Window} from '../../../../lib/data/types.js'

export default class DataMaintenancesWindowUpdate extends BaseCommand {
export default class DataMaintenancesWindowUpdate extends Command {
static args = {
addon: Args.string({
description: 'addon to change window for',
Expand Down Expand Up @@ -40,16 +38,11 @@ export default class DataMaintenancesWindowUpdate extends BaseCommand {
const combinedWindowLabel = `${args.day_of_week} ${args.time_of_day}`
ux.action.start(`Setting maintenance window for ${color.addon(addon.name!)} to ${combinedWindowLabel}`)

const {body: result} = await this.dataApi.post<Window>(
`/data/maintenances/v1/${addon.id}/window`,
{
...this.dataApi.defaults,
body: {
day_of_week: args.day_of_week,
time_of_day: args.time_of_day,
},
},
)
const {data} = new HerokuSDK()
const result = await data.maintenance.updateWindow(addon.id!, {
day_of_week: args.day_of_week,
time_of_day: args.time_of_day,
})
ux.action.stop()

if (flags.json) {
Expand Down
7 changes: 5 additions & 2 deletions src/commands/pg/backups/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {Command, flags} from '@heroku-cli/command'
import {color, hux, utils} from '@heroku/heroku-cli-util'
import {color, hux} from '@heroku/heroku-cli-util'
import {HerokuSDK} from '@heroku/sdk'
import {ux} from '@oclif/core/ux'

import type {BackupTransfer} from '../../../lib/pg/types.js'

import backupsFactory from '../../../lib/pg/backups.js'
import {listTransfersByApp} from '../../../lib/pg/sdk-adapter.js'

export default class Index extends Command {
static description = 'list database backups'
Expand All @@ -24,7 +26,8 @@ export default class Index extends Command {
public async run(): Promise<void> {
const {flags: {app}} = await this.parse(Index)

const {body: transfers} = await this.heroku.get<BackupTransfer[]>(`/client/v11/apps/${app}/transfers`, {hostname: utils.pg.host()})
const {data} = new HerokuSDK()
const transfers = await listTransfersByApp(data, app)
// NOTE that the sort order is descending
transfers.sort((transferA, transferB) => transferB.created_at.localeCompare(transferA.created_at))

Expand Down
Loading
Loading