alchemy 0.0.8 → 0.1.1
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/package.json +23 -22
- package/src/alchemize.ts +151 -0
- package/src/apply.ts +117 -0
- package/src/components/agent/agent.ts +45 -0
- package/src/components/agent/anthropic.ts +27 -0
- package/src/components/agent/index.ts +4 -0
- package/src/components/agent/model.ts +22 -0
- package/src/components/agent/openai.ts +44 -0
- package/src/components/agent/package.ts +126 -0
- package/src/components/agent/requirements.ts +111 -0
- package/src/components/agent/scrape.ts +47 -0
- package/src/components/agent/tsconfig.ts +150 -0
- package/src/components/agent/typescript.ts +177 -0
- package/src/components/aws/bucket.ts +168 -0
- package/src/components/aws/function.ts +302 -0
- package/src/components/aws/index.ts +6 -0
- package/src/components/aws/policy.ts +245 -0
- package/src/components/aws/queue.ts +222 -0
- package/src/components/aws/role.ts +245 -0
- package/src/components/aws/table.ts +172 -0
- package/src/components/esbuild.ts +128 -0
- package/src/components/fs.ts +45 -0
- package/src/destroy.ts +63 -0
- package/src/error.ts +16 -0
- package/src/global.ts +72 -0
- package/src/index.ts +6 -0
- package/src/input.ts +16 -0
- package/src/main.ts +34 -0
- package/src/output.ts +49 -0
- package/src/resource.ts +313 -0
- package/src/state.ts +100 -0
- package/test/aws/function.test.ts +144 -0
- package/test/aws/queue.test.ts +85 -0
- package/test/aws/role.test.ts +153 -0
- package/test/aws/table.test.ts +73 -0
- package/test/esbuild.test.ts +41 -0
- package/test/handler.ts +9 -0
- package/tsconfig.json +7 -0
- package/Column.js +0 -1
- package/Create.js +0 -1
- package/Delete.js +0 -1
- package/Drop.js +0 -1
- package/Insert.js +0 -1
- package/Makefile +0 -8
- package/README.md +0 -0
- package/Select.js +0 -1
- package/Table.js +0 -1
- package/Transaction.js +0 -1
- package/Types.js +0 -1
- package/Update.js +0 -1
- package/alchemy.js +0 -57
- package/alchemy.sublime-project +0 -9
- package/alchemy.sublime-workspace +0 -2304
- package/config/app.js +0 -8
- package/config/db.js +0 -20
- package/copy.js +0 -1
- package/defaultEngine.js +0 -8
- package/engine.js +0 -282
- package/getInstantiatorFunction.js +0 -28
- package/index.js +0 -52
- package/jstest.js +0 -3
- package/loggerFactory.js +0 -27
- package/logs/alchemy.log +0 -46
- package/logs/sql.log +0 -41
- package/sql/Column.js +0 -158
- package/sql/Columnable.js +0 -21
- package/sql/Create.js +0 -15
- package/sql/Delete.js +0 -34
- package/sql/Drop.js +0 -36
- package/sql/Insert.js +0 -96
- package/sql/SchemaElement.js +0 -11
- package/sql/Select.js +0 -192
- package/sql/SqlBase.js +0 -134
- package/sql/SqlStatement.js +0 -80
- package/sql/Table.js +0 -96
- package/sql/Transaction.js +0 -45
- package/sql/Update.js +0 -105
- package/sql/defineElement.js +0 -26
- package/sql/defineStatement.js +0 -26
- package/sql/types/BaseType.js +0 -43
- package/sql/types/BigSerialType.js +0 -5
- package/sql/types/IntegerType.js +0 -5
- package/sql/types/JsonType.js +0 -5
- package/sql/types/SerialType.js +0 -5
- package/sql/types/StringType.js +0 -31
- package/sql/types/TimestampType.js +0 -5
- package/sql/types/UUIDType.js +0 -5
- package/sql/types/defineType.js +0 -25
- package/sql/types/getType.js +0 -12
- package/sql/types/index.js +0 -11
- package/sql/where.js +0 -31
- package/sql/whereToString.js +0 -29
- package/test/ColumnTests.js +0 -80
- package/test/CreateTable.js +0 -54
- package/test/DeleteTests.js +0 -28
- package/test/DropTests.js +0 -23
- package/test/GetTypeTests.js +0 -56
- package/test/InsertTests.js +0 -86
- package/test/SelectExistsTests.js +0 -26
- package/test/SelectTests.js +0 -130
- package/test/TableTests.js +0 -46
- package/test.js +0 -8
- package/test.ls +0 -4
- package/validate.js +0 -26
- package/x.js +0 -5
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateQueueCommand,
|
|
3
|
+
DeleteQueueCommand,
|
|
4
|
+
GetQueueAttributesCommand,
|
|
5
|
+
GetQueueUrlCommand,
|
|
6
|
+
SQSClient,
|
|
7
|
+
} from "@aws-sdk/client-sqs";
|
|
8
|
+
import { Resource } from "../../resource";
|
|
9
|
+
|
|
10
|
+
export interface QueueProps {
|
|
11
|
+
queueName: string;
|
|
12
|
+
fifo?: boolean;
|
|
13
|
+
visibilityTimeout?: number;
|
|
14
|
+
messageRetentionPeriod?: number;
|
|
15
|
+
maximumMessageSize?: number;
|
|
16
|
+
delaySeconds?: number;
|
|
17
|
+
receiveMessageWaitTimeSeconds?: number;
|
|
18
|
+
contentBasedDeduplication?: boolean;
|
|
19
|
+
deduplicationScope?: "messageGroup" | "queue";
|
|
20
|
+
fifoThroughputLimit?: "perQueue" | "perMessageGroupId";
|
|
21
|
+
tags?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface QueueOutput extends QueueProps {
|
|
25
|
+
id: string; // Same as queueName
|
|
26
|
+
arn: string;
|
|
27
|
+
url: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class Queue extends Resource(
|
|
31
|
+
"sqs::Queue",
|
|
32
|
+
async (ctx, props: QueueProps) => {
|
|
33
|
+
const client = new SQSClient({});
|
|
34
|
+
const queueName = props.fifo ? `${props.queueName}.fifo` : props.queueName;
|
|
35
|
+
|
|
36
|
+
if (ctx.event === "delete") {
|
|
37
|
+
try {
|
|
38
|
+
// Get queue URL first
|
|
39
|
+
const urlResponse = await client.send(
|
|
40
|
+
new GetQueueUrlCommand({
|
|
41
|
+
QueueName: queueName,
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Delete the queue
|
|
46
|
+
await client.send(
|
|
47
|
+
new DeleteQueueCommand({
|
|
48
|
+
QueueUrl: urlResponse.QueueUrl,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Wait for queue to be deleted
|
|
53
|
+
let queueDeleted = false;
|
|
54
|
+
while (!queueDeleted) {
|
|
55
|
+
try {
|
|
56
|
+
await client.send(
|
|
57
|
+
new GetQueueUrlCommand({
|
|
58
|
+
QueueName: queueName,
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
// If we get here, queue still exists
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
63
|
+
} catch (error: any) {
|
|
64
|
+
if (error.name === "QueueDoesNotExist") {
|
|
65
|
+
queueDeleted = true;
|
|
66
|
+
} else {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
if (error.name !== "QueueDoesNotExist") {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
...props,
|
|
79
|
+
id: props.queueName,
|
|
80
|
+
arn: `arn:aws:sqs:${client.config.region}:${process.env.AWS_ACCOUNT_ID}:${queueName}`,
|
|
81
|
+
url: "",
|
|
82
|
+
};
|
|
83
|
+
} else {
|
|
84
|
+
// Create queue with attributes
|
|
85
|
+
const attributes: Record<string, string> = {};
|
|
86
|
+
|
|
87
|
+
if (props.visibilityTimeout !== undefined) {
|
|
88
|
+
attributes["VisibilityTimeout"] = props.visibilityTimeout.toString();
|
|
89
|
+
}
|
|
90
|
+
if (props.messageRetentionPeriod !== undefined) {
|
|
91
|
+
attributes["MessageRetentionPeriod"] =
|
|
92
|
+
props.messageRetentionPeriod.toString();
|
|
93
|
+
}
|
|
94
|
+
if (props.maximumMessageSize !== undefined) {
|
|
95
|
+
attributes["MaximumMessageSize"] = props.maximumMessageSize.toString();
|
|
96
|
+
}
|
|
97
|
+
if (props.delaySeconds !== undefined) {
|
|
98
|
+
attributes["DelaySeconds"] = props.delaySeconds.toString();
|
|
99
|
+
}
|
|
100
|
+
if (props.receiveMessageWaitTimeSeconds !== undefined) {
|
|
101
|
+
attributes["ReceiveMessageWaitTimeSeconds"] =
|
|
102
|
+
props.receiveMessageWaitTimeSeconds.toString();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// FIFO specific attributes
|
|
106
|
+
if (props.fifo) {
|
|
107
|
+
attributes["FifoQueue"] = "true";
|
|
108
|
+
if (props.contentBasedDeduplication) {
|
|
109
|
+
attributes["ContentBasedDeduplication"] = "true";
|
|
110
|
+
}
|
|
111
|
+
if (props.deduplicationScope) {
|
|
112
|
+
attributes["DeduplicationScope"] = props.deduplicationScope;
|
|
113
|
+
}
|
|
114
|
+
if (props.fifoThroughputLimit) {
|
|
115
|
+
attributes["FifoThroughputLimit"] = props.fifoThroughputLimit;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Convert tags to AWS format
|
|
120
|
+
const tags = props.tags
|
|
121
|
+
? Object.entries(props.tags).reduce(
|
|
122
|
+
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
|
123
|
+
{},
|
|
124
|
+
)
|
|
125
|
+
: undefined;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// Create the queue
|
|
129
|
+
const createResponse = await client.send(
|
|
130
|
+
new CreateQueueCommand({
|
|
131
|
+
QueueName: queueName,
|
|
132
|
+
Attributes: attributes,
|
|
133
|
+
tags,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Get queue attributes
|
|
138
|
+
const attributesResponse = await client.send(
|
|
139
|
+
new GetQueueAttributesCommand({
|
|
140
|
+
QueueUrl: createResponse.QueueUrl,
|
|
141
|
+
AttributeNames: ["QueueArn"],
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...props,
|
|
147
|
+
id: props.queueName,
|
|
148
|
+
arn: attributesResponse.Attributes!.QueueArn!,
|
|
149
|
+
url: createResponse.QueueUrl!,
|
|
150
|
+
};
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
if (error.name === "QueueAlreadyExists") {
|
|
153
|
+
// Get existing queue URL
|
|
154
|
+
const urlResponse = await client.send(
|
|
155
|
+
new GetQueueUrlCommand({
|
|
156
|
+
QueueName: queueName,
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Get queue attributes
|
|
161
|
+
const attributesResponse = await client.send(
|
|
162
|
+
new GetQueueAttributesCommand({
|
|
163
|
+
QueueUrl: urlResponse.QueueUrl,
|
|
164
|
+
AttributeNames: ["QueueArn"],
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
...props,
|
|
170
|
+
id: props.queueName,
|
|
171
|
+
arn: attributesResponse.Attributes!.QueueArn!,
|
|
172
|
+
url: urlResponse.QueueUrl!,
|
|
173
|
+
};
|
|
174
|
+
} else if (error.name === "QueueDeletedRecently") {
|
|
175
|
+
// Queue was recently deleted, wait and retry
|
|
176
|
+
const maxRetries = 3;
|
|
177
|
+
let retryCount = 0;
|
|
178
|
+
|
|
179
|
+
while (retryCount < maxRetries) {
|
|
180
|
+
try {
|
|
181
|
+
// Wait for 60 seconds before retrying
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 61000));
|
|
183
|
+
|
|
184
|
+
// Retry creating the queue
|
|
185
|
+
const createResponse = await client.send(
|
|
186
|
+
new CreateQueueCommand({
|
|
187
|
+
QueueName: queueName,
|
|
188
|
+
Attributes: attributes,
|
|
189
|
+
tags,
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Get queue attributes
|
|
194
|
+
const attributesResponse = await client.send(
|
|
195
|
+
new GetQueueAttributesCommand({
|
|
196
|
+
QueueUrl: createResponse.QueueUrl,
|
|
197
|
+
AttributeNames: ["QueueArn"],
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
...props,
|
|
203
|
+
id: props.queueName,
|
|
204
|
+
arn: attributesResponse.Attributes!.QueueArn!,
|
|
205
|
+
url: createResponse.QueueUrl!,
|
|
206
|
+
};
|
|
207
|
+
} catch (retryError: any) {
|
|
208
|
+
if (
|
|
209
|
+
retryError.name !== "QueueDeletedRecently" ||
|
|
210
|
+
retryCount === maxRetries - 1
|
|
211
|
+
) {
|
|
212
|
+
throw retryError;
|
|
213
|
+
}
|
|
214
|
+
retryCount++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
) {}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateRoleCommand,
|
|
3
|
+
DeleteRoleCommand,
|
|
4
|
+
DeleteRolePolicyCommand,
|
|
5
|
+
EntityAlreadyExistsException,
|
|
6
|
+
GetRoleCommand,
|
|
7
|
+
IAMClient,
|
|
8
|
+
NoSuchEntityException,
|
|
9
|
+
PutRolePolicyCommand,
|
|
10
|
+
type Tag,
|
|
11
|
+
TagRoleCommand,
|
|
12
|
+
UpdateAssumeRolePolicyCommand,
|
|
13
|
+
UpdateRoleCommand,
|
|
14
|
+
} from "@aws-sdk/client-iam";
|
|
15
|
+
import { ignore } from "../../error";
|
|
16
|
+
import { type Context, Resource } from "../../resource";
|
|
17
|
+
import type { PolicyDocument } from "./policy";
|
|
18
|
+
|
|
19
|
+
export interface RoleProps {
|
|
20
|
+
roleName: string;
|
|
21
|
+
assumeRolePolicy: PolicyDocument;
|
|
22
|
+
description?: string;
|
|
23
|
+
path?: string;
|
|
24
|
+
maxSessionDuration?: number;
|
|
25
|
+
permissionsBoundary?: string;
|
|
26
|
+
policies?: Array<{
|
|
27
|
+
policyName: string;
|
|
28
|
+
policyDocument: PolicyDocument;
|
|
29
|
+
}>;
|
|
30
|
+
managedPolicyArns?: string[];
|
|
31
|
+
tags?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RoleOutput extends RoleProps {
|
|
35
|
+
id: string; // Same as roleName
|
|
36
|
+
arn: string;
|
|
37
|
+
uniqueId: string; // Unique identifier for the role
|
|
38
|
+
roleId: string; // The stable and unique string identifying the role
|
|
39
|
+
createDate: Date;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class Role extends Resource(
|
|
43
|
+
"iam::Role",
|
|
44
|
+
async (ctx: Context<RoleOutput>, props: RoleProps): Promise<RoleOutput> => {
|
|
45
|
+
const client = new IAMClient({});
|
|
46
|
+
|
|
47
|
+
if (ctx.event === "delete") {
|
|
48
|
+
// Delete any inline policies first
|
|
49
|
+
if (props.policies) {
|
|
50
|
+
for (const policy of props.policies) {
|
|
51
|
+
await ignore(NoSuchEntityException.name, () =>
|
|
52
|
+
client.send(
|
|
53
|
+
new DeleteRolePolicyCommand({
|
|
54
|
+
RoleName: props.roleName,
|
|
55
|
+
PolicyName: policy.policyName,
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await ignore(NoSuchEntityException.name, () =>
|
|
63
|
+
client.send(
|
|
64
|
+
new DeleteRoleCommand({
|
|
65
|
+
RoleName: props.roleName,
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
...props,
|
|
71
|
+
id: props.roleName,
|
|
72
|
+
arn: `arn:aws:iam::${process.env.AWS_ACCOUNT_ID}:role/${props.roleName}`,
|
|
73
|
+
uniqueId: "",
|
|
74
|
+
roleId: "",
|
|
75
|
+
createDate: new Date(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const assumeRolePolicyDocument = JSON.stringify(props.assumeRolePolicy);
|
|
80
|
+
let role;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
if (ctx.event === "create") {
|
|
84
|
+
// Try to create the role
|
|
85
|
+
await client.send(
|
|
86
|
+
new CreateRoleCommand({
|
|
87
|
+
RoleName: props.roleName,
|
|
88
|
+
AssumeRolePolicyDocument: assumeRolePolicyDocument,
|
|
89
|
+
Description: props.description,
|
|
90
|
+
Path: props.path,
|
|
91
|
+
MaxSessionDuration: props.maxSessionDuration,
|
|
92
|
+
PermissionsBoundary: props.permissionsBoundary,
|
|
93
|
+
Tags: [
|
|
94
|
+
...Object.entries(props.tags || {}).map(([Key, Value]) => ({
|
|
95
|
+
Key,
|
|
96
|
+
Value,
|
|
97
|
+
})),
|
|
98
|
+
{
|
|
99
|
+
Key: "alchemy_stage",
|
|
100
|
+
Value: ctx.stage,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
Key: "alchemy_resource",
|
|
104
|
+
Value: ctx.resourceID,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
} catch (error: any) {
|
|
111
|
+
if (
|
|
112
|
+
error instanceof EntityAlreadyExistsException &&
|
|
113
|
+
ctx.event === "create"
|
|
114
|
+
) {
|
|
115
|
+
// Check if we were the ones who created it
|
|
116
|
+
const existingRole = await client.send(
|
|
117
|
+
new GetRoleCommand({
|
|
118
|
+
RoleName: props.roleName,
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
const roleTags =
|
|
122
|
+
existingRole.Role?.Tags?.reduce(
|
|
123
|
+
(acc, tag) => {
|
|
124
|
+
acc[tag.Key!] = tag.Value!;
|
|
125
|
+
return acc;
|
|
126
|
+
},
|
|
127
|
+
{} as Record<string, string>,
|
|
128
|
+
) || {};
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
roleTags.alchemy_stage !== ctx.stage ||
|
|
132
|
+
roleTags.alchemy_resource !== ctx.resourceID
|
|
133
|
+
) {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
} else if (error.name !== NoSuchEntityException.name) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Get or update the role
|
|
142
|
+
role = await client.send(
|
|
143
|
+
new GetRoleCommand({
|
|
144
|
+
RoleName: props.roleName,
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Update assume role policy if it changed
|
|
149
|
+
if (role.Role?.AssumeRolePolicyDocument !== assumeRolePolicyDocument) {
|
|
150
|
+
await client.send(
|
|
151
|
+
new UpdateAssumeRolePolicyCommand({
|
|
152
|
+
RoleName: props.roleName,
|
|
153
|
+
PolicyDocument: assumeRolePolicyDocument,
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update role description and max session duration if they changed
|
|
159
|
+
if (
|
|
160
|
+
role.Role?.Description !== props.description ||
|
|
161
|
+
role.Role?.MaxSessionDuration !== props.maxSessionDuration
|
|
162
|
+
) {
|
|
163
|
+
await client.send(
|
|
164
|
+
new UpdateRoleCommand({
|
|
165
|
+
RoleName: props.roleName,
|
|
166
|
+
Description: props.description,
|
|
167
|
+
MaxSessionDuration: props.maxSessionDuration,
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Update tags
|
|
173
|
+
const newTags = {
|
|
174
|
+
...props.tags,
|
|
175
|
+
alchemy_stage: ctx.stage,
|
|
176
|
+
alchemy_resource: ctx.resourceID,
|
|
177
|
+
};
|
|
178
|
+
const tags: Tag[] = Object.entries(newTags).map(([Key, Value]) => ({
|
|
179
|
+
Key,
|
|
180
|
+
Value,
|
|
181
|
+
}));
|
|
182
|
+
await client.send(
|
|
183
|
+
new TagRoleCommand({
|
|
184
|
+
RoleName: props.roleName,
|
|
185
|
+
Tags: tags,
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Handle policy changes
|
|
190
|
+
const previousPolicies =
|
|
191
|
+
ctx.event === "update" ? ctx.output.policies || [] : [];
|
|
192
|
+
const currentPolicies = props.policies || [];
|
|
193
|
+
|
|
194
|
+
// Delete policies that were removed
|
|
195
|
+
for (const oldPolicy of previousPolicies) {
|
|
196
|
+
if (
|
|
197
|
+
!currentPolicies.some(
|
|
198
|
+
(p: { policyName: string }) => p.policyName === oldPolicy.policyName,
|
|
199
|
+
)
|
|
200
|
+
) {
|
|
201
|
+
await ignore(NoSuchEntityException.name, () =>
|
|
202
|
+
client.send(
|
|
203
|
+
new DeleteRolePolicyCommand({
|
|
204
|
+
RoleName: props.roleName,
|
|
205
|
+
PolicyName: oldPolicy.policyName,
|
|
206
|
+
}),
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Update or create policies
|
|
213
|
+
for (const policy of currentPolicies) {
|
|
214
|
+
const oldPolicy = previousPolicies.find(
|
|
215
|
+
(p) => p.policyName === policy.policyName,
|
|
216
|
+
);
|
|
217
|
+
if (
|
|
218
|
+
!oldPolicy ||
|
|
219
|
+
JSON.stringify(oldPolicy.policyDocument) !==
|
|
220
|
+
JSON.stringify(policy.policyDocument)
|
|
221
|
+
) {
|
|
222
|
+
await client.send(
|
|
223
|
+
new PutRolePolicyCommand({
|
|
224
|
+
RoleName: props.roleName,
|
|
225
|
+
PolicyName: policy.policyName,
|
|
226
|
+
PolicyDocument: JSON.stringify(policy.policyDocument),
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!role?.Role) {
|
|
233
|
+
throw new Error(`Failed to create or update role ${props.roleName}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
...props,
|
|
238
|
+
id: props.roleName,
|
|
239
|
+
arn: role.Role.Arn!,
|
|
240
|
+
uniqueId: role.Role.RoleId!,
|
|
241
|
+
roleId: role.Role.RoleId!,
|
|
242
|
+
createDate: role.Role.CreateDate!,
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
) {}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateTableCommand,
|
|
3
|
+
DeleteTableCommand,
|
|
4
|
+
DescribeTableCommand,
|
|
5
|
+
DynamoDBClient,
|
|
6
|
+
type KeySchemaElement,
|
|
7
|
+
ResourceInUseException,
|
|
8
|
+
ResourceNotFoundException,
|
|
9
|
+
} from "@aws-sdk/client-dynamodb";
|
|
10
|
+
import { ignore } from "../../error";
|
|
11
|
+
import { type Context, Resource } from "../../resource";
|
|
12
|
+
|
|
13
|
+
export interface TableProps {
|
|
14
|
+
tableName: string;
|
|
15
|
+
partitionKey: {
|
|
16
|
+
name: string;
|
|
17
|
+
type: "S" | "N" | "B";
|
|
18
|
+
};
|
|
19
|
+
sortKey?: {
|
|
20
|
+
name: string;
|
|
21
|
+
type: "S" | "N" | "B";
|
|
22
|
+
};
|
|
23
|
+
billingMode?: "PROVISIONED" | "PAY_PER_REQUEST";
|
|
24
|
+
readCapacity?: number;
|
|
25
|
+
writeCapacity?: number;
|
|
26
|
+
tags?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TableOutput extends TableProps {
|
|
30
|
+
id: string; // Same as tableName
|
|
31
|
+
arn: string;
|
|
32
|
+
streamArn?: string;
|
|
33
|
+
tableId: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Table extends Resource(
|
|
37
|
+
"dynamo::Table",
|
|
38
|
+
async (
|
|
39
|
+
ctx: Context<TableOutput>,
|
|
40
|
+
props: TableProps,
|
|
41
|
+
): Promise<TableOutput> => {
|
|
42
|
+
const client = new DynamoDBClient({});
|
|
43
|
+
|
|
44
|
+
if (ctx.event === "delete") {
|
|
45
|
+
await ignore(ResourceNotFoundException.name, () =>
|
|
46
|
+
client.send(
|
|
47
|
+
new DeleteTableCommand({
|
|
48
|
+
TableName: props.tableName,
|
|
49
|
+
}),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Wait for table to be deleted
|
|
54
|
+
let tableDeleted = false;
|
|
55
|
+
while (!tableDeleted) {
|
|
56
|
+
try {
|
|
57
|
+
await client.send(
|
|
58
|
+
new DescribeTableCommand({
|
|
59
|
+
TableName: props.tableName,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
// If we get here, table still exists
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof ResourceNotFoundException) {
|
|
66
|
+
tableDeleted = true;
|
|
67
|
+
} else {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...props,
|
|
75
|
+
id: props.tableName,
|
|
76
|
+
arn: `arn:aws:dynamodb:${client.config.region}:${process.env.AWS_ACCOUNT_ID}:table/${props.tableName}`,
|
|
77
|
+
tableId: "",
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
try {
|
|
81
|
+
const attributeDefinitions = [
|
|
82
|
+
{
|
|
83
|
+
AttributeName: props.partitionKey.name,
|
|
84
|
+
AttributeType: props.partitionKey.type,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const keySchema: KeySchemaElement[] = [
|
|
89
|
+
{
|
|
90
|
+
AttributeName: props.partitionKey.name,
|
|
91
|
+
KeyType: "HASH",
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
if (props.sortKey) {
|
|
96
|
+
attributeDefinitions.push({
|
|
97
|
+
AttributeName: props.sortKey.name,
|
|
98
|
+
AttributeType: props.sortKey.type,
|
|
99
|
+
});
|
|
100
|
+
keySchema.push({
|
|
101
|
+
AttributeName: props.sortKey.name,
|
|
102
|
+
KeyType: "RANGE",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await client.send(
|
|
107
|
+
new CreateTableCommand({
|
|
108
|
+
TableName: props.tableName,
|
|
109
|
+
AttributeDefinitions: attributeDefinitions,
|
|
110
|
+
KeySchema: keySchema,
|
|
111
|
+
BillingMode: props.billingMode || "PAY_PER_REQUEST",
|
|
112
|
+
ProvisionedThroughput:
|
|
113
|
+
props.billingMode === "PROVISIONED"
|
|
114
|
+
? {
|
|
115
|
+
ReadCapacityUnits: props.readCapacity || 5,
|
|
116
|
+
WriteCapacityUnits: props.writeCapacity || 5,
|
|
117
|
+
}
|
|
118
|
+
: undefined,
|
|
119
|
+
Tags: props.tags
|
|
120
|
+
? Object.entries(props.tags).map(([Key, Value]) => ({
|
|
121
|
+
Key,
|
|
122
|
+
Value,
|
|
123
|
+
}))
|
|
124
|
+
: undefined,
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Wait for table to be active
|
|
129
|
+
let tableActive = false;
|
|
130
|
+
let tableDescription;
|
|
131
|
+
while (!tableActive) {
|
|
132
|
+
const response = await client.send(
|
|
133
|
+
new DescribeTableCommand({
|
|
134
|
+
TableName: props.tableName,
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
tableActive = response.Table?.TableStatus === "ACTIVE";
|
|
138
|
+
if (tableActive) {
|
|
139
|
+
tableDescription = response.Table;
|
|
140
|
+
} else {
|
|
141
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...props,
|
|
147
|
+
id: props.tableName,
|
|
148
|
+
arn: tableDescription!.TableArn!,
|
|
149
|
+
streamArn: tableDescription!.LatestStreamArn,
|
|
150
|
+
tableId: tableDescription!.TableId!,
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof ResourceInUseException) {
|
|
154
|
+
// Table already exists, get its details
|
|
155
|
+
const response = await client.send(
|
|
156
|
+
new DescribeTableCommand({
|
|
157
|
+
TableName: props.tableName,
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
...props,
|
|
162
|
+
id: props.tableName,
|
|
163
|
+
arn: response.Table!.TableArn!,
|
|
164
|
+
streamArn: response.Table!.LatestStreamArn,
|
|
165
|
+
tableId: response.Table!.TableId!,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
) {}
|