Skip to content
13 changes: 12 additions & 1 deletion packages/cli/src/commands/spaces/trusted-ips/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,18 @@ export default class Add extends Command {
ruleset.rules.push({action: 'allow', source: args.source})
await this.heroku.put(url, {body: ruleset})
ux.log(`Added ${color.cyan.bold(args.source)} to trusted IP ranges on ${color.cyan.bold(space)}`)
ux.warn('It may take a few moments for the changes to take effect.')

// Fetch updated ruleset to check applied status
const {body: updatedRuleset} = await this.heroku.get<Heroku.InboundRuleset>(url)
// Check applied status to inform users whether rules are effectively applied to the space.
// The applied field is optional for backward compatibility with API versions that don't include it yet.
// Once the API always includes the applied field (W-19525612), this can be simplified to:
// if (updatedRuleset.applied) { ... } else { ... }
if (updatedRuleset.applied === true) {
ux.log('Trusted IP rules are applied to this space.')
} else if (updatedRuleset.applied === false) {
ux.log('Trusted IP rules are not applied to this space. Update your Trusted IP list to trigger a re-application of the rules.')
}
}

private isUniqueRule(ruleset: Heroku.InboundRuleset, source: string): ruleset is Required<Heroku.InboundRuleset> {
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/commands/spaces/trusted-ips/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,15 @@ export default class Index extends Command {
} else {
hux.styledHeader(`${space} has no trusted IP ranges. All inbound web requests to dynos are blocked.`)
}

// Check applied status to inform users whether rules are effectively applied to the space.
// The applied field is optional for backward compatibility with API versions that don't include it yet.
// Once the API always includes the applied field (W-19525612), this can be simplified to:
// if (ruleset.applied) { ... } else { ... }
if (ruleset.applied === true) {
ux.log('Trusted IP rules are applied to this space.')
} else if (ruleset.applied === false) {
ux.log('Trusted IP rules are not applied to this space. Update your Trusted IP list to trigger a re-application of the rules.')
}
}
}
13 changes: 12 additions & 1 deletion packages/cli/src/commands/spaces/trusted-ips/remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ export default class Remove extends Command {

await this.heroku.put(url, {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.')

// Fetch updated ruleset to check applied status
const {body: updatedRuleset} = await this.heroku.get<Heroku.InboundRuleset>(url)
// Check applied status to inform users whether rules are effectively applied to the space.
// The applied field is optional for backward compatibility with API versions that don't include it yet.
// Once the API always includes the applied field (W-19525612), this can be simplified to:
// if (updatedRuleset.applied) { ... } else { ... }
if (updatedRuleset.applied === true) {
ux.log('Trusted IP rules are applied to this space.')
} else if (updatedRuleset.applied === false) {
ux.log('Trusted IP rules are not applied to this space. Update your Trusted IP list to trigger a re-application of the rules.')
}
}
}
18 changes: 12 additions & 6 deletions packages/cli/src/lib/types/fir.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4423,13 +4423,19 @@ export interface InboundRuleset {
* @example "2012-01-01T12:00:00Z"
*/
readonly created_at: string;
rules: Rule[];
/**
* unique email address of account
*
* @example "[email protected]"
*/
rules: Rule[];
/**
* unique email address of account
*
* @example "[email protected]"
*/
created_by: string;
/**
* whether the trusted IP rules have been applied to the space
*
* @example true
*/
applied?: boolean;
}
/**
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,77 @@ describe('trusted-ips:add', function () {
],
})
.reply(200, {rules: []})
.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'},
],
applied: true,
})
await runCommand(Cmd, ['127.0.0.1/20', '--space', 'my-space', '--confirm', 'my-space'])
expect(stdout.output).to.eq('Added 127.0.0.1/20 to trusted IP ranges on my-space\nTrusted IP rules are applied to this space.\n')
api.done()
})

it('shows message when applied is false after add', 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'},
],
})
.put('/spaces/my-space/inbound-ruleset', {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
{source: '127.0.0.1/20', action: 'allow'},
],
})
.reply(200, {rules: []})
.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'},
],
applied: false,
})
await runCommand(Cmd, ['127.0.0.1/20', '--space', 'my-space', '--confirm', 'my-space'])
expect(stdout.output).to.include('Added 127.0.0.1/20 to trusted IP ranges on my-space')
expect(stdout.output).to.include('Trusted IP rules are not applied to this space. Update your Trusted IP list to trigger a re-application of the rules.')
api.done()
})

it('shows nothing when applied is undefined (backward compatibility)', 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'},
],
})
.put('/spaces/my-space/inbound-ruleset', {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
{source: '127.0.0.1/20', action: 'allow'},
],
})
.reply(200, {rules: []})
.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'},
],
})
await runCommand(Cmd, ['127.0.0.1/20', '--space', 'my-space', '--confirm', 'my-space'])
expect(stdout.output).to.eq('Added 127.0.0.1/20 to trusted IP ranges on my-space\n')
api.done()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ describe('trusted-ips', function () {
rules: [
{source: '127.0.0.1/20', action: 'allow'},
],
applied: true,
})
await runCommand(Cmd, ['--space', 'my-space'])
expect(stdout.output).to.equal(heredoc(`
=== Trusted IP Ranges

127.0.0.1/20
Trusted IP rules are applied to this space.
`))
api.done()
})
Expand All @@ -38,9 +40,10 @@ describe('trusted-ips', function () {
created_at: now,
created_by: 'dickeyxxx',
rules: [],
applied: true,
})
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')
expect(stdout.output).to.equal('=== my-space has no trusted IP ranges. All inbound web requests to dynos are blocked.\n\nTrusted IP rules are applied to this space.\n')
api.done()
})

Expand All @@ -53,6 +56,7 @@ describe('trusted-ips', function () {
rules: [
{source: '127.0.0.1/20', action: 'allow'},
],
applied: true,
}

const api = nock('https://api.heroku.com:443')
Expand All @@ -62,4 +66,43 @@ describe('trusted-ips', function () {
expect(JSON.parse(stdout.output)).to.eql(ruleSet)
api.done()
})

it('shows message when applied is false', 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'},
],
applied: false,
})
await runCommand(Cmd, ['--space', 'my-space'])
expect(stdout.output).to.include('Trusted IP rules are not applied to this space. Update your Trusted IP list to trigger a re-application of the rules.')
api.done()
})

it('shows nothing when applied is undefined (backward compatibility)', 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()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,79 @@ describe('trusted-ips:remove', function () {
],
})
.reply(200, {rules: []})
.get('/spaces/my-space/inbound-ruleset')
.reply(200, {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
],
applied: true,
})
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
Trusted IP rules are applied to this space.
`))
api.done()
})

it('shows message when applied is false after remove', 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: []})
.get('/spaces/my-space/inbound-ruleset')
.reply(200, {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
],
applied: false,
})
await runCommand(Cmd, ['127.0.0.1/20', '--space', 'my-space'])
expect(stdout.output).to.include('Removed 127.0.0.1/20 from trusted IP ranges on my-space')
expect(stdout.output).to.include('Trusted IP rules are not applied to this space. Update your Trusted IP list to trigger a re-application of the rules.')
api.done()
})

it('shows nothing when applied is undefined (backward compatibility)', 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: []})
.get('/spaces/my-space/inbound-ruleset')
.reply(200, {
created_by: 'dickeyxxx',
rules: [
{source: '128.0.0.1/20', action: 'allow'},
],
})
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
Expand Down
Loading