Adds the ability to provide `transformations` to modify the properties and resource options that will be used for any child resource of a component or stack. This offers an "escape hatch" to modify the behaviour of a component by peeking behind it's abstraction. For example, it can be used to add a resource option (`additionalSecretOutputs`, `aliases`, `protect`, etc.) to a specific known child of a component, or to modify some input property to a child resource if the component does not (yet) expose the ability to control that input directly. It could also be used for more interesting scenarios - such as: 1. Automatically applying tags to all resources that support them in a stack (or component) 2. Injecting real dependencies between stringly-referenced resources in a Helm Chart 3. Injecting explicit names using a preferred naming convention across all resources in a stack 4. Injecting `import` onto all resources by doing a lookup into a name=>id mapping Because this feature makes it possible to peek behind a component abstraction, it must be used with care in cases where the component is versioned independently of the use of transformations. Also, this can result in "spooky action at a distance", so should be used judiciously. That said - this can be used as an escape hatch to unblock a wide variety of common use cases without waiting on changes to be made in a component implementation. Each transformation is passed the `resource`, `name`, `type`, `props` and `opts` that are passed into the `Resource` constructor for any resource descended from the resource that has the transformation applied. The transformation callback can optionally return alternate versions of the `props` and `opts` to be used in place of the original values provided to the resource constructor. Fixes #2068.
227 lines
12 KiB
TypeScript
227 lines
12 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.
|
|
|
|
// tslint:disable
|
|
|
|
import * as assert from "assert";
|
|
import { ComponentResourceOptions, ProviderResource, merge, mergeOptions } from "../resource";
|
|
import { asyncTest } from "./util";
|
|
|
|
describe("options", () => {
|
|
describe("merge", () => {
|
|
describe("scaler", () => {
|
|
it("keeps value from opts1 if not provided in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ id: "a" }, {});
|
|
assert.strictEqual(result.id, "a");
|
|
}));
|
|
it("keeps value from opts2 if not provided in opts1", asyncTest(async () => {
|
|
const result = mergeOptions({ }, { id: "a" });
|
|
assert.strictEqual(result.id, "a");
|
|
}));
|
|
it("overwrites value from opts1 if given null in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ id: "a" }, { id: null! });
|
|
assert.strictEqual(result.id, null);
|
|
}));
|
|
it("overwrites value from opts1 if given undefined in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ id: "a" }, { id: undefined });
|
|
assert.strictEqual(result.id, undefined);
|
|
}));
|
|
it("overwrites value from opts1 if given value in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ id: "a" }, { id: "b" });
|
|
assert.strictEqual(result.id, "b");
|
|
}));
|
|
it("overwrites promise-value from opts1 if given value in opts2", asyncTest(async () => {
|
|
const result: any = mergeOptions({ id: Promise.resolve("a") }, { id: "b" });
|
|
assert.strictEqual(await result.id.promise(), "b");
|
|
}));
|
|
it("overwrites value from opts1 if given promise-value in opts2", asyncTest(async () => {
|
|
const result: any = mergeOptions({ id: "a" }, { id: Promise.resolve("b") });
|
|
assert.strictEqual(await result.id.promise(), "b");
|
|
}));
|
|
it("overwrites promise-value from opts1 if given promise-value in opts2", asyncTest(async () => {
|
|
const result: any = mergeOptions({ id: Promise.resolve("a") }, { id: Promise.resolve("b") });
|
|
assert.strictEqual(await result.id.promise(), "b");
|
|
}));
|
|
});
|
|
|
|
describe("array", () => {
|
|
it("keeps value from opts1 if not provided in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ ignoreChanges: ["a"] }, {});
|
|
assert.deepStrictEqual(result.ignoreChanges, ["a"]);
|
|
}));
|
|
it("keeps value from opts2 if not provided in opts1", asyncTest(async () => {
|
|
const result = mergeOptions({ }, { ignoreChanges: ["a"] });
|
|
assert.deepStrictEqual(result.ignoreChanges, ["a"]);
|
|
}));
|
|
it("does nothing to value from opts1 if given null in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ ignoreChanges: ["a"] }, { ignoreChanges: null! });
|
|
assert.deepStrictEqual(result.ignoreChanges, ["a"]);
|
|
}));
|
|
it("does nothing to value from opts1 if given undefined in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ ignoreChanges: ["a"] }, { ignoreChanges: undefined });
|
|
assert.deepStrictEqual(result.ignoreChanges, ["a"]);
|
|
}));
|
|
it("merges values from opts1 if given value in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ ignoreChanges: ["a"] }, { ignoreChanges: ["b"] });
|
|
assert.deepStrictEqual(result.ignoreChanges, ["a", "b"]);
|
|
}));
|
|
|
|
describe("including promises", () => {
|
|
it("merges non-promise in opts1 and promise in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ aliases: ["a"] }, { aliases: [Promise.resolve("b")] });
|
|
const aliases = result.aliases!;
|
|
assert.deepStrictEqual(Array.isArray(aliases), true);
|
|
assert.deepStrictEqual(aliases.length, 2);
|
|
assert.deepStrictEqual(aliases[0], "a");
|
|
assert.deepStrictEqual(aliases[1] instanceof Promise, true);
|
|
|
|
const val1 = await aliases[1];
|
|
assert.deepStrictEqual(val1, "b");
|
|
}));
|
|
it("merges promise in opts1 and non-promise in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ aliases: [Promise.resolve("a")] }, { aliases: ["b"] });
|
|
const aliases = result.aliases!;
|
|
assert.deepStrictEqual(Array.isArray(aliases), true);
|
|
assert.deepStrictEqual(aliases.length, 2);
|
|
assert.deepStrictEqual(aliases[0] instanceof Promise, true);
|
|
assert.deepStrictEqual(aliases[1], "b");
|
|
|
|
const val0 = await aliases[0];
|
|
assert.deepStrictEqual(val0, "a");
|
|
}));
|
|
});
|
|
});
|
|
|
|
describe("arrayTransformations", () => {
|
|
const a = () => undefined;
|
|
const b = () => undefined;
|
|
it("keeps value from opts1 if not provided in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ transformations: [a] }, {});
|
|
assert.deepStrictEqual(result.transformations, [a]);
|
|
}));
|
|
it("keeps value from opts2 if not provided in opts1", asyncTest(async () => {
|
|
const result = mergeOptions({ }, { transformations: [a] });
|
|
assert.deepStrictEqual(result.transformations, [a]);
|
|
}));
|
|
it("does nothing to value from opts1 if given null in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ transformations: [a] }, { transformations: null! });
|
|
assert.deepStrictEqual(result.transformations, [a]);
|
|
}));
|
|
it("does nothing to value from opts1 if given undefined in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ transformations: [a] }, { transformations: undefined });
|
|
assert.deepStrictEqual(result.transformations, [a]);
|
|
}));
|
|
it("merges values from opts1 if given value in opts2", asyncTest(async () => {
|
|
const result = mergeOptions({ transformations: [a] }, { transformations: [b] });
|
|
assert.deepStrictEqual(result.transformations, [a, b]);
|
|
}));
|
|
});
|
|
|
|
describe("providers", () => {
|
|
const awsProvider = <ProviderResource>{ getPackage: () => "aws" };
|
|
const azureProvider = <ProviderResource>{ getPackage: () => "azure" };
|
|
const gcpProvider = <ProviderResource>{ getPackage: () => "gcp" };
|
|
|
|
it("merges singleton into map", () => {
|
|
const result = mergeOptions({ providers: { aws: awsProvider } }, { provider: azureProvider });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
it("merges singleton-array into map", () => {
|
|
const result = mergeOptions({ providers: { aws: awsProvider } }, { providers: [azureProvider] });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
it("merges array into map", () => {
|
|
const result = mergeOptions({ providers: { aws: awsProvider } }, { providers: [azureProvider, gcpProvider] });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider, gcp: gcpProvider } });
|
|
});
|
|
|
|
it("merges map into singleton", () => {
|
|
const result = mergeOptions({ provider: awsProvider }, { providers: { azure: azureProvider } });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
it("merges map into singleton-array", () => {
|
|
const result = mergeOptions({ providers: [awsProvider] }, { providers: { azure: azureProvider } });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
it("merges map into array", () => {
|
|
const result = mergeOptions({ providers: [awsProvider, azureProvider] }, { providers: { gcp: gcpProvider } });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider, gcp: gcpProvider } });
|
|
});
|
|
|
|
it("merges map into map", () => {
|
|
const result = mergeOptions({ providers: { aws: awsProvider } }, { providers: { azure: azureProvider } });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
|
|
it("merges array into array", () => {
|
|
const result = mergeOptions({ providers: [awsProvider] }, { providers: [azureProvider] });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
|
|
it("merges singleton into singleton", () => {
|
|
const result = mergeOptions(<ComponentResourceOptions>{ provider: awsProvider }, { provider: azureProvider });
|
|
assert.deepStrictEqual(result, { providers: { aws: awsProvider, azure: azureProvider } });
|
|
});
|
|
});
|
|
|
|
describe("dependsOn", () => {
|
|
function mergeDependsOn(a: any, b: any): any {
|
|
return merge(a, b, /*alwaysCreateArray:*/ true);
|
|
}
|
|
|
|
it("merges two scalers into array", () => {
|
|
const result = mergeDependsOn("a", "b");
|
|
assert.deepStrictEqual(result, ["a", "b"]);
|
|
});
|
|
it("merges array and scaler", () => {
|
|
const result = mergeDependsOn(["a"], "b");
|
|
assert.deepStrictEqual(result, ["a", "b"]);
|
|
});
|
|
it("merges scaler and array", () => {
|
|
const result = mergeDependsOn("a", ["b"]);
|
|
assert.deepStrictEqual(result, ["a", "b"]);
|
|
});
|
|
|
|
it("merges promise-scaler and scaler into array", async () => {
|
|
const result = mergeDependsOn(Promise.resolve("a"), "b");
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
it("merges scaler and promise-scaler into array", async () => {
|
|
const result = mergeDependsOn("a", Promise.resolve("b"));
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
it("merges promise-scaler and promise-scaler into array", async () => {
|
|
const result = mergeDependsOn(Promise.resolve("a"), Promise.resolve("b"));
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
|
|
it("merges promise-array and scaler into array", async () => {
|
|
const result = mergeDependsOn(Promise.resolve(["a"]), "b");
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
it("merges promise-scaler and array into array", async () => {
|
|
const result = mergeDependsOn(Promise.resolve("a"), ["b"]);
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
it("merges promise-scaler and promise-array into array", async () => {
|
|
const result = mergeDependsOn(Promise.resolve("a"), Promise.resolve(["b"]));
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
it("merges promise-array and promise-array into array", async () => {
|
|
const result = mergeDependsOn(Promise.resolve(["a"]), Promise.resolve(["b"]));
|
|
assert.deepStrictEqual(await result.promise(), ["a", "b"]);
|
|
});
|
|
});
|
|
});
|
|
});
|