@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.
- package/dist/lib/__tests__/setup.d.ts +48 -0
- package/dist/lib/__tests__/setup.js +1 -0
- package/dist/lib/app.d.ts +8 -4
- package/dist/lib/app.js +24 -6
- package/dist/lib/config/aws/accountId.d.ts +6 -0
- package/dist/lib/config/aws/accountId.js +33 -0
- package/dist/lib/config/aws/backupGlobalSettings.d.ts +29 -0
- package/dist/lib/config/aws/backupGlobalSettings.js +51 -0
- package/dist/lib/config/aws/costAllocationTags.d.ts +12 -0
- package/dist/lib/config/aws/costAllocationTags.js +48 -0
- package/dist/lib/config/aws/eventBus.d.ts +7 -0
- package/dist/lib/config/aws/eventBus.js +24 -0
- package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +10 -0
- package/dist/lib/config/aws/identityCenterGroupMembership.js +115 -0
- package/dist/lib/config/aws/ipamDelegateAdmin.d.ts +8 -0
- package/dist/lib/config/aws/ipamDelegateAdmin.js +58 -0
- package/dist/lib/config/aws/ipamPoolId.d.ts +10 -0
- package/dist/lib/config/aws/ipamPoolId.js +39 -0
- package/dist/lib/config/aws/organisation.d.ts +30 -0
- package/dist/lib/config/aws/organisation.js +95 -0
- package/dist/lib/config/aws/organisationId.d.ts +7 -0
- package/dist/lib/config/aws/organisationId.js +46 -0
- package/dist/lib/config/aws/organisationsAccess.d.ts +10 -0
- package/dist/lib/config/aws/organisationsAccess.js +50 -0
- package/dist/lib/config/aws/ramSharing.d.ts +4 -0
- package/dist/lib/config/aws/ramSharing.js +35 -0
- package/dist/lib/config/monitoring.d.ts +18 -0
- package/dist/lib/config/monitoring.js +22 -0
- package/dist/lib/lambda-assets/static-site-forms/asset/index.js +175 -0
- package/dist/lib/lambda-assets/static-site-forms/asset/package.json +4 -0
- package/dist/lib/patterns/aws/cdn.d.ts +19 -2
- package/dist/lib/patterns/aws/cdn.js +13 -2
- package/dist/lib/patterns/aws/cicdRole.d.ts +67 -0
- package/dist/lib/patterns/aws/cicdRole.js +68 -0
- package/dist/lib/patterns/aws/compute.js +9 -0
- package/dist/lib/patterns/aws/computeLambda.d.ts +38 -3
- package/dist/lib/patterns/aws/computeLambda.js +29 -2
- package/dist/lib/patterns/aws/hostedZone.d.ts +28 -0
- package/dist/lib/patterns/aws/hostedZone.js +150 -0
- package/dist/lib/patterns/aws/interfaces/pattern.d.ts +134 -5
- package/dist/lib/patterns/aws/interfaces/pattern.js +6 -0
- package/dist/lib/patterns/aws/managedAccount.d.ts +9 -0
- package/dist/lib/patterns/aws/managedAccount.js +55 -0
- package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +4 -0
- package/dist/lib/patterns/aws/managedIdentityCenter.js +24 -0
- package/dist/lib/patterns/aws/managedOrganisation.d.ts +36 -0
- package/dist/lib/patterns/aws/managedOrganisation.js +97 -0
- package/dist/lib/patterns/aws/managedPlatform.d.ts +12 -0
- package/dist/lib/patterns/aws/managedPlatform.js +29 -0
- package/dist/lib/patterns/aws/pattern.d.ts +20 -5
- package/dist/lib/patterns/aws/pattern.js +12 -6
- package/dist/lib/patterns/aws/staticSite.d.ts +59 -0
- package/dist/lib/patterns/aws/staticSite.js +307 -0
- package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +9 -0
- package/dist/lib/patterns/aws/subdomainHostedZone.js +39 -0
- package/dist/lib/resources/aws/cdn/cloudFront.d.ts +35 -2
- package/dist/lib/resources/aws/cdn/cloudFront.js +63 -25
- package/dist/lib/resources/aws/cdn/index.d.ts +1 -0
- package/dist/lib/resources/aws/cdn/index.js +1 -0
- package/dist/lib/resources/aws/cdn/responseHeadersPolicy.d.ts +56 -0
- package/dist/lib/resources/aws/cdn/responseHeadersPolicy.js +92 -0
- package/dist/lib/resources/aws/cicd/cicdRole.d.ts +65 -0
- package/dist/lib/resources/aws/cicd/cicdRole.js +191 -0
- package/dist/lib/resources/aws/compute/ecsFreeTier.d.ts +75 -0
- package/dist/lib/resources/aws/compute/ecsFreeTier.js +1 -0
- package/dist/lib/resources/aws/compute/ecsImages.js +3 -25
- package/dist/lib/resources/aws/compute/ecsSpot.d.ts +75 -0
- package/dist/lib/resources/aws/compute/ecsSpot.js +1 -0
- package/dist/lib/resources/aws/compute/imageTagParameter.d.ts +12 -0
- package/dist/lib/resources/aws/compute/imageTagParameter.js +32 -0
- package/dist/lib/resources/aws/compute/lambda.d.ts +7 -0
- package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.d.ts +20 -0
- package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.js +1 -0
- package/dist/lib/resources/aws/database/database.d.ts +14 -0
- package/dist/lib/resources/aws/database/database.js +28 -0
- package/dist/lib/resources/aws/database/databaseInstance.d.ts +15 -0
- package/dist/lib/resources/aws/database/databaseInstance.js +30 -0
- package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.d.ts +13 -0
- package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.js +52 -0
- package/dist/lib/resources/aws/iam/securityGroup.d.ts +5 -0
- package/dist/lib/resources/aws/iam/securityGroup.js +14 -0
- package/dist/lib/resources/aws/monitoring/monitoringRole.d.ts +28 -0
- package/dist/lib/resources/aws/monitoring/monitoringRole.js +125 -0
- package/dist/lib/resources/aws/networking/domainCertificate.d.ts +7 -0
- package/dist/lib/resources/aws/networking/domainCertificate.js +9 -7
- package/dist/lib/resources/aws/utilities/cfnOutput.d.ts +5 -0
- package/dist/lib/resources/aws/utilities/cfnOutput.js +1 -0
- package/dist/lib/utils/addSuffixToEmail.js +5 -1
- package/dist/lib/utils/capitalizeString.d.ts +1 -0
- package/dist/lib/utils/capitalizeString.js +7 -0
- package/package.json +7 -5
- package/dist/lib/config/aws/identityCenterMembership.d.ts +0 -11
- 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
|
-
/**
|
|
127
|
-
|
|
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
|