Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Update command warning message for Fir spaces #3171

Merged
merged 9 commits into from
Jan 13, 2025
21 changes: 18 additions & 3 deletions packages/cli/src/commands/spaces/destroy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {Args, ux} from '@oclif/core'
import color from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import * as Heroku from '@heroku-cli/schema'
import heredoc from 'tsheredoc'
import confirmCommand from '../../lib/confirmCommand'
import {displayNat} from '../../lib/spaces/spaces'
import color from '@heroku-cli/color'
import {Space} from '../../lib/types/fir'

type RequiredSpaceWithNat = Required<Heroku.Space> & {outbound_ips?: Required<Heroku.SpaceNetworkAddressTranslation>}
type RequiredSpaceWithNat = Required<Space> & {outbound_ips?: Required<Heroku.SpaceNetworkAddressTranslation>}

export default class Destroy extends Command {
static topic = 'spaces'
Expand Down Expand Up @@ -40,10 +41,24 @@ export default class Destroy extends Command {

let natWarning = ''
const {body: space} = await this.heroku.get<RequiredSpaceWithNat>(`/spaces/${spaceName}`)

if (space.state === 'allocated') {
({body: space.outbound_ips} = await this.heroku.get<Required<Heroku.SpaceNetworkAddressTranslation>>(`/spaces/${spaceName}/nat`))
if (space.outbound_ips && space.outbound_ips.state === 'enabled') {
natWarning = `The Outbound IPs for this space will be reused!\nEnsure that external services no longer allow these Outbound IPs: ${displayNat(space.outbound_ips)}\n`
const ipv6 = space.generation?.name === 'fir' ? ' and IPv6' : ''
natWarning = heredoc`
${color.dim('===')} ${color.bold('WARNING: Outbound IPs Will Be Reused')}
${color.yellow(`⚠️ Deleting this space frees up the following outbound IPv4${ipv6} IPs for reuse:`)}
${color.bold(displayNat(space.outbound_ips) ?? '')}

${color.dim('Update the following configurations:')}
${color.dim('=')} IP allowlists
${color.dim('=')} Firewall rules
${color.dim('=')} Security group configurations
${color.dim('=')} Network ACLs

${color.yellow(`Ensure that you remove the listed IPv4${ipv6} addresses from your security configurations.`)}
`
}
}

Expand Down
85 changes: 78 additions & 7 deletions packages/cli/test/unit/commands/spaces/destroy.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,101 @@ import runCommand from '../../../helpers/runCommand'
import * as nock from 'nock'
import {expect} from 'chai'
import heredoc from 'tsheredoc'
import {ux} from '@oclif/core'
import * as sinon from 'sinon'

describe('spaces:destroy', function () {
const now = new Date()

beforeEach(function () {
sinon.stub(ux, 'prompt').resolves('my-space')
})

afterEach(function () {
nock.cleanAll()
sinon.restore()
})

it('destroys a space', async function () {
it('shows extended NAT warning for fir generation space', async function () {
const api = nock('https://api.heroku.com')
.get('/spaces/my-space')
.reply(200, {name: 'my-space', team: {name: 'my-team'}, region: {name: 'my-region'}, state: 'allocated', created_at: now})
.reply(200, {
name: 'my-space',
team: {name: 'my-team'},
region: {name: 'my-region'},
state: 'allocated',
created_at: now,
generation: {name: 'fir'},
})
.get('/spaces/my-space/nat')
.reply(200, {state: 'enabled', sources: ['1.1.1.1', '2.2.2.2']})
.delete('/spaces/my-space')
.reply(200)

await runCommand(Cmd, ['--space', 'my-space', '--confirm', 'my-space'])
await runCommand(Cmd, ['--space', 'my-space'])
api.done()
const replacer = /([»›])/g
expect(stderr.output.replace(replacer, '')).to.eq(heredoc(` › Warning: Destructive Action
› This command will destroy the space my-space
› === WARNING: Outbound IPs Will Be Reused
› ⚠️ Deleting this space frees up the following outbound IPv4 and IPv6 IPs
› for reuse:
› 1.1.1.1, 2.2.2.2
› Update the following configurations:
› = IP allowlists
› = Firewall rules
› = Security group configurations
› = Network ACLs
› Ensure that you remove the listed IPv4 and IPv6 addresses from your
› security configurations.

Destroying space my-space...
Destroying space my-space... done
`.replace(replacer, '')))
})

it('shows simple NAT warning for non-fir generation space', async function () {
const api = nock('https://api.heroku.com')
.get('/spaces/my-space')
.reply(200, {
name: 'my-space',
team: {name: 'my-team'},
region: {name: 'my-region'},
state: 'allocated',
created_at: now,
generation: {name: 'cedar'},
})
.get('/spaces/my-space/nat')
.reply(200, {state: 'enabled', sources: ['1.1.1.1', '2.2.2.2']})
.delete('/spaces/my-space')
.reply(200)

await runCommand(Cmd, ['--space', 'my-space'])
api.done()
const replacer = /([»›])/g
expect(stderr.output.replace(replacer, '')).to.eq(heredoc(` › Warning: Destructive Action
› This command will destroy the space my-space
› === WARNING: Outbound IPs Will Be Reused
› ⚠️ Deleting this space frees up the following outbound IPv4 IPs for reuse:
› 1.1.1.1, 2.2.2.2
› Update the following configurations:
› = IP allowlists
› = Firewall rules
› = Security group configurations
› = Network ACLs
› Ensure that you remove the listed IPv4 addresses from your security
› configurations.

expect(stderr.output).to.eq(heredoc`
Destroying space my-space...
Destroying space my-space... done
`)
Destroying space my-space...
Destroying space my-space... done
`.replace(replacer, '')))
})
})
Loading