Skip to content

Commit

Permalink
update dyno restart and stop with fir support (#3061)
Browse files Browse the repository at this point in the history
* Add logic for new dyno restart interface

* Add tests for new restart functionality

* Add ps:stop changes

* fix tests

* Fix help text test for stop dyno

* Fix ps:restart example
  • Loading branch information
eablack authored Oct 29, 2024
1 parent 0c489e5 commit bd8a15e
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 64 deletions.
57 changes: 43 additions & 14 deletions packages/cli/src/commands/ps/restart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,73 @@ import {Command, flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'
import color from '@heroku-cli/color'
import {Args, ux} from '@oclif/core'

import {ProcessTypeCompletion} from '@heroku-cli/command/lib/completions'
import heredoc from 'tsheredoc'
export default class Restart extends Command {
static description = 'restart app dynos'
static description = heredoc(`
restart app dynos
if neither --dyno nor --type are specified, restarts all dynos on app
`)

static topic = 'ps'
static aliases = ['dyno:restart']
static hiddenAliases = ['restart']

static examples = [
'$ heroku ps:restart web.1',
'$ heroku ps:restart web',
'$ heroku ps:restart',
'$ heroku ps:restart --app myapp --dyno web.1',
'$ heroku ps:restart --app myapp --type web',
'$ heroku ps:restart --app myapp',
]

static help = 'if DYNO is not specified, restarts all dynos on app'

static args = {
dyno: Args.string({required: false}),
dyno: Args.string({required: false, deprecated: true}),
}

static flags = {
app: flags.app({required: true}),
remote: flags.remote(),
dyno: flags.string({
char: 'd',
description: 'restart a specific dyno (such as "web-123-456" or "worker.2")',
}),
type: flags.string({
description: 'restart all dynos of a process type (such as "web" or "worker")',
completion: ProcessTypeCompletion,
exclusive: ['dyno'],
}),
}

async run() {
const {args, flags} = await this.parse(Restart)

const app = flags.app
const dyno = args.dyno

const dyno = flags.dyno || args.dyno
const type = flags.type
let msg = 'Restarting'
let restartUrl

if (type) {
msg += ` all ${color.cyan(type)} dynos`
restartUrl = `/apps/${app}/formations/${encodeURIComponent(type)}`
} else if (dyno) {
if (args.dyno) {
ux.warn(`Passing DYNO as an arg is deprecated. Please use ${color.cmd('heroku ps:restart --dyno')} or ${color.cmd('heroku ps:restart --type')} instead.`)
}

msg += ` dyno ${color.cyan(dyno)}`
restartUrl = `/apps/${app}/dynos/${encodeURIComponent(dyno)}`
} else {
msg += ' all dynos'
restartUrl = `/apps/${app}/dynos`
}

if (dyno) msg += ` ${color.cyan(dyno)}`
msg += (dyno && dyno.includes('.')) ? ' dyno' : ' dynos'
msg += ` on ${color.app(app)}`

ux.action.start(msg)
await this.heroku.delete<Heroku.Dyno>(dyno ? `/apps/${app}/dynos/${encodeURIComponent(dyno)}` : `/apps/${app}/dynos`)
await this.heroku.delete<Heroku.Dyno>(restartUrl, {
headers: {
Accept: 'application/vnd.heroku+json; version=3.sdk',
},
})
ux.action.stop()
}
}
49 changes: 39 additions & 10 deletions packages/cli/src/commands/ps/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,68 @@ import {Command, flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'
import color from '@heroku-cli/color'
import {Args, ux} from '@oclif/core'
import {ProcessTypeCompletion} from '@heroku-cli/command/lib/completions'
import heredoc from 'tsheredoc'

export default class Stop extends Command {
static description = 'stop app dyno'
static description = 'stop app dyno or dyno type'
static topic = 'ps'
static aliases = ['dyno:stop', 'ps:kill', 'dyno:kill']
static hiddenAliases = ['stop', 'kill']

static examples = [
'$ heroku ps:stop run.1828',
'$ heroku ps:stop run',
'$ heroku ps:stop --app myapp --dyno run.1828',
'$ heroku ps:stop --app myapp --type run',
]

static help = 'stop app dyno or dyno type'

static args = {
dyno: Args.string({required: true}),
dyno: Args.string({required: false, deprecated: true}),
}

static flags = {
app: flags.app({required: true}),
remote: flags.remote(),
dyno: flags.string({
char: 'd',
description: 'stop a specific dyno (such as "web-123-456" or "worker.2")',
}),
type: flags.string({
description: 'stop all dynos of a process type (such as "web" or "worker")',
completion: ProcessTypeCompletion,
exclusive: ['dyno'],
}),
}

async run() {
const {args, flags} = await this.parse(Stop)

const app = flags.app
const dyno = args.dyno
const dyno = flags.dyno || args.dyno
const type = flags.type
let msg = 'Stopping'
let stopUrl = ''

if (type) {
msg += ` all ${color.cyan(type)} dynos`
stopUrl = `/apps/${app}/formations/${encodeURIComponent(type)}/actions/stop`
} else if (dyno) {
if (args.dyno) {
ux.warn(`Passing DYNO as an arg is deprecated. Please use ${color.cmd('heroku ps:stop --dyno')} or ${color.cmd('heroku ps:stop --type')} instead.`)
}

msg += ` dyno ${color.cyan(dyno)}`
stopUrl = `/apps/${app}/dynos/${encodeURIComponent(dyno)}/actions/stop`
} else {
ux.error(heredoc(`
Please specify a process type or dyno to stop.
See more help with --help
`))
}

const type = dyno.includes('.') ? 'ps' : 'type'
msg += ` on ${color.app(app)}`

ux.action.start(`Stopping ${color.cyan(dyno)} ${type === 'ps' ? 'dyno' : 'dynos'} on ${color.app(app)}`)
await this.heroku.post<Heroku.Dyno>(`/apps/${app}/dynos/${dyno}/actions/stop`)
ux.action.start(msg)
await this.heroku.post<Heroku.Dyno>(stopUrl, {headers: {Accept: 'application/vnd.heroku+json; version=3.sdk'}})
ux.action.stop()
}
}
1 change: 1 addition & 0 deletions packages/cli/src/commands/spaces/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class Create extends Command {
CIDR: 10.0.0.0/16
Data CIDR: 172.23.0.0/20
State: allocating
Generation: fir
Created at: 2016-01-06T03:23:13Z
`]

Expand Down
8 changes: 4 additions & 4 deletions packages/cli/test/acceptance/commands-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ domains:wait wait for domain to be active for
drains display the log drains of an app
drains:add adds a log drain to an app
drains:remove removes a log drain from an app
dyno:kill stop app dyno
dyno:kill stop app dyno or dyno type
dyno:resize manage dyno sizes
dyno:restart restart app dynos
dyno:scale scale dyno quantity up or down
dyno:stop stop app dyno
dyno:stop stop app dyno or dyno type
features list available app features
features:disable disables an app feature
features:enable enables an app feature
Expand Down Expand Up @@ -238,12 +238,12 @@ ps:autoscale:enable enable web dyno autoscaling
ps:copy Copy a file from a dyno to the local filesystem
ps:exec Create an SSH session to a dyno
ps:forward Forward traffic on a local port to a dyno
ps:kill stop app dyno
ps:kill stop app dyno or dyno type
ps:resize manage dyno sizes
ps:restart restart app dynos
ps:scale scale dyno quantity up or down
ps:socks Launch a SOCKS proxy into a dyno
ps:stop stop app dyno
ps:stop stop app dyno or dyno type
ps:type manage dyno sizes
ps:wait wait for all dynos to be running latest version after a release
psql open a psql shell to the database
Expand Down
81 changes: 69 additions & 12 deletions packages/cli/test/unit/commands/ps/restart.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,73 @@
import {expect, test} from '@oclif/test'
import {stderr} from 'stdout-stderr'
import Cmd from '../../../../src/commands/ps/restart'
import runCommand from '../../../helpers/runCommand'
import * as nock from 'nock'
import heredoc from 'tsheredoc'
import expectOutput from '../../../helpers/utils/expectOutput'
import {expect} from 'chai'
import stripAnsi = require('strip-ansi')

describe('ps:restart', function () {
test
.stdout()
.stderr()
.nock('https://api.heroku.com:443', api => api
it('restarts all dynos', async function () {
nock('https://api.heroku.com')
.delete('/apps/myapp/dynos')
.reply(200),
)
.command(['ps:restart', '-a', 'myapp'])
.it('restarts all dynos', ({stdout, stderr}) => {
expect(stdout).to.be.empty
expect(stderr).to.contains('Restarting dynos on ⬢ myapp... done\n')
})
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
])
expectOutput(stderr.output, heredoc(`
Restarting all dynos on ⬢ myapp...
Restarting all dynos on ⬢ myapp... done
`))
})

it('restarts web dynos', async function () {
nock('https://api.heroku.com')
.delete('/apps/myapp/formations/web')
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
'--type',
'web',
])
expectOutput(stderr.output, heredoc(`
Restarting all web dynos on ⬢ myapp...
Restarting all web dynos on ⬢ myapp... done
`))
})

it('restarts a specific dyno', async function () {
nock('https://api.heroku.com')
.delete('/apps/myapp/dynos/web.1')
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
'--dyno',
'web.1',
])
expectOutput(stderr.output, heredoc(`
Restarting dyno web.1 on ⬢ myapp...
Restarting dyno web.1 on ⬢ myapp... done
`))
})

it('emits a warning when passing dyno as an arg', async function () {
nock('https://api.heroku.com')
.delete('/apps/myapp/dynos/web.1')
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
'web.1',
])
expect(stripAnsi(stderr.output)).to.include('Warning: Passing DYNO as an arg is deprecated.')
expect(stderr.output).to.include('Restarting dyno web.1 on ⬢ myapp... done')
})
})
86 changes: 62 additions & 24 deletions packages/cli/test/unit/commands/ps/stop.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,67 @@
import {expect, test} from '@oclif/test'
import {stderr} from 'stdout-stderr'
import Cmd from '../../../../src/commands/ps/stop'
import runCommand from '../../../helpers/runCommand'
import * as nock from 'nock'
import heredoc from 'tsheredoc'
import expectOutput from '../../../helpers/utils/expectOutput'
import {expect} from 'chai'
import stripAnsi = require('strip-ansi')

describe('ps:stop', function () {
test
.stdout()
.stderr()
.nock('https://api.heroku.com:443', api => api
.post('/apps/myapp/dynos/web/actions/stop')
.reply(200),
)
.command(['ps:stop', 'web', '-a', 'myapp'])
.it('stops all web dynos', ({stdout, stderr}) => {
expect(stdout).to.be.empty
expect(stderr).to.contains('Stopping web dynos on ⬢ myapp... done\n')
it('requires a dyno name or type', async function () {
await runCommand(Cmd, [
'--app',
'myapp',
]).catch(error => {
expect(error.message).to.include('Please specify a process type or dyno to stop.')
})
})

test
.stdout()
.stderr()
.nock('https://api.heroku.com:443', api => api
.post('/apps/myapp/dynos/run.10/actions/stop')
.reply(200),
)
.command(['ps:stop', 'run.10', '-a', 'myapp'])
.it('stops run.10 dyno', ({stdout, stderr}) => {
expect(stdout).to.be.empty
expect(stderr).to.contains('Stopping run.10 dyno on ⬢ myapp... done\n')
})
it('restarts web dynos', async function () {
nock('https://api.heroku.com')
.post('/apps/myapp/formations/web/actions/stop')
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
'--type',
'web',
])
expectOutput(stderr.output, heredoc(`
Stopping all web dynos on ⬢ myapp...
Stopping all web dynos on ⬢ myapp... done
`))
})

it('restarts a specific dyno', async function () {
nock('https://api.heroku.com')
.post('/apps/myapp/dynos/web.1/actions/stop')
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
'--dyno',
'web.1',
])
expectOutput(stderr.output, heredoc(`
Stopping dyno web.1 on ⬢ myapp...
Stopping dyno web.1 on ⬢ myapp... done
`))
})

it('emits a warning when passing dyno as an arg', async function () {
nock('https://api.heroku.com')
.post('/apps/myapp/dynos/web.1/actions/stop')
.reply(202)

await runCommand(Cmd, [
'--app',
'myapp',
'web.1',
])
expect(stripAnsi(stderr.output)).to.include('Warning: Passing DYNO as an arg is deprecated.')
expect(stderr.output).to.include('Stopping dyno web.1 on ⬢ myapp... done')
})
})

0 comments on commit bd8a15e

Please sign in to comment.