@jaypie/constructs 1.1.53 → 1.1.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { CDK as CDK$2, ConfigurationError, mergeDomain, isValidSubdomain, isValidHostname as isValidHostname$1 } from '@jaypie/cdk';
2
- export { CDK } from '@jaypie/cdk';
3
- import { Construct } from 'constructs';
4
1
  import * as cdk from 'aws-cdk-lib';
5
- import { Tags, Stack, Duration, RemovalPolicy, Fn, CfnOutput, SecretValue } from 'aws-cdk-lib';
2
+ import { Tags, Stack, Duration, RemovalPolicy, CfnStack, Fn, CfnOutput, SecretValue } from 'aws-cdk-lib';
3
+ import * as s3 from 'aws-cdk-lib/aws-s3';
4
+ import { Bucket, StorageClass, BucketAccessControl, EventType } from 'aws-cdk-lib/aws-s3';
5
+ import { Construct } from 'constructs';
6
6
  import * as acm from 'aws-cdk-lib/aws-certificatemanager';
7
7
  import * as apiGateway from 'aws-cdk-lib/aws-apigateway';
8
8
  import * as route53 from 'aws-cdk-lib/aws-route53';
@@ -10,19 +10,242 @@ import { TxtRecord, NsRecord, MxRecord, CnameRecord, ARecord, RecordTarget, Host
10
10
  import * as route53Targets from 'aws-cdk-lib/aws-route53-targets';
11
11
  import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
12
12
  import { DatadogLambda } from 'datadog-cdk-constructs-v2';
13
+ import { ConfigurationError } from '@jaypie/errors';
14
+ import { Role, PolicyStatement, Policy, FederatedPrincipal, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
13
15
  import * as lambda from 'aws-cdk-lib/aws-lambda';
14
16
  import * as logDestinations from 'aws-cdk-lib/aws-logs-destinations';
15
- import * as s3 from 'aws-cdk-lib/aws-s3';
16
17
  import * as s3n from 'aws-cdk-lib/aws-s3-notifications';
18
+ import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications';
17
19
  import * as sqs from 'aws-cdk-lib/aws-sqs';
18
20
  import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
19
- import { Role, FederatedPrincipal, PolicyStatement, Effect, ServicePrincipal, ManagedPolicy } from 'aws-cdk-lib/aws-iam';
21
+ import { Rule, RuleTargetInput } from 'aws-cdk-lib/aws-events';
22
+ import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets';
20
23
  import { LogGroup, RetentionDays, FilterPattern } from 'aws-cdk-lib/aws-logs';
24
+ import { Trail, ReadWriteType } from 'aws-cdk-lib/aws-cloudtrail';
21
25
  import { CfnPermissionSet, CfnAssignment } from 'aws-cdk-lib/aws-sso';
22
26
  import { CfnApplication } from 'aws-cdk-lib/aws-sam';
23
27
  import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
24
28
  import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
25
29
 
30
+ const CDK$2 = {
31
+ ACCOUNT: {
32
+ DEVELOPMENT: "development",
33
+ MANAGEMENT: "management",
34
+ OPERATIONS: "operations",
35
+ PRODUCTION: "production",
36
+ SANDBOX: "sandbox",
37
+ SECURITY: "security",
38
+ STAGE: "stage",
39
+ },
40
+ BUILD: {
41
+ CONFIG: {
42
+ ALL: "all",
43
+ API: "api",
44
+ INFRASTRUCTURE: "infrastructure",
45
+ NONE: "none",
46
+ WEB: "web",
47
+ },
48
+ PERSONAL: "personal",
49
+ /**
50
+ * @deprecated rename "ephemeral" to "personal" (since 2/24/2025)
51
+ */
52
+ EPHEMERAL: "ephemeral",
53
+ /**
54
+ * @deprecated as even "ephemeral" builds have static assets (since 7/6/2024)
55
+ */
56
+ STATIC: "static",
57
+ },
58
+ CREATION: {
59
+ CDK: "cdk",
60
+ CLOUDFORMATION_TEMPLATE: "template",
61
+ MANUAL: "manual",
62
+ },
63
+ DATADOG: {
64
+ SITE: "datadoghq.com",
65
+ LAYER: {
66
+ // https://docs.datadoghq.com/serverless/aws_lambda/installation/nodejs/?tab=awscdk
67
+ NODE: 127, // 127 on 9/12/2025
68
+ EXTENSION: 86, // 86 on 9/12/2025
69
+ },
70
+ },
71
+ DEFAULT: {
72
+ REGION: "us-east-1",
73
+ },
74
+ DNS: {
75
+ CONFIG: {
76
+ TTL: 300, // 5 minutes in seconds for Route53
77
+ },
78
+ RECORD: {
79
+ A: "A",
80
+ CNAME: "CNAME",
81
+ MX: "MX",
82
+ NS: "NS",
83
+ TXT: "TXT",
84
+ },
85
+ },
86
+ DURATION: {
87
+ EXPRESS_API: 30,
88
+ LAMBDA_MAXIMUM: 900,
89
+ LAMBDA_WORKER: 900,
90
+ },
91
+ ENV: {
92
+ DEMO: "demo", // Mirror of production
93
+ DEVELOPMENT: "development", // Internal most stable development space
94
+ /** @deprecated */ EPHEMERAL: "ephemeral", // Alias for "build"
95
+ LOCAL: "local",
96
+ /** @deprecated */ MAIN: "main", // Alias for development
97
+ META: "meta", // For non-environment/infrastructure stacks
98
+ PERSONAL: "personal", // Personal builds using resources provided by sandbox
99
+ PREVIEW: "preview", // External next thing to be released
100
+ PRODUCTION: "production",
101
+ RELEASE: "release", // Internal next thing to be released
102
+ REVIEW: "review", // Internal place to collaborate on issues
103
+ SANDBOX: "sandbox", // Internal build space with no guaranteed longevity
104
+ TRAINING: "training", // aka "test"; mirror of production for external audiences
105
+ },
106
+ HOST: {
107
+ APEX: "@",
108
+ },
109
+ IMPORT: {
110
+ DATADOG_LOG_FORWARDER: "account-datadog-forwarder",
111
+ DATADOG_ROLE: "account-datadog-role",
112
+ DATADOG_SECRET: "account-datadog-secret",
113
+ LOG_BUCKET: "account-log-bucket",
114
+ OIDC_PROVIDER: "github-oidc-provider",
115
+ },
116
+ LAMBDA: {
117
+ LOG_RETENTION: 90,
118
+ MEMORY_SIZE: 1024,
119
+ },
120
+ PRINCIPAL: {
121
+ ROUTE53: "route53.amazonaws.com",
122
+ },
123
+ PRINCIPAL_TYPE: {
124
+ GROUP: "GROUP",
125
+ USER: "USER",
126
+ },
127
+ PROJECT: {
128
+ INFRASTRUCTURE: "infrastructure",
129
+ },
130
+ ROLE: {
131
+ API: "api",
132
+ DEPLOY: "deploy",
133
+ HOSTING: "hosting",
134
+ MONITORING: "monitoring",
135
+ NETWORKING: "networking",
136
+ PROCESSING: "processing",
137
+ SECURITY: "security",
138
+ STACK: "stack",
139
+ STORAGE: "storage",
140
+ TOY: "toy",
141
+ },
142
+ SERVICE: {
143
+ DATADOG: "datadog",
144
+ INFRASTRUCTURE: "infrastructure",
145
+ LIBRARIES: "libraries",
146
+ NONE: "none",
147
+ SSO: "sso",
148
+ TRACE: "trace",
149
+ },
150
+ TAG: {
151
+ BUILD_DATE: "buildDate",
152
+ BUILD_HEX: "buildHex",
153
+ BUILD_NUMBER: "buildNumber",
154
+ BUILD_TIME: "buildTime",
155
+ BUILD_TYPE: "buildType",
156
+ COMMIT: "commit",
157
+ CREATION: "creation",
158
+ ENV: "env",
159
+ NONCE: "nonce",
160
+ PROJECT: "project",
161
+ ROLE: "role",
162
+ SERVICE: "service",
163
+ SPONSOR: "sponsor",
164
+ STACK: "stack",
165
+ STACK_SHA: "stackSha",
166
+ VENDOR: "vendor",
167
+ VERSION: "version",
168
+ },
169
+ TARGET_TYPE: {
170
+ AWS_ACCOUNT: "AWS_ACCOUNT",
171
+ },
172
+ VENDOR: {
173
+ ANTHROPIC: "anthropic",
174
+ AUTH0: "auth0",
175
+ DATADOG: "datadog",
176
+ KNOWTRACE: "knowtrace",
177
+ MONGODB: "mongodb",
178
+ OPENAI: "openai",
179
+ SPLINTERLANDS: "splinterlands",
180
+ },
181
+ };
182
+
183
+ class JaypieAccountLoggingBucket extends Construct {
184
+ /**
185
+ * Create a new account-wide logging S3 bucket with lifecycle policies and export
186
+ */
187
+ constructor(scope, idOrProps, propsOrUndefined) {
188
+ // Handle overloaded constructor signatures
189
+ let props;
190
+ let id;
191
+ if (typeof idOrProps === "string") {
192
+ // First param is ID, second is props
193
+ props = propsOrUndefined || {};
194
+ id = idOrProps;
195
+ }
196
+ else {
197
+ // First param is props
198
+ props = idOrProps || {};
199
+ id = props.id || "AccountLoggingBucket";
200
+ }
201
+ super(scope, id);
202
+ // Generate default bucket name with PROJECT_NONCE
203
+ const defaultBucketName = process.env.PROJECT_NONCE
204
+ ? `account-logging-stack-${process.env.PROJECT_NONCE.toLowerCase()}`
205
+ : "account-logging-stack";
206
+ // Extract Jaypie-specific options
207
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
208
+ const { bucketName = defaultBucketName, createOutput = true, expirationDays = 365, exportName = CDK$2.IMPORT.LOG_BUCKET, glacierTransitionDays = 180, id: _id, infrequentAccessTransitionDays = 30, outputDescription = "Account-wide logging bucket", project, service = CDK$2.SERVICE.INFRASTRUCTURE, ...bucketProps } = props;
209
+ // Create the bucket with lifecycle rules
210
+ this.bucket = new Bucket(this, "Bucket", {
211
+ accessControl: BucketAccessControl.LOG_DELIVERY_WRITE,
212
+ bucketName,
213
+ lifecycleRules: [
214
+ {
215
+ expiration: cdk.Duration.days(expirationDays),
216
+ transitions: [
217
+ {
218
+ storageClass: StorageClass.INFREQUENT_ACCESS,
219
+ transitionAfter: cdk.Duration.days(infrequentAccessTransitionDays),
220
+ },
221
+ {
222
+ storageClass: StorageClass.GLACIER,
223
+ transitionAfter: cdk.Duration.days(glacierTransitionDays),
224
+ },
225
+ ],
226
+ },
227
+ ],
228
+ ...bucketProps,
229
+ });
230
+ // Add tags
231
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
232
+ if (service) {
233
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.SERVICE, service);
234
+ }
235
+ if (project) {
236
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.PROJECT, project);
237
+ }
238
+ // Create CloudFormation output if enabled
239
+ if (createOutput) {
240
+ new cdk.CfnOutput(this, "BucketNameOutput", {
241
+ description: outputDescription,
242
+ exportName,
243
+ value: this.bucket.bucketName,
244
+ });
245
+ }
246
+ }
247
+ }
248
+
26
249
  function addDatadogLayers(lambdaFunction, options = {}) {
27
250
  const datadogApiKeyArn = options?.datadogApiKeyArn;
28
251
  const resolvedDatadogApiKeyArn = datadogApiKeyArn ||
@@ -145,6 +368,55 @@ function envHostname({ component, domain, env, subdomain, } = {}) {
145
368
  return parts.join(".");
146
369
  }
147
370
 
371
+ /**
372
+ * Extends the Datadog IAM role with additional permissions
373
+ *
374
+ * Checks for CDK_ENV_DATADOG_ROLE_ARN environment variable.
375
+ * If found, creates a custom policy with:
376
+ * - budgets:ViewBudget
377
+ * - logs:DescribeLogGroups
378
+ *
379
+ * @param scope - The construct scope
380
+ * @param options - Configuration options
381
+ * @returns The created Policy, or undefined if CDK_ENV_DATADOG_ROLE_ARN is not set
382
+ */
383
+ function extendDatadogRole(scope, options) {
384
+ const datadogRoleArn = process.env.CDK_ENV_DATADOG_ROLE_ARN;
385
+ // Early return if no Datadog role ARN is configured
386
+ if (!datadogRoleArn) {
387
+ return undefined;
388
+ }
389
+ const { id = "DatadogCustomPolicy", project, service = CDK$2.SERVICE.DATADOG, } = options || {};
390
+ // Lookup the Datadog role
391
+ const datadogRole = Role.fromRoleArn(scope, "DatadogRole", datadogRoleArn);
392
+ // Build policy statements
393
+ const statements = [
394
+ // Allow view budget
395
+ new PolicyStatement({
396
+ actions: ["budgets:ViewBudget"],
397
+ resources: ["*"],
398
+ }),
399
+ // Allow describe log groups
400
+ new PolicyStatement({
401
+ actions: ["logs:DescribeLogGroups"],
402
+ resources: ["*"],
403
+ }),
404
+ ];
405
+ // Create the custom policy
406
+ const datadogCustomPolicy = new Policy(scope, id, {
407
+ roles: [datadogRole],
408
+ statements,
409
+ });
410
+ // Add tags
411
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.SERVICE, service);
412
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
413
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
414
+ if (project) {
415
+ cdk.Tags.of(datadogCustomPolicy).add(CDK$2.TAG.PROJECT, project);
416
+ }
417
+ return datadogCustomPolicy;
418
+ }
419
+
148
420
  /**
149
421
  * Check if the current environment matches the given environment
150
422
  */
@@ -164,6 +436,79 @@ function isSandboxEnv() {
164
436
  return isEnv(CDK$2.ENV.SANDBOX);
165
437
  }
166
438
 
439
+ // In short the RFC 1035 standard for valid hostnames is:
440
+ // 1. Must be less than 253 characters
441
+ // 2. Must start with a letter
442
+ // 3. Must end with a letter or number
443
+ // 4. Can only contain letters, numbers, and hyphens
444
+ // 5. Last part of the domain must be at least 2 characters
445
+ function validPart$1(part) {
446
+ if (!part.match(/^[a-z]/))
447
+ return false;
448
+ if (!part.match(/[a-z0-9]$/))
449
+ return false;
450
+ return /^[a-zA-Z0-9-]+$/.test(part);
451
+ }
452
+ function isValidHostname$1(hostname) {
453
+ // Check hostname is a string
454
+ if (typeof hostname !== "string")
455
+ return false;
456
+ // Convert hostname to lowercase
457
+ const check = hostname.toString().toLowerCase();
458
+ // Check hostname is less than 253 characters
459
+ if (check.length > 253)
460
+ return false;
461
+ // Split on dots
462
+ const parts = check.split(".");
463
+ // Check each part is validPart
464
+ const validParts = parts.map(validPart$1);
465
+ // Confirm all parts are valid
466
+ if (!validParts.every((part) => part))
467
+ return false;
468
+ // Confirm last part is at least 2 characters
469
+ const lastPart = parts[parts.length - 1];
470
+ if (lastPart.length < 2)
471
+ return false;
472
+ // Confirm last part is all letters
473
+ if (!lastPart.match(/^[a-z]+$/))
474
+ return false;
475
+ // This is a valid hostname
476
+ return true;
477
+ }
478
+
479
+ function validPart(part) {
480
+ if (!part.match(/^[a-z]/))
481
+ return false;
482
+ if (!part.match(/[a-z0-9]$/))
483
+ return false;
484
+ return /^[a-zA-Z0-9-]+$/.test(part);
485
+ }
486
+ function isValidSubdomain(subdomain) {
487
+ // Check subdomain is a string
488
+ if (typeof subdomain !== "string")
489
+ return false;
490
+ // Special case for apex
491
+ if (subdomain === CDK$2.HOST.APEX)
492
+ return true;
493
+ // Convert subdomain to lowercase
494
+ const check = subdomain.toString().toLowerCase();
495
+ // Check subdomain is less than 250 characters
496
+ // We use 250 instead of 253 because we need to leave room for the dot top-level domain
497
+ if (check.length > 250)
498
+ return false;
499
+ // Split on dots
500
+ const parts = check.split(".");
501
+ // Check each part is validPart
502
+ const validParts = parts.map(validPart);
503
+ // Confirm all parts are valid
504
+ if (!validParts.every((part) => part))
505
+ return false;
506
+ // Do not care if last part is at least 2 characters
507
+ // Do not care if last part is all letters
508
+ // This is a valid subdomain
509
+ return true;
510
+ }
511
+
167
512
  function jaypieLambdaEnv(options = {}) {
168
513
  const { initialEnvironment = {} } = options;
169
514
  // Start with empty environment - we'll only add valid values
@@ -218,6 +563,21 @@ function jaypieLambdaEnv(options = {}) {
218
563
  return environment;
219
564
  }
220
565
 
566
+ function mergeDomain(subDomain, hostedZone) {
567
+ if (!hostedZone) {
568
+ throw new ConfigurationError("hostedZone is required");
569
+ }
570
+ if (!subDomain) {
571
+ // Return hostedZone if subDomain is not passed
572
+ // Pass CDK.HOST.APEX to explicitly indicate apex domain
573
+ return hostedZone;
574
+ }
575
+ if (subDomain === CDK$2.HOST.APEX) {
576
+ return hostedZone;
577
+ }
578
+ return `${subDomain}.${hostedZone}`;
579
+ }
580
+
221
581
  const DEFAULT_FUNCTION_NAME$1 = "DatadogForwarderFunction";
222
582
  // Cache to store resolved functions
223
583
  // Using nested structure to support multiple functions per scope with automatic GC
@@ -1096,6 +1456,175 @@ class JaypieBucketQueuedLambda extends JaypieQueuedLambda {
1096
1456
  }
1097
1457
  }
1098
1458
 
1459
+ class JaypieDatadogBucket extends Construct {
1460
+ /**
1461
+ * Create a new S3 bucket for Datadog log archiving with automatic IAM permissions
1462
+ */
1463
+ constructor(scope, idOrProps, propsOrUndefined) {
1464
+ // Handle overloaded constructor signatures
1465
+ let props;
1466
+ let id;
1467
+ if (typeof idOrProps === "string") {
1468
+ // First param is ID, second is props
1469
+ props = propsOrUndefined || {};
1470
+ id = idOrProps;
1471
+ }
1472
+ else {
1473
+ // First param is props
1474
+ props = idOrProps || {};
1475
+ id = props.id || "DatadogArchiveBucket";
1476
+ }
1477
+ super(scope, id);
1478
+ // Extract Jaypie-specific options
1479
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1480
+ const { grantDatadogAccess = true, id: _id, project, service = CDK$2.SERVICE.DATADOG, ...bucketProps } = props;
1481
+ // Create the bucket
1482
+ this.bucket = new Bucket(this, "Bucket", bucketProps);
1483
+ // Add tags to bucket
1484
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.SERVICE, service);
1485
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1486
+ if (project) {
1487
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.PROJECT, project);
1488
+ }
1489
+ // Grant Datadog role access to bucket if enabled
1490
+ if (grantDatadogAccess) {
1491
+ this.policy = this.grantDatadogRoleBucketAccess({ project, service });
1492
+ }
1493
+ }
1494
+ /**
1495
+ * Grants the Datadog IAM role access to this bucket
1496
+ *
1497
+ * Checks for CDK_ENV_DATADOG_ROLE_ARN environment variable.
1498
+ * If found, creates a custom policy with:
1499
+ * - s3:ListBucket on bucket
1500
+ * - s3:GetObject and s3:PutObject on bucket/*
1501
+ *
1502
+ * @param options - Configuration options
1503
+ * @returns The created Policy, or undefined if CDK_ENV_DATADOG_ROLE_ARN is not set
1504
+ */
1505
+ grantDatadogRoleBucketAccess(options) {
1506
+ const datadogRoleArn = process.env.CDK_ENV_DATADOG_ROLE_ARN;
1507
+ // Early return if no Datadog role ARN is configured
1508
+ if (!datadogRoleArn) {
1509
+ return undefined;
1510
+ }
1511
+ const { project, service = CDK$2.SERVICE.DATADOG } = options || {};
1512
+ // Lookup the Datadog role
1513
+ const datadogRole = Role.fromRoleArn(this, "DatadogRole", datadogRoleArn);
1514
+ // Build policy statements for bucket access
1515
+ const statements = [
1516
+ // Allow list bucket
1517
+ new PolicyStatement({
1518
+ actions: ["s3:ListBucket"],
1519
+ resources: [this.bucket.bucketArn],
1520
+ }),
1521
+ // Allow read and write to the bucket
1522
+ new PolicyStatement({
1523
+ actions: ["s3:GetObject", "s3:PutObject"],
1524
+ resources: [`${this.bucket.bucketArn}/*`],
1525
+ }),
1526
+ ];
1527
+ // Create the custom policy
1528
+ const datadogBucketPolicy = new Policy(this, "DatadogBucketPolicy", {
1529
+ roles: [datadogRole],
1530
+ statements,
1531
+ });
1532
+ // Add tags
1533
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.SERVICE, service);
1534
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1535
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
1536
+ if (project) {
1537
+ cdk.Tags.of(datadogBucketPolicy).add(CDK$2.TAG.PROJECT, project);
1538
+ }
1539
+ return datadogBucketPolicy;
1540
+ }
1541
+ }
1542
+
1543
+ const DATADOG_FORWARDER_TEMPLATE_URL = "https://datadog-cloudformation-template.s3.amazonaws.com/aws/forwarder/latest.yaml";
1544
+ const DEFAULT_RESERVED_CONCURRENCY = "10";
1545
+ class JaypieDatadogForwarder extends Construct {
1546
+ /**
1547
+ * Create a new Datadog forwarder with CloudFormation nested stack
1548
+ */
1549
+ constructor(scope, idOrProps, propsOrUndefined) {
1550
+ // Handle overloaded constructor signatures
1551
+ let props;
1552
+ let id;
1553
+ if (typeof idOrProps === "string") {
1554
+ // First param is ID, second is props
1555
+ props = propsOrUndefined || {};
1556
+ id = idOrProps;
1557
+ }
1558
+ else {
1559
+ // First param is props
1560
+ props = idOrProps || {};
1561
+ id = props.id || "DatadogForwarder";
1562
+ }
1563
+ super(scope, id);
1564
+ // Resolve options with defaults
1565
+ const { account = process.env.CDK_ENV_ACCOUNT, additionalTags, createOutput = true, datadogApiKey = process.env.CDK_ENV_DATADOG_API_KEY, enableCloudFormationEvents = true, enableRoleExtension = true, exportName = CDK$2.IMPORT.DATADOG_LOG_FORWARDER, project, reservedConcurrency = DEFAULT_RESERVED_CONCURRENCY, service = CDK$2.VENDOR.DATADOG, templateUrl = DATADOG_FORWARDER_TEMPLATE_URL, } = props;
1566
+ // Validate required parameters
1567
+ if (!datadogApiKey) {
1568
+ throw new Error("Datadog API key is required. Provide via datadogApiKey prop or CDK_ENV_DATADOG_API_KEY environment variable.");
1569
+ }
1570
+ // Build Datadog tags
1571
+ let ddTags = account ? `account:${account}` : "";
1572
+ if (additionalTags) {
1573
+ ddTags = ddTags ? `${ddTags},${additionalTags}` : additionalTags;
1574
+ }
1575
+ // Deploy Datadog CloudFormation stack
1576
+ this.cfnStack = new CfnStack(this, "Stack", {
1577
+ parameters: {
1578
+ DdApiKey: datadogApiKey,
1579
+ DdTags: ddTags,
1580
+ ReservedConcurrency: reservedConcurrency,
1581
+ },
1582
+ templateUrl,
1583
+ });
1584
+ // Add tags to stack
1585
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1586
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.SERVICE, service);
1587
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
1588
+ if (project) {
1589
+ cdk.Tags.of(this.cfnStack).add(CDK$2.TAG.PROJECT, project);
1590
+ }
1591
+ // Extract forwarder function from stack outputs
1592
+ this.forwarderFunction = lambda.Function.fromFunctionArn(this, "Function", this.cfnStack.getAtt("Outputs.DatadogForwarderArn").toString());
1593
+ // Extend Datadog role with custom permissions if enabled
1594
+ if (enableRoleExtension) {
1595
+ extendDatadogRole(this, { project, service });
1596
+ }
1597
+ // Create CloudFormation events rule if enabled
1598
+ if (enableCloudFormationEvents) {
1599
+ this.eventsRule = new Rule(this, "CloudFormationEventsRule", {
1600
+ eventPattern: {
1601
+ source: ["aws.cloudformation"],
1602
+ },
1603
+ targets: [
1604
+ new LambdaFunction(this.forwarderFunction, {
1605
+ event: RuleTargetInput.fromEventPath("$"),
1606
+ }),
1607
+ ],
1608
+ });
1609
+ // Add tags to events rule
1610
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1611
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.SERVICE, service);
1612
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.VENDOR, CDK$2.VENDOR.DATADOG);
1613
+ if (project) {
1614
+ cdk.Tags.of(this.eventsRule).add(CDK$2.TAG.PROJECT, project);
1615
+ }
1616
+ }
1617
+ // Create CloudFormation output if enabled
1618
+ if (createOutput) {
1619
+ new cdk.CfnOutput(this, "ForwarderArnOutput", {
1620
+ description: "Datadog Log Forwarder Lambda ARN",
1621
+ exportName,
1622
+ value: this.cfnStack.getAtt("Outputs.DatadogForwarderArn").toString(),
1623
+ });
1624
+ }
1625
+ }
1626
+ }
1627
+
1099
1628
  // It is a consumer if the environment is ephemeral
1100
1629
  function checkEnvIsConsumer(env = process.env) {
1101
1630
  return (env.PROJECT_ENV === CDK$2.ENV.PERSONAL ||
@@ -1319,6 +1848,86 @@ class JaypieDnsRecord extends Construct {
1319
1848
  }
1320
1849
  }
1321
1850
 
1851
+ class JaypieEventsRule extends Construct {
1852
+ /**
1853
+ * Create a new EventBridge rule that targets a Lambda function
1854
+ */
1855
+ constructor(scope, idOrSourceOrProps, propsOrUndefined) {
1856
+ // Handle overloaded constructor signatures
1857
+ let props;
1858
+ let id;
1859
+ if (typeof idOrSourceOrProps === "string") {
1860
+ // Check if it looks like an AWS source (starts with "aws.")
1861
+ if (idOrSourceOrProps.startsWith("aws.")) {
1862
+ // First param is source, second is props
1863
+ props = propsOrUndefined || {};
1864
+ props.source = idOrSourceOrProps;
1865
+ // Generate ID from source
1866
+ const sourceName = idOrSourceOrProps
1867
+ .replace("aws.", "")
1868
+ .split(".")
1869
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
1870
+ .join("");
1871
+ id = props.id || `${sourceName}EventsRule`;
1872
+ }
1873
+ else {
1874
+ // First param is ID, second is props
1875
+ props = propsOrUndefined || {};
1876
+ id = idOrSourceOrProps;
1877
+ }
1878
+ }
1879
+ else {
1880
+ // First param is props
1881
+ props = idOrSourceOrProps || {};
1882
+ if (props.source) {
1883
+ const sourceName = typeof props.source === "string"
1884
+ ? props.source
1885
+ .replace("aws.", "")
1886
+ .split(".")
1887
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
1888
+ .join("")
1889
+ : "Events";
1890
+ id = props.id || `${sourceName}EventsRule`;
1891
+ }
1892
+ else {
1893
+ id = props.id || "EventsRule";
1894
+ }
1895
+ }
1896
+ super(scope, id);
1897
+ // Extract Jaypie-specific options
1898
+ const { id: _id, project, service = CDK$2.SERVICE.DATADOG, source, targetFunction, vendor = CDK$2.VENDOR.DATADOG, ...ruleProps } = props;
1899
+ // Resolve target function
1900
+ this.targetFunction =
1901
+ targetFunction || resolveDatadogForwarderFunction(scope);
1902
+ // Build event pattern if source is specified
1903
+ const eventPattern = source
1904
+ ? {
1905
+ ...ruleProps.eventPattern,
1906
+ source: Array.isArray(source) ? source : [source],
1907
+ }
1908
+ : ruleProps.eventPattern;
1909
+ // Build rule props
1910
+ const finalRuleProps = {
1911
+ ...ruleProps,
1912
+ eventPattern,
1913
+ targets: [
1914
+ new LambdaFunction(this.targetFunction, {
1915
+ event: RuleTargetInput.fromEventPath("$"),
1916
+ }),
1917
+ ],
1918
+ };
1919
+ // Create the rule
1920
+ this.rule = new Rule(this, "Rule", finalRuleProps);
1921
+ // Add tags
1922
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
1923
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.SERVICE, service);
1924
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.VENDOR, vendor);
1925
+ if (project) {
1926
+ cdk.Tags.of(this.rule).add(CDK$2.TAG.PROJECT, project);
1927
+ }
1928
+ }
1929
+ }
1930
+
1322
1931
  class JaypieGitHubDeployRole extends Construct {
1323
1932
  constructor(scope, id = "GitHubDeployRole", props = {}) {
1324
1933
  super(scope, id);
@@ -1564,6 +2173,100 @@ class JaypieOpenAiSecret extends JaypieEnvSecret {
1564
2173
  }
1565
2174
  }
1566
2175
 
2176
+ class JaypieOrganizationTrail extends Construct {
2177
+ /**
2178
+ * Create a new organization CloudTrail with S3 bucket and lifecycle policies
2179
+ */
2180
+ constructor(scope, idOrProps, propsOrUndefined) {
2181
+ // Handle overloaded constructor signatures
2182
+ let props;
2183
+ let id;
2184
+ if (typeof idOrProps === "string") {
2185
+ // First param is ID, second is props
2186
+ props = propsOrUndefined || {};
2187
+ id = idOrProps;
2188
+ }
2189
+ else {
2190
+ // First param is props
2191
+ props = idOrProps || {};
2192
+ const defaultName = process.env.PROJECT_NONCE
2193
+ ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
2194
+ : "organization-cloudtrail";
2195
+ id = props.id || `${props.trailName || defaultName}-Trail`;
2196
+ }
2197
+ super(scope, id);
2198
+ // Resolve options with defaults
2199
+ const { bucketName = process.env.PROJECT_NONCE
2200
+ ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
2201
+ : "organization-cloudtrail", enableDatadogNotifications = true, enableFileValidation = false, expirationDays = 365, glacierTransitionDays = 180, infrequentAccessTransitionDays = 30, project, service = CDK$2.SERVICE.INFRASTRUCTURE, trailName = process.env.PROJECT_NONCE
2202
+ ? `organization-cloudtrail-${process.env.PROJECT_NONCE}`
2203
+ : "organization-cloudtrail", } = props;
2204
+ // Create the S3 bucket for CloudTrail logs
2205
+ this.bucket = new Bucket(this, "Bucket", {
2206
+ accessControl: BucketAccessControl.LOG_DELIVERY_WRITE,
2207
+ bucketName,
2208
+ lifecycleRules: [
2209
+ {
2210
+ expiration: cdk.Duration.days(expirationDays),
2211
+ transitions: [
2212
+ {
2213
+ storageClass: StorageClass.INFREQUENT_ACCESS,
2214
+ transitionAfter: cdk.Duration.days(infrequentAccessTransitionDays),
2215
+ },
2216
+ {
2217
+ storageClass: StorageClass.GLACIER,
2218
+ transitionAfter: cdk.Duration.days(glacierTransitionDays),
2219
+ },
2220
+ ],
2221
+ },
2222
+ ],
2223
+ });
2224
+ // Add CloudTrail bucket policies
2225
+ this.bucket.addToResourcePolicy(new PolicyStatement({
2226
+ actions: ["s3:GetBucketAcl"],
2227
+ effect: Effect.ALLOW,
2228
+ principals: [new ServicePrincipal("cloudtrail.amazonaws.com")],
2229
+ resources: [this.bucket.bucketArn],
2230
+ }));
2231
+ this.bucket.addToResourcePolicy(new PolicyStatement({
2232
+ actions: ["s3:PutObject"],
2233
+ conditions: {
2234
+ StringEquals: {
2235
+ "s3:x-amz-acl": "bucket-owner-full-control",
2236
+ },
2237
+ },
2238
+ effect: Effect.ALLOW,
2239
+ principals: [new ServicePrincipal("cloudtrail.amazonaws.com")],
2240
+ resources: [`${this.bucket.bucketArn}/*`],
2241
+ }));
2242
+ // Add tags to bucket
2243
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.SERVICE, service);
2244
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2245
+ if (project) {
2246
+ cdk.Tags.of(this.bucket).add(CDK$2.TAG.PROJECT, project);
2247
+ }
2248
+ // Add Datadog notifications if enabled
2249
+ if (enableDatadogNotifications) {
2250
+ const datadogForwarderFunction = resolveDatadogForwarderFunction(scope);
2251
+ this.bucket.addEventNotification(EventType.OBJECT_CREATED, new LambdaDestination(datadogForwarderFunction));
2252
+ }
2253
+ // Create the organization trail
2254
+ this.trail = new Trail(this, "Trail", {
2255
+ bucket: this.bucket,
2256
+ enableFileValidation,
2257
+ isOrganizationTrail: true,
2258
+ managementEvents: ReadWriteType.ALL,
2259
+ trailName,
2260
+ });
2261
+ // Add tags to trail
2262
+ cdk.Tags.of(this.trail).add(CDK$2.TAG.SERVICE, service);
2263
+ cdk.Tags.of(this.trail).add(CDK$2.TAG.ROLE, CDK$2.ROLE.MONITORING);
2264
+ if (project) {
2265
+ cdk.Tags.of(this.trail).add(CDK$2.TAG.PROJECT, project);
2266
+ }
2267
+ }
2268
+ }
2269
+
1567
2270
  /**
1568
2271
  * JaypieSsoPermissions Construct
1569
2272
  *
@@ -2177,5 +2880,5 @@ class JaypieWebDeploymentBucket extends Construct {
2177
2880
  }
2178
2881
  }
2179
2882
 
2180
- export { JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieDatadogSecret, JaypieDnsRecord, JaypieEnvSecret, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieOpenAiSecret, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, addDatadogLayers, constructEnvName, constructStackName, constructTagger, envHostname, isEnv, isProductionEnv, isSandboxEnv, jaypieLambdaEnv, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveHostedZone, resolveParamsAndSecrets };
2883
+ export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDnsRecord, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMongoDbSecret, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, addDatadogLayers, constructEnvName, constructStackName, constructTagger, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveHostedZone, resolveParamsAndSecrets };
2181
2884
  //# sourceMappingURL=index.js.map