@aws/nx-plugin 1.1.1 → 1.2.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 +30 -29
- package/generators.json +8 -0
- package/migrations.json +1 -1
- package/package.json +1 -1
- package/src/agentcore-harness/generator.d.ts +1 -1
- package/src/greengrass/component/common.d.ts +60 -0
- package/src/greengrass/component/common.js +191 -0
- package/src/greengrass/component/common.js.map +1 -0
- package/src/greengrass/component/dependencies.d.ts +101 -0
- package/src/greengrass/component/dependencies.js +37 -0
- package/src/greengrass/component/dependencies.js.map +1 -0
- package/src/greengrass/component/generator.d.ts +174 -0
- package/src/greengrass/component/generator.js +32 -0
- package/src/greengrass/component/generator.js.map +1 -0
- package/src/greengrass/component/metadata.d.ts +2 -0
- package/src/greengrass/component/metadata.js +14 -0
- package/src/greengrass/component/metadata.js.map +1 -0
- package/src/greengrass/component/python/__snapshots__/generator.spec.ts.snap +450 -0
- package/src/greengrass/component/python/files/component/main.py.template +60 -0
- package/src/greengrass/component/python/files/component/recipe.yaml.template +30 -0
- package/src/greengrass/component/python/files/gdk/gdk-config.json.template +13 -0
- package/src/greengrass/component/python/files/tests/test___componentSnakeCase__.py.template +37 -0
- package/src/greengrass/component/python/generator.d.ts +15 -0
- package/src/greengrass/component/python/generator.js +192 -0
- package/src/greengrass/component/python/generator.js.map +1 -0
- package/src/greengrass/component/schema.d.js +6 -0
- package/src/greengrass/component/schema.d.js.map +1 -0
- package/src/greengrass/component/schema.d.ts +18 -0
- package/src/greengrass/component/schema.json +82 -0
- package/src/greengrass/component/typescript/__snapshots__/generator.spec.ts.snap +275 -0
- package/src/greengrass/component/typescript/files/component/recipe.yaml.template +41 -0
- package/src/greengrass/component/typescript/files/entrypoint/main.ts.template +58 -0
- package/src/greengrass/component/typescript/files/gdk/gdk-config.json.template +13 -0
- package/src/greengrass/component/typescript/files/scripts/vendor-crt-addon.ts.template +115 -0
- package/src/greengrass/component/typescript/generator.d.ts +16 -0
- package/src/greengrass/component/typescript/generator.js +194 -0
- package/src/greengrass/component/typescript/generator.js.map +1 -0
- package/src/infra/app/generator.d.ts +1 -1
- package/src/init/generator.d.ts +1 -1
- package/src/internal/test-matrix/generator.js +42 -0
- package/src/internal/test-matrix/generator.js.map +1 -1
- package/src/license/dependency-check/pre-approved.js +2 -1
- package/src/license/dependency-check/pre-approved.js.map +1 -1
- package/src/open-api/py-client/generator.d.ts +1 -1
- package/src/preset/__snapshots__/generator.spec.ts.snap +2 -0
- package/src/preset/generator.d.ts +1 -1
- package/src/py/agent/a2a-connection/generator.d.ts +1 -1
- package/src/py/agent/gateway-connection/generator.d.ts +1 -1
- package/src/py/agent/mcp-connection/generator.d.ts +1 -1
- package/src/py/agent/react-connection/generator.d.ts +1 -1
- package/src/py/fast-api/react/generator.d.ts +1 -1
- package/src/py/project/generator.js +2 -0
- package/src/py/project/generator.js.map +1 -1
- package/src/sdk/greengrass.d.ts +6 -0
- package/src/sdk/greengrass.js +6 -0
- package/src/sdk/greengrass.js.map +1 -0
- package/src/smithy/project/generator.d.ts +1 -1
- package/src/smithy/react-connection/generator.d.ts +1 -1
- package/src/terraform/project/generator.d.ts +1 -1
- package/src/trpc/react/generator.d.ts +1 -1
- package/src/ts/agent/a2a-connection/generator.d.ts +1 -1
- package/src/ts/agent/gateway-connection/generator.d.ts +1 -1
- package/src/ts/agent/mcp-connection/generator.d.ts +1 -1
- package/src/ts/agent/react-connection/generator.d.ts +1 -1
- package/src/ts/astro-docs/generator.d.ts +1 -1
- package/src/ts/dcr-proxy/generator.d.ts +1 -1
- package/src/ts/dynamodb/generator.d.ts +1 -1
- package/src/ts/lambda-function/generator.d.ts +1 -1
- package/src/ts/lib/generator.d.ts +1 -1
- package/src/ts/nx-generator/generator.d.ts +1 -1
- package/src/ts/nx-migration/generator.d.ts +1 -1
- package/src/ts/nx-plugin/generator.d.ts +1 -1
- package/src/ts/rdb/generator.d.ts +1 -1
- package/src/ts/react-website/agui/generator.d.ts +1 -1
- package/src/utils/__snapshots__/shared-greengrass-scripts.spec.ts.snap +1279 -0
- package/src/utils/files/common/scripts/src/greengrass/build-artifact.ts.template +236 -0
- package/src/utils/files/common/scripts/src/greengrass/component-logs.ts.template +48 -0
- package/src/utils/files/common/scripts/src/greengrass/deploy-local.ts.template +497 -0
- package/src/utils/files/common/scripts/src/greengrass/recipe-utils.ts.template +356 -0
- package/src/utils/files/common/scripts/src/greengrass/zip-writer.ts.template +121 -0
- package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +3 -3
- package/src/utils/files/terraform/src/metrics/metrics.tf.template +1 -1
- package/src/utils/greengrass/constants.d.ts +58 -0
- package/src/utils/greengrass/constants.js +63 -0
- package/src/utils/greengrass/constants.js.map +1 -0
- package/src/utils/greengrass/naming.d.ts +46 -0
- package/src/utils/greengrass/naming.js +67 -0
- package/src/utils/greengrass/naming.js.map +1 -0
- package/src/utils/greengrass/recipe.d.ts +143 -0
- package/src/utils/greengrass/recipe.js +119 -0
- package/src/utils/greengrass/recipe.js.map +1 -0
- package/src/utils/greengrass-constructs/files/cdk/app/greengrass-component/__componentDirName__.ts.template +42 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/artifact-bucket.ts.template +82 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/component-version.ts.template +370 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/deployment.ts.template +276 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/index.ts.template +5 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/next-version.ts.template +119 -0
- package/src/utils/greengrass-constructs/files/cdk/core/greengrass/recipe.ts.template +90 -0
- package/src/utils/greengrass-constructs/files/next-version/__nextVersionFileName__.template +195 -0
- package/src/utils/greengrass-constructs/files/terraform/app/greengrass-component/__componentDirName__/__componentDirName__.tf.template +26 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/artifact-bucket/main.tf.template +204 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/component-version/main.tf.template +156 -0
- package/src/utils/greengrass-constructs/files/terraform/core/greengrass/deployment/main.tf.template +186 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.d.ts +57 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.js +154 -0
- package/src/utils/greengrass-constructs/greengrass-constructs.js.map +1 -0
- package/src/utils/nx.d.ts +15 -0
- package/src/utils/nx.js +32 -0
- package/src/utils/nx.js.map +1 -1
- package/src/utils/shared-greengrass-scripts.d.ts +21 -0
- package/src/utils/shared-greengrass-scripts.js +43 -0
- package/src/utils/shared-greengrass-scripts.js.map +1 -0
- package/src/utils/versions.d.ts +7 -0
- package/src/utils/versions.js +7 -0
- package/src/utils/versions.js.map +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { pascalCase } from "../names.js";
|
|
5
|
+
import { getNpmScope } from "../npm-scope.js";
|
|
6
|
+
/**
|
|
7
|
+
* Characters a Greengrass v2 `ComponentName` may contain. Matches the
|
|
8
|
+
* `CreateComponentVersion` API's own constraint.
|
|
9
|
+
*/ export const COMPONENT_NAME_PATTERN = /^[a-zA-Z0-9-_.]+$/;
|
|
10
|
+
/** Longest `ComponentName` the `CreateComponentVersion` API accepts. */ export const MAX_COMPONENT_NAME_LENGTH = 128;
|
|
11
|
+
/** Longest name that fits the CDK artifact prefix with its 32-character hash. */ export const MAX_CDK_COMPONENT_NAME_LENGTH = 68;
|
|
12
|
+
/**
|
|
13
|
+
* Prefix AWS reserves for its own public components. A user-authored
|
|
14
|
+
* `ComponentName` starting with this is rejected by the service, so the
|
|
15
|
+
* plugin rejects it up front with an actionable message instead.
|
|
16
|
+
*/ export const RESERVED_COMPONENT_NAME_PREFIX = 'aws.greengrass.';
|
|
17
|
+
/**
|
|
18
|
+
* Validate a `ComponentName` against the constraints the service enforces:
|
|
19
|
+
* the allowed character set, the length limit, and the reserved
|
|
20
|
+
* `aws.greengrass.` prefix. Returns one issue per violated constraint, empty
|
|
21
|
+
* when the name is valid.
|
|
22
|
+
*/ export const validateComponentName = (componentName)=>{
|
|
23
|
+
const issues = [];
|
|
24
|
+
if (!COMPONENT_NAME_PATTERN.test(componentName)) {
|
|
25
|
+
issues.push({
|
|
26
|
+
reason: 'invalid-characters',
|
|
27
|
+
message: `ComponentName "${componentName}" must match ${COMPONENT_NAME_PATTERN} (letters, digits, hyphens, underscores and dots only).`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (componentName.length > MAX_COMPONENT_NAME_LENGTH) {
|
|
31
|
+
issues.push({
|
|
32
|
+
reason: 'too-long',
|
|
33
|
+
message: `ComponentName "${componentName}" is ${componentName.length} characters long, exceeding the ${MAX_COMPONENT_NAME_LENGTH} character limit.`
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (componentName.startsWith(RESERVED_COMPONENT_NAME_PREFIX)) {
|
|
37
|
+
issues.push({
|
|
38
|
+
reason: 'reserved-prefix',
|
|
39
|
+
message: `ComponentName "${componentName}" uses the "${RESERVED_COMPONENT_NAME_PREFIX}" prefix, which AWS reserves for its own components.`
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return issues;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Validate a `ComponentName`, throwing an actionable error naming every
|
|
46
|
+
* violated constraint when it is invalid.
|
|
47
|
+
*/ export const assertValidComponentName = (componentName)=>{
|
|
48
|
+
const issues = validateComponentName(componentName);
|
|
49
|
+
if (issues.length > 0) {
|
|
50
|
+
throw new Error(issues.map((issue)=>issue.message).join(' '));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Reduce an npm scope to the character set {@link COMPONENT_NAME_PATTERN}
|
|
55
|
+
* allows: lower-cased, any other character collapsed to a single `-`, and
|
|
56
|
+
* leading/trailing separators trimmed so the result never starts or ends a
|
|
57
|
+
* `ComponentName` segment with a stray punctuation mark.
|
|
58
|
+
*/ const sanitizeForComponentName = (segment)=>segment.toLowerCase().replace(/[^a-z0-9-_.]+/g, '-').replace(/^[-_.]+|[-_.]+$/g, '');
|
|
59
|
+
/**
|
|
60
|
+
* Build the default `ComponentName` for a new component: `com.<sanitized
|
|
61
|
+
* workspace npm scope>.<PascalCaseName>`, the reversed-domain style the
|
|
62
|
+
* service's own samples use. Reflects the workspace's npm scope at
|
|
63
|
+
* generation time; re-scoping the workspace afterwards does not rename
|
|
64
|
+
* already-generated components.
|
|
65
|
+
*/ export const buildDefaultComponentName = (tree, name)=>`com.${sanitizeForComponentName(getNpmScope(tree) ?? 'monorepo')}.${pascalCase(name)}`;
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/greengrass/naming.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { Tree } from '@nx/devkit';\nimport { pascalCase } from '../names.js';\nimport { getNpmScope } from '../npm-scope.js';\n\n/**\n * Characters a Greengrass v2 `ComponentName` may contain. Matches the\n * `CreateComponentVersion` API's own constraint.\n */\nexport const COMPONENT_NAME_PATTERN = /^[a-zA-Z0-9-_.]+$/;\n\n/** Longest `ComponentName` the `CreateComponentVersion` API accepts. */\nexport const MAX_COMPONENT_NAME_LENGTH = 128;\n\n/** Longest name that fits the CDK artifact prefix with its 32-character hash. */\nexport const MAX_CDK_COMPONENT_NAME_LENGTH = 68;\n\n/**\n * Prefix AWS reserves for its own public components. A user-authored\n * `ComponentName` starting with this is rejected by the service, so the\n * plugin rejects it up front with an actionable message instead.\n */\nexport const RESERVED_COMPONENT_NAME_PREFIX = 'aws.greengrass.';\n\n/** Why a `ComponentName` failed validation. */\nexport type ComponentNameIssueReason =\n | 'invalid-characters'\n | 'too-long'\n | 'reserved-prefix';\n\nexport interface ComponentNameIssue {\n readonly reason: ComponentNameIssueReason;\n readonly message: string;\n}\n\n/**\n * Validate a `ComponentName` against the constraints the service enforces:\n * the allowed character set, the length limit, and the reserved\n * `aws.greengrass.` prefix. Returns one issue per violated constraint, empty\n * when the name is valid.\n */\nexport const validateComponentName = (\n componentName: string,\n): ComponentNameIssue[] => {\n const issues: ComponentNameIssue[] = [];\n\n if (!COMPONENT_NAME_PATTERN.test(componentName)) {\n issues.push({\n reason: 'invalid-characters',\n message: `ComponentName \"${componentName}\" must match ${COMPONENT_NAME_PATTERN} (letters, digits, hyphens, underscores and dots only).`,\n });\n }\n\n if (componentName.length > MAX_COMPONENT_NAME_LENGTH) {\n issues.push({\n reason: 'too-long',\n message: `ComponentName \"${componentName}\" is ${componentName.length} characters long, exceeding the ${MAX_COMPONENT_NAME_LENGTH} character limit.`,\n });\n }\n\n if (componentName.startsWith(RESERVED_COMPONENT_NAME_PREFIX)) {\n issues.push({\n reason: 'reserved-prefix',\n message: `ComponentName \"${componentName}\" uses the \"${RESERVED_COMPONENT_NAME_PREFIX}\" prefix, which AWS reserves for its own components.`,\n });\n }\n\n return issues;\n};\n\n/**\n * Validate a `ComponentName`, throwing an actionable error naming every\n * violated constraint when it is invalid.\n */\nexport const assertValidComponentName = (componentName: string): void => {\n const issues = validateComponentName(componentName);\n if (issues.length > 0) {\n throw new Error(issues.map((issue) => issue.message).join(' '));\n }\n};\n\n/**\n * Reduce an npm scope to the character set {@link COMPONENT_NAME_PATTERN}\n * allows: lower-cased, any other character collapsed to a single `-`, and\n * leading/trailing separators trimmed so the result never starts or ends a\n * `ComponentName` segment with a stray punctuation mark.\n */\nconst sanitizeForComponentName = (segment: string): string =>\n segment\n .toLowerCase()\n .replace(/[^a-z0-9-_.]+/g, '-')\n .replace(/^[-_.]+|[-_.]+$/g, '');\n\n/**\n * Build the default `ComponentName` for a new component: `com.<sanitized\n * workspace npm scope>.<PascalCaseName>`, the reversed-domain style the\n * service's own samples use. Reflects the workspace's npm scope at\n * generation time; re-scoping the workspace afterwards does not rename\n * already-generated components.\n */\nexport const buildDefaultComponentName = (tree: Tree, name: string): string =>\n `com.${sanitizeForComponentName(getNpmScope(tree) ?? 'monorepo')}.${pascalCase(name)}`;\n"],"names":["pascalCase","getNpmScope","COMPONENT_NAME_PATTERN","MAX_COMPONENT_NAME_LENGTH","MAX_CDK_COMPONENT_NAME_LENGTH","RESERVED_COMPONENT_NAME_PREFIX","validateComponentName","componentName","issues","test","push","reason","message","length","startsWith","assertValidComponentName","Error","map","issue","join","sanitizeForComponentName","segment","toLowerCase","replace","buildDefaultComponentName","tree","name"],"mappings":"AAAA;;;CAGC,GAED,SAASA,UAAU,QAAQ,cAAc;AACzC,SAASC,WAAW,QAAQ,kBAAkB;AAE9C;;;CAGC,GACD,OAAO,MAAMC,yBAAyB,oBAAoB;AAE1D,sEAAsE,GACtE,OAAO,MAAMC,4BAA4B,IAAI;AAE7C,+EAA+E,GAC/E,OAAO,MAAMC,gCAAgC,GAAG;AAEhD;;;;CAIC,GACD,OAAO,MAAMC,iCAAiC,kBAAkB;AAahE;;;;;CAKC,GACD,OAAO,MAAMC,wBAAwB,CACnCC;IAEA,MAAMC,SAA+B,EAAE;IAEvC,IAAI,CAACN,uBAAuBO,IAAI,CAACF,gBAAgB;QAC/CC,OAAOE,IAAI,CAAC;YACVC,QAAQ;YACRC,SAAS,CAAC,eAAe,EAAEL,cAAc,aAAa,EAAEL,uBAAuB,uDAAuD,CAAC;QACzI;IACF;IAEA,IAAIK,cAAcM,MAAM,GAAGV,2BAA2B;QACpDK,OAAOE,IAAI,CAAC;YACVC,QAAQ;YACRC,SAAS,CAAC,eAAe,EAAEL,cAAc,KAAK,EAAEA,cAAcM,MAAM,CAAC,gCAAgC,EAAEV,0BAA0B,iBAAiB,CAAC;QACrJ;IACF;IAEA,IAAII,cAAcO,UAAU,CAACT,iCAAiC;QAC5DG,OAAOE,IAAI,CAAC;YACVC,QAAQ;YACRC,SAAS,CAAC,eAAe,EAAEL,cAAc,YAAY,EAAEF,+BAA+B,oDAAoD,CAAC;QAC7I;IACF;IAEA,OAAOG;AACT,EAAE;AAEF;;;CAGC,GACD,OAAO,MAAMO,2BAA2B,CAACR;IACvC,MAAMC,SAASF,sBAAsBC;IACrC,IAAIC,OAAOK,MAAM,GAAG,GAAG;QACrB,MAAM,IAAIG,MAAMR,OAAOS,GAAG,CAAC,CAACC,QAAUA,MAAMN,OAAO,EAAEO,IAAI,CAAC;IAC5D;AACF,EAAE;AAEF;;;;;CAKC,GACD,MAAMC,2BAA2B,CAACC,UAChCA,QACGC,WAAW,GACXC,OAAO,CAAC,kBAAkB,KAC1BA,OAAO,CAAC,oBAAoB;AAEjC;;;;;;CAMC,GACD,OAAO,MAAMC,4BAA4B,CAACC,MAAYC,OACpD,CAAC,IAAI,EAAEN,yBAAyBnB,YAAYwB,SAAS,YAAY,CAAC,EAAEzB,WAAW0B,OAAO,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const NEXT_VERSION_SENTINELS: readonly ["NEXT_PATCH", "NEXT_MINOR", "NEXT_MAJOR"];
|
|
3
|
+
export type NextVersionSentinel = (typeof NEXT_VERSION_SENTINELS)[number];
|
|
4
|
+
/** Whether a value requests deploy-time component version resolution. */
|
|
5
|
+
export declare const isNextVersionSentinel: (value: string) => value is NextVersionSentinel;
|
|
6
|
+
/** Maps a version sentinel to its semantic-version bump strategy. */
|
|
7
|
+
export declare const nextVersionStrategy: (sentinel: NextVersionSentinel) => "patch" | "minor" | "major";
|
|
8
|
+
/** Whether a value is a semantic version or a deploy-time version sentinel. */
|
|
9
|
+
export declare const isValidComponentVersion: (value: string) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Typed model of the Greengrass v2 recipe format (`RecipeFormatVersion`
|
|
12
|
+
* `2020-01-25`) and a zod schema over it.
|
|
13
|
+
*
|
|
14
|
+
* `recipe.yaml` is user-owned (vended once with `OverwriteStrategy.KeepExisting`,
|
|
15
|
+
* never rewritten wholesale), so validation covers ONLY the fields the plugin
|
|
16
|
+
* itself reads or rewrites: `ComponentName`, `ComponentVersion`, and the
|
|
17
|
+
* manifest/artifact/lifecycle paths a future build step touches. Every schema
|
|
18
|
+
* below is a loose object: fields the plugin does not model pass through
|
|
19
|
+
* `parseRecipe`/`validateRecipe` untouched rather than being stripped or
|
|
20
|
+
* rejected. There is deliberately no whole-document string substitution
|
|
21
|
+
* anywhere in this module; a caller that needs to rewrite a value does so on
|
|
22
|
+
* the parsed object at its specific path (eg. `raw.Manifests[0].Artifacts[0].Uri`)
|
|
23
|
+
* and re-serializes with `yaml.dump`.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* A component lifecycle step's arguments. Step names (`Run`, `Install`,
|
|
27
|
+
* `Startup`, `Shutdown`, `Bootstrap`, `Recover`, ...) and each step's shape
|
|
28
|
+
* (a bare script string, an object with `script`/`requiresPrivilege`/`setEnv`/
|
|
29
|
+
* `timeout`, or a per-platform selection map) are entirely user-authored and
|
|
30
|
+
* not modeled field-by-field here, so an unrecognised step or key is
|
|
31
|
+
* preserved rather than rejected.
|
|
32
|
+
*/
|
|
33
|
+
export type Lifecycle = Record<string, unknown>;
|
|
34
|
+
/** `Manifests[].Platform`. */
|
|
35
|
+
export interface RecipePlatform {
|
|
36
|
+
readonly os?: string;
|
|
37
|
+
readonly architecture?: string;
|
|
38
|
+
readonly [key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
/** `Manifests[].Artifacts[]`. */
|
|
41
|
+
export interface RecipeArtifact {
|
|
42
|
+
readonly Uri: string;
|
|
43
|
+
readonly [key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
/** `Manifests[]`. */
|
|
46
|
+
export interface RecipeManifest {
|
|
47
|
+
readonly Platform?: RecipePlatform;
|
|
48
|
+
readonly Name?: string;
|
|
49
|
+
readonly Lifecycle?: Lifecycle;
|
|
50
|
+
readonly Artifacts?: readonly RecipeArtifact[];
|
|
51
|
+
readonly Selections?: readonly string[];
|
|
52
|
+
readonly [key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
/** `ComponentConfiguration`. */
|
|
55
|
+
export interface ComponentConfiguration {
|
|
56
|
+
readonly DefaultConfiguration?: Record<string, unknown>;
|
|
57
|
+
readonly [key: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
export type ComponentDependencyType = 'HARD' | 'SOFT';
|
|
60
|
+
/** A single entry of `ComponentDependencies`. */
|
|
61
|
+
export interface ComponentDependencyRequirement {
|
|
62
|
+
readonly VersionRequirement?: string;
|
|
63
|
+
readonly DependencyType?: ComponentDependencyType;
|
|
64
|
+
readonly [key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
/** `ComponentDependencies`, keyed by the dependency's `ComponentName`. */
|
|
67
|
+
export type ComponentDependencies = Record<string, ComponentDependencyRequirement>;
|
|
68
|
+
/** The full Greengrass v2 recipe document. */
|
|
69
|
+
export interface GreengrassRecipe {
|
|
70
|
+
readonly RecipeFormatVersion: string;
|
|
71
|
+
readonly ComponentName: string;
|
|
72
|
+
readonly ComponentVersion: string;
|
|
73
|
+
readonly ComponentDescription?: string;
|
|
74
|
+
readonly ComponentPublisher?: string;
|
|
75
|
+
readonly ComponentConfiguration?: ComponentConfiguration;
|
|
76
|
+
readonly ComponentDependencies?: ComponentDependencies;
|
|
77
|
+
readonly Manifests: readonly RecipeManifest[];
|
|
78
|
+
readonly [key: string]: unknown;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Schema for the fields the plugin consumes. `.looseObject` at every level
|
|
82
|
+
* means an unrecognised top-level key, manifest key or lifecycle key parses
|
|
83
|
+
* through unchanged rather than being stripped; `ComponentName` and
|
|
84
|
+
* `ComponentVersion` are the only fields checked against a service
|
|
85
|
+
* constraint, via `superRefine` so both issues surface together when a
|
|
86
|
+
* recipe violates both.
|
|
87
|
+
*/
|
|
88
|
+
export declare const GreengrassRecipeSchema: z.ZodObject<{
|
|
89
|
+
RecipeFormatVersion: z.ZodString;
|
|
90
|
+
ComponentName: z.ZodString;
|
|
91
|
+
ComponentVersion: z.ZodString;
|
|
92
|
+
ComponentDescription: z.ZodOptional<z.ZodString>;
|
|
93
|
+
ComponentPublisher: z.ZodOptional<z.ZodString>;
|
|
94
|
+
ComponentConfiguration: z.ZodOptional<z.ZodObject<{
|
|
95
|
+
DefaultConfiguration: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
96
|
+
}, z.core.$loose>>;
|
|
97
|
+
ComponentDependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
98
|
+
VersionRequirement: z.ZodOptional<z.ZodString>;
|
|
99
|
+
DependencyType: z.ZodOptional<z.ZodEnum<{
|
|
100
|
+
HARD: "HARD";
|
|
101
|
+
SOFT: "SOFT";
|
|
102
|
+
}>>;
|
|
103
|
+
}, z.core.$loose>>>;
|
|
104
|
+
Manifests: z.ZodArray<z.ZodObject<{
|
|
105
|
+
Platform: z.ZodOptional<z.ZodObject<{
|
|
106
|
+
os: z.ZodOptional<z.ZodString>;
|
|
107
|
+
architecture: z.ZodOptional<z.ZodString>;
|
|
108
|
+
}, z.core.$loose>>;
|
|
109
|
+
Name: z.ZodOptional<z.ZodString>;
|
|
110
|
+
Lifecycle: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
111
|
+
Artifacts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
112
|
+
Uri: z.ZodString;
|
|
113
|
+
}, z.core.$loose>>>;
|
|
114
|
+
Selections: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
115
|
+
}, z.core.$loose>>;
|
|
116
|
+
}, z.core.$loose>;
|
|
117
|
+
/** One validation failure, with the recipe path it applies to. */
|
|
118
|
+
export interface RecipeValidationIssue {
|
|
119
|
+
/** Dot-separated path into the recipe, eg. `Manifests.0.Artifacts.0.Uri`. */
|
|
120
|
+
readonly path: string;
|
|
121
|
+
readonly message: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate an already-parsed recipe object against {@link GreengrassRecipeSchema},
|
|
125
|
+
* returning every violation found rather than throwing on the first. Empty
|
|
126
|
+
* when the recipe is valid.
|
|
127
|
+
*/
|
|
128
|
+
export declare const validateRecipe: (raw: unknown) => RecipeValidationIssue[];
|
|
129
|
+
export interface ParsedRecipe {
|
|
130
|
+
/** The validated, typed view of the recipe. */
|
|
131
|
+
readonly recipe: GreengrassRecipe;
|
|
132
|
+
/** The unvalidated result of parsing the YAML, exactly as loaded. */
|
|
133
|
+
readonly raw: unknown;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parse a recipe YAML document and validate it, throwing when the fields the
|
|
137
|
+
* plugin consumes are invalid. `raw` is the plain object `js-yaml` produced,
|
|
138
|
+
* before validation; a caller that needs to rewrite the recipe (eg. to
|
|
139
|
+
* substitute an artifact URI) mutates `raw` at the specific path and
|
|
140
|
+
* re-serializes it with `yaml.dump`, so fields this module does not model are
|
|
141
|
+
* never disturbed.
|
|
142
|
+
*/
|
|
143
|
+
export declare const parseRecipe: (yamlSource: string) => ParsedRecipe;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import yaml from "js-yaml";
|
|
5
|
+
import { valid as validSemver } from "semver";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { validateComponentName } from "./naming.js";
|
|
8
|
+
export const NEXT_VERSION_SENTINELS = [
|
|
9
|
+
'NEXT_PATCH',
|
|
10
|
+
'NEXT_MINOR',
|
|
11
|
+
'NEXT_MAJOR'
|
|
12
|
+
];
|
|
13
|
+
/** Whether a value requests deploy-time component version resolution. */ export const isNextVersionSentinel = (value)=>NEXT_VERSION_SENTINELS.includes(value);
|
|
14
|
+
/** Maps a version sentinel to its semantic-version bump strategy. */ export const nextVersionStrategy = (sentinel)=>{
|
|
15
|
+
switch(sentinel){
|
|
16
|
+
case 'NEXT_PATCH':
|
|
17
|
+
return 'patch';
|
|
18
|
+
case 'NEXT_MINOR':
|
|
19
|
+
return 'minor';
|
|
20
|
+
case 'NEXT_MAJOR':
|
|
21
|
+
return 'major';
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Whether a value is a semantic version or a deploy-time version sentinel. */ export const isValidComponentVersion = (value)=>Boolean(validSemver(value)) || isNextVersionSentinel(value);
|
|
25
|
+
const RecipePlatformSchema = z.looseObject({
|
|
26
|
+
os: z.string().optional(),
|
|
27
|
+
architecture: z.string().optional()
|
|
28
|
+
});
|
|
29
|
+
const RecipeArtifactSchema = z.looseObject({
|
|
30
|
+
Uri: z.string()
|
|
31
|
+
});
|
|
32
|
+
const LifecycleSchema = z.record(z.string(), z.unknown());
|
|
33
|
+
const RecipeManifestSchema = z.looseObject({
|
|
34
|
+
Platform: RecipePlatformSchema.optional(),
|
|
35
|
+
Name: z.string().optional(),
|
|
36
|
+
Lifecycle: LifecycleSchema.optional(),
|
|
37
|
+
Artifacts: z.array(RecipeArtifactSchema).optional(),
|
|
38
|
+
Selections: z.array(z.string()).optional()
|
|
39
|
+
});
|
|
40
|
+
const ComponentConfigurationSchema = z.looseObject({
|
|
41
|
+
DefaultConfiguration: z.record(z.string(), z.unknown()).optional()
|
|
42
|
+
});
|
|
43
|
+
const ComponentDependencyRequirementSchema = z.looseObject({
|
|
44
|
+
VersionRequirement: z.string().optional(),
|
|
45
|
+
DependencyType: z.enum([
|
|
46
|
+
'HARD',
|
|
47
|
+
'SOFT'
|
|
48
|
+
]).optional()
|
|
49
|
+
});
|
|
50
|
+
const ComponentDependenciesSchema = z.record(z.string(), ComponentDependencyRequirementSchema);
|
|
51
|
+
/**
|
|
52
|
+
* Schema for the fields the plugin consumes. `.looseObject` at every level
|
|
53
|
+
* means an unrecognised top-level key, manifest key or lifecycle key parses
|
|
54
|
+
* through unchanged rather than being stripped; `ComponentName` and
|
|
55
|
+
* `ComponentVersion` are the only fields checked against a service
|
|
56
|
+
* constraint, via `superRefine` so both issues surface together when a
|
|
57
|
+
* recipe violates both.
|
|
58
|
+
*/ export const GreengrassRecipeSchema = z.looseObject({
|
|
59
|
+
RecipeFormatVersion: z.string(),
|
|
60
|
+
ComponentName: z.string(),
|
|
61
|
+
ComponentVersion: z.string(),
|
|
62
|
+
ComponentDescription: z.string().optional(),
|
|
63
|
+
ComponentPublisher: z.string().optional(),
|
|
64
|
+
ComponentConfiguration: ComponentConfigurationSchema.optional(),
|
|
65
|
+
ComponentDependencies: ComponentDependenciesSchema.optional(),
|
|
66
|
+
Manifests: z.array(RecipeManifestSchema)
|
|
67
|
+
}).superRefine((recipe, ctx)=>{
|
|
68
|
+
for (const issue of validateComponentName(recipe.ComponentName)){
|
|
69
|
+
ctx.addIssue({
|
|
70
|
+
code: 'custom',
|
|
71
|
+
message: issue.message,
|
|
72
|
+
path: [
|
|
73
|
+
'ComponentName'
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (!isValidComponentVersion(recipe.ComponentVersion)) {
|
|
78
|
+
ctx.addIssue({
|
|
79
|
+
code: 'custom',
|
|
80
|
+
message: `ComponentVersion "${recipe.ComponentVersion}" must be a valid semantic version (eg. "1.0.0") or one of NEXT_PATCH, NEXT_MINOR, NEXT_MAJOR.`,
|
|
81
|
+
path: [
|
|
82
|
+
'ComponentVersion'
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const toValidationIssues = (error)=>error.issues.map((issue)=>({
|
|
88
|
+
path: issue.path.join('.'),
|
|
89
|
+
message: issue.message
|
|
90
|
+
}));
|
|
91
|
+
/**
|
|
92
|
+
* Validate an already-parsed recipe object against {@link GreengrassRecipeSchema},
|
|
93
|
+
* returning every violation found rather than throwing on the first. Empty
|
|
94
|
+
* when the recipe is valid.
|
|
95
|
+
*/ export const validateRecipe = (raw)=>{
|
|
96
|
+
const result = GreengrassRecipeSchema.safeParse(raw);
|
|
97
|
+
return result.success ? [] : toValidationIssues(result.error);
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Parse a recipe YAML document and validate it, throwing when the fields the
|
|
101
|
+
* plugin consumes are invalid. `raw` is the plain object `js-yaml` produced,
|
|
102
|
+
* before validation; a caller that needs to rewrite the recipe (eg. to
|
|
103
|
+
* substitute an artifact URI) mutates `raw` at the specific path and
|
|
104
|
+
* re-serializes it with `yaml.dump`, so fields this module does not model are
|
|
105
|
+
* never disturbed.
|
|
106
|
+
*/ export const parseRecipe = (yamlSource)=>{
|
|
107
|
+
const raw = yaml.load(yamlSource);
|
|
108
|
+
const result = GreengrassRecipeSchema.safeParse(raw);
|
|
109
|
+
if (!result.success) {
|
|
110
|
+
const issues = toValidationIssues(result.error).map((issue)=>` - ${issue.path || '(root)'}: ${issue.message}`).join('\n');
|
|
111
|
+
throw new Error(`Invalid Greengrass recipe:\n${issues}`);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
recipe: result.data,
|
|
115
|
+
raw
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/utils/greengrass/recipe.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport yaml from 'js-yaml';\nimport { valid as validSemver } from 'semver';\nimport { z } from 'zod';\nimport { validateComponentName } from './naming.js';\n\nexport const NEXT_VERSION_SENTINELS = [\n 'NEXT_PATCH',\n 'NEXT_MINOR',\n 'NEXT_MAJOR',\n] as const;\n\nexport type NextVersionSentinel = (typeof NEXT_VERSION_SENTINELS)[number];\n\n/** Whether a value requests deploy-time component version resolution. */\nexport const isNextVersionSentinel = (\n value: string,\n): value is NextVersionSentinel =>\n NEXT_VERSION_SENTINELS.includes(value as NextVersionSentinel);\n\n/** Maps a version sentinel to its semantic-version bump strategy. */\nexport const nextVersionStrategy = (\n sentinel: NextVersionSentinel,\n): 'patch' | 'minor' | 'major' => {\n switch (sentinel) {\n case 'NEXT_PATCH':\n return 'patch';\n case 'NEXT_MINOR':\n return 'minor';\n case 'NEXT_MAJOR':\n return 'major';\n }\n};\n\n/** Whether a value is a semantic version or a deploy-time version sentinel. */\nexport const isValidComponentVersion = (value: string): boolean =>\n Boolean(validSemver(value)) || isNextVersionSentinel(value);\n\n/**\n * Typed model of the Greengrass v2 recipe format (`RecipeFormatVersion`\n * `2020-01-25`) and a zod schema over it.\n *\n * `recipe.yaml` is user-owned (vended once with `OverwriteStrategy.KeepExisting`,\n * never rewritten wholesale), so validation covers ONLY the fields the plugin\n * itself reads or rewrites: `ComponentName`, `ComponentVersion`, and the\n * manifest/artifact/lifecycle paths a future build step touches. Every schema\n * below is a loose object: fields the plugin does not model pass through\n * `parseRecipe`/`validateRecipe` untouched rather than being stripped or\n * rejected. There is deliberately no whole-document string substitution\n * anywhere in this module; a caller that needs to rewrite a value does so on\n * the parsed object at its specific path (eg. `raw.Manifests[0].Artifacts[0].Uri`)\n * and re-serializes with `yaml.dump`.\n */\n\n/**\n * A component lifecycle step's arguments. Step names (`Run`, `Install`,\n * `Startup`, `Shutdown`, `Bootstrap`, `Recover`, ...) and each step's shape\n * (a bare script string, an object with `script`/`requiresPrivilege`/`setEnv`/\n * `timeout`, or a per-platform selection map) are entirely user-authored and\n * not modeled field-by-field here, so an unrecognised step or key is\n * preserved rather than rejected.\n */\nexport type Lifecycle = Record<string, unknown>;\n\n/** `Manifests[].Platform`. */\nexport interface RecipePlatform {\n readonly os?: string;\n readonly architecture?: string;\n readonly [key: string]: unknown;\n}\n\n/** `Manifests[].Artifacts[]`. */\nexport interface RecipeArtifact {\n readonly Uri: string;\n readonly [key: string]: unknown;\n}\n\n/** `Manifests[]`. */\nexport interface RecipeManifest {\n readonly Platform?: RecipePlatform;\n readonly Name?: string;\n readonly Lifecycle?: Lifecycle;\n readonly Artifacts?: readonly RecipeArtifact[];\n readonly Selections?: readonly string[];\n readonly [key: string]: unknown;\n}\n\n/** `ComponentConfiguration`. */\nexport interface ComponentConfiguration {\n readonly DefaultConfiguration?: Record<string, unknown>;\n readonly [key: string]: unknown;\n}\n\nexport type ComponentDependencyType = 'HARD' | 'SOFT';\n\n/** A single entry of `ComponentDependencies`. */\nexport interface ComponentDependencyRequirement {\n readonly VersionRequirement?: string;\n readonly DependencyType?: ComponentDependencyType;\n readonly [key: string]: unknown;\n}\n\n/** `ComponentDependencies`, keyed by the dependency's `ComponentName`. */\nexport type ComponentDependencies = Record<\n string,\n ComponentDependencyRequirement\n>;\n\n/** The full Greengrass v2 recipe document. */\nexport interface GreengrassRecipe {\n readonly RecipeFormatVersion: string;\n readonly ComponentName: string;\n readonly ComponentVersion: string;\n readonly ComponentDescription?: string;\n readonly ComponentPublisher?: string;\n readonly ComponentConfiguration?: ComponentConfiguration;\n readonly ComponentDependencies?: ComponentDependencies;\n readonly Manifests: readonly RecipeManifest[];\n readonly [key: string]: unknown;\n}\n\nconst RecipePlatformSchema = z.looseObject({\n os: z.string().optional(),\n architecture: z.string().optional(),\n});\n\nconst RecipeArtifactSchema = z.looseObject({\n Uri: z.string(),\n});\n\nconst LifecycleSchema = z.record(z.string(), z.unknown());\n\nconst RecipeManifestSchema = z.looseObject({\n Platform: RecipePlatformSchema.optional(),\n Name: z.string().optional(),\n Lifecycle: LifecycleSchema.optional(),\n Artifacts: z.array(RecipeArtifactSchema).optional(),\n Selections: z.array(z.string()).optional(),\n});\n\nconst ComponentConfigurationSchema = z.looseObject({\n DefaultConfiguration: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst ComponentDependencyRequirementSchema = z.looseObject({\n VersionRequirement: z.string().optional(),\n DependencyType: z.enum(['HARD', 'SOFT']).optional(),\n});\n\nconst ComponentDependenciesSchema = z.record(\n z.string(),\n ComponentDependencyRequirementSchema,\n);\n\n/**\n * Schema for the fields the plugin consumes. `.looseObject` at every level\n * means an unrecognised top-level key, manifest key or lifecycle key parses\n * through unchanged rather than being stripped; `ComponentName` and\n * `ComponentVersion` are the only fields checked against a service\n * constraint, via `superRefine` so both issues surface together when a\n * recipe violates both.\n */\nexport const GreengrassRecipeSchema = z\n .looseObject({\n RecipeFormatVersion: z.string(),\n ComponentName: z.string(),\n ComponentVersion: z.string(),\n ComponentDescription: z.string().optional(),\n ComponentPublisher: z.string().optional(),\n ComponentConfiguration: ComponentConfigurationSchema.optional(),\n ComponentDependencies: ComponentDependenciesSchema.optional(),\n Manifests: z.array(RecipeManifestSchema),\n })\n .superRefine((recipe, ctx) => {\n for (const issue of validateComponentName(recipe.ComponentName)) {\n ctx.addIssue({\n code: 'custom',\n message: issue.message,\n path: ['ComponentName'],\n });\n }\n\n if (!isValidComponentVersion(recipe.ComponentVersion)) {\n ctx.addIssue({\n code: 'custom',\n message: `ComponentVersion \"${recipe.ComponentVersion}\" must be a valid semantic version (eg. \"1.0.0\") or one of NEXT_PATCH, NEXT_MINOR, NEXT_MAJOR.`,\n path: ['ComponentVersion'],\n });\n }\n });\n\n/** One validation failure, with the recipe path it applies to. */\nexport interface RecipeValidationIssue {\n /** Dot-separated path into the recipe, eg. `Manifests.0.Artifacts.0.Uri`. */\n readonly path: string;\n readonly message: string;\n}\n\nconst toValidationIssues = (error: z.ZodError): RecipeValidationIssue[] =>\n error.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n }));\n\n/**\n * Validate an already-parsed recipe object against {@link GreengrassRecipeSchema},\n * returning every violation found rather than throwing on the first. Empty\n * when the recipe is valid.\n */\nexport const validateRecipe = (raw: unknown): RecipeValidationIssue[] => {\n const result = GreengrassRecipeSchema.safeParse(raw);\n return result.success ? [] : toValidationIssues(result.error);\n};\n\nexport interface ParsedRecipe {\n /** The validated, typed view of the recipe. */\n readonly recipe: GreengrassRecipe;\n /** The unvalidated result of parsing the YAML, exactly as loaded. */\n readonly raw: unknown;\n}\n\n/**\n * Parse a recipe YAML document and validate it, throwing when the fields the\n * plugin consumes are invalid. `raw` is the plain object `js-yaml` produced,\n * before validation; a caller that needs to rewrite the recipe (eg. to\n * substitute an artifact URI) mutates `raw` at the specific path and\n * re-serializes it with `yaml.dump`, so fields this module does not model are\n * never disturbed.\n */\nexport const parseRecipe = (yamlSource: string): ParsedRecipe => {\n const raw = yaml.load(yamlSource);\n const result = GreengrassRecipeSchema.safeParse(raw);\n\n if (!result.success) {\n const issues = toValidationIssues(result.error)\n .map((issue) => ` - ${issue.path || '(root)'}: ${issue.message}`)\n .join('\\n');\n throw new Error(`Invalid Greengrass recipe:\\n${issues}`);\n }\n\n return { recipe: result.data as GreengrassRecipe, raw };\n};\n"],"names":["yaml","valid","validSemver","z","validateComponentName","NEXT_VERSION_SENTINELS","isNextVersionSentinel","value","includes","nextVersionStrategy","sentinel","isValidComponentVersion","Boolean","RecipePlatformSchema","looseObject","os","string","optional","architecture","RecipeArtifactSchema","Uri","LifecycleSchema","record","unknown","RecipeManifestSchema","Platform","Name","Lifecycle","Artifacts","array","Selections","ComponentConfigurationSchema","DefaultConfiguration","ComponentDependencyRequirementSchema","VersionRequirement","DependencyType","enum","ComponentDependenciesSchema","GreengrassRecipeSchema","RecipeFormatVersion","ComponentName","ComponentVersion","ComponentDescription","ComponentPublisher","ComponentConfiguration","ComponentDependencies","Manifests","superRefine","recipe","ctx","issue","addIssue","code","message","path","toValidationIssues","error","issues","map","join","validateRecipe","raw","result","safeParse","success","parseRecipe","yamlSource","load","Error","data"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as url from 'url';
|
|
2
|
+
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
3
|
+
import { Construct } from 'constructs';
|
|
4
|
+
import {
|
|
5
|
+
type GreengrassArtifactBucket,
|
|
6
|
+
GreengrassComponentVersion,
|
|
7
|
+
} from '../../core/greengrass/index<% if (esm) { %>.js<% } %>';
|
|
8
|
+
|
|
9
|
+
export interface <%- componentNameClassName %>Props {
|
|
10
|
+
/** The bucket this component version's artifact is uploaded to. */
|
|
11
|
+
readonly bucket: GreengrassArtifactBucket;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @default RemovalPolicy.RETAIN
|
|
15
|
+
*/
|
|
16
|
+
readonly removalPolicy?: RemovalPolicy;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Publish "<%- componentDisplayName %>" from its <%- componentDirName %>-artifact build.
|
|
21
|
+
* Keep these paths aligned with the host project when editing this construct.
|
|
22
|
+
*/
|
|
23
|
+
export class <%- componentNameClassName %> extends GreengrassComponentVersion {
|
|
24
|
+
constructor(scope: Construct, id: string, props: <%- componentNameClassName %>Props) {
|
|
25
|
+
super(scope, id, {
|
|
26
|
+
recipesDir: url.fileURLToPath(
|
|
27
|
+
new URL(
|
|
28
|
+
'../../../../../../<%- recipesDirFromRoot %>',
|
|
29
|
+
<% if (esm) { %>import.meta.url<% } else { %>require('url').pathToFileURL(__filename).href<% } %>,
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
artifactsDir: url.fileURLToPath(
|
|
33
|
+
new URL(
|
|
34
|
+
'../../../../../../<%- artifactsDirFromRoot %>',
|
|
35
|
+
<% if (esm) { %>import.meta.url<% } else { %>require('url').pathToFileURL(__filename).href<% } %>,
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
bucket: props.bucket,
|
|
39
|
+
removalPolicy: props.removalPolicy,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { RemovalPolicy, Tags } from 'aws-cdk-lib';
|
|
2
|
+
import { Grant, type IGrantable } from 'aws-cdk-lib/aws-iam';
|
|
3
|
+
import { suppressRules } from '../checkov<% if (esm) { %>.js<% } %>';
|
|
4
|
+
import {
|
|
5
|
+
BlockPublicAccess,
|
|
6
|
+
Bucket,
|
|
7
|
+
BucketEncryption,
|
|
8
|
+
type IBucket,
|
|
9
|
+
} from 'aws-cdk-lib/aws-s3';
|
|
10
|
+
import { Construct } from 'constructs';
|
|
11
|
+
|
|
12
|
+
export interface GreengrassArtifactBucketProps {
|
|
13
|
+
/**
|
|
14
|
+
* Physical name for a new bucket; ignored when importing a bucket.
|
|
15
|
+
*/
|
|
16
|
+
readonly bucketName?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Import a bucket with its existing encryption, versioning and access settings.
|
|
20
|
+
*/
|
|
21
|
+
readonly existingBucketName?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Retention for the created repository; ignored when importing a bucket.
|
|
25
|
+
* @default RemovalPolicy.RETAIN
|
|
26
|
+
*/
|
|
27
|
+
readonly removalPolicy?: RemovalPolicy;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Own a retained, versioned, encrypted, TLS-only artifact repository.
|
|
32
|
+
* Import it into other stacks with existingBucketName.
|
|
33
|
+
*/
|
|
34
|
+
export class GreengrassArtifactBucket extends Construct {
|
|
35
|
+
public readonly bucket: IBucket;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
scope: Construct,
|
|
39
|
+
id: string,
|
|
40
|
+
props: GreengrassArtifactBucketProps = {},
|
|
41
|
+
) {
|
|
42
|
+
super(scope, id);
|
|
43
|
+
|
|
44
|
+
if (props.existingBucketName) {
|
|
45
|
+
this.bucket = Bucket.fromBucketName(
|
|
46
|
+
this,
|
|
47
|
+
'ImportedBucket',
|
|
48
|
+
props.existingBucketName,
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.bucket = new Bucket(this, 'Bucket', {
|
|
54
|
+
bucketName: props.bucketName,
|
|
55
|
+
versioned: true,
|
|
56
|
+
encryption: BucketEncryption.S3_MANAGED,
|
|
57
|
+
enforceSSL: true,
|
|
58
|
+
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
|
|
59
|
+
removalPolicy: props.removalPolicy ?? RemovalPolicy.RETAIN,
|
|
60
|
+
});
|
|
61
|
+
suppressRules(
|
|
62
|
+
this.bucket,
|
|
63
|
+
['CKV_AWS_18'],
|
|
64
|
+
'Server access logging would need a second retained log bucket; component artifacts are immutable published bytes and reads are auditable through CloudTrail data events',
|
|
65
|
+
);
|
|
66
|
+
// Mark retained repositories for inventory and deliberate cleanup.
|
|
67
|
+
Tags.of(this.bucket).add('greengrass-artifact-repository', 'true');
|
|
68
|
+
// Keep artifact keys until no published version needs them.
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Grant artifact reads within keyPrefix, defaulting to the workspace namespace.
|
|
73
|
+
*/
|
|
74
|
+
public grantReadTo(grantee: IGrantable, keyPrefix: string = '<%- componentKeyPrefix %>'): Grant {
|
|
75
|
+
return Grant.addToPrincipalOrResource({
|
|
76
|
+
grantee,
|
|
77
|
+
actions: ['s3:GetObject'],
|
|
78
|
+
resourceArns: [this.bucket.arnForObjects(keyPrefix)],
|
|
79
|
+
resource: this.bucket,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|