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

refactor(spaces): Move command 'spaces:vpn:connections' to CLI #2875

Merged
merged 5 commits into from
May 10, 2024
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
3 changes: 0 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@
"@heroku-cli/command-v9": "npm:@heroku-cli/command@^9.0.2",
"@heroku-cli/notifications": "^1.2.4",
"@heroku-cli/plugin-certs-v5": "^9.0.0-alpha.0",
"@heroku-cli/plugin-ci-v5": "^9.0.0-alpha.0",
"@heroku-cli/plugin-ps": "^8.1.7",
"@heroku-cli/plugin-ps-exec": "^2.4.0",
"@heroku-cli/plugin-run": "8.1.4",
"@heroku-cli/plugin-spaces": "^9.0.0-alpha.0",
"@heroku-cli/schema": "^1.0.25",
"@heroku/buildpack-registry": "^1.0.1",
"@heroku/eventsource": "^1.0.7",
Expand Down Expand Up @@ -183,7 +181,6 @@
"@oclif/plugin-legacy",
"@heroku-cli/plugin-certs-v5",
"@heroku-cli/plugin-ps-exec",
"@heroku-cli/plugin-spaces",
"@oclif/plugin-commands",
"@oclif/plugin-help",
"@oclif/plugin-not-found",
Expand Down
66 changes: 66 additions & 0 deletions packages/cli/src/commands/spaces/vpn/connections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {Command, flags} from '@heroku-cli/command'
import {ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import heredoc from 'tsheredoc'
import {displayVPNStatus} from '../../../lib/spaces/format'

type VpnConnectionTunnels = Required<Heroku.PrivateSpacesVpn>['tunnels']

export default class Connections extends Command {
static topic = 'spaces'
static description = 'list the VPN Connections for a space'
static example = heredoc`
$ heroku spaces:vpn:connections --space my-space
=== my-space VPN Connections
Name Status Tunnels
────── ────── ───────
office active UP/UP
`
static flags = {
space: flags.string({char: 's', description: 'space to get VPN connections from', required: true}),
json: flags.boolean({description: 'output in json format'}),
}

public async run(): Promise<void> {
const {flags} = await this.parse(Connections)
const {space, json} = flags
const {body: connections} = await this.heroku.get<Required<Heroku.PrivateSpacesVpn>[]>(`/spaces/${space}/vpn-connections`)
this.render(space, connections, json)
}

protected render(space: string, connections: Required<Heroku.PrivateSpacesVpn>[], json: boolean) {
if (json) {
ux.styledJSON(connections)
} else {
this.displayVPNConnections(space, connections)
}
}

protected displayVPNConnections(space: string, connections: Required<Heroku.PrivateSpacesVpn>[]) {
if (connections.length === 0) {
ux.log('No VPN Connections have been created yet')
return
}

ux.styledHeader(`${space} VPN Connections`)

ux.table(
connections,
{
Name: {
get: c => c.name || c.id,
},
Status: {
get: c => displayVPNStatus(c.status),
},
Tunnels: {
get: c => this.tunnelFormat(c.tunnels),
},
},
)
}

protected tunnelFormat(t: VpnConnectionTunnels) {
return t.map(tunnel => displayVPNStatus(tunnel.status)).join('/')
}
}
5 changes: 5 additions & 0 deletions packages/cli/src/lib/spaces/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export function hostStatus(s: string) {
return `${color.red(s)}`
case 'released':
return `${color.gray(s)}`
default:
return s
}
}

Expand All @@ -29,6 +31,9 @@ export function peeringStatus(s: string) {
case 'failed':
case 'deleted':
case 'rejected':
return `${color.red(s)}`
default:
return s
}
}

Expand Down
113 changes: 113 additions & 0 deletions packages/cli/test/unit/commands/spaces/vpn/connections.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {stdout} from 'stdout-stderr'
import Cmd from '../../../../../src/commands/spaces/vpn/connections'
import runCommand from '../../../../helpers/runCommand'
import * as nock from 'nock'
import {expect} from 'chai'
import heredoc from 'tsheredoc'

describe('spaces:vpn:connections', function () {
afterEach(function () {
nock.cleanAll()
})

const space = {
id: '123456789012',
name: 'office',
public_ip: '35.161.69.30',
routable_cidrs: [
'172.16.0.0/16',
],
ike_version: 1,
space_cidr_block: '10.0.0.0/16',
status: 'active',
status_message: 'Active',
tunnels: [
{
last_status_change: '2016-10-25T22:10:05Z',
ip: '52.44.146.197',
customer_ip: '52.44.146.197',
pre_shared_key: 'secret',
status: 'UP',
status_message: 'status message',
},
{
last_status_change: '2016-10-25T22:09:05Z',
ip: '52.44.146.196',
customer_ip: '52.44.146.196',
pre_shared_key: 'secret',
status: 'UP',
status_message: 'status message',
},
],
}

it('displays no connection message if none exist', async function () {
const api = nock('https://api.heroku.com')
.get('/spaces/my-space/vpn-connections')
.reply(200, [])

await runCommand(Cmd, [
'--space',
'my-space',
])

api.done()
expect(stdout.output).to.eq('No VPN Connections have been created yet\n')
})

it('displays VPN Connections', async function () {
const api = nock('https://api.heroku.com')
.get('/spaces/my-space/vpn-connections')
.reply(200, [space])

await runCommand(Cmd, [
'--space',
'my-space',
])

api.done()
expect(stdout.output).to.eq(heredoc`
=== my-space VPN Connections

Name Status Tunnels
────── ────── ───────
office active UP/UP
`)
})

it('displays VPN Connection ID when name is unavailable', async function () {
const conn = {...space, name: ''}
const api = nock('https://api.heroku.com')
.get('/spaces/my-space/vpn-connections')
.reply(200, [conn])

await runCommand(Cmd, [
'--space',
'my-space',
])

api.done()
expect(stdout.output).to.eq(heredoc`
=== my-space VPN Connections

Name Status Tunnels
──────────── ────── ───────
123456789012 active UP/UP
`)
})

it('displays VPN Connections in JSON', async function () {
const api = nock('https://api.heroku.com')
.get('/spaces/my-space/vpn-connections')
.reply(200, [space])

await runCommand(Cmd, [
'--space',
'my-space',
'--json',
])

api.done()
expect(JSON.parse(stdout.output)).to.eql([space])
})
})
56 changes: 56 additions & 0 deletions packages/cli/test/unit/lib/spaces/format.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {expect} from 'chai'
sbosio marked this conversation as resolved.
Show resolved Hide resolved
import {displayCIDR, hostStatus, peeringStatus, displayVPNStatus} from '../../../../src/lib/spaces/format'

describe('displayCIDR', function () {
it('formats an array of cidrs', function () {
return expect(displayCIDR(['a', 'b'])).to.eq('a, b')
})

it('formats an array with a single cidr', function () {
return expect(displayCIDR(['a'])).to.eq('a')
})

it('returns empty string if cidr has no value', function () {
// eslint-disable-next-line unicorn/no-useless-undefined
return expect(displayCIDR(undefined)).to.eq('')
})
})

describe('hostStatus', function () {
it('returns ANSI colorized host status if status meets switch statement condition, uncolorized value if it does not', function () {
expect(hostStatus('available')).to.eq('\u001B[32mavailable\u001B[39m')
expect(hostStatus('under-assessment')).to.eq('\u001B[33munder-assessment\u001B[39m')
expect(hostStatus('permanent-failure')).to.eq('\u001B[31mpermanent-failure\u001B[39m')
expect(hostStatus('released-permanent-failure')).to.eq('\u001B[31mreleased-permanent-failure\u001B[39m')
expect(hostStatus('released')).to.eq('\u001B[2mreleased\u001B[22m')
expect(hostStatus('foo')).to.eq('foo')
})
})

describe('displayVPNStatus', function () {
it('returns ANSI colorized VPN status if status meets switch statement condition, uncolorized value if it does not', function () {
expect(displayVPNStatus('UP')).to.eq('\u001B[32mUP\u001B[39m')
expect(displayVPNStatus('available')).to.eq('\u001B[32mavailable\u001B[39m')
expect(displayVPNStatus('pending')).to.eq('\u001B[33mpending\u001B[39m')
expect(displayVPNStatus('provisioning')).to.eq('\u001B[33mprovisioning\u001B[39m')
expect(displayVPNStatus('deprovisioning')).to.eq('\u001B[33mdeprovisioning\u001B[39m')
expect(displayVPNStatus('DOWN')).to.eq('\u001B[31mDOWN\u001B[39m')
expect(displayVPNStatus('deleting')).to.eq('\u001B[31mdeleting\u001B[39m')
expect(displayVPNStatus('deleted')).to.eq('\u001B[31mdeleted\u001B[39m')
expect(displayVPNStatus('foo')).to.eq('foo')
})
})

describe('peeringStatus', function () {
it('returns ANSI colorized peering status if status meets switch statement condition, uncolorized value if it does not', function () {
expect(peeringStatus('active')).to.eq('\u001B[32mactive\u001B[39m')
expect(peeringStatus('pending-acceptance')).to.eq('\u001B[33mpending-acceptance\u001B[39m')
expect(peeringStatus('provisioning')).to.eq('\u001B[33mprovisioning\u001B[39m')
expect(peeringStatus('expired')).to.eq('\u001B[31mexpired\u001B[39m')
expect(peeringStatus('failed')).to.eq('\u001B[31mfailed\u001B[39m')
expect(peeringStatus('deleted')).to.eq('\u001B[31mdeleted\u001B[39m')
expect(peeringStatus('rejected')).to.eq('\u001B[31mrejected\u001B[39m')
expect(peeringStatus('foo')).to.eq('foo')
})
})

3 changes: 0 additions & 3 deletions packages/spaces/.gitignore

This file was deleted.

6 changes: 0 additions & 6 deletions packages/spaces/.npmignore

This file was deleted.

Loading
Loading