Browse Source

Add -e (#126)

* implement `-e`

* index: pass along `env` to `/create`

* index: add `listSecrets` method

* secrets: fix api calls

* support for escaping
master
Guillermo Rauch 8 years ago
committed by GitHub
parent
commit
a3fe6eda84
  1. 105
      bin/now-deploy
  2. 12
      lib/index.js
  3. 14
      lib/secrets.js

105
bin/now-deploy

@ -20,6 +20,7 @@ const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'version', 'debug', 'force', 'login', 'no-clipboard', 'forward-npm', 'docker', 'npm'],
alias: {
env: 'e',
help: 'h',
config: 'c',
debug: 'd',
@ -49,16 +50,17 @@ const help = () => {
${chalk.dim('Options:')}
-h, --help output usage information
-v, --version output the version number
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} config file
-d, --debug debug mode [off]
-f, --force force a new deployment even if nothing has changed
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} login token
-L, --login configure login
-p, --public deployment is public (\`/_src\` is exposed) [on for oss, off for premium]
-C, --no-clipboard do not attempt to copy URL to clipboard
-N, --forward-npm Forward login information to install private NPM modules
-h, --help output usage information
-v, --version output the version number
-c ${chalk.underline('FILE')}, --config=${chalk.underline('FILE')} config file
-d, --debug debug mode [off]
-f, --force force a new deployment even if nothing has changed
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline('TOKEN')} login token
-L, --login configure login
-p, --public deployment is public (\${clalk.dim('`/_src`')} is exposed) [on for oss, off for premium]
-e, --env include an env var (e.g.: ${chalk.dim('`-e KEY=value`')}). Can appear many times.
-C, --no-clipboard do not attempt to copy URL to clipboard
-N, --forward-npm forward login information to install private NPM modules
${chalk.dim('Examples:')}
@ -78,6 +80,14 @@ const help = () => {
${chalk.cyan('$ now alias deploymentId custom-domain.com')}
${chalk.gray('–')} Stores a secret
${chalk.cyan('$ now secret add mysql-password 123456')}
${chalk.gray('–')} Deploys with ENV vars (using the ${chalk.dim('`mysql-password`')} secret stored above)
${chalk.cyan('$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password')}
${chalk.gray('–')} Displays comprehensive help for the subcommand ${chalk.dim('`list`')}
${chalk.cyan('$ now help list')}
@ -229,9 +239,84 @@ async function sync (token) {
const now = new Now(apiUrl, token, { debug });
const envs = [].concat(argv.env);
let secrets;
const findSecret = async (uidOrName) => {
if (!secrets) secrets = await now.listSecrets();
return secrets.filter((secret) => {
return secret.name === uidOrName || secret.uid === uidOrName;
});
};
const env_ = await Promise.all(envs.map(async (kv) => {
const [key, val_, ...rest] = kv.split('=');
let val;
if (rest.length) {
error(`Invalid env ${chalk.bold(`"${kv}"`)}. It cannot contain more than one ${chalk.dim(`=`)} symbol`);
return process.exit(1);
}
if (/[^A-z0-9_]/i.test(key)) {
error(`Invalid ${chalk.dim('-e')} key ${chalk.bold(`"${chalk.bold(key)}"`)}. Only letters, digits and underscores are allowed.`);
return process.exit(1);
}
if ('' === key || null == key) {
error(`Invalid env option ${chalk.bold(`"${kv}"`)}`);
return process.exit(1);
}
if (val_ == null) {
if (!(key in process.env)) {
error(`No value specified for env ${chalk.bold(`"${chalk.bold(key)}"`)} and it was not found in your env.`);
return process.exit(1);
} else {
console.log(`> Reading ${chalk.bold(`"${chalk.bold(key)}"`)} from your env (as no value was specified)`);
// escape value if it begins with @
val = process.env[key].replace(/^\@/, '\\@');
}
} else {
val = val_;
}
if ('@' === val[0]) {
const uidOrName = val.substr(1);
const secrets = await findSecret(uidOrName);
if (secrets.length === 0) {
if ('' === uidOrName) {
error(`Empty reference provided for env key ${chalk.bold(`"${chalk.bold(key)}"`)}`);
} else {
error(`No secret found by uid or name ${chalk.bold(`"${uidOrName}"`)}`);
}
return process.exit(1);
} else if (secrets.length > 1) {
error(`Ambiguous secret ${chalk.bold(`"${uidOrName}"`)} (matches ${chalk.bold(secrets.length)} secrets)`);
return process.exit(1);
} else {
val = { uid: secrets[0].uid };
}
}
return [
key,
// add support for escaping the @ as \@
val.replace(/^\\@/, '@')
];
}));
let env = {};
env_
.filter(v => !!v)
.forEach(([key, val]) => {
if (key in env) console.log(`> ${chalk.yellow('NOTE:')} Overriding duplicate env key ${chalk.bold(`"${key}"`)}`);
env[key] = val
});
try {
await now.create(path, {
env,
deploymentType,
forceNew,
forceSync,

12
lib/index.js

@ -37,6 +37,7 @@ export default class Now extends EventEmitter {
async create (path, {
wantsPublic,
quiet = false,
env = {},
forceNew = false,
forceSync = false,
forwardNpm = false,
@ -177,6 +178,7 @@ export default class Now extends EventEmitter {
const res = await this._fetch('/now/create', {
method: 'POST',
body: {
env,
public: wantsPublic,
forceNew,
forceSync,
@ -310,6 +312,16 @@ export default class Now extends EventEmitter {
uploadChunk();
}
async listSecrets () {
return this.retry(async (bail, attempt) => {
if (this._debug) console.time(`> [debug] #${attempt} GET /secrets`);
const res = await this._fetch('/now/secrets');
if (this._debug) console.timeEnd(`> [debug] #${attempt} GET /secrets`);
const body = await res.json();
return body.secrets;
});
}
async list (app) {
const query = app ? `?app=${encodeURIComponent(app)}` : '';

14
lib/secrets.js

@ -3,19 +3,13 @@ import Now from '../lib';
export default class Secrets extends Now {
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;
});
return this.listSecrets();
}
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' });
const res = await this._fetch(`/now/secrets/${nameOrId}`, { method: 'DELETE' });
if (this._debug) console.timeEnd(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`);
if (403 === res.status) {
@ -41,7 +35,7 @@ export default class Secrets extends Now {
add (name, value) {
return this.retry(async (bail, attempt) => {
if (this._debug) console.time(`> [debug] #${attempt} POST /secrets`);
const res = await this._fetch('/secrets', {
const res = await this._fetch('/now/secrets', {
method: 'POST',
body: {
name,
@ -73,7 +67,7 @@ export default class Secrets extends Now {
rename (nameOrId, newName) {
return this.retry(async (bail, attempt) => {
if (this._debug) console.time(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`);
const res = await this._fetch(`/secrets/${nameOrId}`, {
const res = await this._fetch(`/now/secrets/${nameOrId}`, {
method: 'PATCH',
body: {
name: newName

Loading…
Cancel
Save