Skip to content

Commit

Permalink
Merge branch 'prerelease/9.0.0-alpha' into sbosio/spaces-drains-get
Browse files Browse the repository at this point in the history
  • Loading branch information
sbosio committed May 8, 2024
2 parents 3efe17e + c5525c7 commit 87dfc34
Show file tree
Hide file tree
Showing 14 changed files with 410 additions and 446 deletions.
56 changes: 56 additions & 0 deletions packages/cli/src/commands/spaces/trusted-ips/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import heredoc from 'tsheredoc'

export default class Index extends Command {
static aliases = ['trusted-ips']
static topic = 'trusted-ips'
static description = heredoc(`
list trusted IP ranges for a space
Trusted IP ranges are only available on Private Spaces.
The space name is a required parameter. Newly created spaces will have 0.0.0.0/0 set by default
allowing all traffic to applications in the space. More than one CIDR block can be provided at
a time to the commands listed below. For example 1.2.3.4/20 and 5.6.7.8/20 can be added with:
`)

static flags = {
space: flags.string({char: 's', description: 'space to get inbound rules from'}),
json: flags.boolean({description: 'output in json format'}),
}

static args = {
space: Args.string({hidden: true}),
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(Index)
const space = flags.space || args.space
if (!space) {
throw new Error('Space name required.\nUSAGE: heroku trusted-ips my-space')
}

const {body: rules} = await this.heroku.get<Required<Heroku.InboundRuleset>>(`/spaces/${space}/inbound-ruleset`,
{
headers: {Accept: 'application/vnd.heroku+json; version=3.dogwood'},
})

if (flags.json) {
ux.log(JSON.stringify(rules, null, 2))
} else {
this.displayRules(space, rules)
}
}

private displayRules(space: string, ruleset: Required<Heroku.InboundRuleset>) {
if (ruleset.rules.length > 0) {
ux.styledHeader('Trusted IP Ranges')
for (const rule of ruleset.rules) {
ux.log(rule.source)
}
} else {
ux.styledHeader(`${space} has no trusted IP ranges. All inbound web requests to dynos are blocked.`)
}
}
}
48 changes: 48 additions & 0 deletions packages/cli/src/commands/spaces/trusted-ips/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import color from '@heroku-cli/color'
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import heredoc from 'tsheredoc'

export default class Remove extends Command {
static aliases = ['trusted-ips:remove']
static topic = 'trusted-ips'
static description = heredoc(`
Remove a range from the list of trusted IP ranges
Uses CIDR notation.`)

static examples = [heredoc(`
$ heroku trusted-ips:remove --space my-space 192.168.2.0/24
Removed 192.168.2.0/24 from trusted IP ranges on my-space
`)]

static flags = {
space: flags.string({optional: false, description: 'space to remove rule from'}),
confirm: flags.string({description: 'set to space name to bypass confirm prompt'}),
}

static args = {
source: Args.string({required: true}),
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(Remove)
const space = flags.space
const url = `/spaces/${space}/inbound-ruleset`
const opts = {headers: {Accept: 'application/vnd.heroku+json; version=3.dogwood'}}
const {body: rules} = await this.heroku.get<Heroku.InboundRuleset>(url, opts)
if (rules.rules?.length === 0) {
throw new Error('No IP ranges are configured. Nothing to do.')
}

const originalLength = rules.rules?.length
rules.rules = rules.rules?.filter(r => r.source !== args.source)
if (rules.rules?.length === originalLength) {
throw new Error(`No IP range matching ${args.source} was found.`)
}

await this.heroku.put(url, {...opts, body: rules})
ux.log(`Removed ${color.cyan.bold(args.source)} from trusted IP ranges on ${color.cyan.bold(space)}`)
ux.warn('It may take a few moments for the changes to take effect.')
}
}
85 changes: 85 additions & 0 deletions packages/cli/src/commands/spaces/vpn/info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {Command, flags} from '@heroku-cli/command'
import {Args, ux} from '@oclif/core'
import * as Heroku from '@heroku-cli/schema'
import heredoc from 'tsheredoc'
import {displayCIDR, displayVPNStatus} from '../../../lib/spaces/format'

export default class Info extends Command {
static topic = 'spaces';
static description = 'display the information for VPN';
static example = heredoc(`
$ heroku spaces:vpn:info --space my-space vpn-connection-name
=== vpn-connection-name VPN Tunnel Info
Name: vpn-connection-name
ID: 123456789012
Public IP: 35.161.69.30
Routable CIDRs: 172.16.0.0/16
Status: failed
Status Message: supplied CIDR block already in use
=== my-space Tunnel Info
VPN Tunnel IP Address Status Last Changed Details
────────── ───────────── ────── ──────────────────── ─────────────
Tunnel 1 52.44.146.197 UP 2016-10-25T22:09:05Z status message
Tunnel 2 52.44.146.197 UP 2016-10-25T22:09:05Z status message
`)

static flags = {
space: flags.string({
char: 's',
description: 'space the vpn connection belongs to',
required: true,
}),
json: flags.boolean({description: 'output in json format'}),
}

static args = {
name: Args.string({
description: 'name or id of the VPN connection to get info from',
required: true,
}),
}

public async run(): Promise<void> {
const {flags, args} = await this.parse(Info)
const {space, json} = flags
const {name} = args
const {body: vpnConnection} = await this.heroku.get<Heroku.PrivateSpacesVpn>(`/spaces/${space}/vpn-connections/${name}`)
const connectionName = vpnConnection.name || name
this.render(connectionName, vpnConnection, json)
}

private displayVPNInfo(name: string, vpnConnection: Heroku.PrivateSpacesVpn) {
ux.styledHeader(`${name} VPN Info`)
ux.styledObject({
Name: name,
ID: vpnConnection.id,
'Public IP': vpnConnection.public_ip,
'Routable CIDRs': displayCIDR(vpnConnection.routable_cidrs),
Status: `${displayVPNStatus(vpnConnection.status)}`,
'Status Message': vpnConnection.status_message,
}, ['Name', 'ID', 'Public IP', 'Routable CIDRs', 'State', 'Status', 'Status Message'])
const vpnConnectionTunnels = vpnConnection.tunnels || []
vpnConnectionTunnels.forEach((val, i) => {
val.tunnel_id = 'Tunnel ' + (i + 1)
})
ux.styledHeader(`${name} VPN Tunnel Info`)
ux.table(vpnConnectionTunnels, {
tunnel_id: {header: 'VPN Tunnel'},
ip: {header: 'IP Address'},
status: {
header: 'Status',
get: row => displayVPNStatus(row.status),
},
last_status_change: {header: 'Status Last Changed'},
status_message: {header: 'Details'},
})
}

private render(name: string, vpnConnection: Heroku.PrivateSpacesVpn, json: boolean) {
if (json) {
ux.styledJSON(vpnConnection)
} else {
this.displayVPNInfo(name, vpnConnection)
}
}
}
15 changes: 15 additions & 0 deletions packages/cli/src/lib/spaces/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ export function peeringStatus(s: string) {
case 'failed':
case 'deleted':
case 'rejected':
}
}

export function displayVPNStatus(s: string | undefined) {
switch (s) {
case 'UP':
case 'available':
return `${color.green(s)}`
case 'pending':
case 'provisioning':
case 'deprovisioning':
return `${color.yellow(s)}`
case 'DOWN':
case 'deleting':
case 'deleted':
return `${color.red(s)}`
default:
return s
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {expect} from '@oclif/test'
import * as nock from 'nock'
import {stdout} from 'stdout-stderr'
import heredoc from 'tsheredoc'
import Cmd from '../../../../../src/commands/spaces/trusted-ips'
import runCommand from '../../../../helpers/runCommand'

const now = new Date()

describe('trusted-ips', function () {
it('shows the trusted IP ranges', async function () {
const api = nock('https://api.heroku.com:443')
.get('/spaces/my-space/inbound-ruleset')
.reply(200, {
version: '1',
default_action: 'allow',
created_at: now,
created_by: 'dickeyxxx',
rules: [
{source: '127.0.0.1/20', action: 'allow'},
],
})
await runCommand(Cmd, ['--space', 'my-space'])
expect(stdout.output).to.equal(heredoc(`
=== Trusted IP Ranges
127.0.0.1/20
`))
api.done()
})

it('shows the trusted IP ranges with blank rules', async function () {
const api = nock('https://api.heroku.com:443')
.get('/spaces/my-space/inbound-ruleset')
.reply(200, {
version: '1',
default_action: 'allow',
created_at: now,
created_by: 'dickeyxxx',
rules: [],
})
await runCommand(Cmd, ['--space', 'my-space'])
expect(stdout.output).to.equal('=== my-space has no trusted IP ranges. All inbound web requests to dynos are blocked.\n\n')
api.done()
})

it('shows the trusted IP ranges --json', async function () {
const ruleSet = {
version: '1',
default_action: 'allow',
created_at: now.toISOString(),
created_by: 'dickeyxxx',
rules: [
{source: '127.0.0.1/20', action: 'allow'},
],
}

const api = nock('https://api.heroku.com:443')
.get('/spaces/my-space/inbound-ruleset')
.reply(200, ruleSet)
await runCommand(Cmd, ['--space', 'my-space', '--json', 'true'])
expect(JSON.parse(stdout.output)).to.eql(ruleSet)
api.done()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {expect} from '@oclif/test'
import * as nock from 'nock'
import {stdout} from 'stdout-stderr'
import heredoc from 'tsheredoc'
import Cmd from '../../../../../src/commands/spaces/trusted-ips/remove'
import runCommand from '../../../../helpers/runCommand'

describe('trusted-ips:remove', function () {
it('removes a CIDR entry from the trusted IP ranges', async function () {
const api = nock('https://api.heroku.com:443')
.get('/spaces/my-space/inbound-ruleset')
.reply(200, {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
{source: '127.0.0.1/20', action: 'allow'},
],
},
)
.put('/spaces/my-space/inbound-ruleset', {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
],
})
.reply(200, {rules: []})
await runCommand(Cmd, ['127.0.0.1/20', '--space', 'my-space'])
expect(stdout.output).to.eq(heredoc(`
Removed 127.0.0.1/20 from trusted IP ranges on my-space
`))
api.done()
})
})
Loading

0 comments on commit 87dfc34

Please sign in to comment.