mirror of https://github.com/pulumi/pulumi.git
539 lines
19 KiB
TypeScript
539 lines
19 KiB
TypeScript
// Copyright 2016-2018, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { RunError } from "./errors";
|
|
import { getProject as metadataGetProject } from "./metadata";
|
|
import { Output } from "./output";
|
|
import { allConfig, getConfig as runtimeGetConfig } from "./runtime/config";
|
|
|
|
function makeSecret<T>(value: T): Output<T> {
|
|
const output = require("./output");
|
|
return new output.Output(
|
|
[],
|
|
Promise.resolve(value),
|
|
/*isKnown:*/ Promise.resolve(true),
|
|
/*isSecret:*/ Promise.resolve(true),
|
|
Promise.resolve([]),
|
|
);
|
|
}
|
|
|
|
function getProject(): string {
|
|
return metadataGetProject();
|
|
}
|
|
|
|
// This is used to capture and serialize the results of
|
|
// getProject for use in non-pulumi engine contexts
|
|
(<any>getProject).captureReplacement = () => {
|
|
const project = metadataGetProject();
|
|
const funcToSerialize = () => project;
|
|
return funcToSerialize;
|
|
};
|
|
|
|
function getConfig(k: string): string | undefined {
|
|
return runtimeGetConfig(k);
|
|
}
|
|
|
|
// This is used to capture and serialize the results of
|
|
// getConfig for use in non-pulumi engine contexts
|
|
(<any>getConfig).captureReplacement = () => {
|
|
const config = allConfig();
|
|
|
|
const funcToSerialize = (k: string) => config[k];
|
|
return funcToSerialize;
|
|
};
|
|
|
|
/**
|
|
* Config is a bag of related configuration state. Each bag contains any number of configuration variables, indexed by
|
|
* simple keys, and each has a name that uniquely identifies it; two bags with different names do not share values for
|
|
* variables that otherwise share the same key. For example, a bag whose name is `pulumi:foo`, with keys `a`, `b`,
|
|
* and `c`, is entirely separate from a bag whose name is `pulumi:bar` with the same simple key names. Each key has a
|
|
* fully qualified names, such as `pulumi:foo:a`, ..., and `pulumi:bar:a`, respectively.
|
|
*/
|
|
export class Config {
|
|
/**
|
|
* name is the configuration bag's logical name and uniquely identifies it. The default is the name of the current
|
|
* project.
|
|
*/
|
|
public readonly name: string;
|
|
|
|
constructor(name?: string) {
|
|
if (name === undefined) {
|
|
name = getProject();
|
|
}
|
|
|
|
if (name.endsWith(":config")) {
|
|
name = name.replace(/:config$/, "");
|
|
}
|
|
|
|
this.name = name;
|
|
}
|
|
|
|
/**
|
|
* get loads an optional configuration value by its key, or undefined if it doesn't exist.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
private getImpl<K extends string = string>(
|
|
key: string,
|
|
opts?: StringConfigOptions<K>,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): K | undefined {
|
|
const fullKey = this.fullKey(key);
|
|
const v: string | undefined = getConfig(fullKey);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
// TODO[pulumi/pulumi#7127]: Re-enable the warning.
|
|
// Temporarily disabling the new warning.
|
|
// if (use && insteadOf && isConfigSecret(fullKey)) {
|
|
// log.warn(`Configuration '${fullKey}' value is a secret; ` +
|
|
// `use \`${use.name}\` instead of \`${insteadOf.name}\``);
|
|
// }
|
|
if (opts) {
|
|
// SAFETY: if allowedValues != null, verifying v ∈ K[]
|
|
if (opts.allowedValues !== undefined && opts.allowedValues.indexOf(v as any) === -1) {
|
|
throw new ConfigEnumError(fullKey, v, opts.allowedValues);
|
|
} else if (opts.minLength !== undefined && v.length < opts.minLength) {
|
|
throw new ConfigRangeError(fullKey, v, opts.minLength, undefined);
|
|
} else if (opts.maxLength !== undefined && v.length > opts.maxLength) {
|
|
throw new ConfigRangeError(fullKey, v, undefined, opts.maxLength);
|
|
} else if (opts.pattern !== undefined) {
|
|
let pattern = opts.pattern;
|
|
if (typeof pattern === "string") {
|
|
pattern = new RegExp(pattern);
|
|
}
|
|
if (!pattern.test(v)) {
|
|
throw new ConfigPatternError(fullKey, v, pattern);
|
|
}
|
|
}
|
|
}
|
|
// SAFETY:
|
|
// allowedValues != null ⇒ v ∈ K[]
|
|
// allowedValues == null ⇒ K = string & v : string
|
|
return v as K;
|
|
}
|
|
|
|
/**
|
|
* get loads an optional configuration value by its key, or undefined if it doesn't exist.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public get<K extends string = string>(key: string, opts?: StringConfigOptions<K>): K | undefined {
|
|
return this.getImpl(key, opts, this.getSecret, this.get);
|
|
}
|
|
|
|
/**
|
|
* getSecret loads an optional configuration value by its key, marking it as a secret, or undefined if it
|
|
* doesn't exist.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public getSecret<K extends string = string>(key: string, opts?: StringConfigOptions<K>): Output<K> | undefined {
|
|
const v = this.getImpl(key, opts);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return makeSecret(v);
|
|
}
|
|
|
|
private getBooleanImpl(
|
|
key: string,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): boolean | undefined {
|
|
const v: string | undefined = this.getImpl(key, undefined, use, insteadOf);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
} else if (v === "true") {
|
|
return true;
|
|
} else if (v === "false") {
|
|
return false;
|
|
}
|
|
throw new ConfigTypeError(this.fullKey(key), v, "boolean");
|
|
}
|
|
|
|
/**
|
|
* getBoolean loads an optional configuration value, as a boolean, by its key, or undefined if it doesn't exist.
|
|
* If the configuration value isn't a legal boolean, this function will throw an error.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public getBoolean(key: string): boolean | undefined {
|
|
return this.getBooleanImpl(key, this.getSecretBoolean, this.getBoolean);
|
|
}
|
|
|
|
/**
|
|
* getSecretBoolean loads an optional configuration value, as a boolean, by its key, making it as a secret
|
|
* or undefined if it doesn't exist. If the configuration value isn't a legal boolean, this function will
|
|
* throw an error.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public getSecretBoolean(key: string): Output<boolean> | undefined {
|
|
const v = this.getBooleanImpl(key);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return makeSecret(v);
|
|
}
|
|
|
|
private getNumberImpl(
|
|
key: string,
|
|
opts?: NumberConfigOptions,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): number | undefined {
|
|
const v: string | undefined = this.getImpl(key, undefined, use, insteadOf);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
const f: number = parseFloat(v);
|
|
if (isNaN(f)) {
|
|
throw new ConfigTypeError(this.fullKey(key), v, "number");
|
|
}
|
|
if (opts) {
|
|
if (opts.min !== undefined && f < opts.min) {
|
|
throw new ConfigRangeError(this.fullKey(key), f, opts.min, undefined);
|
|
} else if (opts.max !== undefined && f > opts.max) {
|
|
throw new ConfigRangeError(this.fullKey(key), f, undefined, opts.max);
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
/**
|
|
* getNumber loads an optional configuration value, as a number, by its key, or undefined if it doesn't exist.
|
|
* If the configuration value isn't a legal number, this function will throw an error.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public getNumber(key: string, opts?: NumberConfigOptions): number | undefined {
|
|
return this.getNumberImpl(key, opts, this.getSecretNumber, this.getNumber);
|
|
}
|
|
|
|
/**
|
|
* getSecretNumber loads an optional configuration value, as a number, by its key, marking it as a secret
|
|
* or undefined if it doesn't exist.
|
|
* If the configuration value isn't a legal number, this function will throw an error.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public getSecretNumber(key: string, opts?: NumberConfigOptions): Output<number> | undefined {
|
|
const v = this.getNumberImpl(key, opts);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return makeSecret(v);
|
|
}
|
|
|
|
private getObjectImpl<T>(
|
|
key: string,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): T | undefined {
|
|
const v: string | undefined = this.getImpl(key, undefined, use, insteadOf);
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
return <T>JSON.parse(v);
|
|
} catch (err) {
|
|
throw new ConfigTypeError(this.fullKey(key), v, "JSON object");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* getObject loads an optional configuration value, as an object, by its key, or undefined if it doesn't exist.
|
|
* This routine simply JSON parses and doesn't validate the shape of the contents.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public getObject<T>(key: string): T | undefined {
|
|
return this.getObjectImpl<T>(key, this.getSecretObject, this.getObject);
|
|
}
|
|
|
|
/**
|
|
* getSecretObject loads an optional configuration value, as an object, by its key, marking it as a secret
|
|
* or undefined if it doesn't exist.
|
|
* This routine simply JSON parses and doesn't validate the shape of the contents.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public getSecretObject<T>(key: string): Output<T> | undefined {
|
|
const v = this.getObjectImpl<T>(key);
|
|
|
|
if (v === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
return makeSecret<T>(v);
|
|
}
|
|
|
|
private requireImpl<K extends string = string>(
|
|
key: string,
|
|
secret: boolean,
|
|
opts?: StringConfigOptions<K>,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): K {
|
|
const v: K | undefined = this.getImpl(key, opts, use, insteadOf);
|
|
if (v === undefined) {
|
|
throw new ConfigMissingError(this.fullKey(key), secret);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* require loads a configuration value by its given key. If it doesn't exist, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public require<K extends string = string>(key: string, opts?: StringConfigOptions<K>): K {
|
|
return this.requireImpl(key, false, opts, this.requireSecret, this.require);
|
|
}
|
|
|
|
/**
|
|
* require loads a configuration value by its given key, marking it as a secret. If it doesn't exist, an error
|
|
* is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public requireSecret<K extends string = string>(key: string, opts?: StringConfigOptions<K>): Output<K> {
|
|
return makeSecret(this.requireImpl(key, true, opts));
|
|
}
|
|
|
|
private requireBooleanImpl(
|
|
key: string,
|
|
secret: boolean,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): boolean {
|
|
const v: boolean | undefined = this.getBooleanImpl(key, use, insteadOf);
|
|
if (v === undefined) {
|
|
throw new ConfigMissingError(this.fullKey(key), secret);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* requireBoolean loads a configuration value, as a boolean, by its given key. If it doesn't exist, or the
|
|
* configuration value is not a legal boolean, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public requireBoolean(key: string): boolean {
|
|
return this.requireBooleanImpl(key, false, this.requireSecretBoolean, this.requireBoolean);
|
|
}
|
|
|
|
/**
|
|
* requireSecretBoolean loads a configuration value, as a boolean, by its given key, marking it as a secret.
|
|
* If it doesn't exist, or the configuration value is not a legal boolean, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public requireSecretBoolean(key: string): Output<boolean> {
|
|
return makeSecret(this.requireBooleanImpl(key, true));
|
|
}
|
|
|
|
private requireNumberImpl(
|
|
key: string,
|
|
secret: boolean,
|
|
opts?: NumberConfigOptions,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): number {
|
|
const v: number | undefined = this.getNumberImpl(key, opts, use, insteadOf);
|
|
if (v === undefined) {
|
|
throw new ConfigMissingError(this.fullKey(key), secret);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* requireNumber loads a configuration value, as a number, by its given key. If it doesn't exist, or the
|
|
* configuration value is not a legal number, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public requireNumber(key: string, opts?: NumberConfigOptions): number {
|
|
return this.requireNumberImpl(key, false, opts, this.requireSecretNumber, this.requireNumber);
|
|
}
|
|
|
|
/**
|
|
* requireSecretNumber loads a configuration value, as a number, by its given key, marking it as a secret.
|
|
* If it doesn't exist, or the configuration value is not a legal number, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
* @param opts An options bag to constrain legal values.
|
|
*/
|
|
public requireSecretNumber(key: string, opts?: NumberConfigOptions): Output<number> {
|
|
return makeSecret(this.requireNumberImpl(key, true, opts));
|
|
}
|
|
|
|
private requireObjectImpl<T>(
|
|
key: string,
|
|
secret: boolean,
|
|
use?: (...args: any[]) => any,
|
|
insteadOf?: (...args: any[]) => any,
|
|
): T {
|
|
const v: T | undefined = this.getObjectImpl<T>(key, use, insteadOf);
|
|
if (v === undefined) {
|
|
throw new ConfigMissingError(this.fullKey(key), secret);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* requireObject loads a configuration value as a JSON string and deserializes the JSON into a JavaScript object. If
|
|
* it doesn't exist, or the configuration value is not a legal JSON string, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public requireObject<T>(key: string): T {
|
|
return this.requireObjectImpl<T>(key, false, this.requireSecretObject, this.requireObject);
|
|
}
|
|
|
|
/**
|
|
* requireSecretObject loads a configuration value as a JSON string and deserializes the JSON into a JavaScript
|
|
* object, marking it as a secret. If it doesn't exist, or the configuration value is not a legal JSON
|
|
* string, an error is thrown.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
public requireSecretObject<T>(key: string): Output<T> {
|
|
return makeSecret(this.requireObjectImpl<T>(key, true));
|
|
}
|
|
|
|
/**
|
|
* fullKey turns a simple configuration key into a fully resolved one, by prepending the bag's name.
|
|
*
|
|
* @param key The key to lookup.
|
|
*/
|
|
private fullKey(key: string): string {
|
|
return `${this.name}:${key}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* StringConfigOptions may be used to constrain the set of legal values a string config value may contain.
|
|
*/
|
|
export interface StringConfigOptions<K extends string = string> {
|
|
/**
|
|
* The legal enum values. If it does not match, a ConfigEnumError is thrown.
|
|
*/
|
|
allowedValues?: K[];
|
|
/**
|
|
* The minimum string length. If the string is not this long, a ConfigRangeError is thrown.
|
|
*/
|
|
minLength?: number;
|
|
/**
|
|
* The maximum string length. If the string is longer than this, a ConfigRangeError is thrown.
|
|
*/
|
|
maxLength?: number;
|
|
/**
|
|
* A regular expression the string must match. If it does not match, a ConfigPatternError is thrown.
|
|
*/
|
|
pattern?: string | RegExp;
|
|
}
|
|
|
|
/**
|
|
* NumberConfigOptions may be used to constrain the set of legal values a number config value may contain.
|
|
*/
|
|
export interface NumberConfigOptions {
|
|
/**
|
|
* The minimum number value, inclusive. If the number is less than this, a ConfigRangeError is thrown.
|
|
*/
|
|
min?: number;
|
|
/**
|
|
* The maximum number value, inclusive. If the number is greater than this, a ConfigRangeError is thrown.
|
|
*/
|
|
max?: number;
|
|
}
|
|
|
|
/**
|
|
* ConfigTypeError is used when a configuration value is of the wrong type.
|
|
*/
|
|
class ConfigTypeError extends RunError {
|
|
constructor(key: string, v: any, expectedType: string) {
|
|
super(`Configuration '${key}' value '${v}' is not a valid ${expectedType}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ConfigEnumError is used when a configuration value isn't a correct enum value.
|
|
*/
|
|
class ConfigEnumError extends RunError {
|
|
constructor(key: string, v: any, values: any[]) {
|
|
super(`Configuration '${key}' value '${v}' is not a legal enum value (${JSON.stringify(values)})`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ConfigRangeError is used when a configuration value is outside of the range of legal sizes.
|
|
*/
|
|
class ConfigRangeError extends RunError {
|
|
constructor(key: string, v: any, min: number | undefined, max: number | undefined) {
|
|
let range: string;
|
|
if (max === undefined) {
|
|
range = `min ${min}`;
|
|
} else if (min === undefined) {
|
|
range = `max ${max}`;
|
|
} else {
|
|
range = `${min}-${max}`;
|
|
}
|
|
if (typeof v === "string") {
|
|
range += " chars";
|
|
}
|
|
super(`Configuration '${key}' value '${v}' is outside of the legal range (${range}, inclusive)`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ConfigPatternError is used when a configuration value does not match the given regular expression.
|
|
*/
|
|
class ConfigPatternError extends RunError {
|
|
constructor(key: string, v: string, regexp: RegExp) {
|
|
super(`Configuration '${key}' value '${v}' does not match the regular expression ${regexp.toString()}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ConfigMissingError is used when a configuration value is completely missing.
|
|
*/
|
|
class ConfigMissingError extends RunError {
|
|
constructor(
|
|
public key: string,
|
|
public secret: boolean,
|
|
) {
|
|
super(
|
|
`Missing required configuration variable '${key}'\n` +
|
|
`\tplease set a value using the command \`pulumi config set${
|
|
secret ? " --secret " : " "
|
|
}${key} <value>\``,
|
|
);
|
|
}
|
|
}
|