For providers which do not natively support secrets (which is all of them today), we annotate output values coming back from the provider if there is a coresponding secret input in the inputs we passed in. This logic was not tearing into rich objects, so if you passed a secret as a member of an array or object into a resource provider, we would lose the secretness on the way back. Because of the interaction with Check (where we call Check and then take the values returned by the provider as inputs for all calls to Diff/Update), this would apply not only to the Output values of a resource but also the Inputs (because the secret metadata would not flow from the inputs of check to the outputs). This change augments our logic which transfers secrets metadata from one property map to another to handle these additional cases.
48 lines
No EOL
2.5 KiB
TypeScript
48 lines
No EOL
2.5 KiB
TypeScript
import * as pulumi from "@pulumi/pulumi";
|
|
|
|
import { ReflectResource, DummyResource } from "./provider";
|
|
|
|
const c = new pulumi.Config();
|
|
|
|
// ApiKey is an Output<string> and marked as a secret. If it is used as an input for any resources, the value will
|
|
// be encrypted.
|
|
const apiKey = c.requireSecret("apiKey");
|
|
|
|
// A plaintext message. We could turn this into a secret after the fact by passing it to `pulumi.secret` if we wished.
|
|
const message = c.require("message");
|
|
|
|
// Secrets are viral. When you combine secrets with `pulumi.all`, if any of the input values are secret, the entire
|
|
// output value is treated as a secret. Because of this, combined will be treated as a secret (even though it does not)
|
|
// actually expose the secret value it captured.
|
|
const combined = pulumi.all([apiKey, message]).apply(([s, p]) => {
|
|
return p;
|
|
})
|
|
|
|
// Since these inputs are either directly secrets, or become secrets via an `apply` of a secret, we expect that in
|
|
// the state file, they will be encrypted.
|
|
export const secretMessage = new ReflectResource("sValue", apiKey).value;
|
|
export const secretApply = new ReflectResource("sApply", apiKey.apply(x => x.length)).value;
|
|
|
|
// These are paintext values, so they will be stored as is in the state file.
|
|
export const plaintextMessage = new ReflectResource("pValue", message).value;
|
|
export const plaintextApply = new ReflectResource("pApply", message.length).value;
|
|
|
|
// These are secrets, as well, based on the composition above. We expect that these will also be stored as secrets
|
|
// in the state file.
|
|
export const combinedMessage = new ReflectResource("cValue", combined).value;
|
|
export const combinedApply = new ReflectResource("cApply", combined.apply(x => x.length)).value;
|
|
|
|
// With a rich structure like this, we expect that the actual reasource properties in the state file will be stored
|
|
// as a mixture of plaintext and secrets, but the outputed stack property will be a secret (because part of the value
|
|
// property contains a secret, and that means the entire Output object must be marked as a secret.
|
|
export const richStructure = new ReflectResource("rValue", {
|
|
plain: pulumi.output("plaintext"),
|
|
secret: pulumi.secret("secret value"),
|
|
}).value;
|
|
|
|
// The dummy resource just provides a single output named "value" with a simple message. But we can use
|
|
// `additionalSecretOutputs` as a way to enforce that it is treated as a secret.
|
|
export const dummyValue = new DummyResource("pDummy").value;
|
|
export const dummyValueAdditionalSecrets = new DummyResource("sDummy", {
|
|
additionalSecretOutputs: ["value"],
|
|
}).value; |