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