Guillermo Rauch
9 years ago
5 changed files with 216 additions and 4 deletions
@ -0,0 +1,175 @@ |
|||||
|
#!/usr/bin/env node |
||||
|
import chalk from 'chalk'; |
||||
|
import minimist from 'minimist'; |
||||
|
import * as cfg from '../lib/cfg'; |
||||
|
import { error } from '../lib/error'; |
||||
|
import NowSecrets from '../lib/secrets'; |
||||
|
import ms from 'ms'; |
||||
|
|
||||
|
const argv = minimist(process.argv.slice(2), { |
||||
|
boolean: ['help', 'debug', 'base64'], |
||||
|
alias: { |
||||
|
help: 'h', |
||||
|
debug: 'd', |
||||
|
base64: 'b' |
||||
|
} |
||||
|
}); |
||||
|
const subcommand = argv._[0]; |
||||
|
|
||||
|
// options |
||||
|
const help = () => { |
||||
|
console.log(` |
||||
|
${chalk.bold('𝚫 now secrets')} <ls | add | rm> <domain> |
||||
|
|
||||
|
${chalk.dim('Options:')} |
||||
|
|
||||
|
-h, --help output usage information |
||||
|
-b, --base64 treat value as base64-encoded |
||||
|
-d, --debug debug mode [off] |
||||
|
|
||||
|
${chalk.dim('Examples:')} |
||||
|
|
||||
|
${chalk.gray('–')} Lists all your secrets: |
||||
|
|
||||
|
${chalk.cyan('$ now secrets ls')} |
||||
|
|
||||
|
${chalk.gray('–')} Adds a new secret: |
||||
|
|
||||
|
${chalk.cyan('$ now secrets add my-secret "my value"')} |
||||
|
|
||||
|
${chalk.gray('–')} Once added, a secret's value can't be retrieved in plaintext anymore |
||||
|
${chalk.gray('–')} If the secret's value is more than one word, wrap it in quotes |
||||
|
${chalk.gray('–')} Actually, when in doubt, wrap your value in quotes |
||||
|
|
||||
|
${chalk.gray('–')} Exposes a secret as an env variable: |
||||
|
|
||||
|
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)} |
||||
|
|
||||
|
Notice the ${chalk.cyan.bold('`@`')} symbol which makes the value a secret reference. |
||||
|
|
||||
|
${chalk.gray('–')} Renames a secret: |
||||
|
|
||||
|
${chalk.cyan(`$ now secrets rename my-secret my-renamed-secret`)} |
||||
|
|
||||
|
${chalk.gray('–')} Removes a secret: |
||||
|
|
||||
|
${chalk.cyan(`$ now secrets rm my-secret`)} |
||||
|
`); |
||||
|
}; |
||||
|
|
||||
|
// options |
||||
|
const debug = argv.debug; |
||||
|
const apiUrl = argv.url || 'https://api.zeit.co'; |
||||
|
|
||||
|
const exit = (code) => { |
||||
|
// we give stdout some time to flush out |
||||
|
// because there's a node bug where |
||||
|
// stdout writes are asynchronous |
||||
|
// https://github.com/nodejs/node/issues/6456 |
||||
|
setTimeout(() => process.exit(code || 0), 100); |
||||
|
}; |
||||
|
|
||||
|
if (argv.help || !subcommand) { |
||||
|
help(); |
||||
|
exit(0); |
||||
|
} else { |
||||
|
const config = cfg.read(); |
||||
|
|
||||
|
Promise.resolve(config.token || login(apiUrl)) |
||||
|
.then(async (token) => { |
||||
|
try { |
||||
|
await run(token); |
||||
|
} catch (err) { |
||||
|
if (err.userError) { |
||||
|
error(err.message); |
||||
|
} else { |
||||
|
error(`Unknown error: ${err.stack}`); |
||||
|
} |
||||
|
exit(1); |
||||
|
} |
||||
|
}) |
||||
|
.catch((e) => { |
||||
|
error(`Authentication error – ${e.message}`); |
||||
|
exit(1); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async function run (token) { |
||||
|
const secrets = new NowSecrets(apiUrl, token, { debug }); |
||||
|
const args = argv._.slice(1); |
||||
|
const start = Date.now(); |
||||
|
|
||||
|
if ('ls' === subcommand || 'list' === subcommand) { |
||||
|
if (0 !== args.length) { |
||||
|
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}`); |
||||
|
return exit(1); |
||||
|
} |
||||
|
|
||||
|
const list = []; |
||||
|
|
||||
|
const elapsed = ms(new Date() - start); |
||||
|
console.log(`> ${list.length} secrets found ${chalk.gray(`[${elapsed}]`)}`); |
||||
|
return secrets.close(); |
||||
|
} |
||||
|
|
||||
|
if ('rm' === subcommand || 'remove' === subcommand) { |
||||
|
if (1 !== args.length) { |
||||
|
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret rm <id | name>`')}`); |
||||
|
return exit(1); |
||||
|
} |
||||
|
|
||||
|
const elapsed = ms(new Date() - start); |
||||
|
console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.name)} ${chalk.gray(`(${secret.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}`); |
||||
|
return secrets.close(); |
||||
|
} |
||||
|
|
||||
|
if ('rename' === subcommand) { |
||||
|
if (2 !== args.length) { |
||||
|
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret rename <old-name> <new-name>`')}`); |
||||
|
return exit(1); |
||||
|
} |
||||
|
|
||||
|
const secret = { |
||||
|
name: 'my-password', |
||||
|
uid: 'sec_iuh32u23bfigf2gu' |
||||
|
}; |
||||
|
|
||||
|
const elapsed = ms(new Date() - start); |
||||
|
console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.name)} ${chalk.gray(`(${secret.uid})`)} renamed to ${chalk.bold('my-secret-name')} ${chalk.gray(`[${elapsed}]`)}`); |
||||
|
return secrets.close(); |
||||
|
} |
||||
|
|
||||
|
if ('add' === subcommand || 'set' === subcommand) { |
||||
|
if (2 !== args.length) { |
||||
|
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret add <name> <value>`')}`); |
||||
|
if (args.length > 2) { |
||||
|
const [, ...rest] = args; |
||||
|
console.log('> If your secret has spaces, make sure to wrap it in quotes. Example: \n' |
||||
|
+ ` ${chalk.cyan('$ now secret add ${args[0]} "${escaped}"')} `); |
||||
|
} |
||||
|
return exit(1); |
||||
|
} |
||||
|
|
||||
|
const [name, value_] = args; |
||||
|
|
||||
|
let value; |
||||
|
if (argv.base64) { |
||||
|
value = { base64: value_ }; |
||||
|
} else { |
||||
|
value = value_; |
||||
|
} |
||||
|
|
||||
|
const secret = { |
||||
|
name: 'my-password', |
||||
|
uid: 'sec_iuh32u23bfigf2gu' |
||||
|
}; |
||||
|
|
||||
|
const elapsed = ms(new Date() - start); |
||||
|
console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.name)} ${chalk.gray(`(${secret.uid})`)} added ${chalk.gray(`[${elapsed}]`)}`); |
||||
|
return secrets.close(); |
||||
|
} |
||||
|
|
||||
|
error('Please specify a valid subcommand: ls | add | rename | rm'); |
||||
|
help(); |
||||
|
exit(1); |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
import Now from '../lib'; |
||||
|
|
||||
|
export default class Secrets extends Now { |
||||
|
|
||||
|
async ls () { |
||||
|
return this.retry(async (bail, attempt) => { |
||||
|
if (this._debug) console.time(`> [debug] #${attempt} GET /secrets`); |
||||
|
const res = await this._fetch('/secrets'); |
||||
|
if (this._debug) console.timeEnd(`> [debug] #${attempt} GET /secrets`); |
||||
|
const body = await res.json(); |
||||
|
return body.secrets; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async rm (nameOrId) { |
||||
|
return this.retry(async (bail, attempt) => { |
||||
|
if (this._debug) console.time(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`); |
||||
|
const res = await this._fetch(`/secrets/${nameOrId}`, { method: 'DELETE' }); |
||||
|
if (this._debug) console.timeEnd(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`); |
||||
|
|
||||
|
if (403 === res.status) { |
||||
|
return bail(new Error('Unauthorized')); |
||||
|
} |
||||
|
|
||||
|
if (res.status !== 200) { |
||||
|
const body = await res.json(); |
||||
|
throw new Error(body.error.message); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async add (name, value) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
Loading…
Reference in new issue