@continuous-excellence/ze-great-dashboard-aws 0.13.7 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/bootstrap/core-ecs-v1.yml +86 -0
- package/bootstrap/core-v1.yml +6 -1
- package/bootstrap/dashboard-bootstrap.example.json +1 -0
- package/bootstrap/github-oidc-ecs-v1.yml +39 -0
- package/bootstrap/github-oidc-v2.yml +6 -1
- package/client/.vite/manifest.json +1 -1
- package/client/assets/index-zOEGeAGM.js +3 -0
- package/client/assets/index-zOEGeAGM.js.map +1 -0
- package/client/index.html +1 -1
- package/dist/bootstrap.d.ts +7 -2
- package/dist/cli.js +967 -821
- package/dist/compute-mode.d.ts +14 -0
- package/dist/guided.d.ts +2 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +143 -32
- package/dist/lambda.mjs +13 -2
- package/package.json +2 -1
- package/template-ecs.yml +101 -0
- package/template.yml +2 -1
- package/client/assets/index-DEohlAIs.js +0 -3
- package/client/assets/index-DEohlAIs.js.map +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ComputeMode = 'lambda' | 'ecs';
|
|
2
|
+
export type ComputeModeInputs = {
|
|
3
|
+
/** Mode from the checked-in bootstrap manifest. */
|
|
4
|
+
persisted?: string;
|
|
5
|
+
/** Mode from an existing/generated CloudFormation parameter file. */
|
|
6
|
+
artifact?: string;
|
|
7
|
+
/** Optional CLI assertion used for inspection and repair. */
|
|
8
|
+
explicit?: string;
|
|
9
|
+
};
|
|
10
|
+
/** Resolves every deployment mode decision and rejects contradictory selectors. */
|
|
11
|
+
export declare function resolveComputeMode(input?: ComputeModeInputs): ComputeMode;
|
|
12
|
+
export declare function computeMode(config: {
|
|
13
|
+
mode?: string;
|
|
14
|
+
}): ComputeMode;
|
package/dist/guided.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BootstrapConfig } from './bootstrap.js';
|
|
1
|
+
import { type BootstrapConfig, type ComputeMode } from './bootstrap.js';
|
|
2
2
|
import { type BootstrapProvider, bootstrapHandoff, type CommandRunner } from './handoff.js';
|
|
3
3
|
export type BootstrapCheckStatus = 'ready' | 'missing' | 'mismatch' | 'unverified';
|
|
4
4
|
export type BootstrapPreflightCheck = {
|
|
@@ -11,6 +11,7 @@ export type BootstrapPreflight = {
|
|
|
11
11
|
checks: BootstrapPreflightCheck[];
|
|
12
12
|
};
|
|
13
13
|
export type BootstrapInitInput = {
|
|
14
|
+
mode?: ComputeMode;
|
|
14
15
|
slug: string;
|
|
15
16
|
repository: string;
|
|
16
17
|
environment: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ComputeMode } from './bootstrap.js';
|
|
2
|
+
export { type BootstrapConfig, type BootstrapConsistency, type BootstrapKind, type BootstrapPlan, type BootstrapTemplateInspection, bootstrapConsistency, bootstrapContractVersion, bootstrapPlan, bootstrapTemplate, bootstrapTemplatePath, bootstrapTemplateRevision, type CloudFormationParameterValue, type ComputeMode, computeMode, coreBootstrapOutputs, type DeployedBootstrapStack, deployedBootstrapStack, mergeBootstrapParameters, requireComputeMode, requiredBootstrapParameters, resolveComputeMode, } from './bootstrap.js';
|
|
2
3
|
export { type BootstrapCheck, type BootstrapCheckDependencies, type BootstrapResourceDifference, type BootstrapResourceDrift, type BootstrapResourceDriftResult, type BootstrapStackCheck, checkBootstrap, formatBootstrapCheckText, } from './bootstrap-check.js';
|
|
3
4
|
export { type BootstrapCheckStatus, type BootstrapInitInput, type BootstrapPreflight, type BootstrapPreflightCheck, bootstrapGuide, bootstrapPreflight, scaffoldBootstrapManifest, } from './guided.js';
|
|
4
5
|
export { type BootstrapHandoff, type BootstrapPhase, type BootstrapProvider, type BootstrapVerification, bootstrapHandoff, type CommandRunner, githubOidcProvider, verifyBootstrap, } from './handoff.js';
|
|
5
6
|
export type ReleaseMetadata = {
|
|
7
|
+
computeMode: ComputeMode;
|
|
6
8
|
dashboardVersion: string;
|
|
7
9
|
clientAssetUrl: string;
|
|
8
10
|
serverRuntimeVersion: string;
|
|
@@ -14,6 +16,7 @@ export type ReleaseMetadata = {
|
|
|
14
16
|
};
|
|
15
17
|
export type PackagedRelease = ReleaseMetadata & {
|
|
16
18
|
artifactKey: string;
|
|
19
|
+
image?: string;
|
|
17
20
|
};
|
|
18
21
|
export type LambdaPackageOptions = {
|
|
19
22
|
boardConfigPath: string;
|
|
@@ -23,7 +26,13 @@ export type LambdaPackageOptions = {
|
|
|
23
26
|
/** ARN of the consumer-owned JSON credential-map secret, never a secret value. */
|
|
24
27
|
secretReference?: string;
|
|
25
28
|
};
|
|
29
|
+
export type EcsPackageOptions = Omit<LambdaPackageOptions, 'secretReference'> & {
|
|
30
|
+
secretReference?: string;
|
|
31
|
+
imageReference?: string;
|
|
32
|
+
};
|
|
26
33
|
export declare function packageLambda(options: LambdaPackageOptions): Promise<PackagedRelease>;
|
|
34
|
+
/** Packages the published container reference and ECS CloudFormation handoff. */
|
|
35
|
+
export declare function packageEcs(options: EcsPackageOptions): Promise<PackagedRelease>;
|
|
27
36
|
export type PublishClientAssetsOptions = {
|
|
28
37
|
assetsDir: string;
|
|
29
38
|
assetsBucket: string;
|
|
@@ -32,4 +41,4 @@ export type PublishClientAssetsOptions = {
|
|
|
32
41
|
};
|
|
33
42
|
/** Publishes the versioned client half of a provider-managed release. */
|
|
34
43
|
export declare function publishClientAssets(options: PublishClientAssetsOptions): Promise<string>;
|
|
35
|
-
export declare function cloudFormationTemplate(): Promise<string>;
|
|
44
|
+
export declare function cloudFormationTemplate(mode?: ComputeMode): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -721,6 +721,9 @@ function zipSync(data, opts) {
|
|
|
721
721
|
return out;
|
|
722
722
|
}
|
|
723
723
|
|
|
724
|
+
// packages/aws/src/index.ts
|
|
725
|
+
import { parseDocument, Scalar } from "yaml";
|
|
726
|
+
|
|
724
727
|
// packages/aws/src/release.ts
|
|
725
728
|
import { createHash } from "node:crypto";
|
|
726
729
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
@@ -843,16 +846,52 @@ function sha256(value2) {
|
|
|
843
846
|
// packages/aws/src/bootstrap.ts
|
|
844
847
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
845
848
|
import { fileURLToPath } from "node:url";
|
|
849
|
+
|
|
850
|
+
// packages/aws/src/compute-mode.ts
|
|
851
|
+
function valid(value2, label) {
|
|
852
|
+
if (value2 !== "lambda" && value2 !== "ecs")
|
|
853
|
+
throw new Error(`${label} must be lambda or ecs (received ${value2})`);
|
|
854
|
+
return value2;
|
|
855
|
+
}
|
|
856
|
+
function resolveComputeMode(input = {}) {
|
|
857
|
+
const persisted = input.persisted === void 0 ? void 0 : valid(input.persisted, "persisted mode");
|
|
858
|
+
const artifact = input.artifact === void 0 ? void 0 : valid(input.artifact, "ComputeMode");
|
|
859
|
+
const explicit = input.explicit === void 0 ? void 0 : valid(input.explicit, "mode");
|
|
860
|
+
const selected = persisted ?? explicit ?? artifact ?? "lambda";
|
|
861
|
+
if (artifact !== void 0 && artifact !== selected)
|
|
862
|
+
throw new Error(
|
|
863
|
+
`ComputeMode ${artifact} disagrees with persisted mode ${selected}; regenerate the bootstrap and parameter artifacts to change deployment mode`
|
|
864
|
+
);
|
|
865
|
+
if (explicit !== void 0 && explicit !== selected)
|
|
866
|
+
throw new Error(
|
|
867
|
+
`explicit mode ${explicit} disagrees with persisted mode ${selected}; regenerate the bootstrap and parameter artifacts to change deployment mode`
|
|
868
|
+
);
|
|
869
|
+
return selected;
|
|
870
|
+
}
|
|
871
|
+
function computeMode(config) {
|
|
872
|
+
return resolveComputeMode({ persisted: config.mode });
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// packages/aws/src/bootstrap.ts
|
|
846
876
|
var templates = {
|
|
847
|
-
core:
|
|
848
|
-
|
|
877
|
+
core: {
|
|
878
|
+
lambda: "../bootstrap/core-v1.yml",
|
|
879
|
+
ecs: "../bootstrap/core-ecs-v1.yml"
|
|
880
|
+
},
|
|
881
|
+
"github-oidc": {
|
|
882
|
+
lambda: "../bootstrap/github-oidc-v2.yml",
|
|
883
|
+
ecs: "../bootstrap/github-oidc-ecs-v1.yml"
|
|
884
|
+
}
|
|
849
885
|
};
|
|
850
|
-
|
|
886
|
+
function requireComputeMode(persisted, explicit) {
|
|
887
|
+
return resolveComputeMode({ persisted: persisted.mode, explicit });
|
|
888
|
+
}
|
|
889
|
+
async function bootstrapTemplatePath(kind, mode = "lambda") {
|
|
851
890
|
if (!Object.hasOwn(templates, kind)) throw new Error(`Unknown bootstrap template: ${kind}`);
|
|
852
|
-
return fileURLToPath(new URL(templates[kind], import.meta.url));
|
|
891
|
+
return fileURLToPath(new URL(templates[kind][mode], import.meta.url));
|
|
853
892
|
}
|
|
854
|
-
async function bootstrapTemplate(kind) {
|
|
855
|
-
return readFile2(await bootstrapTemplatePath(kind), "utf8");
|
|
893
|
+
async function bootstrapTemplate(kind, mode = "lambda") {
|
|
894
|
+
return readFile2(await bootstrapTemplatePath(kind, mode), "utf8");
|
|
856
895
|
}
|
|
857
896
|
function bootstrapContractVersion(template) {
|
|
858
897
|
const version = template.match(/BootstrapContractVersion:\s*\{\s*Value:\s*'([^']+)'\s*}/)?.[1];
|
|
@@ -889,9 +928,10 @@ async function bootstrapPlan(config) {
|
|
|
889
928
|
);
|
|
890
929
|
const packageTemplates = await Promise.all(
|
|
891
930
|
["core", "github-oidc"].map(async (kind) => {
|
|
931
|
+
const mode = computeMode(config);
|
|
892
932
|
const [path, template] = await Promise.all([
|
|
893
|
-
bootstrapTemplatePath(kind),
|
|
894
|
-
bootstrapTemplate(kind)
|
|
933
|
+
bootstrapTemplatePath(kind, mode),
|
|
934
|
+
bootstrapTemplate(kind, mode)
|
|
895
935
|
]);
|
|
896
936
|
return inspectTemplate(kind, path, template);
|
|
897
937
|
})
|
|
@@ -964,6 +1004,7 @@ function bootstrapConsistency(kind, config, stackInput, expectedContract, expect
|
|
|
964
1004
|
ArtifactBucketName: config.core?.artifactBucketName,
|
|
965
1005
|
ApplicationStackName: config.core?.applicationStackName,
|
|
966
1006
|
DashboardFunctionName: config.core?.dashboardFunctionName,
|
|
1007
|
+
ComputeMode: config.mode,
|
|
967
1008
|
RuntimeSecretArn: config.core?.runtimeSecretArn ?? "",
|
|
968
1009
|
ArtifactKmsKeyArn: config.core?.artifactKmsKeyArn ?? ""
|
|
969
1010
|
} : {
|
|
@@ -975,6 +1016,7 @@ function bootstrapConsistency(kind, config, stackInput, expectedContract, expect
|
|
|
975
1016
|
CoreBootstrapStackName: config.core?.stackName,
|
|
976
1017
|
ApplicationStackName: config.core?.applicationStackName,
|
|
977
1018
|
ArtifactBucketName: config.core?.artifactBucketName,
|
|
1019
|
+
ComputeMode: config.mode,
|
|
978
1020
|
CloudFormationExecutionRoleArn: coreOutputs.CloudFormationExecutionRoleArn,
|
|
979
1021
|
ConsumerGatewayStackName: config.githubOidc?.consumerGatewayStackName ?? ""
|
|
980
1022
|
};
|
|
@@ -1037,7 +1079,7 @@ function requiredBootstrapParameters(kind, values) {
|
|
|
1037
1079
|
];
|
|
1038
1080
|
const missing = keys.filter((key) => !values[key]);
|
|
1039
1081
|
if (missing.length) throw new Error(`Missing required bootstrap values: ${missing.join(", ")}`);
|
|
1040
|
-
const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : ["ConsumerGatewayStackName"];
|
|
1082
|
+
const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn", "ComputeMode"] : ["ConsumerGatewayStackName", "ComputeMode"];
|
|
1041
1083
|
return [...keys, ...optional2].filter((key) => values[key] !== void 0).map((ParameterKey) => ({ ParameterKey, ParameterValue: values[ParameterKey] ?? "" }));
|
|
1042
1084
|
}
|
|
1043
1085
|
|
|
@@ -1460,14 +1502,16 @@ var githubOidcProvider = {
|
|
|
1460
1502
|
}
|
|
1461
1503
|
}
|
|
1462
1504
|
};
|
|
1463
|
-
async function contracts() {
|
|
1505
|
+
async function contracts(config) {
|
|
1464
1506
|
return {
|
|
1465
|
-
core: bootstrapContractVersion(await bootstrapTemplate("core")),
|
|
1466
|
-
githubOidc: bootstrapContractVersion(
|
|
1507
|
+
core: bootstrapContractVersion(await bootstrapTemplate("core", computeMode(config))),
|
|
1508
|
+
githubOidc: bootstrapContractVersion(
|
|
1509
|
+
await bootstrapTemplate("github-oidc", computeMode(config))
|
|
1510
|
+
)
|
|
1467
1511
|
};
|
|
1468
1512
|
}
|
|
1469
1513
|
async function bootstrapHandoff(input) {
|
|
1470
|
-
const expectedContracts = await contracts();
|
|
1514
|
+
const expectedContracts = await contracts(input.config);
|
|
1471
1515
|
const coreCapturePath = input.coreStackPath ?? workFile(input.workDir, "core-deployed-stack.json");
|
|
1472
1516
|
const core = input.coreStack === void 0 ? void 0 : deployedBootstrapStack(input.coreStack, expectedContracts.core);
|
|
1473
1517
|
const oidc = input.githubOidcStack === void 0 ? void 0 : deployedBootstrapStack(input.githubOidcStack, expectedContracts.githubOidc);
|
|
@@ -1477,7 +1521,7 @@ async function bootstrapHandoff(input) {
|
|
|
1477
1521
|
ApplicationStackName: input.config.core?.applicationStackName,
|
|
1478
1522
|
DashboardFunctionName: input.config.core?.dashboardFunctionName
|
|
1479
1523
|
});
|
|
1480
|
-
const templatePath = await bootstrapTemplatePath("core");
|
|
1524
|
+
const templatePath = await bootstrapTemplatePath("core", computeMode(input.config));
|
|
1481
1525
|
return {
|
|
1482
1526
|
phase: "core",
|
|
1483
1527
|
expectedContracts,
|
|
@@ -1517,7 +1561,7 @@ async function bootstrapHandoff(input) {
|
|
|
1517
1561
|
ConsumerGatewayStackName: input.config.githubOidc?.consumerGatewayStackName,
|
|
1518
1562
|
...coreBootstrapOutputs(core)
|
|
1519
1563
|
});
|
|
1520
|
-
const templatePath = await bootstrapTemplatePath("github-oidc");
|
|
1564
|
+
const templatePath = await bootstrapTemplatePath("github-oidc", computeMode(input.config));
|
|
1521
1565
|
return {
|
|
1522
1566
|
phase: "github-oidc",
|
|
1523
1567
|
expectedContracts,
|
|
@@ -1584,7 +1628,7 @@ function assertEqual(actual, expected, label) {
|
|
|
1584
1628
|
);
|
|
1585
1629
|
}
|
|
1586
1630
|
async function verifyBootstrap(input) {
|
|
1587
|
-
const expected = await contracts();
|
|
1631
|
+
const expected = await contracts(input.config);
|
|
1588
1632
|
const core = deployedBootstrapStack(input.coreStack, expected.core);
|
|
1589
1633
|
const oidc = deployedBootstrapStack(input.githubOidcStack, expected.githubOidc);
|
|
1590
1634
|
for (const [kind, capture] of [
|
|
@@ -1732,6 +1776,7 @@ async function discovered(input) {
|
|
|
1732
1776
|
const repo = await optional(input.runner, "gh", ["api", `repos/${input.repository}`]);
|
|
1733
1777
|
const github = parseOrUnavailable(repo) ?? {};
|
|
1734
1778
|
return {
|
|
1779
|
+
...input.mode ? { mode: input.mode } : {},
|
|
1735
1780
|
accountId,
|
|
1736
1781
|
region: region2?.trim() || void 0,
|
|
1737
1782
|
ownerId: typeof github.owner?.id === "number" || typeof github.owner?.id === "string" ? String(github.owner.id) : void 0,
|
|
@@ -1747,6 +1792,8 @@ async function scaffoldBootstrapManifest(input) {
|
|
|
1747
1792
|
const environment = value(input.environment, "environment");
|
|
1748
1793
|
const providerArn = value(input.providerArn, "github OIDC provider ARN");
|
|
1749
1794
|
if (!accountFromArn(providerArn)) throw new Error("github OIDC provider ARN is invalid");
|
|
1795
|
+
if (input.mode && input.mode !== "lambda" && input.mode !== "ecs")
|
|
1796
|
+
throw new Error("mode must be lambda or ecs");
|
|
1750
1797
|
const found = await discovered(input);
|
|
1751
1798
|
const accountId = input.accountId ?? found.accountId;
|
|
1752
1799
|
const region2 = input.region ?? found.region;
|
|
@@ -1970,27 +2017,45 @@ async function bootstrapGuide(input) {
|
|
|
1970
2017
|
|
|
1971
2018
|
// packages/aws/src/index.ts
|
|
1972
2019
|
var run = promisify(execFile);
|
|
2020
|
+
function requireImageDigest(image) {
|
|
2021
|
+
if (!/^[^\s@]+@sha256:[a-f0-9]{64}$/.test(image))
|
|
2022
|
+
throw new Error(
|
|
2023
|
+
"ECS imageReference must be an immutable registry digest (image@sha256:<64 hex>)"
|
|
2024
|
+
);
|
|
2025
|
+
return image;
|
|
2026
|
+
}
|
|
1973
2027
|
function deploymentTemplate(template, values) {
|
|
1974
|
-
const
|
|
1975
|
-
if (
|
|
2028
|
+
const document = parseDocument(template);
|
|
2029
|
+
if (document.errors.length)
|
|
2030
|
+
throw new Error(`Invalid CloudFormation template: ${document.errors[0]}`);
|
|
2031
|
+
const managedValue = document.toJS().Metadata?.PackageManagedParameters;
|
|
2032
|
+
if (!Array.isArray(managedValue))
|
|
1976
2033
|
throw new Error("CloudFormation template has no PackageManagedParameters metadata");
|
|
1977
|
-
const managed =
|
|
2034
|
+
const managed = managedValue.filter((key) => typeof key === "string");
|
|
2035
|
+
if (managed.length !== managedValue.length)
|
|
2036
|
+
throw new Error("CloudFormation template has invalid PackageManagedParameters metadata");
|
|
1978
2037
|
const missing = managed.filter((key) => !Object.hasOwn(values, key));
|
|
1979
2038
|
const unknown = Object.keys(values).filter((key) => !managed.includes(key));
|
|
1980
2039
|
if (missing.length || unknown.length)
|
|
1981
2040
|
throw new Error(
|
|
1982
2041
|
`Package-managed CloudFormation parameters are out of sync (missing: ${missing.join(", ") || "none"}; unknown: ${unknown.join(", ") || "none"})`
|
|
1983
2042
|
);
|
|
1984
|
-
|
|
1985
|
-
const
|
|
1986
|
-
const
|
|
1987
|
-
if (!
|
|
1988
|
-
throw new Error(`
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2043
|
+
for (const key of managed) {
|
|
2044
|
+
const path = ["Parameters", key];
|
|
2045
|
+
const parameter = document.getIn(path);
|
|
2046
|
+
if (!parameter || typeof parameter !== "object")
|
|
2047
|
+
throw new Error(`CloudFormation template has no declaration for ${key}`);
|
|
2048
|
+
const current = document.getIn([...path, "Default"], true);
|
|
2049
|
+
if (current !== void 0) {
|
|
2050
|
+
if (String(current) !== values[key])
|
|
2051
|
+
throw new Error(`Package-managed CloudFormation parameter ${key} has a conflicting default`);
|
|
2052
|
+
} else {
|
|
2053
|
+
const scalar = new Scalar(values[key]);
|
|
2054
|
+
scalar.type = "QUOTE_DOUBLE";
|
|
2055
|
+
document.setIn([...path, "Default"], scalar);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return document.toString({ lineWidth: 0 });
|
|
1994
2059
|
}
|
|
1995
2060
|
async function packageLambda(options) {
|
|
1996
2061
|
const outputDir = resolve2(options.outputDir);
|
|
@@ -2009,6 +2074,7 @@ async function packageLambda(options) {
|
|
|
2009
2074
|
});
|
|
2010
2075
|
const runtimeMetadata = {
|
|
2011
2076
|
...release.metadata,
|
|
2077
|
+
computeMode: "lambda",
|
|
2012
2078
|
artifactChecksums: {
|
|
2013
2079
|
...release.metadata.artifactChecksums,
|
|
2014
2080
|
"index.mjs": sha256(await readFile3(join2(runtimeDir, "index.mjs")))
|
|
@@ -2045,13 +2111,49 @@ async function packageLambda(options) {
|
|
|
2045
2111
|
`);
|
|
2046
2112
|
await writeFile2(
|
|
2047
2113
|
join2(outputDir, "template.yml"),
|
|
2048
|
-
deploymentTemplate(await cloudFormationTemplate(), {
|
|
2114
|
+
deploymentTemplate(await cloudFormationTemplate("lambda"), {
|
|
2115
|
+
ComputeMode: "lambda",
|
|
2049
2116
|
LambdaArtifactKey: packagedRelease.artifactKey,
|
|
2050
2117
|
DashboardVersion: packagedRelease.dashboardVersion
|
|
2051
2118
|
})
|
|
2052
2119
|
);
|
|
2053
2120
|
return packagedRelease;
|
|
2054
2121
|
}
|
|
2122
|
+
async function packageEcs(options) {
|
|
2123
|
+
const outputDir = resolve2(options.outputDir);
|
|
2124
|
+
await mkdir2(outputDir, { recursive: true });
|
|
2125
|
+
const imageReference = requireImageDigest(options.imageReference ?? "");
|
|
2126
|
+
const release = await assembleRelease({
|
|
2127
|
+
boardConfigPath: options.boardConfigPath,
|
|
2128
|
+
outputDir,
|
|
2129
|
+
version: options.version,
|
|
2130
|
+
providers: ["aws-ecs-fargate"],
|
|
2131
|
+
assetDomain: options.assetDomain,
|
|
2132
|
+
secretReference: options.secretReference
|
|
2133
|
+
});
|
|
2134
|
+
const packagedRelease = {
|
|
2135
|
+
...release.metadata,
|
|
2136
|
+
computeMode: "ecs",
|
|
2137
|
+
image: imageReference,
|
|
2138
|
+
artifactKey: `ecs/${sha256(JSON.stringify({ ...release.metadata, image: imageReference }))}`
|
|
2139
|
+
};
|
|
2140
|
+
await writeFile2(join2(outputDir, "release.json"), `${JSON.stringify(packagedRelease, null, 2)}
|
|
2141
|
+
`);
|
|
2142
|
+
await writeFile2(
|
|
2143
|
+
join2(outputDir, "template.yml"),
|
|
2144
|
+
deploymentTemplate(await cloudFormationTemplate("ecs"), {
|
|
2145
|
+
ComputeMode: "ecs",
|
|
2146
|
+
Image: imageReference,
|
|
2147
|
+
DashboardVersion: packagedRelease.dashboardVersion
|
|
2148
|
+
})
|
|
2149
|
+
);
|
|
2150
|
+
await writeFile2(
|
|
2151
|
+
join2(outputDir, "SHA256SUMS"),
|
|
2152
|
+
`${sha256(await readFile3(join2(outputDir, "release.json")))} release.json
|
|
2153
|
+
`
|
|
2154
|
+
);
|
|
2155
|
+
return packagedRelease;
|
|
2156
|
+
}
|
|
2055
2157
|
async function publishClientAssets(options) {
|
|
2056
2158
|
const assetsDir = resolve2(options.assetsDir);
|
|
2057
2159
|
await readFile3(join2(assetsDir, "index.html"));
|
|
@@ -2076,8 +2178,13 @@ async function publishClientAssets(options) {
|
|
|
2076
2178
|
]);
|
|
2077
2179
|
return assetPath;
|
|
2078
2180
|
}
|
|
2079
|
-
async function cloudFormationTemplate() {
|
|
2080
|
-
return readFile3(
|
|
2181
|
+
async function cloudFormationTemplate(mode = "lambda") {
|
|
2182
|
+
return readFile3(
|
|
2183
|
+
fileURLToPath2(
|
|
2184
|
+
new URL(mode === "ecs" ? "../template-ecs.yml" : "../template.yml", import.meta.url)
|
|
2185
|
+
),
|
|
2186
|
+
"utf8"
|
|
2187
|
+
);
|
|
2081
2188
|
}
|
|
2082
2189
|
export {
|
|
2083
2190
|
bootstrapConsistency,
|
|
@@ -2091,14 +2198,18 @@ export {
|
|
|
2091
2198
|
bootstrapTemplateRevision,
|
|
2092
2199
|
checkBootstrap,
|
|
2093
2200
|
cloudFormationTemplate,
|
|
2201
|
+
computeMode,
|
|
2094
2202
|
coreBootstrapOutputs,
|
|
2095
2203
|
deployedBootstrapStack,
|
|
2096
2204
|
formatBootstrapCheckText,
|
|
2097
2205
|
githubOidcProvider,
|
|
2098
2206
|
mergeBootstrapParameters,
|
|
2207
|
+
packageEcs,
|
|
2099
2208
|
packageLambda,
|
|
2100
2209
|
publishClientAssets,
|
|
2210
|
+
requireComputeMode,
|
|
2101
2211
|
requiredBootstrapParameters,
|
|
2212
|
+
resolveComputeMode,
|
|
2102
2213
|
scaffoldBootstrapManifest,
|
|
2103
2214
|
verifyBootstrap
|
|
2104
2215
|
};
|
package/dist/lambda.mjs
CHANGED
|
@@ -63988,6 +63988,12 @@ var panelSchema = external_exports.looseObject({
|
|
|
63988
63988
|
/** Advisory in v1 — a panel without a position renders in config order rather than not at all. */
|
|
63989
63989
|
position: positionSchema.optional(),
|
|
63990
63990
|
refresh: durationSchema.optional(),
|
|
63991
|
+
/** Active pipeline runs are checked more often than normal panels. */
|
|
63992
|
+
running_refresh: durationSchema.optional(),
|
|
63993
|
+
/** Polling cadence around the estimated completion boundary. */
|
|
63994
|
+
running_completion_refresh: durationSchema.optional(),
|
|
63995
|
+
/** Maximum length of the tighter completion polling burst. */
|
|
63996
|
+
running_completion_window: durationSchema.optional(),
|
|
63991
63997
|
/** Optional active-run treatment. Omitted selects a visible treatment at random. */
|
|
63992
63998
|
running_animation: runningAnimationSchema.optional(),
|
|
63993
63999
|
/** Local animation-demo cycle duration; ignored by other panel types. */
|
|
@@ -64008,6 +64014,9 @@ var sourceSchema = external_exports.looseObject({
|
|
|
64008
64014
|
});
|
|
64009
64015
|
var boardSchema = external_exports.object({
|
|
64010
64016
|
refresh: durationSchema.optional(),
|
|
64017
|
+
running_refresh: durationSchema.optional(),
|
|
64018
|
+
running_completion_refresh: durationSchema.optional(),
|
|
64019
|
+
running_completion_window: durationSchema.optional(),
|
|
64011
64020
|
panels: external_exports.array(panelSchema).min(1).superRefine((panels, ctx) => {
|
|
64012
64021
|
const seen = /* @__PURE__ */ new Map();
|
|
64013
64022
|
panels.forEach((panel, index) => {
|
|
@@ -67150,8 +67159,10 @@ function isLocalHost(host) {
|
|
|
67150
67159
|
async function startup(options = {}) {
|
|
67151
67160
|
const config2 = loadConfig2();
|
|
67152
67161
|
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
67153
|
-
await
|
|
67154
|
-
|
|
67162
|
+
const [, boardConfig] = await Promise.all([
|
|
67163
|
+
waitForTemplate(config2, fetcher),
|
|
67164
|
+
loadBoardConfig(config2.boardConfigUrl, fetcher)
|
|
67165
|
+
]);
|
|
67155
67166
|
const board = selectBoard(config2.board, boardConfig);
|
|
67156
67167
|
const resolvedConfig = { ...config2, board };
|
|
67157
67168
|
const credentialNames = Object.values(boardConfig.sources).map((source) => source.token_env).filter((name) => Boolean(name));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@continuous-excellence/ze-great-dashboard-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AWS Lambda and CloudFormation adapter for Ze Great Dashboard.",
|
|
6
6
|
"keywords": [
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"client",
|
|
32
32
|
"bootstrap",
|
|
33
33
|
"template.yml",
|
|
34
|
+
"template-ecs.yml",
|
|
34
35
|
"LICENSE"
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
package/template-ecs.yml
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
AWSTemplateFormatVersion: '2010-09-09'
|
|
2
|
+
Description: Ze Great Dashboard ECS/Fargate application (consumer-owned network and ingress)
|
|
3
|
+
|
|
4
|
+
Metadata:
|
|
5
|
+
PackageManagedParameters: [ComputeMode, Image, DashboardVersion]
|
|
6
|
+
|
|
7
|
+
Parameters:
|
|
8
|
+
ComputeMode: { Type: String, Default: ecs, AllowedValues: [ecs] }
|
|
9
|
+
Image: { Type: String }
|
|
10
|
+
DashboardVersion: { Type: String }
|
|
11
|
+
Name: { Type: String, Default: dashboard }
|
|
12
|
+
AssetBaseUrl: { Type: String, Default: https://public-assets.zegreatrob.com }
|
|
13
|
+
BoardConfigPath: { Type: String, Default: /app/boards/example.yaml }
|
|
14
|
+
Subnets: { Type: List<AWS::EC2::Subnet::Id> }
|
|
15
|
+
SecurityGroups: { Type: List<AWS::EC2::SecurityGroup::Id> }
|
|
16
|
+
DesiredCount: { Type: Number, Default: 1, MinValue: 1 }
|
|
17
|
+
Cpu: { Type: String, Default: '256' }
|
|
18
|
+
Memory: { Type: String, Default: '512' }
|
|
19
|
+
ContainerPort: { Type: Number, Default: 3000 }
|
|
20
|
+
LogRetentionInDays: { Type: Number, Default: 14, MinValue: 1 }
|
|
21
|
+
SecretReference: { Type: String, Default: '' }
|
|
22
|
+
|
|
23
|
+
Conditions:
|
|
24
|
+
HasSecretReference: !Not [!Equals [!Ref SecretReference, '']]
|
|
25
|
+
|
|
26
|
+
Resources:
|
|
27
|
+
Cluster:
|
|
28
|
+
Type: AWS::ECS::Cluster
|
|
29
|
+
Properties: { ClusterName: !Ref Name }
|
|
30
|
+
LogGroup:
|
|
31
|
+
Type: AWS::Logs::LogGroup
|
|
32
|
+
Properties: { LogGroupName: !Sub '/aws/ecs/${Name}', RetentionInDays: !Ref LogRetentionInDays }
|
|
33
|
+
ExecutionRole:
|
|
34
|
+
Type: AWS::IAM::Role
|
|
35
|
+
Properties:
|
|
36
|
+
RoleName: !Sub '${Name}-execution'
|
|
37
|
+
AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [{ Effect: Allow, Principal: { Service: ecs-tasks.amazonaws.com }, Action: sts:AssumeRole }] }
|
|
38
|
+
ManagedPolicyArns: [arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy]
|
|
39
|
+
TaskRole:
|
|
40
|
+
Type: AWS::IAM::Role
|
|
41
|
+
Properties:
|
|
42
|
+
RoleName: !Sub '${Name}-task'
|
|
43
|
+
AssumeRolePolicyDocument: { Version: '2012-10-17', Statement: [{ Effect: Allow, Principal: { Service: ecs-tasks.amazonaws.com }, Action: sts:AssumeRole }] }
|
|
44
|
+
Policies:
|
|
45
|
+
- PolicyName: read-consumer-credentials
|
|
46
|
+
PolicyDocument:
|
|
47
|
+
Version: '2012-10-17'
|
|
48
|
+
Statement:
|
|
49
|
+
- Effect: Allow
|
|
50
|
+
Action: [secretsmanager:DescribeSecret, secretsmanager:GetSecretValue]
|
|
51
|
+
Resource: !If [HasSecretReference, !Ref SecretReference, !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:ze-great-dashboard-no-secret-configured']
|
|
52
|
+
- Effect: Allow
|
|
53
|
+
Action: ssm:GetParameter
|
|
54
|
+
Resource: !If [HasSecretReference, !Ref SecretReference, !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/ze-great-dashboard-no-credential-configured']
|
|
55
|
+
- Effect: Allow
|
|
56
|
+
Action: kms:Decrypt
|
|
57
|
+
Resource: '*'
|
|
58
|
+
Condition:
|
|
59
|
+
StringEquals:
|
|
60
|
+
'kms:ViaService': !Sub 'ssm.${AWS::Region}.amazonaws.com'
|
|
61
|
+
'kms:EncryptionContext:PARAMETER_ARN': !Ref SecretReference
|
|
62
|
+
TaskDefinition:
|
|
63
|
+
Type: AWS::ECS::TaskDefinition
|
|
64
|
+
Properties:
|
|
65
|
+
Family: !Ref Name
|
|
66
|
+
Cpu: !Ref Cpu
|
|
67
|
+
Memory: !Ref Memory
|
|
68
|
+
NetworkMode: awsvpc
|
|
69
|
+
RequiresCompatibilities: [FARGATE]
|
|
70
|
+
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
|
|
71
|
+
TaskRoleArn: !GetAtt TaskRole.Arn
|
|
72
|
+
ContainerDefinitions:
|
|
73
|
+
- Name: dashboard
|
|
74
|
+
Image: !Ref Image
|
|
75
|
+
Essential: true
|
|
76
|
+
PortMappings: [{ ContainerPort: !Ref ContainerPort, Protocol: tcp }]
|
|
77
|
+
Environment:
|
|
78
|
+
- { Name: ASSET_PATH, Value: !Sub '${AssetBaseUrl}/dashboard/${DashboardVersion}' }
|
|
79
|
+
- { Name: BOARD_CONFIG_URL, Value: !Ref BoardConfigPath }
|
|
80
|
+
- { Name: HOST, Value: 0.0.0.0 }
|
|
81
|
+
- { Name: PORT, Value: '3000' }
|
|
82
|
+
- { Name: SECRET_REFERENCE, Value: !If [HasSecretReference, !Ref SecretReference, ''] }
|
|
83
|
+
LogConfiguration: { LogDriver: awslogs, Options: { awslogs-group: !Ref LogGroup, awslogs-region: !Ref AWS::Region, awslogs-stream-prefix: dashboard } }
|
|
84
|
+
HealthCheck: { Command: [CMD-SHELL, 'node /app/docker-healthcheck.mjs'], Interval: 30, Timeout: 5, Retries: 3, StartPeriod: 20 }
|
|
85
|
+
Service:
|
|
86
|
+
Type: AWS::ECS::Service
|
|
87
|
+
DependsOn: LogGroup
|
|
88
|
+
Properties:
|
|
89
|
+
Cluster: !Ref Cluster
|
|
90
|
+
ServiceName: !Ref Name
|
|
91
|
+
DesiredCount: !Ref DesiredCount
|
|
92
|
+
LaunchType: FARGATE
|
|
93
|
+
TaskDefinition: !Ref TaskDefinition
|
|
94
|
+
NetworkConfiguration: { AwsvpcConfiguration: { AssignPublicIp: DISABLED, Subnets: !Ref Subnets, SecurityGroups: !Ref SecurityGroups } }
|
|
95
|
+
|
|
96
|
+
Outputs:
|
|
97
|
+
ComputeMode: { Value: !Ref ComputeMode }
|
|
98
|
+
ServiceName: { Value: !Ref Service }
|
|
99
|
+
ClusterName: { Value: !Ref Cluster }
|
|
100
|
+
Image: { Value: !Ref Image }
|
|
101
|
+
AssetPath: { Value: !Sub '${AssetBaseUrl}/dashboard/${DashboardVersion}' }
|
package/template.yml
CHANGED
|
@@ -2,9 +2,10 @@ AWSTemplateFormatVersion: '2010-09-09'
|
|
|
2
2
|
Description: Ze Great Dashboard private Lambda application (integrate through a customer-managed gateway)
|
|
3
3
|
|
|
4
4
|
Metadata:
|
|
5
|
-
PackageManagedParameters: [LambdaArtifactKey, DashboardVersion]
|
|
5
|
+
PackageManagedParameters: [ComputeMode, LambdaArtifactKey, DashboardVersion]
|
|
6
6
|
|
|
7
7
|
Parameters:
|
|
8
|
+
ComputeMode: { Type: String, Default: lambda, AllowedValues: [lambda] }
|
|
8
9
|
Name: { Type: String, Default: dashboard }
|
|
9
10
|
LambdaArtifactBucket: { Type: String }
|
|
10
11
|
LambdaArtifactKey: { Type: String }
|