dcl-ops-lib 8.3.3 → 9.1.0

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.
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSQSTriggeredFargateTask = void 0;
4
+ const aws = require("@pulumi/aws");
5
+ const pulumi = require("@pulumi/pulumi");
6
+ const stack_1 = require("./stack");
7
+ const scheduledTaskBase_1 = require("./scheduledTaskBase");
8
+ /**
9
+ * Creates a Fargate task that is triggered by messages in an SQS queue.
10
+ * Uses EventBridge Pipes to connect the SQS queue to ECS task execution.
11
+ *
12
+ * @param taskName A name for this task. For example, "order-processor"
13
+ * @param dockerImage The docker image to run
14
+ * @param options Configuration options for the SQS-triggered task
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * await createSQSTriggeredFargateTask("order-processor", "myapp/processor:latest", {
19
+ * sqsTrigger: {
20
+ * queueArn: ordersQueue.arn,
21
+ * batchSize: 10,
22
+ * maximumBatchingWindowSeconds: 30,
23
+ * },
24
+ * cpu: 512,
25
+ * memory: 1024,
26
+ * environment: [{ name: "DB_HOST", value: dbHost }],
27
+ * slackChannel: "#order-processing-alerts",
28
+ * team: "platform",
29
+ * });
30
+ * ```
31
+ */
32
+ async function createSQSTriggeredFargateTask(taskName, dockerImage, options) {
33
+ const { sqsTrigger, ...baseOptions } = options;
34
+ // Create base infrastructure (task definition, security group, IAM roles, etc.)
35
+ const base = await (0, scheduledTaskBase_1.createBaseTaskInfrastructure)(taskName, dockerImage, baseOptions, ["pipes.amazonaws.com"] // EventBridge Pipes service
36
+ );
37
+ // Add SQS permissions to the EventBridge role
38
+ const sqsPolicy = new aws.iam.RolePolicy(`${base.resourcePrefix}-sqs-policy`, {
39
+ role: base.eventBridgeRole.id,
40
+ policy: pulumi.output(sqsTrigger.queueArn).apply((queueArn) => JSON.stringify({
41
+ Version: "2012-10-17",
42
+ Statement: [
43
+ {
44
+ Effect: "Allow",
45
+ Action: [
46
+ // Permissions needed to read from SQS
47
+ "sqs:ReceiveMessage",
48
+ "sqs:DeleteMessage",
49
+ "sqs:GetQueueAttributes",
50
+ ],
51
+ Resource: queueArn,
52
+ },
53
+ ],
54
+ })),
55
+ });
56
+ // Create EventBridge Pipe to connect SQS queue to ECS task
57
+ const pipe = new aws.pipes.Pipe(`${base.resourcePrefix}-sqs-pipe`, {
58
+ // Unique name for this pipe
59
+ name: (0, stack_1.getStackScopedName)(taskName),
60
+ // IAM role that the pipe assumes to read from source and invoke target
61
+ roleArn: base.eventBridgeRole.arn,
62
+ // Source: the SQS queue to read messages from
63
+ source: sqsTrigger.queueArn,
64
+ // Source configuration for SQS
65
+ sourceParameters: {
66
+ sqsQueueParameters: {
67
+ // Number of messages to retrieve per batch (1-10000)
68
+ batchSize: sqsTrigger.batchSize || 1,
69
+ // Maximum time to wait for a full batch before triggering (0-300 seconds)
70
+ maximumBatchingWindowInSeconds: sqsTrigger.maximumBatchingWindowSeconds || 0,
71
+ },
72
+ },
73
+ // Target: the ECS cluster to run tasks on
74
+ target: base.clusterArn,
75
+ // Target configuration for ECS tasks
76
+ targetParameters: {
77
+ ecsTaskParameters: {
78
+ // The task definition to run
79
+ taskDefinitionArn: base.taskDefinition.arn,
80
+ // Number of task instances to launch per invocation
81
+ taskCount: base.taskCount,
82
+ // Launch type: FARGATE for serverless containers
83
+ launchType: "FARGATE",
84
+ // Network configuration for awsvpc network mode
85
+ networkConfiguration: {
86
+ awsVpcConfiguration: {
87
+ // Private subnets where the task will run
88
+ subnets: base.subnetIds,
89
+ // Security groups controlling inbound/outbound traffic
90
+ securityGroups: base.securityGroups,
91
+ // Whether to assign a public IP (ENABLED/DISABLED)
92
+ assignPublicIp: base.assignPublicIp ? "ENABLED" : "DISABLED",
93
+ },
94
+ },
95
+ },
96
+ },
97
+ tags: { ServiceName: taskName, Team: base.team },
98
+ }, { dependsOn: [base.eventBridgePolicy, sqsPolicy, base.taskDefinition] });
99
+ return {
100
+ taskDefinition: base.taskDefinition,
101
+ taskSecurityGroup: base.taskSecurityGroup,
102
+ logGroup: base.logGroup,
103
+ executionRole: base.executionRole,
104
+ taskRole: base.taskRole,
105
+ eventBridgeRole: base.eventBridgeRole,
106
+ pipe,
107
+ };
108
+ }
109
+ exports.createSQSTriggeredFargateTask = createSQSTriggeredFargateTask;
110
+ //# sourceMappingURL=createSQSTriggeredFargateTask.js.map
@@ -0,0 +1,75 @@
1
+ import * as pulumi from "@pulumi/pulumi";
2
+ import { BaseFargateTaskOptions, RetryPolicyConfig } from "./scheduledTaskBase";
3
+ /**
4
+ * Schedule configuration for the task
5
+ */
6
+ export type ScheduleConfig = {
7
+ /**
8
+ * Schedule expression in cron or rate format
9
+ * Cron: "cron(0 12 * * ? *)" - runs daily at noon UTC
10
+ * Rate: "rate(1 hour)" - runs every hour
11
+ */
12
+ expression: string;
13
+ /**
14
+ * Timezone for the schedule (default: "UTC")
15
+ * Example: "America/New_York", "Europe/London"
16
+ */
17
+ timezone?: string;
18
+ /**
19
+ * Whether the schedule is enabled (default: true)
20
+ */
21
+ enabled?: boolean;
22
+ /**
23
+ * Optional start date for the schedule (ISO 8601 format)
24
+ */
25
+ startDate?: string;
26
+ /**
27
+ * Optional end date for the schedule (ISO 8601 format)
28
+ */
29
+ endDate?: string;
30
+ };
31
+ /**
32
+ * Options for creating a scheduled Fargate task
33
+ */
34
+ export type ScheduledFargateTaskOptions = BaseFargateTaskOptions & {
35
+ /**
36
+ * Schedule configuration (cron/rate expression) - required
37
+ */
38
+ schedule: ScheduleConfig;
39
+ /**
40
+ * Retry policy for failed task invocations
41
+ */
42
+ retryPolicy?: RetryPolicyConfig;
43
+ };
44
+ /**
45
+ * Creates a scheduled Fargate task that runs on a cron/rate schedule.
46
+ *
47
+ * @param taskName A name for this task. For example, "daily-cleanup"
48
+ * @param dockerImage The docker image to run
49
+ * @param options Configuration options for the scheduled task
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * await createScheduledFargateTask("daily-cleanup", "myapp/cleanup:latest", {
54
+ * schedule: {
55
+ * expression: "cron(0 2 * * ? *)", // Daily at 2 AM
56
+ * timezone: "America/New_York",
57
+ * },
58
+ * cpu: 512,
59
+ * memory: 1024,
60
+ * environment: [{ name: "DB_HOST", value: dbHost }],
61
+ * slackChannel: "#scheduled-tasks-alerts",
62
+ * team: "platform",
63
+ * });
64
+ * ```
65
+ */
66
+ export declare function createScheduledFargateTask(taskName: string, dockerImage: string | Promise<string> | pulumi.OutputInstance<string>, options: ScheduledFargateTaskOptions): Promise<{
67
+ taskDefinition: import("@pulumi/aws/ecs/taskDefinition").TaskDefinition;
68
+ taskSecurityGroup: import("@pulumi/aws/ec2/securityGroup").SecurityGroup;
69
+ logGroup: import("@pulumi/aws/cloudwatch/logGroup").LogGroup;
70
+ executionRole: import("@pulumi/aws/iam/role").Role;
71
+ taskRole: import("@pulumi/aws/iam/role").Role;
72
+ eventBridgeRole: import("@pulumi/aws/iam/role").Role;
73
+ scheduler: import("@pulumi/aws/scheduler/schedule").Schedule;
74
+ scheduleGroup: import("@pulumi/aws/scheduler/scheduleGroup").ScheduleGroup;
75
+ }>;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createScheduledFargateTask = void 0;
4
+ const aws = require("@pulumi/aws");
5
+ const stack_1 = require("./stack");
6
+ const scheduledTaskBase_1 = require("./scheduledTaskBase");
7
+ /**
8
+ * Creates a scheduled Fargate task that runs on a cron/rate schedule.
9
+ *
10
+ * @param taskName A name for this task. For example, "daily-cleanup"
11
+ * @param dockerImage The docker image to run
12
+ * @param options Configuration options for the scheduled task
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * await createScheduledFargateTask("daily-cleanup", "myapp/cleanup:latest", {
17
+ * schedule: {
18
+ * expression: "cron(0 2 * * ? *)", // Daily at 2 AM
19
+ * timezone: "America/New_York",
20
+ * },
21
+ * cpu: 512,
22
+ * memory: 1024,
23
+ * environment: [{ name: "DB_HOST", value: dbHost }],
24
+ * slackChannel: "#scheduled-tasks-alerts",
25
+ * team: "platform",
26
+ * });
27
+ * ```
28
+ */
29
+ async function createScheduledFargateTask(taskName, dockerImage, options) {
30
+ const { schedule, retryPolicy, ...baseOptions } = options;
31
+ // Create base infrastructure (task definition, security group, IAM roles, etc.)
32
+ const base = await (0, scheduledTaskBase_1.createBaseTaskInfrastructure)(taskName, dockerImage, baseOptions, ["scheduler.amazonaws.com"] // EventBridge Scheduler service
33
+ );
34
+ // Schedule groups allow organizing related schedules together
35
+ // and applying resource-based policies at the group level
36
+ const scheduleGroup = new aws.scheduler.ScheduleGroup(`${base.resourcePrefix}-schedule-group`, {
37
+ name: (0, stack_1.getStackScopedName)(taskName),
38
+ tags: { ServiceName: taskName, Team: base.team },
39
+ });
40
+ const scheduler = new aws.scheduler.Schedule(`${base.resourcePrefix}-schedule`, {
41
+ // Unique name for this schedule within the AWS account and region
42
+ name: (0, stack_1.getStackScopedName)(taskName),
43
+ // Associate with the schedule group for organization
44
+ groupName: scheduleGroup.name,
45
+ // Schedule expression: supports cron() and rate() formats
46
+ // Examples: "cron(0 12 * * ? *)" for daily at noon, "rate(1 hour)" for hourly
47
+ scheduleExpression: schedule.expression,
48
+ // Timezone for interpreting cron expressions (default: UTC)
49
+ // Examples: "America/New_York", "Europe/London", "UTC"
50
+ scheduleExpressionTimezone: schedule.timezone || "UTC",
51
+ // Whether the schedule is active (ENABLED) or paused (DISABLED)
52
+ state: schedule.enabled !== false ? "ENABLED" : "DISABLED",
53
+ // Optional: Schedule won't trigger before this date (ISO 8601 format)
54
+ startDate: schedule.startDate,
55
+ // Optional: Schedule won't trigger after this date (ISO 8601 format)
56
+ endDate: schedule.endDate,
57
+ // Flexible time window allows EventBridge to invoke the target within a window
58
+ // OFF = invoke at exact scheduled time, FLEXIBLE = invoke within specified window
59
+ flexibleTimeWindow: {
60
+ mode: "OFF",
61
+ },
62
+ target: {
63
+ // Target is the ECS cluster that will run the task
64
+ arn: base.clusterArn,
65
+ // IAM role that EventBridge assumes to invoke the target
66
+ roleArn: base.eventBridgeRole.arn,
67
+ // ECS-specific parameters for running Fargate tasks
68
+ ecsParameters: {
69
+ // The task definition to run
70
+ taskDefinitionArn: base.taskDefinition.arn,
71
+ // Number of task instances to launch (default: 1)
72
+ taskCount: base.taskCount,
73
+ // Launch type: FARGATE for serverless, EC2 for container instances
74
+ launchType: "FARGATE",
75
+ // Network configuration for tasks using awsvpc network mode
76
+ networkConfiguration: {
77
+ // Private subnets where the task will run
78
+ subnets: base.subnetIds,
79
+ // Security groups controlling inbound/outbound traffic
80
+ securityGroups: base.securityGroups,
81
+ // Whether to assign a public IP (needed for ECR pulls without NAT)
82
+ assignPublicIp: base.assignPublicIp,
83
+ },
84
+ },
85
+ // Retry policy if the scheduler fails to invoke the target
86
+ retryPolicy: retryPolicy
87
+ ? {
88
+ // Maximum number of retry attempts (0-185)
89
+ maximumRetryAttempts: retryPolicy.maxRetries,
90
+ // Maximum time to keep retrying before giving up (in seconds)
91
+ maximumEventAgeInSeconds: retryPolicy.maxAgeSeconds,
92
+ }
93
+ : undefined,
94
+ },
95
+ }, { dependsOn: [base.eventBridgePolicy, base.taskDefinition] });
96
+ return {
97
+ taskDefinition: base.taskDefinition,
98
+ taskSecurityGroup: base.taskSecurityGroup,
99
+ logGroup: base.logGroup,
100
+ executionRole: base.executionRole,
101
+ taskRole: base.taskRole,
102
+ eventBridgeRole: base.eventBridgeRole,
103
+ scheduler,
104
+ scheduleGroup,
105
+ };
106
+ }
107
+ exports.createScheduledFargateTask = createScheduledFargateTask;
108
+ //# sourceMappingURL=createScheduledFargateTask.js.map
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.exposePublicService = void 0;
13
4
  const aws = require("@pulumi/aws");
@@ -38,87 +29,85 @@ function isUnProxiedDomain(v) {
38
29
  * @param domain
39
30
  * @param port
40
31
  */
41
- function exposePublicService(name, domain, port, healthCheck = {}, vpcId, extraOptions, deregistrationDelay) {
42
- return __awaiter(this, void 0, void 0, function* () {
43
- const { alb, listener } = yield (0, alb_1.getAlb)();
44
- const healthCheckValue = Object.assign({}, DEFAULT_HEALTHCHECK_VALUES, healthCheck);
45
- const isProxied = isProxiedDomain(extraOptions);
46
- const isUnproxied = isUnProxiedDomain(extraOptions);
47
- const createCloudflareRecord = isProxied || isUnproxied || domain.endsWith(`.${domain_1.publicDomain}`);
48
- const onlyCloudflare = (extraOptions && extraOptions.skipInternalDomain) || false;
49
- const createInternalDomain = !onlyCloudflare;
50
- const certificate = (0, certificate_1.getCertificateFor)(domain);
51
- const slug = name;
52
- const targetVpcId = vpcId ? vpcId : (yield aws.ec2.getVpc({ default: true }, { async: true })).id;
53
- const targetDeregistrationDelay = deregistrationDelay ? deregistrationDelay : 300;
54
- const targetGroup = new aws.alb.TargetGroup("tg-" + slug.substr(-32 + 12), {
55
- protocol: "HTTP",
56
- targetType: "ip",
57
- port,
58
- healthCheck: healthCheckValue,
59
- vpcId: targetVpcId,
60
- deregistrationDelay: targetDeregistrationDelay,
32
+ async function exposePublicService(name, domain, port, healthCheck = {}, vpcId, extraOptions, deregistrationDelay) {
33
+ const { alb, listener } = await (0, alb_1.getAlb)();
34
+ const healthCheckValue = Object.assign({}, DEFAULT_HEALTHCHECK_VALUES, healthCheck);
35
+ const isProxied = isProxiedDomain(extraOptions);
36
+ const isUnproxied = isUnProxiedDomain(extraOptions);
37
+ const createCloudflareRecord = isProxied || isUnproxied || domain.endsWith(`.${domain_1.publicDomain}`);
38
+ const onlyCloudflare = (extraOptions && extraOptions.skipInternalDomain) || false;
39
+ const createInternalDomain = !onlyCloudflare;
40
+ const certificate = (0, certificate_1.getCertificateFor)(domain);
41
+ const slug = name;
42
+ const targetVpcId = vpcId ? vpcId : (await aws.ec2.getVpc({ default: true }, { async: true })).id;
43
+ const targetDeregistrationDelay = deregistrationDelay ? deregistrationDelay : 300;
44
+ const targetGroup = new aws.alb.TargetGroup("tg-" + slug.substr(-32 + 12), {
45
+ protocol: "HTTP",
46
+ targetType: "ip",
47
+ port,
48
+ healthCheck: healthCheckValue,
49
+ vpcId: targetVpcId,
50
+ deregistrationDelay: targetDeregistrationDelay,
51
+ });
52
+ const domainParts = (0, getDomainAndSubdomain_1.getDomainAndSubdomain)(domain);
53
+ const enabledHostnames = [];
54
+ let record;
55
+ let cloudflareRecord;
56
+ if (createInternalDomain) {
57
+ const hostedZone = await aws.route53.getZone({ name: domainParts.parentDomain }, { async: true });
58
+ record = new aws.route53.Record(domain, {
59
+ name: domainParts.subdomain,
60
+ zoneId: hostedZone.zoneId,
61
+ type: "A",
62
+ aliases: [
63
+ {
64
+ name: alb.dnsName,
65
+ zoneId: alb.zoneId,
66
+ evaluateTargetHealth: false,
67
+ },
68
+ ],
61
69
  });
62
- const domainParts = (0, getDomainAndSubdomain_1.getDomainAndSubdomain)(domain);
63
- const enabledHostnames = [];
64
- let record;
65
- let cloudflareRecord;
66
- if (createInternalDomain) {
67
- const hostedZone = yield aws.route53.getZone({ name: domainParts.parentDomain }, { async: true });
68
- record = new aws.route53.Record(domain, {
69
- name: domainParts.subdomain,
70
- zoneId: hostedZone.zoneId,
71
- type: "A",
72
- aliases: [
73
- {
74
- name: alb.dnsName,
75
- zoneId: alb.zoneId,
76
- evaluateTargetHealth: false,
77
- },
78
- ],
70
+ enabledHostnames.push(domain);
71
+ }
72
+ if (createCloudflareRecord) {
73
+ enabledHostnames.push(domainParts.subdomain + "." + domain_1.publicDomain);
74
+ if (isUnProxiedDomain(extraOptions)) {
75
+ cloudflareRecord = await (0, cloudflare_1.setRecord)({
76
+ recordName: domainParts.subdomain,
77
+ type: "CNAME",
78
+ value: alb.dnsName,
79
+ proxied: false,
80
+ ttl: extraOptions.ttl || 600,
79
81
  });
80
- enabledHostnames.push(domain);
81
82
  }
82
- if (createCloudflareRecord) {
83
- enabledHostnames.push(domainParts.subdomain + "." + domain_1.publicDomain);
84
- if (isUnProxiedDomain(extraOptions)) {
85
- cloudflareRecord = yield (0, cloudflare_1.setRecord)({
86
- recordName: domainParts.subdomain,
87
- type: "CNAME",
88
- value: alb.dnsName,
89
- proxied: false,
90
- ttl: extraOptions.ttl || 600,
91
- });
92
- }
93
- else {
94
- cloudflareRecord = yield (0, cloudflare_1.setRecord)({
95
- recordName: domainParts.subdomain,
96
- type: "CNAME",
97
- value: alb.dnsName,
98
- proxied: true,
99
- });
100
- }
101
- }
102
- if (enabledHostnames.length) {
103
- new aws.alb.ListenerRule(`${domain_1.env}-ls-${slug}`, {
104
- listenerArn: listener.arn,
105
- conditions: [{ hostHeader: { values: enabledHostnames } }, ...((extraOptions === null || extraOptions === void 0 ? void 0 : extraOptions.targetGroupConditions) || [])],
106
- actions: [
107
- {
108
- type: "forward",
109
- targetGroupArn: targetGroup.arn,
110
- },
111
- ],
83
+ else {
84
+ cloudflareRecord = await (0, cloudflare_1.setRecord)({
85
+ recordName: domainParts.subdomain,
86
+ type: "CNAME",
87
+ value: alb.dnsName,
88
+ proxied: true,
112
89
  });
113
90
  }
114
- return {
115
- domain,
116
- certificate,
117
- record,
118
- targetGroup,
119
- cloudflareRecord,
120
- };
121
- });
91
+ }
92
+ if (enabledHostnames.length) {
93
+ new aws.alb.ListenerRule(`${domain_1.env}-ls-${slug}`, {
94
+ listenerArn: listener.arn,
95
+ conditions: [{ hostHeader: { values: enabledHostnames } }, ...(extraOptions?.targetGroupConditions || [])],
96
+ actions: [
97
+ {
98
+ type: "forward",
99
+ targetGroupArn: targetGroup.arn,
100
+ },
101
+ ],
102
+ });
103
+ }
104
+ return {
105
+ domain,
106
+ certificate,
107
+ record,
108
+ targetGroup,
109
+ cloudflareRecord,
110
+ };
122
111
  }
123
112
  exports.exposePublicService = exposePublicService;
124
113
  //# sourceMappingURL=exposePublicService.js.map