Skip to content

Commit

Permalink
refactor(spaces): Move command 'spaces:vpn:connections' to CLI (#2875)
Browse files Browse the repository at this point in the history
* Move command 'spaces:vpn:connections' to CLI

* Migrating missing unit tests for lib file

* Removing package 'spaces'
  • Loading branch information
sbosio committed May 10, 2024
1 parent 0f9d12c commit f43713a
Show file tree
Hide file tree
Showing 28 changed files with 246 additions and 2,208 deletions.
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'
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

0 comments on commit f43713a

Please sign in to comment.