2018-05-22 19:43:36 +00: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.
|
2017-09-01 00:57:49 +00:00
|
|
|
|
|
|
|
import * as assert from "assert";
|
Take an initial stab at closure serialization
This change contains an initial implementation of closure serialization
built atop V8, rather than our own custom runtime. This requires that
we use a Node.js dynamic C++ module, so that we can access the V8
APIs directly. No build magic is required beyond node-gyp.
For the most part, this was straight forward, except for one part: we
have to use internal V8 APIs. This is required for two reasons:
1) We need access to the function's lexical closure environment, so
that we may look up closure variables. Although there is a
tantalizingly-close v8::Object::CreationContext, its implementation
intentionally pokes through closure contexts in order to recover
the Function constructor context instead. That's not what we
want. We want the raw, unadulterated Function::context.
2) We need to control the lexical lookups of free variables so that
they can look past chained contexts, lexical contexts, withs, and
eval-style context extensions. Simply runing a v8::Script, or
simulating an eval, doesn't do the trick. Hence, we need to access
the unexported v8::internal::Context::Lookup function.
There is a third reason which is not yet implemented: free variable
calculation. I could use Esprima, or do my own scanner for free
variables, but I'd prefer to simply use the V8 parser so that we're
using the same JavaScript parser across all components. That too
is not part of the v8.h API, so we'll need to crack it open more.
To be clear, these are still exported public APIs, in proper headers
that are distributed with both Node and V8. They simply aren't part
of the "stable" v8.h surface area. As a result, I do expect that
maintaining this will be tricky, and I'd like to keep exploring how
to do this without needing the internal dependency. For instance,
although this works with node-gyp just fine, we will probably be
brittle across versions of Node/V8, when the internal APIs might be
changing. This will introduce unfortunate versioning headaches (all,
hopefully and thankfully, caught at compile-time).
2017-09-02 20:09:28 +00:00
|
|
|
import { Config, runtime } from "../index";
|
2017-09-01 00:57:49 +00:00
|
|
|
|
|
|
|
describe("config", () => {
|
|
|
|
it("works, basically", () => {
|
|
|
|
// Set up some config and then read them back as strings.
|
2018-07-31 15:37:46 +00:00
|
|
|
runtime.setConfig("pkg:a", "foo");
|
|
|
|
runtime.setConfig("pkg:bar", "b");
|
|
|
|
runtime.setConfig("pkg:baz", "baz");
|
|
|
|
runtime.setConfig("otherpkg:a", "babble");
|
|
|
|
runtime.setConfig("otherpkg:nothere", "bazzle");
|
|
|
|
const config = new Config("pkg");
|
2017-09-01 00:57:49 +00:00
|
|
|
assert.strictEqual("foo", config.get("a"));
|
|
|
|
assert.strictEqual("foo", config.require("a"));
|
|
|
|
assert.strictEqual("b", config.get("bar"));
|
|
|
|
assert.strictEqual("b", config.require("bar"));
|
|
|
|
assert.strictEqual("baz", config.get("baz"));
|
|
|
|
assert.strictEqual("baz", config.require("baz"));
|
|
|
|
assert.strictEqual(undefined, config.get("nothere"));
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.throws(() => {
|
|
|
|
config.require("missing");
|
|
|
|
});
|
2017-09-01 00:57:49 +00:00
|
|
|
});
|
|
|
|
it("does strongly typed too!", () => {
|
|
|
|
// Set up some config and then read them back as typed things.
|
2018-07-31 15:37:46 +00:00
|
|
|
runtime.setConfig("pkg:boolf", "false");
|
|
|
|
runtime.setConfig("pkg:boolt", "true");
|
|
|
|
runtime.setConfig("pkg:num", "42.333");
|
2023-04-28 22:27:10 +00:00
|
|
|
runtime.setConfig("pkg:array", '[ 0, false, 2, "foo" ]');
|
|
|
|
runtime.setConfig("pkg:struct", '{ "foo": "bar", "mim": [] }');
|
2018-07-31 15:37:46 +00:00
|
|
|
const config = new Config("pkg");
|
2017-09-01 00:57:49 +00:00
|
|
|
assert.strictEqual(false, config.getBoolean("boolf"));
|
|
|
|
assert.strictEqual(false, config.requireBoolean("boolf"));
|
|
|
|
assert.strictEqual(true, config.getBoolean("boolt"));
|
|
|
|
assert.strictEqual(true, config.requireBoolean("boolt"));
|
|
|
|
assert.strictEqual(undefined, config.getBoolean("boolmissing"));
|
|
|
|
assert.strictEqual(42.333, config.getNumber("num"));
|
|
|
|
assert.strictEqual(42.333, config.requireNumber("num"));
|
|
|
|
assert.strictEqual(undefined, config.getNumber("nummissing"));
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.deepStrictEqual([0, false, 2, "foo"], config.getObject<any>("array"));
|
|
|
|
assert.deepStrictEqual([0, false, 2, "foo"], config.requireObject<any>("array"));
|
|
|
|
assert.deepStrictEqual({ foo: "bar", mim: [] }, config.getObject<any>("struct"));
|
|
|
|
assert.deepStrictEqual({ foo: "bar", mim: [] }, config.requireObject<any>("struct"));
|
2017-09-01 00:57:49 +00:00
|
|
|
assert.strictEqual(undefined, config.getObject<any>("complexmissing"));
|
|
|
|
// ensure requireX throws when missing:
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.throws(() => {
|
|
|
|
config.requireBoolean("missing");
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.requireNumber("missing");
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.requireObject<any>("missing");
|
|
|
|
});
|
2017-09-01 00:57:49 +00:00
|
|
|
// ensure getX throws when the value is of the wrong type:
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.throws(() => {
|
|
|
|
config.getBoolean("num");
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.getNumber("boolf");
|
|
|
|
});
|
2017-09-01 00:57:49 +00:00
|
|
|
});
|
Add more config helpers
Everytime I convert a CloudFormation template to Pulumi, I inevitably
run into the fact that CloudFormation has advanced "schema" capabilities
for input variables, like min/max for numbers and string lengths, enums,
and regex pattern matching. This is always cumbersome to convert.
In this change, I've added a number of config helpers for these cases:
For string enums:
getEnum(key: string, values: string[]): string | undefined;
requireEnum(key: string: values: string[]): string;
For min/max strlen:
getMinMax(key: string, min: number, max: number): string | undefined;
requireMinMax(key: string, min: number, max: number): string;
For regex patterns:
getPattern(key: string, regexp: string | RegExp): string | undefined;
requirePattern(key: string, regexp: string | RegExp): string;
For min/max strlen _and_ regex patterns:
getMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string | undefined;
requireMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string;
For min/max numbers:
getNumberMinMax(key: string, min: number, max: number): number | undefined;
requireNumberMinMax(key: string, min: number, max: number): number;
Each function throws a detailed RunError-derived exception type if the
configuration value doesn't meet the constraint.
This fixes pulumi/pulumi#1671.
2018-08-28 20:14:27 +00:00
|
|
|
it("does enums", () => {
|
|
|
|
runtime.setConfig("pkg:color", "orange");
|
|
|
|
const config = new Config("pkg");
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.strictEqual("orange", config.get("color", { allowedValues: ["purple", "orange", "blue"] }));
|
|
|
|
assert.strictEqual(undefined, config.get("missing", { allowedValues: ["purple", "orange", "blue"] }));
|
|
|
|
assert.strictEqual("orange", config.require("color", { allowedValues: ["purple", "orange", "blue"] }));
|
|
|
|
assert.throws(() => {
|
|
|
|
config.get("color", { allowedValues: ["purple", "black", "blue"] });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("color", { allowedValues: ["purple", "black", "blue"] });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("missing", { allowedValues: ["purple", "orange", "blue"] });
|
|
|
|
});
|
Add more config helpers
Everytime I convert a CloudFormation template to Pulumi, I inevitably
run into the fact that CloudFormation has advanced "schema" capabilities
for input variables, like min/max for numbers and string lengths, enums,
and regex pattern matching. This is always cumbersome to convert.
In this change, I've added a number of config helpers for these cases:
For string enums:
getEnum(key: string, values: string[]): string | undefined;
requireEnum(key: string: values: string[]): string;
For min/max strlen:
getMinMax(key: string, min: number, max: number): string | undefined;
requireMinMax(key: string, min: number, max: number): string;
For regex patterns:
getPattern(key: string, regexp: string | RegExp): string | undefined;
requirePattern(key: string, regexp: string | RegExp): string;
For min/max strlen _and_ regex patterns:
getMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string | undefined;
requireMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string;
For min/max numbers:
getNumberMinMax(key: string, min: number, max: number): number | undefined;
requireNumberMinMax(key: string, min: number, max: number): number;
Each function throws a detailed RunError-derived exception type if the
configuration value doesn't meet the constraint.
This fixes pulumi/pulumi#1671.
2018-08-28 20:14:27 +00:00
|
|
|
});
|
|
|
|
it("does min/max (strlen)", () => {
|
|
|
|
runtime.setConfig("pkg:strlen", "abcdefgh");
|
|
|
|
const config = new Config("pkg");
|
2018-08-29 01:05:24 +00:00
|
|
|
assert.strictEqual("abcdefgh", config.get("strlen", { minLength: 0, maxLength: 8 }));
|
|
|
|
assert.strictEqual("abcdefgh", config.get("strlen", { minLength: 8, maxLength: 8 }));
|
|
|
|
assert.strictEqual("abcdefgh", config.get("strlen", { minLength: 0, maxLength: 16 }));
|
|
|
|
assert.strictEqual(undefined, config.get("missing", { minLength: 0, maxLength: 8 }));
|
|
|
|
assert.strictEqual("abcdefgh", config.require("strlen", { minLength: 0, maxLength: 8 }));
|
|
|
|
assert.strictEqual("abcdefgh", config.require("strlen", { minLength: 8, maxLength: 8 }));
|
|
|
|
assert.strictEqual("abcdefgh", config.require("strlen", { minLength: 0, maxLength: 16 }));
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.throws(() => {
|
|
|
|
config.get("strlen", { minLength: 9, maxLength: 16 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.get("strlen", { minLength: 0, maxLength: 7 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("strlen", { minLength: 9, maxLength: 16 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("strlen", { minLength: 0, maxLength: 7 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("missing", { minLength: 0, maxLength: 8 });
|
|
|
|
});
|
Add more config helpers
Everytime I convert a CloudFormation template to Pulumi, I inevitably
run into the fact that CloudFormation has advanced "schema" capabilities
for input variables, like min/max for numbers and string lengths, enums,
and regex pattern matching. This is always cumbersome to convert.
In this change, I've added a number of config helpers for these cases:
For string enums:
getEnum(key: string, values: string[]): string | undefined;
requireEnum(key: string: values: string[]): string;
For min/max strlen:
getMinMax(key: string, min: number, max: number): string | undefined;
requireMinMax(key: string, min: number, max: number): string;
For regex patterns:
getPattern(key: string, regexp: string | RegExp): string | undefined;
requirePattern(key: string, regexp: string | RegExp): string;
For min/max strlen _and_ regex patterns:
getMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string | undefined;
requireMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string;
For min/max numbers:
getNumberMinMax(key: string, min: number, max: number): number | undefined;
requireNumberMinMax(key: string, min: number, max: number): number;
Each function throws a detailed RunError-derived exception type if the
configuration value doesn't meet the constraint.
This fixes pulumi/pulumi#1671.
2018-08-28 20:14:27 +00:00
|
|
|
});
|
|
|
|
it("does pattern matching", () => {
|
|
|
|
runtime.setConfig("pkg:pattern", "aBcDeFgH");
|
|
|
|
const config = new Config("pkg");
|
2018-08-29 01:05:24 +00:00
|
|
|
assert.strictEqual("aBcDeFgH", config.get("pattern", { pattern: /^[a-zA-Z]*$/ }));
|
|
|
|
assert.strictEqual("aBcDeFgH", config.get("pattern", { pattern: "^[a-zA-Z]*$" }));
|
|
|
|
assert.strictEqual(undefined, config.get("missing", { pattern: /^[a-zA-Z]*$/ }));
|
|
|
|
assert.strictEqual("aBcDeFgH", config.require("pattern", { pattern: /^[a-zA-Z]*$/ }));
|
|
|
|
assert.strictEqual("aBcDeFgH", config.require("pattern", { pattern: "^[a-zA-Z]*$" }));
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.throws(() => {
|
|
|
|
config.get("pattern", { pattern: /^[a-z]*$/ });
|
|
|
|
}, "bad pattern: get");
|
|
|
|
assert.throws(() => {
|
|
|
|
config.get("pattern", { pattern: "/^[a-z]*$/" });
|
|
|
|
}, "bad pattern (string): get");
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("pattern", { pattern: /^[a-z]*$/ });
|
|
|
|
}, "bad pattern: require");
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("pattern", { pattern: "/^[a-z]*$/" });
|
|
|
|
}, "bad pattern (string): require");
|
|
|
|
assert.throws(() => {
|
|
|
|
config.require("missing", { pattern: /^[a-z]*$/ });
|
|
|
|
}, "missing");
|
Add more config helpers
Everytime I convert a CloudFormation template to Pulumi, I inevitably
run into the fact that CloudFormation has advanced "schema" capabilities
for input variables, like min/max for numbers and string lengths, enums,
and regex pattern matching. This is always cumbersome to convert.
In this change, I've added a number of config helpers for these cases:
For string enums:
getEnum(key: string, values: string[]): string | undefined;
requireEnum(key: string: values: string[]): string;
For min/max strlen:
getMinMax(key: string, min: number, max: number): string | undefined;
requireMinMax(key: string, min: number, max: number): string;
For regex patterns:
getPattern(key: string, regexp: string | RegExp): string | undefined;
requirePattern(key: string, regexp: string | RegExp): string;
For min/max strlen _and_ regex patterns:
getMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string | undefined;
requireMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string;
For min/max numbers:
getNumberMinMax(key: string, min: number, max: number): number | undefined;
requireNumberMinMax(key: string, min: number, max: number): number;
Each function throws a detailed RunError-derived exception type if the
configuration value doesn't meet the constraint.
This fixes pulumi/pulumi#1671.
2018-08-28 20:14:27 +00:00
|
|
|
});
|
|
|
|
it("does min/max (numbers)", () => {
|
|
|
|
runtime.setConfig("pkg:quantity", "8");
|
|
|
|
const config = new Config("pkg");
|
2018-08-29 01:05:24 +00:00
|
|
|
assert.strictEqual(8, config.getNumber("quantity", { min: 0, max: 8 }));
|
|
|
|
assert.strictEqual(8, config.getNumber("quantity", { min: 8, max: 8 }));
|
|
|
|
assert.strictEqual(8, config.getNumber("quantity", { min: 0, max: 16 }));
|
|
|
|
assert.strictEqual(undefined, config.getNumber("missing", { min: 0, max: 8 }));
|
|
|
|
assert.strictEqual(8, config.requireNumber("quantity", { min: 0, max: 8 }));
|
|
|
|
assert.strictEqual(8, config.requireNumber("quantity", { min: 8, max: 8 }));
|
|
|
|
assert.strictEqual(8, config.requireNumber("quantity", { min: 0, max: 16 }));
|
2023-04-28 22:27:10 +00:00
|
|
|
assert.throws(() => {
|
|
|
|
config.getNumber("quantity", { min: 9, max: 16 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.getNumber("quantity", { min: 0, max: 7 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.requireNumber("quantity", { min: 9, max: 16 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.requireNumber("quantity", { min: 0, max: 7 });
|
|
|
|
});
|
|
|
|
assert.throws(() => {
|
|
|
|
config.requireNumber("missing", { min: 0, max: 8 });
|
|
|
|
});
|
Add more config helpers
Everytime I convert a CloudFormation template to Pulumi, I inevitably
run into the fact that CloudFormation has advanced "schema" capabilities
for input variables, like min/max for numbers and string lengths, enums,
and regex pattern matching. This is always cumbersome to convert.
In this change, I've added a number of config helpers for these cases:
For string enums:
getEnum(key: string, values: string[]): string | undefined;
requireEnum(key: string: values: string[]): string;
For min/max strlen:
getMinMax(key: string, min: number, max: number): string | undefined;
requireMinMax(key: string, min: number, max: number): string;
For regex patterns:
getPattern(key: string, regexp: string | RegExp): string | undefined;
requirePattern(key: string, regexp: string | RegExp): string;
For min/max strlen _and_ regex patterns:
getMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string | undefined;
requireMinMaxPattern(key: string, min: number, max: number,
regexp: string | RegExp): string;
For min/max numbers:
getNumberMinMax(key: string, min: number, max: number): number | undefined;
requireNumberMinMax(key: string, min: number, max: number): number;
Each function throws a detailed RunError-derived exception type if the
configuration value doesn't meet the constraint.
This fixes pulumi/pulumi#1671.
2018-08-28 20:14:27 +00:00
|
|
|
});
|
2017-09-01 00:57:49 +00:00
|
|
|
});
|