2018-11-14 22:33:35 +01:00
|
|
|
// 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.
|
|
|
|
|
2019-02-01 03:08:17 +01:00
|
|
|
import { all, Input, Output, output } from "./output";
|
|
|
|
import { CustomResource, CustomResourceOptions } from "./resource";
|
2019-11-19 21:51:14 +01:00
|
|
|
import * as invoke from "./runtime/invoke";
|
2019-06-17 21:25:56 +02:00
|
|
|
import { promiseResult } from "./utils";
|
2018-11-14 22:33:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Manages a reference to a Pulumi stack. The referenced stack's outputs are available via the
|
|
|
|
* `outputs` property or the `output` method.
|
|
|
|
*/
|
|
|
|
export class StackReference extends CustomResource {
|
|
|
|
/**
|
|
|
|
* The name of the referenced stack.
|
|
|
|
*/
|
2019-09-12 01:21:35 +02:00
|
|
|
public readonly name!: Output<string>;
|
2018-11-14 22:33:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The outputs of the referenced stack.
|
|
|
|
*/
|
2019-09-12 01:21:35 +02:00
|
|
|
public readonly outputs!: Output<{[name: string]: any}>;
|
2018-11-14 22:33:35 +01:00
|
|
|
|
Do not taint all stack outputs as secrets if just one is
When using StackReference, if the stack you reference contains any
secret outputs, we have to mark the entire `outputs` member as a
secret output. This is because we only track secretness on a per
`Output<T>` basis.
For `getSecret` and friends, however, we know the name of the output
you are looking up and we can be smarter about if the returned
`Output<T>` should be treated as a secret or not.
This change augments the provider for StackReference such that it also
returns a list of top level stack output names who's values contain
secrets. In the language SDKs, we use this information, when present,
to decide if we should return an `Output<T>` that is marked as a
secret or not. Since the SDK and CLI are independent components, care
is taken to ensure that when the CLI does not return this information,
we behave as we did before (i.e. if any output is a secret, we treat
every output as a secret).
Fixes #2744
2019-08-13 02:02:30 +02:00
|
|
|
/**
|
|
|
|
* The names of any stack outputs which contain secrets.
|
|
|
|
*/
|
2019-09-12 01:21:35 +02:00
|
|
|
public readonly secretOutputNames!: Output<string[]>;
|
Do not taint all stack outputs as secrets if just one is
When using StackReference, if the stack you reference contains any
secret outputs, we have to mark the entire `outputs` member as a
secret output. This is because we only track secretness on a per
`Output<T>` basis.
For `getSecret` and friends, however, we know the name of the output
you are looking up and we can be smarter about if the returned
`Output<T>` should be treated as a secret or not.
This change augments the provider for StackReference such that it also
returns a list of top level stack output names who's values contain
secrets. In the language SDKs, we use this information, when present,
to decide if we should return an `Output<T>` that is marked as a
secret or not. Since the SDK and CLI are independent components, care
is taken to ensure that when the CLI does not return this information,
we behave as we did before (i.e. if any output is a secret, we treat
every output as a secret).
Fixes #2744
2019-08-13 02:02:30 +02:00
|
|
|
|
2019-11-19 21:51:14 +01:00
|
|
|
// Values we stash to support the getOutputSync and requireOutputSync calls without
|
|
|
|
// having to go through the async values above.
|
|
|
|
|
|
|
|
private readonly stackReferenceName: Input<string>;
|
|
|
|
private syncOutputsSupported: boolean | undefined;
|
|
|
|
private syncName: string | undefined;
|
|
|
|
private syncOutputs: Record<string, any> | undefined;
|
|
|
|
private syncSecretOutputNames: string[] | undefined;
|
|
|
|
|
2018-11-14 22:33:35 +01:00
|
|
|
/**
|
|
|
|
* Create a StackReference resource with the given unique name, arguments, and options.
|
|
|
|
*
|
|
|
|
* If args is not specified, the name of the referenced stack will be the name of the StackReference resource.
|
|
|
|
*
|
|
|
|
* @param name The _unique_ name of the stack reference.
|
|
|
|
* @param args The arguments to use to populate this resource's properties.
|
|
|
|
* @Param opts A bag of options that control this resource's behavior.
|
|
|
|
*/
|
|
|
|
constructor(name: string, args?: StackReferenceArgs, opts?: CustomResourceOptions) {
|
|
|
|
args = args || {};
|
|
|
|
|
2019-11-19 21:51:14 +01:00
|
|
|
const stackReferenceName = args.name || name;
|
|
|
|
|
2018-11-14 22:33:35 +01:00
|
|
|
super("pulumi:pulumi:StackReference", name, {
|
2019-11-19 21:51:14 +01:00
|
|
|
name: stackReferenceName,
|
2018-11-14 22:33:35 +01:00
|
|
|
outputs: undefined,
|
2019-08-14 21:12:23 +02:00
|
|
|
secretOutputNames: undefined,
|
2019-11-19 21:51:14 +01:00
|
|
|
}, { ...opts, id: stackReferenceName });
|
|
|
|
|
|
|
|
this.stackReferenceName = stackReferenceName;
|
2018-11-14 22:33:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-08-01 20:27:32 +02:00
|
|
|
* Fetches the value of the named stack output, or undefined if the stack output was not found.
|
2018-11-14 22:33:35 +01:00
|
|
|
*
|
|
|
|
* @param name The name of the stack output to fetch.
|
|
|
|
*/
|
|
|
|
public getOutput(name: Input<string>): Output<any> {
|
2019-11-19 21:51:14 +01:00
|
|
|
// Note that this is subtly different from "apply" here. A default "apply" will set the secret bit if any
|
2019-08-14 21:12:23 +02:00
|
|
|
// of the inputs are a secret, and this.outputs is always a secret if it contains any secrets. We do this dance
|
|
|
|
// so we can ensure that the Output we return is not needlessly tainted as a secret.
|
Do not taint all stack outputs as secrets if just one is
When using StackReference, if the stack you reference contains any
secret outputs, we have to mark the entire `outputs` member as a
secret output. This is because we only track secretness on a per
`Output<T>` basis.
For `getSecret` and friends, however, we know the name of the output
you are looking up and we can be smarter about if the returned
`Output<T>` should be treated as a secret or not.
This change augments the provider for StackReference such that it also
returns a list of top level stack output names who's values contain
secrets. In the language SDKs, we use this information, when present,
to decide if we should return an `Output<T>` that is marked as a
secret or not. Since the SDK and CLI are independent components, care
is taken to ensure that when the CLI does not return this information,
we behave as we did before (i.e. if any output is a secret, we treat
every output as a secret).
Fixes #2744
2019-08-13 02:02:30 +02:00
|
|
|
const value = all([output(name), this.outputs]).apply(([n, os]) => os[n]);
|
2019-08-14 21:12:23 +02:00
|
|
|
return new Output(value.resources(), value.promise(), value.isKnown, isSecretOutputName(this, output(name)));
|
2018-11-14 22:33:35 +01:00
|
|
|
}
|
2019-06-17 21:25:56 +02:00
|
|
|
|
2019-08-01 20:27:32 +02:00
|
|
|
/**
|
|
|
|
* Fetches the value of the named stack output, or throws an error if the output was not found.
|
|
|
|
*
|
|
|
|
* @param name The name of the stack output to fetch.
|
|
|
|
*/
|
|
|
|
public requireOutput(name: Input<string>): Output<any> {
|
Do not taint all stack outputs as secrets if just one is
When using StackReference, if the stack you reference contains any
secret outputs, we have to mark the entire `outputs` member as a
secret output. This is because we only track secretness on a per
`Output<T>` basis.
For `getSecret` and friends, however, we know the name of the output
you are looking up and we can be smarter about if the returned
`Output<T>` should be treated as a secret or not.
This change augments the provider for StackReference such that it also
returns a list of top level stack output names who's values contain
secrets. In the language SDKs, we use this information, when present,
to decide if we should return an `Output<T>` that is marked as a
secret or not. Since the SDK and CLI are independent components, care
is taken to ensure that when the CLI does not return this information,
we behave as we did before (i.e. if any output is a secret, we treat
every output as a secret).
Fixes #2744
2019-08-13 02:02:30 +02:00
|
|
|
const value = all([output(this.name), output(name), this.outputs]).apply(([stackname, n, os]) => {
|
2019-08-01 20:27:32 +02:00
|
|
|
if (!os.hasOwnProperty(n)) {
|
|
|
|
throw new Error(`Required output '${n}' does not exist on stack '${stackname}'.`);
|
|
|
|
}
|
|
|
|
return os[n];
|
|
|
|
});
|
2019-08-14 21:12:23 +02:00
|
|
|
return new Output(value.resources(), value.promise(), value.isKnown, isSecretOutputName(this, output(name)));
|
2019-08-01 20:27:32 +02:00
|
|
|
}
|
|
|
|
|
2019-06-17 21:25:56 +02:00
|
|
|
/**
|
2019-11-19 21:51:14 +01:00
|
|
|
* Fetches the value promptly of the named stack output. May return undefined if the value is
|
2019-06-17 21:25:56 +02:00
|
|
|
* not known for some reason.
|
|
|
|
*
|
2019-11-19 21:51:14 +01:00
|
|
|
* This operation is not supported (and will throw) if the named stack output is a secret.
|
2019-06-17 21:25:56 +02:00
|
|
|
*
|
|
|
|
* @param name The name of the stack output to fetch.
|
|
|
|
*/
|
|
|
|
public getOutputSync(name: string): any {
|
2019-11-19 21:51:14 +01:00
|
|
|
const [out, isSecret] = this.readOutputSync("getOutputSync", name, false /*required*/);
|
2019-06-17 21:25:56 +02:00
|
|
|
if (isSecret) {
|
2019-11-19 21:51:14 +01:00
|
|
|
throw new Error("Cannot call 'getOutputSync' if the referenced stack output is a secret. Use 'getOutput' instead.");
|
2019-06-17 21:25:56 +02:00
|
|
|
}
|
2019-11-19 21:51:14 +01:00
|
|
|
return out;
|
2019-06-17 21:25:56 +02:00
|
|
|
}
|
2019-08-01 20:27:32 +02:00
|
|
|
|
|
|
|
/**
|
2019-11-19 21:51:14 +01:00
|
|
|
* Fetches the value promptly of the named stack output. Throws an error if the stack output is
|
2019-08-01 20:27:32 +02:00
|
|
|
* not found.
|
|
|
|
*
|
2019-11-19 21:51:14 +01:00
|
|
|
* This operation is not supported (and will throw) if the named stack output is a secret.
|
2019-08-01 20:27:32 +02:00
|
|
|
*
|
|
|
|
* @param name The name of the stack output to fetch.
|
|
|
|
*/
|
|
|
|
public requireOutputSync(name: string): any {
|
2019-11-19 21:51:14 +01:00
|
|
|
const [out, isSecret] = this.readOutputSync("requireOutputSync", name, true /*required*/);
|
2019-08-01 20:27:32 +02:00
|
|
|
if (isSecret) {
|
2019-11-19 21:51:14 +01:00
|
|
|
throw new Error("Cannot call 'requireOutputSync' if the referenced stack output is a secret. Use 'requireOutput' instead.");
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
private readOutputSync(callerName: string, outputName: string, required: boolean): [any, boolean] {
|
|
|
|
const [stackName, outputs, secretNames, supported] = this.readOutputsSync("requireOutputSync");
|
|
|
|
|
|
|
|
// If the synchronous readStackOutputs call is supported by the engine, use its results.
|
|
|
|
if (supported) {
|
|
|
|
if (required && !outputs.hasOwnProperty(outputName)) {
|
|
|
|
throw new Error(`Required output '${outputName}' does not exist on stack '${stackName}'.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [outputs[outputName], secretNames.includes(outputName)];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, fall back to promiseResult.
|
|
|
|
console.log(`StackReference.${callerName} may cause your program to hang. Please update to the latest version of the Pulumi CLI.
|
|
|
|
For more details see: https://www.pulumi.com/docs/troubleshooting/#stackreference-sync`);
|
|
|
|
|
|
|
|
const out = required ? this.requireOutput(outputName) : this.getOutput(outputName);
|
|
|
|
return [promiseResult(out.promise()), promiseResult(out.isSecret)];
|
|
|
|
}
|
|
|
|
|
|
|
|
private readOutputsSync(callerName: string): [string, Record<string, any>, string[], boolean] {
|
|
|
|
// See if we already attempted to read in the outputs synchronously. If so, just use those values.
|
|
|
|
if (this.syncOutputs) {
|
|
|
|
return [this.syncName!, this.syncOutputs, this.syncSecretOutputNames!, this.syncOutputsSupported!];
|
2019-08-01 20:27:32 +02:00
|
|
|
}
|
|
|
|
|
2019-11-19 21:51:14 +01:00
|
|
|
// We need to pass along our StackReference name to the engine so it knows what results to
|
|
|
|
// return. However, because we're doing this synchronously, we can only do this safely if
|
|
|
|
// the stack-reference name is synchronously known (i.e. it's a string and not a
|
|
|
|
// Promise/Output). If it is only asynchronously known, then warn the user and make an unsafe
|
|
|
|
// call to the deasync lib to get the name.
|
|
|
|
let stackName: string;
|
|
|
|
if (this.stackReferenceName instanceof Promise) {
|
|
|
|
// Have to do an explicit console.log here as the call to utils.promiseResult may hang
|
|
|
|
// node, and that may prevent our normal logging calls from making it back to the user.
|
|
|
|
console.log(
|
|
|
|
`Call made to StackReference.${callerName} with a StackReference with a Promise name. This is now deprecated and may cause the program to hang.
|
|
|
|
For more details see: https://www.pulumi.com/docs/troubleshooting/#stackreference-sync`);
|
|
|
|
|
|
|
|
stackName = promiseResult(this.stackReferenceName);
|
|
|
|
}
|
|
|
|
else if (Output.isInstance(this.stackReferenceName)) {
|
|
|
|
console.log(
|
|
|
|
`Call made to StackReference.${callerName} with a StackReference with an Output name. This is now deprecated and may cause the program to hang.
|
|
|
|
For more details see: https://www.pulumi.com/docs/troubleshooting/#stackreference-sync`);
|
|
|
|
|
|
|
|
stackName = promiseResult(this.stackReferenceName.promise());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
stackName = this.stackReferenceName;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const res = invoke.invokeSync<ReadStackOutputsResult>(
|
|
|
|
"pulumi:pulumi:readStackOutputs", { name: stackName });
|
|
|
|
this.syncName = stackName;
|
|
|
|
this.syncOutputs = res.outputs;
|
|
|
|
this.syncSecretOutputNames = res.secretOutputNames;
|
|
|
|
this.syncOutputsSupported = true;
|
|
|
|
} catch {
|
|
|
|
this.syncOutputs = {};
|
|
|
|
this.syncOutputsSupported = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [this.syncName!, this.syncOutputs, this.syncSecretOutputNames!, this.syncOutputsSupported];
|
2019-08-01 20:27:32 +02:00
|
|
|
}
|
2018-11-14 22:33:35 +01:00
|
|
|
}
|
|
|
|
|
2019-11-19 21:51:14 +01:00
|
|
|
// Shape of the result that the engine returns to us when we invoke 'pulumi:pulumi:readStackOutputs'
|
|
|
|
interface ReadStackOutputsResult {
|
|
|
|
name: string;
|
|
|
|
outputs: Record<string, any>;
|
|
|
|
secretOutputNames: string[];
|
|
|
|
}
|
|
|
|
|
2018-11-14 22:33:35 +01:00
|
|
|
/**
|
|
|
|
* The set of arguments for constructing a StackReference resource.
|
|
|
|
*/
|
|
|
|
export interface StackReferenceArgs {
|
|
|
|
/**
|
|
|
|
* The name of the stack to reference.
|
|
|
|
*/
|
|
|
|
readonly name?: Input<string>;
|
|
|
|
}
|
2019-08-14 21:12:23 +02:00
|
|
|
|
|
|
|
async function isSecretOutputName(sr: StackReference, name: Input<string>): Promise<boolean> {
|
|
|
|
const nameOutput = output(name);
|
|
|
|
|
|
|
|
// If either the name or set of secret outputs is unknown, we can't do anything smart, so we just copy the
|
|
|
|
// secretness from the entire outputs value.
|
|
|
|
if (!((await nameOutput.isKnown) && (await sr.secretOutputNames.isKnown))) {
|
2019-08-22 20:04:29 +02:00
|
|
|
return await sr.outputs.isSecret;
|
2019-08-14 21:12:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, if we have a list of outputs we know are secret, we can use that list to determine if this
|
|
|
|
// output should be secret. Names could be falsy here in cases where we are using an older CLI that did
|
|
|
|
// not return this information (in this case we again fallback to the secretness of outputs value).
|
|
|
|
const names = await sr.secretOutputNames.promise();
|
|
|
|
if (!names) {
|
|
|
|
return await sr.outputs.isSecret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return names.includes(await nameOutput.promise());
|
|
|
|
}
|