@fjall/components-infrastructure 2.29.0 → 2.30.3

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.
Files changed (93) hide show
  1. package/dist/lib/__tests__/setup.d.ts +48 -0
  2. package/dist/lib/__tests__/setup.js +1 -0
  3. package/dist/lib/app.d.ts +8 -4
  4. package/dist/lib/app.js +24 -6
  5. package/dist/lib/config/aws/accountId.d.ts +6 -0
  6. package/dist/lib/config/aws/accountId.js +33 -0
  7. package/dist/lib/config/aws/backupGlobalSettings.d.ts +29 -0
  8. package/dist/lib/config/aws/backupGlobalSettings.js +51 -0
  9. package/dist/lib/config/aws/costAllocationTags.d.ts +12 -0
  10. package/dist/lib/config/aws/costAllocationTags.js +48 -0
  11. package/dist/lib/config/aws/eventBus.d.ts +7 -0
  12. package/dist/lib/config/aws/eventBus.js +24 -0
  13. package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +10 -0
  14. package/dist/lib/config/aws/identityCenterGroupMembership.js +115 -0
  15. package/dist/lib/config/aws/ipamDelegateAdmin.d.ts +8 -0
  16. package/dist/lib/config/aws/ipamDelegateAdmin.js +58 -0
  17. package/dist/lib/config/aws/ipamPoolId.d.ts +10 -0
  18. package/dist/lib/config/aws/ipamPoolId.js +39 -0
  19. package/dist/lib/config/aws/organisation.d.ts +30 -0
  20. package/dist/lib/config/aws/organisation.js +95 -0
  21. package/dist/lib/config/aws/organisationId.d.ts +7 -0
  22. package/dist/lib/config/aws/organisationId.js +46 -0
  23. package/dist/lib/config/aws/organisationsAccess.d.ts +10 -0
  24. package/dist/lib/config/aws/organisationsAccess.js +50 -0
  25. package/dist/lib/config/aws/ramSharing.d.ts +4 -0
  26. package/dist/lib/config/aws/ramSharing.js +35 -0
  27. package/dist/lib/config/monitoring.d.ts +18 -0
  28. package/dist/lib/config/monitoring.js +22 -0
  29. package/dist/lib/lambda-assets/static-site-forms/asset/index.js +175 -0
  30. package/dist/lib/lambda-assets/static-site-forms/asset/package.json +4 -0
  31. package/dist/lib/patterns/aws/cdn.d.ts +19 -2
  32. package/dist/lib/patterns/aws/cdn.js +13 -2
  33. package/dist/lib/patterns/aws/cicdRole.d.ts +67 -0
  34. package/dist/lib/patterns/aws/cicdRole.js +68 -0
  35. package/dist/lib/patterns/aws/compute.js +9 -0
  36. package/dist/lib/patterns/aws/computeLambda.d.ts +38 -3
  37. package/dist/lib/patterns/aws/computeLambda.js +29 -2
  38. package/dist/lib/patterns/aws/hostedZone.d.ts +28 -0
  39. package/dist/lib/patterns/aws/hostedZone.js +150 -0
  40. package/dist/lib/patterns/aws/interfaces/pattern.d.ts +134 -5
  41. package/dist/lib/patterns/aws/interfaces/pattern.js +6 -0
  42. package/dist/lib/patterns/aws/managedAccount.d.ts +9 -0
  43. package/dist/lib/patterns/aws/managedAccount.js +55 -0
  44. package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +4 -0
  45. package/dist/lib/patterns/aws/managedIdentityCenter.js +24 -0
  46. package/dist/lib/patterns/aws/managedOrganisation.d.ts +36 -0
  47. package/dist/lib/patterns/aws/managedOrganisation.js +97 -0
  48. package/dist/lib/patterns/aws/managedPlatform.d.ts +12 -0
  49. package/dist/lib/patterns/aws/managedPlatform.js +29 -0
  50. package/dist/lib/patterns/aws/pattern.d.ts +20 -5
  51. package/dist/lib/patterns/aws/pattern.js +12 -6
  52. package/dist/lib/patterns/aws/staticSite.d.ts +59 -0
  53. package/dist/lib/patterns/aws/staticSite.js +307 -0
  54. package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +9 -0
  55. package/dist/lib/patterns/aws/subdomainHostedZone.js +39 -0
  56. package/dist/lib/resources/aws/cdn/cloudFront.d.ts +35 -2
  57. package/dist/lib/resources/aws/cdn/cloudFront.js +63 -25
  58. package/dist/lib/resources/aws/cdn/index.d.ts +1 -0
  59. package/dist/lib/resources/aws/cdn/index.js +1 -0
  60. package/dist/lib/resources/aws/cdn/responseHeadersPolicy.d.ts +56 -0
  61. package/dist/lib/resources/aws/cdn/responseHeadersPolicy.js +92 -0
  62. package/dist/lib/resources/aws/cicd/cicdRole.d.ts +65 -0
  63. package/dist/lib/resources/aws/cicd/cicdRole.js +191 -0
  64. package/dist/lib/resources/aws/compute/ecsFreeTier.d.ts +75 -0
  65. package/dist/lib/resources/aws/compute/ecsFreeTier.js +1 -0
  66. package/dist/lib/resources/aws/compute/ecsImages.js +3 -25
  67. package/dist/lib/resources/aws/compute/ecsSpot.d.ts +75 -0
  68. package/dist/lib/resources/aws/compute/ecsSpot.js +1 -0
  69. package/dist/lib/resources/aws/compute/imageTagParameter.d.ts +12 -0
  70. package/dist/lib/resources/aws/compute/imageTagParameter.js +32 -0
  71. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -0
  72. package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.d.ts +20 -0
  73. package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.js +1 -0
  74. package/dist/lib/resources/aws/database/database.d.ts +14 -0
  75. package/dist/lib/resources/aws/database/database.js +28 -0
  76. package/dist/lib/resources/aws/database/databaseInstance.d.ts +15 -0
  77. package/dist/lib/resources/aws/database/databaseInstance.js +30 -0
  78. package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.d.ts +13 -0
  79. package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.js +52 -0
  80. package/dist/lib/resources/aws/iam/securityGroup.d.ts +5 -0
  81. package/dist/lib/resources/aws/iam/securityGroup.js +14 -0
  82. package/dist/lib/resources/aws/monitoring/monitoringRole.d.ts +28 -0
  83. package/dist/lib/resources/aws/monitoring/monitoringRole.js +125 -0
  84. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +7 -0
  85. package/dist/lib/resources/aws/networking/domainCertificate.js +9 -7
  86. package/dist/lib/resources/aws/utilities/cfnOutput.d.ts +5 -0
  87. package/dist/lib/resources/aws/utilities/cfnOutput.js +1 -0
  88. package/dist/lib/utils/addSuffixToEmail.js +5 -1
  89. package/dist/lib/utils/capitalizeString.d.ts +1 -0
  90. package/dist/lib/utils/capitalizeString.js +7 -0
  91. package/package.json +7 -5
  92. package/dist/lib/config/aws/identityCenterMembership.d.ts +0 -11
  93. package/dist/lib/config/aws/identityCenterMembership.js +0 -61
@@ -0,0 +1,67 @@
1
+ import { Construct } from "constructs";
2
+ import { type Role } from "aws-cdk-lib/aws-iam";
3
+ import { type CICDProvider } from "../../resources/aws/cicd/cicdRole";
4
+ export interface CICDRoleProps {
5
+ /**
6
+ * The CI/CD provider (github-actions, buildkite, gitlab-ci)
7
+ */
8
+ provider: CICDProvider;
9
+ /**
10
+ * For GitHub Actions: repository in format "org/repo"
11
+ * For Buildkite: organization slug
12
+ * For GitLab CI: project path in format "group/project"
13
+ */
14
+ repositoryOrOrg: string;
15
+ /**
16
+ * Optional custom role name
17
+ * Defaults to "FjallDeploy-{provider}-{appName}"
18
+ */
19
+ roleName?: string;
20
+ /**
21
+ * Optional ARN of existing OIDC provider (skips creation)
22
+ * Useful when OIDC provider already exists in the account
23
+ */
24
+ existingProviderArn?: string;
25
+ }
26
+ /**
27
+ * High-level pattern for adding CI/CD OIDC authentication to an application.
28
+ *
29
+ * This creates an OIDC provider (if needed) and a deploy role that can be assumed
30
+ * by your CI/CD system without long-lived credentials.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { App, CICDRole } from "@fjall/components-infrastructure";
35
+ *
36
+ * const app = App.getApp("MyApp");
37
+ *
38
+ * // Add GitHub Actions OIDC authentication
39
+ * const cicdRole = new CICDRole(app, "GitHubDeployRole", {
40
+ * provider: "github-actions",
41
+ * repositoryOrOrg: "myorg/myrepo"
42
+ * });
43
+ *
44
+ * // Continue with normal application setup
45
+ * app.addCompute(/* ... *\/);
46
+ * ```
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // Use existing OIDC provider (created via fjall cicd setup)
51
+ * const cicdRole = new CICDRole(app, "GitHubDeployRole", {
52
+ * provider: "github-actions",
53
+ * repositoryOrOrg: "myorg/myrepo",
54
+ * existingProviderArn: "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
55
+ * });
56
+ * ```
57
+ */
58
+ export declare class CICDRole extends Construct {
59
+ readonly role: Role;
60
+ readonly provider: CICDProvider;
61
+ constructor(scope: Construct, id: string, props: CICDRoleProps);
62
+ /**
63
+ * Get the role ARN for use in CI/CD configuration
64
+ */
65
+ getRoleArn(): string;
66
+ }
67
+ export default CICDRole;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CICDRole = void 0;
4
+ const constructs_1 = require("constructs");
5
+ const app_1 = require("../../app");
6
+ const cicdRole_1 = require("../../resources/aws/cicd/cicdRole");
7
+ /**
8
+ * High-level pattern for adding CI/CD OIDC authentication to an application.
9
+ *
10
+ * This creates an OIDC provider (if needed) and a deploy role that can be assumed
11
+ * by your CI/CD system without long-lived credentials.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { App, CICDRole } from "@fjall/components-infrastructure";
16
+ *
17
+ * const app = App.getApp("MyApp");
18
+ *
19
+ * // Add GitHub Actions OIDC authentication
20
+ * const cicdRole = new CICDRole(app, "GitHubDeployRole", {
21
+ * provider: "github-actions",
22
+ * repositoryOrOrg: "myorg/myrepo"
23
+ * });
24
+ *
25
+ * // Continue with normal application setup
26
+ * app.addCompute(/* ... *\/);
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Use existing OIDC provider (created via fjall cicd setup)
32
+ * const cicdRole = new CICDRole(app, "GitHubDeployRole", {
33
+ * provider: "github-actions",
34
+ * repositoryOrOrg: "myorg/myrepo",
35
+ * existingProviderArn: "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
36
+ * });
37
+ * ```
38
+ */
39
+ class CICDRole extends constructs_1.Construct {
40
+ constructor(scope, id, props) {
41
+ super(scope, id);
42
+ // Get the App instance
43
+ const app = scope instanceof app_1.default ? scope : app_1.default.getInstance();
44
+ const networkStack = app.getDefaultNetworkStack();
45
+ this.provider = props.provider;
46
+ // Build the config for the factory
47
+ const config = {
48
+ provider: props.provider,
49
+ appName: app["name"],
50
+ repositoryOrOrg: props.repositoryOrOrg,
51
+ roleName: props.roleName,
52
+ existingProviderArn: props.existingProviderArn
53
+ };
54
+ // Create the CI/CD role using the factory
55
+ this.role = cicdRole_1.default.build(`${app["name"]}CICDRole`, config)(app, networkStack.getStack());
56
+ // Register the role with the network stack
57
+ networkStack.addConstruct(this.role);
58
+ }
59
+ /**
60
+ * Get the role ARN for use in CI/CD configuration
61
+ */
62
+ getRoleArn() {
63
+ return this.role.roleArn;
64
+ }
65
+ }
66
+ exports.CICDRole = CICDRole;
67
+ exports.default = CICDRole;
68
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2ljZFJvbGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9saWIvcGF0dGVybnMvYXdzL2NpY2RSb2xlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJDQUF1QztBQUV2QyxtQ0FBNEI7QUFDNUIsZ0VBRzJDO0FBNEIzQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQStCRztBQUNILE1BQWEsUUFBUyxTQUFRLHNCQUFTO0lBSXJDLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBb0I7UUFDNUQsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQix1QkFBdUI7UUFDdkIsTUFBTSxHQUFHLEdBQUcsS0FBSyxZQUFZLGFBQUcsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxhQUFHLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDN0QsTUFBTSxZQUFZLEdBQUcsR0FBRyxDQUFDLHNCQUFzQixFQUFFLENBQUM7UUFFbEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQUMsUUFBUSxDQUFDO1FBRS9CLG1DQUFtQztRQUNuQyxNQUFNLE1BQU0sR0FBbUI7WUFDN0IsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRO1lBQ3hCLE9BQU8sRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDO1lBQ3BCLGVBQWUsRUFBRSxLQUFLLENBQUMsZUFBZTtZQUN0QyxRQUFRLEVBQUUsS0FBSyxDQUFDLFFBQVE7WUFDeEIsbUJBQW1CLEVBQUUsS0FBSyxDQUFDLG1CQUFtQjtTQUMvQyxDQUFDO1FBRUYsMENBQTBDO1FBQzFDLElBQUksQ0FBQyxJQUFJLEdBQUcsa0JBQWUsQ0FBQyxLQUFLLENBQUMsR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FDakUsR0FBRyxFQUNILFlBQVksQ0FBQyxRQUFRLEVBQUUsQ0FDeEIsQ0FBQztRQUVGLDJDQUEyQztRQUMzQyxZQUFZLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN2QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxVQUFVO1FBQ2YsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztJQUMzQixDQUFDO0NBQ0Y7QUF0Q0QsNEJBc0NDO0FBRUQsa0JBQWUsUUFBUSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCB7IHR5cGUgUm9sZSB9IGZyb20gXCJhd3MtY2RrLWxpYi9hd3MtaWFtXCI7XG5pbXBvcnQgQXBwIGZyb20gXCIuLi8uLi9hcHBcIjtcbmltcG9ydCBDSUNEUm9sZUZhY3RvcnksIHtcbiAgdHlwZSBDSUNEUHJvdmlkZXIsXG4gIHR5cGUgQ0lDRFJvbGVDb25maWdcbn0gZnJvbSBcIi4uLy4uL3Jlc291cmNlcy9hd3MvY2ljZC9jaWNkUm9sZVwiO1xuXG5leHBvcnQgaW50ZXJmYWNlIENJQ0RSb2xlUHJvcHMge1xuICAvKipcbiAgICogVGhlIENJL0NEIHByb3ZpZGVyIChnaXRodWItYWN0aW9ucywgYnVpbGRraXRlLCBnaXRsYWItY2kpXG4gICAqL1xuICBwcm92aWRlcjogQ0lDRFByb3ZpZGVyO1xuXG4gIC8qKlxuICAgKiBGb3IgR2l0SHViIEFjdGlvbnM6IHJlcG9zaXRvcnkgaW4gZm9ybWF0IFwib3JnL3JlcG9cIlxuICAgKiBGb3IgQnVpbGRraXRlOiBvcmdhbml6YXRpb24gc2x1Z1xuICAgKiBGb3IgR2l0TGFiIENJOiBwcm9qZWN0IHBhdGggaW4gZm9ybWF0IFwiZ3JvdXAvcHJvamVjdFwiXG4gICAqL1xuICByZXBvc2l0b3J5T3JPcmc6IHN0cmluZztcblxuICAvKipcbiAgICogT3B0aW9uYWwgY3VzdG9tIHJvbGUgbmFtZVxuICAgKiBEZWZhdWx0cyB0byBcIkZqYWxsRGVwbG95LXtwcm92aWRlcn0te2FwcE5hbWV9XCJcbiAgICovXG4gIHJvbGVOYW1lPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBPcHRpb25hbCBBUk4gb2YgZXhpc3RpbmcgT0lEQyBwcm92aWRlciAoc2tpcHMgY3JlYXRpb24pXG4gICAqIFVzZWZ1bCB3aGVuIE9JREMgcHJvdmlkZXIgYWxyZWFkeSBleGlzdHMgaW4gdGhlIGFjY291bnRcbiAgICovXG4gIGV4aXN0aW5nUHJvdmlkZXJBcm4/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogSGlnaC1sZXZlbCBwYXR0ZXJuIGZvciBhZGRpbmcgQ0kvQ0QgT0lEQyBhdXRoZW50aWNhdGlvbiB0byBhbiBhcHBsaWNhdGlvbi5cbiAqXG4gKiBUaGlzIGNyZWF0ZXMgYW4gT0lEQyBwcm92aWRlciAoaWYgbmVlZGVkKSBhbmQgYSBkZXBsb3kgcm9sZSB0aGF0IGNhbiBiZSBhc3N1bWVkXG4gKiBieSB5b3VyIENJL0NEIHN5c3RlbSB3aXRob3V0IGxvbmctbGl2ZWQgY3JlZGVudGlhbHMuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IEFwcCwgQ0lDRFJvbGUgfSBmcm9tIFwiQGZqYWxsL2NvbXBvbmVudHMtaW5mcmFzdHJ1Y3R1cmVcIjtcbiAqXG4gKiBjb25zdCBhcHAgPSBBcHAuZ2V0QXBwKFwiTXlBcHBcIik7XG4gKlxuICogLy8gQWRkIEdpdEh1YiBBY3Rpb25zIE9JREMgYXV0aGVudGljYXRpb25cbiAqIGNvbnN0IGNpY2RSb2xlID0gbmV3IENJQ0RSb2xlKGFwcCwgXCJHaXRIdWJEZXBsb3lSb2xlXCIsIHtcbiAqICAgcHJvdmlkZXI6IFwiZ2l0aHViLWFjdGlvbnNcIixcbiAqICAgcmVwb3NpdG9yeU9yT3JnOiBcIm15b3JnL215cmVwb1wiXG4gKiB9KTtcbiAqXG4gKiAvLyBDb250aW51ZSB3aXRoIG5vcm1hbCBhcHBsaWNhdGlvbiBzZXR1cFxuICogYXBwLmFkZENvbXB1dGUoLyogLi4uICpcXC8pO1xuICogYGBgXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIFVzZSBleGlzdGluZyBPSURDIHByb3ZpZGVyIChjcmVhdGVkIHZpYSBmamFsbCBjaWNkIHNldHVwKVxuICogY29uc3QgY2ljZFJvbGUgPSBuZXcgQ0lDRFJvbGUoYXBwLCBcIkdpdEh1YkRlcGxveVJvbGVcIiwge1xuICogICBwcm92aWRlcjogXCJnaXRodWItYWN0aW9uc1wiLFxuICogICByZXBvc2l0b3J5T3JPcmc6IFwibXlvcmcvbXlyZXBvXCIsXG4gKiAgIGV4aXN0aW5nUHJvdmlkZXJBcm46IFwiYXJuOmF3czppYW06OjEyMzQ1Njc4OTAxMjpvaWRjLXByb3ZpZGVyL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tXCJcbiAqIH0pO1xuICogYGBgXG4gKi9cbmV4cG9ydCBjbGFzcyBDSUNEUm9sZSBleHRlbmRzIENvbnN0cnVjdCB7XG4gIHB1YmxpYyByZWFkb25seSByb2xlOiBSb2xlO1xuICBwdWJsaWMgcmVhZG9ubHkgcHJvdmlkZXI6IENJQ0RQcm92aWRlcjtcblxuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogQ0lDRFJvbGVQcm9wcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZCk7XG5cbiAgICAvLyBHZXQgdGhlIEFwcCBpbnN0YW5jZVxuICAgIGNvbnN0IGFwcCA9IHNjb3BlIGluc3RhbmNlb2YgQXBwID8gc2NvcGUgOiBBcHAuZ2V0SW5zdGFuY2UoKTtcbiAgICBjb25zdCBuZXR3b3JrU3RhY2sgPSBhcHAuZ2V0RGVmYXVsdE5ldHdvcmtTdGFjaygpO1xuXG4gICAgdGhpcy5wcm92aWRlciA9IHByb3BzLnByb3ZpZGVyO1xuXG4gICAgLy8gQnVpbGQgdGhlIGNvbmZpZyBmb3IgdGhlIGZhY3RvcnlcbiAgICBjb25zdCBjb25maWc6IENJQ0RSb2xlQ29uZmlnID0ge1xuICAgICAgcHJvdmlkZXI6IHByb3BzLnByb3ZpZGVyLFxuICAgICAgYXBwTmFtZTogYXBwW1wibmFtZVwiXSxcbiAgICAgIHJlcG9zaXRvcnlPck9yZzogcHJvcHMucmVwb3NpdG9yeU9yT3JnLFxuICAgICAgcm9sZU5hbWU6IHByb3BzLnJvbGVOYW1lLFxuICAgICAgZXhpc3RpbmdQcm92aWRlckFybjogcHJvcHMuZXhpc3RpbmdQcm92aWRlckFyblxuICAgIH07XG5cbiAgICAvLyBDcmVhdGUgdGhlIENJL0NEIHJvbGUgdXNpbmcgdGhlIGZhY3RvcnlcbiAgICB0aGlzLnJvbGUgPSBDSUNEUm9sZUZhY3RvcnkuYnVpbGQoYCR7YXBwW1wibmFtZVwiXX1DSUNEUm9sZWAsIGNvbmZpZykoXG4gICAgICBhcHAsXG4gICAgICBuZXR3b3JrU3RhY2suZ2V0U3RhY2soKVxuICAgICk7XG5cbiAgICAvLyBSZWdpc3RlciB0aGUgcm9sZSB3aXRoIHRoZSBuZXR3b3JrIHN0YWNrXG4gICAgbmV0d29ya1N0YWNrLmFkZENvbnN0cnVjdCh0aGlzLnJvbGUpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgcm9sZSBBUk4gZm9yIHVzZSBpbiBDSS9DRCBjb25maWd1cmF0aW9uXG4gICAqL1xuICBwdWJsaWMgZ2V0Um9sZUFybigpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLnJvbGUucm9sZUFybjtcbiAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBDSUNEUm9sZTtcbiJdfQ==
@@ -221,6 +221,10 @@ export class ComputeFactory {
221
221
  ...computeProps,
222
222
  appName: app.getName()
223
223
  };
224
+ // `ecrRepository` is already defaulted to the app's shared default
225
+ // container registry above (line ~323) when omitted — the same
226
+ // repo ECS services push into — so a container Lambda always has
227
+ // one by this point, whether or not `docker` is set.
224
228
  // Always register Lambda with manifest collector for CLI discovery
225
229
  // This ensures all Lambdas are discoverable for secrets management,
226
230
  // even those that don't currently have secrets configured
@@ -242,6 +246,11 @@ export class ComputeFactory {
242
246
  if (lambdaImportedSecretNames.size > 0) {
243
247
  manifestLambda.importedSecretNames = Array.from(lambdaImportedSecretNames);
244
248
  }
249
+ if (lambdaComputeProps.deployment === "container" &&
250
+ lambdaComputeProps.docker !== undefined) {
251
+ manifestLambda.docker = lambdaComputeProps.docker;
252
+ manifestLambda.imageKey = lambdaComputeProps.image ?? id;
253
+ }
245
254
  collector.addLambda(manifestLambda);
246
255
  return new LambdaCompute(scope, id, lambdaComputeProps);
247
256
  }
@@ -8,6 +8,7 @@ import { type ILambdaCompute } from "./interfaces/compute.js";
8
8
  import { type ConnectionSpec } from "./interfaces/connector.js";
9
9
  import { LambdaFunction } from "../../resources/aws/compute/lambda.js";
10
10
  import { type SecretImport } from "../../resources/aws/secrets/index.js";
11
+ import { type DockerBuild } from "@fjall/util/manifest/schemas";
11
12
  export { Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "aws-cdk-lib/aws-lambda";
12
13
  /**
13
14
  * Lambda function URL configuration.
@@ -114,17 +115,47 @@ interface BaseLambdaProps {
114
115
  * automatically set to FROM_IMAGE.
115
116
  *
116
117
  * @example
118
+ * // Bring your own image (built/pushed out of band)
117
119
  * app.addCompute(ComputeFactory.build("ImageLambda", {
118
120
  * type: "lambda",
119
121
  * deployment: "container",
120
122
  * ecrRepository: app.getDefaultContainerRegistry()
121
123
  * }));
124
+ *
125
+ * @example
126
+ * // fjall builds and pushes the image on every `fjall deploy`
127
+ * app.addCompute(ComputeFactory.build("ImageLambda", {
128
+ * type: "lambda",
129
+ * deployment: "container",
130
+ * docker: { path: "lambda.Dockerfile" }
131
+ * }));
122
132
  */
123
133
  export interface ContainerLambdaProps extends BaseLambdaProps {
124
134
  /** Container-based deployment using ECR image */
125
135
  deployment: "container";
126
- /** ECR repository containing the Lambda container image */
127
- ecrRepository: Repository | RepositoryImage;
136
+ /**
137
+ * ECR repository containing the Lambda container image. Optional when
138
+ * `docker` is set — defaults to the app's shared default container
139
+ * registry (the same repo ECS services push into).
140
+ */
141
+ ecrRepository?: Repository | RepositoryImage;
142
+ /**
143
+ * When set, `fjall deploy` builds and pushes this Dockerfile into the app's
144
+ * shared default container registry, then wires the function to the
145
+ * resulting content-hash-tagged image — the same mechanism ECS services
146
+ * already use. Omit to keep managing the image out of band (the previous
147
+ * behaviour: the function tracks a moving `:latest` tag in `ecrRepository`).
148
+ */
149
+ docker?: DockerBuild;
150
+ /**
151
+ * Shared build/tag key for Lambda functions that intentionally run the
152
+ * same container image (e.g. several handlers packaged from one
153
+ * Dockerfile). Functions sharing one `image` value build once and share
154
+ * one content-hash tag. Defaults to this Lambda's own construct id, so
155
+ * omitting it is safe when no sharing is intended. Only meaningful when
156
+ * `docker` is set.
157
+ */
158
+ image?: string;
128
159
  }
129
160
  /**
130
161
  * Code-based Lambda using inline code or S3.
@@ -177,8 +208,12 @@ export interface ResolvedLambdaDeployment {
177
208
  /**
178
209
  * Resolve Lambda deployment configuration from props.
179
210
  * Handles container vs code deployment types.
211
+ *
212
+ * `scope`/`id` are only used on the container-with-`docker` path, to create
213
+ * (or reuse) the per-key `CfnParameter` that `fjall deploy` overrides with
214
+ * the content-hash tag after building and pushing the image.
180
215
  */
181
- export declare function resolveLambdaDeployment(props: LambdaComputeProps): ResolvedLambdaDeployment;
216
+ export declare function resolveLambdaDeployment(scope: Construct, id: string, props: LambdaComputeProps): ResolvedLambdaDeployment;
182
217
  /**
183
218
  * Lambda compute wrapper implementing ILambdaCompute.
184
219
  * Provides type-safe access to Lambda-specific resources.
@@ -4,18 +4,45 @@ import { Repository } from "aws-cdk-lib/aws-ecr";
4
4
  import { Construct } from "constructs";
5
5
  import { processConnections } from "../../utils/connections.js";
6
6
  import { LambdaFunction } from "../../resources/aws/compute/lambda.js";
7
+ import { getOrCreateImageTagParameter } from "../../resources/aws/compute/imageTagParameter.js";
7
8
  import { COMPUTE_DEFAULTS } from "./compute.js";
9
+ import { evaluateBakeGuard } from "@fjall/util/docker";
10
+ import { toKebab } from "@fjall/util";
8
11
  // Re-export Lambda types from CDK for user convenience
9
12
  export { Architecture, HttpMethod, InvokeMode } from "aws-cdk-lib/aws-lambda";
10
13
  /**
11
14
  * Resolve Lambda deployment configuration from props.
12
15
  * Handles container vs code deployment types.
16
+ *
17
+ * `scope`/`id` are only used on the container-with-`docker` path, to create
18
+ * (or reuse) the per-key `CfnParameter` that `fjall deploy` overrides with
19
+ * the content-hash tag after building and pushing the image.
13
20
  */
14
- export function resolveLambdaDeployment(props) {
21
+ export function resolveLambdaDeployment(scope, id, props) {
15
22
  if (props.deployment === "container") {
23
+ if (!props.ecrRepository) {
24
+ throw new Error(`Lambda '${id}': ecrRepository is required unless \`docker\` is set ` +
25
+ "(ComputeFactory fills it in automatically from the app's default container registry when docker is present).");
26
+ }
16
27
  if (!(props.ecrRepository instanceof Repository)) {
17
28
  throw new Error("ecrRepository must be a Repository instance for Lambda compute type");
18
29
  }
30
+ if (props.docker !== undefined) {
31
+ const appPrefix = props.appName !== undefined ? toKebab(props.appName) : "";
32
+ const findings = evaluateBakeGuard(props.docker.buildArgs, appPrefix).findings;
33
+ if (findings.length > 0) {
34
+ throw new Error(`Lambda '${id}': ${findings.map((f) => f.message).join(" ")}`);
35
+ }
36
+ const tagKey = props.image ?? id;
37
+ const param = getOrCreateImageTagParameter(scope, tagKey);
38
+ return {
39
+ code: Code.fromEcrImage(props.ecrRepository, {
40
+ tagOrDigest: param.valueAsString
41
+ }),
42
+ handler: Handler.FROM_IMAGE,
43
+ runtime: Runtime.FROM_IMAGE
44
+ };
45
+ }
19
46
  return {
20
47
  code: Code.fromEcrImage(props.ecrRepository, {
21
48
  tagOrDigest: COMPUTE_DEFAULTS.ECS.IMAGE_TAG
@@ -40,7 +67,7 @@ export class LambdaCompute extends Construct {
40
67
  lambdaFunction;
41
68
  constructor(scope, id, props) {
42
69
  super(scope, id);
43
- const { code, handler, runtime } = resolveLambdaDeployment(props);
70
+ const { code, handler, runtime } = resolveLambdaDeployment(scope, id, props);
44
71
  // Convert presence-based functionUrl config to legacy boolean format
45
72
  const functionUrlConfig = props.functionUrl;
46
73
  const enableFunctionUrl = functionUrlConfig !== undefined && functionUrlConfig !== false;
@@ -0,0 +1,28 @@
1
+ import { Construct } from "constructs";
2
+ import { type ARecordProps, type CnameRecordProps, HostedZone as AWSHostedZone, type MxRecordProps, type NsRecordProps, type TxtRecordProps } from "aws-cdk-lib/aws-route53";
3
+ import { type AwsStack } from "../../resources";
4
+ export interface HostedZoneProps {
5
+ zoneName: string;
6
+ hostedZoneId?: string;
7
+ }
8
+ export declare class HostedZoneFactory {
9
+ static import(stack: AwsStack, hostedZoneId: string, zoneName: string): HostedZone;
10
+ }
11
+ export declare class HostedZone extends Construct {
12
+ private hostedZone;
13
+ private hostedZoneId;
14
+ private importedHostedZone;
15
+ private zoneName;
16
+ private scope;
17
+ constructor(scope: Construct, id: string, props: HostedZoneProps);
18
+ getInternalHostedZone(): AWSHostedZone;
19
+ safeZoneName(): string;
20
+ addHostedZone(zoneName: string): void;
21
+ importHostedZone(zoneName: string): void;
22
+ delegateHostedZone(): void;
23
+ addARecord(props: Omit<ARecordProps, "zone">): void;
24
+ addCname(props: Omit<CnameRecordProps, "zone">): void;
25
+ addMx(props: Omit<MxRecordProps, "zone">): void;
26
+ addNS(props: Omit<NsRecordProps, "zone">): void;
27
+ addTxt(props: Omit<TxtRecordProps, "zone">): void;
28
+ }
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HostedZone = exports.HostedZoneFactory = void 0;
4
+ const constructs_1 = require("constructs");
5
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
6
+ const iam_1 = require("../../resources/aws/iam");
7
+ const aws_iam_1 = require("aws-cdk-lib/aws-iam");
8
+ const aws_route53_1 = require("aws-cdk-lib/aws-route53");
9
+ function getSafeZoneName(zoneName) {
10
+ return zoneName.split(".").join("");
11
+ }
12
+ class HostedZoneFactory {
13
+ static import(stack, hostedZoneId, zoneName) {
14
+ const safeZoneName = getSafeZoneName(zoneName);
15
+ return new HostedZone(stack.getStack(), `${safeZoneName}HostedZone`, {
16
+ hostedZoneId: hostedZoneId,
17
+ zoneName: zoneName
18
+ });
19
+ }
20
+ }
21
+ exports.HostedZoneFactory = HostedZoneFactory;
22
+ class HostedZone extends constructs_1.Construct {
23
+ constructor(scope, id, props) {
24
+ super(scope, id);
25
+ this.scope = scope;
26
+ this.zoneName = props.zoneName;
27
+ // If a hostedZoneId is provided import it, unless create a new hosted zone
28
+ if (!props.hostedZoneId) {
29
+ this.addHostedZone(props.zoneName);
30
+ this.hostedZoneId = this.hostedZone.hostedZoneId;
31
+ this.delegateHostedZone();
32
+ }
33
+ else {
34
+ this.hostedZoneId = props.hostedZoneId;
35
+ this.importHostedZone(props.zoneName);
36
+ }
37
+ }
38
+ getInternalHostedZone() {
39
+ return this.hostedZone || this.importedHostedZone;
40
+ }
41
+ safeZoneName() {
42
+ return getSafeZoneName(this.zoneName);
43
+ }
44
+ addHostedZone(zoneName) {
45
+ this.hostedZone = new aws_route53_1.HostedZone(this, `${this.safeZoneName}HostedZone`, {
46
+ zoneName: zoneName,
47
+ comment: `Hosted Zone for ${zoneName}`
48
+ });
49
+ }
50
+ importHostedZone(zoneName) {
51
+ this.importedHostedZone = aws_route53_1.HostedZone.fromHostedZoneAttributes(
52
+ // HostedZone.fromHostedZoneAttributes explicitly must be passed a stack as parent scope
53
+ this.scope,
54
+ // Use safeZoneName in the id so we can theoretically
55
+ // import multiple hosted zones in a single app
56
+ `${this.safeZoneName()}ImportedHostedZone`, {
57
+ hostedZoneId: this.hostedZoneId,
58
+ zoneName: zoneName
59
+ });
60
+ aws_cdk_lib_1.Tags.of(this.importedHostedZone).add("fjall:costAllocation:environment", "management");
61
+ aws_cdk_lib_1.Tags.of(this.importedHostedZone).add("fjall:costAllocation:service", "hostedZone");
62
+ aws_cdk_lib_1.Tags.of(this.importedHostedZone).add("fjall:costAllocation:domain", zoneName);
63
+ // Make export name unique by including the stack name to avoid conflicts
64
+ // when multiple stacks import the same hosted zone
65
+ const stack = aws_cdk_lib_1.Stack.of(this);
66
+ const stackName = stack.stackName;
67
+ const exportName = `${stackName}${this.safeZoneName()}HostedZoneId`;
68
+ new aws_cdk_lib_1.CfnOutput(this, `${this.safeZoneName()}HostedZoneId`, {
69
+ key: `${this.safeZoneName()}HostedZoneId`,
70
+ value: this.hostedZoneId,
71
+ exportName: exportName
72
+ });
73
+ }
74
+ delegateHostedZone() {
75
+ const domainName = this.hostedZone.zoneName.split(".", 1);
76
+ const role = new iam_1.Role(this, `${domainName}DelegateHostedZoneRole`, {
77
+ assumedBy: new aws_iam_1.OrganizationPrincipal(aws_cdk_lib_1.Fn.importValue("OrganisationId")),
78
+ roleName: `${domainName}DelegateHostedZoneRole`,
79
+ inlinePolicies: {
80
+ ["listHostedZones"]: new aws_iam_1.PolicyDocument({
81
+ statements: [
82
+ new aws_iam_1.PolicyStatement({
83
+ actions: ["route53:ListHostedZonesByName"],
84
+ resources: ["*"]
85
+ })
86
+ ]
87
+ }),
88
+ ["changeResourceRecordSets"]: new aws_iam_1.PolicyDocument({
89
+ statements: [
90
+ new aws_iam_1.PolicyStatement({
91
+ actions: ["route53:ChangeResourceRecordSets"],
92
+ resources: [
93
+ `arn:aws:route53:::hostedzone/${this.hostedZone.hostedZoneId}`
94
+ ]
95
+ })
96
+ ]
97
+ })
98
+ }
99
+ });
100
+ this.hostedZone.grantDelegation(role);
101
+ new aws_cdk_lib_1.CfnOutput(this, "DelegateHostedZoneRoleArn", {
102
+ key: `${domainName}DelegateHostedZoneRoleArn`,
103
+ value: role.roleArn,
104
+ exportName: `${domainName}DelegateHostedZoneRoleArn`
105
+ });
106
+ }
107
+ addARecord(props) {
108
+ new aws_route53_1.ARecord(this, "ARecord", {
109
+ zone: this.importedHostedZone,
110
+ recordName: props.recordName,
111
+ comment: props.comment || `A Record for ${this.importedHostedZone.zoneName}`,
112
+ target: props.target,
113
+ ttl: props.ttl
114
+ });
115
+ }
116
+ addCname(props) {
117
+ new aws_route53_1.CnameRecord(this, ``, {
118
+ domainName: props.domainName,
119
+ zone: this.importedHostedZone,
120
+ comment: props.comment || `CNAME Record for ${this.importedHostedZone.zoneName}`,
121
+ ttl: props.ttl
122
+ });
123
+ }
124
+ addMx(props) {
125
+ new aws_route53_1.MxRecord(this, ``, {
126
+ zone: this.importedHostedZone,
127
+ comment: props.comment || `MX Record for ${this.importedHostedZone.zoneName}`,
128
+ values: props.values,
129
+ ttl: props.ttl
130
+ });
131
+ }
132
+ addNS(props) {
133
+ new aws_route53_1.NsRecord(this, ``, {
134
+ zone: this.importedHostedZone,
135
+ comment: props.comment || `NS Record for ${this.importedHostedZone.zoneName}`,
136
+ values: props.values,
137
+ ttl: props.ttl
138
+ });
139
+ }
140
+ addTxt(props) {
141
+ new aws_route53_1.TxtRecord(this, ``, {
142
+ zone: this.importedHostedZone,
143
+ comment: props.comment || `TXT Record for ${this.importedHostedZone.zoneName}`,
144
+ values: props.values,
145
+ ttl: props.ttl
146
+ });
147
+ }
148
+ }
149
+ exports.HostedZone = HostedZone;
150
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaG9zdGVkWm9uZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2xpYi9wYXR0ZXJucy9hd3MvaG9zdGVkWm9uZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyQ0FBdUM7QUFDdkMsNkNBQXlEO0FBQ3pELGlEQUErQztBQUMvQyxpREFJNkI7QUFDN0IseURBYWlDO0FBSWpDLFNBQVMsZUFBZSxDQUFDLFFBQWdCO0lBQ3ZDLE9BQU8sUUFBUSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDdEMsQ0FBQztBQU9ELE1BQWEsaUJBQWlCO0lBQzVCLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBZSxFQUFFLFlBQW9CLEVBQUUsUUFBZ0I7UUFDbkUsTUFBTSxZQUFZLEdBQUcsZUFBZSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRS9DLE9BQU8sSUFBSSxVQUFVLENBQUMsS0FBSyxDQUFDLFFBQVEsRUFBRSxFQUFFLEdBQUcsWUFBWSxZQUFZLEVBQUU7WUFDbkUsWUFBWSxFQUFFLFlBQVk7WUFDMUIsUUFBUSxFQUFFLFFBQVE7U0FDbkIsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBVEQsOENBU0M7QUFFRCxNQUFhLFVBQVcsU0FBUSxzQkFBUztJQVF2QyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXNCO1FBQzlELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7UUFDbkIsSUFBSSxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQUMsUUFBUSxDQUFDO1FBRS9CLDJFQUEyRTtRQUMzRSxJQUFJLENBQUMsS0FBSyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3hCLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQ25DLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFB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QUNwQixHQUFHLEVBQUUsS0FBSyxDQUFDLEdBQUc7U0FDZixDQUFDLENBQUM7SUFDTCxDQUFDO0NBQ0Y7QUExS0QsZ0NBMEtDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29uc3RydWN0IH0gZnJvbSBcImNvbnN0cnVjdHNcIjtcbmltcG9ydCB7IENmbk91dHB1dCwgRm4sIFN0YWNrLCBUYWdzIH0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgeyBSb2xlIH0gZnJvbSBcIi4uLy4uL3Jlc291cmNlcy9hd3MvaWFtXCI7XG5pbXBvcnQge1xuICBPcmdhbml6YXRpb25QcmluY2lwYWwsXG4gIFBvbGljeURvY3VtZW50LFxuICBQb2xpY3lTdGF0ZW1lbnRcbn0gZnJvbSBcImF3cy1jZGstbGliL2F3cy1pYW1cIjtcbmltcG9ydCB7XG4gIEFSZWNvcmQsXG4gIHR5cGUgQVJlY29yZFByb3BzLFxuICBDbmFtZVJlY29yZCxcbiAgdHlwZSBDbmFtZVJlY29yZFByb3BzLFxuICBIb3N0ZWRab25lIGFzIEFXU0hvc3RlZFpvbmUsXG4gIHR5cGUgSUhvc3RlZFpvbmUsXG4gIE14UmVjb3JkLFxuICB0eXBlIE14UmVjb3JkUHJvcHMsXG4gIE5zUmVjb3JkLFxuICB0eXBlIE5zUmVjb3JkUHJvcHMsXG4gIFR4dFJlY29yZCxcbiAgdHlwZSBUeHRSZWNvcmRQcm9wc1xufSBmcm9tIFwiYXdzLWNkay1saWIvYXdzLXJvdXRlNTNcIjtcblxuaW1wb3J0IHsgdHlwZSBBd3NTdGFjayB9IGZyb20gXCIuLi8uLi9yZXNvdXJjZXNcIjtcblxuZnVuY3Rpb24gZ2V0U2FmZVpvbmVOYW1lKHpvbmVOYW1lOiBzdHJpbmcpIHtcbiAgcmV0dXJuIHpvbmVOYW1lLnNwbGl0KFwiLlwiKS5qb2luKFwiXCIpO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEhvc3RlZFpvbmVQcm9wcyB7XG4gIHpvbmVOYW1lOiBzdHJpbmc7XG4gIGhvc3RlZFpvbmVJZD86IHN0cmluZztcbn1cblxuZXhwb3J0IGNsYXNzIEhvc3RlZFpvbmVGYWN0b3J5IHtcbiAgc3RhdGljIGltcG9ydChzdGFjazogQXdzU3RhY2ssIGhvc3RlZFpvbmVJZDogc3RyaW5nLCB6b25lTmFtZTogc3RyaW5nKSB7XG4gICAgY29uc3Qgc2FmZVpvbmVOYW1lID0gZ2V0U2FmZVpvbmVOYW1lKHpvbmVOYW1lKTtcblxuICAgIHJldHVybiBuZXcgSG9zdGVkWm9uZShzdGFjay5nZXRTdGFjaygpLCBgJHtzYWZlWm9uZU5hbWV9SG9zdGVkWm9uZWAsIHtcbiAgICAgIGhvc3RlZFpvbmVJZDogaG9zdGVkWm9uZUlkLFxuICAgICAgem9uZU5hbWU6IHpvbmVOYW1lXG4gICAgfSk7XG4gIH1cbn1cblxuZXhwb3J0IGNsYXNzIEhvc3RlZFpvbmUgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBwcml2YXRlIGhvc3RlZFpvbmU6IEFXU0hvc3RlZFpvbmU7XG4gIHByaXZhdGUgaG9zdGVkWm9uZUlkOiBzdHJpbmc7XG4gIHByaXZhdGUgaW1wb3J0ZWRIb3N0ZWRab25lOiBJSG9zdGVkWm9uZTtcbiAgcHJpdmF0ZSB6b25lTmFtZTogc3RyaW5nO1xuXG4gIHByaXZhdGUgc2NvcGU6IENvbnN0cnVjdDtcblxuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogSG9zdGVkWm9uZVByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIHRoaXMuc2NvcGUgPSBzY29wZTtcbiAgICB0aGlzLnpvbmVOYW1lID0gcHJvcHMuem9uZU5hbWU7XG5cbiAgICAvLyBJZiBhIGhvc3RlZFpvbmVJZCBpcyBwcm92aWRlZCBpbXBvcnQgaXQsIHVubGVzcyBjcmVhdGUgYSBuZXcgaG9zdGVkIHpvbmVcbiAgICBpZiAoIXByb3BzLmhvc3RlZFpvbmVJZCkge1xuICAgICAgdGhpcy5hZGRIb3N0ZWRab25lKHByb3BzLnpvbmVOYW1lKTtcbiAgICAgIHRoaXMuaG9zdGVkWm9uZUlkID0gdGhpcy5ob3N0ZWRab25lLmhvc3RlZFpvbmVJZDtcbiAgICAgIHRoaXMuZGVsZWdhdGVIb3N0ZWRab25lKCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuaG9zdGVkWm9uZUlkID0gcHJvcHMuaG9zdGVkWm9uZUlkO1xuICAgICAgdGhpcy5pbXBvcnRIb3N0ZWRab25lKHByb3BzLnpvbmVOYW1lKTtcbiAgICB9XG4gIH1cblxuICBnZXRJbnRlcm5hbEhvc3RlZFpvbmUoKSB7XG4gICAgcmV0dXJuIHRoaXMuaG9zdGVkWm9uZSB8fCB0aGlzLmltcG9ydGVkSG9zdGVkWm9uZTtcbiAgfVxuXG4gIHNhZmVab25lTmFtZSgpIHtcbiAgICByZXR1cm4gZ2V0U2FmZVpvbmVOYW1lKHRoaXMuem9uZU5hbWUpO1xuICB9XG5cbiAgYWRkSG9zdGVkWm9uZSh6b25lTmFtZTogc3RyaW5nKSB7XG4gICAgdGhpcy5ob3N0ZWRab25lID0gbmV3IEFXU0hvc3RlZFpvbmUoXG4gICAgICB0aGlzLFxuICAgICAgYCR7dGhpcy5zYWZlWm9uZU5hbWV9SG9zdGVkWm9uZWAsXG4gICAgICB7XG4gICAgICAgIHpvbmVOYW1lOiB6b25lTmFtZSxcbiAgICAgICAgY29tbWVudDogYEhvc3RlZCBab25lIGZvciAke3pvbmVOYW1lfWBcbiAgICAgIH1cbiAgICApO1xuICB9XG5cbiAgaW1wb3J0SG9zdGVkWm9uZSh6b25lTmFtZTogc3RyaW5nKSB7XG4gICAgdGhpcy5pbXBvcnRlZEhvc3RlZFpvbmUgPSBBV1NIb3N0ZWRab25lLmZyb21Ib3N0ZWRab25lQXR0cmlidXRlcyhcbiAgICAgIC8vIEhvc3RlZFpvbmUuZnJvbUhvc3RlZFpvbmVBdHRyaWJ1dGVzIGV4cGxpY2l0bHkgbXVzdCBiZSBwYXNzZWQgYSBzdGFjayBhcyBwYXJlbnQgc2NvcGVcbiAgICAgIHRoaXMuc2NvcGUsXG5cbiAgICAgIC8vIFVzZSBzYWZlWm9uZU5hbWUgaW4gdGhlIGlkIHNvIHdlIGNhbiB0aGVvcmV0aWNhbGx5XG4gICAgICAvLyAgaW1wb3J0IG11bHRpcGxlIGhvc3RlZCB6b25lcyBpbiBhIHNpbmdsZSBhcHBcbiAgICAgIGAke3RoaXMuc2FmZVpvbmVOYW1lKCl9SW1wb3J0ZWRIb3N0ZWRab25lYCxcbiAgICAgIHtcbiAgICAgICAgaG9zdGVkWm9uZUlkOiB0aGlzLmhvc3RlZFpvbmVJZCxcbiAgICAgICAgem9uZU5hbWU6IHpvbmVOYW1lXG4gICAgICB9XG4gICAgKTtcblxuICAgIFRhZ3Mub2YodGhpcy5pbXBvcnRlZEhvc3RlZFpvbmUpLmFkZChcbiAgICAgIFwiZmphbGw6Y29zdEFsbG9jYXRpb246ZW52aXJvbm1lbnRcIixcbiAgICAgIFwibWFuYWdlbWVudFwiXG4gICAgKTtcbiAgICBUYWdzLm9mKHRoaXMuaW1wb3J0ZWRIb3N0ZWRab25lKS5hZGQoXG4gICAgICBcImZqYWxsOmNvc3RBbGxvY2F0aW9uOnNlcnZpY2VcIixcbiAgICAgIFwiaG9zdGVkWm9uZVwiXG4gICAgKTtcbiAgICBUYWdzLm9mKHRoaXMuaW1wb3J0ZWRIb3N0ZWRab25lKS5hZGQoXG4gICAgICBcImZqYWxsOmNvc3RBbGxvY2F0aW9uOmRvbWFpblwiLFxuICAgICAgem9uZU5hbWVcbiAgICApO1xuXG4gICAgLy8gTWFrZSBleHBvcnQgbmFtZSB1bmlxdWUgYnkgaW5jbHVkaW5nIHRoZSBzdGFjayBuYW1lIHRvIGF2b2lkIGNvbmZsaWN0c1xuICAgIC8vIHdoZW4gbXVsdGlwbGUgc3RhY2tzIGltcG9ydCB0aGUgc2FtZSBob3N0ZWQgem9uZVxuICAgIGNvbnN0IHN0YWNrID0gU3RhY2sub2YodGhpcyk7XG4gICAgY29uc3Qgc3RhY2tOYW1lID0gc3RhY2suc3RhY2tOYW1lO1xuICAgIGNvbnN0IGV4cG9ydE5hbWUgPSBgJHtzdGFja05hbWV9JHt0aGlzLnNhZmVab25lTmFtZSgpfUhvc3RlZFpvbmVJZGA7XG5cbiAgICBuZXcgQ2ZuT3V0cHV0KHRoaXMsIGAke3RoaXMuc2FmZVpvbmVOYW1lKCl9SG9zdGVkWm9uZUlkYCwge1xuICAgICAga2V5OiBgJHt0aGlzLnNhZmVab25lTmFtZSgpfUhvc3RlZFpvbmVJZGAsXG4gICAgICB2YWx1ZTogdGhpcy5ob3N0ZWRab25lSWQsXG4gICAgICBleHBvcnROYW1lOiBleHBvcnROYW1lXG4gICAgfSk7XG4gIH1cblxuICBkZWxlZ2F0ZUhvc3RlZFpvbmUoKSB7XG4gICAgY29uc3QgZG9tYWluTmFtZSA9IHRoaXMuaG9zdGVkWm9uZS56b25lTmFtZS5zcGxpdChcIi5cIiwgMSk7XG4gICAgY29uc3Qgcm9sZSA9IG5ldyBSb2xlKHRoaXMsIGAke2RvbWFpbk5hbWV9RGVsZWdhdGVIb3N0ZWRab25lUm9sZWAsIHtcbiAgICAgIGFzc3VtZWRCeTogbmV3IE9yZ2FuaXphdGlvblByaW5jaXBhbChGbi5pbXBvcnRWYWx1ZShcIk9yZ2FuaXNhdGlvbklkXCIpKSxcbiAgICAgIHJvbGVOYW1lOiBgJHtkb21haW5OYW1lfURlbGVnYXRlSG9zdGVkWm9uZVJvbGVgLFxuICAgICAgaW5saW5lUG9saWNpZXM6IHtcbiAgICAgICAgW1wibGlzdEhvc3RlZFpvbmVzXCJdOiBuZXcgUG9saWN5RG9jdW1lbnQoe1xuICAgICAgICAgIHN0YXRlbWVudHM6IFtcbiAgICAgICAgICAgIG5ldyBQb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgICAgICBhY3Rpb25zOiBbXCJyb3V0ZTUzOkxpc3RIb3N0ZWRab25lc0J5TmFtZVwiXSxcbiAgICAgICAgICAgICAgcmVzb3VyY2VzOiBbXCIqXCJdXG4gICAgICAgICAgICB9KVxuICAgICAgICAgIF1cbiAgICAgICAgfSksXG4gICAgICAgIFtcImNoYW5nZVJlc291cmNlUmVjb3JkU2V0c1wiXTogbmV3IFBvbGljeURvY3VtZW50KHtcbiAgICAgICAgICBzdGF0ZW1lbnRzOiBbXG4gICAgICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICAgICAgYWN0aW9uczogW1wicm91dGU1MzpDaGFuZ2VSZXNvdXJjZVJlY29yZFNldHNcIl0sXG4gICAgICAgICAgICAgIHJlc291cmNlczogW1xuICAgICAgICAgICAgICAgIGBhcm46YXdzOnJvdXRlNTM6Ojpob3N0ZWR6b25lLyR7dGhpcy5ob3N0ZWRab25lLmhvc3RlZFpvbmVJZH1gXG4gICAgICAgICAgICAgIF1cbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgXVxuICAgICAgICB9KVxuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdGhpcy5ob3N0ZWRab25lLmdyYW50RGVsZWdhdGlvbihyb2xlKTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJEZWxlZ2F0ZUhvc3RlZFpvbmVSb2xlQXJuXCIsIHtcbiAgICAgIGtleTogYCR7ZG9tYWluTmFtZX1EZWxlZ2F0ZUhvc3RlZFpvbmVSb2xlQXJuYCxcbiAgICAgIHZhbHVlOiByb2xlLnJvbGVBcm4sXG4gICAgICBleHBvcnROYW1lOiBgJHtkb21haW5OYW1lfURlbGVnYXRlSG9zdGVkWm9uZVJvbGVBcm5gXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgYWRkQVJlY29yZChwcm9wczogT21pdDxBUmVjb3JkUHJvcHMsIFwiem9uZVwiPikge1xuICAgIG5ldyBBUmVjb3JkKHRoaXMsIFwiQVJlY29yZFwiLCB7XG4gICAgICB6b25lOiB0aGlzLmltcG9ydGVkSG9zdGVkWm9uZSxcbiAgICAgIHJlY29yZE5hbWU6IHByb3BzLnJlY29yZE5hbWUsXG4gICAgICBjb21tZW50OlxuICAgICAgICBwcm9wcy5jb21tZW50IHx8IGBBIFJlY29yZCBmb3IgJHt0aGlzLmltcG9ydGVkSG9zdGVkWm9uZS56b25lTmFtZX1gLFxuICAgICAgdGFyZ2V0OiBwcm9wcy50YXJnZXQsXG4gICAgICB0dGw6IHByb3BzLnR0bFxuICAgIH0pO1xuICB9XG5cbiAgcHVibGljIGFkZENuYW1lKHByb3BzOiBPbWl0PENuYW1lUmVjb3JkUHJvcHMsIFwiem9uZVwiPikge1xuICAgIG5ldyBDbmFtZVJlY29yZCh0aGlzLCBgYCwge1xuICAgICAgZG9tYWluTmFtZTogcHJvcHMuZG9tYWluTmFtZSxcbiAgICAgIHpvbmU6IHRoaXMuaW1wb3J0ZWRIb3N0ZWRab25lLFxuICAgICAgY29tbWVudDpcbiAgICAgICAgcHJvcHMuY29tbWVudCB8fCBgQ05BTUUgUmVjb3JkIGZvciAke3RoaXMuaW1wb3J0ZWRIb3N0ZWRab25lLnpvbmVOYW1lfWAsXG4gICAgICB0dGw6IHByb3BzLnR0bFxuICAgIH0pO1xuICB9XG5cbiAgcHVibGljIGFkZE14KHByb3BzOiBPbWl0PE14UmVjb3JkUHJvcHMsIFwiem9uZVwiPikge1xuICAgIG5ldyBNeFJlY29yZCh0aGlzLCBgYCwge1xuICAgICAgem9uZTogdGhpcy5pbXBvcnRlZEhvc3RlZFpvbmUsXG4gICAgICBjb21tZW50OlxuICAgICAgICBwcm9wcy5jb21tZW50IHx8IGBNWCBSZWNvcmQgZm9yICR7dGhpcy5pbXBvcnRlZEhvc3RlZFpvbmUuem9uZU5hbWV9YCxcbiAgICAgIHZhbHVlczogcHJvcHMudmFsdWVzLFxuICAgICAgdHRsOiBwcm9wcy50dGxcbiAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBhZGROUyhwcm9wczogT21pdDxOc1JlY29yZFByb3BzLCBcInpvbmVcIj4pIHtcbiAgICBuZXcgTnNSZWNvcmQodGhpcywgYGAsIHtcbiAgICAgIHpvbmU6IHRoaXMuaW1wb3J0ZWRIb3N0ZWRab25lLFxuICAgICAgY29tbWVudDpcbiAgICAgICAgcHJvcHMuY29tbWVudCB8fCBgTlMgUmVjb3JkIGZvciAke3RoaXMuaW1wb3J0ZWRIb3N0ZWRab25lLnpvbmVOYW1lfWAsXG4gICAgICB2YWx1ZXM6IHByb3BzLnZhbHVlcyxcbiAgICAgIHR0bDogcHJvcHMudHRsXG4gICAgfSk7XG4gIH1cblxuICBwdWJsaWMgYWRkVHh0KHByb3BzOiBPbWl0PFR4dFJlY29yZFByb3BzLCBcInpvbmVcIj4pIHtcbiAgICBuZXcgVHh0UmVjb3JkKHRoaXMsIGBgLCB7XG4gICAgICB6b25lOiB0aGlzLmltcG9ydGVkSG9zdGVkWm9uZSxcbiAgICAgIGNvbW1lbnQ6XG4gICAgICAgIHByb3BzLmNvbW1lbnQgfHwgYFRYVCBSZWNvcmQgZm9yICR7dGhpcy5pbXBvcnRlZEhvc3RlZFpvbmUuem9uZU5hbWV9YCxcbiAgICAgIHZhbHVlczogcHJvcHMudmFsdWVzLFxuICAgICAgdHRsOiBwcm9wcy50dGxcbiAgICB9KTtcbiAgfVxufVxuIl19