puls-dev 0.2.0 → 0.2.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Puls-dev
1
+ # Pulsdev.io
2
2
 
3
3
  **Intent-driven infrastructure-as-code. Describe what you want - Puls figures out create, update, or skip.**
4
4
 
@@ -12,6 +12,8 @@ import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
12
12
  import { RDSClient } from "@aws-sdk/client-rds";
13
13
  import { SQSClient } from "@aws-sdk/client-sqs";
14
14
  import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
15
+ import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
16
+ import { SNSClient } from "@aws-sdk/client-sns";
15
17
  export declare const getS3Client: (region?: string) => S3Client;
16
18
  export declare const getCFClient: () => CloudFrontClient;
17
19
  export declare const getR53Client: () => Route53Client;
@@ -26,3 +28,5 @@ export declare const getCWLogsClient: (region?: string) => CloudWatchLogsClient;
26
28
  export declare const getRDSClient: (region?: string) => RDSClient;
27
29
  export declare const getSQSClient: (region?: string) => SQSClient;
28
30
  export declare const getSecretsClient: (region?: string) => SecretsManagerClient;
31
+ export declare const getCWClient: (region?: string) => CloudWatchClient;
32
+ export declare const getSNSClient: (region?: string) => SNSClient;
@@ -12,6 +12,8 @@ import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
12
12
  import { RDSClient } from "@aws-sdk/client-rds";
13
13
  import { SQSClient } from "@aws-sdk/client-sqs";
14
14
  import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
15
+ import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
16
+ import { SNSClient } from "@aws-sdk/client-sns";
15
17
  import { Config } from "../../core/config.js";
16
18
  function getRegion() {
17
19
  const region = Config.get().providers.aws?.region;
@@ -34,3 +36,5 @@ export const getCWLogsClient = (region) => new CloudWatchLogsClient({ region: re
34
36
  export const getRDSClient = (region) => new RDSClient({ region: region ?? getRegion() });
35
37
  export const getSQSClient = (region) => new SQSClient({ region: region ?? getRegion() });
36
38
  export const getSecretsClient = (region) => new SecretsManagerClient({ region: region ?? getRegion() });
39
+ export const getCWClient = (region) => new CloudWatchClient({ region: region ?? getRegion() });
40
+ export const getSNSClient = (region) => new SNSClient({ region: region ?? getRegion() });
@@ -0,0 +1,44 @@
1
+ import { BaseBuilder } from "../../core/resource.js";
2
+ import { Output } from "../../core/output.js";
3
+ import { FargateBuilder } from "./fargate.js";
4
+ import { RDSBuilder } from "./rds.js";
5
+ import { SNSTopicBuilder } from "./sns.js";
6
+ type ComparisonOperator = "GreaterThanOrEqualToThreshold" | "GreaterThanThreshold" | "LessThanThreshold" | "LessThanOrEqualToThreshold";
7
+ type Statistic = "Average" | "Sum" | "SampleCount" | "Maximum" | "Minimum";
8
+ export declare class CloudWatchAlarmBuilder extends BaseBuilder {
9
+ readonly out: {
10
+ name: Output<string>;
11
+ arn: Output<string>;
12
+ };
13
+ private _namespace?;
14
+ private _metricName?;
15
+ private _dimensions?;
16
+ private _comparison?;
17
+ private _threshold?;
18
+ private _period?;
19
+ private _evaluationPeriods?;
20
+ private _statistic?;
21
+ private _actions;
22
+ resolvedArn: string | null;
23
+ constructor(name: string);
24
+ metric(namespace: string, name: string, dimensions?: Record<string, string>): this;
25
+ comparison(op: ComparisonOperator): this;
26
+ threshold(value: number): this;
27
+ period(seconds: number): this;
28
+ evaluationPeriods(periods: number): this;
29
+ statistic(stat: Statistic): this;
30
+ actions(sns: string | SNSTopicBuilder): this;
31
+ fargateCPU(fargate: FargateBuilder, thresholdPercent: number): this;
32
+ fargateMemory(fargate: FargateBuilder, thresholdPercent: number): this;
33
+ rdsCPU(rds: RDSBuilder, thresholdPercent: number): this;
34
+ rdsStorage(rds: RDSBuilder, thresholdBytes: number): this;
35
+ private discoverAlarm;
36
+ deploy(): Promise<{
37
+ name: string;
38
+ arn: string | null;
39
+ }>;
40
+ destroy(): Promise<{
41
+ destroyed: string;
42
+ }>;
43
+ }
44
+ export {};
@@ -0,0 +1,205 @@
1
+ import { DescribeAlarmsCommand, PutMetricAlarmCommand, DeleteAlarmsCommand, } from "@aws-sdk/client-cloudwatch";
2
+ import { BaseBuilder } from "../../core/resource.js";
3
+ import { Output } from "../../core/output.js";
4
+ import { getCWClient } from "./api.js";
5
+ export class CloudWatchAlarmBuilder extends BaseBuilder {
6
+ out = {
7
+ name: new Output(),
8
+ arn: new Output(),
9
+ };
10
+ _namespace;
11
+ _metricName;
12
+ _dimensions;
13
+ _comparison;
14
+ _threshold;
15
+ _period;
16
+ _evaluationPeriods;
17
+ _statistic;
18
+ _actions = [];
19
+ resolvedArn = null;
20
+ constructor(name) {
21
+ super(name);
22
+ this.out.name.resolve(name);
23
+ this.discoveryPromise = this.discoverAlarm(name);
24
+ }
25
+ metric(namespace, name, dimensions) {
26
+ this._namespace = namespace;
27
+ this._metricName = name;
28
+ if (dimensions) {
29
+ this._dimensions = dimensions;
30
+ }
31
+ return this;
32
+ }
33
+ comparison(op) {
34
+ this._comparison = op;
35
+ return this;
36
+ }
37
+ threshold(value) {
38
+ this._threshold = value;
39
+ return this;
40
+ }
41
+ period(seconds) {
42
+ this._period = seconds;
43
+ return this;
44
+ }
45
+ evaluationPeriods(periods) {
46
+ this._evaluationPeriods = periods;
47
+ return this;
48
+ }
49
+ statistic(stat) {
50
+ this._statistic = stat;
51
+ return this;
52
+ }
53
+ actions(sns) {
54
+ this._actions.push(sns);
55
+ return this;
56
+ }
57
+ fargateCPU(fargate, thresholdPercent) {
58
+ this._namespace = "AWS/ECS";
59
+ this._metricName = "CPUUtilization";
60
+ this._dimensions = {
61
+ ClusterName: fargate.clusterName,
62
+ ServiceName: fargate.serviceName,
63
+ };
64
+ this._comparison = "GreaterThanOrEqualToThreshold";
65
+ this._threshold = thresholdPercent;
66
+ this._period = this._period ?? 300;
67
+ this._evaluationPeriods = this._evaluationPeriods ?? 1;
68
+ this._statistic = this._statistic ?? "Average";
69
+ return this;
70
+ }
71
+ fargateMemory(fargate, thresholdPercent) {
72
+ this._namespace = "AWS/ECS";
73
+ this._metricName = "MemoryUtilization";
74
+ this._dimensions = {
75
+ ClusterName: fargate.clusterName,
76
+ ServiceName: fargate.serviceName,
77
+ };
78
+ this._comparison = "GreaterThanOrEqualToThreshold";
79
+ this._threshold = thresholdPercent;
80
+ this._period = this._period ?? 300;
81
+ this._evaluationPeriods = this._evaluationPeriods ?? 1;
82
+ this._statistic = this._statistic ?? "Average";
83
+ return this;
84
+ }
85
+ rdsCPU(rds, thresholdPercent) {
86
+ this._namespace = "AWS/RDS";
87
+ this._metricName = "CPUUtilization";
88
+ this._dimensions = {
89
+ DBInstanceIdentifier: rds.dbInstanceIdentifier,
90
+ };
91
+ this._comparison = "GreaterThanOrEqualToThreshold";
92
+ this._threshold = thresholdPercent;
93
+ this._period = this._period ?? 300;
94
+ this._evaluationPeriods = this._evaluationPeriods ?? 1;
95
+ this._statistic = this._statistic ?? "Average";
96
+ return this;
97
+ }
98
+ rdsStorage(rds, thresholdBytes) {
99
+ this._namespace = "AWS/RDS";
100
+ this._metricName = "FreeStorageSpace";
101
+ this._dimensions = {
102
+ DBInstanceIdentifier: rds.dbInstanceIdentifier,
103
+ };
104
+ this._comparison = "LessThanThreshold";
105
+ this._threshold = thresholdBytes;
106
+ this._period = this._period ?? 300;
107
+ this._evaluationPeriods = this._evaluationPeriods ?? 1;
108
+ this._statistic = this._statistic ?? "Average";
109
+ return this;
110
+ }
111
+ async discoverAlarm(name) {
112
+ const cw = getCWClient();
113
+ try {
114
+ const result = await cw.send(new DescribeAlarmsCommand({ AlarmNames: [name] }));
115
+ const match = (result.MetricAlarms ?? []).find((a) => a.AlarmName === name);
116
+ if (match) {
117
+ this.resolvedArn = match.AlarmArn ?? null;
118
+ if (this.resolvedArn) {
119
+ this.out.arn.resolve(this.resolvedArn);
120
+ }
121
+ return match;
122
+ }
123
+ return null;
124
+ }
125
+ catch (e) {
126
+ if (e.name === "CredentialsProviderError")
127
+ return null;
128
+ throw e;
129
+ }
130
+ }
131
+ async deploy() {
132
+ const dryRun = this.isDryRunActive();
133
+ const existing = await this.discoveryPromise;
134
+ const cw = getCWClient();
135
+ console.log(`\n⏰ Finalizing CloudWatch Alarm "${this.name}"...`);
136
+ if (!this._namespace || !this._metricName || !this._comparison || this._threshold === undefined) {
137
+ throw new Error(`[CloudWatchAlarm:${this.name}] Metric namespace, name, comparison operator, and threshold are required.`);
138
+ }
139
+ // Resolve SNS action ARNs
140
+ const alarmActions = [];
141
+ for (const action of this._actions) {
142
+ if (typeof action === "string") {
143
+ alarmActions.push(action);
144
+ }
145
+ else {
146
+ const arn = await action.out.arn.get();
147
+ alarmActions.push(arn);
148
+ }
149
+ }
150
+ const dimensionsArray = this._dimensions
151
+ ? Object.entries(this._dimensions).map(([Name, Value]) => ({ Name, Value }))
152
+ : undefined;
153
+ const alarmParams = {
154
+ AlarmName: this.name,
155
+ ComparisonOperator: this._comparison,
156
+ EvaluationPeriods: this._evaluationPeriods ?? 1,
157
+ MetricName: this._metricName,
158
+ Namespace: this._namespace,
159
+ Period: this._period ?? 300,
160
+ Threshold: this._threshold,
161
+ Statistic: this._statistic ?? "Average",
162
+ ActionsEnabled: alarmActions.length > 0,
163
+ AlarmActions: alarmActions.length > 0 ? alarmActions : undefined,
164
+ Dimensions: dimensionsArray,
165
+ };
166
+ if (dryRun) {
167
+ console.log(` 📝 [PLAN] ${existing ? "Update" : "Create"} CloudWatch alarm "${this.name}"`);
168
+ console.log(` └─ Metric: ${this._namespace}/${this._metricName}`);
169
+ console.log(` └─ Comparison: ${this._comparison} | Threshold: ${this._threshold}`);
170
+ if (this._dimensions) {
171
+ console.log(` └─ Dimensions: ${JSON.stringify(this._dimensions)}`);
172
+ }
173
+ if (alarmActions.length > 0) {
174
+ console.log(` └─ Actions: ${alarmActions.join(", ")}`);
175
+ }
176
+ this.resolvedArn = existing?.AlarmArn ?? `arn:aws:cloudwatch:us-east-1:000000000000:alarm:DRYRUN-${this.name}`;
177
+ this.out.arn.resolve(this.resolvedArn);
178
+ return { name: this.name, arn: this.resolvedArn };
179
+ }
180
+ await cw.send(new PutMetricAlarmCommand(alarmParams));
181
+ const describeResult = await cw.send(new DescribeAlarmsCommand({ AlarmNames: [this.name] }));
182
+ this.resolvedArn = describeResult.MetricAlarms?.[0]?.AlarmArn ?? `arn:aws:cloudwatch:us-east-1:000000000000:alarm:${this.name}`;
183
+ this.out.arn.resolve(this.resolvedArn);
184
+ console.log(`🚀 Created/Updated CloudWatch Alarm "${this.name}" (arn=${this.resolvedArn})`);
185
+ await this.deploySidecars();
186
+ return { name: this.name, arn: this.resolvedArn };
187
+ }
188
+ async destroy() {
189
+ const dryRun = this.isDryRunActive();
190
+ const existing = await this.discoveryPromise;
191
+ console.log(`\n🗑️ Destroying CloudWatch Alarm "${this.name}"...`);
192
+ if (!existing) {
193
+ console.log(` ✅ Alarm "${this.name}" does not exist - nothing to do`);
194
+ return { destroyed: this.name };
195
+ }
196
+ if (dryRun) {
197
+ console.log(` 📝 [PLAN] Delete CloudWatch alarm "${this.name}"`);
198
+ return { destroyed: this.name };
199
+ }
200
+ const cw = getCWClient();
201
+ await cw.send(new DeleteAlarmsCommand({ AlarmNames: [this.name] }));
202
+ console.log(` ✅ Deleted CloudWatch alarm "${this.name}"`);
203
+ return { destroyed: this.name };
204
+ }
205
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,224 @@
1
+ import { test, describe, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert";
3
+ import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
4
+ import { SNSClient } from "@aws-sdk/client-sns";
5
+ import { ECSClient } from "@aws-sdk/client-ecs";
6
+ import { RDSClient } from "@aws-sdk/client-rds";
7
+ import { CloudWatchAlarmBuilder } from "./cloudwatch.js";
8
+ import { SNSTopicBuilder } from "./sns.js";
9
+ import { FargateBuilder } from "./fargate.js";
10
+ import { RDSBuilder } from "./rds.js";
11
+ import { Config } from "../../core/config.js";
12
+ describe("CloudWatchAlarmBuilder Unit Tests", () => {
13
+ let originalCwSend;
14
+ let originalSnsSend;
15
+ let originalEcsSend;
16
+ let originalRdsSend;
17
+ let cwCalls = [];
18
+ let mockCwResponses = {};
19
+ beforeEach(() => {
20
+ Config.set({
21
+ dryRun: false,
22
+ providers: {
23
+ aws: { region: "us-east-1" },
24
+ },
25
+ });
26
+ cwCalls = [];
27
+ mockCwResponses = {};
28
+ originalCwSend = CloudWatchClient.prototype.send;
29
+ originalSnsSend = SNSClient.prototype.send;
30
+ originalEcsSend = ECSClient.prototype.send;
31
+ originalRdsSend = RDSClient.prototype.send;
32
+ CloudWatchClient.prototype.send = async function (command) {
33
+ const commandName = command.constructor.name;
34
+ const input = command.input;
35
+ cwCalls.push({ commandName, input });
36
+ if (mockCwResponses[commandName]) {
37
+ const handler = mockCwResponses[commandName];
38
+ if (typeof handler === "function")
39
+ return handler(input);
40
+ if (handler instanceof Error)
41
+ throw handler;
42
+ return handler;
43
+ }
44
+ return {};
45
+ };
46
+ SNSClient.prototype.send = async function (command) {
47
+ return {};
48
+ };
49
+ ECSClient.prototype.send = async function (command) {
50
+ return {};
51
+ };
52
+ RDSClient.prototype.send = async function (command) {
53
+ return {};
54
+ };
55
+ });
56
+ afterEach(() => {
57
+ CloudWatchClient.prototype.send = originalCwSend;
58
+ SNSClient.prototype.send = originalSnsSend;
59
+ ECSClient.prototype.send = originalEcsSend;
60
+ RDSClient.prototype.send = originalRdsSend;
61
+ });
62
+ test("gracefully handles discovery when alarm does not exist", async () => {
63
+ mockCwResponses["DescribeAlarmsCommand"] = { MetricAlarms: [] };
64
+ const builder = new CloudWatchAlarmBuilder("my-alarm");
65
+ const discoveryResult = await builder.discoveryPromise;
66
+ assert.strictEqual(discoveryResult, null);
67
+ assert.ok(cwCalls.some((c) => c.commandName === "DescribeAlarmsCommand"));
68
+ });
69
+ test("discovers existing alarm successfully", async () => {
70
+ mockCwResponses["DescribeAlarmsCommand"] = {
71
+ MetricAlarms: [
72
+ {
73
+ AlarmName: "my-alarm",
74
+ AlarmArn: "arn:aws:cloudwatch:us-east-1:123456789012:alarm:my-alarm",
75
+ },
76
+ ],
77
+ };
78
+ const builder = new CloudWatchAlarmBuilder("my-alarm");
79
+ const discoveryResult = await builder.discoveryPromise;
80
+ assert.ok(discoveryResult);
81
+ assert.strictEqual(builder.resolvedArn, "arn:aws:cloudwatch:us-east-1:123456789012:alarm:my-alarm");
82
+ const resolvedArn = await builder.out.arn.get();
83
+ assert.strictEqual(resolvedArn, "arn:aws:cloudwatch:us-east-1:123456789012:alarm:my-alarm");
84
+ });
85
+ test("creates a custom metric alarm", async () => {
86
+ mockCwResponses["DescribeAlarmsCommand"] = { MetricAlarms: [] };
87
+ const builder = new CloudWatchAlarmBuilder("custom-alarm")
88
+ .metric("MyCustomNamespace", "Errors", { Service: "checkout" })
89
+ .comparison("GreaterThanThreshold")
90
+ .threshold(10)
91
+ .period(60)
92
+ .evaluationPeriods(2)
93
+ .statistic("Sum");
94
+ await builder.deploy();
95
+ const putCall = cwCalls.find((c) => c.commandName === "PutMetricAlarmCommand");
96
+ assert.ok(putCall);
97
+ assert.deepStrictEqual(putCall.input, {
98
+ AlarmName: "custom-alarm",
99
+ ComparisonOperator: "GreaterThanThreshold",
100
+ EvaluationPeriods: 2,
101
+ MetricName: "Errors",
102
+ Namespace: "MyCustomNamespace",
103
+ Period: 60,
104
+ Threshold: 10,
105
+ Statistic: "Sum",
106
+ ActionsEnabled: false,
107
+ AlarmActions: undefined,
108
+ Dimensions: [{ Name: "Service", Value: "checkout" }],
109
+ });
110
+ });
111
+ test("auto-wires specialized Fargate CPU and memory helper alarms", async () => {
112
+ mockCwResponses["DescribeAlarmsCommand"] = { MetricAlarms: [] };
113
+ const fargate = new FargateBuilder("my-api-service").cluster("my-prod-cluster");
114
+ const cpuAlarm = new CloudWatchAlarmBuilder("fargate-cpu")
115
+ .fargateCPU(fargate, 80)
116
+ .evaluationPeriods(3);
117
+ await cpuAlarm.deploy();
118
+ const cpuPutCall = cwCalls.find((c) => c.commandName === "PutMetricAlarmCommand" && c.input.AlarmName === "fargate-cpu");
119
+ assert.ok(cpuPutCall);
120
+ assert.deepStrictEqual(cpuPutCall.input, {
121
+ AlarmName: "fargate-cpu",
122
+ ComparisonOperator: "GreaterThanOrEqualToThreshold",
123
+ EvaluationPeriods: 3,
124
+ MetricName: "CPUUtilization",
125
+ Namespace: "AWS/ECS",
126
+ Period: 300,
127
+ Threshold: 80,
128
+ Statistic: "Average",
129
+ ActionsEnabled: false,
130
+ AlarmActions: undefined,
131
+ Dimensions: [
132
+ { Name: "ClusterName", Value: "my-prod-cluster" },
133
+ { Name: "ServiceName", Value: "my-api-service" },
134
+ ],
135
+ });
136
+ const memAlarm = new CloudWatchAlarmBuilder("fargate-mem").fargateMemory(fargate, 85);
137
+ await memAlarm.deploy();
138
+ const memPutCall = cwCalls.find((c) => c.commandName === "PutMetricAlarmCommand" && c.input.AlarmName === "fargate-mem");
139
+ assert.ok(memPutCall);
140
+ assert.deepStrictEqual(memPutCall.input.MetricName, "MemoryUtilization");
141
+ assert.deepStrictEqual(memPutCall.input.Namespace, "AWS/ECS");
142
+ assert.deepStrictEqual(memPutCall.input.Threshold, 85);
143
+ });
144
+ test("auto-wires specialized RDS CPU and storage helper alarms", async () => {
145
+ mockCwResponses["DescribeAlarmsCommand"] = { MetricAlarms: [] };
146
+ const rds = new RDSBuilder("my-database");
147
+ const cpuAlarm = new CloudWatchAlarmBuilder("rds-cpu").rdsCPU(rds, 90);
148
+ await cpuAlarm.deploy();
149
+ const cpuPutCall = cwCalls.find((c) => c.commandName === "PutMetricAlarmCommand" && c.input.AlarmName === "rds-cpu");
150
+ assert.ok(cpuPutCall);
151
+ assert.deepStrictEqual(cpuPutCall.input, {
152
+ AlarmName: "rds-cpu",
153
+ ComparisonOperator: "GreaterThanOrEqualToThreshold",
154
+ EvaluationPeriods: 1,
155
+ MetricName: "CPUUtilization",
156
+ Namespace: "AWS/RDS",
157
+ Period: 300,
158
+ Threshold: 90,
159
+ Statistic: "Average",
160
+ ActionsEnabled: false,
161
+ AlarmActions: undefined,
162
+ Dimensions: [{ Name: "DBInstanceIdentifier", Value: "my-database" }],
163
+ });
164
+ const storageAlarm = new CloudWatchAlarmBuilder("rds-storage").rdsStorage(rds, 5000000000);
165
+ await storageAlarm.deploy();
166
+ const storagePutCall = cwCalls.find((c) => c.commandName === "PutMetricAlarmCommand" && c.input.AlarmName === "rds-storage");
167
+ assert.ok(storagePutCall);
168
+ assert.strictEqual(storagePutCall.input.ComparisonOperator, "LessThanThreshold");
169
+ assert.strictEqual(storagePutCall.input.MetricName, "FreeStorageSpace");
170
+ assert.strictEqual(storagePutCall.input.Namespace, "AWS/RDS");
171
+ assert.strictEqual(storagePutCall.input.Threshold, 5000000000);
172
+ });
173
+ test("integrates with SNSTopicBuilder eagerly by awaiting its ARN", async () => {
174
+ mockCwResponses["DescribeAlarmsCommand"] = { MetricAlarms: [] };
175
+ // Set up a mock topic
176
+ const topic = new SNSTopicBuilder("alert-topic");
177
+ topic.resolvedArn = "arn:aws:sns:us-east-1:123456789012:alert-topic";
178
+ topic.out.arn.resolve("arn:aws:sns:us-east-1:123456789012:alert-topic");
179
+ const alarm = new CloudWatchAlarmBuilder("metric-alarm")
180
+ .metric("AWS/Billing", "EstimatedCharges")
181
+ .comparison("GreaterThanThreshold")
182
+ .threshold(100)
183
+ .actions(topic);
184
+ await alarm.deploy();
185
+ const putCall = cwCalls.find((c) => c.commandName === "PutMetricAlarmCommand");
186
+ assert.ok(putCall);
187
+ assert.strictEqual(putCall.input.ActionsEnabled, true);
188
+ assert.deepStrictEqual(putCall.input.AlarmActions, [
189
+ "arn:aws:sns:us-east-1:123456789012:alert-topic",
190
+ ]);
191
+ });
192
+ test("destroys an existing alarm successfully", async () => {
193
+ mockCwResponses["DescribeAlarmsCommand"] = {
194
+ MetricAlarms: [
195
+ {
196
+ AlarmName: "my-alarm",
197
+ AlarmArn: "arn:aws:cloudwatch:us-east-1:123456789012:alarm:my-alarm",
198
+ },
199
+ ],
200
+ };
201
+ const builder = new CloudWatchAlarmBuilder("my-alarm");
202
+ await builder.discoveryPromise;
203
+ const destroyResult = await builder.destroy();
204
+ assert.deepStrictEqual(destroyResult, { destroyed: "my-alarm" });
205
+ const deleteCall = cwCalls.find((c) => c.commandName === "DeleteAlarmsCommand");
206
+ assert.ok(deleteCall);
207
+ assert.deepStrictEqual(deleteCall.input.AlarmNames, ["my-alarm"]);
208
+ });
209
+ test("runs in dry run mode safely", async () => {
210
+ Config.set({
211
+ dryRun: true,
212
+ providers: {
213
+ aws: { region: "us-east-1" },
214
+ },
215
+ });
216
+ const builder = new CloudWatchAlarmBuilder("my-alarm")
217
+ .metric("AWS/Billing", "EstimatedCharges")
218
+ .comparison("GreaterThanThreshold")
219
+ .threshold(50);
220
+ const deployResult = await builder.deploy();
221
+ assert.ok(deployResult.arn.includes("DRYRUN"));
222
+ assert.ok(!cwCalls.some((c) => c.commandName === "PutMetricAlarmCommand"));
223
+ });
224
+ });
@@ -12,6 +12,8 @@ export declare class FargateBuilder extends BaseBuilder {
12
12
  private _securityGroupIds?;
13
13
  resolvedArn: string | null;
14
14
  constructor(name: string);
15
+ get clusterName(): string;
16
+ get serviceName(): string;
15
17
  image(img: string): this;
16
18
  cpu(units: number): this;
17
19
  memory(mb: number): this;
@@ -31,6 +31,12 @@ export class FargateBuilder extends BaseBuilder {
31
31
  super(name);
32
32
  this.discoveryPromise = this.discoverService(name);
33
33
  }
34
+ get clusterName() {
35
+ return this._clusterName;
36
+ }
37
+ get serviceName() {
38
+ return this.name;
39
+ }
34
40
  image(img) {
35
41
  this._image = img;
36
42
  return this;
@@ -0,0 +1,52 @@
1
+ import { BaseBuilder } from "../../core/resource.js";
2
+ import { Output } from "../../core/output.js";
3
+ import type { IAMPolicyDocument } from "../../types/aws.js";
4
+ export declare class IAMPolicyBuilder extends BaseBuilder {
5
+ readonly out: {
6
+ arn: Output<string>;
7
+ };
8
+ private _document?;
9
+ private _description?;
10
+ private _path;
11
+ resolvedArn: string | null;
12
+ constructor(name: string);
13
+ private discoverPolicy;
14
+ document(doc: IAMPolicyDocument): this;
15
+ description(desc: string): this;
16
+ path(p: string): this;
17
+ deploy(): Promise<{
18
+ name: string;
19
+ arn: string | null;
20
+ }>;
21
+ destroy(): Promise<{
22
+ destroyed: string;
23
+ }>;
24
+ }
25
+ export declare class IAMRoleBuilder extends BaseBuilder {
26
+ readonly out: {
27
+ arn: Output<string>;
28
+ name: Output<string>;
29
+ };
30
+ private _assumeRolePolicy;
31
+ private _managedPolicies;
32
+ private _inlinePolicies;
33
+ private _description?;
34
+ private _path;
35
+ private _maxSessionDuration?;
36
+ resolvedArn: string | null;
37
+ constructor(name: string);
38
+ private discoverRole;
39
+ assumeRolePolicy(doc: IAMPolicyDocument): this;
40
+ attach(policyArnOrBuilder: string | IAMPolicyBuilder): this;
41
+ inlinePolicy(name: string, doc: IAMPolicyDocument): this;
42
+ description(desc: string): this;
43
+ path(p: string): this;
44
+ maxSessionDuration(seconds: number): this;
45
+ deploy(): Promise<{
46
+ name: string;
47
+ arn: string | null;
48
+ }>;
49
+ destroy(): Promise<{
50
+ destroyed: string;
51
+ }>;
52
+ }