Enable PULUMI_CONFIG envvars, use them

This change adds environment variable fallbacks for configuration
variables, such that you can either set them explicitly, as a specific
variable PULUMI_CONFIG_<K>, or an entire JSON serialized bag via
PULUMI_CONFIG.

This is convenient when simply invoking programs at the command line,
via node, e.g.

    PULUMI_CONFIG_AWS_CONFIG_REGION=us-west-2 node bin/index.js

Our language host also now uses this to communicate config when invoking
a Run RPC, rather than at the command line.  This fixes pulumi/pulumi#336.
This commit is contained in:
joeduffy 2017-10-11 18:41:52 -07:00
parent ce87899792
commit 65184ec6bd
3 changed files with 71 additions and 20 deletions

View file

@ -34,19 +34,7 @@ export function main(args: string[]): void {
boolean: [ "dry-run" ],
string: [ "parallel", "pwd", "monitor", "engine" ],
unknown: (arg: string) => {
// If unknown, first see if it's a --config.k=v flag.
const cix = arg.indexOf("-config");
if (cix === 0 || cix === 1) {
const kix = arg.indexOf(".");
const vix = arg.indexOf("=");
if (kix === -1 || vix === -1) {
console.error(`fatal: --config flag malformed (expected '--config.key=val')`);
usage();
process.exit(-1);
}
config[arg.substring(kix+1, vix)] = arg.substring(vix+1);
return false;
} else if (arg.indexOf("-") === 0) {
if (arg.indexOf("-") === 0) {
console.error(`fatal: Unrecognized flag ${arg}`);
usage();
process.exit(-1);
@ -57,9 +45,10 @@ export function main(args: string[]): void {
stopEarly: true,
});
// Set any configuration keys/values that were found.
for (const key of Object.keys(config)) {
runtime.setConfig(key, config[key]);
// If any config variables are present, parse and set them, so the subsequent accesses are fast.
const envObject: {[key: string]: string} = runtime.getConfigEnv();
for (const key of Object.keys(envObject)) {
runtime.setConfig(key, envObject[key]);
}
// If there is a --pwd directive, switch directories.

View file

@ -1,5 +1,11 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
/**
* configEnvKey is the environment variable key for configuration that we will check in the event that a
* configuration variable is missing. Explicit overrides take precedence.
*/
export const configEnvKey = "PULUMI_CONFIG";
let config: {[key: string]: string} = {};
/**
@ -13,6 +19,54 @@ export function setConfig(k: string, v: string): void {
* getConfig returns a configuration variable's value or undefined if it is unset.
*/
export function getConfig(k: string): string | undefined {
return config[k];
// If the config has been set explicitly, use it.
if (config.hasOwnProperty(k)) {
return config[k];
}
// If there is a specific PULUMI_CONFIG_<k> variable, use it.
const envKey: string = getConfigEnvKey(k);
if (process.env.hasOwnProperty(envKey)) {
return process.env[envKey];
}
// If the config hasn't been set, but there is a process-wide PULUMI_CONFIG envvar, use it.
const envObject: {[key: string]: string} = getConfigEnv();
if (envObject.hasOwnProperty(k)) {
return envObject[k];
}
return undefined;
}
/**
* getConfigEnvKey returns a scrubbed environment variable key, PULUMI_CONFIG_<k>, that can be used for
* setting explicit varaibles. This is unlike PULUMI_CONFIG which is just a JSON-serialized bag.
*/
export function getConfigEnvKey(key: string): string {
let envkey: string = "";
for (let c of key) {
if (c == '_' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
envkey += c;
}
else if (c >= 'a' && c <= 'z') {
envkey += c.toUpperCase();
}
else {
envkey += '_';
}
}
return `${configEnvKey}_${envkey}`;
}
/**
* getConfigEnv returns the environment map that will be used for config checking when variables aren't set.
*/
export function getConfigEnv(): {[key: string]: string} {
const envConfig = process.env.PULUMI_CONFIG;
if (envConfig) {
return JSON.parse(envConfig);
}
return {};
}

View file

@ -3,6 +3,7 @@
import * as childprocess from "child_process";
import * as os from "os";
import * as path from "path";
import * as runtime from "../runtime";
let grpc = require("grpc");
let langproto = require("../proto/languages_pb.js");
@ -52,12 +53,17 @@ function runRPC(call: any, callback: any): void {
path.join(__filename, "..", "..", "cmd", "run"),
];
// Serialize the config args using "--config.k=v" flags.
// Serialize the config args using an environment variable.
let env: {[key: string]: string} = {};
let config: any = req.getConfigMap();
if (config) {
// First flatten the config into a regular (non-RPC) object.
let configForEnv: {[key: string]: string} = {};
for (let entry of config.entries()) {
args.push(`--config.${entry[0]}=${entry[1]}`);
configForEnv[(entry[0] as string)] = (entry[1] as string);
}
// Now JSON serialize the config into an environment variable.
env[runtime.configEnvKey] = JSON.stringify(configForEnv);
}
// If this is a dry-run, tell the program so.
@ -111,7 +117,9 @@ function runRPC(call: any, callback: any): void {
// We spawn a new process to run the program. This is required because we don't want the run to complete
// until the Node message loop quiesces. It also gives us an extra level of isolation.
proc = childprocess.spawn(process.argv[0], args);
proc = childprocess.spawn(process.argv[0], args, {
env: Object.assign({}, process.env, env),
});
proc.stdout.on("data", (data: string | Buffer) => {
console.log(stripEOL(data));
});