Skip to content

Commit

Permalink
feat(redis-v5): add JSON output to redis:info
Browse files Browse the repository at this point in the history
Some customers are parsing the output of `redis:info` from the CLI in
order to get metadata about their Redis add-ons. In order to ease this
in lieu of a public API endpoint, lets add a JSON output for this
information.
  • Loading branch information
mble authored and fivetanley committed Apr 12, 2021
1 parent 36c90fe commit ddd2bba
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 42 deletions.
3 changes: 2 additions & 1 deletion packages/redis-v5/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
needsApp: true,
needsAuth: true,
args: [{ name: 'database', optional: true }],
flags: [{ name: 'json', description: 'format output as JSON', hasValue: false }],
description: 'gets information about redis',
run: cli.command((ctx, heroku) => api(ctx, heroku).info())
run: cli.command((ctx, heroku) => api(ctx, heroku).info()),
}
3 changes: 2 additions & 1 deletion packages/redis-v5/commands/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
needsApp: true,
needsAuth: true,
args: [{ name: 'database', optional: true }],
flags: [{ name: 'json', description: 'format output as JSON', hasValue: false }],
description: 'gets information about redis',
run: cli.command((ctx, heroku) => api(ctx, heroku).info())
run: cli.command((ctx, heroku) => api(ctx, heroku).info()),
}
58 changes: 41 additions & 17 deletions packages/redis-v5/lib/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@ const ADDON = process.env.HEROKU_REDIS_ADDON_NAME || 'heroku-redis'

module.exports = (context, heroku) => {
return {
request (path, method, body) {
let headers = { 'Accept': 'application/json' }
request(path, method, body) {
let headers = { Accept: 'application/json' }
if (process.env.HEROKU_HEADERS) {
Object.assign(headers, JSON.parse(process.env.HEROKU_HEADERS))
}
return heroku[(method || 'GET').toLowerCase()](path, {
host: HOST,
auth: `${context.auth.username}:${context.auth.password}`,
headers,
body
body,
})
},

makeAddonsFilter (filter) {
makeAddonsFilter(filter) {
if (filter) {
filter = filter.toUpperCase()
}

function matches (addon) {
function matches(addon) {
for (let i = 0; i < addon.config_vars.length; i++) {
let cfgName = addon.config_vars[i].toUpperCase()
if (cfgName.indexOf(filter) >= 0) {
Expand All @@ -37,7 +37,7 @@ module.exports = (context, heroku) => {
return false
}

function onResponse (addons) {
function onResponse(addons) {
let redisAddons = []
for (let i = 0; i < addons.length; i++) {
let addon = addons[i]
Expand All @@ -53,7 +53,7 @@ module.exports = (context, heroku) => {
return onResponse
},

async getRedisAddon (addonsList) {
async getRedisAddon(addonsList) {
addonsList = addonsList || heroku.get(`/apps/${context.app}/addons`)

let addonsFilter = this.makeAddonsFilter(context.args.database)
Expand All @@ -62,30 +62,49 @@ module.exports = (context, heroku) => {
if (addons.length === 0) {
cli.exit(1, 'No Redis instances found.')
} else if (addons.length > 1) {
let names = addons.map(function (addon) { return addon.name })
let names = addons.map(function (addon) {
return addon.name
})
cli.exit(1, `Please specify a single instance. Found: ${names.join(', ')}`)
}

return addons[0]
},

async info () {
async info() {
let addons = await heroku.get(`/apps/${context.app}/addons`)
// filter out non-redis addons
addons = this.makeAddonsFilter(context.args.database)(addons)
// get info for each db
let databases = addons.map(addon => {
let databases = addons.map((addon) => {
return {
addon: addon,
redis: this.request(`/redis/v0/databases/${addon.name}`).catch(function (err) {
if (err.statusCode !== 404) {
throw (err)
throw err
}
return null
})
}),
}
})

if (context.flags.json) {
let redii = []
for (let db of databases) {
let redis = await db.redis
if (redis == null) {
continue
}

redis.app = db.addon.app
redis.config_vars = db.addon.config_vars
const { formation, metaas_source, port, ...filteredRedis } = redis
redii.push(filteredRedis)
}
cli.styledJSON(redii)
return
}

// print out the info of the addon and redis db info
for (let db of databases) {
const redis = await db.redis
Expand All @@ -94,11 +113,16 @@ module.exports = (context, heroku) => {
}

cli.styledHeader(`${db.addon.name} (${db.addon.config_vars.join(', ')})`)
cli.styledHash(redis.info.reduce(function (memo, row) {
memo[row.name] = row.values
return memo
}, {}), redis.info.map(function (row) { return row.name }))
cli.styledHash(
redis.info.reduce(function (memo, row) {
memo[row.name] = row.values
return memo
}, {}),
redis.info.map(function (row) {
return row.name
}),
)
}
}
},
}
}
93 changes: 70 additions & 23 deletions packages/redis-v5/test/commands/info.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let nock = require('nock')

let commands = [
{ txt: ':info', command: require('../../commands/info') },
{ txt: '', command: require('../../commands/index') }
{ txt: '', command: require('../../commands/index') },
]

commands.forEach((cmd) => {
Expand All @@ -18,47 +18,92 @@ commands.forEach((cmd) => {
})

it('# prints out nothing when there is no redis DB', function () {
let app = nock('https://api.heroku.com:443')
.get('/apps/example/addons').reply(200, [])
let app = nock('https://api.heroku.com:443').get('/apps/example/addons').reply(200, [])

return command.run({ app: 'example', args: {} })
return command
.run({ app: 'example', args: {}, flags: {} })
.then(() => app.done())
.then(() => expect(cli.stdout).to.equal(''))
.then(() => expect(cli.stderr).to.equal(''))
})

it('# prints out redis info', function () {
let app = nock('https://api.heroku.com:443')
.get('/apps/example/addons').reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] }
.get('/apps/example/addons')
.reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] },
])

let redis = nock('https://redis-api.heroku.com:443')
.get('/redis/v0/databases/redis-haiku').reply(200, { info: [
{ name: 'Foo', values: ['Bar', 'Biz'] }
] })
.get('/redis/v0/databases/redis-haiku')
.reply(200, { info: [{ name: 'Foo', values: ['Bar', 'Biz'] }] })

return command.run({ app: 'example', args: {}, auth: { username: 'foobar', password: 'password' } })
return command
.run({ app: 'example', args: {}, flags: {}, auth: { username: 'foobar', password: 'password' } })
.then(() => app.done())
.then(() => redis.done())
.then(() => expect(cli.stdout).to.equal(
`=== redis-haiku (REDIS_FOO, REDIS_BAR)
.then(() =>
expect(cli.stdout).to.equal(
`=== redis-haiku (REDIS_FOO, REDIS_BAR)
Foo: Bar
Biz
`))
`,
),
)
.then(() => expect(cli.stderr).to.equal(''))
})

it('# prints out redis info when not found', function () {
it('# prints out JSON-formatted redis info', function () {
let app = nock('https://api.heroku.com:443')
.get('/apps/example/addons').reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] }
.get('/apps/example/addons')
.reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] },
])

let redis = nock('https://redis-api.heroku.com:443')
.get('/redis/v0/databases/redis-haiku').reply(404, {})
.get('/redis/v0/databases/redis-haiku')
.reply(200, { info: [{ name: 'Foo', values: ['Bar', 'Biz'] }] })

return command
.run({ app: 'example', args: {}, flags: { json: true }, auth: { username: 'foobar', password: 'password' } })
.then(() => app.done())
.then(() => redis.done())
.then(() =>
expect(cli.stdout).to.equal(
`[
{
"info": [
{
"name": "Foo",
"values": [
"Bar",
"Biz"
]
}
],
"config_vars": [
"REDIS_FOO",
"REDIS_BAR"
]
}
]
`,
),
)
.then(() => expect(cli.stderr).to.equal(''))
})

it('# prints out redis info when not found', function () {
let app = nock('https://api.heroku.com:443')
.get('/apps/example/addons')
.reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] },
])

let redis = nock('https://redis-api.heroku.com:443').get('/redis/v0/databases/redis-haiku').reply(404, {})

return command.run({ app: 'example', args: {}, auth: { username: 'foobar', password: 'password' } })
return command
.run({ app: 'example', args: {}, flags: {}, auth: { username: 'foobar', password: 'password' } })
.then(() => app.done())
.then(() => redis.done())
.then(() => expect(cli.stdout).to.equal(''))
Expand All @@ -67,14 +112,16 @@ Foo: Bar

it('# prints out redis info when error', function () {
nock('https://api.heroku.com:443')
.get('/apps/example/addons').reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] }
.get('/apps/example/addons')
.reply(200, [
{ name: 'redis-haiku', addon_service: { name: 'heroku-redis' }, config_vars: ['REDIS_FOO', 'REDIS_BAR'] },
])

nock('https://redis-api.heroku.com:443')
.get('/redis/v0/databases/redis-haiku').reply(503, {})
nock('https://redis-api.heroku.com:443').get('/redis/v0/databases/redis-haiku').reply(503, {})

return expect(command.run({ app: 'example', args: {}, auth: { username: 'foobar', password: 'password' } })).to.be.rejectedWith(/Expected response to be successful, got 503/)
return expect(
command.run({ app: 'example', args: {}, flags: {}, auth: { username: 'foobar', password: 'password' } }),
).to.be.rejectedWith(/Expected response to be successful, got 503/)
})
})
})

0 comments on commit ddd2bba

Please sign in to comment.