alchemy 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/lib/$.d.ts +12 -0
- package/lib/$.d.ts.map +1 -0
- package/lib/$.js +42 -0
- package/lib/$.js.map +1 -0
- package/lib/alchemize.d.ts.map +1 -1
- package/lib/alchemize.js +7 -0
- package/lib/alchemize.js.map +1 -1
- package/lib/apply.d.ts +2 -2
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +1 -1
- package/lib/apply.js.map +1 -1
- package/lib/aws/account-id.d.ts +5 -0
- package/lib/aws/account-id.d.ts.map +1 -0
- package/lib/aws/account-id.js +10 -0
- package/lib/aws/account-id.js.map +1 -0
- package/lib/aws/index.d.ts +1 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/oidc/github-oidc-provider.d.ts +11 -0
- package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/github-oidc-provider.js +22 -0
- package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
- package/lib/aws/oidc/index.d.ts +3 -0
- package/lib/aws/oidc/index.d.ts.map +1 -0
- package/lib/aws/oidc/index.js +3 -0
- package/lib/aws/oidc/index.js.map +1 -0
- package/lib/aws/oidc/oidc-provider.d.ts +60 -0
- package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/oidc-provider.js +140 -0
- package/lib/aws/oidc/oidc-provider.js.map +1 -0
- package/lib/aws/queue.d.ts +24 -0
- package/lib/aws/queue.d.ts.map +1 -1
- package/lib/aws/queue.js +6 -1
- package/lib/aws/queue.js.map +1 -1
- package/lib/aws/role.d.ts.map +1 -1
- package/lib/aws/role.js +49 -1
- package/lib/aws/role.js.map +1 -1
- package/lib/aws/ses.d.ts.map +1 -1
- package/lib/aws/ses.js +20 -28
- package/lib/aws/ses.js.map +1 -1
- package/lib/aws/table.d.ts.map +1 -1
- package/lib/aws/table.js +95 -73
- package/lib/aws/table.js.map +1 -1
- package/lib/cloudflare/api.d.ts +4 -0
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +6 -0
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +19 -4
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js +0 -5
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +20 -0
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -0
- package/lib/cloudflare/durable-object-namespace.js +21 -0
- package/lib/cloudflare/durable-object-namespace.js.map +1 -0
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +3 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +5 -1
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/static-site-router.d.ts +11 -2
- package/lib/cloudflare/static-site-router.d.ts.map +1 -1
- package/lib/cloudflare/static-site-router.js +98 -76
- package/lib/cloudflare/static-site-router.js.map +1 -1
- package/lib/cloudflare/static-site.d.ts +15 -15
- package/lib/cloudflare/static-site.d.ts.map +1 -1
- package/lib/cloudflare/static-site.js +45 -47
- package/lib/cloudflare/static-site.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +130 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -0
- package/lib/cloudflare/worker-metadata.js +2 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -0
- package/lib/cloudflare/worker-migration.d.ts +110 -0
- package/lib/cloudflare/worker-migration.d.ts.map +1 -0
- package/lib/cloudflare/worker-migration.js +11 -0
- package/lib/cloudflare/worker-migration.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +34 -13
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +341 -321
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/destroy.d.ts +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +2 -1
- package/lib/destroy.js.map +1 -1
- package/lib/esbuild.d.ts +1 -1
- package/lib/esbuild.d.ts.map +1 -1
- package/lib/esbuild.js +3 -1
- package/lib/esbuild.js.map +1 -1
- package/lib/github/client.d.ts +34 -0
- package/lib/github/client.d.ts.map +1 -0
- package/lib/github/client.js +91 -0
- package/lib/github/client.js.map +1 -0
- package/lib/github/index.d.ts +2 -0
- package/lib/github/index.d.ts.map +1 -0
- package/lib/github/index.js +2 -0
- package/lib/github/index.js.map +1 -0
- package/lib/github/secret.d.ts +46 -0
- package/lib/github/secret.d.ts.map +1 -0
- package/lib/github/secret.js +103 -0
- package/lib/github/secret.js.map +1 -0
- package/lib/global.d.ts +0 -2
- package/lib/global.d.ts.map +1 -1
- package/lib/global.js +0 -2
- package/lib/global.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/output.d.ts +4 -3
- package/lib/output.d.ts.map +1 -1
- package/lib/output.js +10 -3
- package/lib/output.js.map +1 -1
- package/lib/print.d.ts +7 -0
- package/lib/print.d.ts.map +1 -0
- package/lib/print.js +10 -0
- package/lib/print.js.map +1 -0
- package/lib/resource.d.ts +8 -4
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +125 -91
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +3 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +4 -12
- package/lib/scope.js.map +1 -1
- package/lib/utils/retry.d.ts +13 -0
- package/lib/utils/retry.d.ts.map +1 -0
- package/lib/utils/retry.js +31 -0
- package/lib/utils/retry.js.map +1 -0
- package/package.json +12 -3
- package/src/$.ts +69 -0
- package/src/alchemize.ts +12 -0
- package/src/apply.ts +4 -4
- package/src/aws/account-id.ts +11 -0
- package/src/aws/index.ts +1 -0
- package/src/aws/oidc/github-oidc-provider.ts +34 -0
- package/src/aws/oidc/index.ts +2 -0
- package/src/aws/oidc/oidc-provider.ts +253 -0
- package/src/aws/queue.ts +23 -1
- package/src/aws/role.ts +69 -0
- package/src/aws/ses.ts +27 -31
- package/src/aws/table.ts +113 -82
- package/src/cloudflare/api.ts +6 -0
- package/src/cloudflare/bindings.ts +43 -26
- package/src/cloudflare/durable-object-namespace.ts +37 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/kv-namespace.ts +12 -1
- package/src/cloudflare/static-site-router.ts +124 -82
- package/src/cloudflare/static-site.ts +66 -69
- package/src/cloudflare/worker-metadata.ts +154 -0
- package/src/cloudflare/worker-migration.ts +145 -0
- package/src/cloudflare/worker.ts +557 -429
- package/src/destroy.ts +2 -2
- package/src/esbuild.ts +3 -0
- package/src/github/client.ts +115 -0
- package/src/github/index.ts +1 -0
- package/src/github/secret.ts +172 -0
- package/src/global.ts +0 -3
- package/src/index.ts +2 -1
- package/src/output.ts +24 -8
- package/src/print.ts +18 -0
- package/src/resource.ts +156 -115
- package/src/scope.ts +7 -12
- package/src/utils/retry.ts +36 -0
package/src/$.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Input } from "./input";
|
|
2
|
+
import { type Output, OutputChain } from "./output";
|
|
3
|
+
import { getScope } from "./scope";
|
|
4
|
+
|
|
5
|
+
export type $<T> = T extends
|
|
6
|
+
| undefined
|
|
7
|
+
| null
|
|
8
|
+
| boolean
|
|
9
|
+
| number
|
|
10
|
+
| string
|
|
11
|
+
| symbol
|
|
12
|
+
| bigint
|
|
13
|
+
? Input<T>
|
|
14
|
+
: T extends (...args: infer Args extends any[]) => infer R
|
|
15
|
+
? (
|
|
16
|
+
...args: {
|
|
17
|
+
[i in keyof Args]: $<Args[i]>;
|
|
18
|
+
}
|
|
19
|
+
) => Output<Awaited<R>>
|
|
20
|
+
: T extends any[]
|
|
21
|
+
? $array<T>
|
|
22
|
+
: $object<T>;
|
|
23
|
+
|
|
24
|
+
type $object<T> = {
|
|
25
|
+
[i in keyof T]: $<T[i]>;
|
|
26
|
+
};
|
|
27
|
+
type $array<T extends any[]> = T extends [infer First, ...infer Rest]
|
|
28
|
+
? [$<First>, ...$object<Rest>]
|
|
29
|
+
: [];
|
|
30
|
+
|
|
31
|
+
export function $<T>(value: T): $<T> {
|
|
32
|
+
return new Proxy(() => {}, {
|
|
33
|
+
apply: (_: T, thisArg, args) => {
|
|
34
|
+
const target = value as (...args: any[]) => any;
|
|
35
|
+
let argsOutput: Output<any[]> | undefined;
|
|
36
|
+
for (const arg of args) {
|
|
37
|
+
if (arg instanceof OutputChain) {
|
|
38
|
+
if (argsOutput) {
|
|
39
|
+
argsOutput = arg.apply((arg) =>
|
|
40
|
+
argsOutput!.apply((...args) => [...args, arg]),
|
|
41
|
+
);
|
|
42
|
+
} else {
|
|
43
|
+
argsOutput = arg.apply((arg) => [arg]);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
if (argsOutput) {
|
|
47
|
+
argsOutput = argsOutput.apply((args) => [...args, arg]);
|
|
48
|
+
} else {
|
|
49
|
+
argsOutput = new OutputChain({} as any, () => [arg]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const scope = getScope();
|
|
55
|
+
const output = argsOutput
|
|
56
|
+
? argsOutput.apply((args) => target(...args))
|
|
57
|
+
: new OutputChain({} as any, () => target());
|
|
58
|
+
scope.callbacks.push(output);
|
|
59
|
+
return $(output);
|
|
60
|
+
},
|
|
61
|
+
get: (_: any, prop) => {
|
|
62
|
+
const target = value as any;
|
|
63
|
+
if (target instanceof OutputChain) {
|
|
64
|
+
return $(target.apply((t) => t[prop]));
|
|
65
|
+
}
|
|
66
|
+
return $(target[prop]);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
package/src/alchemize.ts
CHANGED
|
@@ -51,6 +51,7 @@ export async function alchemize(options?: AlchemizeOptions) {
|
|
|
51
51
|
const stateStore = new (options?.stateStore ?? defaultStateStore)(
|
|
52
52
|
scope.getScopePath(stage),
|
|
53
53
|
);
|
|
54
|
+
const callbacks = scope.callbacks;
|
|
54
55
|
|
|
55
56
|
if (nodes.size > 0) {
|
|
56
57
|
await stateStore.init?.();
|
|
@@ -81,6 +82,17 @@ export async function alchemize(options?: AlchemizeOptions) {
|
|
|
81
82
|
if (options?.destroyOrphans === false) {
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
85
|
+
|
|
86
|
+
await Promise.all(
|
|
87
|
+
callbacks.map((callback) =>
|
|
88
|
+
evaluate(callback, {
|
|
89
|
+
stage,
|
|
90
|
+
scope,
|
|
91
|
+
stateStore,
|
|
92
|
+
quiet: options?.quiet,
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
);
|
|
84
96
|
}
|
|
85
97
|
const aliveIDs = new Set(
|
|
86
98
|
mode === "up"
|
package/src/apply.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultStage, defaultStateStore } from "./global";
|
|
2
|
-
import { type Output, OutputChain, type
|
|
2
|
+
import { type Output, OutputChain, type Resolved } from "./output";
|
|
3
3
|
import {
|
|
4
4
|
Input,
|
|
5
5
|
Provider,
|
|
@@ -25,7 +25,7 @@ export interface ApplyOptions {
|
|
|
25
25
|
export async function apply<T>(
|
|
26
26
|
output: T,
|
|
27
27
|
options?: Partial<ApplyOptions>,
|
|
28
|
-
): Promise<
|
|
28
|
+
): Promise<Resolved<T>> {
|
|
29
29
|
const stage = options?.stage ?? defaultStage;
|
|
30
30
|
const scope = options?.scope ?? getScope();
|
|
31
31
|
const statePath = scope.getScopePath(stage);
|
|
@@ -33,7 +33,7 @@ export async function apply<T>(
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
await evaluate(output, { stage, scope, stateStore, quiet: options?.quiet })
|
|
36
|
-
).value as
|
|
36
|
+
).value as Resolved<T>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
class Evaluated<T> {
|
|
@@ -83,7 +83,7 @@ export async function evaluate<T>(
|
|
|
83
83
|
);
|
|
84
84
|
resolve!(new Evaluated(result, [resourceID, ...deps]));
|
|
85
85
|
} catch (error) {
|
|
86
|
-
console.error(error);
|
|
86
|
+
// console.error(error);
|
|
87
87
|
reject!(error);
|
|
88
88
|
}
|
|
89
89
|
return promise;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
2
|
+
|
|
3
|
+
const sts = new STSClient({});
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper to get the current AWS account ID
|
|
7
|
+
*/
|
|
8
|
+
export async function getAccountId(): Promise<string> {
|
|
9
|
+
const identity = await sts.send(new GetCallerIdentityCommand({}));
|
|
10
|
+
return identity.Account!;
|
|
11
|
+
}
|
package/src/aws/index.ts
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Input } from "../../input";
|
|
2
|
+
import { isOutput } from "../../output";
|
|
3
|
+
import { OIDCProvider, type OIDCProviderProps } from "./oidc-provider";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default thumbprint for GitHub's OIDC provider
|
|
7
|
+
* This is the certificate thumbprint that GitHub uses to sign OIDC tokens
|
|
8
|
+
* @see https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_GITHUB_THUMBPRINT = "6938fd4d98bab03faadb97b34396831e3780aea1";
|
|
11
|
+
|
|
12
|
+
export interface GitHubOIDCProviderProps
|
|
13
|
+
extends Omit<OIDCProviderProps, "thumbprint"> {
|
|
14
|
+
owner: string;
|
|
15
|
+
repository: string;
|
|
16
|
+
thumbprint?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class GitHubOIDCProvider extends OIDCProvider {
|
|
20
|
+
constructor(id: string, props: Input<GitHubOIDCProviderProps>) {
|
|
21
|
+
super(
|
|
22
|
+
id,
|
|
23
|
+
isOutput<GitHubOIDCProviderProps>(props)
|
|
24
|
+
? props.apply((props) => ({
|
|
25
|
+
...props,
|
|
26
|
+
thumbprint: props.thumbprint ?? DEFAULT_GITHUB_THUMBPRINT,
|
|
27
|
+
}))
|
|
28
|
+
: ({
|
|
29
|
+
...props,
|
|
30
|
+
thumbprint: props.thumbprint ?? DEFAULT_GITHUB_THUMBPRINT,
|
|
31
|
+
} as Input<OIDCProviderProps>),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateOpenIDConnectProviderCommand,
|
|
3
|
+
DeleteOpenIDConnectProviderCommand,
|
|
4
|
+
GetOpenIDConnectProviderCommand,
|
|
5
|
+
GetRoleCommand,
|
|
6
|
+
IAMClient,
|
|
7
|
+
type Tag,
|
|
8
|
+
UpdateAssumeRolePolicyCommand,
|
|
9
|
+
} from "@aws-sdk/client-iam";
|
|
10
|
+
import { type Context, Resource } from "../../resource";
|
|
11
|
+
import { getAccountId } from "../account-id";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Properties for configuring an AWS OIDC provider for GitHub Actions
|
|
15
|
+
*/
|
|
16
|
+
export interface OIDCProviderProps {
|
|
17
|
+
/**
|
|
18
|
+
* The GitHub organization or user that owns the repository
|
|
19
|
+
*/
|
|
20
|
+
owner: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The name of the GitHub repository
|
|
24
|
+
*/
|
|
25
|
+
repository: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional list of branches to restrict access to
|
|
29
|
+
* If not provided, all branches will be allowed
|
|
30
|
+
*/
|
|
31
|
+
branches?: string[];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional list of environments to restrict access to
|
|
35
|
+
* If not provided, all environments will be allowed
|
|
36
|
+
*/
|
|
37
|
+
environments?: string[];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The ARN of the IAM role to be assumed
|
|
41
|
+
*/
|
|
42
|
+
roleArn: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Optional maximum session duration in seconds (default: 3600)
|
|
46
|
+
*/
|
|
47
|
+
maxSessionDuration?: number;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Thumbprint for the OIDC provider
|
|
51
|
+
*/
|
|
52
|
+
thumbprint: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional AWS region (defaults to AWS_REGION environment variable)
|
|
56
|
+
*/
|
|
57
|
+
region?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Output returned after OIDC provider configuration
|
|
62
|
+
*/
|
|
63
|
+
export interface OIDCProviderOutput extends OIDCProviderProps {
|
|
64
|
+
/**
|
|
65
|
+
* The ARN of the OIDC provider
|
|
66
|
+
*/
|
|
67
|
+
providerArn: string;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Time at which the provider was created
|
|
71
|
+
*/
|
|
72
|
+
createdAt: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Unique identifier for our trust policy statement
|
|
77
|
+
* Used to track and remove our specific statement without affecting others
|
|
78
|
+
*/
|
|
79
|
+
const TRUST_POLICY_SID = "GitHubOIDCTrust";
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resource for configuring AWS OIDC provider for GitHub Actions
|
|
83
|
+
*/
|
|
84
|
+
export class OIDCProvider extends Resource(
|
|
85
|
+
"aws::OIDCProvider",
|
|
86
|
+
async (ctx: Context<OIDCProviderOutput>, props: OIDCProviderProps) => {
|
|
87
|
+
// Initialize AWS SDK client
|
|
88
|
+
const client = new IAMClient({
|
|
89
|
+
region: props.region,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (ctx.event === "delete") {
|
|
93
|
+
if (ctx.output?.providerArn) {
|
|
94
|
+
try {
|
|
95
|
+
// First, remove our trust policy statement from the role
|
|
96
|
+
const getRole = await client.send(
|
|
97
|
+
new GetRoleCommand({
|
|
98
|
+
RoleName: props.roleArn.split("/").pop(),
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (getRole.Role?.AssumeRolePolicyDocument) {
|
|
103
|
+
const policy = JSON.parse(
|
|
104
|
+
decodeURIComponent(getRole.Role.AssumeRolePolicyDocument),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Remove our specific statement while preserving others
|
|
108
|
+
policy.Statement = policy.Statement.filter(
|
|
109
|
+
(stmt: any) => stmt.Sid !== TRUST_POLICY_SID,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await client.send(
|
|
113
|
+
new UpdateAssumeRolePolicyCommand({
|
|
114
|
+
RoleName: props.roleArn.split("/").pop(),
|
|
115
|
+
PolicyDocument: JSON.stringify(policy),
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Then delete the OIDC provider if we're the last user
|
|
121
|
+
const provider = await client.send(
|
|
122
|
+
new GetOpenIDConnectProviderCommand({
|
|
123
|
+
OpenIDConnectProviderArn: ctx.output.providerArn,
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Only delete the provider if it exists and has no other tags
|
|
128
|
+
if (provider && (!provider.Tags || provider.Tags.length === 0)) {
|
|
129
|
+
await client.send(
|
|
130
|
+
new DeleteOpenIDConnectProviderCommand({
|
|
131
|
+
OpenIDConnectProviderArn: ctx.output.providerArn,
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// Log but don't throw on cleanup errors
|
|
137
|
+
console.error("Error during cleanup:", error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const url = "https://token.actions.githubusercontent.com";
|
|
145
|
+
const thumbprint = props.thumbprint;
|
|
146
|
+
|
|
147
|
+
// Create or get existing OIDC provider
|
|
148
|
+
let providerArn: string;
|
|
149
|
+
try {
|
|
150
|
+
const createProvider = await client.send(
|
|
151
|
+
new CreateOpenIDConnectProviderCommand({
|
|
152
|
+
Url: url,
|
|
153
|
+
ClientIDList: ["sts.amazonaws.com"],
|
|
154
|
+
ThumbprintList: [thumbprint],
|
|
155
|
+
Tags: [
|
|
156
|
+
{
|
|
157
|
+
Key: "ManagedBy",
|
|
158
|
+
Value: "alchemy",
|
|
159
|
+
},
|
|
160
|
+
] as Tag[],
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
providerArn = createProvider.OpenIDConnectProviderArn!;
|
|
164
|
+
} catch (error: any) {
|
|
165
|
+
if (error.name === "EntityAlreadyExistsException") {
|
|
166
|
+
// Provider exists, use its ARN
|
|
167
|
+
providerArn = `arn:aws:iam::${await getAccountId()}:oidc-provider/${url}`;
|
|
168
|
+
} else {
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Get current role policy
|
|
174
|
+
const getRole = await client.send(
|
|
175
|
+
new GetRoleCommand({
|
|
176
|
+
RoleName: props.roleArn.split("/").pop(),
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
let policy: any;
|
|
181
|
+
if (getRole.Role?.AssumeRolePolicyDocument) {
|
|
182
|
+
policy = JSON.parse(
|
|
183
|
+
decodeURIComponent(getRole.Role.AssumeRolePolicyDocument),
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
policy = {
|
|
187
|
+
Version: "2012-10-17",
|
|
188
|
+
Statement: [],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Construct the trust policy conditions
|
|
193
|
+
const conditions: Record<string, any> = {
|
|
194
|
+
StringEquals: {
|
|
195
|
+
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
|
|
196
|
+
"token.actions.githubusercontent.com:sub": `repo:${props.owner}/${props.repository}:`,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (props.branches?.length) {
|
|
201
|
+
conditions.StringEquals["token.actions.githubusercontent.com:sub"] =
|
|
202
|
+
props.branches.map(
|
|
203
|
+
(branch) =>
|
|
204
|
+
`repo:${props.owner}/${props.repository}:ref:refs/heads/${branch}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (props.environments?.length) {
|
|
209
|
+
conditions.StringEquals["token.actions.githubusercontent.com:sub"] =
|
|
210
|
+
props.environments.map(
|
|
211
|
+
(env) =>
|
|
212
|
+
`repo:${props.owner}/${props.repository}:environment:${env}`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Create our policy statement
|
|
217
|
+
const ourStatement = {
|
|
218
|
+
Sid: TRUST_POLICY_SID,
|
|
219
|
+
Effect: "Allow",
|
|
220
|
+
Principal: {
|
|
221
|
+
Federated: providerArn,
|
|
222
|
+
},
|
|
223
|
+
Action: "sts:AssumeRoleWithWebIdentity",
|
|
224
|
+
Condition: conditions,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Remove any existing statement with our SID and add the new one
|
|
228
|
+
policy.Statement = [
|
|
229
|
+
...policy.Statement.filter(
|
|
230
|
+
(stmt: any) => stmt.Sid !== TRUST_POLICY_SID,
|
|
231
|
+
),
|
|
232
|
+
ourStatement,
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
// Update the role's trust policy
|
|
236
|
+
await client.send(
|
|
237
|
+
new UpdateAssumeRolePolicyCommand({
|
|
238
|
+
RoleName: props.roleArn.split("/").pop(),
|
|
239
|
+
PolicyDocument: JSON.stringify(policy),
|
|
240
|
+
}),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
...props,
|
|
245
|
+
providerArn,
|
|
246
|
+
createdAt: Date.now(),
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error("Error configuring OIDC provider:", error);
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
) {}
|
package/src/aws/queue.ts
CHANGED
|
@@ -8,14 +8,30 @@ import {
|
|
|
8
8
|
import { Resource } from "../resource";
|
|
9
9
|
|
|
10
10
|
export interface QueueProps {
|
|
11
|
+
/**
|
|
12
|
+
* Name of the queue
|
|
13
|
+
* For FIFO queues, the name must end with the .fifo suffix
|
|
14
|
+
*/
|
|
11
15
|
queueName: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether this is a FIFO queue.
|
|
19
|
+
* If true, the queueName must end with .fifo suffix
|
|
20
|
+
*/
|
|
12
21
|
fifo?: boolean;
|
|
22
|
+
|
|
13
23
|
visibilityTimeout?: number;
|
|
14
24
|
messageRetentionPeriod?: number;
|
|
15
25
|
maximumMessageSize?: number;
|
|
16
26
|
delaySeconds?: number;
|
|
17
27
|
receiveMessageWaitTimeSeconds?: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Enables content-based deduplication for FIFO queues.
|
|
31
|
+
* Only applicable when fifo is true.
|
|
32
|
+
*/
|
|
18
33
|
contentBasedDeduplication?: boolean;
|
|
34
|
+
|
|
19
35
|
deduplicationScope?: "messageGroup" | "queue";
|
|
20
36
|
fifoThroughputLimit?: "perQueue" | "perMessageGroupId";
|
|
21
37
|
tags?: Record<string, string>;
|
|
@@ -31,7 +47,13 @@ export class Queue extends Resource(
|
|
|
31
47
|
"sqs::Queue",
|
|
32
48
|
async (ctx, props: QueueProps) => {
|
|
33
49
|
const client = new SQSClient({});
|
|
34
|
-
|
|
50
|
+
// Don't automatically add .fifo suffix - user must include it in queueName
|
|
51
|
+
const queueName = props.queueName;
|
|
52
|
+
|
|
53
|
+
// Validate that FIFO queues have .fifo suffix
|
|
54
|
+
if (props.fifo && !queueName.endsWith(".fifo")) {
|
|
55
|
+
throw new Error("FIFO queue names must end with .fifo suffix");
|
|
56
|
+
}
|
|
35
57
|
|
|
36
58
|
if (ctx.event === "delete") {
|
|
37
59
|
try {
|
package/src/aws/role.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AttachRolePolicyCommand,
|
|
2
3
|
CreateRoleCommand,
|
|
3
4
|
DeleteRoleCommand,
|
|
4
5
|
DeleteRolePolicyCommand,
|
|
6
|
+
DetachRolePolicyCommand,
|
|
5
7
|
EntityAlreadyExistsException,
|
|
6
8
|
GetRoleCommand,
|
|
7
9
|
IAMClient,
|
|
10
|
+
ListAttachedRolePoliciesCommand,
|
|
8
11
|
NoSuchEntityException,
|
|
9
12
|
PutRolePolicyCommand,
|
|
10
13
|
type Tag,
|
|
@@ -59,6 +62,32 @@ export class Role extends Resource(
|
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
// We need to detach managed policies before deleting the role
|
|
66
|
+
// First, get all attached policies
|
|
67
|
+
try {
|
|
68
|
+
const attachedPoliciesResponse = await client.send(
|
|
69
|
+
new ListAttachedRolePoliciesCommand({
|
|
70
|
+
RoleName: props.roleName,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Detach all managed policies
|
|
75
|
+
const attachedPolicies =
|
|
76
|
+
attachedPoliciesResponse.AttachedPolicies || [];
|
|
77
|
+
for (const policy of attachedPolicies) {
|
|
78
|
+
await client.send(
|
|
79
|
+
new DetachRolePolicyCommand({
|
|
80
|
+
RoleName: props.roleName,
|
|
81
|
+
PolicyArn: policy.PolicyArn!,
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
} catch (error: any) {
|
|
86
|
+
if (error.name !== NoSuchEntityException.name) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
await ignore(NoSuchEntityException.name, () =>
|
|
63
92
|
client.send(
|
|
64
93
|
new DeleteRoleCommand({
|
|
@@ -229,6 +258,46 @@ export class Role extends Resource(
|
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
|
|
261
|
+
// Handle managed policy attachments
|
|
262
|
+
// Get currently attached policies
|
|
263
|
+
const attachedPoliciesResponse = await client.send(
|
|
264
|
+
new ListAttachedRolePoliciesCommand({
|
|
265
|
+
RoleName: props.roleName,
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const currentAttachedPolicies =
|
|
270
|
+
attachedPoliciesResponse.AttachedPolicies || [];
|
|
271
|
+
const currentPolicyArns = currentAttachedPolicies.map((p) => p.PolicyArn!);
|
|
272
|
+
|
|
273
|
+
// If we're updating, use an empty array as default when managedPolicyArns is undefined
|
|
274
|
+
// to ensure we detach all managed policies
|
|
275
|
+
const desiredPolicyArns = props.managedPolicyArns || [];
|
|
276
|
+
|
|
277
|
+
// Detach policies that are no longer needed
|
|
278
|
+
for (const policyArn of currentPolicyArns) {
|
|
279
|
+
if (!desiredPolicyArns.includes(policyArn)) {
|
|
280
|
+
await client.send(
|
|
281
|
+
new DetachRolePolicyCommand({
|
|
282
|
+
RoleName: props.roleName,
|
|
283
|
+
PolicyArn: policyArn,
|
|
284
|
+
}),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Attach new policies that weren't attached before
|
|
290
|
+
for (const policyArn of desiredPolicyArns) {
|
|
291
|
+
if (!currentPolicyArns.includes(policyArn)) {
|
|
292
|
+
await client.send(
|
|
293
|
+
new AttachRolePolicyCommand({
|
|
294
|
+
RoleName: props.roleName,
|
|
295
|
+
PolicyArn: policyArn,
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
232
301
|
if (!role?.Role) {
|
|
233
302
|
throw new Error(`Failed to create or update role ${props.roleName}`);
|
|
234
303
|
}
|