From 78407924caa13147c419105a71072662e2b18260 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 20 Nov 2024 12:49:54 -0500 Subject: [PATCH 01/26] added nuke --- .tools/test/stacks/aws-nuke | 1 + .../stacks/plugin/typescript/plugin_stack.ts | 339 +++++++++--------- 2 files changed, 173 insertions(+), 167 deletions(-) create mode 160000 .tools/test/stacks/aws-nuke diff --git a/.tools/test/stacks/aws-nuke b/.tools/test/stacks/aws-nuke new file mode 160000 index 00000000000..5d59d89fbdf --- /dev/null +++ b/.tools/test/stacks/aws-nuke @@ -0,0 +1 @@ +Subproject commit 5d59d89fbdf6851d848fbaa9c9196970ad898e48 diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index a182f13f3f2..bce927fd850 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -1,16 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 + import * as cdk from "aws-cdk-lib"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as events from "aws-cdk-lib/aws-events"; -import * as targets from "aws-cdk-lib/aws-events-targets"; +import * as codebuild from "aws-cdk-lib/aws-codebuild"; import * as iam from "aws-cdk-lib/aws-iam"; -import * as lambda from "aws-cdk-lib/aws-lambda"; -import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; import * as s3 from "aws-cdk-lib/aws-s3"; import * as sns from "aws-cdk-lib/aws-sns"; -import * as sqs from "aws-cdk-lib/aws-sqs"; +import * as events from "aws-cdk-lib/aws-events"; +import * as targets from "aws-cdk-lib/aws-events-targets"; +import * as stepfunctions from "aws-cdk-lib/aws-stepfunctions"; +import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as batch from "aws-cdk-lib/aws-batch"; +import * as sqs from "aws-cdk-lib/aws-sqs"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; import * as subs from "aws-cdk-lib/aws-sns-subscriptions"; import { Construct } from "constructs"; import { readAccountConfig } from "../../config/targets"; @@ -19,16 +23,16 @@ import variableConfigJson from "../../config/variables.json"; const toolName = process.env.TOOL_NAME ?? "defaultToolName"; -class PluginStack extends cdk.Stack { +export class PluginStack extends cdk.Stack { private awsRegion: string; private adminAccountId: string; private batchMemory: string; private batchVcpus: string; - private batchStorage: string; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); + // Configurations for Plugin-specific logic const acctConfig = readAccountConfig("../../config/targets.yaml"); const resourceConfig = readResourceConfig("../../config/resources.yaml"); @@ -41,10 +45,8 @@ class PluginStack extends cdk.Stack { const sqsQueue = new sqs.Queue(this, `BatchJobQueue-${toolName}`); if (acctConfig[`${toolName}`].status === "enabled") { this.initSubscribeSns(sqsQueue, snsTopic); - // https://docs.aws.amazon.com/batch/latest/APIReference/API_ResourceRequirement.html this.batchMemory = acctConfig[`${toolName}`]?.memory ?? "16384"; // MiB this.batchVcpus = acctConfig[`${toolName}`]?.vcpus ?? "4"; // CPUs - this.batchStorage = acctConfig[`${toolName}`]?.storage ?? "20"; // GiB } const [jobDefinition, jobQueue] = this.initBatchFargate(); @@ -54,57 +56,149 @@ class PluginStack extends cdk.Stack { if (acctConfig[`${toolName}`].status === "enabled") { this.initLogFunction(adminBucketName); } + + // Nuke Account Cleansing logic + const bucketName = "nuke-account-cleanser-config"; + const nukeRoleName = "nuke-auto-account-cleanser"; + const dryRunFlag = "true"; + const nukeVersion = "2.21.2"; + const owner = "OpsAdmin"; + + // S3 Bucket + const nukeBucket = new s3.Bucket(this, "NukeBucket", { + bucketName: `${bucketName}-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + removalPolicy: cdk.RemovalPolicy.RETAIN, + }); + + // SNS Topic + const nukeTopic = new sns.Topic(this, "NukeTopic", { + topicName: "nuke-cleanser-notify-topic", + }); + + // IAM Role for CodeBuild + const codebuildRole = new iam.Role(this, "CodeBuildRole", { + assumedBy: new iam.ServicePrincipal("codebuild.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), + ], + }); + + // CodeBuild Project + const codebuildProject = new codebuild.Project(this, "NukeCodeBuild", { + projectName: "AccountNuker", + source: codebuild.Source.s3({ + bucket: nukeBucket, + path: "buildspec.yml", + }), + environment: { + buildImage: codebuild.LinuxBuildImage.STANDARD_5_0, + computeType: codebuild.ComputeType.SMALL, + privileged: true, + }, + role: codebuildRole, + environmentVariables: { + AWS_NukeDryRun: { value: dryRunFlag }, + AWS_NukeVersion: { value: nukeVersion }, + }, + }); + + // Step Functions Role + const stepFunctionsRole = new iam.Role(this, "StepFunctionsRole", { + assumedBy: new iam.ServicePrincipal("states.amazonaws.com"), + inlinePolicies: { + StepFunctionPolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: ["codebuild:StartBuild"], + resources: [codebuildProject.projectArn], + }), + new iam.PolicyStatement({ + actions: ["sns:Publish"], + resources: [nukeTopic.topicArn], + }), + ], + }), + }, + }); + + // Step Functions State Machine + const codeBuildTask = new tasks.CodeBuildStartBuild(this, "CodeBuildTask", { + project: codebuildProject, + environmentVariablesOverride: { + Region: tasks.TaskInput.fromDataAt("$.region_id"), + }, + }); + + const stateMachineDefinition = new stepfunctions.Map(this, "MapState", { + itemsPath: stepfunctions.JsonPath.stringAt("$.InputPayLoad.region_list"), + parameters: { + region_id: stepfunctions.JsonPath.stringAt("$$.Map.Item.Value"), + nuke_dry_run: stepfunctions.JsonPath.stringAt("$.InputPayLoad.nuke_dry_run"), + nuke_version: stepfunctions.JsonPath.stringAt("$.InputPayLoad.nuke_version"), + }, + }).iterator(codeBuildTask); // Correct use of the Map iterator + + + const stateMachine = new stepfunctions.StateMachine(this, "NukeStateMachine", { + stateMachineName: "NukeAccountCleanser", + definition: stateMachineDefinition, + role: stepFunctionsRole, + }); + + // EventBridge Rule for Nuke Schedule + new events.Rule(this, "EventBridgeRule", { + schedule: events.Schedule.cron({ minute: "0", hour: "7" }), + targets: [new targets.SfnStateMachine(stateMachine)], + }); + + // Outputs + new cdk.CfnOutput(this, "NukeBucketName", { value: nukeBucket.bucketName }); + new cdk.CfnOutput(this, "NukeTopicArn", { value: nukeTopic.topicArn }); + new cdk.CfnOutput(this, "AdminBucketName", { value: adminBucketName }); + new cdk.CfnOutput(this, "AdminTopicArn", { value: snsTopic.topicArn }); } private initGetTopic(topicName: string): sns.ITopic { const externalSnsTopicArn = `arn:aws:sns:${this.awsRegion}:${this.adminAccountId}:${topicName}`; - return sns.Topic.fromTopicArn( - this, - "ExternalSNSTopic", - externalSnsTopicArn, - ); + return sns.Topic.fromTopicArn(this, "ExternalSNSTopic", externalSnsTopicArn); } private initBatchFargate(): [batch.CfnJobDefinition, batch.CfnJobQueue] { - const batchExecutionRole = new iam.Role( - this, - `BatchExecutionRole-${toolName}`, - { - assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"), - roleName: `BatchExecutionRole-${toolName}`, - inlinePolicies: { - BatchLoggingPolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - "logs:DescribeLogStreams", - ], - resources: ["arn:aws:logs:*:*:*"], - }), - ], - }), - }, - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), - iam.ManagedPolicy.fromAwsManagedPolicyName( - "AmazonEC2ContainerRegistryReadOnly", - ), - iam.ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AmazonECSTaskExecutionRolePolicy", - ), - ], + const batchExecutionRole = new iam.Role(this, `BatchExecutionRole-${toolName}`, { + assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"), + roleName: `BatchExecutionRole-${toolName}`, + inlinePolicies: { + BatchLoggingPolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + ], + resources: ["arn:aws:logs:*:*:*"], + }), + ], + }), }, - ); + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), + iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonEC2ContainerRegistryReadOnly"), + iam.ManagedPolicy.fromAwsManagedPolicyName( + "service-role/AmazonECSTaskExecutionRolePolicy" + ), + ], + }); const vpc = ec2.Vpc.fromLookup(this, "Vpc", { isDefault: true }); const sg = new ec2.SecurityGroup(this, "sg", { securityGroupName: "batch-sg", vpc, }); + const fargateEnvironment = new batch.CfnComputeEnvironment( this, `FargateEnv-${toolName}`, @@ -116,10 +210,10 @@ class PluginStack extends cdk.Stack { securityGroupIds: [sg.securityGroupId], maxvCpus: 1, }, - }, + } ); - const containerImageUri = `${this.adminAccountId}.dkr.ecr.us-east-1.amazonaws.com/${toolName}:latest`; + const containerImageUri = `${this.adminAccountId}.dkr.ecr.${this.awsRegion}.amazonaws.com/${toolName}:latest`; const jobDefinition = new batch.CfnJobDefinition(this, "JobDefn", { type: "container", @@ -127,22 +221,11 @@ class PluginStack extends cdk.Stack { image: containerImageUri, jobRoleArn: batchExecutionRole.roleArn, executionRoleArn: batchExecutionRole.roleArn, - networkConfiguration: { - assignPublicIp: "ENABLED", - }, + networkConfiguration: { assignPublicIp: "ENABLED" }, resourceRequirements: [ - { - type: "VCPU", - value: this.batchVcpus, - }, - { - type: "MEMORY", - value: this.batchMemory, - }, + { type: "VCPU", value: this.batchVcpus }, + { type: "MEMORY", value: this.batchMemory }, ], - ephemeralStorage: { - sizeInGib: this.batchStorage, - }, environment: variableConfigJson, }, platformCapabilities: ["FARGATE"], @@ -151,10 +234,7 @@ class PluginStack extends cdk.Stack { const jobQueue = new batch.CfnJobQueue(this, `JobQueue-${toolName}`, { priority: 0, computeEnvironmentOrder: [ - { - computeEnvironment: fargateEnvironment.ref, - order: 0, - }, + { computeEnvironment: fargateEnvironment.ref, order: 0 }, ], }); @@ -163,32 +243,26 @@ class PluginStack extends cdk.Stack { private initSubscribeSns(sqsQueue: sqs.Queue, snsTopic: sns.ITopic): void { snsTopic.addSubscription( - new subs.SqsSubscription(sqsQueue, { rawMessageDelivery: true }), + new subs.SqsSubscription(sqsQueue, { rawMessageDelivery: true }) ); } private initBatchLambda( jobQueue: batch.CfnJobQueue, - jobDefinition: batch.CfnJobDefinition, + jobDefinition: batch.CfnJobDefinition ): lambda.Function { - const executionRole = new iam.Role( - this, - `BatchLambdaExecutionRole-${toolName}`, - { - assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), - ], - }, - ); + const executionRole = new iam.Role(this, `BatchLambdaExecutionRole-${toolName}`, { + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + ], + }); executionRole.addToPolicy( new iam.PolicyStatement({ actions: ["batch:SubmitJob", "batch:DescribeJobs"], resources: ["*"], - }), + }) ); return new lambda.Function(this, `SubmitBatchJob-${toolName}`, { @@ -206,22 +280,16 @@ class PluginStack extends cdk.Stack { private initSqsLambdaIntegration( lambdaFunction: lambda.Function, - sqsQueue: sqs.Queue, + sqsQueue: sqs.Queue ): void { - // Add the SQS queue as an event source for the Lambda function. lambdaFunction.addEventSource(new SqsEventSource(sqsQueue)); - - // Grant permissions to allow the function to receive messages from the queue. sqsQueue.grantConsumeMessages(lambdaFunction); - - // Add IAM policy to the Lambda function's execution role to allow it to receive messages from the SQS queue. lambdaFunction.addToRolePolicy( new iam.PolicyStatement({ actions: ["sqs:ReceiveMessage"], resources: [sqsQueue.queueArn], - }), + }) ); - // Additionally, ensure the Lambda function can create and write to CloudWatch Logs. lambdaFunction.addToRolePolicy( new iam.PolicyStatement({ actions: [ @@ -229,94 +297,44 @@ class PluginStack extends cdk.Stack { "logs:CreateLogStream", "logs:PutLogEvents", ], - resources: ["*"], // You might want to restrict this to specific log groups. - }), + resources: ["*"], + }) ); } private initLogFunction(adminBucketName: string): void { - // S3 Bucket to store logs within this account. const bucket = new s3.Bucket(this, "LogBucket", { versioned: false, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); - // Execution role for AWS Lambda function to use - // To get logs and ship them to the Admin account. - // This role is referenced in the Admin stack configuration. - // Modifying it will sever cross-account connection. const executionRole = new iam.Role(this, "CloudWatchExecutionRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), description: "Allows Lambda function to get logs from CloudWatch", roleName: "CloudWatchExecutionRole", managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AWSLambdaBasicExecutionRole", - ), + iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), ], }); - // Update bucket permissions to allow Lambda - const statement = new iam.PolicyStatement({ - actions: [ - "s3:PutObject", - "s3:PutObjectAcl", - "s3:DeleteObject", - "s3:ListBucket", - "s3:GetObject", - ], - resources: [`${bucket.bucketArn}/*`, bucket.bucketArn], - }); - statement.addArnPrincipal( - `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/CloudWatchExecutionRole`, - ); - statement.addArnPrincipal(`arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:root`); - bucket.addToResourcePolicy(statement); - - // Attach custom policy to allow Lambda to get logs from CloudWatch. - executionRole.addToPolicy( + bucket.addToResourcePolicy( new iam.PolicyStatement({ - actions: ["logs:GetLogEvents", "logs:DescribeLogStreams"], - resources: [`arn:aws:logs:${this.awsRegion}:${cdk.Aws.ACCOUNT_ID}:*`], - }), - ); - - // Attach custom policy to allow Lambda to get and put to local logs bucket. - executionRole.addToPolicy( - new iam.PolicyStatement({ - actions: [ - "s3:PutObject", - "s3:PutObjectAcl", - "s3:GetObject", - "s3:ListBucket", - "s3:DeleteObject", - ], + actions: ["s3:PutObject", "s3:PutObjectAcl", "s3:DeleteObject", "s3:ListBucket", "s3:GetObject"], resources: [`${bucket.bucketArn}/*`, bucket.bucketArn], - }), + principals: [new iam.ArnPrincipal(executionRole.roleArn)], + }) ); - // Attach custom policy to allow Lambda to get and put to admin logs bucket. executionRole.addToPolicy( new iam.PolicyStatement({ - actions: [ - "s3:PutObject", - "s3:PutObjectAcl", - "s3:GetObject", - "s3:ListBucket", - "s3:DeleteObject", - ], - resources: [ - `arn:aws:s3:::${adminBucketName}/*`, - `arn:aws:s3:::${adminBucketName}`, - ], - }), + actions: ["logs:GetLogEvents", "logs:DescribeLogStreams"], + resources: [`arn:aws:logs:${this.awsRegion}:${cdk.Aws.ACCOUNT_ID}:*`], + }) ); - // Define the Lambda function. const lambdaFunction = new lambda.Function(this, "BatchJobCompleteLambda", { runtime: lambda.Runtime.PYTHON_3_8, handler: "export_logs.handler", - role: executionRole, code: lambda.Code.fromAsset("lambda"), timeout: cdk.Duration.seconds(60), environment: { @@ -326,29 +344,16 @@ class PluginStack extends cdk.Stack { }, }); - // CloudWatch Event Rule to trigger the Lambda function. const batchRule = new events.Rule(this, "BatchAllEventsRule", { - eventPattern: { - source: ["aws.batch"], - }, + eventPattern: { source: ["aws.batch"] }, }); - // Add the Lambda function as a target for the CloudWatch Event Rule. batchRule.addTarget(new targets.LambdaFunction(lambdaFunction)); } } const app = new cdk.App(); - -new PluginStack( - app, - `PluginStack-${process.env.TOOL_NAME?.replace("_", "-")}`, - { - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION, - }, - }, -); - +new PluginStack(app, "PluginStack", { + env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, +}); app.synth(); From c088de5f3e2748f9f7cd69698eede6154e35733e Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 20 Nov 2024 12:51:51 -0500 Subject: [PATCH 02/26] added nuke --- .../stacks/plugin/typescript/plugin_stack.ts | 345 +++++++++--------- 1 file changed, 170 insertions(+), 175 deletions(-) diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index bce927fd850..b87aeb41da2 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -1,20 +1,16 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - import * as cdk from "aws-cdk-lib"; -import * as codebuild from "aws-cdk-lib/aws-codebuild"; -import * as iam from "aws-cdk-lib/aws-iam"; -import * as s3 from "aws-cdk-lib/aws-s3"; -import * as sns from "aws-cdk-lib/aws-sns"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as events from "aws-cdk-lib/aws-events"; import * as targets from "aws-cdk-lib/aws-events-targets"; -import * as stepfunctions from "aws-cdk-lib/aws-stepfunctions"; -import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as batch from "aws-cdk-lib/aws-batch"; -import * as sqs from "aws-cdk-lib/aws-sqs"; +import * as iam from "aws-cdk-lib/aws-iam"; import * as lambda from "aws-cdk-lib/aws-lambda"; import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources"; +import * as s3 from "aws-cdk-lib/aws-s3"; +import * as sns from "aws-cdk-lib/aws-sns"; +import * as sqs from "aws-cdk-lib/aws-sqs"; +import * as batch from "aws-cdk-lib/aws-batch"; import * as subs from "aws-cdk-lib/aws-sns-subscriptions"; import { Construct } from "constructs"; import { readAccountConfig } from "../../config/targets"; @@ -23,16 +19,16 @@ import variableConfigJson from "../../config/variables.json"; const toolName = process.env.TOOL_NAME ?? "defaultToolName"; -export class PluginStack extends cdk.Stack { +class PluginStack extends cdk.Stack { private awsRegion: string; private adminAccountId: string; private batchMemory: string; private batchVcpus: string; + private batchStorage: number; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); - // Configurations for Plugin-specific logic const acctConfig = readAccountConfig("../../config/targets.yaml"); const resourceConfig = readResourceConfig("../../config/resources.yaml"); @@ -45,8 +41,10 @@ export class PluginStack extends cdk.Stack { const sqsQueue = new sqs.Queue(this, `BatchJobQueue-${toolName}`); if (acctConfig[`${toolName}`].status === "enabled") { this.initSubscribeSns(sqsQueue, snsTopic); + // https://docs.aws.amazon.com/batch/latest/APIReference/API_ResourceRequirement.html this.batchMemory = acctConfig[`${toolName}`]?.memory ?? "16384"; // MiB this.batchVcpus = acctConfig[`${toolName}`]?.vcpus ?? "4"; // CPUs + this.batchStorage = acctConfig[`${toolName}`]?.storage ?? 20; // GiB } const [jobDefinition, jobQueue] = this.initBatchFargate(); @@ -56,149 +54,57 @@ export class PluginStack extends cdk.Stack { if (acctConfig[`${toolName}`].status === "enabled") { this.initLogFunction(adminBucketName); } - - // Nuke Account Cleansing logic - const bucketName = "nuke-account-cleanser-config"; - const nukeRoleName = "nuke-auto-account-cleanser"; - const dryRunFlag = "true"; - const nukeVersion = "2.21.2"; - const owner = "OpsAdmin"; - - // S3 Bucket - const nukeBucket = new s3.Bucket(this, "NukeBucket", { - bucketName: `${bucketName}-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, - removalPolicy: cdk.RemovalPolicy.RETAIN, - }); - - // SNS Topic - const nukeTopic = new sns.Topic(this, "NukeTopic", { - topicName: "nuke-cleanser-notify-topic", - }); - - // IAM Role for CodeBuild - const codebuildRole = new iam.Role(this, "CodeBuildRole", { - assumedBy: new iam.ServicePrincipal("codebuild.amazonaws.com"), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), - ], - }); - - // CodeBuild Project - const codebuildProject = new codebuild.Project(this, "NukeCodeBuild", { - projectName: "AccountNuker", - source: codebuild.Source.s3({ - bucket: nukeBucket, - path: "buildspec.yml", - }), - environment: { - buildImage: codebuild.LinuxBuildImage.STANDARD_5_0, - computeType: codebuild.ComputeType.SMALL, - privileged: true, - }, - role: codebuildRole, - environmentVariables: { - AWS_NukeDryRun: { value: dryRunFlag }, - AWS_NukeVersion: { value: nukeVersion }, - }, - }); - - // Step Functions Role - const stepFunctionsRole = new iam.Role(this, "StepFunctionsRole", { - assumedBy: new iam.ServicePrincipal("states.amazonaws.com"), - inlinePolicies: { - StepFunctionPolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - actions: ["codebuild:StartBuild"], - resources: [codebuildProject.projectArn], - }), - new iam.PolicyStatement({ - actions: ["sns:Publish"], - resources: [nukeTopic.topicArn], - }), - ], - }), - }, - }); - - // Step Functions State Machine - const codeBuildTask = new tasks.CodeBuildStartBuild(this, "CodeBuildTask", { - project: codebuildProject, - environmentVariablesOverride: { - Region: tasks.TaskInput.fromDataAt("$.region_id"), - }, - }); - - const stateMachineDefinition = new stepfunctions.Map(this, "MapState", { - itemsPath: stepfunctions.JsonPath.stringAt("$.InputPayLoad.region_list"), - parameters: { - region_id: stepfunctions.JsonPath.stringAt("$$.Map.Item.Value"), - nuke_dry_run: stepfunctions.JsonPath.stringAt("$.InputPayLoad.nuke_dry_run"), - nuke_version: stepfunctions.JsonPath.stringAt("$.InputPayLoad.nuke_version"), - }, - }).iterator(codeBuildTask); // Correct use of the Map iterator - - - const stateMachine = new stepfunctions.StateMachine(this, "NukeStateMachine", { - stateMachineName: "NukeAccountCleanser", - definition: stateMachineDefinition, - role: stepFunctionsRole, - }); - - // EventBridge Rule for Nuke Schedule - new events.Rule(this, "EventBridgeRule", { - schedule: events.Schedule.cron({ minute: "0", hour: "7" }), - targets: [new targets.SfnStateMachine(stateMachine)], - }); - - // Outputs - new cdk.CfnOutput(this, "NukeBucketName", { value: nukeBucket.bucketName }); - new cdk.CfnOutput(this, "NukeTopicArn", { value: nukeTopic.topicArn }); - new cdk.CfnOutput(this, "AdminBucketName", { value: adminBucketName }); - new cdk.CfnOutput(this, "AdminTopicArn", { value: snsTopic.topicArn }); } private initGetTopic(topicName: string): sns.ITopic { const externalSnsTopicArn = `arn:aws:sns:${this.awsRegion}:${this.adminAccountId}:${topicName}`; - return sns.Topic.fromTopicArn(this, "ExternalSNSTopic", externalSnsTopicArn); + return sns.Topic.fromTopicArn( + this, + "ExternalSNSTopic", + externalSnsTopicArn, + ); } private initBatchFargate(): [batch.CfnJobDefinition, batch.CfnJobQueue] { - const batchExecutionRole = new iam.Role(this, `BatchExecutionRole-${toolName}`, { - assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"), - roleName: `BatchExecutionRole-${toolName}`, - inlinePolicies: { - BatchLoggingPolicy: new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents", - "logs:DescribeLogStreams", - ], - resources: ["arn:aws:logs:*:*:*"], - }), - ], - }), + const batchExecutionRole = new iam.Role( + this, + `BatchExecutionRole-${toolName}`, + { + assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"), + roleName: `BatchExecutionRole-${toolName}`, + inlinePolicies: { + BatchLoggingPolicy: new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + ], + resources: ["arn:aws:logs:*:*:*"], + }), + ], + }), + }, + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), + iam.ManagedPolicy.fromAwsManagedPolicyName( + "AmazonEC2ContainerRegistryReadOnly", + ), + iam.ManagedPolicy.fromAwsManagedPolicyName( + "service-role/AmazonECSTaskExecutionRolePolicy", + ), + ], }, - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess"), - iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonEC2ContainerRegistryReadOnly"), - iam.ManagedPolicy.fromAwsManagedPolicyName( - "service-role/AmazonECSTaskExecutionRolePolicy" - ), - ], - }); + ); const vpc = ec2.Vpc.fromLookup(this, "Vpc", { isDefault: true }); const sg = new ec2.SecurityGroup(this, "sg", { securityGroupName: "batch-sg", vpc, }); - const fargateEnvironment = new batch.CfnComputeEnvironment( this, `FargateEnv-${toolName}`, @@ -210,10 +116,10 @@ export class PluginStack extends cdk.Stack { securityGroupIds: [sg.securityGroupId], maxvCpus: 1, }, - } + }, ); - const containerImageUri = `${this.adminAccountId}.dkr.ecr.${this.awsRegion}.amazonaws.com/${toolName}:latest`; + const containerImageUri = `${this.adminAccountId}.dkr.ecr.us-east-1.amazonaws.com/${toolName}:latest`; const jobDefinition = new batch.CfnJobDefinition(this, "JobDefn", { type: "container", @@ -221,11 +127,22 @@ export class PluginStack extends cdk.Stack { image: containerImageUri, jobRoleArn: batchExecutionRole.roleArn, executionRoleArn: batchExecutionRole.roleArn, - networkConfiguration: { assignPublicIp: "ENABLED" }, + networkConfiguration: { + assignPublicIp: "ENABLED", + }, resourceRequirements: [ - { type: "VCPU", value: this.batchVcpus }, - { type: "MEMORY", value: this.batchMemory }, + { + type: "VCPU", + value: this.batchVcpus, + }, + { + type: "MEMORY", + value: this.batchMemory, + }, ], + ephemeralStorage: { + sizeInGib: this.batchStorage, + }, environment: variableConfigJson, }, platformCapabilities: ["FARGATE"], @@ -234,7 +151,10 @@ export class PluginStack extends cdk.Stack { const jobQueue = new batch.CfnJobQueue(this, `JobQueue-${toolName}`, { priority: 0, computeEnvironmentOrder: [ - { computeEnvironment: fargateEnvironment.ref, order: 0 }, + { + computeEnvironment: fargateEnvironment.ref, + order: 0, + }, ], }); @@ -243,30 +163,36 @@ export class PluginStack extends cdk.Stack { private initSubscribeSns(sqsQueue: sqs.Queue, snsTopic: sns.ITopic): void { snsTopic.addSubscription( - new subs.SqsSubscription(sqsQueue, { rawMessageDelivery: true }) + new subs.SqsSubscription(sqsQueue, { rawMessageDelivery: true }), ); } private initBatchLambda( jobQueue: batch.CfnJobQueue, - jobDefinition: batch.CfnJobDefinition + jobDefinition: batch.CfnJobDefinition, ): lambda.Function { - const executionRole = new iam.Role(this, `BatchLambdaExecutionRole-${toolName}`, { - assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), - ], - }); + const executionRole = new iam.Role( + this, + `BatchLambdaExecutionRole-${toolName}`, + { + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName( + "service-role/AWSLambdaBasicExecutionRole", + ), + ], + }, + ); executionRole.addToPolicy( new iam.PolicyStatement({ actions: ["batch:SubmitJob", "batch:DescribeJobs"], resources: ["*"], - }) + }), ); return new lambda.Function(this, `SubmitBatchJob-${toolName}`, { - runtime: lambda.Runtime.PYTHON_3_8, + runtime: lambda.Runtime.PYTHON_3_9, handler: "submit_job.handler", code: lambda.Code.fromAsset("lambda"), environment: { @@ -280,16 +206,22 @@ export class PluginStack extends cdk.Stack { private initSqsLambdaIntegration( lambdaFunction: lambda.Function, - sqsQueue: sqs.Queue + sqsQueue: sqs.Queue, ): void { + // Add the SQS queue as an event source for the Lambda function. lambdaFunction.addEventSource(new SqsEventSource(sqsQueue)); + + // Grant permissions to allow the function to receive messages from the queue. sqsQueue.grantConsumeMessages(lambdaFunction); + + // Add IAM policy to the Lambda function's execution role to allow it to receive messages from the SQS queue. lambdaFunction.addToRolePolicy( new iam.PolicyStatement({ actions: ["sqs:ReceiveMessage"], resources: [sqsQueue.queueArn], - }) + }), ); + // Additionally, ensure the Lambda function can create and write to CloudWatch Logs. lambdaFunction.addToRolePolicy( new iam.PolicyStatement({ actions: [ @@ -297,44 +229,94 @@ export class PluginStack extends cdk.Stack { "logs:CreateLogStream", "logs:PutLogEvents", ], - resources: ["*"], - }) + resources: ["*"], // You might want to restrict this to specific log groups. + }), ); } private initLogFunction(adminBucketName: string): void { + // S3 Bucket to store logs within this account. const bucket = new s3.Bucket(this, "LogBucket", { versioned: false, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, }); + // Execution role for AWS Lambda function to use + // To get logs and ship them to the Admin account. + // This role is referenced in the Admin stack configuration. + // Modifying it will sever cross-account connection. const executionRole = new iam.Role(this, "CloudWatchExecutionRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), description: "Allows Lambda function to get logs from CloudWatch", roleName: "CloudWatchExecutionRole", managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"), + iam.ManagedPolicy.fromAwsManagedPolicyName( + "service-role/AWSLambdaBasicExecutionRole", + ), ], }); - bucket.addToResourcePolicy( - new iam.PolicyStatement({ - actions: ["s3:PutObject", "s3:PutObjectAcl", "s3:DeleteObject", "s3:ListBucket", "s3:GetObject"], - resources: [`${bucket.bucketArn}/*`, bucket.bucketArn], - principals: [new iam.ArnPrincipal(executionRole.roleArn)], - }) + // Update bucket permissions to allow Lambda + const statement = new iam.PolicyStatement({ + actions: [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:DeleteObject", + "s3:ListBucket", + "s3:GetObject", + ], + resources: [`${bucket.bucketArn}/*`, bucket.bucketArn], + }); + statement.addArnPrincipal( + `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/CloudWatchExecutionRole`, ); + statement.addArnPrincipal(`arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:root`); + bucket.addToResourcePolicy(statement); + // Attach custom policy to allow Lambda to get logs from CloudWatch. executionRole.addToPolicy( new iam.PolicyStatement({ actions: ["logs:GetLogEvents", "logs:DescribeLogStreams"], resources: [`arn:aws:logs:${this.awsRegion}:${cdk.Aws.ACCOUNT_ID}:*`], - }) + }), ); + // Attach custom policy to allow Lambda to get and put to local logs bucket. + executionRole.addToPolicy( + new iam.PolicyStatement({ + actions: [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:GetObject", + "s3:ListBucket", + "s3:DeleteObject", + ], + resources: [`${bucket.bucketArn}/*`, bucket.bucketArn], + }), + ); + + // Attach custom policy to allow Lambda to get and put to admin logs bucket. + executionRole.addToPolicy( + new iam.PolicyStatement({ + actions: [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:GetObject", + "s3:ListBucket", + "s3:DeleteObject", + ], + resources: [ + `arn:aws:s3:::${adminBucketName}/*`, + `arn:aws:s3:::${adminBucketName}`, + ], + }), + ); + + // Define the Lambda function. const lambdaFunction = new lambda.Function(this, "BatchJobCompleteLambda", { - runtime: lambda.Runtime.PYTHON_3_8, + runtime: lambda.Runtime.PYTHON_3_9, handler: "export_logs.handler", + role: executionRole, code: lambda.Code.fromAsset("lambda"), timeout: cdk.Duration.seconds(60), environment: { @@ -344,16 +326,29 @@ export class PluginStack extends cdk.Stack { }, }); + // CloudWatch Event Rule to trigger the Lambda function. const batchRule = new events.Rule(this, "BatchAllEventsRule", { - eventPattern: { source: ["aws.batch"] }, + eventPattern: { + source: ["aws.batch"], + }, }); + // Add the Lambda function as a target for the CloudWatch Event Rule. batchRule.addTarget(new targets.LambdaFunction(lambdaFunction)); } } const app = new cdk.App(); -new PluginStack(app, "PluginStack", { - env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, -}); -app.synth(); + +new PluginStack( + app, + `PluginStack-${process.env.TOOL_NAME?.replace("_", "-")}`, + { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + }, +); + +app.synth(); \ No newline at end of file From f56338f71f9a080eb4247610b9caa973c4483de0 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 27 Nov 2024 11:08:47 -0500 Subject: [PATCH 03/26] Updates --- .tools/test/stacks/aws-nuke | 1 - .tools/test/stacks/aws-nuke/README.md | 127 + .../stacks/aws-nuke/architecture-overview.png | Bin 0 -> 93022 bytes .../test/stacks/aws-nuke/bin/nuke_cleanser.ts | 15 + .tools/test/stacks/aws-nuke/cdk.json | 81 + .tools/test/stacks/aws-nuke/jest.config.js | 8 + .../aws-nuke/lib/nuke_cleanser-stack.ts | 739 +++ .tools/test/stacks/aws-nuke/migrate.json | 4 + .../stacks/aws-nuke/nuke_config_update.py | 190 + .../stacks/aws-nuke/nuke_generic_config.yaml | 243 + .tools/test/stacks/aws-nuke/package-lock.json | 4434 +++++++++++++++++ .tools/test/stacks/aws-nuke/package.json | 27 + .tools/test/stacks/aws-nuke/tsconfig.json | 31 + 13 files changed, 5899 insertions(+), 1 deletion(-) delete mode 160000 .tools/test/stacks/aws-nuke create mode 100644 .tools/test/stacks/aws-nuke/README.md create mode 100644 .tools/test/stacks/aws-nuke/architecture-overview.png create mode 100644 .tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts create mode 100644 .tools/test/stacks/aws-nuke/cdk.json create mode 100644 .tools/test/stacks/aws-nuke/jest.config.js create mode 100644 .tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts create mode 100644 .tools/test/stacks/aws-nuke/migrate.json create mode 100644 .tools/test/stacks/aws-nuke/nuke_config_update.py create mode 100644 .tools/test/stacks/aws-nuke/nuke_generic_config.yaml create mode 100644 .tools/test/stacks/aws-nuke/package-lock.json create mode 100644 .tools/test/stacks/aws-nuke/package.json create mode 100644 .tools/test/stacks/aws-nuke/tsconfig.json diff --git a/.tools/test/stacks/aws-nuke b/.tools/test/stacks/aws-nuke deleted file mode 160000 index 5d59d89fbdf..00000000000 --- a/.tools/test/stacks/aws-nuke +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5d59d89fbdf6851d848fbaa9c9196970ad898e48 diff --git a/.tools/test/stacks/aws-nuke/README.md b/.tools/test/stacks/aws-nuke/README.md new file mode 100644 index 00000000000..a96158f761a --- /dev/null +++ b/.tools/test/stacks/aws-nuke/README.md @@ -0,0 +1,127 @@ + +# aws-nuke for Weathertop + +AWS Nuke is an open source tool created by [ekristen](https://github.com/ekristen/aws-nuke). + +It searches for deleteable resources in the provided AWS account and deletes those which are not considered "Default" or "AWS-Managed", taking your account back to Day 1 with few exceptions. + + +## ⚠ Important +This is a very destructive tool! It should not be deployed without fully understanding the impact it will have on your AWS accounts. +Please use caution and configure this tool to delete unused resources only in your lower test/sandbox environment accounts. + +## Overview + +The code in this repository helps you set up the following architecture: + +![infrastructure-overview](architecture-overview.png) + +## Feature Outline + +1. **Scheduled Trigger**: Amazon EventBridge invokes AWS Step Functions daily. +2. **Regional Scalability**: Runs AWS CodeBuild projects per region. +4. **Custom Config**: Pulls resource filters and region targets in [nuke_generic_config.yaml](nuke_generic_config.yaml). + +## Prerequisites + +1. **AWS-Nuke Binary**: Open-source library from [ekristen](https://github.com/ekristen/aws-nuke) staged in S3. +2. **AWS Account Alias**: Must exist in the IAM Dashboard for the target account. +3. **AWS CodeBuild**: Required for runtime and compute. +4. **AWS S3 Bucket**: Stores the nuke config file and AWS-Nuke binary (latest version). +5. **AWS Step Functions**: Orchestrates multi-region parallel CodeBuild invocations. +6. **AWS SNS Topic**: Sends daily resource cleanup reports and CodeBuild job statuses. +7. **AWS EventBridge Rule**: Cron-based schedule to trigger the workflow with region inputs. +8. **Network Connectivity**: Ensure VPC allows downloads from GitHub or stage the binary in S3/artifactory for restricted environments. + +## Setup and Installation + +* Clone the [repo](https://github.com/ekristen/aws-nuke). +* Determine the `id` of the account to be deployed for nuking. +* Narrow [filters](nuke_generic_config.yaml) for the resources/accounts you wish to nuke. +* Deploy the stack using the below command. You can run it in any desired region. +```sh +cdk bootstrap && cdk deploy +``` + +## Testing +Once stack is created, upload the [nuke generic config file](nuke_generic_config.yaml) and the [python script](cp nuke_config_update.py) to the S3 bucket using the commands below. + +You can find the name of the S3 bucket generated from the CloudFormation console `Outputs` tab. +```sh +aws s3 cp nuke_generic_config.yaml --region us-east-1 s3://{your-bucket-name} +aws s3 cp nuke_config_update.py --region us-east-1 s3://{your-bucket-name} +``` +* Run the stack manually by triggering the StepFunctions with the below sample input payload. (which is pre-configured in the EventBridge Target as a Constant JSON input). You can configure this to run in parallel on the required number of regions by updating the region_list parameter. + +```sh +{ + "InputPayLoad": { + "nuke_dry_run": "true", + "nuke_version": "2.21.2", + "region_list": [ + "global", + "us-west-1", + "us-east-1" + ] + } +} +``` + +## Other notes: +* The tool is currently configured to run at a schedule as desired typically off hours 3:00a EST. It can be easily configured with a rate() or cron() expression by editing the cfn template file + +* The workflow also sends out a detailed report to an SNS topic with an active email subscription on what resources were deleted after the job is successful for each region which simplifies traversing and parsing the complex logs spit out by the aws-nuke binary. + +* If the workflow is successful , the stack will send out + - One email for each of the regions where nuke CodeBuild job was invoked with details of the build execution , the list of resources which was deleted along with the log file path. + - The StepFunctions workflow also sends out another email when the whole Map state process completes successfully. Sample email template given below. + +```sh + Account Cleansing Process Completed; + + ------------------------------------------------------------------ + Summary of the process: + ------------------------------------------------------------------ + DryRunMode : true + Account ID : 123456789012 + Target Region : us-west-1 + Build State : JOB SUCCEEDED + Build ID : AccountNuker-NukeCleanser:4509a9b5 + CodeBuild Project Name : AccountNuker-NukeCleanser + Process Start Time : Thu Feb 23 04:05:21 UTC 2023 + Process End Time : Thu Feb 23 04:05:54 UTC 2023 + Log Stream Path : AccountNuker-NukeCleanser/logPath + ------------------------------------------------------------------ + ################ Nuke Cleanser Logs ################# + + FAILED RESOURCES + ------------------------------- + Total number of Resources that would be removed: + 3 + us-west-1 - SQSQueue - https://sqs.us-east-1.amazonaws.com/123456789012/test-nuke-queue - would remove + us-west-1 - SNSTopic - TopicARN: arn:aws:sns:us-east-1:123456789012:test-nuke-topic - [TopicARN: "arn:aws:sns:us-east-1:123456789012:test-topic"] - would remove + us-west-1 - S3Bucket - s3://test-nuke-bucket-us-west-1 - [CreationDate: "2023-01-25 11:13:14 +0000 UTC", Name: "test-nuke-bucket-us-west-1"] - would remove + +``` + +## Monitoring queries + +Use the `aws-cli` to get CloudWatch logs associated with your most recent nuclear activity. + +```sh +# Get the current Unix timestamp +CURRENT_TIME=$(date +%s000) + +# Get the timestamp from 5 minutes ago +FIVE_MINUTES_AGO=$(($(date +%s) - 300))000 + +# Filter log events using above date range +aws logs filter-log-events \ + --log-group-name AccountNuker-nuke-auto-account-cleanser \ + --start-time $FIVE_MINUTES_AGO --end-time $CURRENT_TIME \ + --log-stream-names "10409c89-a90f-4af7-9642-0df9bc9f0855" \ + --filter-pattern removed \ + --no-interleaved \ + --output text \ + --limit 5 +``` diff --git a/.tools/test/stacks/aws-nuke/architecture-overview.png b/.tools/test/stacks/aws-nuke/architecture-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..846466f8f49b69b5b00829c8c9f4a22d48280a80 GIT binary patch literal 93022 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%0DGVm2`Y+r>zm`<0&ri8bQFO&%16m=MF>fB~ zWpT+f%GMfPdm#yu8%?HoDvuK+V!=}trD9`dcQj%8fBN!Y!sIC7xmV; zkz!aNaa+U7vj*=eh}+I%uKjq$OUoea0c(fD)~9uDm|tq%Xj+Iv669>6rh1z7nYJh` zugamlR8hqgeMT%3tO|uSHKivM;Y5cKEV)@J#e`LCud|p**2uY|yDRw;0-gv=SPYC& zM7N42Eca>Xk5$=^+IXNbi1lrIB+RREg=2A3P;0ZVCu;GONZrlvuhl7u#k}Nr>?uDZ zcvyeD$hdQBXC!%s#W5#{JK8A);Q79v$w$xgIlH7WuZ%HG1uRf2qVUPc*Bz>)Yt!nn zaV#Gulyp4;WG%3jP_Yhnru+e`me-Bs*%8Qz80TdJ{;PCGT8k1opK%6{S1F=0l3aD- zs=>-Iv0>9{X5u8AjyVHM_1NUejAdeW4<~Vu(ynsG`PJg$f=4Yrlb@SYX0tqJwCxvD zg7HN1(80Ud4L>v)b5X|#EZL0fuX366^#b?=CZz7^Hn#4H1@olCL)F>+XFK?Int2EC zQ|_Z&Upsi@Y2WPt)+mezDV2lvbH~hsR?(clLP&KiJ5sH_9cs?B7oUg15V+OuNyB9d z(C`el$~(#+L0!R&G3Vlu9SWDc1a>!c-t*YbBdhfb2uuK6SH4xDLH;n}%g{4ajBoo{ zHUIWO7L&&sCw8wZelEQ-kOPV5iPt1HKOSg+z^Tws41xP>cLcp?C5Q$w3P9)TZwc8* z;NNu`a95oU)tIp>h*C*hG8A&$0yoY`>;rmXJt0zmt|)o_wistjgjc(*KA)MpP>+y6Kn>8LsU6k1=K*;)~hY*_G|QnUcnHoWL7j;_+@S>x*< z>w;@A9^ka&KM;OhSBL~B*|g*=cD<0CV@ES^`)0s zhYf8!DfSk;f#4Tt*2|?h`|)0;t0TmVFw=AeW1XS`Y8U*zNBn{n%6WvOJLE$d`g-h; z1EZY)(lgD1aWvwvrW$py>VVt|RBaw&5N^`kLUWu1T-R4P zU>+Nq`1Fc5Kq{8{>~yNcrG{$eISiD0L}x#)Ch3&-+%ZUEO=9c2=~Ozrr38{>^H0Pa z8I^*QqHhhQPX^D3`mdrL74vnbN-4J z24+GC;xwo%kbAXOdl0X)E4zndN2i~TnrV`}xf<-)hXa2}_!y9uB)W6>@-- zVp$!sc&u2WKo983w%?aGviAEtiE9##|-@O2vB5K5AlLZ9ilQ< z+)01`U#af!l0!RW&~Qr?)a3fO^v!mUjSnuy!BG-<*8wH-^ey+ke2O{`i9=P~28GAZ zu&|LpzfF}UWu|}JkvH*oa1>)#K*DF7p?n zaAsB75R*Kw&L-%0@b@&g#U=}MZ;Smb{_kMpDQ|e~V4Z4xn)>*l_nMk)1x9s<4lJYD;GbuNoumu8#4 z8u*qOnmH-X6_b#VXOZHgA>z5$?@u7AWx}8xWjj$}1y~;?S}gHA)*4s(E(lKF?lsZ$ ze+Jj&PcerhC+J=BXz0kuua;mU&}ZFWAK@E~y^zr&I63Q40_!pFiO&Rm*Erb{(s_yp z^a>fC7b9`wzJsL3b@cS0z>GO9!Nj$TzQM%o8s}%HQ%>xE2XR<^5_>iN9(AP4;r;a= zKVE*nIM(Y=<&Pq@q!T8RA-VtH0S|PD!FH+!MSXY!=bOWfuGorLTd0>Oi5*O}vJjqe zNYS7IKPBgHh7~#|t?MldjwEcs)TCNy`sKP)#AfCLe%gS|fOYMyko}!hyvwL~%!W6H zM27`VL!7{uGxf7EoCfce@8IU)_4s$#JtD*%0AnJaWC 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

juJH! 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 U!Z)5@W07ymNGeF=iRpR$FM$We+yDRo literal 0 HcmV?d00001 diff --git a/.tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts b/.tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts new file mode 100644 index 00000000000..4f580113651 --- /dev/null +++ b/.tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts @@ -0,0 +1,15 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { NukeCleanserStack } from '../lib/nuke_cleanser-stack'; + +const app = new cdk.App(); + +new NukeCleanserStack(app, 'NukeCleanser', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); + +app.synth(); \ No newline at end of file diff --git a/.tools/test/stacks/aws-nuke/cdk.json b/.tools/test/stacks/aws-nuke/cdk.json new file mode 100644 index 00000000000..79baba4607c --- /dev/null +++ b/.tools/test/stacks/aws-nuke/cdk.json @@ -0,0 +1,81 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/nuke_cleanser.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "cdk-migrate": true + } +} diff --git a/.tools/test/stacks/aws-nuke/jest.config.js b/.tools/test/stacks/aws-nuke/jest.config.js new file mode 100644 index 00000000000..08263b8954a --- /dev/null +++ b/.tools/test/stacks/aws-nuke/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts b/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts new file mode 100644 index 00000000000..3d6961962b0 --- /dev/null +++ b/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts @@ -0,0 +1,739 @@ +import * as cdk from 'aws-cdk-lib'; +import * as codebuild from 'aws-cdk-lib/aws-codebuild'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as stepfunctions from 'aws-cdk-lib/aws-stepfunctions'; + +export interface NukeCleanserStackProps extends cdk.StackProps { + /** + * The name of the bucket where the nuke binary and config files are stored + * @default 'nuke-account-cleanser-config' + */ + readonly bucketName?: string; + /** + * The name of the Nuke Role to be assumed within each account, providing permissions to cleanse the account(s) + * @default 'nuke-auto-account-cleanser' + */ + readonly nukeCleanserRoleName?: string; + /** + * IAM Path + * @default '/' + */ + readonly iamPath?: string; + /** + * The dry run flag to run for the aws-nuke. By default it is set to True which will not delete any resources. + * @default 'true' + */ + readonly awsNukeDryRunFlag?: string; + /** + * The aws-nuke latest version to be used from internal artifactory/S3. Make sure to check the latest releases and any resource additions added. As you update the version, you will have to handle filtering any new resources that gets updated with that new version. + * @default '2.21.2' + */ + readonly awsNukeVersion?: string; + /** + * The SNS Topic name to publish the nuke output logs email + * @default 'nuke-cleanser-notify-topic' + */ + readonly nukeTopicName?: string; + /** + * The Owner of the account to be used for tagging purpose + * @default 'OpsAdmin' + */ + readonly owner?: string; +} + +/** + * This CFN Template creates a StepFunction state machine definition , to invoke a CodeBuild Project and associated resources for setting up a single-account script that cleanses the resources across supplied regions via AWS-Nuke. It also creates the role for the nuke cleanser which is used for configuring the credentials for aws-nuke binary to cleanse resources within that account/region. + + */ +export class NukeCleanserStack extends cdk.Stack { + /** + * Arn of SNS Topic used for notifying nuke results in email + */ + public readonly nukeTopicArn; + /** + * S3 bucket created with the random generated name + */ + public readonly nukeS3BucketValue; + + public constructor(scope: cdk.App, id: string, props: NukeCleanserStackProps = {}) { + super(scope, id, props); + + // Applying default props + props = { + ...props, + bucketName: props.bucketName ?? 'nuke-account-cleanser-config', + nukeCleanserRoleName: props.nukeCleanserRoleName ?? 'nuke-auto-account-cleanser', + iamPath: props.iamPath ?? '/', + awsNukeDryRunFlag: props.awsNukeDryRunFlag ?? 'true', + awsNukeVersion: props.awsNukeVersion ?? '2.21.2', + nukeTopicName: props.nukeTopicName ?? 'nuke-cleanser-notify-topic', + owner: props.owner ?? 'OpsAdmin', + }; + + // Resources + const nukeAccountCleanserPolicy = new iam.CfnManagedPolicy(this, 'NukeAccountCleanserPolicy', { + managedPolicyName: 'NukeAccountCleanser', + policyDocument: { + Statement: [ + { + Action: [ + 'access-analyzer:*', + 'autoscaling:*', + 'aws-portal:*', + 'budgets:*', + 'cloudtrail:*', + 'cloudwatch:*', + 'config:*', + 'ec2:*', + 'ec2messages:*', + 'elasticloadbalancing:*', + 'eks:*', + 'elasticache:*', + 'events:*', + 'firehose:*', + 'guardduty:*', + 'iam:*', + 'inspector:*', + 'kinesis:*', + 'kms:*', + 'lambda:*', + 'logs:*', + 'organizations:*', + 'pricing:*', + 's3:*', + 'secretsmanager:*', + 'securityhub:*', + 'sns:*', + 'sqs:*', + 'ssm:*', + 'ssmmessages:*', + 'sts:*', + 'support:*', + 'tag:*', + 'trustedadvisor:*', + 'waf-regional:*', + 'wafv2:*', + 'cloudformation:*', + ], + Effect: 'Allow', + Resource: '*', + Sid: 'WhitelistedServices', + }, + ], + Version: '2012-10-17', + }, + description: 'Managed policy for nuke account cleansing', + path: props.iamPath!, + }); + + const nukeEmailTopic = new sns.CfnTopic(this, 'NukeEmailTopic', { + displayName: 'NukeTopic', + fifoTopic: false, + kmsMasterKeyId: 'alias/aws/sns', + subscription: [ + { + endpoint: 'test@test.com', + protocol: 'email', + }, + ], + topicName: props.nukeTopicName!, + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + }); + + const nukeS3Bucket = new s3.CfnBucket(this, 'NukeS3Bucket', { + bucketName: [ + props.bucketName!, + this.account, + this.region, + cdk.Fn.select(0, cdk.Fn.split('-', cdk.Fn.select(2, cdk.Fn.split('/', this.stackId)))), + ].join('-'), + publicAccessBlockConfiguration: { + blockPublicAcls: true, + ignorePublicAcls: true, + blockPublicPolicy: true, + restrictPublicBuckets: true, + }, + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + }); + nukeS3Bucket.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN; + + const nukeCodeBuildProjectRole = new iam.CfnRole(this, 'NukeCodeBuildProjectRole', { + roleName: `NukeCodeBuildProject-${this.stackName}`, + assumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: 'codebuild.amazonaws.com', + }, + Action: 'sts:AssumeRole', + }, + ], + }, + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + policies: [ + { + policyName: 'NukeCodeBuildLogsPolicy', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: [ + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + 'logs:DescribeLogStreams', + 'logs:FilterLogEvents', + ], + Resource: [ + `arn:aws:logs:${this.region}:${this.account}:log-group:AccountNuker-${this.stackName}`, + `arn:aws:logs:${this.region}:${this.account}:log-group:AccountNuker-${this.stackName}:*`, + ], + }, + ], + }, + }, + { + policyName: 'AssumeNukePolicy', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: 'sts:AssumeRole', + Resource: `arn:aws:iam::*:role/${props.nukeCleanserRoleName!}`, + }, + ], + }, + }, + { + policyName: 'NukeListOUAccounts', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: 'organizations:ListAccountsForParent', + Resource: '*', + }, + ], + }, + }, + { + policyName: 'S3BucketReadOnly', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: [ + 's3:Get*', + 's3:List*', + ], + Resource: [ + `arn:aws:s3:::${nukeS3Bucket.ref}`, + `arn:aws:s3:::${nukeS3Bucket.ref}/*`, + ], + }, + ], + }, + }, + { + policyName: 'SNSPublishPolicy', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: [ + 'sns:ListTagsForResource', + 'sns:ListSubscriptionsByTopic', + 'sns:GetTopicAttributes', + 'sns:Publish', + ], + Resource: [ + nukeEmailTopic.ref, + ], + }, + ], + }, + }, + ], + }); + + const nukeS3BucketPolicy = new s3.CfnBucketPolicy(this, 'NukeS3BucketPolicy', { + bucket: nukeS3Bucket.ref, + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Sid: 'ForceSSLOnlyAccess', + Effect: 'Deny', + Principal: '*', + Action: 's3:*', + Resource: [ + `arn:aws:s3:::${nukeS3Bucket.ref}`, + `arn:aws:s3:::${nukeS3Bucket.ref}/*`, + ], + Condition: { + Bool: { + 'aws:SecureTransport': 'false', + }, + }, + }, + ], + }, + }); + + const nukeAccountCleanserRole = new iam.CfnRole(this, 'NukeAccountCleanserRole', { + roleName: props.nukeCleanserRoleName!, + description: 'Nuke Auto account cleanser role for Dev/Sandbox accounts', + maxSessionDuration: 7200, + tags: [ + { + key: 'privileged', + value: 'true', + }, + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'description', + value: 'PrivilegedReadWrite:auto-account-cleanser-role', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + assumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + AWS: [ + nukeCodeBuildProjectRole.attrArn, + ], + }, + }, + ], + Version: '2012-10-17', + }, + managedPolicyArns: [ + nukeAccountCleanserPolicy.ref, + ], + path: props.iamPath!, + }); + + const nukeCodeBuildProject = new codebuild.CfnProject(this, 'NukeCodeBuildProject', { + artifacts: { + type: 'NO_ARTIFACTS', + }, + badgeEnabled: false, + description: 'Builds a container to run AWS-Nuke for all accounts within the specified account/regions', + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + environment: { + computeType: 'BUILD_GENERAL1_SMALL', + image: 'aws/codebuild/docker:18.09.0', + imagePullCredentialsType: 'CODEBUILD', + privilegedMode: true, + type: 'LINUX_CONTAINER', + environmentVariables: [ + { + name: 'AWS_NukeDryRun', + type: 'PLAINTEXT', + value: props.awsNukeDryRunFlag!, + }, + { + name: 'AWS_NukeVersion', + type: 'PLAINTEXT', + value: props.awsNukeVersion!, + }, + { + name: 'Publish_TopicArn', + type: 'PLAINTEXT', + value: nukeEmailTopic.ref, + }, + { + name: 'NukeS3Bucket', + type: 'PLAINTEXT', + value: nukeS3Bucket.ref, + }, + { + name: 'NukeAssumeRoleArn', + type: 'PLAINTEXT', + value: nukeAccountCleanserRole.attrArn, + }, + { + name: 'NukeCodeBuildProjectName', + type: 'PLAINTEXT', + value: `AccountNuker-${this.stackName}`, + }, + ], + }, + logsConfig: { + cloudWatchLogs: { + groupName: `AccountNuker-${this.stackName}`, + status: 'ENABLED', + }, + }, + name: `AccountNuker-${this.stackName}`, + serviceRole: nukeCodeBuildProjectRole.attrArn, + timeoutInMinutes: 120, + source: { + buildSpec: 'version: 0.2\nphases:\n install:\n on-failure: ABORT\n commands:\n - export AWS_NUKE_VERSION=$AWS_NukeVersion\n - apt-get install -y wget\n - apt-get install jq\n - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate\n - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz\n - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64\n - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke\n - aws-nuke version\n - echo \"Setting aws cli profile with config file for role assumption using metadata\"\n - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}\n - aws configure set profile.nuke.credential_source \"EcsContainer\"\n - export AWS_PROFILE=nuke\n - export AWS_DEFAULT_PROFILE=nuke\n - export AWS_SDK_LOAD_CONFIG=1\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n build:\n on-failure: CONTINUE\n commands:\n - echo \" ------------------------------------------------ \" >> error_log.txt\n - echo \"Getting nuke generic config file from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .\n - echo \"Updating the TARGET_REGION in the generic config from the parameter\"\n - sed -i \"s/TARGET_REGION/$NukeTargetRegion/g\" nuke_generic_config.yaml\n - echo \"Getting filter/exclusion python script from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n - echo \"Running Config filter/update script\";\n - python3 nuke_config_update.py --account $account_id --region \"$NukeTargetRegion\";\n - echo \"Configured nuke_config.yaml\";\n - echo \"Running Nuke on Account\";\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done\n elif [ \"$AWS_NukeDryRun\" = \"false\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done\n else\n echo \"Couldn\'t determine Dryrun flag...exiting\"\n exit 1\n fi\n - nuke_pid=$!;\n - wait $nuke_pid;\n - echo \"Checking if Nuke Process completed for account\"\n - |\n if cat aws-nuke.log | grep -F \"Error: The specified account doesn\"; then\n echo \"Nuke errored due to no AWS account alias set up - exiting\"\n cat aws-nuke.log >> error_log.txt\n exit 1\n else\n echo \"Nuke completed Successfully - Continuing\"\n fi\n\n post_build:\n commands:\n - echo $CODEBUILD_BUILD_SUCCEEDING\n - echo \"Get current timestamp for naming reports\"\n - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))\n - CURR_TIME_UTC=$(date -u)\n - |\n {\n echo \" Account Cleansing Process Failed;\"\n echo \"\"\n \n echo \" ----------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ----------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ----------------------------------------------------------------\"\n echo \" ################# Failed Nuke Process - Exiting ###################\"\n echo \"\"\n } >> fail_email_template.txt\n - | \n if [ \"$CODEBUILD_BUILD_SUCCEEDING\" = \"0\" ]; then \n echo \" Couldn\'t process Nuke Cleanser - Exiting \" >> fail_email_template.txt\n cat error_log.txt >> fail_email_template.txt\n aws sns publish --topic-arn $Publish_TopicArn --message file://fail_email_template.txt --subject \"Nuke Account Cleanser Failed in account $account_id and region $NukeTargetRegion\"\n exit 1;\n fi\n - sleep 120\n - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;\n - CURR_TIME_UTC=$(date -u)\n - | \n if [ -z \"${LOG_STREAM_NAME}\" ]; then\n echo \"Couldn\'t find the log stream for log events\";\n exit 0;\n else\n aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern \"removed\" --no-interleaved | jq -r .events[].message > log_output.txt;\n awk \'/There are resources in failed state/,/Error: failed/\' aws-nuke.log > failure_email_output.txt\n awk \'/Error: failed/,/\\n/\' failure_email_output.txt > failed_log_output.txt\n fi\n - |\n if [ -r log_output.txt ]; then\n content=$(cat log_output.txt)\n echo $content\n elif [ -f \"log_output.txt\" ]; then\n echo \"The file log_output.txt exists but is not readable to the script.\"\n else\n echo \"The file log_output.txt does not exist.\"\n fi\n - echo \"Publishing Log Ouput to SNS:\"\n - sub=\"Nuke Account Cleanser Succeeded in account \"$account_id\" and region \"$NukeTargetRegion\"\"\n - |\n {\n echo \" Account Cleansing Process Completed;\"\n echo \"\"\n \n echo \" ------------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ------------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ------------------------------------------------------------------\"\n echo \" ################### Nuke Cleanser Logs ####################\"\n echo \"\"\n } >> email_template.txt\n\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"No Resources scanned and nukeable yet\"\n - echo \"Number of Resources that is filtered by config:\" >> email_template.txt\n - cat aws-nuke.log | grep -c \" - filtered by config\" || echo 0 >> email_template.txt\n - echo \" ------------------------------------------ \" >> email_template.txt\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n echo \"RESOURCES THAT WOULD BE REMOVED:\" >> email_template.txt\n echo \" ----------------------------------------- \" >> email_template.txt\n cat aws-nuke.log | grep -c \" - would remove\" || echo 0 >> email_template.txt\n cat aws-nuke.log | grep -F \" - would remove\" >> email_template.txt || echo \"No resources to be removed\" >> email_template.txt\n else\n echo \" FAILED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat failed_log_output.txt >> email_template.txt\n echo \" SUCCESSFULLY NUKED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat log_output.txt >> email_template.txt\n fi\n - aws sns publish --topic-arn $Publish_TopicArn --message file://email_template.txt --subject \"$sub\"\n - echo \"Resources Nukeable:\"\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"Nothing Nukeable yet\"\n - echo \"Total number of Resources that would be removed:\"\n - cat aws-nuke.log | grep -c \" - would remove\" || echo \"Nothing would be removed yet\"\n - echo \"Total number of Resources Deleted:\"\n - cat aws-nuke.log | grep -c \" - removed\" || echo \"Nothing deleted yet\"\n - echo \"List of Resources Deleted today:\"\n - cat aws-nuke.log | grep -F \" - removed\" || echo \"Nothing deleted yet\"\n', + type: 'NO_SOURCE', + }, + }); + + const nukeStepFunctionRole = new iam.CfnRole(this, 'NukeStepFunctionRole', { + roleName: 'nuke-account-cleanser-codebuild-state-machine-role', + assumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: [ + `states.${this.region}.amazonaws.com`, + ], + }, + Action: [ + 'sts:AssumeRole', + ], + }, + ], + }, + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + path: '/', + policies: [ + { + policyName: 'nuke-account-cleanser-codebuild-state-machine-policy', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: [ + 'codebuild:StartBuild', + 'codebuild:StartBuild', + 'codebuild:StopBuild', + 'codebuild:StartBuildBatch', + 'codebuild:StopBuildBatch', + 'codebuild:RetryBuild', + 'codebuild:RetryBuildBatch', + 'codebuild:BatchGet*', + 'codebuild:GetResourcePolicy', + 'codebuild:DescribeTestCases', + 'codebuild:DescribeCodeCoverages', + 'codebuild:List*', + ], + Resource: [ + nukeCodeBuildProject.attrArn, + ], + }, + { + Effect: 'Allow', + Action: [ + 'events:PutTargets', + 'events:PutRule', + 'events:DescribeRule', + ], + Resource: `arn:aws:events:${this.region}:${this.account}:rule/StepFunctionsGetEventForCodeBuildStartBuildRule`, + }, + { + Effect: 'Allow', + Action: [ + 'sns:Publish', + ], + Resource: [ + nukeEmailTopic.ref, + ], + }, + { + Effect: 'Allow', + Action: [ + 'states:DescribeStateMachine', + 'states:ListExecutions', + 'states:StartExecution', + 'states:StopExecution', + 'states:DescribeExecution', + ], + Resource: [ + `arn:aws:states:${this.region}:${this.account}:stateMachine:nuke-account-cleanser-codebuild-state-machine`, + ], + }, + ], + }, + }, + ], + }); + + const nukeStepFunction = new stepfunctions.CfnStateMachine(this, 'NukeStepFunction', { + stateMachineName: 'nuke-account-cleanser-codebuild-state-machine', + roleArn: nukeStepFunctionRole.attrArn, + definitionString: `{ + "Comment": "AWS Nuke Account Cleanser for multi-region single account clean up using SFN Map state parallel invocation of CodeBuild project.", + "StartAt": "StartNukeCodeBuildForEachRegion", + "States": { + "StartNukeCodeBuildForEachRegion": { + "Type": "Map", + "ItemsPath": "$.InputPayLoad.region_list", + "Parameters": { + "region_id.$": "$$.Map.Item.Value", + "nuke_dry_run.$": "$.InputPayLoad.nuke_dry_run", + "nuke_version.$": "$.InputPayLoad.nuke_version" + }, + "Next": "Clean Output and Notify", + "MaxConcurrency": 0, + "Iterator": { + "StartAt": "Trigger Nuke CodeBuild Job", + "States": { + "Trigger Nuke CodeBuild Job": { + "Type": "Task", + "Resource": "arn:aws:states:::codebuild:startBuild.sync", + "Parameters": { + "ProjectName": "${nukeCodeBuildProject.attrArn}", + "EnvironmentVariablesOverride": [ + { + "Name": "NukeTargetRegion", + "Type": "PLAINTEXT", + "Value.$": "$.region_id" + }, + { + "Name": "AWS_NukeDryRun", + "Type": "PLAINTEXT", + "Value.$": "$.nuke_dry_run" + }, + { + "Name": "AWS_NukeVersion", + "Type": "PLAINTEXT", + "Value.$": "$.nuke_version" + } + ] + }, + "Next": "Check Nuke CodeBuild Job Status", + "ResultSelector": { + "NukeBuildOutput.$": "$.Build" + }, + "ResultPath": "$.AccountCleanserRegionOutput", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "BackoffRate": 1, + "IntervalSeconds": 1, + "MaxAttempts": 1 + } + ], + "Catch": [ + { + "ErrorEquals": [ + "States.ALL" + ], + "Next": "Nuke Failed", + "ResultPath": "$.AccountCleanserRegionOutput" + } + ] + }, + "Check Nuke CodeBuild Job Status": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus", + "StringEquals": "SUCCEEDED", + "Next": "Nuke Success" + }, + { + "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus", + "StringEquals": "FAILED", + "Next": "Nuke Failed" + } + ], + "Default": "Nuke Success" + }, + "Nuke Success": { + "Type": "Pass", + "Parameters": { + "Status": "Succeeded", + "Region.$": "$.region_id", + "CodeBuild Status.$": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus" + }, + "ResultPath": "$.result", + "End": true + }, + "Nuke Failed": { + "Type": "Pass", + "Parameters": { + "Status": "Failed", + "Region.$": "$.region_id", + "CodeBuild Status.$": "States.Format('Nuke Account Cleanser failed with error {}. Check CodeBuild execution for input region {} to investigate', $.AccountCleanserRegionOutput.Error, $.region_id)" + }, + "ResultPath": "$.result", + "End": true + } + } + }, + "ResultSelector": { + "filteredResult.$": "$..result" + }, + "ResultPath": "$.NukeFinalMapAllRegionsOutput" + }, + "Clean Output and Notify": { + "Type": "Task", + "Resource": "arn:aws:states:::sns:publish", + "Parameters": { + "Subject": "State Machine for Nuke Account Cleanser completed", + "Message.$": "States.Format('Nuke Account Cleanser completed for input payload: {}. ----------------------------------------- Check the summmary of execution below: {}', $.InputPayLoad, $.NukeFinalMapAllRegionsOutput.filteredResult)", + "TopicArn": "${nukeEmailTopic.ref}" + }, + "End": true + } + } + }`, + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + }); + + const eventBridgeNukeScheduleRole = new iam.CfnRole(this, 'EventBridgeNukeScheduleRole', { + roleName: `EventBridgeNukeSchedule-${this.stackName}`, + assumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: 'events.amazonaws.com', + }, + Action: 'sts:AssumeRole', + }, + ], + }, + tags: [ + { + key: 'DoNotNuke', + value: 'True', + }, + { + key: 'owner', + value: props.owner!, + }, + ], + policies: [ + { + policyName: 'EventBridgeNukeStateMachineExecutionPolicy', + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: 'states:StartExecution', + Resource: nukeStepFunction.ref, + }, + ], + }, + }, + ], + }); + + const eventBridgeNukeSchedule = new events.CfnRule(this, 'EventBridgeNukeSchedule', { + name: `EventBridgeNukeSchedule-${this.stackName}`, + description: 'Scheduled Event for running AWS Nuke on the target accounts within the specified regions', + scheduleExpression: 'cron(0 7 ? * * *)', + state: 'ENABLED', + roleArn: eventBridgeNukeScheduleRole.attrArn, + targets: [ + { + arn: nukeStepFunction.ref, + roleArn: eventBridgeNukeScheduleRole.attrArn, + id: nukeStepFunction.attrName, + input: `{ + "InputPayLoad": { + "nuke_dry_run": "${props.awsNukeDryRunFlag!}", + "nuke_version": "${props.awsNukeVersion!}", + "region_list": [ + "us-west-1", + "us-east-1" + ] + } + }`, + }, + ], + }); + + // Outputs + this.nukeTopicArn = nukeEmailTopic.ref; + new cdk.CfnOutput(this, 'CfnOutputNukeTopicArn', { + key: 'NukeTopicArn', + description: 'Arn of SNS Topic used for notifying nuke results in email', + value: this.nukeTopicArn!.toString(), + }); + this.nukeS3BucketValue = nukeS3Bucket.ref; + new cdk.CfnOutput(this, 'CfnOutputNukeS3BucketValue', { + key: 'NukeS3BucketValue', + description: 'S3 bucket created with the random generated name', + value: this.nukeS3BucketValue!.toString(), + }); + } +} diff --git a/.tools/test/stacks/aws-nuke/migrate.json b/.tools/test/stacks/aws-nuke/migrate.json new file mode 100644 index 00000000000..fae30d4a543 --- /dev/null +++ b/.tools/test/stacks/aws-nuke/migrate.json @@ -0,0 +1,4 @@ +{ + "//": "This file is generated by cdk migrate. It will be automatically deleted after the first successful deployment of this app to the environment of the original resources.", + "Source": "NukeCleanser" +} \ No newline at end of file diff --git a/.tools/test/stacks/aws-nuke/nuke_config_update.py b/.tools/test/stacks/aws-nuke/nuke_config_update.py new file mode 100644 index 00000000000..c41b1c9eae6 --- /dev/null +++ b/.tools/test/stacks/aws-nuke/nuke_config_update.py @@ -0,0 +1,190 @@ +""" + Python class responsible for updating the nuke generic config , based on exceptions to be filtered + and also updates dynamically the region attribute passed in from the StepFunctions invocation. This should be modified to suit your needs. +""" +import boto3 +import yaml +import argparse +import copy + +GLOBAL_RESOURCE_EXCEPTIONS = [ + {"property": "tag:DoNotNuke", "value": "True"}, + {"property": "tag:Permanent", "value": "True"}, + {"type": "regex", "value": ".*auto-account-cleanser*.*"}, + {"type": "regex", "value": ".*nuke-account-cleanser*.*"}, + {"type": "regex", "value": ".*securityhub*.*"}, + {"type": "regex", "value": ".*aws-prod*.*"}, +] + + +class StackInfo: + + def __init__(self, account, target_regions): + self.session = boto3.Session(profile_name="nuke") + # Regions to be targeted set from the Stepfunctions/CodeBuild workflow + self.regions = target_regions + self.resources = {} + self.config = {} + self.account = account + + def Populate(self): + self.UpdateCFNStackList() + self.OverrideDefaultConfig() + + def UpdateCFNStackList(self): + try: + for region in self.regions: + cfn_client = self.session.client("cloudformation", region_name=region) + stack_paginator = cfn_client.get_paginator("list_stacks") + responses = stack_paginator.paginate( + StackStatusFilter=[ + "CREATE_COMPLETE", + "UPDATE_COMPLETE", + "UPDATE_ROLLBACK_COMPLETE", + "IMPORT_COMPLETE", + "IMPORT_ROLLBACK_COMPLETE", + ] + ) + for page in responses: + for stack in page.get("StackSummaries"): + self.GetCFNResources(stack, cfn_client) + self.BuildIamExclusionList(region) + except Exception as e: + print("Error in calling UpdateCFNStackList:\n {}".format(e)) + + def GetCFNResources(self, stack, cfn_client): + try: + stack_name = stack.get("StackName") + + if stack_name is None: + stack_name = stack.get("PhysicalResourceId") + + stack_description = cfn_client.describe_stacks(StackName=stack_name) + print("Stack Description: ", stack_description) + tags = stack_description.get("Stacks")[0].get("Tags") + for tag in tags: + key = tag.get("Key") + value = tag.get("Value") + if key == "AWS_Solutions" and value == "LandingZoneStackSet": + if "CloudFormationStack" in self.resources: + self.resources["CloudFormationStack"].append( + stack.get("StackName") + ) + else: + self.resources["CloudFormationStack"] = [stack.get("StackName")] + stack_resources = cfn_client.list_stack_resources( + StackName=stack_name + ) + for resource in stack_resources.get("StackResourceSummaries"): + if resource.get("ResourceType") == "AWS::CloudFormation::Stack": + self.GetCFNResources(resource, cfn_client) + else: + nuke_type = self.UpdateResourceName(resource["ResourceType"]) + if nuke_type in self.resources: + self.resources[nuke_type].append( + { + "type": "regex", + "value": resource["PhysicalResourceId"], + } + ) + else: + self.resources[nuke_type] = [ + { + "type": "regex", + "value": resource["PhysicalResourceId"], + } + ] + except Exception as e: + print("Error calling GetCFNResources:\n {}".format(e)) + + def UpdateResourceName(self, resource): + nuke_type = str.replace(resource, "AWS::", "") + nuke_type = str.replace(nuke_type, "::", "") + nuke_type = str.replace(nuke_type, "Config", "ConfigService", 1) + return nuke_type + + def BuildIamExclusionList(self, region): + # This excludes and appends to the config IAMRole resources , the roles that are federated principals + # You can add any other custom filterting logic based on regions for IAM/Global roles that should be excluded + iam_client = self.session.client("iam", region_name=region) + iam_paginator = iam_client.get_paginator("list_roles") + responses = iam_paginator.paginate() + for page in responses: + for role in page["Roles"]: + apd = role.get("AssumeRolePolicyDocument") + if apd is not None: + for item in apd.get("Statement"): + if item is not None: + for principal in item.get("Principal"): + if principal == "Federated": + if "IAMRole" in self.resources: + self.resources["IAMRole"].append( + role.get("RoleName") + ) + else: + self.resources["IAMRole"] = [ + role.get("RoleName") + ] + + def OverrideDefaultConfig(self): + # Open the nuke_generic_config.yaml and merge the captured resources/exclusions with it + try: + with open(r"nuke_generic_config.yaml") as config_file: + self.config = yaml.load(config_file) + # Not all resources handled by the tool, but we will add them to the exclusion anyhow. + for resource in self.resources: + if resource in self.config["accounts"]["ACCOUNT"]["filters"]: + self.config["accounts"]["ACCOUNT"]["filters"][resource].extend( + self.resources[resource] + ) + else: + self.config["accounts"]["ACCOUNT"]["filters"][ + resource + ] = self.resources[resource] + self.config["accounts"][self.account] = copy.deepcopy( + self.config["accounts"]["ACCOUNT"] + ) + if "ACCOUNT" in self.config["accounts"]: + self.config["accounts"].pop("ACCOUNT", None) + # Global exclusions apply to every type of resource + for resource in self.config["accounts"][self.account]["filters"]: + for exception in GLOBAL_RESOURCE_EXCEPTIONS: + self.config["accounts"][self.account]["filters"][resource].append( + exception.copy() + ) + config_file.close() + except Exception as e: + print("Failed merging nuke-config-test.yaml with error {}".format(e)) + exit(1) + + def WriteConfig(self): + # CodeBuild script updates the target region in the generic config and is validated here. + try: + for region in self.config["regions"]: + local_config = stackInfo.config.copy() + local_config["regions"] = [region] + filename = "nuke_config_{}.yaml".format(region) + with open(filename, "w") as output_file: + output = yaml.dump(local_config, output_file) + output_file.close() + except Exception as e: + print("Failed opening nuke_config.yaml for writing with error {}".format(e)) + + +try: + parser = argparse.ArgumentParser() + parser.add_argument("--account", dest="account", help="Account to nuke") # Account and Region from StepFunctions - CodeBuild overridden params + parser.add_argument("--region", dest="region", help="Region to target for nuke") + args = parser.parse_args() + if not args.account or not args.region: + parser.print_help() + exit(1) +except Exception as e: + print(e) + exit(1) + +if __name__ == "__main__": + print("Incoming Args: ", args) + stackInfo = StackInfo(args.account, [args.region]) + stackInfo.Populate() + stackInfo.WriteConfig() \ No newline at end of file diff --git a/.tools/test/stacks/aws-nuke/nuke_generic_config.yaml b/.tools/test/stacks/aws-nuke/nuke_generic_config.yaml new file mode 100644 index 00000000000..d8ed5e1e6d8 --- /dev/null +++ b/.tools/test/stacks/aws-nuke/nuke_generic_config.yaml @@ -0,0 +1,243 @@ +regions: + - TARGET_REGION # will be overridden during execution based on region parameter + +account-blocklist: + - 1234567890 + +resource-types: + excludes: + - ACMCertificate + - AWSBackupPlan + - AWSBackupRecoveryPoint + - AWSBackupSelection + - AWSBackupVault + - AWSBackupVaultAccessPolicy + - CloudTrailTrail + - CloudWatchEventsTarget + - CodeCommitRepository + - CodeStarProject + - ConfigServiceConfigRule + - ECRRepository + - EC2Address + - EC2ClientVpnEndpoint + - EC2ClientVpnEndpointAttachment + - EC2CustomerGateway + - EC2DHCPOption + - EC2DefaultSecurityGroupRule + - EC2EgressOnlyInternetGateway + - EC2InternetGateway + - EC2InternetGatewayAttachment + - EC2KeyPair + - EC2NetworkACL + - EC2NetworkInterface + - EC2RouteTable + - EC2SecurityGroup + - EC2Subnet + - EC2VPC + - EC2VPCEndpoint + - IAMGroup + - IAMGroupPolicy + - IAMGroupPolicyAttachment + - IAMInstanceProfile + - IAMInstanceProfileRole + - IAMLoginProfile + - IAMOpenIDConnectProvider + - IAMPolicy + - IAMRole + - IAMRolePolicy + - IAMRolePolicyAttachment + - IAMSAMLProvider + - IAMServerCertificate + - IAMServiceSpecificCredential + - IAMSigningCertificate + - IAMUser + - IAMUserAccessKey + - IAMUserGroupAttachment + - IAMUserPolicy + - IAMUserPolicyAttachment + - IAMUserSSHPublicKey + - IAMVirtualMFADevice + - KMSAlias + - KMSKey + - Route53HostedZone + - Route53ResourceRecordSet + - S3Bucket + - S3Object + - SecretsManagerSecret + - SQSQueue + - SSMParameter + +accounts: + ACCOUNT: + filters: + EC2VPC: + - property: IsDefault + value: "true" + EC2DHCPOption: + - property: DefaultVPC + value: "true" + EC2InternetGateway: + - property: DefaultVPC + value: "true" + EC2InternetGatewayAttachment: + - property: DefaultVPC + value: "true" + EC2Subnet: + - property: DefaultVPC + value: "true" + EC2RouteTable: + - property: DefaultVPC + value: "true" + EC2DefaultSecurityGroupRule: + - property: SecurityGroupId + type: glob + value: "*" + SQSQueue: + - property: "QueueURL" + type: "glob" + value: "*PluginStack-*" + SQSQueuePolicy: + - property: "name" + type: "glob" + value: "*PluginStack-*" + SNSSubscription: + - property: "name" + type: "glob" + value: "*PluginStack-*" + IAMRole: + - property: "name" + type: "glob" + value: "*PluginStack-*" + IAMPolicy: + - property: "name" + type: "glob" + value: "*PluginStack-*" + SecurityGroup: + - property: "group-name" + type: "glob" + value: "*PluginStack-*" + BatchComputeEnvironment: + - property: "name" + type: "glob" + value: "*PluginStack-*" + BatchJobDefinition: + - property: "name" + type: "glob" + value: "*PluginStack-*" + BatchJobQueue: + - property: "name" + type: "glob" + value: "*PluginStack-*" + LambdaFunction: + - property: "Name" + type: "glob" + value: "*PluginStack-*" + LambdaEventSourceMapping: + - property: "EventSourceArn" + type: "glob" + value: "*PluginStack-*" + - property: "FunctionArn" + type: "glob" + value: "*PluginStack-*" + S3Bucket: + - property: "name" + type: "glob" + value: "*PluginStack-*" + S3BucketPolicy: + - property: "bucket-name" + type: "glob" + value: "*PluginStack-*" + EventRule: + - property: "name" + type: "glob" + value: "*PluginStack-*" + - property: "arn" + type: "glob" + value: "*:events:us-east-1:*:rule/PluginStack-*" + LambdaPermission: + - property: "name" + type: "glob" + value: "*PluginStack-*" + CDKMetadata: + - property: "name" + type: "glob" + value: "*PluginStack-*" + CloudWatchEventsRule: + - property: "Name" + type: "glob" + value: "PluginStack-*" + CloudWatchEventsTarget: + - property: "Rule" + type: "glob" + value: "*PluginStack-*" + - property: "Target ID" + type: "glob" + value: "*PluginStack-*" + GuardDutyDetector: + - property: DetectorID + type: glob + value: "*" + CloudTrailTrail: + - type: regex + value: "^(AccountGuardian|Isengard).*DO-NOT-DELETE.*$" + CloudWatchEventsRule: + - type: regex + value: "^Rule: (AccountGuardian-.*DO-NOT-DELETE|AwsSecurity.*DO-NOT-DELETE|DO-NOT-DELETE-GatedGarden-.*)$" + CloudWatchEventsTarget: + - type: regex + value: "^Rule: (AccountGuardian-.*DO-NOT-DELETE.*|AwsSecurity.*DO-NOT-DELETE|DO-NOT-DELETE-GatedGarden-.*)$" + CloudWatchLogsLogGroup: + - type: regex + value: "^(AccountGuardian-).*$" + ConfigServiceDeliveryChannel: + - "pitbull-default" + ConfigServiceConfigRule: + - type: regex + value: "^(managed-ec2-patch-compliance|ec2-managed-by-systems-manager-REMEDIATE|pvre-.*-REMEDIATE|.*-pvre-.*-REMEDIATE)$" + S3Bucket: + - property: Name + type: regex + value: "^(cdktoolkit-stagingbucket-.*|pitbull-aws-config-.*|cloudtrail-awslogs-.*-isengard-do-not-delete|do-not-delete-gatedgarden-audit-.*)$" + S3Object: + - property: Bucket + type: regex + value: "^(cdktoolkit-stagingbucket-.*|pitbull-aws-config-.*|cloudtrail-awslogs-.*-isengard-do-not-delete|do-not-delete-gatedgarden-audit-.*)$" + ConfigServiceConfigurationRecorder: + - "MainRecorder" + CloudFormationStack: + - property: Name + type: regex + value: "^(CDKToolkit|AccountGuardian|.*DO-NOT-DELETE)$" + - property: Name + type: regex + value: "^(PluginStack).*$" + - property: Name + type: regex + value: "^(pvre.*|PVRE.*)$" + - property: Name + type: regex + value: "^(.*PatchBaseline.*)$" + IAMPolicy: + - property: Name + type: regex + value: "^(ConfigAccessPolicy|ResourceConfigurationCollectorPolicy|CloudFormationRefereeService|EC2CapacityReservationService|AwsSecurit.*AuditPolicy)$" + IAMRole: + - property: Name + type: regex + value: "^(AWSServiceRoleFor.*|.*DO-NOT-DELETE|^Isengard.*|Admin|ReadOnly|GatedGarden.*Audit|ShadowTrooper.*|InternalAuditInternal|EC2CapacityReservationService|AccessAnalyzerTrustedService|EC2CapacityReservationService|AwsSecurit.*Audit|AWS.*Audit)$" + IAMRolePolicy: + - property: role:RoleName + type: regex + value: "^(.*DO-NOT-DELETE|Isengard.*|GatedGarden.*Audit|AccountGuardian.*|ShadowTrooper.*|AccessAnalyzerTrustedService|AwsSecurit.*Audit)$" + IAMRolePolicyAttachment: + - property: RoleName + type: regex + value: "^(Admin|ReadOnly|AWSServiceRoleFor.*|.*DO-NOT-DELETE|Isengard.*|InternalAuditInternal|EC2CapacityReservationService|AWSVAPTAudit|AwsSecurit.*Audit)$" + SSMDocument: + - type: regex + value: "^(AccountGuardian|Isengard).*DO-NOT-DELETE.*$" + SSMResourceDataSync: + - type: regex + value: "^(AccountGuardian|Isengard).*DO-NOT-DELETE.*$" + + diff --git a/.tools/test/stacks/aws-nuke/package-lock.json b/.tools/test/stacks/aws-nuke/package-lock.json new file mode 100644 index 00000000000..027197ce78b --- /dev/null +++ b/.tools/test/stacks/aws-nuke/package-lock.json @@ -0,0 +1,4434 @@ +{ + "name": "nuke_cleanser", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nuke_cleanser", + "version": "0.1.0", + "dependencies": { + "aws-cdk-lib": "^2.164.1", + "constructs": "^10.4.2", + "source-map-support": "^0.5.21" + }, + "bin": { + "nuke_cleanser": "bin/nuke_cleanser.js" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/node": "22.5.4", + "aws-cdk": "2.164.1", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.212", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.212.tgz", + "integrity": "sha512-7WqbnWUkBBcAzEdfRrpz6sCOheUPf4JEUdGvzJ4EEufXeT7v7nRbRmTvUBbQ+OQlCv9UrVj9XuFxKPjkvneGMQ==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.3.tgz", + "integrity": "sha512-cDG1w3ieM6eOT9mTefRuTypk95+oyD7P5X/wRltwmYxU7nZc3+076YEVS6vrjDKr3ADYbfn0lDKpfB1FBtO9CQ==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "38.0.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-38.0.1.tgz", + "integrity": "sha512-KvPe+NMWAulfNVwY7jenFhzhuLhLqJ/OPy5jx7wUstbjnYnjRVLpUHPU3yCjXFE0J8cuJVdx95BJ4rOs66Pi9w==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "^1.4.1", + "semver": "^7.6.3" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-cdk": { + "version": "2.164.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.164.1.tgz", + "integrity": "sha512-dWRViQgHLe7GHkPIQGA+8EQSm8TBcxemyCC3HHW3wbLMWUDbspio9Dktmw5EmWxlFjjWh86Dk1JWf1zKQo8C5g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.170.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.170.0.tgz", + "integrity": "sha512-hlfoOJUZmAY3TjOXjWhAYKlrPcfGNTXA24NirwkEYOX+t1HD8OLSrYZvluMc7nWgIZf1Mq1g6M0xNEZJqykPrA==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.208", + "@aws-cdk/asset-kubectl-v20": "^2.1.3", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^38.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.2", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001683", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz", + "integrity": "sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", + "license": "Apache-2.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.64", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", + "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/.tools/test/stacks/aws-nuke/package.json b/.tools/test/stacks/aws-nuke/package.json new file mode 100644 index 00000000000..4a43665015b --- /dev/null +++ b/.tools/test/stacks/aws-nuke/package.json @@ -0,0 +1,27 @@ +{ + "name": "nuke_cleanser", + "version": "0.1.0", + "bin": { + "nuke_cleanser": "bin/nuke_cleanser.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/node": "22.5.4", + "aws-cdk": "2.164.1", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "~5.6.2" + }, + "dependencies": { + "aws-cdk-lib": "^2.164.1", + "constructs": "^10.4.2", + "source-map-support": "^0.5.21" + } +} diff --git a/.tools/test/stacks/aws-nuke/tsconfig.json b/.tools/test/stacks/aws-nuke/tsconfig.json new file mode 100644 index 00000000000..aaa7dc510f1 --- /dev/null +++ b/.tools/test/stacks/aws-nuke/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} From ef540cdf2ec43d7cbe131e807258a29ed1af5b2c Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 27 Nov 2024 12:00:30 -0500 Subject: [PATCH 04/26] lib/bin --- .tools/test/stacks/aws-nuke/README.md | 2 +- .../aws-nuke/lib/nuke_cleanser-stack.ts | 87 +------------------ 2 files changed, 3 insertions(+), 86 deletions(-) diff --git a/.tools/test/stacks/aws-nuke/README.md b/.tools/test/stacks/aws-nuke/README.md index a96158f761a..b59ab8528f0 100644 --- a/.tools/test/stacks/aws-nuke/README.md +++ b/.tools/test/stacks/aws-nuke/README.md @@ -72,7 +72,7 @@ aws s3 cp nuke_config_update.py --region us-east-1 s3://{your-bucket-name} * The workflow also sends out a detailed report to an SNS topic with an active email subscription on what resources were deleted after the job is successful for each region which simplifies traversing and parsing the complex logs spit out by the aws-nuke binary. -* If the workflow is successful , the stack will send out +* If the workflow is successful, the stack will send out - One email for each of the regions where nuke CodeBuild job was invoked with details of the build execution , the list of resources which was deleted along with the log file path. - The StepFunctions workflow also sends out another email when the whole Map state process completes successfully. Sample email template given below. diff --git a/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts b/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts index 3d6961962b0..b0a3803d699 100644 --- a/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts +++ b/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts @@ -32,11 +32,6 @@ export interface NukeCleanserStackProps extends cdk.StackProps { * @default '2.21.2' */ readonly awsNukeVersion?: string; - /** - * The SNS Topic name to publish the nuke output logs email - * @default 'nuke-cleanser-notify-topic' - */ - readonly nukeTopicName?: string; /** * The Owner of the account to be used for tagging purpose * @default 'OpsAdmin' @@ -49,10 +44,6 @@ export interface NukeCleanserStackProps extends cdk.StackProps { */ export class NukeCleanserStack extends cdk.Stack { - /** - * Arn of SNS Topic used for notifying nuke results in email - */ - public readonly nukeTopicArn; /** * S3 bucket created with the random generated name */ @@ -69,7 +60,6 @@ export class NukeCleanserStack extends cdk.Stack { iamPath: props.iamPath ?? '/', awsNukeDryRunFlag: props.awsNukeDryRunFlag ?? 'true', awsNukeVersion: props.awsNukeVersion ?? '2.21.2', - nukeTopicName: props.nukeTopicName ?? 'nuke-cleanser-notify-topic', owner: props.owner ?? 'OpsAdmin', }; @@ -129,29 +119,6 @@ export class NukeCleanserStack extends cdk.Stack { path: props.iamPath!, }); - const nukeEmailTopic = new sns.CfnTopic(this, 'NukeEmailTopic', { - displayName: 'NukeTopic', - fifoTopic: false, - kmsMasterKeyId: 'alias/aws/sns', - subscription: [ - { - endpoint: 'test@test.com', - protocol: 'email', - }, - ], - topicName: props.nukeTopicName!, - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - }); - const nukeS3Bucket = new s3.CfnBucket(this, 'NukeS3Bucket', { bucketName: [ props.bucketName!, @@ -270,26 +237,6 @@ export class NukeCleanserStack extends cdk.Stack { ], }, }, - { - policyName: 'SNSPublishPolicy', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: [ - 'sns:ListTagsForResource', - 'sns:ListSubscriptionsByTopic', - 'sns:GetTopicAttributes', - 'sns:Publish', - ], - Resource: [ - nukeEmailTopic.ref, - ], - }, - ], - }, - }, ], }); @@ -319,7 +266,7 @@ export class NukeCleanserStack extends cdk.Stack { const nukeAccountCleanserRole = new iam.CfnRole(this, 'NukeAccountCleanserRole', { roleName: props.nukeCleanserRoleName!, - description: 'Nuke Auto account cleanser role for Dev/Sandbox accounts', + description: 'Nuke Auto account cleanser role for target accounts', maxSessionDuration: 7200, tags: [ { @@ -392,11 +339,6 @@ export class NukeCleanserStack extends cdk.Stack { type: 'PLAINTEXT', value: props.awsNukeVersion!, }, - { - name: 'Publish_TopicArn', - type: 'PLAINTEXT', - value: nukeEmailTopic.ref, - }, { name: 'NukeS3Bucket', type: 'PLAINTEXT', @@ -424,7 +366,7 @@ export class NukeCleanserStack extends cdk.Stack { serviceRole: nukeCodeBuildProjectRole.attrArn, timeoutInMinutes: 120, source: { - buildSpec: 'version: 0.2\nphases:\n install:\n on-failure: ABORT\n commands:\n - export AWS_NUKE_VERSION=$AWS_NukeVersion\n - apt-get install -y wget\n - apt-get install jq\n - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate\n - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz\n - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64\n - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke\n - aws-nuke version\n - echo \"Setting aws cli profile with config file for role assumption using metadata\"\n - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}\n - aws configure set profile.nuke.credential_source \"EcsContainer\"\n - export AWS_PROFILE=nuke\n - export AWS_DEFAULT_PROFILE=nuke\n - export AWS_SDK_LOAD_CONFIG=1\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n build:\n on-failure: CONTINUE\n commands:\n - echo \" ------------------------------------------------ \" >> error_log.txt\n - echo \"Getting nuke generic config file from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .\n - echo \"Updating the TARGET_REGION in the generic config from the parameter\"\n - sed -i \"s/TARGET_REGION/$NukeTargetRegion/g\" nuke_generic_config.yaml\n - echo \"Getting filter/exclusion python script from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n - echo \"Running Config filter/update script\";\n - python3 nuke_config_update.py --account $account_id --region \"$NukeTargetRegion\";\n - echo \"Configured nuke_config.yaml\";\n - echo \"Running Nuke on Account\";\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done\n elif [ \"$AWS_NukeDryRun\" = \"false\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done\n else\n echo \"Couldn\'t determine Dryrun flag...exiting\"\n exit 1\n fi\n - nuke_pid=$!;\n - wait $nuke_pid;\n - echo \"Checking if Nuke Process completed for account\"\n - |\n if cat aws-nuke.log | grep -F \"Error: The specified account doesn\"; then\n echo \"Nuke errored due to no AWS account alias set up - exiting\"\n cat aws-nuke.log >> error_log.txt\n exit 1\n else\n echo \"Nuke completed Successfully - Continuing\"\n fi\n\n post_build:\n commands:\n - echo $CODEBUILD_BUILD_SUCCEEDING\n - echo \"Get current timestamp for naming reports\"\n - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))\n - CURR_TIME_UTC=$(date -u)\n - |\n {\n echo \" Account Cleansing Process Failed;\"\n echo \"\"\n \n echo \" ----------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ----------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ----------------------------------------------------------------\"\n echo \" ################# Failed Nuke Process - Exiting ###################\"\n echo \"\"\n } >> fail_email_template.txt\n - | \n if [ \"$CODEBUILD_BUILD_SUCCEEDING\" = \"0\" ]; then \n echo \" Couldn\'t process Nuke Cleanser - Exiting \" >> fail_email_template.txt\n cat error_log.txt >> fail_email_template.txt\n aws sns publish --topic-arn $Publish_TopicArn --message file://fail_email_template.txt --subject \"Nuke Account Cleanser Failed in account $account_id and region $NukeTargetRegion\"\n exit 1;\n fi\n - sleep 120\n - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;\n - CURR_TIME_UTC=$(date -u)\n - | \n if [ -z \"${LOG_STREAM_NAME}\" ]; then\n echo \"Couldn\'t find the log stream for log events\";\n exit 0;\n else\n aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern \"removed\" --no-interleaved | jq -r .events[].message > log_output.txt;\n awk \'/There are resources in failed state/,/Error: failed/\' aws-nuke.log > failure_email_output.txt\n awk \'/Error: failed/,/\\n/\' failure_email_output.txt > failed_log_output.txt\n fi\n - |\n if [ -r log_output.txt ]; then\n content=$(cat log_output.txt)\n echo $content\n elif [ -f \"log_output.txt\" ]; then\n echo \"The file log_output.txt exists but is not readable to the script.\"\n else\n echo \"The file log_output.txt does not exist.\"\n fi\n - echo \"Publishing Log Ouput to SNS:\"\n - sub=\"Nuke Account Cleanser Succeeded in account \"$account_id\" and region \"$NukeTargetRegion\"\"\n - |\n {\n echo \" Account Cleansing Process Completed;\"\n echo \"\"\n \n echo \" ------------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ------------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ------------------------------------------------------------------\"\n echo \" ################### Nuke Cleanser Logs ####################\"\n echo \"\"\n } >> email_template.txt\n\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"No Resources scanned and nukeable yet\"\n - echo \"Number of Resources that is filtered by config:\" >> email_template.txt\n - cat aws-nuke.log | grep -c \" - filtered by config\" || echo 0 >> email_template.txt\n - echo \" ------------------------------------------ \" >> email_template.txt\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n echo \"RESOURCES THAT WOULD BE REMOVED:\" >> email_template.txt\n echo \" ----------------------------------------- \" >> email_template.txt\n cat aws-nuke.log | grep -c \" - would remove\" || echo 0 >> email_template.txt\n cat aws-nuke.log | grep -F \" - would remove\" >> email_template.txt || echo \"No resources to be removed\" >> email_template.txt\n else\n echo \" FAILED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat failed_log_output.txt >> email_template.txt\n echo \" SUCCESSFULLY NUKED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat log_output.txt >> email_template.txt\n fi\n - aws sns publish --topic-arn $Publish_TopicArn --message file://email_template.txt --subject \"$sub\"\n - echo \"Resources Nukeable:\"\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"Nothing Nukeable yet\"\n - echo \"Total number of Resources that would be removed:\"\n - cat aws-nuke.log | grep -c \" - would remove\" || echo \"Nothing would be removed yet\"\n - echo \"Total number of Resources Deleted:\"\n - cat aws-nuke.log | grep -c \" - removed\" || echo \"Nothing deleted yet\"\n - echo \"List of Resources Deleted today:\"\n - cat aws-nuke.log | grep -F \" - removed\" || echo \"Nothing deleted yet\"\n', + buildSpec: 'version: 0.2\nphases:\n install:\n on-failure: ABORT\n commands:\n - export AWS_NUKE_VERSION=$AWS_NukeVersion\n - apt-get install -y wget\n - apt-get install jq\n - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate\n - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz\n - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64\n - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke\n - aws-nuke version\n - echo \"Setting aws cli profile with config file for role assumption using metadata\"\n - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}\n - aws configure set profile.nuke.credential_source \"EcsContainer\"\n - export AWS_PROFILE=nuke\n - export AWS_DEFAULT_PROFILE=nuke\n - export AWS_SDK_LOAD_CONFIG=1\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n build:\n on-failure: CONTINUE\n commands:\n - echo \" ------------------------------------------------ \" >> error_log.txt\n - echo \"Getting nuke generic config file from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .\n - echo \"Updating the TARGET_REGION in the generic config from the parameter\"\n - sed -i \"s/TARGET_REGION/$NukeTargetRegion/g\" nuke_generic_config.yaml\n - echo \"Getting filter/exclusion python script from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n - echo \"Running Config filter/update script\";\n - python3 nuke_config_update.py --account $account_id --region \"$NukeTargetRegion\";\n - echo \"Configured nuke_config.yaml\";\n - echo \"Running Nuke on Account\";\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done\n elif [ \"$AWS_NukeDryRun\" = \"false\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done\n else\n echo \"Couldn\'t determine Dryrun flag...exiting\"\n exit 1\n fi\n - nuke_pid=$!;\n - wait $nuke_pid;\n - echo \"Checking if Nuke Process completed for account\"\n - |\n if cat aws-nuke.log | grep -F \"Error: The specified account doesn\"; then\n echo \"Nuke errored due to no AWS account alias set up - exiting\"\n cat aws-nuke.log >> error_log.txt\n exit 1\n else\n echo \"Nuke completed Successfully - Continuing\"\n fi\n\n post_build:\n commands:\n - echo $CODEBUILD_BUILD_SUCCEEDING\n - echo \"Get current timestamp for naming reports\"\n - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))\n - CURR_TIME_UTC=$(date -u)\n - |\n {\n echo \" Account Cleansing Process Failed;\"\n echo \"\"\n \n echo \" ----------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ----------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ----------------------------------------------------------------\"\n echo \" ################# Failed Nuke Process - Exiting ###################\"\n echo \"\"\n } >> fail_email_template.txt\n - | \n if [ \"$CODEBUILD_BUILD_SUCCEEDING\" = \"0\" ]; then \n echo \" Couldn\'t process Nuke Cleanser - Exiting \" >> fail_email_template.txt\n cat error_log.txt >> fail_email_template.txt\n exit 1;\n fi\n - sleep 120\n - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;\n - CURR_TIME_UTC=$(date -u)\n - | \n if [ -z \"${LOG_STREAM_NAME}\" ]; then\n echo \"Couldn\'t find the log stream for log events\";\n exit 0;\n else\n aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern \"removed\" --no-interleaved | jq -r .events[].message > log_output.txt;\n awk \'/There are resources in failed state/,/Error: failed/\' aws-nuke.log > failure_email_output.txt\n awk \'/Error: failed/,/\\n/\' failure_email_output.txt > failed_log_output.txt\n fi\n - |\n if [ -r log_output.txt ]; then\n content=$(cat log_output.txt)\n echo $content\n elif [ -f \"log_output.txt\" ]; then\n echo \"The file log_output.txt exists but is not readable to the script.\"\n else\n echo \"The file log_output.txt does not exist.\"\n fi\n - echo \"Publishing Log Ouput to SNS:\"\n - sub=\"Nuke Account Cleanser Succeeded in account \"$account_id\" and region \"$NukeTargetRegion\"\"\n - |\n {\n echo \" Account Cleansing Process Completed;\"\n echo \"\"\n \n echo \" ------------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ------------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ------------------------------------------------------------------\"\n echo \" ################### Nuke Cleanser Logs ####################\"\n echo \"\"\n } >> email_template.txt\n\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"No Resources scanned and nukeable yet\"\n - echo \"Number of Resources that is filtered by config:\" >> email_template.txt\n - cat aws-nuke.log | grep -c \" - filtered by config\" || echo 0 >> email_template.txt\n - echo \" ------------------------------------------ \" >> email_template.txt\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n echo \"RESOURCES THAT WOULD BE REMOVED:\" >> email_template.txt\n echo \" ----------------------------------------- \" >> email_template.txt\n cat aws-nuke.log | grep -c \" - would remove\" || echo 0 >> email_template.txt\n cat aws-nuke.log | grep -F \" - would remove\" >> email_template.txt || echo \"No resources to be removed\" >> email_template.txt\n else\n echo \" FAILED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat failed_log_output.txt >> email_template.txt\n echo \" SUCCESSFULLY NUKED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat log_output.txt >> email_template.txt\n fi\n - echo \"Resources Nukeable:\"\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"Nothing Nukeable yet\"\n - echo \"Total number of Resources that would be removed:\"\n - cat aws-nuke.log | grep -c \" - would remove\" || echo \"Nothing would be removed yet\"\n - echo \"Total number of Resources Deleted:\"\n - cat aws-nuke.log | grep -c \" - removed\" || echo \"Nothing deleted yet\"\n - echo \"List of Resources Deleted today:\"\n - cat aws-nuke.log | grep -F \" - removed\" || echo \"Nothing deleted yet\"\n', type: 'NO_SOURCE', }, }); @@ -493,15 +435,6 @@ export class NukeCleanserStack extends cdk.Stack { ], Resource: `arn:aws:events:${this.region}:${this.account}:rule/StepFunctionsGetEventForCodeBuildStartBuildRule`, }, - { - Effect: 'Allow', - Action: [ - 'sns:Publish', - ], - Resource: [ - nukeEmailTopic.ref, - ], - }, { Effect: 'Allow', Action: [ @@ -631,16 +564,6 @@ export class NukeCleanserStack extends cdk.Stack { "filteredResult.$": "$..result" }, "ResultPath": "$.NukeFinalMapAllRegionsOutput" - }, - "Clean Output and Notify": { - "Type": "Task", - "Resource": "arn:aws:states:::sns:publish", - "Parameters": { - "Subject": "State Machine for Nuke Account Cleanser completed", - "Message.$": "States.Format('Nuke Account Cleanser completed for input payload: {}. ----------------------------------------- Check the summmary of execution below: {}', $.InputPayLoad, $.NukeFinalMapAllRegionsOutput.filteredResult)", - "TopicArn": "${nukeEmailTopic.ref}" - }, - "End": true } } }`, @@ -723,12 +646,6 @@ export class NukeCleanserStack extends cdk.Stack { }); // Outputs - this.nukeTopicArn = nukeEmailTopic.ref; - new cdk.CfnOutput(this, 'CfnOutputNukeTopicArn', { - key: 'NukeTopicArn', - description: 'Arn of SNS Topic used for notifying nuke results in email', - value: this.nukeTopicArn!.toString(), - }); this.nukeS3BucketValue = nukeS3Bucket.ref; new cdk.CfnOutput(this, 'CfnOutputNukeS3BucketValue', { key: 'NukeS3BucketValue', From 2f03638e26acc00047917264381a247cf54631b7 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 27 Nov 2024 14:38:09 -0500 Subject: [PATCH 05/26] getting aws-nuke finally off the ground. be careful out there --- .../test/stacks/aws-nuke/bin/nuke_cleanser.ts | 15 - .tools/test/stacks/aws-nuke/cdk.json | 2 +- .tools/test/stacks/aws-nuke/jest.config.js | 8 - .tools/test/stacks/aws-nuke/migrate.json | 4 - ...uke_cleanser-stack.ts => nuke_cleanser.ts} | 23 +- .tools/test/stacks/aws-nuke/package-lock.json | 4382 +++-------------- .tools/test/stacks/aws-nuke/package.json | 2 +- 7 files changed, 591 insertions(+), 3845 deletions(-) delete mode 100644 .tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts delete mode 100644 .tools/test/stacks/aws-nuke/jest.config.js delete mode 100644 .tools/test/stacks/aws-nuke/migrate.json rename .tools/test/stacks/aws-nuke/{lib/nuke_cleanser-stack.ts => nuke_cleanser.ts} (97%) diff --git a/.tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts b/.tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts deleted file mode 100644 index 4f580113651..00000000000 --- a/.tools/test/stacks/aws-nuke/bin/nuke_cleanser.ts +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node -import 'source-map-support/register'; -import * as cdk from 'aws-cdk-lib'; -import { NukeCleanserStack } from '../lib/nuke_cleanser-stack'; - -const app = new cdk.App(); - -new NukeCleanserStack(app, 'NukeCleanser', { - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION, - }, -}); - -app.synth(); \ No newline at end of file diff --git a/.tools/test/stacks/aws-nuke/cdk.json b/.tools/test/stacks/aws-nuke/cdk.json index 79baba4607c..fc7b7f2a784 100644 --- a/.tools/test/stacks/aws-nuke/cdk.json +++ b/.tools/test/stacks/aws-nuke/cdk.json @@ -1,5 +1,5 @@ { - "app": "npx ts-node --prefer-ts-exts bin/nuke_cleanser.ts", + "app": "npx ts-node --prefer-ts-exts nuke_cleanser.ts", "watch": { "include": [ "**" diff --git a/.tools/test/stacks/aws-nuke/jest.config.js b/.tools/test/stacks/aws-nuke/jest.config.js deleted file mode 100644 index 08263b8954a..00000000000 --- a/.tools/test/stacks/aws-nuke/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/test'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest' - } -}; diff --git a/.tools/test/stacks/aws-nuke/migrate.json b/.tools/test/stacks/aws-nuke/migrate.json deleted file mode 100644 index fae30d4a543..00000000000 --- a/.tools/test/stacks/aws-nuke/migrate.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "//": "This file is generated by cdk migrate. It will be automatically deleted after the first successful deployment of this app to the environment of the original resources.", - "Source": "NukeCleanser" -} \ No newline at end of file diff --git a/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts b/.tools/test/stacks/aws-nuke/nuke_cleanser.ts similarity index 97% rename from .tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts rename to .tools/test/stacks/aws-nuke/nuke_cleanser.ts index b0a3803d699..ab81e1f4583 100644 --- a/.tools/test/stacks/aws-nuke/lib/nuke_cleanser-stack.ts +++ b/.tools/test/stacks/aws-nuke/nuke_cleanser.ts @@ -1,9 +1,9 @@ +#!/usr/bin/env node import * as cdk from 'aws-cdk-lib'; import * as codebuild from 'aws-cdk-lib/aws-codebuild'; import * as events from 'aws-cdk-lib/aws-events'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; -import * as sns from 'aws-cdk-lib/aws-sns'; import * as stepfunctions from 'aws-cdk-lib/aws-stepfunctions'; export interface NukeCleanserStackProps extends cdk.StackProps { @@ -39,17 +39,13 @@ export interface NukeCleanserStackProps extends cdk.StackProps { readonly owner?: string; } -/** - * This CFN Template creates a StepFunction state machine definition , to invoke a CodeBuild Project and associated resources for setting up a single-account script that cleanses the resources across supplied regions via AWS-Nuke. It also creates the role for the nuke cleanser which is used for configuring the credentials for aws-nuke binary to cleanse resources within that account/region. - - */ -export class NukeCleanserStack extends cdk.Stack { +class NukeCleanserStack extends cdk.Stack { /** * S3 bucket created with the random generated name */ public readonly nukeS3BucketValue; - public constructor(scope: cdk.App, id: string, props: NukeCleanserStackProps = {}) { + constructor(scope: cdk.App, id: string, props: NukeCleanserStackProps = {}) { super(scope, id, props); // Applying default props @@ -266,7 +262,7 @@ export class NukeCleanserStack extends cdk.Stack { const nukeAccountCleanserRole = new iam.CfnRole(this, 'NukeAccountCleanserRole', { roleName: props.nukeCleanserRoleName!, - description: 'Nuke Auto account cleanser role for target accounts', + description: 'Nuke Auto account cleanser role for Dev/Sandbox accounts', maxSessionDuration: 7200, tags: [ { @@ -462,6 +458,7 @@ export class NukeCleanserStack extends cdk.Stack { "StartAt": "StartNukeCodeBuildForEachRegion", "States": { "StartNukeCodeBuildForEachRegion": { + "End": true, "Type": "Map", "ItemsPath": "$.InputPayLoad.region_list", "Parameters": { @@ -469,7 +466,6 @@ export class NukeCleanserStack extends cdk.Stack { "nuke_dry_run.$": "$.InputPayLoad.nuke_dry_run", "nuke_version.$": "$.InputPayLoad.nuke_version" }, - "Next": "Clean Output and Notify", "MaxConcurrency": 0, "Iterator": { "StartAt": "Trigger Nuke CodeBuild Job", @@ -654,3 +650,12 @@ export class NukeCleanserStack extends cdk.Stack { }); } } + +const app = new cdk.App(); +new NukeCleanserStack(app, 'NukeCleanser', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); +app.synth(); diff --git a/.tools/test/stacks/aws-nuke/package-lock.json b/.tools/test/stacks/aws-nuke/package-lock.json index 027197ce78b..7838a659b57 100644 --- a/.tools/test/stacks/aws-nuke/package-lock.json +++ b/.tools/test/stacks/aws-nuke/package-lock.json @@ -13,32 +13,16 @@ "source-map-support": "^0.5.21" }, "bin": { - "nuke_cleanser": "bin/nuke_cleanser.js" + "nuke_cleanser": "nuke_cleanser.js" }, "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "22.5.4", "aws-cdk": "2.164.1", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "~5.6.2" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@aws-cdk/asset-awscli-v1": { "version": "2.2.212", "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.212.tgz", @@ -105,3806 +89,962 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=12" } }, - "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "jest-get-type": "^29.6.3" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=6.9.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" - }, - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.26.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/istanbul-lib-report": "*" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "undici-types": "~6.19.2" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/yargs-parser": "*" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "bin": { + "acorn": "bin/acorn" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "acorn": "^8.11.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "color-convert": "^2.0.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "engines": { + "node": ">=8" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/aws-cdk": { + "version": "2.164.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.164.1.tgz", + "integrity": "sha512-dWRViQgHLe7GHkPIQGA+8EQSm8TBcxemyCC3HHW3wbLMWUDbspio9Dktmw5EmWxlFjjWh86Dk1JWf1zKQo8C5g==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "license": "Apache-2.0", + "bin": { + "cdk": "bin/cdk" }, "engines": { - "node": ">=6.9.0" + "node": ">= 14.15.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "optionalDependencies": { + "fsevents": "2.3.2" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", + "node_modules/aws-cdk-lib": { + "version": "2.170.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.170.0.tgz", + "integrity": "sha512-hlfoOJUZmAY3TjOXjWhAYKlrPcfGNTXA24NirwkEYOX+t1HD8OLSrYZvluMc7nWgIZf1Mq1g6M0xNEZJqykPrA==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@aws-cdk/asset-awscli-v1": "^2.2.208", + "@aws-cdk/asset-kubectl-v20": "^2.1.3", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^38.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" }, "engines": { - "node": ">=6.9.0" + "node": ">= 14.15.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "constructs": "^10.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, "license": "MIT" }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.2.0", + "inBundle": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=14.14" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 4" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "universalify": "^2.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/aws-cdk": { - "version": "2.164.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.164.1.tgz", - "integrity": "sha512-dWRViQgHLe7GHkPIQGA+8EQSm8TBcxemyCC3HHW3wbLMWUDbspio9Dktmw5EmWxlFjjWh86Dk1JWf1zKQo8C5g==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 14.15.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.170.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.170.0.tgz", - "integrity": "sha512-hlfoOJUZmAY3TjOXjWhAYKlrPcfGNTXA24NirwkEYOX+t1HD8OLSrYZvluMc7nWgIZf1Mq1g6M0xNEZJqykPrA==", - "bundleDependencies": [ - "@balena/dockerignore", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "table", - "yaml", - "mime-types" - ], - "license": "Apache-2.0", - "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.208", - "@aws-cdk/asset-kubectl-v20": "^2.1.3", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^38.0.1", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.2", - "jsonschema": "^1.4.1", - "mime-types": "^2.1.35", - "minimatch": "^3.1.2", - "punycode": "^2.3.1", - "semver": "^7.6.3", - "table": "^6.8.2", - "yaml": "1.10.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "constructs": "^10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.17.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/astral-regex": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/aws-cdk-lib/node_modules/case": { - "version": "1.6.3", - "inBundle": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { - "version": "3.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.3", - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/jsonfile": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { - "version": "4.4.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/mime-db": { - "version": "1.52.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-types": { - "version": "2.1.35", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-cdk-lib/node_modules/require-from-string": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.6.3", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/slice-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.2", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yaml": { - "version": "1.10.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001683", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz", - "integrity": "sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/constructs": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", - "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", - "license": "Apache-2.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.64", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", - "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "mime-db": "1.52.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=8" } }, - "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.2", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6" + "node": ">=10.0.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 10.0.0" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, "engines": { - "node": ">=8.6" + "node": ">=8" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "color-name": "~1.1.4" }, "engines": { - "node": "*" + "node": ">=7.0.0" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", + "license": "Apache-2.0" }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=0.3.1" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "ISC" }, - "node_modules/path-exists": { + "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.12.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8.6" + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format": { + "node_modules/jest-message-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, "license": "MIT" }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "ISC" }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "resolve-from": "^5.0.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=8.6" } }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, @@ -3937,13 +1077,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -3957,81 +1090,6 @@ "node": ">=10" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4045,41 +1103,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4093,68 +1116,6 @@ "node": ">=8.0" } }, - "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.6.3", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -4199,29 +1160,6 @@ } } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -4243,37 +1181,6 @@ "dev": true, "license": "MIT" }, - "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -4281,132 +1188,6 @@ "dev": true, "license": "MIT" }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -4416,19 +1197,6 @@ "engines": { "node": ">=6" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/.tools/test/stacks/aws-nuke/package.json b/.tools/test/stacks/aws-nuke/package.json index 4a43665015b..f7f74fb1d35 100644 --- a/.tools/test/stacks/aws-nuke/package.json +++ b/.tools/test/stacks/aws-nuke/package.json @@ -2,7 +2,7 @@ "name": "nuke_cleanser", "version": "0.1.0", "bin": { - "nuke_cleanser": "bin/nuke_cleanser.js" + "nuke_cleanser": "nuke_cleanser.ts" }, "scripts": { "build": "tsc", From df507f6f0d01997c03ac56837d3cf6378e2a2b0f Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 27 Nov 2024 17:12:26 -0500 Subject: [PATCH 06/26] fixes to integrate script --- .tools/test/stacks/deploy.py | 18 ++- .../{aws-nuke => nuke/typescript}/README.md | 23 +-- .../typescript}/architecture-overview.png | Bin .../{aws-nuke => nuke/typescript}/cdk.json | 0 .../typescript}/nuke_cleanser.ts | 0 .../typescript}/nuke_config_update.py | 0 .../typescript}/nuke_generic_config.yaml | 0 .../typescript}/package-lock.json | 0 .../typescript}/package.json | 0 .../stacks/nuke/typescript/trigger_dry_run.py | 76 ++++++++++ .../typescript}/tsconfig.json | 0 .../nuke/typescript/upload_job_scripts.py | 134 ++++++++++++++++++ .../stacks/plugin/typescript/plugin_stack.ts | 10 +- 13 files changed, 245 insertions(+), 16 deletions(-) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/README.md (85%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/architecture-overview.png (100%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/cdk.json (100%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/nuke_cleanser.ts (100%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/nuke_config_update.py (100%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/nuke_generic_config.yaml (100%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/package-lock.json (100%) rename .tools/test/stacks/{aws-nuke => nuke/typescript}/package.json (100%) create mode 100644 .tools/test/stacks/nuke/typescript/trigger_dry_run.py rename .tools/test/stacks/{aws-nuke => nuke/typescript}/tsconfig.json (100%) create mode 100644 .tools/test/stacks/nuke/typescript/upload_job_scripts.py diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index bd0d6474bfd..ec985398b5e 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -7,6 +7,7 @@ import yaml import time import re +from nuke.typescript.upload_job_scripts import process_stack_and_upload_files def run_shell_command(command, env_vars=None): @@ -60,7 +61,7 @@ def deploy_resources(account_id, account_name, dir, lang="typescript"): Args: account_id (str): The AWS account ID where resources will be deployed. account_name (str): A human-readable name for the account, used for environment variables. - dir (str): The base directory containing deployment scripts or configurations. + dir (str): The base directory containing deployment scripts or configurations. One of: admin, plugin, images lang (str, optional): The programming language of the deployment scripts. Defaults to 'typescript'. Changes to the desired directory, sets up necessary environment variables, and executes @@ -135,9 +136,22 @@ def main(): for account_name, account_info in items: print( - f"Reading from account {account_name} with ID {account_info['account_id']}" + f"Deploying to account {account_name} with ID {account_info['account_id']}" ) deploy_resources(account_info["account_id"], account_name, args.type) + if 'plugin' in args.type: + print( + f"Also: 💣 deploying nuke to account {account_name} with ID {account_info['account_id']}" + ) + os.chdir("../..") + deploy_resources(account_info["account_id"], account_name, 'nuke') + breakpoint() + process_stack_and_upload_files( + "NukeCleanser", + ["nuke_generic_config.yaml", "nuke_config_update.py"], + region="us-east-1" + ) + os.chdir("../..") if __name__ == "__main__": diff --git a/.tools/test/stacks/aws-nuke/README.md b/.tools/test/stacks/nuke/typescript/README.md similarity index 85% rename from .tools/test/stacks/aws-nuke/README.md rename to .tools/test/stacks/nuke/typescript/README.md index b59ab8528f0..8ed6ae657d0 100644 --- a/.tools/test/stacks/aws-nuke/README.md +++ b/.tools/test/stacks/nuke/typescript/README.md @@ -43,24 +43,29 @@ The code in this repository helps you set up the following architecture: cdk bootstrap && cdk deploy ``` -## Testing -Once stack is created, upload the [nuke generic config file](nuke_generic_config.yaml) and the [python script](cp nuke_config_update.py) to the S3 bucket using the commands below. +Note a successful stack creation, e.g.: -You can find the name of the S3 bucket generated from the CloudFormation console `Outputs` tab. -```sh -aws s3 cp nuke_generic_config.yaml --region us-east-1 s3://{your-bucket-name} -aws s3 cp nuke_config_update.py --region us-east-1 s3://{your-bucket-name} +```bash + ✅ NukeCleanser + +✨ Deployment time: 172.66s + +Outputs: +NukeCleanser.NukeS3BucketValue = nuke-account-cleanser-config-616362312345-us-east-1-c043b470 +Stack ARN: +arn:aws:cloudformation:us-east-1:123456788985:stack/NukeCleanser/cfhdkiott-acec-11ef-ba2e-4555c1356d07 ``` -* Run the stack manually by triggering the StepFunctions with the below sample input payload. (which is pre-configured in the EventBridge Target as a Constant JSON input). You can configure this to run in parallel on the required number of regions by updating the region_list parameter. +Next, run `python upload_job_files.py` to upload two files in this directory: `nuke_config_update.py` and `nuke_generic_config.yaml`. + +## Testing +To test this stack, run `python trigger_dry_run.py` which will invoke the state machine execution in dry-run mode using the following payload: ```sh { "InputPayLoad": { "nuke_dry_run": "true", "nuke_version": "2.21.2", "region_list": [ - "global", - "us-west-1", "us-east-1" ] } diff --git a/.tools/test/stacks/aws-nuke/architecture-overview.png b/.tools/test/stacks/nuke/typescript/architecture-overview.png similarity index 100% rename from .tools/test/stacks/aws-nuke/architecture-overview.png rename to .tools/test/stacks/nuke/typescript/architecture-overview.png diff --git a/.tools/test/stacks/aws-nuke/cdk.json b/.tools/test/stacks/nuke/typescript/cdk.json similarity index 100% rename from .tools/test/stacks/aws-nuke/cdk.json rename to .tools/test/stacks/nuke/typescript/cdk.json diff --git a/.tools/test/stacks/aws-nuke/nuke_cleanser.ts b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts similarity index 100% rename from .tools/test/stacks/aws-nuke/nuke_cleanser.ts rename to .tools/test/stacks/nuke/typescript/nuke_cleanser.ts diff --git a/.tools/test/stacks/aws-nuke/nuke_config_update.py b/.tools/test/stacks/nuke/typescript/nuke_config_update.py similarity index 100% rename from .tools/test/stacks/aws-nuke/nuke_config_update.py rename to .tools/test/stacks/nuke/typescript/nuke_config_update.py diff --git a/.tools/test/stacks/aws-nuke/nuke_generic_config.yaml b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml similarity index 100% rename from .tools/test/stacks/aws-nuke/nuke_generic_config.yaml rename to .tools/test/stacks/nuke/typescript/nuke_generic_config.yaml diff --git a/.tools/test/stacks/aws-nuke/package-lock.json b/.tools/test/stacks/nuke/typescript/package-lock.json similarity index 100% rename from .tools/test/stacks/aws-nuke/package-lock.json rename to .tools/test/stacks/nuke/typescript/package-lock.json diff --git a/.tools/test/stacks/aws-nuke/package.json b/.tools/test/stacks/nuke/typescript/package.json similarity index 100% rename from .tools/test/stacks/aws-nuke/package.json rename to .tools/test/stacks/nuke/typescript/package.json diff --git a/.tools/test/stacks/nuke/typescript/trigger_dry_run.py b/.tools/test/stacks/nuke/typescript/trigger_dry_run.py new file mode 100644 index 00000000000..c58eff84791 --- /dev/null +++ b/.tools/test/stacks/nuke/typescript/trigger_dry_run.py @@ -0,0 +1,76 @@ +import boto3 +import json + +def get_step_function_arn(stack_name): + """ + Retrieve the ARN of the Step Function from the NukeCleanser CloudFormation stack. + + :param stack_name: The name of the CloudFormation stack. + :return: The ARN of the Step Function, or None if not found. + """ + cloudformation = boto3.client('cloudformation') + + try: + response = cloudformation.describe_stack_resources(StackName=stack_name) + resources = response['StackResources'] + + for resource in resources: + if resource['ResourceType'] == 'AWS::StepFunctions::StateMachine': + return resource['PhysicalResourceId'] + + print(f"No Step Function found in stack '{stack_name}'.") + return None + + except cloudformation.exceptions.ClientError as e: + print(f"Error describing stack: {e}") + return None + + +def trigger_step_function(step_function_arn, payload): + """ + Trigger a Step Functions execution with the given input payload. + + :param step_function_arn: The ARN of the Step Function to trigger. + :param payload: The input payload to pass to the Step Function execution. + """ + stepfunctions = boto3.client('stepfunctions') + + try: + response = stepfunctions.start_execution( + stateMachineArn=step_function_arn, + input=json.dumps(payload) + ) + print(f"Step Function triggered successfully.") + print(f"Execution ARN: {response['executionArn']}") + return response['executionArn'] + + except stepfunctions.exceptions.ClientError as e: + print(f"Error triggering Step Function: {e}") + return None + + +if __name__ == "__main__": + # CloudFormation stack name + stack_name = "NukeCleanser" + + # Input payload + input_payload = { + "InputPayLoad": { + "nuke_dry_run": "true", + "nuke_version": "2.21.2", + "region_list": [ + "us-east-1" + ] + } + } + + # Get the ARN of the Step Function from the stack + step_function_arn = get_step_function_arn(stack_name) + + if step_function_arn: + print(f"Found Step Function ARN: {step_function_arn}") + # Trigger the Step Function + trigger_step_function(step_function_arn, input_payload) + else: + print(f"Failed to find a Step Function in stack '{stack_name}'.") + diff --git a/.tools/test/stacks/aws-nuke/tsconfig.json b/.tools/test/stacks/nuke/typescript/tsconfig.json similarity index 100% rename from .tools/test/stacks/aws-nuke/tsconfig.json rename to .tools/test/stacks/nuke/typescript/tsconfig.json diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py new file mode 100644 index 00000000000..47213792f13 --- /dev/null +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -0,0 +1,134 @@ +import boto3 +import os +import json + +def get_s3_bucket_from_stack(stack_name): + """ + Get the S3 bucket name created by a CloudFormation stack. + + :param stack_name: The name of the CloudFormation stack. + :return: The name of the S3 bucket, or None if not found. + """ + cloudformation = boto3.client('cloudformation') + + try: + response = cloudformation.describe_stack_resources(StackName=stack_name) + resources = response['StackResources'] + + for resource in resources: + if resource['ResourceType'] == 'AWS::S3::Bucket': + return resource['PhysicalResourceId'] + + print(f"No S3 bucket found in stack '{stack_name}'.") + return None + + except cloudformation.exceptions.ClientError as e: + print(f"Error describing stack: {e}") + return None + + +def trigger_step_function(step_function_arn, payload): + """ + Trigger a Step Functions execution with the given input payload. + + :param step_function_arn: The ARN of the Step Function to trigger. + :param payload: The input payload to pass to the Step Function execution. + """ + stepfunctions = boto3.client('stepfunctions') + + try: + response = stepfunctions.start_execution( + stateMachineArn=step_function_arn, + input=json.dumps(payload) + ) + print(f"Step Function triggered successfully.") + print(f"Execution ARN: {response['executionArn']}") + return response['executionArn'] + + except stepfunctions.exceptions.ClientError as e: + print(f"Error triggering Step Function: {e}") + return None + + +def upload_file_to_s3(bucket_name, file_name, region="us-east-1"): + """ + Upload a file to an S3 bucket from the `nuke` directory. + + :param bucket_name: The name of the S3 bucket. + :param file_name: The name of the file to upload (relative to the `nuke` directory). + :param region: The AWS region. + """ + s3 = boto3.client('s3', region_name=region) + file_path = os.path.join(file_name) + + try: + s3.upload_file(file_path, bucket_name, file_name) + print(f"Uploaded {file_path} to s3://{bucket_name}/{file_name}") + except Exception as e: + print(f"Error uploading {file_name}: {e}") + + +def process_stack_and_upload_files(stack_name, files, region="us-east-1"): + """ + Retrieve the S3 bucket, upload files from the `nuke` directory, and trigger the Step Function. + + :param stack_name: The name of the CloudFormation stack. + :param files: List of filenames to upload (relative to `nuke`). + :param region: AWS region. + """ + # Retrieve the S3 bucket name from the stack + bucket_name = get_s3_bucket_from_stack(stack_name) + if not bucket_name: + print(f"Failed to find an S3 bucket in stack '{stack_name}'.") + return + + print(f"Found S3 bucket: {bucket_name}") + + # Upload files to the bucket + for file_name in files: + upload_file_to_s3(bucket_name, file_name, region=region) + + # Retrieve the Step Function ARN from the stack + step_function_arn = get_step_function_arn(stack_name) + if not step_function_arn: + print(f"Failed to find a Step Function in stack '{stack_name}'.") + return + + print(f"Found Step Function ARN: {step_function_arn}") + + # Trigger the Step Function + input_payload = { + "InputPayLoad": { + "nuke_dry_run": "true", + "nuke_version": "2.21.2", + "region_list": [ + "us-east-1" + ] + } + } + trigger_step_function(step_function_arn, input_payload) + + +def get_step_function_arn(stack_name): + """ + Retrieve the ARN of the Step Function from a CloudFormation stack. + + :param stack_name: The name of the CloudFormation stack. + :return: The ARN of the Step Function, or None if not found. + """ + cloudformation = boto3.client('cloudformation') + + try: + response = cloudformation.describe_stack_resources(StackName=stack_name) + resources = response['StackResources'] + + for resource in resources: + if resource['ResourceType'] == 'AWS::StepFunctions::StateMachine': + return resource['PhysicalResourceId'] + + print(f"No Step Function found in stack '{stack_name}'.") + return None + + except cloudformation.exceptions.ClientError as e: + print(f"Error describing stack: {e}") + return None diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index b87aeb41da2..7a00cc75cde 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -24,7 +24,7 @@ class PluginStack extends cdk.Stack { private adminAccountId: string; private batchMemory: string; private batchVcpus: string; - private batchStorage: number; + // private batchStorage: number; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); @@ -44,7 +44,7 @@ class PluginStack extends cdk.Stack { // https://docs.aws.amazon.com/batch/latest/APIReference/API_ResourceRequirement.html this.batchMemory = acctConfig[`${toolName}`]?.memory ?? "16384"; // MiB this.batchVcpus = acctConfig[`${toolName}`]?.vcpus ?? "4"; // CPUs - this.batchStorage = acctConfig[`${toolName}`]?.storage ?? 20; // GiB + // this.batchStorage = acctConfig[`${toolName}`]?.storage ?? 20; // GiB } const [jobDefinition, jobQueue] = this.initBatchFargate(); @@ -140,9 +140,9 @@ class PluginStack extends cdk.Stack { value: this.batchMemory, }, ], - ephemeralStorage: { - sizeInGib: this.batchStorage, - }, + // ephemeralStorage: { + // sizeInGib: this.batchStorage, + // }, environment: variableConfigJson, }, platformCapabilities: ["FARGATE"], From 7db7518b72583af9acfb7762d35c231d5dc255e3 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 5 Dec 2024 10:26:22 -0500 Subject: [PATCH 07/26] fixes --- .tools/test/stacks/deploy.py | 123 ++++++++++++++---- .../stacks/nuke/typescript/cdk.context.json | 83 ++++++++++++ .../nuke/typescript/create_account_alias.py | 19 +++ .../stacks/nuke/typescript/nuke_cleanser.ts | 3 + .../nuke/typescript/upload_job_scripts.py | 22 ++-- .../stacks/plugin/typescript/cdk.context.json | 115 +++++++++++++++- 6 files changed, 331 insertions(+), 34 deletions(-) create mode 100644 .tools/test/stacks/nuke/typescript/cdk.context.json create mode 100644 .tools/test/stacks/nuke/typescript/create_account_alias.py diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index ec985398b5e..38300cfd8e4 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -8,8 +8,58 @@ import time import re from nuke.typescript.upload_job_scripts import process_stack_and_upload_files +from nuke.typescript.create_account_alias import create_account_alias +import boto3 +from botocore.exceptions import ClientError, NoCredentialsError +import shutil +def get_caller_identity(): + try: + # Create an STS client + session = boto3.Session() + sts_client = session.client('sts') + + # Get the caller identity + caller_identity = sts_client.get_caller_identity() + + # Print the caller identity details + print("Account ID:", caller_identity['Account']) + print("Arn:", caller_identity['Arn']) + print("UserId:", caller_identity['UserId']) + + # Check if temporary credentials are being used + if 'SessionContext' in caller_identity: + session_context = caller_identity['SessionContext'] + if session_context is not None: + print("Temporary Credentials:") + print("SessionName:", session_context.get('SessionName', 'N/A')) + print("CreationDate:", session_context.get('CreationDate', 'N/A')) + print("ExpirationDate:", session_context.get('Expiration', 'N/A')) + else: + print("Long-term Credentials") + + except NoCredentialsError: + print("No credentials found in shared folder. Credentials wiped!") + except ClientError as e: + print(f"An error occurred: {e}") + +def delete_aws_directory(): + # Path to the .aws directory + aws_dir = os.path.expanduser('~/.aws') + + # Check if the directory exists + if os.path.exists(aws_dir): + try: + # Recursively delete the directory and all its contents + shutil.rmtree(aws_dir) + print(f"Deleted all contents under {aws_dir}.") + except Exception as e: + print(f"Error deleting {aws_dir}: {e}") + else: + print(f"{aws_dir} does not exist.") + +# Call the function def run_shell_command(command, env_vars=None): """ Execute a given shell command securely and return its output. @@ -53,6 +103,29 @@ def validate_alphanumeric(value, name): if not re.match(r"^\w+$", value): raise ValueError(f"{name} must be alphanumeric. Received: {value}") +def get_tokens(account_id): + """ + Get AWS tokens + """ + get_token_tool = os.getenv('TOKEN_TOOL') + get_token_provider = os.getenv('TOKEN_PROVIDER') + + # Securely update tokens + get_tokens_command = [ + get_token_tool, + "credentials", + "update", + "--account", + account_id, + "--provider", + get_token_provider, + "--role", + "weathertop-cdk-deployments", + "--once", + ] + run_shell_command(get_tokens_command) + os.environ['AWS_DEFAULT_REGION'] = 'us-east-1' + get_caller_identity() def deploy_resources(account_id, account_name, dir, lang="typescript"): """ @@ -73,22 +146,8 @@ def deploy_resources(account_id, account_name, dir, lang="typescript"): if dir not in os.getcwd(): os.chdir(f"{dir}/{lang}") - # Securely update tokens - get_tokens_command = [ - "ada", - "credentials", - "update", - "--account", - account_id, - "--provider", - "isengard", - "--role", - "weathertop-cdk-deployments", - "--once", - ] - run_shell_command(get_tokens_command) - # Deploy using CDK + run_shell_command(["cdk", "acknowledge", "31885"]) deploy_command = ["cdk", "deploy", "--require-approval", "never"] print(" ".join(deploy_command)) run_shell_command(deploy_command, env_vars={"TOOL_NAME": account_name}) @@ -97,6 +156,12 @@ def deploy_resources(account_id, account_name, dir, lang="typescript"): # TODO: Add waiter time.sleep(15) + # Wipe credentials + delete_aws_directory() + + # Check to make sure credentials have been wiped + get_caller_identity() + def main(): parser = argparse.ArgumentParser(description="admin, images, or plugin stack.") @@ -136,23 +201,33 @@ def main(): for account_name, account_info in items: print( - f"Deploying to account {account_name} with ID {account_info['account_id']}" + f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n Deploying 🚀 Plugin stack to account {account_name} with ID {account_info['account_id']}" + ) + get_tokens(account_info["account_id"]) + deploy_resources( + account_info["account_id"], + account_name, + args.type ) - deploy_resources(account_info["account_id"], account_name, args.type) if 'plugin' in args.type: print( - f"Also: 💣 deploying nuke to account {account_name} with ID {account_info['account_id']}" + f"Deploying ☢️ AWS-Nuke to account {account_name} with ID {account_info['account_id']}" ) os.chdir("../..") + + get_tokens(account_info["account_id"]) + create_account_alias('weathertop-test') + + get_tokens(account_info["account_id"]) deploy_resources(account_info["account_id"], account_name, 'nuke') - breakpoint() - process_stack_and_upload_files( - "NukeCleanser", - ["nuke_generic_config.yaml", "nuke_config_update.py"], - region="us-east-1" - ) + + get_tokens(account_info["account_id"]) + process_stack_and_upload_files() + os.chdir("../..") + if __name__ == "__main__": + os.environ['JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION'] = 'true' main() diff --git a/.tools/test/stacks/nuke/typescript/cdk.context.json b/.tools/test/stacks/nuke/typescript/cdk.context.json new file mode 100644 index 00000000000..ac449f7bdd4 --- /dev/null +++ b/.tools/test/stacks/nuke/typescript/cdk.context.json @@ -0,0 +1,83 @@ +{ + "acknowledged-issue-numbers": [ + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885 + ] +} diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py new file mode 100644 index 00000000000..923d181d18b --- /dev/null +++ b/.tools/test/stacks/nuke/typescript/create_account_alias.py @@ -0,0 +1,19 @@ +import subprocess + +def create_account_alias(alias_name): + """ + Create a new account alias with the given name. + This function exists because the CDK does not support + this specific CreateAccountAliases API call. + """ + command = [ + "aws", "iam", "create-account-alias", + "--account-alias", alias_name + ] + result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if result.returncode == 0: + print(f"Account alias '{alias_name}' created successfully.") + elif 'EntityAlreadyExists' in result.stderr: + print(f"Account alias '{alias_name}' already exists.") + else: + print(f"Error creating account alias '{alias_name}': {result.stderr}") diff --git a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts index ab81e1f4583..4a10c473c27 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts +++ b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts @@ -652,10 +652,13 @@ class NukeCleanserStack extends cdk.Stack { } const app = new cdk.App(); + new NukeCleanserStack(app, 'NukeCleanser', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, + terminationProtection: true }); + app.synth(); diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 47213792f13..0517cc7a15b 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -9,7 +9,8 @@ def get_s3_bucket_from_stack(stack_name): :param stack_name: The name of the CloudFormation stack. :return: The name of the S3 bucket, or None if not found. """ - cloudformation = boto3.client('cloudformation') + session = boto3.Session() + cloudformation = session.client('cloudformation', region_name='us-east-1') try: response = cloudformation.describe_stack_resources(StackName=stack_name) @@ -34,7 +35,8 @@ def trigger_step_function(step_function_arn, payload): :param step_function_arn: The ARN of the Step Function to trigger. :param payload: The input payload to pass to the Step Function execution. """ - stepfunctions = boto3.client('stepfunctions') + session = boto3.Session() + stepfunctions = session.client('stepfunctions') try: response = stepfunctions.start_execution( @@ -58,7 +60,8 @@ def upload_file_to_s3(bucket_name, file_name, region="us-east-1"): :param file_name: The name of the file to upload (relative to the `nuke` directory). :param region: The AWS region. """ - s3 = boto3.client('s3', region_name=region) + session = boto3.Session() + s3 = session.client('s3', region_name=region) file_path = os.path.join(file_name) try: @@ -68,15 +71,15 @@ def upload_file_to_s3(bucket_name, file_name, region="us-east-1"): print(f"Error uploading {file_name}: {e}") -def process_stack_and_upload_files(stack_name, files, region="us-east-1"): +def process_stack_and_upload_files(): """ Retrieve the S3 bucket, upload files from the `nuke` directory, and trigger the Step Function. - :param stack_name: The name of the CloudFormation stack. - :param files: List of filenames to upload (relative to `nuke`). - :param region: AWS region. """ # Retrieve the S3 bucket name from the stack + stack_name = "NukeCleanser" + files = ["nuke_generic_config.yaml", "nuke_config_update.py"] + region = 'us-east-1' bucket_name = get_s3_bucket_from_stack(stack_name) if not bucket_name: print(f"Failed to find an S3 bucket in stack '{stack_name}'.") @@ -99,7 +102,7 @@ def process_stack_and_upload_files(stack_name, files, region="us-east-1"): # Trigger the Step Function input_payload = { "InputPayLoad": { - "nuke_dry_run": "true", + "nuke_dry_run": "false", "nuke_version": "2.21.2", "region_list": [ "us-east-1" @@ -116,7 +119,8 @@ def get_step_function_arn(stack_name): :param stack_name: The name of the CloudFormation stack. :return: The ARN of the Step Function, or None if not found. """ - cloudformation = boto3.client('cloudformation') + session = boto3.Session() + cloudformation = session.client('cloudformation') try: response = cloudformation.describe_stack_resources(StackName=stack_name) diff --git a/.tools/test/stacks/plugin/typescript/cdk.context.json b/.tools/test/stacks/plugin/typescript/cdk.context.json index dbfec192141..b38bfc335a4 100644 --- a/.tools/test/stacks/plugin/typescript/cdk.context.json +++ b/.tools/test/stacks/plugin/typescript/cdk.context.json @@ -1,5 +1,118 @@ { - "acknowledged-issue-numbers": [19836], + "acknowledged-issue-numbers": [ + 19836, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885 + ], "vpc-provider:account=808326389482:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { "vpcId": "vpc-01ba5951652359b1e", "vpcCidrBlock": "172.31.0.0/16", From 05e5bb32e2be688b112fb0d7de74d6b09909a444 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 5 Dec 2024 10:52:09 -0500 Subject: [PATCH 08/26] refactored utility function --- .tools/test/stacks/config/targets.yaml | 62 +++---- .tools/test/stacks/deploy.py | 65 +++---- .tools/test/stacks/images/python/app.py | 5 +- .../images/scripts/delete_repository.py | 3 +- .../stacks/nuke/typescript/cdk.context.json | 1 + .../nuke/typescript/create_account_alias.py | 12 +- .../nuke/typescript/nuke_config_update.py | 16 +- .../stacks/nuke/typescript/trigger_dry_run.py | 24 ++- .../nuke/typescript/upload_job_scripts.py | 167 +++++++++--------- .../plugin/python/lambda/export_logs.py | 4 +- .../stacks/plugin/typescript/cdk.context.json | 4 + .../plugin/typescript/lambda/export_logs.py | 4 +- 12 files changed, 182 insertions(+), 185 deletions(-) diff --git a/.tools/test/stacks/config/targets.yaml b/.tools/test/stacks/config/targets.yaml index 6c5d86d5258..d612bf74a62 100644 --- a/.tools/test/stacks/config/targets.yaml +++ b/.tools/test/stacks/config/targets.yaml @@ -6,34 +6,34 @@ cpp: dotnetv3: account_id: "441997275833" status: "enabled" -gov2: - account_id: "234521034040" - status: "enabled" -javascriptv3: - account_id: "875008041426" - status: "enabled" -javav2: - account_id: "667348412466" # back-up "814548047983" - status: "enabled" -kotlin: - account_id: "471951630130" # back-up "814548047983" - status: "enabled" -php: - account_id: "733931915187" - status: "enabled" -python: - account_id: "664857444588" - status: "enabled" -ruby: - account_id: "616362385685" - status: "enabled" -rustv1: - account_id: "050288538048" - status: "enabled" - storage: "30" -sapabap: - account_id: "099736152523" - status: "enabled" -swift: - account_id: "637397754108" - status: "enabled" +#gov2: +# account_id: "234521034040" +# status: "enabled" +#javascriptv3: +# account_id: "875008041426" +# status: "enabled" +#javav2: +# account_id: "667348412466" # back-up "814548047983" +# status: "enabled" +#kotlin: +# account_id: "471951630130" # back-up "814548047983" +# status: "enabled" +#php: +# account_id: "733931915187" +# status: "enabled" +#python: +# account_id: "664857444588" +# status: "enabled" +#ruby: +# account_id: "616362385685" +# status: "enabled" +#rustv1: +# account_id: "050288538048" +# status: "enabled" +# storage: "30" +#sapabap: +# account_id: "099736152523" +# status: "enabled" +#swift: +# account_id: "637397754108" +# status: "enabled" diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index 38300cfd8e4..d828ab6b821 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -2,51 +2,43 @@ # SPDX-License-Identifier: Apache-2.0 import argparse -import subprocess import os -import yaml -import time import re -from nuke.typescript.upload_job_scripts import process_stack_and_upload_files -from nuke.typescript.create_account_alias import create_account_alias +import shutil +import subprocess +import time import boto3 +import yaml from botocore.exceptions import ClientError, NoCredentialsError -import shutil + +from nuke.typescript.create_account_alias import create_account_alias +from nuke.typescript.upload_job_scripts import process_stack_and_upload_files + def get_caller_identity(): try: # Create an STS client session = boto3.Session() - sts_client = session.client('sts') + sts_client = session.client("sts") # Get the caller identity caller_identity = sts_client.get_caller_identity() # Print the caller identity details - print("Account ID:", caller_identity['Account']) - print("Arn:", caller_identity['Arn']) - print("UserId:", caller_identity['UserId']) - - # Check if temporary credentials are being used - if 'SessionContext' in caller_identity: - session_context = caller_identity['SessionContext'] - if session_context is not None: - print("Temporary Credentials:") - print("SessionName:", session_context.get('SessionName', 'N/A')) - print("CreationDate:", session_context.get('CreationDate', 'N/A')) - print("ExpirationDate:", session_context.get('Expiration', 'N/A')) - else: - print("Long-term Credentials") + print("Account ID:", caller_identity["Account"]) + print("Arn:", caller_identity["Arn"]) + print("UserId:", caller_identity["UserId"]) except NoCredentialsError: print("No credentials found in shared folder. Credentials wiped!") except ClientError as e: print(f"An error occurred: {e}") + def delete_aws_directory(): # Path to the .aws directory - aws_dir = os.path.expanduser('~/.aws') + aws_dir = os.path.expanduser("~/.aws") # Check if the directory exists if os.path.exists(aws_dir): @@ -59,6 +51,7 @@ def delete_aws_directory(): else: print(f"{aws_dir} does not exist.") + # Call the function def run_shell_command(command, env_vars=None): """ @@ -103,12 +96,13 @@ def validate_alphanumeric(value, name): if not re.match(r"^\w+$", value): raise ValueError(f"{name} must be alphanumeric. Received: {value}") + def get_tokens(account_id): """ Get AWS tokens """ - get_token_tool = os.getenv('TOKEN_TOOL') - get_token_provider = os.getenv('TOKEN_PROVIDER') + get_token_tool = os.getenv("TOKEN_TOOL") + get_token_provider = os.getenv("TOKEN_PROVIDER") # Securely update tokens get_tokens_command = [ @@ -124,9 +118,10 @@ def get_tokens(account_id): "--once", ] run_shell_command(get_tokens_command) - os.environ['AWS_DEFAULT_REGION'] = 'us-east-1' + os.environ["AWS_DEFAULT_REGION"] = "us-east-1" get_caller_identity() + def deploy_resources(account_id, account_name, dir, lang="typescript"): """ Deploy resources to a specified account using configuration specified by directory and language. @@ -189,7 +184,7 @@ def main(): accounts = yaml.safe_load(file) except Exception as e: print(f"Failed to read config data: \n{e}") - + if accounts is None: raise ValueError(f"Could not load accounts for stack {args.type}") @@ -197,29 +192,24 @@ def main(): items = [(args.language, accounts[args.language])] else: items = accounts.items() - - for account_name, account_info in items: + for account_name, account_info in items: print( f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n Deploying 🚀 Plugin stack to account {account_name} with ID {account_info['account_id']}" ) get_tokens(account_info["account_id"]) - deploy_resources( - account_info["account_id"], - account_name, - args.type - ) - if 'plugin' in args.type: + deploy_resources(account_info["account_id"], account_name, args.type) + if "plugin" in args.type: print( f"Deploying ☢️ AWS-Nuke to account {account_name} with ID {account_info['account_id']}" ) os.chdir("../..") get_tokens(account_info["account_id"]) - create_account_alias('weathertop-test') + create_account_alias("weathertop-test") get_tokens(account_info["account_id"]) - deploy_resources(account_info["account_id"], account_name, 'nuke') + deploy_resources(account_info["account_id"], account_name, "nuke") get_tokens(account_info["account_id"]) process_stack_and_upload_files() @@ -227,7 +217,6 @@ def main(): os.chdir("../..") - if __name__ == "__main__": - os.environ['JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION'] = 'true' + os.environ["JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION"] = "true" main() diff --git a/.tools/test/stacks/images/python/app.py b/.tools/test/stacks/images/python/app.py index d653bfcf289..1fc5f22887c 100644 --- a/.tools/test/stacks/images/python/app.py +++ b/.tools/test/stacks/images/python/app.py @@ -4,9 +4,8 @@ import os import aws_cdk as cdk -from public_ecr_repositories_stack.public_ecr_repositories_stack import ( - PublicEcrRepositoriesStack, -) +from public_ecr_repositories_stack.public_ecr_repositories_stack import \ + PublicEcrRepositoriesStack app = cdk.App() PublicEcrRepositoriesStack( diff --git a/.tools/test/stacks/images/scripts/delete_repository.py b/.tools/test/stacks/images/scripts/delete_repository.py index 687388211d5..4cd6e89b91c 100644 --- a/.tools/test/stacks/images/scripts/delete_repository.py +++ b/.tools/test/stacks/images/scripts/delete_repository.py @@ -1,10 +1,11 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -import boto3 import logging import sys +import boto3 + def delete_selected_public_ecr_repos(delete_images=False): """ diff --git a/.tools/test/stacks/nuke/typescript/cdk.context.json b/.tools/test/stacks/nuke/typescript/cdk.context.json index ac449f7bdd4..a69d108d5ea 100644 --- a/.tools/test/stacks/nuke/typescript/cdk.context.json +++ b/.tools/test/stacks/nuke/typescript/cdk.context.json @@ -78,6 +78,7 @@ 31885, 31885, 31885, + 31885, 31885 ] } diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py index 923d181d18b..34f94b70025 100644 --- a/.tools/test/stacks/nuke/typescript/create_account_alias.py +++ b/.tools/test/stacks/nuke/typescript/create_account_alias.py @@ -1,19 +1,19 @@ import subprocess + def create_account_alias(alias_name): """ Create a new account alias with the given name. This function exists because the CDK does not support this specific CreateAccountAliases API call. """ - command = [ - "aws", "iam", "create-account-alias", - "--account-alias", alias_name - ] - result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + command = ["aws", "iam", "create-account-alias", "--account-alias", alias_name] + result = subprocess.run( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) if result.returncode == 0: print(f"Account alias '{alias_name}' created successfully.") - elif 'EntityAlreadyExists' in result.stderr: + elif "EntityAlreadyExists" in result.stderr: print(f"Account alias '{alias_name}' already exists.") else: print(f"Error creating account alias '{alias_name}': {result.stderr}") diff --git a/.tools/test/stacks/nuke/typescript/nuke_config_update.py b/.tools/test/stacks/nuke/typescript/nuke_config_update.py index c41b1c9eae6..6faf6f791f8 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_config_update.py +++ b/.tools/test/stacks/nuke/typescript/nuke_config_update.py @@ -2,11 +2,12 @@ Python class responsible for updating the nuke generic config , based on exceptions to be filtered and also updates dynamically the region attribute passed in from the StepFunctions invocation. This should be modified to suit your needs. """ -import boto3 -import yaml import argparse import copy +import boto3 +import yaml + GLOBAL_RESOURCE_EXCEPTIONS = [ {"property": "tag:DoNotNuke", "value": "True"}, {"property": "tag:Permanent", "value": "True"}, @@ -18,7 +19,6 @@ class StackInfo: - def __init__(self, account, target_regions): self.session = boto3.Session(profile_name="nuke") # Regions to be targeted set from the Stepfunctions/CodeBuild workflow @@ -79,7 +79,9 @@ def GetCFNResources(self, stack, cfn_client): if resource.get("ResourceType") == "AWS::CloudFormation::Stack": self.GetCFNResources(resource, cfn_client) else: - nuke_type = self.UpdateResourceName(resource["ResourceType"]) + nuke_type = self.UpdateResourceName( + resource["ResourceType"] + ) if nuke_type in self.resources: self.resources[nuke_type].append( { @@ -173,7 +175,9 @@ def WriteConfig(self): try: parser = argparse.ArgumentParser() - parser.add_argument("--account", dest="account", help="Account to nuke") # Account and Region from StepFunctions - CodeBuild overridden params + parser.add_argument( + "--account", dest="account", help="Account to nuke" + ) # Account and Region from StepFunctions - CodeBuild overridden params parser.add_argument("--region", dest="region", help="Region to target for nuke") args = parser.parse_args() if not args.account or not args.region: @@ -187,4 +191,4 @@ def WriteConfig(self): print("Incoming Args: ", args) stackInfo = StackInfo(args.account, [args.region]) stackInfo.Populate() - stackInfo.WriteConfig() \ No newline at end of file + stackInfo.WriteConfig() diff --git a/.tools/test/stacks/nuke/typescript/trigger_dry_run.py b/.tools/test/stacks/nuke/typescript/trigger_dry_run.py index c58eff84791..63f0bcacbec 100644 --- a/.tools/test/stacks/nuke/typescript/trigger_dry_run.py +++ b/.tools/test/stacks/nuke/typescript/trigger_dry_run.py @@ -1,6 +1,8 @@ -import boto3 import json +import boto3 + + def get_step_function_arn(stack_name): """ Retrieve the ARN of the Step Function from the NukeCleanser CloudFormation stack. @@ -8,15 +10,15 @@ def get_step_function_arn(stack_name): :param stack_name: The name of the CloudFormation stack. :return: The ARN of the Step Function, or None if not found. """ - cloudformation = boto3.client('cloudformation') + cloudformation = boto3.client("cloudformation") try: response = cloudformation.describe_stack_resources(StackName=stack_name) - resources = response['StackResources'] + resources = response["StackResources"] for resource in resources: - if resource['ResourceType'] == 'AWS::StepFunctions::StateMachine': - return resource['PhysicalResourceId'] + if resource["ResourceType"] == "AWS::StepFunctions::StateMachine": + return resource["PhysicalResourceId"] print(f"No Step Function found in stack '{stack_name}'.") return None @@ -33,16 +35,15 @@ def trigger_step_function(step_function_arn, payload): :param step_function_arn: The ARN of the Step Function to trigger. :param payload: The input payload to pass to the Step Function execution. """ - stepfunctions = boto3.client('stepfunctions') + stepfunctions = boto3.client("stepfunctions") try: response = stepfunctions.start_execution( - stateMachineArn=step_function_arn, - input=json.dumps(payload) + stateMachineArn=step_function_arn, input=json.dumps(payload) ) print(f"Step Function triggered successfully.") print(f"Execution ARN: {response['executionArn']}") - return response['executionArn'] + return response["executionArn"] except stepfunctions.exceptions.ClientError as e: print(f"Error triggering Step Function: {e}") @@ -58,9 +59,7 @@ def trigger_step_function(step_function_arn, payload): "InputPayLoad": { "nuke_dry_run": "true", "nuke_version": "2.21.2", - "region_list": [ - "us-east-1" - ] + "region_list": ["us-east-1"], } } @@ -73,4 +72,3 @@ def trigger_step_function(step_function_arn, payload): trigger_step_function(step_function_arn, input_payload) else: print(f"Failed to find a Step Function in stack '{stack_name}'.") - diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 0517cc7a15b..83259f56674 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -1,138 +1,139 @@ -import boto3 -import os import json +import logging +import os +from typing import Optional + +import boto3 +from botocore.exceptions import ClientError + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Constants +STACK_NAME = "NukeCleanser" +REGION = "us-east-1" +FILES_TO_UPLOAD = ["nuke_generic_config.yaml", "nuke_config_update.py"] +INPUT_PAYLOAD = { + "InputPayLoad": { + "nuke_dry_run": "false", + "nuke_version": "2.21.2", + "region_list": ["us-east-1"], + } +} -def get_s3_bucket_from_stack(stack_name): +# Use session instead of client to avoid +# credential conflict issue related to provider. +session = boto3.Session() + + +def get_resource_from_stack(stack_name: str, resource_type: str) -> Optional[str]: """ - Get the S3 bucket name created by a CloudFormation stack. + Retrieve a specific resource from a CloudFormation stack. :param stack_name: The name of the CloudFormation stack. - :return: The name of the S3 bucket, or None if not found. + :param resource_type: The type of resource to retrieve (e.g., 'AWS::S3::Bucket', 'AWS::StepFunctions::StateMachine'). + :return: The physical resource ID, or None if not found. """ - session = boto3.Session() - cloudformation = session.client('cloudformation', region_name='us-east-1') + cloudformation = session.client("cloudformation", region_name=REGION) try: response = cloudformation.describe_stack_resources(StackName=stack_name) - resources = response['StackResources'] + resources = response["StackResources"] for resource in resources: - if resource['ResourceType'] == 'AWS::S3::Bucket': - return resource['PhysicalResourceId'] + if resource["ResourceType"] == resource_type: + return resource["PhysicalResourceId"] - print(f"No S3 bucket found in stack '{stack_name}'.") + logging.warning(f"No {resource_type} found in stack '{stack_name}'.") return None - except cloudformation.exceptions.ClientError as e: - print(f"Error describing stack: {e}") + except ClientError as e: + logging.error(f"Error describing stack: {e}") return None -def trigger_step_function(step_function_arn, payload): +def get_s3_bucket_from_stack(stack_name: str) -> Optional[str]: + """ + Retrieve the S3 bucket name from a CloudFormation stack. + + :param stack_name: The name of the CloudFormation stack. + :return: The name of the S3 bucket, or None if not found. + """ + return get_resource_from_stack(stack_name, "AWS::S3::Bucket") + + +def get_step_function_arn(stack_name: str) -> Optional[str]: + """ + Retrieve the ARN of the Step Function from a CloudFormation stack. + + :param stack_name: The name of the CloudFormation stack. + :return: The ARN of the Step Function, or None if not found. + """ + return get_resource_from_stack(stack_name, "AWS::StepFunctions::StateMachine") + + +def trigger_step_function(step_function_arn: str, payload: dict) -> Optional[str]: """ Trigger a Step Functions execution with the given input payload. :param step_function_arn: The ARN of the Step Function to trigger. :param payload: The input payload to pass to the Step Function execution. + :return: The execution ARN, or None if an error occurred. """ - session = boto3.Session() - stepfunctions = session.client('stepfunctions') + stepfunctions = session.client("stepfunctions", region_name=REGION) try: response = stepfunctions.start_execution( - stateMachineArn=step_function_arn, - input=json.dumps(payload) + stateMachineArn=step_function_arn, input=json.dumps(payload) ) - print(f"Step Function triggered successfully.") - print(f"Execution ARN: {response['executionArn']}") - return response['executionArn'] + logging.info(f"Step Function triggered successfully.") + logging.info(f"Execution ARN: {response['executionArn']}") + return response["executionArn"] - except stepfunctions.exceptions.ClientError as e: - print(f"Error triggering Step Function: {e}") + except ClientError as e: + logging.error(f"Error triggering Step Function: {e}") return None -def upload_file_to_s3(bucket_name, file_name, region="us-east-1"): +def upload_file_to_s3(bucket_name: str, file_name: str): """ - Upload a file to an S3 bucket from the `nuke` directory. + Upload a file to an S3 bucket from the current directory. :param bucket_name: The name of the S3 bucket. - :param file_name: The name of the file to upload (relative to the `nuke` directory). - :param region: The AWS region. + :param file_name: The name of the file to upload. """ - session = boto3.Session() - s3 = session.client('s3', region_name=region) - file_path = os.path.join(file_name) + s3 = session.client("s3", region_name=REGION) + file_path = os.path.join(os.getcwd(), file_name) try: s3.upload_file(file_path, bucket_name, file_name) - print(f"Uploaded {file_path} to s3://{bucket_name}/{file_name}") + logging.info(f"Uploaded {file_path} to s3://{bucket_name}/{file_name}") except Exception as e: - print(f"Error uploading {file_name}: {e}") + logging.error(f"Error uploading {file_name}: {e}") def process_stack_and_upload_files(): """ - Retrieve the S3 bucket, upload files from the `nuke` directory, and trigger the Step Function. - + Retrieve the S3 bucket and Step Function ARN from the CloudFormation stack, + upload files to the S3 bucket, and trigger the Step Function. """ - # Retrieve the S3 bucket name from the stack - stack_name = "NukeCleanser" - files = ["nuke_generic_config.yaml", "nuke_config_update.py"] - region = 'us-east-1' - bucket_name = get_s3_bucket_from_stack(stack_name) + bucket_name = get_s3_bucket_from_stack(STACK_NAME) if not bucket_name: - print(f"Failed to find an S3 bucket in stack '{stack_name}'.") + logging.error(f"Failed to find an S3 bucket in stack '{STACK_NAME}'.") return - print(f"Found S3 bucket: {bucket_name}") + logging.info(f"Found S3 bucket: {bucket_name}") # Upload files to the bucket - for file_name in files: - upload_file_to_s3(bucket_name, file_name, region=region) + for file_name in FILES_TO_UPLOAD: + upload_file_to_s3(bucket_name, file_name) - # Retrieve the Step Function ARN from the stack - step_function_arn = get_step_function_arn(stack_name) + step_function_arn = get_step_function_arn(STACK_NAME) if not step_function_arn: - print(f"Failed to find a Step Function in stack '{stack_name}'.") + logging.error(f"Failed to find a Step Function in stack '{STACK_NAME}'.") return - print(f"Found Step Function ARN: {step_function_arn}") + logging.info(f"Found Step Function ARN: {step_function_arn}") # Trigger the Step Function - input_payload = { - "InputPayLoad": { - "nuke_dry_run": "false", - "nuke_version": "2.21.2", - "region_list": [ - "us-east-1" - ] - } - } - trigger_step_function(step_function_arn, input_payload) - - -def get_step_function_arn(stack_name): - """ - Retrieve the ARN of the Step Function from a CloudFormation stack. - - :param stack_name: The name of the CloudFormation stack. - :return: The ARN of the Step Function, or None if not found. - """ - session = boto3.Session() - cloudformation = session.client('cloudformation') - - try: - response = cloudformation.describe_stack_resources(StackName=stack_name) - resources = response['StackResources'] - - for resource in resources: - if resource['ResourceType'] == 'AWS::StepFunctions::StateMachine': - return resource['PhysicalResourceId'] - - print(f"No Step Function found in stack '{stack_name}'.") - return None - - except cloudformation.exceptions.ClientError as e: - print(f"Error describing stack: {e}") - return None + trigger_step_function(step_function_arn, INPUT_PAYLOAD) diff --git a/.tools/test/stacks/plugin/python/lambda/export_logs.py b/.tools/test/stacks/plugin/python/lambda/export_logs.py index fff1420edd3..9b3085ed082 100644 --- a/.tools/test/stacks/plugin/python/lambda/export_logs.py +++ b/.tools/test/stacks/plugin/python/lambda/export_logs.py @@ -1,10 +1,10 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +import csv +import io import json import logging import os -import io -import csv import boto3 diff --git a/.tools/test/stacks/plugin/typescript/cdk.context.json b/.tools/test/stacks/plugin/typescript/cdk.context.json index b38bfc335a4..128d1632b2f 100644 --- a/.tools/test/stacks/plugin/typescript/cdk.context.json +++ b/.tools/test/stacks/plugin/typescript/cdk.context.json @@ -111,6 +111,10 @@ 31885, 31885, 31885, + 31885, + 31885, + 31885, + 31885, 31885 ], "vpc-provider:account=808326389482:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { diff --git a/.tools/test/stacks/plugin/typescript/lambda/export_logs.py b/.tools/test/stacks/plugin/typescript/lambda/export_logs.py index 32928fcb1be..d6cd0e03b5c 100644 --- a/.tools/test/stacks/plugin/typescript/lambda/export_logs.py +++ b/.tools/test/stacks/plugin/typescript/lambda/export_logs.py @@ -1,11 +1,11 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +import csv +import io import json import logging import os -import io -import csv import boto3 From 1d7c6a3e1dbfdabfa4863d4b62e6286e4ea4b805 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 5 Dec 2024 11:01:24 -0500 Subject: [PATCH 09/26] refactoring final version --- .tools/test/stacks/deploy.py | 113 +++++++++++------- .tools/test/stacks/images/python/app.py | 5 +- .../stacks/nuke/typescript/cdk.context.json | 3 + .../nuke/typescript/create_account_alias.py | 22 +++- .../nuke/typescript/upload_job_scripts.py | 4 +- .../stacks/plugin/typescript/cdk.context.json | 5 + 6 files changed, 104 insertions(+), 48 deletions(-) diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index d828ab6b821..4dbfbc6cef6 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -1,7 +1,12 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 + +This module contains functions to deploy resources to AWS accounts using AWS CDK. +""" import argparse +import logging import os import re import shutil @@ -15,8 +20,19 @@ from nuke.typescript.create_account_alias import create_account_alias from nuke.typescript.upload_job_scripts import process_stack_and_upload_files +logger = logging.getLogger(__name__) +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) + def get_caller_identity(): + """ + Get the caller identity from AWS STS. + + Logs the account ID, ARN, and user ID of the caller. + Logs an error if no credentials are found or if there is a client error. + """ try: # Create an STS client session = boto3.Session() @@ -25,18 +41,24 @@ def get_caller_identity(): # Get the caller identity caller_identity = sts_client.get_caller_identity() - # Print the caller identity details - print("Account ID:", caller_identity["Account"]) - print("Arn:", caller_identity["Arn"]) - print("UserId:", caller_identity["UserId"]) + # Log the caller identity details + logger.info(f"Credentials Account ID: {caller_identity['Account']}") + logger.debug(f"Arn: {caller_identity['Arn']}") + logger.debug(f"UserId: {caller_identity['UserId']}") except NoCredentialsError: - print("No credentials found in shared folder. Credentials wiped!") + logger.info("No credentials found in shared folder. Credentials wiped!") except ClientError as e: - print(f"An error occurred: {e}") + logger.error(f"An error occurred: {e}") def delete_aws_directory(): + """ + Delete the .aws directory in the user's home directory. + + This function removes the .aws directory and all its contents from the user's + home directory. If the directory does not exist, it logs a message. + """ # Path to the .aws directory aws_dir = os.path.expanduser("~/.aws") @@ -45,24 +67,23 @@ def delete_aws_directory(): try: # Recursively delete the directory and all its contents shutil.rmtree(aws_dir) - print(f"Deleted all contents under {aws_dir}.") + logger.info(f"Deleted all contents under {aws_dir}.") except Exception as e: - print(f"Error deleting {aws_dir}: {e}") + logger.error(f"Error deleting {aws_dir}: {e}") else: - print(f"{aws_dir} does not exist.") + logger.info(f"{aws_dir} does not exist.") -# Call the function def run_shell_command(command, env_vars=None): """ - Execute a given shell command securely and return its output. + Execute a given shell command securely and log its output. Args: - command (list): The command and its arguments listed as separate items. - env_vars (dict, optional): Additional environment variables to set for the command. + command (list): The command and its arguments listed as separate items. + env_vars (dict, optional): Additional environment variables to set for the command. - Outputs the result of the command execution to the console. In case of an error, - it outputs the error message and the stack trace. + Logs the command being executed and its output. In case of an error, + it logs the error message and the stack trace. """ # Prepare the environment env = os.environ.copy() @@ -70,15 +91,15 @@ def run_shell_command(command, env_vars=None): env.update(env_vars) command_str = " ".join(command) - print("COMMAND: " + command_str) + logger.info(f"COMMAND: {command_str}") try: output = subprocess.check_output(command, stderr=subprocess.STDOUT, env=env) - print(f"Command output: {output.decode()}") + logger.info(f"STDOUT:\n{output.decode()}") except subprocess.CalledProcessError as e: - print(f"Error executing command: {e.output.decode()}") + logger.error(f"Error executing command: {e.output.decode()}") raise except Exception as e: - print(f"Exception executing command: {e!r}") + logger.error(f"Exception executing command: {e!r}") raise @@ -87,11 +108,11 @@ def validate_alphanumeric(value, name): Validate that the given value is alphanumeric. Args: - value (str): The value to validate. - name (str): The name of the variable for error messages. + value (str): The value to validate. + name (str): The name of the variable for error messages. Raises: - ValueError: If the value is not alphanumeric. + ValueError: If the value is not alphanumeric. """ if not re.match(r"^\w+$", value): raise ValueError(f"{name} must be alphanumeric. Received: {value}") @@ -99,7 +120,14 @@ def validate_alphanumeric(value, name): def get_tokens(account_id): """ - Get AWS tokens + Get AWS tokens for the specified account. + + Args: + account_id (str): The AWS account ID for which tokens will be obtained. + + Retrieves temporary AWS credentials for the specified account using a token tool + and provider specified in environment variables. Sets the AWS_DEFAULT_REGION + environment variable and logs the caller identity. """ get_token_tool = os.getenv("TOKEN_TOOL") get_token_provider = os.getenv("TOKEN_PROVIDER") @@ -122,15 +150,15 @@ def get_tokens(account_id): get_caller_identity() -def deploy_resources(account_id, account_name, dir, lang="typescript"): +def deploy_resources(account_id, account_name, dir_path, lang="typescript"): """ Deploy resources to a specified account using configuration specified by directory and language. Args: - account_id (str): The AWS account ID where resources will be deployed. - account_name (str): A human-readable name for the account, used for environment variables. - dir (str): The base directory containing deployment scripts or configurations. One of: admin, plugin, images - lang (str, optional): The programming language of the deployment scripts. Defaults to 'typescript'. + account_id (str): The AWS account ID where resources will be deployed. + account_name (str): A human-readable name for the account, used for environment variables. + dir_path (str): The base directory containing deployment scripts or configurations. One of: admin, plugin, images + lang (str, optional): The programming language of the deployment scripts. Defaults to 'typescript'. Changes to the desired directory, sets up necessary environment variables, and executes deployment commands. @@ -138,13 +166,13 @@ def deploy_resources(account_id, account_name, dir, lang="typescript"): validate_alphanumeric(account_id, "account_id") validate_alphanumeric(account_name, "account_name") - if dir not in os.getcwd(): - os.chdir(f"{dir}/{lang}") + if dir_path not in os.getcwd(): + os.chdir(os.path.join(dir_path, lang)) # Deploy using CDK run_shell_command(["cdk", "acknowledge", "31885"]) deploy_command = ["cdk", "deploy", "--require-approval", "never"] - print(" ".join(deploy_command)) + logger.info(" ".join(deploy_command)) run_shell_command(deploy_command, env_vars={"TOOL_NAME": account_name}) # Delay to avoid CLI conflicts @@ -159,7 +187,12 @@ def deploy_resources(account_id, account_name, dir, lang="typescript"): def main(): - parser = argparse.ArgumentParser(description="admin, images, or plugin stack.") + """ + Main function to deploy resources to AWS accounts based on the specified stack type. + """ + parser = argparse.ArgumentParser( + description="Deploy admin, images, or plugin stack." + ) parser.add_argument("type", choices=["admin", "images", "plugin"]) parser.add_argument("--language") args = parser.parse_args() @@ -177,13 +210,13 @@ def main(): } } except Exception as e: - print(f"Failed to read config data: \n{e}") - elif args.type in {"plugin"}: + logger.error(f"Failed to read config data: {e}") + elif args.type == "plugin": try: with open("config/targets.yaml", "r") as file: accounts = yaml.safe_load(file) except Exception as e: - print(f"Failed to read config data: \n{e}") + logger.error(f"Failed to read config data: {e}") if accounts is None: raise ValueError(f"Could not load accounts for stack {args.type}") @@ -194,13 +227,13 @@ def main(): items = accounts.items() for account_name, account_info in items: - print( - f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n Deploying 🚀 Plugin stack to account {account_name} with ID {account_info['account_id']}" + logger.info( + f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n Deploying 🚀 {args.type} stack to account {account_name} with ID {account_info['account_id']}" ) get_tokens(account_info["account_id"]) deploy_resources(account_info["account_id"], account_name, args.type) if "plugin" in args.type: - print( + logger.info( f"Deploying ☢️ AWS-Nuke to account {account_name} with ID {account_info['account_id']}" ) os.chdir("../..") diff --git a/.tools/test/stacks/images/python/app.py b/.tools/test/stacks/images/python/app.py index 1fc5f22887c..d653bfcf289 100644 --- a/.tools/test/stacks/images/python/app.py +++ b/.tools/test/stacks/images/python/app.py @@ -4,8 +4,9 @@ import os import aws_cdk as cdk -from public_ecr_repositories_stack.public_ecr_repositories_stack import \ - PublicEcrRepositoriesStack +from public_ecr_repositories_stack.public_ecr_repositories_stack import ( + PublicEcrRepositoriesStack, +) app = cdk.App() PublicEcrRepositoriesStack( diff --git a/.tools/test/stacks/nuke/typescript/cdk.context.json b/.tools/test/stacks/nuke/typescript/cdk.context.json index a69d108d5ea..79e7b099b42 100644 --- a/.tools/test/stacks/nuke/typescript/cdk.context.json +++ b/.tools/test/stacks/nuke/typescript/cdk.context.json @@ -79,6 +79,9 @@ 31885, 31885, 31885, + 31885, + 31885, + 31885, 31885 ] } diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py index 34f94b70025..acae16d5745 100644 --- a/.tools/test/stacks/nuke/typescript/create_account_alias.py +++ b/.tools/test/stacks/nuke/typescript/create_account_alias.py @@ -1,19 +1,31 @@ +import logging import subprocess +logger = logging.getLogger(__name__) + def create_account_alias(alias_name): """ Create a new account alias with the given name. - This function exists because the CDK does not support - this specific CreateAccountAliases API call. + + Args: + alias_name (str): The desired name for the account alias. + + This function exists because the CDK does not support the specific + CreateAccountAliases API call. It attempts to create an account alias + using the AWS CLI and logs the result. + + If the account alias is created successfully, it logs a success message. + If the account alias already exists, it logs a message indicating that. + If there is any other error, it logs the error message. """ command = ["aws", "iam", "create-account-alias", "--account-alias", alias_name] result = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) if result.returncode == 0: - print(f"Account alias '{alias_name}' created successfully.") + logger.info(f"Account alias '{alias_name}' created successfully.") elif "EntityAlreadyExists" in result.stderr: - print(f"Account alias '{alias_name}' already exists.") + logger.info(f"Account alias '{alias_name}' already exists.") else: - print(f"Error creating account alias '{alias_name}': {result.stderr}") + logger.error(f"Error creating account alias '{alias_name}': {result.stderr}") diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 83259f56674..997b82a15ed 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -7,7 +7,9 @@ from botocore.exceptions import ClientError # Configure logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) # Constants STACK_NAME = "NukeCleanser" diff --git a/.tools/test/stacks/plugin/typescript/cdk.context.json b/.tools/test/stacks/plugin/typescript/cdk.context.json index 128d1632b2f..7d611eb1ba3 100644 --- a/.tools/test/stacks/plugin/typescript/cdk.context.json +++ b/.tools/test/stacks/plugin/typescript/cdk.context.json @@ -115,6 +115,11 @@ 31885, 31885, 31885, + 31885, + 31885, + 31885, + 31885, + 31885, 31885 ], "vpc-provider:account=808326389482:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { From f2ed1d4313d1b55369f9b0fac967e283dee18b21 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 5 Dec 2024 12:36:43 -0500 Subject: [PATCH 10/26] name changing --- .tools/test/stacks/config/targets.yaml | 62 +++---- .../stacks/nuke/typescript/nuke_cleanser.ts | 65 +++---- .../nuke/typescript/nuke_generic_config.yaml | 162 +++++++++--------- .../stacks/plugin/typescript/cdk.context.json | 1 + .../stacks/plugin/typescript/plugin_stack.ts | 1 + 5 files changed, 147 insertions(+), 144 deletions(-) diff --git a/.tools/test/stacks/config/targets.yaml b/.tools/test/stacks/config/targets.yaml index d612bf74a62..6c5d86d5258 100644 --- a/.tools/test/stacks/config/targets.yaml +++ b/.tools/test/stacks/config/targets.yaml @@ -6,34 +6,34 @@ cpp: dotnetv3: account_id: "441997275833" status: "enabled" -#gov2: -# account_id: "234521034040" -# status: "enabled" -#javascriptv3: -# account_id: "875008041426" -# status: "enabled" -#javav2: -# account_id: "667348412466" # back-up "814548047983" -# status: "enabled" -#kotlin: -# account_id: "471951630130" # back-up "814548047983" -# status: "enabled" -#php: -# account_id: "733931915187" -# status: "enabled" -#python: -# account_id: "664857444588" -# status: "enabled" -#ruby: -# account_id: "616362385685" -# status: "enabled" -#rustv1: -# account_id: "050288538048" -# status: "enabled" -# storage: "30" -#sapabap: -# account_id: "099736152523" -# status: "enabled" -#swift: -# account_id: "637397754108" -# status: "enabled" +gov2: + account_id: "234521034040" + status: "enabled" +javascriptv3: + account_id: "875008041426" + status: "enabled" +javav2: + account_id: "667348412466" # back-up "814548047983" + status: "enabled" +kotlin: + account_id: "471951630130" # back-up "814548047983" + status: "enabled" +php: + account_id: "733931915187" + status: "enabled" +python: + account_id: "664857444588" + status: "enabled" +ruby: + account_id: "616362385685" + status: "enabled" +rustv1: + account_id: "050288538048" + status: "enabled" + storage: "30" +sapabap: + account_id: "099736152523" + status: "enabled" +swift: + account_id: "637397754108" + status: "enabled" diff --git a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts index 4a10c473c27..8600600fced 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts +++ b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts @@ -6,17 +6,17 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as stepfunctions from 'aws-cdk-lib/aws-stepfunctions'; -export interface NukeCleanserStackProps extends cdk.StackProps { +export interface NukeStackProps extends cdk.StackProps { /** * The name of the bucket where the nuke binary and config files are stored - * @default 'nuke-account-cleanser-config' + * @default 'account-nuker-config' */ readonly bucketName?: string; /** - * The name of the Nuke Role to be assumed within each account, providing permissions to cleanse the account(s) - * @default 'nuke-auto-account-cleanser' + * The name of the Nuke Role to be assumed within each account, providing permissions to nuke the account(s) + * @default 'account-auto-nuker' */ - readonly nukeCleanserRoleName?: string; + readonly nukeRoleName?: string; /** * IAM Path * @default '/' @@ -39,20 +39,20 @@ export interface NukeCleanserStackProps extends cdk.StackProps { readonly owner?: string; } -class NukeCleanserStack extends cdk.Stack { +class NukeStack extends cdk.Stack { /** * S3 bucket created with the random generated name */ public readonly nukeS3BucketValue; - constructor(scope: cdk.App, id: string, props: NukeCleanserStackProps = {}) { + constructor(scope: cdk.App, id: string, props: NukeStackProps = {}) { super(scope, id, props); // Applying default props props = { ...props, - bucketName: props.bucketName ?? 'nuke-account-cleanser-config', - nukeCleanserRoleName: props.nukeCleanserRoleName ?? 'nuke-auto-account-cleanser', + bucketName: props.bucketName ?? 'account-nuker-config', + nukeRoleName: props.nukeRoleName ?? 'account-auto-nuker', iamPath: props.iamPath ?? '/', awsNukeDryRunFlag: props.awsNukeDryRunFlag ?? 'true', awsNukeVersion: props.awsNukeVersion ?? '2.21.2', @@ -60,8 +60,8 @@ class NukeCleanserStack extends cdk.Stack { }; // Resources - const nukeAccountCleanserPolicy = new iam.CfnManagedPolicy(this, 'NukeAccountCleanserPolicy', { - managedPolicyName: 'NukeAccountCleanser', + const AccountNukerPolicy = new iam.CfnManagedPolicy(this, 'AccountNukerPolicy', { + managedPolicyName: 'AccountNuker', policyDocument: { Statement: [ { @@ -111,7 +111,7 @@ class NukeCleanserStack extends cdk.Stack { ], Version: '2012-10-17', }, - description: 'Managed policy for nuke account cleansing', + description: 'Managed policy for account nuking', path: props.iamPath!, }); @@ -196,7 +196,7 @@ class NukeCleanserStack extends cdk.Stack { { Effect: 'Allow', Action: 'sts:AssumeRole', - Resource: `arn:aws:iam::*:role/${props.nukeCleanserRoleName!}`, + Resource: `arn:aws:iam::*:role/${props.nukeRoleName!}`, }, ], }, @@ -260,9 +260,9 @@ class NukeCleanserStack extends cdk.Stack { }, }); - const nukeAccountCleanserRole = new iam.CfnRole(this, 'NukeAccountCleanserRole', { - roleName: props.nukeCleanserRoleName!, - description: 'Nuke Auto account cleanser role for Dev/Sandbox accounts', + const AccountNukerRole = new iam.CfnRole(this, 'AccountNukerRole', { + roleName: props.nukeRoleName!, + description: 'Account auto nuking role for test accounts', maxSessionDuration: 7200, tags: [ { @@ -275,7 +275,7 @@ class NukeCleanserStack extends cdk.Stack { }, { key: 'description', - value: 'PrivilegedReadWrite:auto-account-cleanser-role', + value: 'PrivilegedReadWrite:account-auto-nuker role', }, { key: 'owner', @@ -297,7 +297,7 @@ class NukeCleanserStack extends cdk.Stack { Version: '2012-10-17', }, managedPolicyArns: [ - nukeAccountCleanserPolicy.ref, + AccountNukerPolicy.ref, ], path: props.iamPath!, }); @@ -343,7 +343,7 @@ class NukeCleanserStack extends cdk.Stack { { name: 'NukeAssumeRoleArn', type: 'PLAINTEXT', - value: nukeAccountCleanserRole.attrArn, + value: AccountNukerRole.attrArn, }, { name: 'NukeCodeBuildProjectName', @@ -362,13 +362,13 @@ class NukeCleanserStack extends cdk.Stack { serviceRole: nukeCodeBuildProjectRole.attrArn, timeoutInMinutes: 120, source: { - buildSpec: 'version: 0.2\nphases:\n install:\n on-failure: ABORT\n commands:\n - export AWS_NUKE_VERSION=$AWS_NukeVersion\n - apt-get install -y wget\n - apt-get install jq\n - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate\n - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz\n - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64\n - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke\n - aws-nuke version\n - echo \"Setting aws cli profile with config file for role assumption using metadata\"\n - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}\n - aws configure set profile.nuke.credential_source \"EcsContainer\"\n - export AWS_PROFILE=nuke\n - export AWS_DEFAULT_PROFILE=nuke\n - export AWS_SDK_LOAD_CONFIG=1\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n build:\n on-failure: CONTINUE\n commands:\n - echo \" ------------------------------------------------ \" >> error_log.txt\n - echo \"Getting nuke generic config file from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .\n - echo \"Updating the TARGET_REGION in the generic config from the parameter\"\n - sed -i \"s/TARGET_REGION/$NukeTargetRegion/g\" nuke_generic_config.yaml\n - echo \"Getting filter/exclusion python script from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n - echo \"Running Config filter/update script\";\n - python3 nuke_config_update.py --account $account_id --region \"$NukeTargetRegion\";\n - echo \"Configured nuke_config.yaml\";\n - echo \"Running Nuke on Account\";\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done\n elif [ \"$AWS_NukeDryRun\" = \"false\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done\n else\n echo \"Couldn\'t determine Dryrun flag...exiting\"\n exit 1\n fi\n - nuke_pid=$!;\n - wait $nuke_pid;\n - echo \"Checking if Nuke Process completed for account\"\n - |\n if cat aws-nuke.log | grep -F \"Error: The specified account doesn\"; then\n echo \"Nuke errored due to no AWS account alias set up - exiting\"\n cat aws-nuke.log >> error_log.txt\n exit 1\n else\n echo \"Nuke completed Successfully - Continuing\"\n fi\n\n post_build:\n commands:\n - echo $CODEBUILD_BUILD_SUCCEEDING\n - echo \"Get current timestamp for naming reports\"\n - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))\n - CURR_TIME_UTC=$(date -u)\n - |\n {\n echo \" Account Cleansing Process Failed;\"\n echo \"\"\n \n echo \" ----------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ----------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ----------------------------------------------------------------\"\n echo \" ################# Failed Nuke Process - Exiting ###################\"\n echo \"\"\n } >> fail_email_template.txt\n - | \n if [ \"$CODEBUILD_BUILD_SUCCEEDING\" = \"0\" ]; then \n echo \" Couldn\'t process Nuke Cleanser - Exiting \" >> fail_email_template.txt\n cat error_log.txt >> fail_email_template.txt\n exit 1;\n fi\n - sleep 120\n - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;\n - CURR_TIME_UTC=$(date -u)\n - | \n if [ -z \"${LOG_STREAM_NAME}\" ]; then\n echo \"Couldn\'t find the log stream for log events\";\n exit 0;\n else\n aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern \"removed\" --no-interleaved | jq -r .events[].message > log_output.txt;\n awk \'/There are resources in failed state/,/Error: failed/\' aws-nuke.log > failure_email_output.txt\n awk \'/Error: failed/,/\\n/\' failure_email_output.txt > failed_log_output.txt\n fi\n - |\n if [ -r log_output.txt ]; then\n content=$(cat log_output.txt)\n echo $content\n elif [ -f \"log_output.txt\" ]; then\n echo \"The file log_output.txt exists but is not readable to the script.\"\n else\n echo \"The file log_output.txt does not exist.\"\n fi\n - echo \"Publishing Log Ouput to SNS:\"\n - sub=\"Nuke Account Cleanser Succeeded in account \"$account_id\" and region \"$NukeTargetRegion\"\"\n - |\n {\n echo \" Account Cleansing Process Completed;\"\n echo \"\"\n \n echo \" ------------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ------------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ------------------------------------------------------------------\"\n echo \" ################### Nuke Cleanser Logs ####################\"\n echo \"\"\n } >> email_template.txt\n\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"No Resources scanned and nukeable yet\"\n - echo \"Number of Resources that is filtered by config:\" >> email_template.txt\n - cat aws-nuke.log | grep -c \" - filtered by config\" || echo 0 >> email_template.txt\n - echo \" ------------------------------------------ \" >> email_template.txt\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n echo \"RESOURCES THAT WOULD BE REMOVED:\" >> email_template.txt\n echo \" ----------------------------------------- \" >> email_template.txt\n cat aws-nuke.log | grep -c \" - would remove\" || echo 0 >> email_template.txt\n cat aws-nuke.log | grep -F \" - would remove\" >> email_template.txt || echo \"No resources to be removed\" >> email_template.txt\n else\n echo \" FAILED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat failed_log_output.txt >> email_template.txt\n echo \" SUCCESSFULLY NUKED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat log_output.txt >> email_template.txt\n fi\n - echo \"Resources Nukeable:\"\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"Nothing Nukeable yet\"\n - echo \"Total number of Resources that would be removed:\"\n - cat aws-nuke.log | grep -c \" - would remove\" || echo \"Nothing would be removed yet\"\n - echo \"Total number of Resources Deleted:\"\n - cat aws-nuke.log | grep -c \" - removed\" || echo \"Nothing deleted yet\"\n - echo \"List of Resources Deleted today:\"\n - cat aws-nuke.log | grep -F \" - removed\" || echo \"Nothing deleted yet\"\n', + buildSpec: 'version: 0.2\nphases:\n install:\n on-failure: ABORT\n commands:\n - export AWS_NUKE_VERSION=$AWS_NukeVersion\n - apt-get install -y wget\n - apt-get install jq\n - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate\n - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz\n - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64\n - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke\n - aws-nuke version\n - echo \"Setting aws cli profile with config file for role assumption using metadata\"\n - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}\n - aws configure set profile.nuke.credential_source \"EcsContainer\"\n - export AWS_PROFILE=nuke\n - export AWS_DEFAULT_PROFILE=nuke\n - export AWS_SDK_LOAD_CONFIG=1\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n build:\n on-failure: CONTINUE\n commands:\n - echo \" ------------------------------------------------ \" >> error_log.txt\n - echo \"Getting nuke generic config file from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .\n - echo \"Updating the TARGET_REGION in the generic config from the parameter\"\n - sed -i \"s/TARGET_REGION/$NukeTargetRegion/g\" nuke_generic_config.yaml\n - echo \"Getting filter/exclusion python script from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n - echo \"Running Config filter/update script\";\n - python3 nuke_config_update.py --account $account_id --region \"$NukeTargetRegion\";\n - echo \"Configured nuke_config.yaml\";\n - echo \"Running Nuke on Account\";\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done\n elif [ \"$AWS_NukeDryRun\" = \"false\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done\n else\n echo \"Couldn\'t determine Dryrun flag...exiting\"\n exit 1\n fi\n - nuke_pid=$!;\n - wait $nuke_pid;\n - echo \"Checking if Nuke Process completed for account\"\n - |\n if cat aws-nuke.log | grep -F \"Error: The specified account doesn\"; then\n echo \"Nuke errored due to no AWS account alias set up - exiting\"\n cat aws-nuke.log >> error_log.txt\n exit 1\n else\n echo \"Nuke completed Successfully - Continuing\"\n fi\n\n post_build:\n commands:\n - echo $CODEBUILD_BUILD_SUCCEEDING\n - echo \"Get current timestamp for naming reports\"\n - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))\n - CURR_TIME_UTC=$(date -u)\n - |\n {\n echo \" Account Nuking Process Failed;\"\n echo \"\"\n \n echo \" ----------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ----------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ----------------------------------------------------------------\"\n echo \" ################# Failed Nuke Process - Exiting ###################\"\n echo \"\"\n } >> fail_email_template.txt\n - | \n if [ \"$CODEBUILD_BUILD_SUCCEEDING\" = \"0\" ]; then \n echo \" Couldn\'t process Account Nuker - Exiting \" >> fail_email_template.txt\n cat error_log.txt >> fail_email_template.txt\n exit 1;\n fi\n - sleep 120\n - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;\n - CURR_TIME_UTC=$(date -u)\n - | \n if [ -z \"${LOG_STREAM_NAME}\" ]; then\n echo \"Couldn\'t find the log stream for log events\";\n exit 0;\n else\n aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern \"removed\" --no-interleaved | jq -r .events[].message > log_output.txt;\n awk \'/There are resources in failed state/,/Error: failed/\' aws-nuke.log > failure_email_output.txt\n awk \'/Error: failed/,/\\n/\' failure_email_output.txt > failed_log_output.txt\n fi\n - |\n if [ -r log_output.txt ]; then\n content=$(cat log_output.txt)\n echo $content\n elif [ -f \"log_output.txt\" ]; then\n echo \"The file log_output.txt exists but is not readable to the script.\"\n else\n echo \"The file log_output.txt does not exist.\"\n fi\n - echo \"Publishing Log Ouput to SNS:\"\n - sub=\"Account Nuker Succeeded in account \"$account_id\" and region \"$NukeTargetRegion\"\"\n - |\n {\n echo \" Account Nuking Process Completed;\"\n echo \"\"\n \n echo \" ------------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ------------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ------------------------------------------------------------------\"\n echo \" ################### Account Nuker Logs ####################\"\n echo \"\"\n } >> email_template.txt\n\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"No Resources scanned and nukeable yet\"\n - echo \"Number of Resources that is filtered by config:\" >> email_template.txt\n - cat aws-nuke.log | grep -c \" - filtered by config\" || echo 0 >> email_template.txt\n - echo \" ------------------------------------------ \" >> email_template.txt\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n echo \"RESOURCES THAT WOULD BE REMOVED:\" >> email_template.txt\n echo \" ----------------------------------------- \" >> email_template.txt\n cat aws-nuke.log | grep -c \" - would remove\" || echo 0 >> email_template.txt\n cat aws-nuke.log | grep -F \" - would remove\" >> email_template.txt || echo \"No resources to be removed\" >> email_template.txt\n else\n echo \" FAILED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat failed_log_output.txt >> email_template.txt\n echo \" SUCCESSFULLY NUKED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat log_output.txt >> email_template.txt\n fi\n - echo \"Resources Nukeable:\"\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"Nothing Nukeable yet\"\n - echo \"Total number of Resources that would be removed:\"\n - cat aws-nuke.log | grep -c \" - would remove\" || echo \"Nothing would be removed yet\"\n - echo \"Total number of Resources Deleted:\"\n - cat aws-nuke.log | grep -c \" - removed\" || echo \"Nothing deleted yet\"\n - echo \"List of Resources Deleted today:\"\n - cat aws-nuke.log | grep -F \" - removed\" || echo \"Nothing deleted yet\"\n', type: 'NO_SOURCE', }, }); const nukeStepFunctionRole = new iam.CfnRole(this, 'NukeStepFunctionRole', { - roleName: 'nuke-account-cleanser-codebuild-state-machine-role', + roleName: 'account-nuker-codebuild-state-machine-role', assumeRolePolicyDocument: { Version: '2012-10-17', Statement: [ @@ -398,7 +398,7 @@ class NukeCleanserStack extends cdk.Stack { path: '/', policies: [ { - policyName: 'nuke-account-cleanser-codebuild-state-machine-policy', + policyName: 'account-nuker-codebuild-state-machine-policy', policyDocument: { Version: '2012-10-17', Statement: [ @@ -441,7 +441,7 @@ class NukeCleanserStack extends cdk.Stack { 'states:DescribeExecution', ], Resource: [ - `arn:aws:states:${this.region}:${this.account}:stateMachine:nuke-account-cleanser-codebuild-state-machine`, + `arn:aws:states:${this.region}:${this.account}:stateMachine:account-nuker-codebuild-state-machine`, ], }, ], @@ -451,10 +451,10 @@ class NukeCleanserStack extends cdk.Stack { }); const nukeStepFunction = new stepfunctions.CfnStateMachine(this, 'NukeStepFunction', { - stateMachineName: 'nuke-account-cleanser-codebuild-state-machine', + stateMachineName: 'account-nuker-codebuild-state-machine', roleArn: nukeStepFunctionRole.attrArn, definitionString: `{ - "Comment": "AWS Nuke Account Cleanser for multi-region single account clean up using SFN Map state parallel invocation of CodeBuild project.", + "Comment": "AWS Account Nuker for multi-region single account clean up using SFN Map state parallel invocation of CodeBuild project.", "StartAt": "StartNukeCodeBuildForEachRegion", "States": { "StartNukeCodeBuildForEachRegion": { @@ -497,7 +497,7 @@ class NukeCleanserStack extends cdk.Stack { "ResultSelector": { "NukeBuildOutput.$": "$.Build" }, - "ResultPath": "$.AccountCleanserRegionOutput", + "ResultPath": "$.AccountNukerRegionOutput", "Retry": [ { "ErrorEquals": [ @@ -514,7 +514,7 @@ class NukeCleanserStack extends cdk.Stack { "States.ALL" ], "Next": "Nuke Failed", - "ResultPath": "$.AccountCleanserRegionOutput" + "ResultPath": "$.AccountNukerRegionOutput" } ] }, @@ -522,12 +522,12 @@ class NukeCleanserStack extends cdk.Stack { "Type": "Choice", "Choices": [ { - "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus", + "Variable": "$.AccountNukerRegionOutput.NukeBuildOutput.BuildStatus", "StringEquals": "SUCCEEDED", "Next": "Nuke Success" }, { - "Variable": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus", + "Variable": "$.AccountNukerRegionOutput.NukeBuildOutput.BuildStatus", "StringEquals": "FAILED", "Next": "Nuke Failed" } @@ -539,7 +539,7 @@ class NukeCleanserStack extends cdk.Stack { "Parameters": { "Status": "Succeeded", "Region.$": "$.region_id", - "CodeBuild Status.$": "$.AccountCleanserRegionOutput.NukeBuildOutput.BuildStatus" + "CodeBuild Status.$": "$.AccountNukerRegionOutput.NukeBuildOutput.BuildStatus" }, "ResultPath": "$.result", "End": true @@ -549,7 +549,7 @@ class NukeCleanserStack extends cdk.Stack { "Parameters": { "Status": "Failed", "Region.$": "$.region_id", - "CodeBuild Status.$": "States.Format('Nuke Account Cleanser failed with error {}. Check CodeBuild execution for input region {} to investigate', $.AccountCleanserRegionOutput.Error, $.region_id)" + "CodeBuild Status.$": "States.Format('Account Nuker failed with error {}. Check CodeBuild execution for input region {} to investigate', $.AccountNukerRegionOutput.Error, $.region_id)" }, "ResultPath": "$.result", "End": true @@ -653,7 +653,8 @@ class NukeCleanserStack extends cdk.Stack { const app = new cdk.App(); -new NukeCleanserStack(app, 'NukeCleanser', { +new NukeStack(app, `NukeStack-${process.env.TOOL_NAME?.replace("_", "-")}`, + { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, diff --git a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml index d8ed5e1e6d8..470bd64087a 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml +++ b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml @@ -92,87 +92,87 @@ accounts: - property: SecurityGroupId type: glob value: "*" - SQSQueue: - - property: "QueueURL" - type: "glob" - value: "*PluginStack-*" - SQSQueuePolicy: - - property: "name" - type: "glob" - value: "*PluginStack-*" - SNSSubscription: - - property: "name" - type: "glob" - value: "*PluginStack-*" - IAMRole: - - property: "name" - type: "glob" - value: "*PluginStack-*" - IAMPolicy: - - property: "name" - type: "glob" - value: "*PluginStack-*" - SecurityGroup: - - property: "group-name" - type: "glob" - value: "*PluginStack-*" - BatchComputeEnvironment: - - property: "name" - type: "glob" - value: "*PluginStack-*" - BatchJobDefinition: - - property: "name" - type: "glob" - value: "*PluginStack-*" - BatchJobQueue: - - property: "name" - type: "glob" - value: "*PluginStack-*" - LambdaFunction: - - property: "Name" - type: "glob" - value: "*PluginStack-*" - LambdaEventSourceMapping: - - property: "EventSourceArn" - type: "glob" - value: "*PluginStack-*" - - property: "FunctionArn" - type: "glob" - value: "*PluginStack-*" - S3Bucket: - - property: "name" - type: "glob" - value: "*PluginStack-*" - S3BucketPolicy: - - property: "bucket-name" - type: "glob" - value: "*PluginStack-*" - EventRule: - - property: "name" - type: "glob" - value: "*PluginStack-*" - - property: "arn" - type: "glob" - value: "*:events:us-east-1:*:rule/PluginStack-*" - LambdaPermission: - - property: "name" - type: "glob" - value: "*PluginStack-*" - CDKMetadata: - - property: "name" - type: "glob" - value: "*PluginStack-*" - CloudWatchEventsRule: - - property: "Name" - type: "glob" - value: "PluginStack-*" - CloudWatchEventsTarget: - - property: "Rule" - type: "glob" - value: "*PluginStack-*" - - property: "Target ID" - type: "glob" - value: "*PluginStack-*" +# SQSQueue: +# - property: "QueueURL" +# type: "glob" +# value: "*PluginStack-*" +# SQSQueuePolicy: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# SNSSubscription: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# IAMRole: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# IAMPolicy: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# SecurityGroup: +# - property: "group-name" +# type: "glob" +# value: "*PluginStack-*" +# BatchComputeEnvironment: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# BatchJobDefinition: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# BatchJobQueue: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# LambdaFunction: +# - property: "Name" +# type: "glob" +# value: "*PluginStack-*" +# LambdaEventSourceMapping: +# - property: "EventSourceArn" +# type: "glob" +# value: "*PluginStack-*" +# - property: "FunctionArn" +# type: "glob" +# value: "*PluginStack-*" +# S3Bucket: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# S3BucketPolicy: +# - property: "bucket-name" +# type: "glob" +# value: "*PluginStack-*" +# EventRule: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# - property: "arn" +# type: "glob" +# value: "*:events:us-east-1:*:rule/PluginStack-*" +# LambdaPermission: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# CDKMetadata: +# - property: "name" +# type: "glob" +# value: "*PluginStack-*" +# CloudWatchEventsRule: +# - property: "Name" +# type: "glob" +# value: "PluginStack-*" +# CloudWatchEventsTarget: +# - property: "Rule" +# type: "glob" +# value: "*PluginStack-*" +# - property: "Target ID" +# type: "glob" +# value: "*PluginStack-*" GuardDutyDetector: - property: DetectorID type: glob diff --git a/.tools/test/stacks/plugin/typescript/cdk.context.json b/.tools/test/stacks/plugin/typescript/cdk.context.json index 7d611eb1ba3..242969d1468 100644 --- a/.tools/test/stacks/plugin/typescript/cdk.context.json +++ b/.tools/test/stacks/plugin/typescript/cdk.context.json @@ -120,6 +120,7 @@ 31885, 31885, 31885, + 31885, 31885 ], "vpc-provider:account=808326389482:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index 7a00cc75cde..89a195e0950 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -348,6 +348,7 @@ new PluginStack( account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, + terminationProtection: true }, ); From 8cdce1d5b5fee9a18514d88e6662c496c24285ad Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 5 Dec 2024 12:38:28 -0500 Subject: [PATCH 11/26] name changes --- .tools/test/stacks/nuke/typescript/nuke_cleanser.ts | 2 +- .tools/test/stacks/nuke/typescript/upload_job_scripts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts index 8600600fced..1e947030d48 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts +++ b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts @@ -653,7 +653,7 @@ class NukeStack extends cdk.Stack { const app = new cdk.App(); -new NukeStack(app, `NukeStack-${process.env.TOOL_NAME?.replace("_", "-")}`, +new NukeStack(app, `AccountNukerStack-${process.env.TOOL_NAME?.replace("_", "-")}`, { env: { account: process.env.CDK_DEFAULT_ACCOUNT, diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 997b82a15ed..8845882d68a 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -12,7 +12,7 @@ ) # Constants -STACK_NAME = "NukeCleanser" +STACK_NAME = "AccountNukerStack" REGION = "us-east-1" FILES_TO_UPLOAD = ["nuke_generic_config.yaml", "nuke_config_update.py"] INPUT_PAYLOAD = { From 69fef7a3f35facc5b43da41b452e8f6b648ea6d7 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Sat, 7 Dec 2024 10:26:26 -0500 Subject: [PATCH 12/26] updates --- .tools/test/DEPLOYMENT.md | 132 +++++----- .tools/test/stacks/deploy.py | 248 +++++++++++------- .../nuke/typescript/create_account_alias.py | 86 +++++- .../nuke/typescript/upload_job_scripts.py | 143 +++++----- 4 files changed, 367 insertions(+), 242 deletions(-) diff --git a/.tools/test/DEPLOYMENT.md b/.tools/test/DEPLOYMENT.md index e4557a49928..39c827e2f56 100644 --- a/.tools/test/DEPLOYMENT.md +++ b/.tools/test/DEPLOYMENT.md @@ -1,92 +1,100 @@ # Deployment Instructions -There are two ways to deploy the code in this directory. +This repository contains infrastructure deployment scripts for testing SDK example code. The infrastructure is managed through AWS CDK and can be deployed in two ways: +1. [deploy.py](#1-using-the-deploy-script) +2. [invoking the CDK directly](#2-invoking-cdk-directly) -## 1. Using the deploy script +## Option 1. Using `deploy.py` -To deploy any stack in this directory, run the [deploy.py](stacks/deploy.py) script. +The [deploy.py](stacks/deploy.py) script is the primary method for deploying the infrastructure stacks. + +It is designed to run from MacOS terminal. + +It uses Python's `subprocess` module to execute CDK commands (as CDK doesn't support exist as a Python library). It relies on environment variables (noted in this document) throughout the deployment process. + +The script handles three types of deployments: + +1. **Images Stack** (`images`): + - Creates empty ECR private repositories for all tools listed in [targets.yaml](stacks/config/targets.yaml) + - Users must implement their own image versioning and pushing mechanism + - Example: GitHub Actions with OIDC provider works well for this purpose + +2. **Admin Stack** (`admin`): + - Deploys event emission infrastructure + - Creates IAM policies for cross-account event subscription + - **Required**: Must be deployed before any plugin stacks + - Works with single or multiple accounts listed in [targets.yaml](stacks/config/targets.yaml) + +3. **Plugin Stack** (`plugin`): + - Deploys two stacks to each account in [targets.yaml](stacks/config/targets.yaml): + 1. Plugin stack that subscribes to admin stack events + 2. Account nuker stack that cleans up residual test resources + - Requires `admin` stack to be deployed first ### Script Prerequisites +- MacOS terminal environment - Python 3.11 installed -- `ada` or equivalent CLI library for fetching AWS credentials. +- AWS CLI and CDK installed and configured (NodeJS 18+) +- Permissions to execute AWS CDK and shell commands (`AdministratorAccess` will work for non-production test environments) +- Configuration files [resources.yaml](stacks/config/resources.yaml) and [targets.yaml](stacks/config/targets.yaml) +- Environment variables set for: + - `TOKEN_TOOL`: Path to credential management tool + - `TOKEN_PROVIDER`: Identity provider for AWS credentials - Dependencies installed in Python virtual environment: - ``` -python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt +python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt`` ``` -- AWS CLI and CDK installed and configured (NodeJS 18+) -- Permissions to execute AWS CDK and shell commands (`AdministratorAccess` will work for non-production test environments) -- Configuration files `resources.yaml` and `targets.yaml`, which exist in the [stacks/config](stacks/config) directory within the same directory as the script - ### Usage #### Command Syntax ```bash -cd stacks ; python deploy.py +cd stacks ; python deploy.py ``` -Replace `` with one of the supported stacks: +Replace `` with one of the supported stacks: - `admin`: Deploys admin-specific resources. - `images`: Deploys image-related resources. - `plugin`: Deploys plugin-specific resources. - - To deploy only a specific language's plugin, pass `--language ` where language is an account in [targets.yaml](stacks/config/targets.yaml). - -#### Additional Notes + - To deploy only a specific language's plugin only, pass `--language ` where `` is an account name in [targets.yaml](stacks/config/targets.yaml). E.g. `python` -The script automatically navigates to the required directory based on the type and language of deployment (typescript is the default). - -Environment variables are set and used during the deployment process. - -Errors during command execution are caught and displayed. - -The script includes a sleep period after deployment to avoid conflicts with simultaneous CDK operations. - -Make sure to check the script's output for any errors or confirmation messages that indicate the deployment's success or failure. Adjust the config files as necessary to match your deployment requirements. +## Technical Notes +This creates some brittleness but provides necessary flexibility for cross-account deployments +#### Additional Notes +Some non-obvious quirks of the script include: + - programmatic file traversing to the required CDK directory based on the type and language of CDK deployment (`typescript` is the default). + - a random-seeming sleep period after deployment to avoid conflicts with the previous CDK operation that may have not killed its thread yet. + - more generally, extensive use of the `subprocess` module which creates some acceptable brittleness that may result in future regression. --- -## 2. Invoking CDK directly - -The second option involves navigating to each stack directory and running the CDK commands. - -The following instructions assume a "plugin account" (the AWS account where testing activities will occur) of "python" (corresponding to a Docker image) per [this repository's configuration](config/targets.yaml). -You can replace Python with any of the other languages listed in this repository's configuration. +## Option 2. Invoking CDK directly -To request an alternate configuration for your own repository or use case, please [submit an issue](https://github.com/awsdocs/aws-doc-sdk-examples/issues/new?labels=type%2Fenhancement&labels=Tools&title=%5BEnhancement%5D%3A+Weathertop+Customization+Request&&) with the `Tools` label. +This option involves navigating to each stack directory([images](stacks/images), [admin](stacks/admin), or [plugin](stacks/plugin)) and running the `cdk` commands explained below. -### 1. Deploy Plugin Stack for your language (e.g. Python) +Required steps for all stack types: +1. Set Python virtualenv within [plugin directory](stacks/plugin/admin). +1. Get AWS account tokens for target account. +1. Run `cdk bootstrap` and `cdk deploy`. -User will: +### Special details for `plugin` type +For the `plugin` type, there are a few important details: +1. User must also run `export LANGUAGE_NAME=python` if your tool is `python`. +1. For the stack to begin accepting test events, you must set `status` to `enabled` for your tool (e.g. `python`) in [targets.yaml](stacks/config/targets.yaml) and redeploy the `admin` stack. +1. To manually trigger test runs, [submit a test job](#submit-test-job) in AWS Batch. -1. Set Python virtualenv within [plugin directory](plugin/admin). -1. `export LANGUAGE_NAME=python`. -1. Get AWS account tokens for plugin account. -1. `cdk bootstrap` and `cdk deploy`. +## Testing & Validation +Users can trigger test runs from within the AWS Console after deploying the `plugin` stack for their chosen tool. -### 2. Enable Consumer Stack to receive event notifications +### Submit test job -User will: +Users can trigger test runs from within the AWS Console after deploying the `plugin` stack for their chosen tool. -1. Set `status` to `enabled` in [targets.yaml](config/targets.yaml) for your language -1. Raise PR. - -Admin will: - -1. Approve and merge PR. -1. Set Python virtualenv within [admin directory](stacks/admin). -1. Get Admin account tokens. -1. `cdk bootstrap` and `cdk deploy`. -1. Request that user [submit a test job](#3-submit-test-job). - -### 3. Submit test job - -User will: - -1. Log into console for Python account +Steps: +1. Log into console for tool AWS account (e.g. `python`) 1. Navigate to "Job Definitions". ![](docs/validation-flow-1.jpg) 1. Click "Submit Job". @@ -97,10 +105,12 @@ User will: ![](docs/validation-flow-4.jpg) 1. Click "Create job". ![](docs/validation-flow-5.jpg) -1. [Validate results of test job](#3-optional-view-test-job-results) - -### 3. Optional: View CloudWatch job results in Batch - -1. Navigate to a job -1. When status is `SUCCEEDED` or `FAILED`, click "Logging" tab. +1. [Validate results of test job](#view-test-run-results) + +### View test run results +1. Log into console for tool AWS account (e.g. `python`) +1. Click `Jobs` and select the only job queue. +2. Toggle `Load all jobs`. +1. View job details by clicking the hyperlinked value in the `Name` field. +2. When status is `SUCCEEDED` or `FAILED`, click "Logging" tab. ![](docs/validation-flow-6.jpg) diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index 4dbfbc6cef6..3dd9a88829c 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -1,8 +1,31 @@ """ -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 +AWS CDK Deployment Script for SDK Testing Infrastructure -This module contains functions to deploy resources to AWS accounts using AWS CDK. +This script manages the deployment of AWS infrastructure components needed for SDK testing. +It handles three main deployment scenarios: + +1. ECR Repository Setup (--type images): + Creates empty ECR private repositories for tools listed in targets.yaml + +2. Admin Stack Deployment (--type admin): + Deploys a stack that emits events and contains IAM policies allowing + cross-account subscription from AWS accounts listed in targets.yaml + +3. Plugin Stack Deployment (--type plugin): + Deploys two stacks in each target account: + - A plugin stack that subscribes to the admin stack's events + - An account nuker stack that cleans up resources left by test executions + +Prerequisites: + - TOKEN_TOOL environment variable set to the token generation tool path + - TOKEN_PROVIDER environment variable set to the token provider + - Appropriate AWS credentials and permissions + - Valid configuration in config/resources.yaml and config/targets.yaml + +Note: + This script uses subprocess to run CDK commands as CDK doesn't support + direct module import. While this creates some brittleness, it provides + necessary flexibility for cross-account deployments. """ import argparse @@ -12,6 +35,8 @@ import shutil import subprocess import time +from pathlib import Path +from typing import Dict, List, Optional, Any, Union import boto3 import yaml @@ -20,13 +45,57 @@ from nuke.typescript.create_account_alias import create_account_alias from nuke.typescript.upload_job_scripts import process_stack_and_upload_files +# Constants +AWS_DEFAULT_REGION = "us-east-1" +CDK_DEPLOYMENT_ROLE = "weathertop-cdk-deployments" +ACCOUNT_ALIAS = "weathertop-test" +CDK_ACKNOWLEDGE_ID = "31885" + +# Configure logging logger = logging.getLogger(__name__) logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) -def get_caller_identity(): +class DeploymentError(Exception): + """Custom exception for deployment-related errors.""" + + pass + + +class ConfigurationManager: + """Manages loading and validation of configuration files.""" + + def __init__(self, config_dir: Path = Path("config")): + self.config_dir = config_dir + + def load_admin_config(self) -> Dict[str, Dict[str, str]]: + """Load admin configuration from resources.yaml.""" + try: + with open(self.config_dir / "resources.yaml") as file: + data = yaml.safe_load(file) + return { + "admin": { + "account_id": str(data["admin_acct"]), + "status": "enabled", + } + } + except Exception as e: + logger.error(f"Failed to read admin config: {e}") + raise + + def load_target_accounts(self) -> Dict[str, Any]: + """Load target accounts from targets.yaml.""" + try: + with open(self.config_dir / "targets.yaml") as file: + return yaml.safe_load(file) + except Exception as e: + logger.error(f"Failed to read targets config: {e}") + raise + + +def get_caller_identity() -> None: """ Get the caller identity from AWS STS. @@ -34,14 +103,10 @@ def get_caller_identity(): Logs an error if no credentials are found or if there is a client error. """ try: - # Create an STS client session = boto3.Session() sts_client = session.client("sts") - - # Get the caller identity caller_identity = sts_client.get_caller_identity() - # Log the caller identity details logger.info(f"Credentials Account ID: {caller_identity['Account']}") logger.debug(f"Arn: {caller_identity['Arn']}") logger.debug(f"UserId: {caller_identity['UserId']}") @@ -52,20 +117,17 @@ def get_caller_identity(): logger.error(f"An error occurred: {e}") -def delete_aws_directory(): +def delete_aws_directory() -> None: """ Delete the .aws directory in the user's home directory. This function removes the .aws directory and all its contents from the user's home directory. If the directory does not exist, it logs a message. """ - # Path to the .aws directory - aws_dir = os.path.expanduser("~/.aws") + aws_dir = Path.home() / ".aws" - # Check if the directory exists - if os.path.exists(aws_dir): + if aws_dir.exists(): try: - # Recursively delete the directory and all its contents shutil.rmtree(aws_dir) logger.info(f"Deleted all contents under {aws_dir}.") except Exception as e: @@ -74,24 +136,26 @@ def delete_aws_directory(): logger.info(f"{aws_dir} does not exist.") -def run_shell_command(command, env_vars=None): +def run_shell_command( + command: List[str], env_vars: Optional[Dict[str, str]] = None +) -> None: """ Execute a given shell command securely and log its output. Args: - command (list): The command and its arguments listed as separate items. - env_vars (dict, optional): Additional environment variables to set for the command. + command: The command and its arguments listed as separate items. + env_vars: Additional environment variables to set for the command. - Logs the command being executed and its output. In case of an error, - it logs the error message and the stack trace. + Raises: + subprocess.CalledProcessError: If the command execution fails. """ - # Prepare the environment env = os.environ.copy() if env_vars: env.update(env_vars) command_str = " ".join(command) logger.info(f"COMMAND: {command_str}") + try: output = subprocess.check_output(command, stderr=subprocess.STDOUT, env=env) logger.info(f"STDOUT:\n{output.decode()}") @@ -103,13 +167,13 @@ def run_shell_command(command, env_vars=None): raise -def validate_alphanumeric(value, name): +def validate_alphanumeric(value: str, name: str) -> None: """ Validate that the given value is alphanumeric. Args: - value (str): The value to validate. - name (str): The name of the variable for error messages. + value: The value to validate. + name: The name of the variable for error messages. Raises: ValueError: If the value is not alphanumeric. @@ -118,21 +182,21 @@ def validate_alphanumeric(value, name): raise ValueError(f"{name} must be alphanumeric. Received: {value}") -def get_tokens(account_id): +def get_tokens(account_id: str) -> None: """ Get AWS tokens for the specified account. Args: - account_id (str): The AWS account ID for which tokens will be obtained. - - Retrieves temporary AWS credentials for the specified account using a token tool - and provider specified in environment variables. Sets the AWS_DEFAULT_REGION - environment variable and logs the caller identity. + account_id: The AWS account ID for which tokens will be obtained. """ get_token_tool = os.getenv("TOKEN_TOOL") get_token_provider = os.getenv("TOKEN_PROVIDER") - # Securely update tokens + if not all([get_token_tool, get_token_provider]): + raise DeploymentError( + "TOKEN_TOOL and TOKEN_PROVIDER environment variables must be set" + ) + get_tokens_command = [ get_token_tool, "credentials", @@ -142,26 +206,28 @@ def get_tokens(account_id): "--provider", get_token_provider, "--role", - "weathertop-cdk-deployments", + CDK_DEPLOYMENT_ROLE, "--once", ] run_shell_command(get_tokens_command) - os.environ["AWS_DEFAULT_REGION"] = "us-east-1" + os.environ["AWS_DEFAULT_REGION"] = AWS_DEFAULT_REGION get_caller_identity() -def deploy_resources(account_id, account_name, dir_path, lang="typescript"): +def deploy_resources( + account_id: str, + account_name: str, + dir_path: Union[str, Path], + lang: str = "typescript", +) -> None: """ Deploy resources to a specified account using configuration specified by directory and language. Args: - account_id (str): The AWS account ID where resources will be deployed. - account_name (str): A human-readable name for the account, used for environment variables. - dir_path (str): The base directory containing deployment scripts or configurations. One of: admin, plugin, images - lang (str, optional): The programming language of the deployment scripts. Defaults to 'typescript'. - - Changes to the desired directory, sets up necessary environment variables, and executes - deployment commands. + account_id: The AWS account ID where resources will be deployed. + account_name: A human-readable name for the account, used for environment variables. + dir_path: The base directory containing deployment scripts or configurations. + lang: The programming language of the deployment scripts. """ validate_alphanumeric(account_id, "account_id") validate_alphanumeric(account_name, "account_name") @@ -169,77 +235,50 @@ def deploy_resources(account_id, account_name, dir_path, lang="typescript"): if dir_path not in os.getcwd(): os.chdir(os.path.join(dir_path, lang)) - # Deploy using CDK - run_shell_command(["cdk", "acknowledge", "31885"]) + run_shell_command(["cdk", "acknowledge", CDK_ACKNOWLEDGE_ID]) deploy_command = ["cdk", "deploy", "--require-approval", "never"] - logger.info(" ".join(deploy_command)) run_shell_command(deploy_command, env_vars={"TOOL_NAME": account_name}) # Delay to avoid CLI conflicts - # TODO: Add waiter + # TODO: Replace with proper waiter implementation time.sleep(15) - # Wipe credentials delete_aws_directory() - - # Check to make sure credentials have been wiped get_caller_identity() -def main(): - """ - Main function to deploy resources to AWS accounts based on the specified stack type. +def deploy_stacks( + stack_type: str, accounts: Dict[str, Any], language: Optional[str] +) -> None: """ - parser = argparse.ArgumentParser( - description="Deploy admin, images, or plugin stack." - ) - parser.add_argument("type", choices=["admin", "images", "plugin"]) - parser.add_argument("--language") - args = parser.parse_args() - - accounts = None + Deploy the specified stack type to all target accounts. - if args.type in {"admin", "images"}: - try: - with open("config/resources.yaml", "r") as file: - data = yaml.safe_load(file) - accounts = { - "admin": { - "account_id": f"{data['admin_acct']}", - "status": "enabled", - } - } - except Exception as e: - logger.error(f"Failed to read config data: {e}") - elif args.type == "plugin": - try: - with open("config/targets.yaml", "r") as file: - accounts = yaml.safe_load(file) - except Exception as e: - logger.error(f"Failed to read config data: {e}") - - if accounts is None: - raise ValueError(f"Could not load accounts for stack {args.type}") - - if args.language: - items = [(args.language, accounts[args.language])] - else: - items = accounts.items() + Args: + stack_type: Type of stack to deploy (admin, images, or plugin) + accounts: Dictionary of account configurations + language: Optional specific language to deploy for + """ + items = [(language, accounts[language])] if language else accounts.items() for account_name, account_info in items: logger.info( - f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n Deploying 🚀 {args.type} stack to account {account_name} with ID {account_info['account_id']}" + f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n" + f"Deploying 🚀 {stack_type} stack to account {account_name}" + f" with ID {account_info['account_id']}" ) + get_tokens(account_info["account_id"]) - deploy_resources(account_info["account_id"], account_name, args.type) - if "plugin" in args.type: + deploy_resources(account_info["account_id"], account_name, stack_type) + + if stack_type == "plugin": logger.info( - f"Deploying ☢️ AWS-Nuke to account {account_name} with ID {account_info['account_id']}" + f"Deploying ☢️ AWS-Nuke to account {account_name}" + f" with ID {account_info['account_id']}" ) os.chdir("../..") get_tokens(account_info["account_id"]) - create_account_alias("weathertop-test") + create_account_alias(ACCOUNT_ALIAS) get_tokens(account_info["account_id"]) deploy_resources(account_info["account_id"], account_name, "nuke") @@ -250,6 +289,37 @@ def main(): os.chdir("../..") +def main() -> None: + """Execute the main deployment workflow.""" + parser = argparse.ArgumentParser( + description="Deploy admin, images, or plugin stack.", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "type", choices=["admin", "images", "plugin"], help="Type of stack to deploy" + ) + parser.add_argument("--language", help="Specific language to deploy for") + args = parser.parse_args() + + config_manager = ConfigurationManager() + + try: + accounts = ( + config_manager.load_admin_config() + if args.type in {"admin", "images"} + else config_manager.load_target_accounts() + ) + + if not accounts: + raise DeploymentError(f"No accounts found for stack type: {args.type}") + + deploy_stacks(args.type, accounts, args.language) + + except Exception as e: + logger.error(f"Deployment failed: {e}") + raise + + if __name__ == "__main__": os.environ["JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION"] = "true" main() diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py index acae16d5745..16220768705 100644 --- a/.tools/test/stacks/nuke/typescript/create_account_alias.py +++ b/.tools/test/stacks/nuke/typescript/create_account_alias.py @@ -1,15 +1,49 @@ +""" +This module is used to create an AWS account alias, which is required by the deploy.py script. + +It provides a function to create an account alias using the AWS CLI, as this specific +operation is not supported by the AWS CDK. +""" + import logging import subprocess +import re +from typing import Optional logger = logging.getLogger(__name__) -def create_account_alias(alias_name): +def _is_valid_alias(alias_name: str) -> bool: """ - Create a new account alias with the given name. + Check if the provided alias name is valid according to AWS rules. + + AWS account alias must be unique and must be between 3 and 63 characters long. + Valid characters are a-z, 0-9 and '-'. Args: - alias_name (str): The desired name for the account alias. + alias_name (str): The alias name to validate. + + Returns: + bool: True if the alias is valid, False otherwise. + """ + pattern = r"^[a-z0-9](([a-z0-9]|-){0,61}[a-z0-9])?$" + return bool(re.match(pattern, alias_name)) and 3 <= len(alias_name) <= 63 + + +def _log_aws_cli_version() -> None: + """ + Log the version of the AWS CLI installed on the system. + """ + try: + result = subprocess.run(["aws", "--version"], capture_output=True, text=True) + logger.info(f"AWS CLI version: {result.stderr.strip()}") + except Exception as e: + logger.warning(f"Unable to determine AWS CLI version: {str(e)}") + + +def create_account_alias(alias_name: str) -> None: + """ + Create a new account alias with the given name. This function exists because the CDK does not support the specific CreateAccountAliases API call. It attempts to create an account alias @@ -18,14 +52,44 @@ def create_account_alias(alias_name): If the account alias is created successfully, it logs a success message. If the account alias already exists, it logs a message indicating that. If there is any other error, it logs the error message. + + Args: + alias_name (str): The desired name for the account alias. """ + # Log AWS CLI version when the function is called + _log_aws_cli_version() + + if not _is_valid_alias(alias_name): + logger.error( + f"Invalid alias name '{alias_name}'. It must be between 3 and 63 characters long and contain only lowercase letters, numbers, and hyphens." + ) + return + command = ["aws", "iam", "create-account-alias", "--account-alias", alias_name] - result = subprocess.run( - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True - ) - if result.returncode == 0: + + try: + subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + ) logger.info(f"Account alias '{alias_name}' created successfully.") - elif "EntityAlreadyExists" in result.stderr: - logger.info(f"Account alias '{alias_name}' already exists.") - else: - logger.error(f"Error creating account alias '{alias_name}': {result.stderr}") + except subprocess.CalledProcessError as e: + if "EntityAlreadyExists" in e.stderr: + logger.info(f"Account alias '{alias_name}' already exists.") + elif "AccessDenied" in e.stderr: + logger.error( + f"Access denied when creating account alias '{alias_name}'. Check your AWS credentials and permissions." + ) + elif "ValidationError" in e.stderr: + logger.error( + f"Validation error when creating account alias '{alias_name}'. The alias might not meet AWS requirements." + ) + else: + logger.error(f"Error creating account alias '{alias_name}': {e.stderr}") + except Exception as e: + logger.error( + f"Unexpected error occurred while creating account alias '{alias_name}': {str(e)}" + ) diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 8845882d68a..0ae77ec4ff0 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -1,17 +1,24 @@ +""" +This module is part of the AccountNuker stack deployment process. +It's invoked by the stacks/deploy.py script to support integration testing. + +Main purposes: +1. Retrieve resources (S3 bucket and Step Function ARN) from a CloudFormation stack. +2. Upload configuration and helper files to the S3 bucket. +3. Trigger a Step Function execution to initiate the account nuking procedure. + +The uploaded files and Step Function execution are used by CodeBuild to execute the nuking procedure. +""" + import json import logging import os -from typing import Optional +from typing import Optional, Dict import boto3 from botocore.exceptions import ClientError -# Configure logging -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) - -# Constants +# Configuration STACK_NAME = "AccountNukerStack" REGION = "us-east-1" FILES_TO_UPLOAD = ["nuke_generic_config.yaml", "nuke_config_update.py"] @@ -23,119 +30,93 @@ } } -# Use session instead of client to avoid -# credential conflict issue related to provider. -session = boto3.Session() - +# Logging setup +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) -def get_resource_from_stack(stack_name: str, resource_type: str) -> Optional[str]: - """ - Retrieve a specific resource from a CloudFormation stack. +# AWS session +session = boto3.Session(region_name=REGION) - :param stack_name: The name of the CloudFormation stack. - :param resource_type: The type of resource to retrieve (e.g., 'AWS::S3::Bucket', 'AWS::StepFunctions::StateMachine'). - :return: The physical resource ID, or None if not found. - """ - cloudformation = session.client("cloudformation", region_name=REGION) +def _get_resource_from_stack(stack_name: str, resource_type: str) -> Optional[str]: + """Retrieve a specific resource from a CloudFormation stack.""" + cloudformation = session.client("cloudformation") try: response = cloudformation.describe_stack_resources(StackName=stack_name) - resources = response["StackResources"] - - for resource in resources: + for resource in response["StackResources"]: if resource["ResourceType"] == resource_type: return resource["PhysicalResourceId"] - - logging.warning(f"No {resource_type} found in stack '{stack_name}'.") - return None - + logger.warning(f"No {resource_type} found in stack '{stack_name}'.") except ClientError as e: - logging.error(f"Error describing stack: {e}") - return None + logger.error(f"Error describing stack: {e}") + return None -def get_s3_bucket_from_stack(stack_name: str) -> Optional[str]: - """ - Retrieve the S3 bucket name from a CloudFormation stack. +def _get_s3_bucket_from_stack(stack_name: str) -> Optional[str]: + """Retrieve the S3 bucket name from a CloudFormation stack.""" + return _get_resource_from_stack(stack_name, "AWS::S3::Bucket") - :param stack_name: The name of the CloudFormation stack. - :return: The name of the S3 bucket, or None if not found. - """ - return get_resource_from_stack(stack_name, "AWS::S3::Bucket") - - -def get_step_function_arn(stack_name: str) -> Optional[str]: - """ - Retrieve the ARN of the Step Function from a CloudFormation stack. - :param stack_name: The name of the CloudFormation stack. - :return: The ARN of the Step Function, or None if not found. - """ - return get_resource_from_stack(stack_name, "AWS::StepFunctions::StateMachine") - - -def trigger_step_function(step_function_arn: str, payload: dict) -> Optional[str]: - """ - Trigger a Step Functions execution with the given input payload. +def _get_step_function_arn(stack_name: str) -> Optional[str]: + """Retrieve the ARN of the Step Function from a CloudFormation stack.""" + return _get_resource_from_stack(stack_name, "AWS::StepFunctions::StateMachine") - :param step_function_arn: The ARN of the Step Function to trigger. - :param payload: The input payload to pass to the Step Function execution. - :return: The execution ARN, or None if an error occurred. - """ - stepfunctions = session.client("stepfunctions", region_name=REGION) +def _trigger_step_function(step_function_arn: str, payload: Dict) -> Optional[str]: + """Trigger a Step Functions execution with the given input payload.""" + stepfunctions = session.client("stepfunctions") try: response = stepfunctions.start_execution( stateMachineArn=step_function_arn, input=json.dumps(payload) ) - logging.info(f"Step Function triggered successfully.") - logging.info(f"Execution ARN: {response['executionArn']}") + logger.info( + f"Step Function triggered successfully. Execution ARN: {response['executionArn']}" + ) return response["executionArn"] - except ClientError as e: - logging.error(f"Error triggering Step Function: {e}") - return None - + logger.error(f"Error triggering Step Function: {e}") + return None -def upload_file_to_s3(bucket_name: str, file_name: str): - """ - Upload a file to an S3 bucket from the current directory. - :param bucket_name: The name of the S3 bucket. - :param file_name: The name of the file to upload. - """ - s3 = session.client("s3", region_name=REGION) +def _upload_file_to_s3(bucket_name: str, file_name: str) -> bool: + """Upload a file to an S3 bucket from the current directory.""" + s3 = session.client("s3") file_path = os.path.join(os.getcwd(), file_name) - try: s3.upload_file(file_path, bucket_name, file_name) - logging.info(f"Uploaded {file_path} to s3://{bucket_name}/{file_name}") - except Exception as e: - logging.error(f"Error uploading {file_name}: {e}") + logger.info(f"Uploaded {file_path} to s3://{bucket_name}/{file_name}") + return True + except FileNotFoundError: + logger.error(f"The file {file_name} was not found in the current directory.") + except ClientError as e: + logger.error(f"Error uploading {file_name}: {e}") + return False def process_stack_and_upload_files(): """ - Retrieve the S3 bucket and Step Function ARN from the CloudFormation stack, - upload files to the S3 bucket, and trigger the Step Function. + Main function to process the stack, upload files, and trigger the Step Function. + This is the only public function intended to be called from outside this module. """ - bucket_name = get_s3_bucket_from_stack(STACK_NAME) + bucket_name = _get_s3_bucket_from_stack(STACK_NAME) if not bucket_name: - logging.error(f"Failed to find an S3 bucket in stack '{STACK_NAME}'.") + logger.error(f"Failed to find an S3 bucket in stack '{STACK_NAME}'.") return - logging.info(f"Found S3 bucket: {bucket_name}") + logger.info(f"Found S3 bucket: {bucket_name}") # Upload files to the bucket for file_name in FILES_TO_UPLOAD: - upload_file_to_s3(bucket_name, file_name) + _upload_file_to_s3(bucket_name, file_name) - step_function_arn = get_step_function_arn(STACK_NAME) + step_function_arn = _get_step_function_arn(STACK_NAME) if not step_function_arn: - logging.error(f"Failed to find a Step Function in stack '{STACK_NAME}'.") + logger.error(f"Failed to find a Step Function in stack '{STACK_NAME}'.") return - logging.info(f"Found Step Function ARN: {step_function_arn}") + logger.info(f"Found Step Function ARN: {step_function_arn}") # Trigger the Step Function - trigger_step_function(step_function_arn, INPUT_PAYLOAD) + _trigger_step_function(step_function_arn, INPUT_PAYLOAD) From 411706088c5492ddb0e9ae9832d35858f7bb68a3 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Tue, 10 Dec 2024 10:54:33 -0500 Subject: [PATCH 13/26] Lots of changes --- .tools/test/stacks/config/targets.yaml | 4 +- .../stacks/nuke/typescript/cdk.context.json | 5 ++ .../nuke/typescript/nuke_generic_config.yaml | 22 +++---- .../nuke/typescript/upload_job_scripts.py | 58 ++++++++++++++++++- .../stacks/plugin/typescript/cdk.context.json | 12 ++++ .../stacks/plugin/typescript/plugin_stack.ts | 10 ++-- 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/.tools/test/stacks/config/targets.yaml b/.tools/test/stacks/config/targets.yaml index 6c5d86d5258..8cb784d2b8b 100644 --- a/.tools/test/stacks/config/targets.yaml +++ b/.tools/test/stacks/config/targets.yaml @@ -30,10 +30,10 @@ ruby: rustv1: account_id: "050288538048" status: "enabled" - storage: "30" + storage: 60 sapabap: account_id: "099736152523" status: "enabled" swift: account_id: "637397754108" - status: "enabled" + status: "enabled" \ No newline at end of file diff --git a/.tools/test/stacks/nuke/typescript/cdk.context.json b/.tools/test/stacks/nuke/typescript/cdk.context.json index 79e7b099b42..0dff385605a 100644 --- a/.tools/test/stacks/nuke/typescript/cdk.context.json +++ b/.tools/test/stacks/nuke/typescript/cdk.context.json @@ -82,6 +82,11 @@ 31885, 31885, 31885, + 31885, + 31885, + 31885, + 31885, + 31885, 31885 ] } diff --git a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml index 470bd64087a..38ea20617d1 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml +++ b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml @@ -132,13 +132,13 @@ accounts: # - property: "Name" # type: "glob" # value: "*PluginStack-*" -# LambdaEventSourceMapping: -# - property: "EventSourceArn" -# type: "glob" -# value: "*PluginStack-*" -# - property: "FunctionArn" -# type: "glob" -# value: "*PluginStack-*" + LambdaEventSourceMapping: + - property: "EventSourceArn" + type: "glob" + value: "*PluginStack-*" + - property: "FunctionArn" + type: "glob" + value: "*PluginStack-*" # S3Bucket: # - property: "name" # type: "glob" @@ -154,10 +154,10 @@ accounts: # - property: "arn" # type: "glob" # value: "*:events:us-east-1:*:rule/PluginStack-*" -# LambdaPermission: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" + LambdaPermission: + - property: "name" + type: "glob" + value: "*PluginStack-*" # CDKMetadata: # - property: "name" # type: "glob" diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 0ae77ec4ff0..cb6b79c7795 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -39,10 +39,63 @@ # AWS session session = boto3.Session(region_name=REGION) +def _get_stack_name(partial_stack_name: str) -> Optional[str]: + """ + Retrieves the full stack name from a partial stack name. + + Args: + partial_stack_name (str): The partial stack name to search for. + + Returns: + Optional[str]: The full stack name if found, otherwise None. + """ + session = boto3.Session() + cloudformation = session.client("cloudformation") + + try: + # List all stacks with status CREATE_COMPLETE + response = cloudformation.list_stacks( + StackStatusFilter=['CREATE_COMPLETE'] + ) + except ClientError as e: + print(f"Error listing stacks: {e}") + return None -def _get_resource_from_stack(stack_name: str, resource_type: str) -> Optional[str]: - """Retrieve a specific resource from a CloudFormation stack.""" + matching_stacks = [] + for stack in response['StackSummaries']: + if partial_stack_name in stack['StackName']: + matching_stacks.append(stack['StackName']) + + if not matching_stacks: + print(f"No matching stacks found for partial name: {partial_stack_name}") + return None + elif len(matching_stacks) > 1: + print(f"WARNING: Found multiple stacks sharing the same partial name: {partial_stack_name}.") + return None + else: + return matching_stacks[0] + + +def _get_resource_from_stack(partial_stack_name: str, resource_type: str) -> Optional[str]: + """ + Retrieve a specific resource from a CloudFormation stack. + + Args: + partial_stack_name (str): The partial name of the CloudFormation stack. + resource_type (str): The type of resource to retrieve (e.g., "AWS::IAM::Role"). + + Returns: + Optional[str]: The physical resource ID if found, otherwise None. + """ + session = boto3.Session() cloudformation = session.client("cloudformation") + + # Get the full stack name from the partial name + stack_name = _get_stack_name(partial_stack_name) + if not stack_name: + logger.warning(f"No stack found with partial name: {partial_stack_name}") + return None + try: response = cloudformation.describe_stack_resources(StackName=stack_name) for resource in response["StackResources"]: @@ -56,6 +109,7 @@ def _get_resource_from_stack(stack_name: str, resource_type: str) -> Optional[st def _get_s3_bucket_from_stack(stack_name: str) -> Optional[str]: """Retrieve the S3 bucket name from a CloudFormation stack.""" + breakpoint() return _get_resource_from_stack(stack_name, "AWS::S3::Bucket") diff --git a/.tools/test/stacks/plugin/typescript/cdk.context.json b/.tools/test/stacks/plugin/typescript/cdk.context.json index 242969d1468..a88fad9fb6b 100644 --- a/.tools/test/stacks/plugin/typescript/cdk.context.json +++ b/.tools/test/stacks/plugin/typescript/cdk.context.json @@ -121,6 +121,18 @@ 31885, 31885, 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, + 31885, 31885 ], "vpc-provider:account=808326389482:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index 89a195e0950..58603896fb4 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -24,7 +24,7 @@ class PluginStack extends cdk.Stack { private adminAccountId: string; private batchMemory: string; private batchVcpus: string; - // private batchStorage: number; + private batchStorage: string; constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); @@ -44,7 +44,7 @@ class PluginStack extends cdk.Stack { // https://docs.aws.amazon.com/batch/latest/APIReference/API_ResourceRequirement.html this.batchMemory = acctConfig[`${toolName}`]?.memory ?? "16384"; // MiB this.batchVcpus = acctConfig[`${toolName}`]?.vcpus ?? "4"; // CPUs - // this.batchStorage = acctConfig[`${toolName}`]?.storage ?? 20; // GiB + this.batchStorage = acctConfig[`${toolName}`]?.storage ?? "21"; // GiB } const [jobDefinition, jobQueue] = this.initBatchFargate(); @@ -140,9 +140,9 @@ class PluginStack extends cdk.Stack { value: this.batchMemory, }, ], - // ephemeralStorage: { - // sizeInGib: this.batchStorage, - // }, + ephemeralStorage: { + sizeInGiB: +this.batchStorage, + }, environment: variableConfigJson, }, platformCapabilities: ["FARGATE"], From 097f82eb83ea8056b9d74c432628bb2aa7ca2b60 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 11 Dec 2024 11:38:10 -0500 Subject: [PATCH 14/26] npm i prettier #--save-dev and --save-exact are recommended npm i prettier #--save-dev and --save-exact are recommended ' --- .tools/test/stacks/deploy.py | 2 +- .../nuke/typescript/create_account_alias.py | 2 +- .../nuke/typescript/upload_job_scripts.py | 21 +++++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index 3dd9a88829c..12935215bde 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -36,7 +36,7 @@ import subprocess import time from pathlib import Path -from typing import Dict, List, Optional, Any, Union +from typing import Any, Dict, List, Optional, Union import boto3 import yaml diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py index 16220768705..a0b70135fcd 100644 --- a/.tools/test/stacks/nuke/typescript/create_account_alias.py +++ b/.tools/test/stacks/nuke/typescript/create_account_alias.py @@ -6,8 +6,8 @@ """ import logging -import subprocess import re +import subprocess from typing import Optional logger = logging.getLogger(__name__) diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index cb6b79c7795..773c72b3c4b 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -13,7 +13,7 @@ import json import logging import os -from typing import Optional, Dict +from typing import Dict, Optional import boto3 from botocore.exceptions import ClientError @@ -39,6 +39,7 @@ # AWS session session = boto3.Session(region_name=REGION) + def _get_stack_name(partial_stack_name: str) -> Optional[str]: """ Retrieves the full stack name from a partial stack name. @@ -54,29 +55,31 @@ def _get_stack_name(partial_stack_name: str) -> Optional[str]: try: # List all stacks with status CREATE_COMPLETE - response = cloudformation.list_stacks( - StackStatusFilter=['CREATE_COMPLETE'] - ) + response = cloudformation.list_stacks(StackStatusFilter=["CREATE_COMPLETE"]) except ClientError as e: print(f"Error listing stacks: {e}") return None matching_stacks = [] - for stack in response['StackSummaries']: - if partial_stack_name in stack['StackName']: - matching_stacks.append(stack['StackName']) + for stack in response["StackSummaries"]: + if partial_stack_name in stack["StackName"]: + matching_stacks.append(stack["StackName"]) if not matching_stacks: print(f"No matching stacks found for partial name: {partial_stack_name}") return None elif len(matching_stacks) > 1: - print(f"WARNING: Found multiple stacks sharing the same partial name: {partial_stack_name}.") + print( + f"WARNING: Found multiple stacks sharing the same partial name: {partial_stack_name}." + ) return None else: return matching_stacks[0] -def _get_resource_from_stack(partial_stack_name: str, resource_type: str) -> Optional[str]: +def _get_resource_from_stack( + partial_stack_name: str, resource_type: str +) -> Optional[str]: """ Retrieve a specific resource from a CloudFormation stack. From c1dcd983c96713f678f1489a5cc3f9f5003cd67c Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 11 Dec 2024 11:40:01 -0500 Subject: [PATCH 15/26] removing files, tslinting" --- package-lock.json | 27 --------------------------- package.json | 5 ----- 2 files changed, 32 deletions(-) delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 95d353a688c..00000000000 --- a/package-lock.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "aws-doc-sdk-examples", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "prettier": "^3.2.5" - } - }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 44701acbd6a..00000000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "devDependencies": { - "prettier": "^3.2.5" - } -} From f2b5b544e0f454c68b20f6f247df4fc3071a7c80 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 11 Dec 2024 12:43:29 -0500 Subject: [PATCH 16/26] PR review --- .tools/test/stacks/deploy.py | 2 + .tools/test/stacks/nuke/typescript/README.md | 23 +----- .../nuke/typescript/create_account_alias.py | 3 +- .../stacks/nuke/typescript/nuke_cleanser.ts | 2 + .../nuke/typescript/nuke_generic_config.yaml | 70 ------------------ .../stacks/nuke/typescript/trigger_dry_run.py | 74 ------------------- .../nuke/typescript/upload_job_scripts.py | 2 + .../stacks/plugin/typescript/plugin_stack.ts | 2 +- 8 files changed, 11 insertions(+), 167 deletions(-) delete mode 100644 .tools/test/stacks/nuke/typescript/trigger_dry_run.py diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index 12935215bde..76215458abb 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """ AWS CDK Deployment Script for SDK Testing Infrastructure diff --git a/.tools/test/stacks/nuke/typescript/README.md b/.tools/test/stacks/nuke/typescript/README.md index 8ed6ae657d0..2049a8ef674 100644 --- a/.tools/test/stacks/nuke/typescript/README.md +++ b/.tools/test/stacks/nuke/typescript/README.md @@ -25,12 +25,7 @@ The code in this repository helps you set up the following architecture: ## Prerequisites 1. **AWS-Nuke Binary**: Open-source library from [ekristen](https://github.com/ekristen/aws-nuke) staged in S3. -2. **AWS Account Alias**: Must exist in the IAM Dashboard for the target account. -3. **AWS CodeBuild**: Required for runtime and compute. -4. **AWS S3 Bucket**: Stores the nuke config file and AWS-Nuke binary (latest version). -5. **AWS Step Functions**: Orchestrates multi-region parallel CodeBuild invocations. -6. **AWS SNS Topic**: Sends daily resource cleanup reports and CodeBuild job statuses. -7. **AWS EventBridge Rule**: Cron-based schedule to trigger the workflow with region inputs. +2. **AWS Account Alias**: Must exist in the IAM Dashboard for the target account. 8. **Network Connectivity**: Ensure VPC allows downloads from GitHub or stage the binary in S3/artifactory for restricted environments. ## Setup and Installation @@ -58,21 +53,7 @@ arn:aws:cloudformation:us-east-1:123456788985:stack/NukeCleanser/cfhdkiott-acec- Next, run `python upload_job_files.py` to upload two files in this directory: `nuke_config_update.py` and `nuke_generic_config.yaml`. -## Testing -To test this stack, run `python trigger_dry_run.py` which will invoke the state machine execution in dry-run mode using the following payload: -```sh -{ - "InputPayLoad": { - "nuke_dry_run": "true", - "nuke_version": "2.21.2", - "region_list": [ - "us-east-1" - ] - } -} -``` - -## Other notes: +## When it runs * The tool is currently configured to run at a schedule as desired typically off hours 3:00a EST. It can be easily configured with a rate() or cron() expression by editing the cfn template file * The workflow also sends out a detailed report to an SNS topic with an active email subscription on what resources were deleted after the job is successful for each region which simplifies traversing and parsing the complex logs spit out by the aws-nuke binary. diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py index a0b70135fcd..c5856c5fd1f 100644 --- a/.tools/test/stacks/nuke/typescript/create_account_alias.py +++ b/.tools/test/stacks/nuke/typescript/create_account_alias.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """ This module is used to create an AWS account alias, which is required by the deploy.py script. @@ -8,7 +10,6 @@ import logging import re import subprocess -from typing import Optional logger = logging.getLogger(__name__) diff --git a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts index 1e947030d48..898aa85d781 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts +++ b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts @@ -1,4 +1,6 @@ #!/usr/bin/env node +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 import * as cdk from 'aws-cdk-lib'; import * as codebuild from 'aws-cdk-lib/aws-codebuild'; import * as events from 'aws-cdk-lib/aws-events'; diff --git a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml index 38ea20617d1..7e1f3cc00f5 100644 --- a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml +++ b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml @@ -92,46 +92,6 @@ accounts: - property: SecurityGroupId type: glob value: "*" -# SQSQueue: -# - property: "QueueURL" -# type: "glob" -# value: "*PluginStack-*" -# SQSQueuePolicy: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# SNSSubscription: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# IAMRole: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# IAMPolicy: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# SecurityGroup: -# - property: "group-name" -# type: "glob" -# value: "*PluginStack-*" -# BatchComputeEnvironment: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# BatchJobDefinition: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# BatchJobQueue: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# LambdaFunction: -# - property: "Name" -# type: "glob" -# value: "*PluginStack-*" LambdaEventSourceMapping: - property: "EventSourceArn" type: "glob" @@ -139,40 +99,10 @@ accounts: - property: "FunctionArn" type: "glob" value: "*PluginStack-*" -# S3Bucket: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# S3BucketPolicy: -# - property: "bucket-name" -# type: "glob" -# value: "*PluginStack-*" -# EventRule: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# - property: "arn" -# type: "glob" -# value: "*:events:us-east-1:*:rule/PluginStack-*" LambdaPermission: - property: "name" type: "glob" value: "*PluginStack-*" -# CDKMetadata: -# - property: "name" -# type: "glob" -# value: "*PluginStack-*" -# CloudWatchEventsRule: -# - property: "Name" -# type: "glob" -# value: "PluginStack-*" -# CloudWatchEventsTarget: -# - property: "Rule" -# type: "glob" -# value: "*PluginStack-*" -# - property: "Target ID" -# type: "glob" -# value: "*PluginStack-*" GuardDutyDetector: - property: DetectorID type: glob diff --git a/.tools/test/stacks/nuke/typescript/trigger_dry_run.py b/.tools/test/stacks/nuke/typescript/trigger_dry_run.py deleted file mode 100644 index 63f0bcacbec..00000000000 --- a/.tools/test/stacks/nuke/typescript/trigger_dry_run.py +++ /dev/null @@ -1,74 +0,0 @@ -import json - -import boto3 - - -def get_step_function_arn(stack_name): - """ - Retrieve the ARN of the Step Function from the NukeCleanser CloudFormation stack. - - :param stack_name: The name of the CloudFormation stack. - :return: The ARN of the Step Function, or None if not found. - """ - cloudformation = boto3.client("cloudformation") - - try: - response = cloudformation.describe_stack_resources(StackName=stack_name) - resources = response["StackResources"] - - for resource in resources: - if resource["ResourceType"] == "AWS::StepFunctions::StateMachine": - return resource["PhysicalResourceId"] - - print(f"No Step Function found in stack '{stack_name}'.") - return None - - except cloudformation.exceptions.ClientError as e: - print(f"Error describing stack: {e}") - return None - - -def trigger_step_function(step_function_arn, payload): - """ - Trigger a Step Functions execution with the given input payload. - - :param step_function_arn: The ARN of the Step Function to trigger. - :param payload: The input payload to pass to the Step Function execution. - """ - stepfunctions = boto3.client("stepfunctions") - - try: - response = stepfunctions.start_execution( - stateMachineArn=step_function_arn, input=json.dumps(payload) - ) - print(f"Step Function triggered successfully.") - print(f"Execution ARN: {response['executionArn']}") - return response["executionArn"] - - except stepfunctions.exceptions.ClientError as e: - print(f"Error triggering Step Function: {e}") - return None - - -if __name__ == "__main__": - # CloudFormation stack name - stack_name = "NukeCleanser" - - # Input payload - input_payload = { - "InputPayLoad": { - "nuke_dry_run": "true", - "nuke_version": "2.21.2", - "region_list": ["us-east-1"], - } - } - - # Get the ARN of the Step Function from the stack - step_function_arn = get_step_function_arn(stack_name) - - if step_function_arn: - print(f"Found Step Function ARN: {step_function_arn}") - # Trigger the Step Function - trigger_step_function(step_function_arn, input_payload) - else: - print(f"Failed to find a Step Function in stack '{stack_name}'.") diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py index 773c72b3c4b..e0d83683399 100644 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 """ This module is part of the AccountNuker stack deployment process. It's invoked by the stacks/deploy.py script to support integration testing. diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index 58603896fb4..66dba0ac63c 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -44,7 +44,7 @@ class PluginStack extends cdk.Stack { // https://docs.aws.amazon.com/batch/latest/APIReference/API_ResourceRequirement.html this.batchMemory = acctConfig[`${toolName}`]?.memory ?? "16384"; // MiB this.batchVcpus = acctConfig[`${toolName}`]?.vcpus ?? "4"; // CPUs - this.batchStorage = acctConfig[`${toolName}`]?.storage ?? "21"; // GiB + this.batchStorage = acctConfig[`${toolName}`]?.storage ?? "30"; // GiB } const [jobDefinition, jobQueue] = this.initBatchFargate(); From 3be0885cc068cc88ff2485e9058c01d64e1574c2 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 11 Dec 2024 12:44:22 -0500 Subject: [PATCH 17/26] newline missing --- .tools/test/stacks/config/targets.yaml | 2 +- .tools/test/stacks/plugin/typescript/plugin_stack.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tools/test/stacks/config/targets.yaml b/.tools/test/stacks/config/targets.yaml index 8cb784d2b8b..7686219e298 100644 --- a/.tools/test/stacks/config/targets.yaml +++ b/.tools/test/stacks/config/targets.yaml @@ -36,4 +36,4 @@ sapabap: status: "enabled" swift: account_id: "637397754108" - status: "enabled" \ No newline at end of file + status: "enabled" diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index 66dba0ac63c..6c1985d71fd 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -352,4 +352,4 @@ new PluginStack( }, ); -app.synth(); \ No newline at end of file +app.synth(); From 9a019a7090f5a549fd5290d79fd695bbaf0d225c Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Wed, 20 Nov 2024 12:49:54 -0500 Subject: [PATCH 18/26] added nuke --- .tools/test/stacks/aws-nuke | 1 + .tools/test/stacks/plugin/typescript/plugin_stack.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 160000 .tools/test/stacks/aws-nuke diff --git a/.tools/test/stacks/aws-nuke b/.tools/test/stacks/aws-nuke new file mode 160000 index 00000000000..5d59d89fbdf --- /dev/null +++ b/.tools/test/stacks/aws-nuke @@ -0,0 +1 @@ +Subproject commit 5d59d89fbdf6851d848fbaa9c9196970ad898e48 diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index 617995f8fbf..d0eca097ec5 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -116,7 +116,7 @@ class PluginStack extends cdk.Stack { securityGroupIds: [sg.securityGroupId], maxvCpus: 1, }, - }, + } ); const containerImageUri = `${this.adminAccountId}.dkr.ecr.us-east-1.amazonaws.com/${toolName}:latest`; @@ -141,7 +141,7 @@ class PluginStack extends cdk.Stack { }, ], ephemeralStorage: { - sizeInGiB: this.batchStorage, + sizeInGib: this.batchStorage, }, environment: variableConfigJson, }, @@ -314,7 +314,7 @@ class PluginStack extends cdk.Stack { // Define the Lambda function. const lambdaFunction = new lambda.Function(this, "BatchJobCompleteLambda", { - runtime: lambda.Runtime.PYTHON_3_9, + runtime: lambda.Runtime.PYTHON_3_8, handler: "export_logs.handler", role: executionRole, code: lambda.Code.fromAsset("lambda"), From 1976a6d925eb1d3b4e51e79463b3f639971abe40 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 12 Dec 2024 11:19:54 -0500 Subject: [PATCH 19/26] moved to different PR --- package-lock.json | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..95d353a688c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "aws-doc-sdk-examples", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "^3.2.5" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} From 569849105e52d187fb11e3a9e8242d18c902798b Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 12 Dec 2024 11:22:04 -0500 Subject: [PATCH 20/26] moved to different PR --- .tools/test/stacks/images/scripts/delete_repository.py | 3 +-- .tools/test/stacks/plugin/python/lambda/export_logs.py | 4 ++-- package.json | 5 +++++ 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 package.json diff --git a/.tools/test/stacks/images/scripts/delete_repository.py b/.tools/test/stacks/images/scripts/delete_repository.py index 4cd6e89b91c..687388211d5 100644 --- a/.tools/test/stacks/images/scripts/delete_repository.py +++ b/.tools/test/stacks/images/scripts/delete_repository.py @@ -1,11 +1,10 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +import boto3 import logging import sys -import boto3 - def delete_selected_public_ecr_repos(delete_images=False): """ diff --git a/.tools/test/stacks/plugin/python/lambda/export_logs.py b/.tools/test/stacks/plugin/python/lambda/export_logs.py index 9b3085ed082..fff1420edd3 100644 --- a/.tools/test/stacks/plugin/python/lambda/export_logs.py +++ b/.tools/test/stacks/plugin/python/lambda/export_logs.py @@ -1,10 +1,10 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -import csv -import io import json import logging import os +import io +import csv import boto3 diff --git a/package.json b/package.json new file mode 100644 index 00000000000..44701acbd6a --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "prettier": "^3.2.5" + } +} From 049f6b052393d51e8e4c370bd996956a866ad068 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 12 Dec 2024 12:36:20 -0500 Subject: [PATCH 21/26] removing files not in PR --- .tools/test/DEPLOYMENT.md | 132 +++++++------- .tools/test/stacks/deploy.py | 329 ++++++++--------------------------- 2 files changed, 134 insertions(+), 327 deletions(-) diff --git a/.tools/test/DEPLOYMENT.md b/.tools/test/DEPLOYMENT.md index 39c827e2f56..e4557a49928 100644 --- a/.tools/test/DEPLOYMENT.md +++ b/.tools/test/DEPLOYMENT.md @@ -1,100 +1,92 @@ # Deployment Instructions -This repository contains infrastructure deployment scripts for testing SDK example code. The infrastructure is managed through AWS CDK and can be deployed in two ways: -1. [deploy.py](#1-using-the-deploy-script) -2. [invoking the CDK directly](#2-invoking-cdk-directly) +There are two ways to deploy the code in this directory. -## Option 1. Using `deploy.py` +## 1. Using the deploy script -The [deploy.py](stacks/deploy.py) script is the primary method for deploying the infrastructure stacks. - -It is designed to run from MacOS terminal. - -It uses Python's `subprocess` module to execute CDK commands (as CDK doesn't support exist as a Python library). It relies on environment variables (noted in this document) throughout the deployment process. - -The script handles three types of deployments: - -1. **Images Stack** (`images`): - - Creates empty ECR private repositories for all tools listed in [targets.yaml](stacks/config/targets.yaml) - - Users must implement their own image versioning and pushing mechanism - - Example: GitHub Actions with OIDC provider works well for this purpose - -2. **Admin Stack** (`admin`): - - Deploys event emission infrastructure - - Creates IAM policies for cross-account event subscription - - **Required**: Must be deployed before any plugin stacks - - Works with single or multiple accounts listed in [targets.yaml](stacks/config/targets.yaml) - -3. **Plugin Stack** (`plugin`): - - Deploys two stacks to each account in [targets.yaml](stacks/config/targets.yaml): - 1. Plugin stack that subscribes to admin stack events - 2. Account nuker stack that cleans up residual test resources - - Requires `admin` stack to be deployed first +To deploy any stack in this directory, run the [deploy.py](stacks/deploy.py) script. ### Script Prerequisites -- MacOS terminal environment - Python 3.11 installed -- AWS CLI and CDK installed and configured (NodeJS 18+) -- Permissions to execute AWS CDK and shell commands (`AdministratorAccess` will work for non-production test environments) -- Configuration files [resources.yaml](stacks/config/resources.yaml) and [targets.yaml](stacks/config/targets.yaml) -- Environment variables set for: - - `TOKEN_TOOL`: Path to credential management tool - - `TOKEN_PROVIDER`: Identity provider for AWS credentials +- `ada` or equivalent CLI library for fetching AWS credentials. - Dependencies installed in Python virtual environment: + ``` -python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt`` +python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt ``` +- AWS CLI and CDK installed and configured (NodeJS 18+) +- Permissions to execute AWS CDK and shell commands (`AdministratorAccess` will work for non-production test environments) +- Configuration files `resources.yaml` and `targets.yaml`, which exist in the [stacks/config](stacks/config) directory within the same directory as the script + ### Usage #### Command Syntax ```bash -cd stacks ; python deploy.py +cd stacks ; python deploy.py ``` -Replace `` with one of the supported stacks: +Replace `` with one of the supported stacks: - `admin`: Deploys admin-specific resources. - `images`: Deploys image-related resources. - `plugin`: Deploys plugin-specific resources. - - To deploy only a specific language's plugin only, pass `--language ` where `` is an account name in [targets.yaml](stacks/config/targets.yaml). E.g. `python` - -## Technical Notes -This creates some brittleness but provides necessary flexibility for cross-account deployments + - To deploy only a specific language's plugin, pass `--language ` where language is an account in [targets.yaml](stacks/config/targets.yaml). #### Additional Notes -Some non-obvious quirks of the script include: - - programmatic file traversing to the required CDK directory based on the type and language of CDK deployment (`typescript` is the default). - - a random-seeming sleep period after deployment to avoid conflicts with the previous CDK operation that may have not killed its thread yet. - - more generally, extensive use of the `subprocess` module which creates some acceptable brittleness that may result in future regression. + +The script automatically navigates to the required directory based on the type and language of deployment (typescript is the default). + +Environment variables are set and used during the deployment process. + +Errors during command execution are caught and displayed. + +The script includes a sleep period after deployment to avoid conflicts with simultaneous CDK operations. + +Make sure to check the script's output for any errors or confirmation messages that indicate the deployment's success or failure. Adjust the config files as necessary to match your deployment requirements. + --- -## Option 2. Invoking CDK directly +## 2. Invoking CDK directly + +The second option involves navigating to each stack directory and running the CDK commands. + +The following instructions assume a "plugin account" (the AWS account where testing activities will occur) of "python" (corresponding to a Docker image) per [this repository's configuration](config/targets.yaml). +You can replace Python with any of the other languages listed in this repository's configuration. -This option involves navigating to each stack directory([images](stacks/images), [admin](stacks/admin), or [plugin](stacks/plugin)) and running the `cdk` commands explained below. +To request an alternate configuration for your own repository or use case, please [submit an issue](https://github.com/awsdocs/aws-doc-sdk-examples/issues/new?labels=type%2Fenhancement&labels=Tools&title=%5BEnhancement%5D%3A+Weathertop+Customization+Request&&) with the `Tools` label. -Required steps for all stack types: -1. Set Python virtualenv within [plugin directory](stacks/plugin/admin). -1. Get AWS account tokens for target account. -1. Run `cdk bootstrap` and `cdk deploy`. +### 1. Deploy Plugin Stack for your language (e.g. Python) -### Special details for `plugin` type -For the `plugin` type, there are a few important details: -1. User must also run `export LANGUAGE_NAME=python` if your tool is `python`. -1. For the stack to begin accepting test events, you must set `status` to `enabled` for your tool (e.g. `python`) in [targets.yaml](stacks/config/targets.yaml) and redeploy the `admin` stack. -1. To manually trigger test runs, [submit a test job](#submit-test-job) in AWS Batch. +User will: -## Testing & Validation -Users can trigger test runs from within the AWS Console after deploying the `plugin` stack for their chosen tool. +1. Set Python virtualenv within [plugin directory](plugin/admin). +1. `export LANGUAGE_NAME=python`. +1. Get AWS account tokens for plugin account. +1. `cdk bootstrap` and `cdk deploy`. -### Submit test job +### 2. Enable Consumer Stack to receive event notifications -Users can trigger test runs from within the AWS Console after deploying the `plugin` stack for their chosen tool. +User will: -Steps: -1. Log into console for tool AWS account (e.g. `python`) +1. Set `status` to `enabled` in [targets.yaml](config/targets.yaml) for your language +1. Raise PR. + +Admin will: + +1. Approve and merge PR. +1. Set Python virtualenv within [admin directory](stacks/admin). +1. Get Admin account tokens. +1. `cdk bootstrap` and `cdk deploy`. +1. Request that user [submit a test job](#3-submit-test-job). + +### 3. Submit test job + +User will: + +1. Log into console for Python account 1. Navigate to "Job Definitions". ![](docs/validation-flow-1.jpg) 1. Click "Submit Job". @@ -105,12 +97,10 @@ Steps: ![](docs/validation-flow-4.jpg) 1. Click "Create job". ![](docs/validation-flow-5.jpg) -1. [Validate results of test job](#view-test-run-results) - -### View test run results -1. Log into console for tool AWS account (e.g. `python`) -1. Click `Jobs` and select the only job queue. -2. Toggle `Load all jobs`. -1. View job details by clicking the hyperlinked value in the `Name` field. -2. When status is `SUCCEEDED` or `FAILED`, click "Logging" tab. +1. [Validate results of test job](#3-optional-view-test-job-results) + +### 3. Optional: View CloudWatch job results in Batch + +1. Navigate to a job +1. When status is `SUCCEEDED` or `FAILED`, click "Logging" tab. ![](docs/validation-flow-6.jpg) diff --git a/.tools/test/stacks/deploy.py b/.tools/test/stacks/deploy.py index 76215458abb..bd0d6474bfd 100644 --- a/.tools/test/stacks/deploy.py +++ b/.tools/test/stacks/deploy.py @@ -1,327 +1,144 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -""" -AWS CDK Deployment Script for SDK Testing Infrastructure - -This script manages the deployment of AWS infrastructure components needed for SDK testing. -It handles three main deployment scenarios: - -1. ECR Repository Setup (--type images): - Creates empty ECR private repositories for tools listed in targets.yaml - -2. Admin Stack Deployment (--type admin): - Deploys a stack that emits events and contains IAM policies allowing - cross-account subscription from AWS accounts listed in targets.yaml - -3. Plugin Stack Deployment (--type plugin): - Deploys two stacks in each target account: - - A plugin stack that subscribes to the admin stack's events - - An account nuker stack that cleans up resources left by test executions - -Prerequisites: - - TOKEN_TOOL environment variable set to the token generation tool path - - TOKEN_PROVIDER environment variable set to the token provider - - Appropriate AWS credentials and permissions - - Valid configuration in config/resources.yaml and config/targets.yaml - -Note: - This script uses subprocess to run CDK commands as CDK doesn't support - direct module import. While this creates some brittleness, it provides - necessary flexibility for cross-account deployments. -""" import argparse -import logging -import os -import re -import shutil import subprocess -import time -from pathlib import Path -from typing import Any, Dict, List, Optional, Union - -import boto3 +import os import yaml -from botocore.exceptions import ClientError, NoCredentialsError - -from nuke.typescript.create_account_alias import create_account_alias -from nuke.typescript.upload_job_scripts import process_stack_and_upload_files - -# Constants -AWS_DEFAULT_REGION = "us-east-1" -CDK_DEPLOYMENT_ROLE = "weathertop-cdk-deployments" -ACCOUNT_ALIAS = "weathertop-test" -CDK_ACKNOWLEDGE_ID = "31885" - -# Configure logging -logger = logging.getLogger(__name__) -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) - - -class DeploymentError(Exception): - """Custom exception for deployment-related errors.""" - - pass - - -class ConfigurationManager: - """Manages loading and validation of configuration files.""" - - def __init__(self, config_dir: Path = Path("config")): - self.config_dir = config_dir - - def load_admin_config(self) -> Dict[str, Dict[str, str]]: - """Load admin configuration from resources.yaml.""" - try: - with open(self.config_dir / "resources.yaml") as file: - data = yaml.safe_load(file) - return { - "admin": { - "account_id": str(data["admin_acct"]), - "status": "enabled", - } - } - except Exception as e: - logger.error(f"Failed to read admin config: {e}") - raise - - def load_target_accounts(self) -> Dict[str, Any]: - """Load target accounts from targets.yaml.""" - try: - with open(self.config_dir / "targets.yaml") as file: - return yaml.safe_load(file) - except Exception as e: - logger.error(f"Failed to read targets config: {e}") - raise - - -def get_caller_identity() -> None: - """ - Get the caller identity from AWS STS. - - Logs the account ID, ARN, and user ID of the caller. - Logs an error if no credentials are found or if there is a client error. - """ - try: - session = boto3.Session() - sts_client = session.client("sts") - caller_identity = sts_client.get_caller_identity() - - logger.info(f"Credentials Account ID: {caller_identity['Account']}") - logger.debug(f"Arn: {caller_identity['Arn']}") - logger.debug(f"UserId: {caller_identity['UserId']}") - - except NoCredentialsError: - logger.info("No credentials found in shared folder. Credentials wiped!") - except ClientError as e: - logger.error(f"An error occurred: {e}") - - -def delete_aws_directory() -> None: - """ - Delete the .aws directory in the user's home directory. - - This function removes the .aws directory and all its contents from the user's - home directory. If the directory does not exist, it logs a message. - """ - aws_dir = Path.home() / ".aws" - - if aws_dir.exists(): - try: - shutil.rmtree(aws_dir) - logger.info(f"Deleted all contents under {aws_dir}.") - except Exception as e: - logger.error(f"Error deleting {aws_dir}: {e}") - else: - logger.info(f"{aws_dir} does not exist.") +import time +import re -def run_shell_command( - command: List[str], env_vars: Optional[Dict[str, str]] = None -) -> None: +def run_shell_command(command, env_vars=None): """ - Execute a given shell command securely and log its output. + Execute a given shell command securely and return its output. Args: - command: The command and its arguments listed as separate items. - env_vars: Additional environment variables to set for the command. + command (list): The command and its arguments listed as separate items. + env_vars (dict, optional): Additional environment variables to set for the command. - Raises: - subprocess.CalledProcessError: If the command execution fails. + Outputs the result of the command execution to the console. In case of an error, + it outputs the error message and the stack trace. """ + # Prepare the environment env = os.environ.copy() if env_vars: env.update(env_vars) command_str = " ".join(command) - logger.info(f"COMMAND: {command_str}") - + print("COMMAND: " + command_str) try: output = subprocess.check_output(command, stderr=subprocess.STDOUT, env=env) - logger.info(f"STDOUT:\n{output.decode()}") + print(f"Command output: {output.decode()}") except subprocess.CalledProcessError as e: - logger.error(f"Error executing command: {e.output.decode()}") + print(f"Error executing command: {e.output.decode()}") raise except Exception as e: - logger.error(f"Exception executing command: {e!r}") + print(f"Exception executing command: {e!r}") raise -def validate_alphanumeric(value: str, name: str) -> None: +def validate_alphanumeric(value, name): """ Validate that the given value is alphanumeric. Args: - value: The value to validate. - name: The name of the variable for error messages. + value (str): The value to validate. + name (str): The name of the variable for error messages. Raises: - ValueError: If the value is not alphanumeric. + ValueError: If the value is not alphanumeric. """ if not re.match(r"^\w+$", value): raise ValueError(f"{name} must be alphanumeric. Received: {value}") -def get_tokens(account_id: str) -> None: +def deploy_resources(account_id, account_name, dir, lang="typescript"): """ - Get AWS tokens for the specified account. + Deploy resources to a specified account using configuration specified by directory and language. Args: - account_id: The AWS account ID for which tokens will be obtained. + account_id (str): The AWS account ID where resources will be deployed. + account_name (str): A human-readable name for the account, used for environment variables. + dir (str): The base directory containing deployment scripts or configurations. + lang (str, optional): The programming language of the deployment scripts. Defaults to 'typescript'. + + Changes to the desired directory, sets up necessary environment variables, and executes + deployment commands. """ - get_token_tool = os.getenv("TOKEN_TOOL") - get_token_provider = os.getenv("TOKEN_PROVIDER") + validate_alphanumeric(account_id, "account_id") + validate_alphanumeric(account_name, "account_name") - if not all([get_token_tool, get_token_provider]): - raise DeploymentError( - "TOKEN_TOOL and TOKEN_PROVIDER environment variables must be set" - ) + if dir not in os.getcwd(): + os.chdir(f"{dir}/{lang}") + # Securely update tokens get_tokens_command = [ - get_token_tool, + "ada", "credentials", "update", "--account", account_id, "--provider", - get_token_provider, + "isengard", "--role", - CDK_DEPLOYMENT_ROLE, + "weathertop-cdk-deployments", "--once", ] run_shell_command(get_tokens_command) - os.environ["AWS_DEFAULT_REGION"] = AWS_DEFAULT_REGION - get_caller_identity() - - -def deploy_resources( - account_id: str, - account_name: str, - dir_path: Union[str, Path], - lang: str = "typescript", -) -> None: - """ - Deploy resources to a specified account using configuration specified by directory and language. - Args: - account_id: The AWS account ID where resources will be deployed. - account_name: A human-readable name for the account, used for environment variables. - dir_path: The base directory containing deployment scripts or configurations. - lang: The programming language of the deployment scripts. - """ - validate_alphanumeric(account_id, "account_id") - validate_alphanumeric(account_name, "account_name") - - if dir_path not in os.getcwd(): - os.chdir(os.path.join(dir_path, lang)) - - run_shell_command(["cdk", "acknowledge", CDK_ACKNOWLEDGE_ID]) + # Deploy using CDK deploy_command = ["cdk", "deploy", "--require-approval", "never"] + print(" ".join(deploy_command)) run_shell_command(deploy_command, env_vars={"TOOL_NAME": account_name}) # Delay to avoid CLI conflicts - # TODO: Replace with proper waiter implementation + # TODO: Add waiter time.sleep(15) - delete_aws_directory() - get_caller_identity() +def main(): + parser = argparse.ArgumentParser(description="admin, images, or plugin stack.") + parser.add_argument("type", choices=["admin", "images", "plugin"]) + parser.add_argument("--language") + args = parser.parse_args() -def deploy_stacks( - stack_type: str, accounts: Dict[str, Any], language: Optional[str] -) -> None: - """ - Deploy the specified stack type to all target accounts. + accounts = None - Args: - stack_type: Type of stack to deploy (admin, images, or plugin) - accounts: Dictionary of account configurations - language: Optional specific language to deploy for - """ - items = [(language, accounts[language])] if language else accounts.items() + if args.type in {"admin", "images"}: + try: + with open("config/resources.yaml", "r") as file: + data = yaml.safe_load(file) + accounts = { + "admin": { + "account_id": f"{data['admin_acct']}", + "status": "enabled", + } + } + except Exception as e: + print(f"Failed to read config data: \n{e}") + elif args.type in {"plugin"}: + try: + with open("config/targets.yaml", "r") as file: + accounts = yaml.safe_load(file) + except Exception as e: + print(f"Failed to read config data: \n{e}") + + if accounts is None: + raise ValueError(f"Could not load accounts for stack {args.type}") + if args.language: + items = [(args.language, accounts[args.language])] + else: + items = accounts.items() + for account_name, account_info in items: - logger.info( - f"\n\n\n\n #### NEW DEPLOYMENT #### \n\n\n\n" - f"Deploying 🚀 {stack_type} stack to account {account_name}" - f" with ID {account_info['account_id']}" - ) - - get_tokens(account_info["account_id"]) - deploy_resources(account_info["account_id"], account_name, stack_type) - - if stack_type == "plugin": - logger.info( - f"Deploying ☢️ AWS-Nuke to account {account_name}" - f" with ID {account_info['account_id']}" - ) - os.chdir("../..") - - get_tokens(account_info["account_id"]) - create_account_alias(ACCOUNT_ALIAS) - - get_tokens(account_info["account_id"]) - deploy_resources(account_info["account_id"], account_name, "nuke") - get_tokens(account_info["account_id"]) - process_stack_and_upload_files() - - os.chdir("../..") - - -def main() -> None: - """Execute the main deployment workflow.""" - parser = argparse.ArgumentParser( - description="Deploy admin, images, or plugin stack.", - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - parser.add_argument( - "type", choices=["admin", "images", "plugin"], help="Type of stack to deploy" - ) - parser.add_argument("--language", help="Specific language to deploy for") - args = parser.parse_args() - - config_manager = ConfigurationManager() - - try: - accounts = ( - config_manager.load_admin_config() - if args.type in {"admin", "images"} - else config_manager.load_target_accounts() + print( + f"Reading from account {account_name} with ID {account_info['account_id']}" ) - - if not accounts: - raise DeploymentError(f"No accounts found for stack type: {args.type}") - - deploy_stacks(args.type, accounts, args.language) - - except Exception as e: - logger.error(f"Deployment failed: {e}") - raise + deploy_resources(account_info["account_id"], account_name, args.type) if __name__ == "__main__": - os.environ["JSII_SILENCE_WARNING_UNTESTED_NODE_VERSION"] = "true" main() From a2321d6851882a5bb3ed1a50345b9427411ed6d0 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 12 Dec 2024 12:37:22 -0500 Subject: [PATCH 22/26] no nuke --- .tools/test/stacks/aws-nuke | 1 - 1 file changed, 1 deletion(-) delete mode 160000 .tools/test/stacks/aws-nuke diff --git a/.tools/test/stacks/aws-nuke b/.tools/test/stacks/aws-nuke deleted file mode 160000 index 5d59d89fbdf..00000000000 --- a/.tools/test/stacks/aws-nuke +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5d59d89fbdf6851d848fbaa9c9196970ad898e48 From 3af2c2616e2548283d94088bbe3553adedf9bb04 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 12 Dec 2024 12:38:16 -0500 Subject: [PATCH 23/26] no nuke --- .tools/test/stacks/nuke/typescript/README.md | 113 -- .../nuke/typescript/architecture-overview.png | Bin 93022 -> 0 bytes .../stacks/nuke/typescript/cdk.context.json | 92 -- .tools/test/stacks/nuke/typescript/cdk.json | 81 -- .../nuke/typescript/create_account_alias.py | 96 -- .../stacks/nuke/typescript/nuke_cleanser.ts | 667 --------- .../nuke/typescript/nuke_config_update.py | 194 --- .../nuke/typescript/nuke_generic_config.yaml | 173 --- .../stacks/nuke/typescript/package-lock.json | 1202 ----------------- .../test/stacks/nuke/typescript/package.json | 27 - .../test/stacks/nuke/typescript/tsconfig.json | 31 - .../nuke/typescript/upload_job_scripts.py | 181 --- 12 files changed, 2857 deletions(-) delete mode 100644 .tools/test/stacks/nuke/typescript/README.md delete mode 100644 .tools/test/stacks/nuke/typescript/architecture-overview.png delete mode 100644 .tools/test/stacks/nuke/typescript/cdk.context.json delete mode 100644 .tools/test/stacks/nuke/typescript/cdk.json delete mode 100644 .tools/test/stacks/nuke/typescript/create_account_alias.py delete mode 100644 .tools/test/stacks/nuke/typescript/nuke_cleanser.ts delete mode 100644 .tools/test/stacks/nuke/typescript/nuke_config_update.py delete mode 100644 .tools/test/stacks/nuke/typescript/nuke_generic_config.yaml delete mode 100644 .tools/test/stacks/nuke/typescript/package-lock.json delete mode 100644 .tools/test/stacks/nuke/typescript/package.json delete mode 100644 .tools/test/stacks/nuke/typescript/tsconfig.json delete mode 100644 .tools/test/stacks/nuke/typescript/upload_job_scripts.py diff --git a/.tools/test/stacks/nuke/typescript/README.md b/.tools/test/stacks/nuke/typescript/README.md deleted file mode 100644 index 2049a8ef674..00000000000 --- a/.tools/test/stacks/nuke/typescript/README.md +++ /dev/null @@ -1,113 +0,0 @@ - -# aws-nuke for Weathertop - -AWS Nuke is an open source tool created by [ekristen](https://github.com/ekristen/aws-nuke). - -It searches for deleteable resources in the provided AWS account and deletes those which are not considered "Default" or "AWS-Managed", taking your account back to Day 1 with few exceptions. - - -## ⚠ Important -This is a very destructive tool! It should not be deployed without fully understanding the impact it will have on your AWS accounts. -Please use caution and configure this tool to delete unused resources only in your lower test/sandbox environment accounts. - -## Overview - -The code in this repository helps you set up the following architecture: - -![infrastructure-overview](architecture-overview.png) - -## Feature Outline - -1. **Scheduled Trigger**: Amazon EventBridge invokes AWS Step Functions daily. -2. **Regional Scalability**: Runs AWS CodeBuild projects per region. -4. **Custom Config**: Pulls resource filters and region targets in [nuke_generic_config.yaml](nuke_generic_config.yaml). - -## Prerequisites - -1. **AWS-Nuke Binary**: Open-source library from [ekristen](https://github.com/ekristen/aws-nuke) staged in S3. -2. **AWS Account Alias**: Must exist in the IAM Dashboard for the target account. -8. **Network Connectivity**: Ensure VPC allows downloads from GitHub or stage the binary in S3/artifactory for restricted environments. - -## Setup and Installation - -* Clone the [repo](https://github.com/ekristen/aws-nuke). -* Determine the `id` of the account to be deployed for nuking. -* Narrow [filters](nuke_generic_config.yaml) for the resources/accounts you wish to nuke. -* Deploy the stack using the below command. You can run it in any desired region. -```sh -cdk bootstrap && cdk deploy -``` - -Note a successful stack creation, e.g.: - -```bash - ✅ NukeCleanser - -✨ Deployment time: 172.66s - -Outputs: -NukeCleanser.NukeS3BucketValue = nuke-account-cleanser-config-616362312345-us-east-1-c043b470 -Stack ARN: -arn:aws:cloudformation:us-east-1:123456788985:stack/NukeCleanser/cfhdkiott-acec-11ef-ba2e-4555c1356d07 -``` - -Next, run `python upload_job_files.py` to upload two files in this directory: `nuke_config_update.py` and `nuke_generic_config.yaml`. - -## When it runs -* The tool is currently configured to run at a schedule as desired typically off hours 3:00a EST. It can be easily configured with a rate() or cron() expression by editing the cfn template file - -* The workflow also sends out a detailed report to an SNS topic with an active email subscription on what resources were deleted after the job is successful for each region which simplifies traversing and parsing the complex logs spit out by the aws-nuke binary. - -* If the workflow is successful, the stack will send out - - One email for each of the regions where nuke CodeBuild job was invoked with details of the build execution , the list of resources which was deleted along with the log file path. - - The StepFunctions workflow also sends out another email when the whole Map state process completes successfully. Sample email template given below. - -```sh - Account Cleansing Process Completed; - - ------------------------------------------------------------------ - Summary of the process: - ------------------------------------------------------------------ - DryRunMode : true - Account ID : 123456789012 - Target Region : us-west-1 - Build State : JOB SUCCEEDED - Build ID : AccountNuker-NukeCleanser:4509a9b5 - CodeBuild Project Name : AccountNuker-NukeCleanser - Process Start Time : Thu Feb 23 04:05:21 UTC 2023 - Process End Time : Thu Feb 23 04:05:54 UTC 2023 - Log Stream Path : AccountNuker-NukeCleanser/logPath - ------------------------------------------------------------------ - ################ Nuke Cleanser Logs ################# - - FAILED RESOURCES - ------------------------------- - Total number of Resources that would be removed: - 3 - us-west-1 - SQSQueue - https://sqs.us-east-1.amazonaws.com/123456789012/test-nuke-queue - would remove - us-west-1 - SNSTopic - TopicARN: arn:aws:sns:us-east-1:123456789012:test-nuke-topic - [TopicARN: "arn:aws:sns:us-east-1:123456789012:test-topic"] - would remove - us-west-1 - S3Bucket - s3://test-nuke-bucket-us-west-1 - [CreationDate: "2023-01-25 11:13:14 +0000 UTC", Name: "test-nuke-bucket-us-west-1"] - would remove - -``` - -## Monitoring queries - -Use the `aws-cli` to get CloudWatch logs associated with your most recent nuclear activity. - -```sh -# Get the current Unix timestamp -CURRENT_TIME=$(date +%s000) - -# Get the timestamp from 5 minutes ago -FIVE_MINUTES_AGO=$(($(date +%s) - 300))000 - -# Filter log events using above date range -aws logs filter-log-events \ - --log-group-name AccountNuker-nuke-auto-account-cleanser \ - --start-time $FIVE_MINUTES_AGO --end-time $CURRENT_TIME \ - --log-stream-names "10409c89-a90f-4af7-9642-0df9bc9f0855" \ - --filter-pattern removed \ - --no-interleaved \ - --output text \ - --limit 5 -``` diff --git a/.tools/test/stacks/nuke/typescript/architecture-overview.png b/.tools/test/stacks/nuke/typescript/architecture-overview.png deleted file mode 100644 index 846466f8f49b69b5b00829c8c9f4a22d48280a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93022 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%0DGVm2`Y+r>zm`<0&ri8bQFO&%16m=MF>fB~ zWpT+f%GMfPdm#yu8%?HoDvuK+V!=}trD9`dcQj%8fBN!Y!sIC7xmV; zkz!aNaa+U7vj*=eh}+I%uKjq$OUoea0c(fD)~9uDm|tq%Xj+Iv669>6rh1z7nYJh` zugamlR8hqgeMT%3tO|uSHKivM;Y5cKEV)@J#e`LCud|p**2uY|yDRw;0-gv=SPYC& zM7N42Eca>Xk5$=^+IXNbi1lrIB+RREg=2A3P;0ZVCu;GONZrlvuhl7u#k}Nr>?uDZ zcvyeD$hdQBXC!%s#W5#{JK8A);Q79v$w$xgIlH7WuZ%HG1uRf2qVUPc*Bz>)Yt!nn zaV#Gulyp4;WG%3jP_Yhnru+e`me-Bs*%8Qz80TdJ{;PCGT8k1opK%6{S1F=0l3aD- zs=>-Iv0>9{X5u8AjyVHM_1NUejAdeW4<~Vu(ynsG`PJg$f=4Yrlb@SYX0tqJwCxvD zg7HN1(80Ud4L>v)b5X|#EZL0fuX366^#b?=CZz7^Hn#4H1@olCL)F>+XFK?Int2EC zQ|_Z&Upsi@Y2WPt)+mezDV2lvbH~hsR?(clLP&KiJ5sH_9cs?B7oUg15V+OuNyB9d z(C`el$~(#+L0!R&G3Vlu9SWDc1a>!c-t*YbBdhfb2uuK6SH4xDLH;n}%g{4ajBoo{ zHUIWO7L&&sCw8wZelEQ-kOPV5iPt1HKOSg+z^Tws41xP>cLcp?C5Q$w3P9)TZwc8* z;NNu`a95oU)tIp>h*C*hG8A&$0yoY`>;rmXJt0zmt|)o_wistjgjc(*KA)MpP>+y6Kn>8LsU6k1=K*;)~hY*_G|QnUcnHoWL7j;_+@S>x*< z>w;@A9^ka&KM;OhSBL~B*|g*=cD<0CV@ES^`)0s zhYf8!DfSk;f#4Tt*2|?h`|)0;t0TmVFw=AeW1XS`Y8U*zNBn{n%6WvOJLE$d`g-h; z1EZY)(lgD1aWvwvrW$py>VVt|RBaw&5N^`kLUWu1T-R4P zU>+Nq`1Fc5Kq{8{>~yNcrG{$eISiD0L}x#)Ch3&-+%ZUEO=9c2=~Ozrr38{>^H0Pa z8I^*QqHhhQPX^D3`mdrL74vnbN-4J z24+GC;xwo%kbAXOdl0X)E4zndN2i~TnrV`}xf<-)hXa2}_!y9uB)W6>@-- zVp$!sc&u2WKo983w%?aGviAEtiE9##|-@O2vB5K5AlLZ9ilQ< z+)01`U#af!l0!RW&~Qr?)a3fO^v!mUjSnuy!BG-<*8wH-^ey+ke2O{`i9=P~28GAZ zu&|LpzfF}UWu|}JkvH*oa1>)#K*DF7p?n zaAsB75R*Kw&L-%0@b@&g#U=}MZ;Smb{_kMpDQ|e~V4Z4xn)>*l_nMk)1x9s<4lJYD;GbuNoumu8#4 z8u*qOnmH-X6_b#VXOZHgA>z5$?@u7AWx}8xWjj$}1y~;?S}gHA)*4s(E(lKF?lsZ$ ze+Jj&PcerhC+J=BXz0kuua;mU&}ZFWAK@E~y^zr&I63Q40_!pFiO&Rm*Erb{(s_yp z^a>fC7b9`wzJsL3b@cS0z>GO9!Nj$TzQM%o8s}%HQ%>xE2XR<^5_>iN9(AP4;r;a= zKVE*nIM(Y=<&Pq@q!T8RA-VtH0S|PD!FH+!MSXY!=bOWfuGorLTd0>Oi5*O}vJjqe zNYS7IKPBgHh7~#|t?MldjwEcs)TCNy`sKP)#AfCLe%gS|fOYMyko}!hyvwL~%!W6H zM27`VL!7{uGxf7EoCfce@8IU)_4s$#JtD*%0AnJaWC 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

juJH! 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 U!Z)5@W07ymNGeF=iRpR$FM$We+yDRo diff --git a/.tools/test/stacks/nuke/typescript/cdk.context.json b/.tools/test/stacks/nuke/typescript/cdk.context.json deleted file mode 100644 index 0dff385605a..00000000000 --- a/.tools/test/stacks/nuke/typescript/cdk.context.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "acknowledged-issue-numbers": [ - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885 - ] -} diff --git a/.tools/test/stacks/nuke/typescript/cdk.json b/.tools/test/stacks/nuke/typescript/cdk.json deleted file mode 100644 index fc7b7f2a784..00000000000 --- a/.tools/test/stacks/nuke/typescript/cdk.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts nuke_cleanser.ts", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, - "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, - "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, - "@aws-cdk/aws-kms:aliasNameRef": true, - "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, - "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, - "@aws-cdk/aws-efs:denyAnonymousAccess": true, - "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, - "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, - "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, - "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, - "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, - "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, - "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, - "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, - "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, - "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, - "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, - "@aws-cdk/aws-eks:nodegroupNameAttribute": true, - "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, - "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, - "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, - "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, - "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, - "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, - "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, - "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, - "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, - "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, - "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, - "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, - "cdk-migrate": true - } -} diff --git a/.tools/test/stacks/nuke/typescript/create_account_alias.py b/.tools/test/stacks/nuke/typescript/create_account_alias.py deleted file mode 100644 index c5856c5fd1f..00000000000 --- a/.tools/test/stacks/nuke/typescript/create_account_alias.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This module is used to create an AWS account alias, which is required by the deploy.py script. - -It provides a function to create an account alias using the AWS CLI, as this specific -operation is not supported by the AWS CDK. -""" - -import logging -import re -import subprocess - -logger = logging.getLogger(__name__) - - -def _is_valid_alias(alias_name: str) -> bool: - """ - Check if the provided alias name is valid according to AWS rules. - - AWS account alias must be unique and must be between 3 and 63 characters long. - Valid characters are a-z, 0-9 and '-'. - - Args: - alias_name (str): The alias name to validate. - - Returns: - bool: True if the alias is valid, False otherwise. - """ - pattern = r"^[a-z0-9](([a-z0-9]|-){0,61}[a-z0-9])?$" - return bool(re.match(pattern, alias_name)) and 3 <= len(alias_name) <= 63 - - -def _log_aws_cli_version() -> None: - """ - Log the version of the AWS CLI installed on the system. - """ - try: - result = subprocess.run(["aws", "--version"], capture_output=True, text=True) - logger.info(f"AWS CLI version: {result.stderr.strip()}") - except Exception as e: - logger.warning(f"Unable to determine AWS CLI version: {str(e)}") - - -def create_account_alias(alias_name: str) -> None: - """ - Create a new account alias with the given name. - - This function exists because the CDK does not support the specific - CreateAccountAliases API call. It attempts to create an account alias - using the AWS CLI and logs the result. - - If the account alias is created successfully, it logs a success message. - If the account alias already exists, it logs a message indicating that. - If there is any other error, it logs the error message. - - Args: - alias_name (str): The desired name for the account alias. - """ - # Log AWS CLI version when the function is called - _log_aws_cli_version() - - if not _is_valid_alias(alias_name): - logger.error( - f"Invalid alias name '{alias_name}'. It must be between 3 and 63 characters long and contain only lowercase letters, numbers, and hyphens." - ) - return - - command = ["aws", "iam", "create-account-alias", "--account-alias", alias_name] - - try: - subprocess.run( - command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=True, - ) - logger.info(f"Account alias '{alias_name}' created successfully.") - except subprocess.CalledProcessError as e: - if "EntityAlreadyExists" in e.stderr: - logger.info(f"Account alias '{alias_name}' already exists.") - elif "AccessDenied" in e.stderr: - logger.error( - f"Access denied when creating account alias '{alias_name}'. Check your AWS credentials and permissions." - ) - elif "ValidationError" in e.stderr: - logger.error( - f"Validation error when creating account alias '{alias_name}'. The alias might not meet AWS requirements." - ) - else: - logger.error(f"Error creating account alias '{alias_name}': {e.stderr}") - except Exception as e: - logger.error( - f"Unexpected error occurred while creating account alias '{alias_name}': {str(e)}" - ) diff --git a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts b/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts deleted file mode 100644 index 898aa85d781..00000000000 --- a/.tools/test/stacks/nuke/typescript/nuke_cleanser.ts +++ /dev/null @@ -1,667 +0,0 @@ -#!/usr/bin/env node -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import * as cdk from 'aws-cdk-lib'; -import * as codebuild from 'aws-cdk-lib/aws-codebuild'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as iam from 'aws-cdk-lib/aws-iam'; -import * as s3 from 'aws-cdk-lib/aws-s3'; -import * as stepfunctions from 'aws-cdk-lib/aws-stepfunctions'; - -export interface NukeStackProps extends cdk.StackProps { - /** - * The name of the bucket where the nuke binary and config files are stored - * @default 'account-nuker-config' - */ - readonly bucketName?: string; - /** - * The name of the Nuke Role to be assumed within each account, providing permissions to nuke the account(s) - * @default 'account-auto-nuker' - */ - readonly nukeRoleName?: string; - /** - * IAM Path - * @default '/' - */ - readonly iamPath?: string; - /** - * The dry run flag to run for the aws-nuke. By default it is set to True which will not delete any resources. - * @default 'true' - */ - readonly awsNukeDryRunFlag?: string; - /** - * The aws-nuke latest version to be used from internal artifactory/S3. Make sure to check the latest releases and any resource additions added. As you update the version, you will have to handle filtering any new resources that gets updated with that new version. - * @default '2.21.2' - */ - readonly awsNukeVersion?: string; - /** - * The Owner of the account to be used for tagging purpose - * @default 'OpsAdmin' - */ - readonly owner?: string; -} - -class NukeStack extends cdk.Stack { - /** - * S3 bucket created with the random generated name - */ - public readonly nukeS3BucketValue; - - constructor(scope: cdk.App, id: string, props: NukeStackProps = {}) { - super(scope, id, props); - - // Applying default props - props = { - ...props, - bucketName: props.bucketName ?? 'account-nuker-config', - nukeRoleName: props.nukeRoleName ?? 'account-auto-nuker', - iamPath: props.iamPath ?? '/', - awsNukeDryRunFlag: props.awsNukeDryRunFlag ?? 'true', - awsNukeVersion: props.awsNukeVersion ?? '2.21.2', - owner: props.owner ?? 'OpsAdmin', - }; - - // Resources - const AccountNukerPolicy = new iam.CfnManagedPolicy(this, 'AccountNukerPolicy', { - managedPolicyName: 'AccountNuker', - policyDocument: { - Statement: [ - { - Action: [ - 'access-analyzer:*', - 'autoscaling:*', - 'aws-portal:*', - 'budgets:*', - 'cloudtrail:*', - 'cloudwatch:*', - 'config:*', - 'ec2:*', - 'ec2messages:*', - 'elasticloadbalancing:*', - 'eks:*', - 'elasticache:*', - 'events:*', - 'firehose:*', - 'guardduty:*', - 'iam:*', - 'inspector:*', - 'kinesis:*', - 'kms:*', - 'lambda:*', - 'logs:*', - 'organizations:*', - 'pricing:*', - 's3:*', - 'secretsmanager:*', - 'securityhub:*', - 'sns:*', - 'sqs:*', - 'ssm:*', - 'ssmmessages:*', - 'sts:*', - 'support:*', - 'tag:*', - 'trustedadvisor:*', - 'waf-regional:*', - 'wafv2:*', - 'cloudformation:*', - ], - Effect: 'Allow', - Resource: '*', - Sid: 'WhitelistedServices', - }, - ], - Version: '2012-10-17', - }, - description: 'Managed policy for account nuking', - path: props.iamPath!, - }); - - const nukeS3Bucket = new s3.CfnBucket(this, 'NukeS3Bucket', { - bucketName: [ - props.bucketName!, - this.account, - this.region, - cdk.Fn.select(0, cdk.Fn.split('-', cdk.Fn.select(2, cdk.Fn.split('/', this.stackId)))), - ].join('-'), - publicAccessBlockConfiguration: { - blockPublicAcls: true, - ignorePublicAcls: true, - blockPublicPolicy: true, - restrictPublicBuckets: true, - }, - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - }); - nukeS3Bucket.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN; - - const nukeCodeBuildProjectRole = new iam.CfnRole(this, 'NukeCodeBuildProjectRole', { - roleName: `NukeCodeBuildProject-${this.stackName}`, - assumeRolePolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: 'codebuild.amazonaws.com', - }, - Action: 'sts:AssumeRole', - }, - ], - }, - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - policies: [ - { - policyName: 'NukeCodeBuildLogsPolicy', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: [ - 'logs:CreateLogGroup', - 'logs:CreateLogStream', - 'logs:PutLogEvents', - 'logs:DescribeLogStreams', - 'logs:FilterLogEvents', - ], - Resource: [ - `arn:aws:logs:${this.region}:${this.account}:log-group:AccountNuker-${this.stackName}`, - `arn:aws:logs:${this.region}:${this.account}:log-group:AccountNuker-${this.stackName}:*`, - ], - }, - ], - }, - }, - { - policyName: 'AssumeNukePolicy', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: 'sts:AssumeRole', - Resource: `arn:aws:iam::*:role/${props.nukeRoleName!}`, - }, - ], - }, - }, - { - policyName: 'NukeListOUAccounts', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: 'organizations:ListAccountsForParent', - Resource: '*', - }, - ], - }, - }, - { - policyName: 'S3BucketReadOnly', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: [ - 's3:Get*', - 's3:List*', - ], - Resource: [ - `arn:aws:s3:::${nukeS3Bucket.ref}`, - `arn:aws:s3:::${nukeS3Bucket.ref}/*`, - ], - }, - ], - }, - }, - ], - }); - - const nukeS3BucketPolicy = new s3.CfnBucketPolicy(this, 'NukeS3BucketPolicy', { - bucket: nukeS3Bucket.ref, - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Sid: 'ForceSSLOnlyAccess', - Effect: 'Deny', - Principal: '*', - Action: 's3:*', - Resource: [ - `arn:aws:s3:::${nukeS3Bucket.ref}`, - `arn:aws:s3:::${nukeS3Bucket.ref}/*`, - ], - Condition: { - Bool: { - 'aws:SecureTransport': 'false', - }, - }, - }, - ], - }, - }); - - const AccountNukerRole = new iam.CfnRole(this, 'AccountNukerRole', { - roleName: props.nukeRoleName!, - description: 'Account auto nuking role for test accounts', - maxSessionDuration: 7200, - tags: [ - { - key: 'privileged', - value: 'true', - }, - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'description', - value: 'PrivilegedReadWrite:account-auto-nuker role', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - assumeRolePolicyDocument: { - Statement: [ - { - Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { - AWS: [ - nukeCodeBuildProjectRole.attrArn, - ], - }, - }, - ], - Version: '2012-10-17', - }, - managedPolicyArns: [ - AccountNukerPolicy.ref, - ], - path: props.iamPath!, - }); - - const nukeCodeBuildProject = new codebuild.CfnProject(this, 'NukeCodeBuildProject', { - artifacts: { - type: 'NO_ARTIFACTS', - }, - badgeEnabled: false, - description: 'Builds a container to run AWS-Nuke for all accounts within the specified account/regions', - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - environment: { - computeType: 'BUILD_GENERAL1_SMALL', - image: 'aws/codebuild/docker:18.09.0', - imagePullCredentialsType: 'CODEBUILD', - privilegedMode: true, - type: 'LINUX_CONTAINER', - environmentVariables: [ - { - name: 'AWS_NukeDryRun', - type: 'PLAINTEXT', - value: props.awsNukeDryRunFlag!, - }, - { - name: 'AWS_NukeVersion', - type: 'PLAINTEXT', - value: props.awsNukeVersion!, - }, - { - name: 'NukeS3Bucket', - type: 'PLAINTEXT', - value: nukeS3Bucket.ref, - }, - { - name: 'NukeAssumeRoleArn', - type: 'PLAINTEXT', - value: AccountNukerRole.attrArn, - }, - { - name: 'NukeCodeBuildProjectName', - type: 'PLAINTEXT', - value: `AccountNuker-${this.stackName}`, - }, - ], - }, - logsConfig: { - cloudWatchLogs: { - groupName: `AccountNuker-${this.stackName}`, - status: 'ENABLED', - }, - }, - name: `AccountNuker-${this.stackName}`, - serviceRole: nukeCodeBuildProjectRole.attrArn, - timeoutInMinutes: 120, - source: { - buildSpec: 'version: 0.2\nphases:\n install:\n on-failure: ABORT\n commands:\n - export AWS_NUKE_VERSION=$AWS_NukeVersion\n - apt-get install -y wget\n - apt-get install jq\n - wget https://github.com/rebuy-de/aws-nuke/releases/download/v$AWS_NUKE_VERSION/aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz --no-check-certificate\n - tar xvf aws-nuke-v$AWS_NUKE_VERSION-linux-amd64.tar.gz\n - chmod +x aws-nuke-v$AWS_NUKE_VERSION-linux-amd64\n - mv aws-nuke-v$AWS_NUKE_VERSION-linux-amd64 /usr/local/bin/aws-nuke\n - aws-nuke version\n - echo \"Setting aws cli profile with config file for role assumption using metadata\"\n - aws configure set profile.nuke.role_arn ${NukeAssumeRoleArn}\n - aws configure set profile.nuke.credential_source \"EcsContainer\"\n - export AWS_PROFILE=nuke\n - export AWS_DEFAULT_PROFILE=nuke\n - export AWS_SDK_LOAD_CONFIG=1\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n build:\n on-failure: CONTINUE\n commands:\n - echo \" ------------------------------------------------ \" >> error_log.txt\n - echo \"Getting nuke generic config file from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_generic_config.yaml .\n - echo \"Updating the TARGET_REGION in the generic config from the parameter\"\n - sed -i \"s/TARGET_REGION/$NukeTargetRegion/g\" nuke_generic_config.yaml\n - echo \"Getting filter/exclusion python script from S3\";\n - aws s3 cp s3://$NukeS3Bucket/nuke_config_update.py .\n - echo \"Getting 12-digit ID of this account\"\n - account_id=$(aws sts get-caller-identity |jq -r \".Account\");\n - echo \"Running Config filter/update script\";\n - python3 nuke_config_update.py --account $account_id --region \"$NukeTargetRegion\";\n - echo \"Configured nuke_config.yaml\";\n - echo \"Running Nuke on Account\";\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --profile nuke 2>&1 |tee -a aws-nuke.log; done\n elif [ \"$AWS_NukeDryRun\" = \"false\" ]; then\n for file in $(ls nuke_config_$NukeTargetRegion*) ; do aws-nuke -c $file --force --max-wait-retries 10 --no-dry-run --profile nuke 2>&1 |tee -a aws-nuke.log; done\n else\n echo \"Couldn\'t determine Dryrun flag...exiting\"\n exit 1\n fi\n - nuke_pid=$!;\n - wait $nuke_pid;\n - echo \"Checking if Nuke Process completed for account\"\n - |\n if cat aws-nuke.log | grep -F \"Error: The specified account doesn\"; then\n echo \"Nuke errored due to no AWS account alias set up - exiting\"\n cat aws-nuke.log >> error_log.txt\n exit 1\n else\n echo \"Nuke completed Successfully - Continuing\"\n fi\n\n post_build:\n commands:\n - echo $CODEBUILD_BUILD_SUCCEEDING\n - echo \"Get current timestamp for naming reports\"\n - BLD_START_TIME=$(date -d @$(($CODEBUILD_START_TIME/1000)))\n - CURR_TIME_UTC=$(date -u)\n - |\n {\n echo \" Account Nuking Process Failed;\"\n echo \"\"\n \n echo \" ----------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ----------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ----------------------------------------------------------------\"\n echo \" ################# Failed Nuke Process - Exiting ###################\"\n echo \"\"\n } >> fail_email_template.txt\n - | \n if [ \"$CODEBUILD_BUILD_SUCCEEDING\" = \"0\" ]; then \n echo \" Couldn\'t process Account Nuker - Exiting \" >> fail_email_template.txt\n cat error_log.txt >> fail_email_template.txt\n exit 1;\n fi\n - sleep 120\n - LOG_STREAM_NAME=$CODEBUILD_LOG_PATH;\n - CURR_TIME_UTC=$(date -u)\n - | \n if [ -z \"${LOG_STREAM_NAME}\" ]; then\n echo \"Couldn\'t find the log stream for log events\";\n exit 0;\n else\n aws logs filter-log-events --log-group-name $NukeCodeBuildProjectName --log-stream-names $LOG_STREAM_NAME --filter-pattern \"removed\" --no-interleaved | jq -r .events[].message > log_output.txt;\n awk \'/There are resources in failed state/,/Error: failed/\' aws-nuke.log > failure_email_output.txt\n awk \'/Error: failed/,/\\n/\' failure_email_output.txt > failed_log_output.txt\n fi\n - |\n if [ -r log_output.txt ]; then\n content=$(cat log_output.txt)\n echo $content\n elif [ -f \"log_output.txt\" ]; then\n echo \"The file log_output.txt exists but is not readable to the script.\"\n else\n echo \"The file log_output.txt does not exist.\"\n fi\n - echo \"Publishing Log Ouput to SNS:\"\n - sub=\"Account Nuker Succeeded in account \"$account_id\" and region \"$NukeTargetRegion\"\"\n - |\n {\n echo \" Account Nuking Process Completed;\"\n echo \"\"\n \n echo \" ------------------------------------------------------------------\"\n echo \" Summary of the process:\"\n echo \" ------------------------------------------------------------------\"\n echo \" DryRunMode : $AWS_NukeDryRun\"\n echo \" Account ID : $account_id\"\n echo \" Target Region : $NukeTargetRegion\"\n echo \" Build State : $([ \"${CODEBUILD_BUILD_SUCCEEDING}\" = \"1\" ] && echo \"JOB SUCCEEDED\" || echo \"JOB FAILED\")\"\n echo \" Build ID : ${CODEBUILD_BUILD_ID}\"\n echo \" CodeBuild Project Name : $NukeCodeBuildProjectName\"\n echo \" Process Start Time : ${BLD_START_TIME}\"\n echo \" Process End Time : ${CURR_TIME_UTC}\"\n echo \" Log Stream Path : $NukeCodeBuildProjectName/${CODEBUILD_LOG_PATH}\"\n echo \" ------------------------------------------------------------------\"\n echo \" ################### Account Nuker Logs ####################\"\n echo \"\"\n } >> email_template.txt\n\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"No Resources scanned and nukeable yet\"\n - echo \"Number of Resources that is filtered by config:\" >> email_template.txt\n - cat aws-nuke.log | grep -c \" - filtered by config\" || echo 0 >> email_template.txt\n - echo \" ------------------------------------------ \" >> email_template.txt\n - |\n if [ \"$AWS_NukeDryRun\" = \"true\" ]; then\n echo \"RESOURCES THAT WOULD BE REMOVED:\" >> email_template.txt\n echo \" ----------------------------------------- \" >> email_template.txt\n cat aws-nuke.log | grep -c \" - would remove\" || echo 0 >> email_template.txt\n cat aws-nuke.log | grep -F \" - would remove\" >> email_template.txt || echo \"No resources to be removed\" >> email_template.txt\n else\n echo \" FAILED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat failed_log_output.txt >> email_template.txt\n echo \" SUCCESSFULLY NUKED RESOURCES \" >> email_template.txt\n echo \" ------------------------------- \" >> email_template.txt\n cat log_output.txt >> email_template.txt\n fi\n - echo \"Resources Nukeable:\"\n - cat aws-nuke.log | grep -F \"Scan complete:\" || echo \"Nothing Nukeable yet\"\n - echo \"Total number of Resources that would be removed:\"\n - cat aws-nuke.log | grep -c \" - would remove\" || echo \"Nothing would be removed yet\"\n - echo \"Total number of Resources Deleted:\"\n - cat aws-nuke.log | grep -c \" - removed\" || echo \"Nothing deleted yet\"\n - echo \"List of Resources Deleted today:\"\n - cat aws-nuke.log | grep -F \" - removed\" || echo \"Nothing deleted yet\"\n', - type: 'NO_SOURCE', - }, - }); - - const nukeStepFunctionRole = new iam.CfnRole(this, 'NukeStepFunctionRole', { - roleName: 'account-nuker-codebuild-state-machine-role', - assumeRolePolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: [ - `states.${this.region}.amazonaws.com`, - ], - }, - Action: [ - 'sts:AssumeRole', - ], - }, - ], - }, - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - path: '/', - policies: [ - { - policyName: 'account-nuker-codebuild-state-machine-policy', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: [ - 'codebuild:StartBuild', - 'codebuild:StartBuild', - 'codebuild:StopBuild', - 'codebuild:StartBuildBatch', - 'codebuild:StopBuildBatch', - 'codebuild:RetryBuild', - 'codebuild:RetryBuildBatch', - 'codebuild:BatchGet*', - 'codebuild:GetResourcePolicy', - 'codebuild:DescribeTestCases', - 'codebuild:DescribeCodeCoverages', - 'codebuild:List*', - ], - Resource: [ - nukeCodeBuildProject.attrArn, - ], - }, - { - Effect: 'Allow', - Action: [ - 'events:PutTargets', - 'events:PutRule', - 'events:DescribeRule', - ], - Resource: `arn:aws:events:${this.region}:${this.account}:rule/StepFunctionsGetEventForCodeBuildStartBuildRule`, - }, - { - Effect: 'Allow', - Action: [ - 'states:DescribeStateMachine', - 'states:ListExecutions', - 'states:StartExecution', - 'states:StopExecution', - 'states:DescribeExecution', - ], - Resource: [ - `arn:aws:states:${this.region}:${this.account}:stateMachine:account-nuker-codebuild-state-machine`, - ], - }, - ], - }, - }, - ], - }); - - const nukeStepFunction = new stepfunctions.CfnStateMachine(this, 'NukeStepFunction', { - stateMachineName: 'account-nuker-codebuild-state-machine', - roleArn: nukeStepFunctionRole.attrArn, - definitionString: `{ - "Comment": "AWS Account Nuker for multi-region single account clean up using SFN Map state parallel invocation of CodeBuild project.", - "StartAt": "StartNukeCodeBuildForEachRegion", - "States": { - "StartNukeCodeBuildForEachRegion": { - "End": true, - "Type": "Map", - "ItemsPath": "$.InputPayLoad.region_list", - "Parameters": { - "region_id.$": "$$.Map.Item.Value", - "nuke_dry_run.$": "$.InputPayLoad.nuke_dry_run", - "nuke_version.$": "$.InputPayLoad.nuke_version" - }, - "MaxConcurrency": 0, - "Iterator": { - "StartAt": "Trigger Nuke CodeBuild Job", - "States": { - "Trigger Nuke CodeBuild Job": { - "Type": "Task", - "Resource": "arn:aws:states:::codebuild:startBuild.sync", - "Parameters": { - "ProjectName": "${nukeCodeBuildProject.attrArn}", - "EnvironmentVariablesOverride": [ - { - "Name": "NukeTargetRegion", - "Type": "PLAINTEXT", - "Value.$": "$.region_id" - }, - { - "Name": "AWS_NukeDryRun", - "Type": "PLAINTEXT", - "Value.$": "$.nuke_dry_run" - }, - { - "Name": "AWS_NukeVersion", - "Type": "PLAINTEXT", - "Value.$": "$.nuke_version" - } - ] - }, - "Next": "Check Nuke CodeBuild Job Status", - "ResultSelector": { - "NukeBuildOutput.$": "$.Build" - }, - "ResultPath": "$.AccountNukerRegionOutput", - "Retry": [ - { - "ErrorEquals": [ - "States.TaskFailed" - ], - "BackoffRate": 1, - "IntervalSeconds": 1, - "MaxAttempts": 1 - } - ], - "Catch": [ - { - "ErrorEquals": [ - "States.ALL" - ], - "Next": "Nuke Failed", - "ResultPath": "$.AccountNukerRegionOutput" - } - ] - }, - "Check Nuke CodeBuild Job Status": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.AccountNukerRegionOutput.NukeBuildOutput.BuildStatus", - "StringEquals": "SUCCEEDED", - "Next": "Nuke Success" - }, - { - "Variable": "$.AccountNukerRegionOutput.NukeBuildOutput.BuildStatus", - "StringEquals": "FAILED", - "Next": "Nuke Failed" - } - ], - "Default": "Nuke Success" - }, - "Nuke Success": { - "Type": "Pass", - "Parameters": { - "Status": "Succeeded", - "Region.$": "$.region_id", - "CodeBuild Status.$": "$.AccountNukerRegionOutput.NukeBuildOutput.BuildStatus" - }, - "ResultPath": "$.result", - "End": true - }, - "Nuke Failed": { - "Type": "Pass", - "Parameters": { - "Status": "Failed", - "Region.$": "$.region_id", - "CodeBuild Status.$": "States.Format('Account Nuker failed with error {}. Check CodeBuild execution for input region {} to investigate', $.AccountNukerRegionOutput.Error, $.region_id)" - }, - "ResultPath": "$.result", - "End": true - } - } - }, - "ResultSelector": { - "filteredResult.$": "$..result" - }, - "ResultPath": "$.NukeFinalMapAllRegionsOutput" - } - } - }`, - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - }); - - const eventBridgeNukeScheduleRole = new iam.CfnRole(this, 'EventBridgeNukeScheduleRole', { - roleName: `EventBridgeNukeSchedule-${this.stackName}`, - assumeRolePolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: 'events.amazonaws.com', - }, - Action: 'sts:AssumeRole', - }, - ], - }, - tags: [ - { - key: 'DoNotNuke', - value: 'True', - }, - { - key: 'owner', - value: props.owner!, - }, - ], - policies: [ - { - policyName: 'EventBridgeNukeStateMachineExecutionPolicy', - policyDocument: { - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Action: 'states:StartExecution', - Resource: nukeStepFunction.ref, - }, - ], - }, - }, - ], - }); - - const eventBridgeNukeSchedule = new events.CfnRule(this, 'EventBridgeNukeSchedule', { - name: `EventBridgeNukeSchedule-${this.stackName}`, - description: 'Scheduled Event for running AWS Nuke on the target accounts within the specified regions', - scheduleExpression: 'cron(0 7 ? * * *)', - state: 'ENABLED', - roleArn: eventBridgeNukeScheduleRole.attrArn, - targets: [ - { - arn: nukeStepFunction.ref, - roleArn: eventBridgeNukeScheduleRole.attrArn, - id: nukeStepFunction.attrName, - input: `{ - "InputPayLoad": { - "nuke_dry_run": "${props.awsNukeDryRunFlag!}", - "nuke_version": "${props.awsNukeVersion!}", - "region_list": [ - "us-west-1", - "us-east-1" - ] - } - }`, - }, - ], - }); - - // Outputs - this.nukeS3BucketValue = nukeS3Bucket.ref; - new cdk.CfnOutput(this, 'CfnOutputNukeS3BucketValue', { - key: 'NukeS3BucketValue', - description: 'S3 bucket created with the random generated name', - value: this.nukeS3BucketValue!.toString(), - }); - } -} - -const app = new cdk.App(); - -new NukeStack(app, `AccountNukerStack-${process.env.TOOL_NAME?.replace("_", "-")}`, - { - env: { - account: process.env.CDK_DEFAULT_ACCOUNT, - region: process.env.CDK_DEFAULT_REGION, - }, - terminationProtection: true -}); - -app.synth(); diff --git a/.tools/test/stacks/nuke/typescript/nuke_config_update.py b/.tools/test/stacks/nuke/typescript/nuke_config_update.py deleted file mode 100644 index 6faf6f791f8..00000000000 --- a/.tools/test/stacks/nuke/typescript/nuke_config_update.py +++ /dev/null @@ -1,194 +0,0 @@ -""" - Python class responsible for updating the nuke generic config , based on exceptions to be filtered - and also updates dynamically the region attribute passed in from the StepFunctions invocation. This should be modified to suit your needs. -""" -import argparse -import copy - -import boto3 -import yaml - -GLOBAL_RESOURCE_EXCEPTIONS = [ - {"property": "tag:DoNotNuke", "value": "True"}, - {"property": "tag:Permanent", "value": "True"}, - {"type": "regex", "value": ".*auto-account-cleanser*.*"}, - {"type": "regex", "value": ".*nuke-account-cleanser*.*"}, - {"type": "regex", "value": ".*securityhub*.*"}, - {"type": "regex", "value": ".*aws-prod*.*"}, -] - - -class StackInfo: - def __init__(self, account, target_regions): - self.session = boto3.Session(profile_name="nuke") - # Regions to be targeted set from the Stepfunctions/CodeBuild workflow - self.regions = target_regions - self.resources = {} - self.config = {} - self.account = account - - def Populate(self): - self.UpdateCFNStackList() - self.OverrideDefaultConfig() - - def UpdateCFNStackList(self): - try: - for region in self.regions: - cfn_client = self.session.client("cloudformation", region_name=region) - stack_paginator = cfn_client.get_paginator("list_stacks") - responses = stack_paginator.paginate( - StackStatusFilter=[ - "CREATE_COMPLETE", - "UPDATE_COMPLETE", - "UPDATE_ROLLBACK_COMPLETE", - "IMPORT_COMPLETE", - "IMPORT_ROLLBACK_COMPLETE", - ] - ) - for page in responses: - for stack in page.get("StackSummaries"): - self.GetCFNResources(stack, cfn_client) - self.BuildIamExclusionList(region) - except Exception as e: - print("Error in calling UpdateCFNStackList:\n {}".format(e)) - - def GetCFNResources(self, stack, cfn_client): - try: - stack_name = stack.get("StackName") - - if stack_name is None: - stack_name = stack.get("PhysicalResourceId") - - stack_description = cfn_client.describe_stacks(StackName=stack_name) - print("Stack Description: ", stack_description) - tags = stack_description.get("Stacks")[0].get("Tags") - for tag in tags: - key = tag.get("Key") - value = tag.get("Value") - if key == "AWS_Solutions" and value == "LandingZoneStackSet": - if "CloudFormationStack" in self.resources: - self.resources["CloudFormationStack"].append( - stack.get("StackName") - ) - else: - self.resources["CloudFormationStack"] = [stack.get("StackName")] - stack_resources = cfn_client.list_stack_resources( - StackName=stack_name - ) - for resource in stack_resources.get("StackResourceSummaries"): - if resource.get("ResourceType") == "AWS::CloudFormation::Stack": - self.GetCFNResources(resource, cfn_client) - else: - nuke_type = self.UpdateResourceName( - resource["ResourceType"] - ) - if nuke_type in self.resources: - self.resources[nuke_type].append( - { - "type": "regex", - "value": resource["PhysicalResourceId"], - } - ) - else: - self.resources[nuke_type] = [ - { - "type": "regex", - "value": resource["PhysicalResourceId"], - } - ] - except Exception as e: - print("Error calling GetCFNResources:\n {}".format(e)) - - def UpdateResourceName(self, resource): - nuke_type = str.replace(resource, "AWS::", "") - nuke_type = str.replace(nuke_type, "::", "") - nuke_type = str.replace(nuke_type, "Config", "ConfigService", 1) - return nuke_type - - def BuildIamExclusionList(self, region): - # This excludes and appends to the config IAMRole resources , the roles that are federated principals - # You can add any other custom filterting logic based on regions for IAM/Global roles that should be excluded - iam_client = self.session.client("iam", region_name=region) - iam_paginator = iam_client.get_paginator("list_roles") - responses = iam_paginator.paginate() - for page in responses: - for role in page["Roles"]: - apd = role.get("AssumeRolePolicyDocument") - if apd is not None: - for item in apd.get("Statement"): - if item is not None: - for principal in item.get("Principal"): - if principal == "Federated": - if "IAMRole" in self.resources: - self.resources["IAMRole"].append( - role.get("RoleName") - ) - else: - self.resources["IAMRole"] = [ - role.get("RoleName") - ] - - def OverrideDefaultConfig(self): - # Open the nuke_generic_config.yaml and merge the captured resources/exclusions with it - try: - with open(r"nuke_generic_config.yaml") as config_file: - self.config = yaml.load(config_file) - # Not all resources handled by the tool, but we will add them to the exclusion anyhow. - for resource in self.resources: - if resource in self.config["accounts"]["ACCOUNT"]["filters"]: - self.config["accounts"]["ACCOUNT"]["filters"][resource].extend( - self.resources[resource] - ) - else: - self.config["accounts"]["ACCOUNT"]["filters"][ - resource - ] = self.resources[resource] - self.config["accounts"][self.account] = copy.deepcopy( - self.config["accounts"]["ACCOUNT"] - ) - if "ACCOUNT" in self.config["accounts"]: - self.config["accounts"].pop("ACCOUNT", None) - # Global exclusions apply to every type of resource - for resource in self.config["accounts"][self.account]["filters"]: - for exception in GLOBAL_RESOURCE_EXCEPTIONS: - self.config["accounts"][self.account]["filters"][resource].append( - exception.copy() - ) - config_file.close() - except Exception as e: - print("Failed merging nuke-config-test.yaml with error {}".format(e)) - exit(1) - - def WriteConfig(self): - # CodeBuild script updates the target region in the generic config and is validated here. - try: - for region in self.config["regions"]: - local_config = stackInfo.config.copy() - local_config["regions"] = [region] - filename = "nuke_config_{}.yaml".format(region) - with open(filename, "w") as output_file: - output = yaml.dump(local_config, output_file) - output_file.close() - except Exception as e: - print("Failed opening nuke_config.yaml for writing with error {}".format(e)) - - -try: - parser = argparse.ArgumentParser() - parser.add_argument( - "--account", dest="account", help="Account to nuke" - ) # Account and Region from StepFunctions - CodeBuild overridden params - parser.add_argument("--region", dest="region", help="Region to target for nuke") - args = parser.parse_args() - if not args.account or not args.region: - parser.print_help() - exit(1) -except Exception as e: - print(e) - exit(1) - -if __name__ == "__main__": - print("Incoming Args: ", args) - stackInfo = StackInfo(args.account, [args.region]) - stackInfo.Populate() - stackInfo.WriteConfig() diff --git a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml b/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml deleted file mode 100644 index 7e1f3cc00f5..00000000000 --- a/.tools/test/stacks/nuke/typescript/nuke_generic_config.yaml +++ /dev/null @@ -1,173 +0,0 @@ -regions: - - TARGET_REGION # will be overridden during execution based on region parameter - -account-blocklist: - - 1234567890 - -resource-types: - excludes: - - ACMCertificate - - AWSBackupPlan - - AWSBackupRecoveryPoint - - AWSBackupSelection - - AWSBackupVault - - AWSBackupVaultAccessPolicy - - CloudTrailTrail - - CloudWatchEventsTarget - - CodeCommitRepository - - CodeStarProject - - ConfigServiceConfigRule - - ECRRepository - - EC2Address - - EC2ClientVpnEndpoint - - EC2ClientVpnEndpointAttachment - - EC2CustomerGateway - - EC2DHCPOption - - EC2DefaultSecurityGroupRule - - EC2EgressOnlyInternetGateway - - EC2InternetGateway - - EC2InternetGatewayAttachment - - EC2KeyPair - - EC2NetworkACL - - EC2NetworkInterface - - EC2RouteTable - - EC2SecurityGroup - - EC2Subnet - - EC2VPC - - EC2VPCEndpoint - - IAMGroup - - IAMGroupPolicy - - IAMGroupPolicyAttachment - - IAMInstanceProfile - - IAMInstanceProfileRole - - IAMLoginProfile - - IAMOpenIDConnectProvider - - IAMPolicy - - IAMRole - - IAMRolePolicy - - IAMRolePolicyAttachment - - IAMSAMLProvider - - IAMServerCertificate - - IAMServiceSpecificCredential - - IAMSigningCertificate - - IAMUser - - IAMUserAccessKey - - IAMUserGroupAttachment - - IAMUserPolicy - - IAMUserPolicyAttachment - - IAMUserSSHPublicKey - - IAMVirtualMFADevice - - KMSAlias - - KMSKey - - Route53HostedZone - - Route53ResourceRecordSet - - S3Bucket - - S3Object - - SecretsManagerSecret - - SQSQueue - - SSMParameter - -accounts: - ACCOUNT: - filters: - EC2VPC: - - property: IsDefault - value: "true" - EC2DHCPOption: - - property: DefaultVPC - value: "true" - EC2InternetGateway: - - property: DefaultVPC - value: "true" - EC2InternetGatewayAttachment: - - property: DefaultVPC - value: "true" - EC2Subnet: - - property: DefaultVPC - value: "true" - EC2RouteTable: - - property: DefaultVPC - value: "true" - EC2DefaultSecurityGroupRule: - - property: SecurityGroupId - type: glob - value: "*" - LambdaEventSourceMapping: - - property: "EventSourceArn" - type: "glob" - value: "*PluginStack-*" - - property: "FunctionArn" - type: "glob" - value: "*PluginStack-*" - LambdaPermission: - - property: "name" - type: "glob" - value: "*PluginStack-*" - GuardDutyDetector: - - property: DetectorID - type: glob - value: "*" - CloudTrailTrail: - - type: regex - value: "^(AccountGuardian|Isengard).*DO-NOT-DELETE.*$" - CloudWatchEventsRule: - - type: regex - value: "^Rule: (AccountGuardian-.*DO-NOT-DELETE|AwsSecurity.*DO-NOT-DELETE|DO-NOT-DELETE-GatedGarden-.*)$" - CloudWatchEventsTarget: - - type: regex - value: "^Rule: (AccountGuardian-.*DO-NOT-DELETE.*|AwsSecurity.*DO-NOT-DELETE|DO-NOT-DELETE-GatedGarden-.*)$" - CloudWatchLogsLogGroup: - - type: regex - value: "^(AccountGuardian-).*$" - ConfigServiceDeliveryChannel: - - "pitbull-default" - ConfigServiceConfigRule: - - type: regex - value: "^(managed-ec2-patch-compliance|ec2-managed-by-systems-manager-REMEDIATE|pvre-.*-REMEDIATE|.*-pvre-.*-REMEDIATE)$" - S3Bucket: - - property: Name - type: regex - value: "^(cdktoolkit-stagingbucket-.*|pitbull-aws-config-.*|cloudtrail-awslogs-.*-isengard-do-not-delete|do-not-delete-gatedgarden-audit-.*)$" - S3Object: - - property: Bucket - type: regex - value: "^(cdktoolkit-stagingbucket-.*|pitbull-aws-config-.*|cloudtrail-awslogs-.*-isengard-do-not-delete|do-not-delete-gatedgarden-audit-.*)$" - ConfigServiceConfigurationRecorder: - - "MainRecorder" - CloudFormationStack: - - property: Name - type: regex - value: "^(CDKToolkit|AccountGuardian|.*DO-NOT-DELETE)$" - - property: Name - type: regex - value: "^(PluginStack).*$" - - property: Name - type: regex - value: "^(pvre.*|PVRE.*)$" - - property: Name - type: regex - value: "^(.*PatchBaseline.*)$" - IAMPolicy: - - property: Name - type: regex - value: "^(ConfigAccessPolicy|ResourceConfigurationCollectorPolicy|CloudFormationRefereeService|EC2CapacityReservationService|AwsSecurit.*AuditPolicy)$" - IAMRole: - - property: Name - type: regex - value: "^(AWSServiceRoleFor.*|.*DO-NOT-DELETE|^Isengard.*|Admin|ReadOnly|GatedGarden.*Audit|ShadowTrooper.*|InternalAuditInternal|EC2CapacityReservationService|AccessAnalyzerTrustedService|EC2CapacityReservationService|AwsSecurit.*Audit|AWS.*Audit)$" - IAMRolePolicy: - - property: role:RoleName - type: regex - value: "^(.*DO-NOT-DELETE|Isengard.*|GatedGarden.*Audit|AccountGuardian.*|ShadowTrooper.*|AccessAnalyzerTrustedService|AwsSecurit.*Audit)$" - IAMRolePolicyAttachment: - - property: RoleName - type: regex - value: "^(Admin|ReadOnly|AWSServiceRoleFor.*|.*DO-NOT-DELETE|Isengard.*|InternalAuditInternal|EC2CapacityReservationService|AWSVAPTAudit|AwsSecurit.*Audit)$" - SSMDocument: - - type: regex - value: "^(AccountGuardian|Isengard).*DO-NOT-DELETE.*$" - SSMResourceDataSync: - - type: regex - value: "^(AccountGuardian|Isengard).*DO-NOT-DELETE.*$" - - diff --git a/.tools/test/stacks/nuke/typescript/package-lock.json b/.tools/test/stacks/nuke/typescript/package-lock.json deleted file mode 100644 index 7838a659b57..00000000000 --- a/.tools/test/stacks/nuke/typescript/package-lock.json +++ /dev/null @@ -1,1202 +0,0 @@ -{ - "name": "nuke_cleanser", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nuke_cleanser", - "version": "0.1.0", - "dependencies": { - "aws-cdk-lib": "^2.164.1", - "constructs": "^10.4.2", - "source-map-support": "^0.5.21" - }, - "bin": { - "nuke_cleanser": "nuke_cleanser.js" - }, - "devDependencies": { - "@types/jest": "^29.5.12", - "@types/node": "22.5.4", - "aws-cdk": "2.164.1", - "ts-node": "^10.9.2", - "typescript": "~5.6.2" - } - }, - "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.212", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.212.tgz", - "integrity": "sha512-7WqbnWUkBBcAzEdfRrpz6sCOheUPf4JEUdGvzJ4EEufXeT7v7nRbRmTvUBbQ+OQlCv9UrVj9XuFxKPjkvneGMQ==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.3.tgz", - "integrity": "sha512-cDG1w3ieM6eOT9mTefRuTypk95+oyD7P5X/wRltwmYxU7nZc3+076YEVS6vrjDKr3ADYbfn0lDKpfB1FBtO9CQ==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", - "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "38.0.1", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-38.0.1.tgz", - "integrity": "sha512-KvPe+NMWAulfNVwY7jenFhzhuLhLqJ/OPy5jx7wUstbjnYnjRVLpUHPU3yCjXFE0J8cuJVdx95BJ4rOs66Pi9w==", - "bundleDependencies": [ - "jsonschema", - "semver" - ], - "license": "Apache-2.0", - "dependencies": { - "jsonschema": "^1.4.1", - "semver": "^7.6.3" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.6.3", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/aws-cdk": { - "version": "2.164.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.164.1.tgz", - "integrity": "sha512-dWRViQgHLe7GHkPIQGA+8EQSm8TBcxemyCC3HHW3wbLMWUDbspio9Dktmw5EmWxlFjjWh86Dk1JWf1zKQo8C5g==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 14.15.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.170.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.170.0.tgz", - "integrity": "sha512-hlfoOJUZmAY3TjOXjWhAYKlrPcfGNTXA24NirwkEYOX+t1HD8OLSrYZvluMc7nWgIZf1Mq1g6M0xNEZJqykPrA==", - "bundleDependencies": [ - "@balena/dockerignore", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "table", - "yaml", - "mime-types" - ], - "license": "Apache-2.0", - "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.208", - "@aws-cdk/asset-kubectl-v20": "^2.1.3", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^38.0.1", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.2", - "jsonschema": "^1.4.1", - "mime-types": "^2.1.35", - "minimatch": "^3.1.2", - "punycode": "^2.3.1", - "semver": "^7.6.3", - "table": "^6.8.2", - "yaml": "1.10.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "constructs": "^10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.17.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/astral-regex": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/aws-cdk-lib/node_modules/case": { - "version": "1.6.3", - "inBundle": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { - "version": "3.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.3", - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/jsonfile": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { - "version": "4.4.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/mime-db": { - "version": "1.52.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-types": { - "version": "2.1.35", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-cdk-lib/node_modules/require-from-string": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.6.3", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/slice-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.2", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yaml": { - "version": "1.10.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/constructs": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", - "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", - "license": "Apache-2.0" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/.tools/test/stacks/nuke/typescript/package.json b/.tools/test/stacks/nuke/typescript/package.json deleted file mode 100644 index f7f74fb1d35..00000000000 --- a/.tools/test/stacks/nuke/typescript/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "nuke_cleanser", - "version": "0.1.0", - "bin": { - "nuke_cleanser": "nuke_cleanser.ts" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" - }, - "devDependencies": { - "@types/jest": "^29.5.12", - "@types/node": "22.5.4", - "aws-cdk": "2.164.1", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "ts-node": "^10.9.2", - "typescript": "~5.6.2" - }, - "dependencies": { - "aws-cdk-lib": "^2.164.1", - "constructs": "^10.4.2", - "source-map-support": "^0.5.21" - } -} diff --git a/.tools/test/stacks/nuke/typescript/tsconfig.json b/.tools/test/stacks/nuke/typescript/tsconfig.json deleted file mode 100644 index aaa7dc510f1..00000000000 --- a/.tools/test/stacks/nuke/typescript/tsconfig.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": [ - "es2020", - "dom" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} diff --git a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py b/.tools/test/stacks/nuke/typescript/upload_job_scripts.py deleted file mode 100644 index e0d83683399..00000000000 --- a/.tools/test/stacks/nuke/typescript/upload_job_scripts.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -""" -This module is part of the AccountNuker stack deployment process. -It's invoked by the stacks/deploy.py script to support integration testing. - -Main purposes: -1. Retrieve resources (S3 bucket and Step Function ARN) from a CloudFormation stack. -2. Upload configuration and helper files to the S3 bucket. -3. Trigger a Step Function execution to initiate the account nuking procedure. - -The uploaded files and Step Function execution are used by CodeBuild to execute the nuking procedure. -""" - -import json -import logging -import os -from typing import Dict, Optional - -import boto3 -from botocore.exceptions import ClientError - -# Configuration -STACK_NAME = "AccountNukerStack" -REGION = "us-east-1" -FILES_TO_UPLOAD = ["nuke_generic_config.yaml", "nuke_config_update.py"] -INPUT_PAYLOAD = { - "InputPayLoad": { - "nuke_dry_run": "false", - "nuke_version": "2.21.2", - "region_list": ["us-east-1"], - } -} - -# Logging setup -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - -# AWS session -session = boto3.Session(region_name=REGION) - - -def _get_stack_name(partial_stack_name: str) -> Optional[str]: - """ - Retrieves the full stack name from a partial stack name. - - Args: - partial_stack_name (str): The partial stack name to search for. - - Returns: - Optional[str]: The full stack name if found, otherwise None. - """ - session = boto3.Session() - cloudformation = session.client("cloudformation") - - try: - # List all stacks with status CREATE_COMPLETE - response = cloudformation.list_stacks(StackStatusFilter=["CREATE_COMPLETE"]) - except ClientError as e: - print(f"Error listing stacks: {e}") - return None - - matching_stacks = [] - for stack in response["StackSummaries"]: - if partial_stack_name in stack["StackName"]: - matching_stacks.append(stack["StackName"]) - - if not matching_stacks: - print(f"No matching stacks found for partial name: {partial_stack_name}") - return None - elif len(matching_stacks) > 1: - print( - f"WARNING: Found multiple stacks sharing the same partial name: {partial_stack_name}." - ) - return None - else: - return matching_stacks[0] - - -def _get_resource_from_stack( - partial_stack_name: str, resource_type: str -) -> Optional[str]: - """ - Retrieve a specific resource from a CloudFormation stack. - - Args: - partial_stack_name (str): The partial name of the CloudFormation stack. - resource_type (str): The type of resource to retrieve (e.g., "AWS::IAM::Role"). - - Returns: - Optional[str]: The physical resource ID if found, otherwise None. - """ - session = boto3.Session() - cloudformation = session.client("cloudformation") - - # Get the full stack name from the partial name - stack_name = _get_stack_name(partial_stack_name) - if not stack_name: - logger.warning(f"No stack found with partial name: {partial_stack_name}") - return None - - try: - response = cloudformation.describe_stack_resources(StackName=stack_name) - for resource in response["StackResources"]: - if resource["ResourceType"] == resource_type: - return resource["PhysicalResourceId"] - logger.warning(f"No {resource_type} found in stack '{stack_name}'.") - except ClientError as e: - logger.error(f"Error describing stack: {e}") - return None - - -def _get_s3_bucket_from_stack(stack_name: str) -> Optional[str]: - """Retrieve the S3 bucket name from a CloudFormation stack.""" - breakpoint() - return _get_resource_from_stack(stack_name, "AWS::S3::Bucket") - - -def _get_step_function_arn(stack_name: str) -> Optional[str]: - """Retrieve the ARN of the Step Function from a CloudFormation stack.""" - return _get_resource_from_stack(stack_name, "AWS::StepFunctions::StateMachine") - - -def _trigger_step_function(step_function_arn: str, payload: Dict) -> Optional[str]: - """Trigger a Step Functions execution with the given input payload.""" - stepfunctions = session.client("stepfunctions") - try: - response = stepfunctions.start_execution( - stateMachineArn=step_function_arn, input=json.dumps(payload) - ) - logger.info( - f"Step Function triggered successfully. Execution ARN: {response['executionArn']}" - ) - return response["executionArn"] - except ClientError as e: - logger.error(f"Error triggering Step Function: {e}") - return None - - -def _upload_file_to_s3(bucket_name: str, file_name: str) -> bool: - """Upload a file to an S3 bucket from the current directory.""" - s3 = session.client("s3") - file_path = os.path.join(os.getcwd(), file_name) - try: - s3.upload_file(file_path, bucket_name, file_name) - logger.info(f"Uploaded {file_path} to s3://{bucket_name}/{file_name}") - return True - except FileNotFoundError: - logger.error(f"The file {file_name} was not found in the current directory.") - except ClientError as e: - logger.error(f"Error uploading {file_name}: {e}") - return False - - -def process_stack_and_upload_files(): - """ - Main function to process the stack, upload files, and trigger the Step Function. - This is the only public function intended to be called from outside this module. - """ - bucket_name = _get_s3_bucket_from_stack(STACK_NAME) - if not bucket_name: - logger.error(f"Failed to find an S3 bucket in stack '{STACK_NAME}'.") - return - - logger.info(f"Found S3 bucket: {bucket_name}") - - # Upload files to the bucket - for file_name in FILES_TO_UPLOAD: - _upload_file_to_s3(bucket_name, file_name) - - step_function_arn = _get_step_function_arn(STACK_NAME) - if not step_function_arn: - logger.error(f"Failed to find a Step Function in stack '{STACK_NAME}'.") - return - - logger.info(f"Found Step Function ARN: {step_function_arn}") - - # Trigger the Step Function - _trigger_step_function(step_function_arn, INPUT_PAYLOAD) From 2d953e468c10f73b9ad0702527e5e6f6829b7ce9 Mon Sep 17 00:00:00 2001 From: ford-at-aws Date: Thu, 12 Dec 2024 12:39:28 -0500 Subject: [PATCH 24/26] fixes --- .tools/test/stacks/plugin/typescript/lambda/export_logs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.tools/test/stacks/plugin/typescript/lambda/export_logs.py b/.tools/test/stacks/plugin/typescript/lambda/export_logs.py index d6cd0e03b5c..32928fcb1be 100644 --- a/.tools/test/stacks/plugin/typescript/lambda/export_logs.py +++ b/.tools/test/stacks/plugin/typescript/lambda/export_logs.py @@ -1,11 +1,11 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -import csv -import io import json import logging import os +import io +import csv import boto3 From 0a325ab48709f5b2400ab5fe64a192767111f1c1 Mon Sep 17 00:00:00 2001 From: ford prior Date: Thu, 12 Dec 2024 12:42:29 -0500 Subject: [PATCH 25/26] Update cdk.context.json --- .../stacks/plugin/typescript/cdk.context.json | 837 +----------------- 1 file changed, 1 insertion(+), 836 deletions(-) diff --git a/.tools/test/stacks/plugin/typescript/cdk.context.json b/.tools/test/stacks/plugin/typescript/cdk.context.json index a88fad9fb6b..d7bec304dfe 100644 --- a/.tools/test/stacks/plugin/typescript/cdk.context.json +++ b/.tools/test/stacks/plugin/typescript/cdk.context.json @@ -1,838 +1,3 @@ { - "acknowledged-issue-numbers": [ - 19836, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885, - 31885 - ], - "vpc-provider:account=808326389482:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-01ba5951652359b1e", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "808326389482", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-01bff8ca0688ecaa1", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-06473eb82596c88b9" - }, - { - "subnetId": "subnet-006b4b85100dc8477", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-06473eb82596c88b9" - }, - { - "subnetId": "subnet-05354b5d36d03dede", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-06473eb82596c88b9" - }, - { - "subnetId": "subnet-0ceb9d90b1f31e337", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-06473eb82596c88b9" - }, - { - "subnetId": "subnet-07c70ddf43d609a6a", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-06473eb82596c88b9" - }, - { - "subnetId": "subnet-0f2db8cea8a43dcda", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-06473eb82596c88b9" - } - ] - } - ] - }, - "vpc-provider:account=260778392212:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0c4a8ac37436f701a", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "260778392212", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0155e9561863fdcca", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0453fcf7da42da759" - }, - { - "subnetId": "subnet-099591e1bbcc9328c", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0453fcf7da42da759" - }, - { - "subnetId": "subnet-01956184cb64b1999", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0453fcf7da42da759" - }, - { - "subnetId": "subnet-069f1c45108e64148", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0453fcf7da42da759" - }, - { - "subnetId": "subnet-05ddd6f4a04ad984c", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0453fcf7da42da759" - }, - { - "subnetId": "subnet-06a225ba1d4ccfa85", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0453fcf7da42da759" - } - ] - } - ] - }, - "vpc-provider:account=770244195820:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-084deb81b434c759e", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "770244195820", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0ba7d4d25db77e38f", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0672ddf96986778fc" - }, - { - "subnetId": "subnet-0edb01048cedcb80d", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0672ddf96986778fc" - }, - { - "subnetId": "subnet-0e09e8c9b0496aaeb", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0672ddf96986778fc" - }, - { - "subnetId": "subnet-003dd86213b5e04fa", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0672ddf96986778fc" - }, - { - "subnetId": "subnet-09210819dc29a8945", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0672ddf96986778fc" - }, - { - "subnetId": "subnet-0c9b11188c4c66a5d", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0672ddf96986778fc" - } - ] - } - ] - }, - "vpc-provider:account=471951630130:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-098a56f71b4485384", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "471951630130", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0953a7cf7307ac0d3", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-00ef5897c7943faa3" - }, - { - "subnetId": "subnet-0133d9f167e609ca9", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-00ef5897c7943faa3" - }, - { - "subnetId": "subnet-090420b785f4a77eb", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-00ef5897c7943faa3" - }, - { - "subnetId": "subnet-0bef1a967454fbf4d", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-00ef5897c7943faa3" - }, - { - "subnetId": "subnet-028dd5c6c29304b8b", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-00ef5897c7943faa3" - }, - { - "subnetId": "subnet-080238325900a3756", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-00ef5897c7943faa3" - } - ] - } - ] - }, - "vpc-provider:account=441997275833:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-061b27d706b5c1e01", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "441997275833", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-041f458270cfc3849", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-063b28cd40655625f" - }, - { - "subnetId": "subnet-09fdc850ea904081a", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-063b28cd40655625f" - }, - { - "subnetId": "subnet-05fa5bc76a7ea2a7b", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-063b28cd40655625f" - }, - { - "subnetId": "subnet-00a179dc09f230e7c", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-063b28cd40655625f" - }, - { - "subnetId": "subnet-04701f86c8159c0c3", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-063b28cd40655625f" - }, - { - "subnetId": "subnet-00011e6ceefe4e2cb", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-063b28cd40655625f" - } - ] - } - ] - }, - "vpc-provider:account=234521034040:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-04536b45fd35f83a8", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "234521034040", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-04ce78b4a5174cb29", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0947fc9db3a69e0a0" - }, - { - "subnetId": "subnet-00c4cd724e6717579", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0947fc9db3a69e0a0" - }, - { - "subnetId": "subnet-0508c1ed8fac7a2e8", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0947fc9db3a69e0a0" - }, - { - "subnetId": "subnet-007bb46fb8d35022c", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0947fc9db3a69e0a0" - }, - { - "subnetId": "subnet-086663a99a7f340bd", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0947fc9db3a69e0a0" - }, - { - "subnetId": "subnet-08b49e402f47fba90", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0947fc9db3a69e0a0" - } - ] - } - ] - }, - "vpc-provider:account=875008041426:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0b71c618f0e0a77ac", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "875008041426", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0c691dc57cb92fcd9", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0c778bde0d135405f" - }, - { - "subnetId": "subnet-09b243f3b17f95d11", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0c778bde0d135405f" - }, - { - "subnetId": "subnet-0bc541f5aee12af23", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0c778bde0d135405f" - }, - { - "subnetId": "subnet-06a052e9e61f5ab85", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0c778bde0d135405f" - }, - { - "subnetId": "subnet-0505e9feb530b88b0", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0c778bde0d135405f" - }, - { - "subnetId": "subnet-00513ba7a8c5800fb", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0c778bde0d135405f" - } - ] - } - ] - }, - "vpc-provider:account=667348412466:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-00853ea8890f3162a", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "667348412466", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-001c2ba191dfe3ea5", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0b5add4635925af8f" - }, - { - "subnetId": "subnet-03a321b213a18f743", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0b5add4635925af8f" - }, - { - "subnetId": "subnet-08646cf8b5512d2e7", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0b5add4635925af8f" - }, - { - "subnetId": "subnet-0820eacdd95a3087f", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0b5add4635925af8f" - }, - { - "subnetId": "subnet-0e020eddca0eed498", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0b5add4635925af8f" - }, - { - "subnetId": "subnet-046edb8aab65c4959", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0b5add4635925af8f" - } - ] - } - ] - }, - "vpc-provider:account=733931915187:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0f0f33dfa113d3698", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "733931915187", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0a3e72d40bffae28d", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-05bd1dcb6f9db265d" - }, - { - "subnetId": "subnet-0370623c688a201fc", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-05bd1dcb6f9db265d" - }, - { - "subnetId": "subnet-0d3d123b2d9bedb72", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-05bd1dcb6f9db265d" - }, - { - "subnetId": "subnet-012cbbdd769e56b6a", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-05bd1dcb6f9db265d" - }, - { - "subnetId": "subnet-06ee5f5198462e294", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-05bd1dcb6f9db265d" - }, - { - "subnetId": "subnet-004bbd4a5d57dfb4c", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-05bd1dcb6f9db265d" - } - ] - } - ] - }, - "vpc-provider:account=664857444588:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-080237fd0e3d63daa", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "664857444588", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0242c311b93817122", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-010e654fa5d042560" - }, - { - "subnetId": "subnet-08ebe88b1fc63d919", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-010e654fa5d042560" - }, - { - "subnetId": "subnet-09908c0f3cb796c18", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-010e654fa5d042560" - }, - { - "subnetId": "subnet-027b340b4ddbaf0f2", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-010e654fa5d042560" - }, - { - "subnetId": "subnet-0d5df3a56a119efdc", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-010e654fa5d042560" - }, - { - "subnetId": "subnet-0fed4e28df48ee839", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-010e654fa5d042560" - } - ] - } - ] - }, - "vpc-provider:account=616362385685:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-08ee07dce7c63bf8e", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "616362385685", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-04c062707fa14096f", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0d00b332c03516d18" - }, - { - "subnetId": "subnet-0a2f8448f7b06ba2e", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0d00b332c03516d18" - }, - { - "subnetId": "subnet-0588243e08389fcdc", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0d00b332c03516d18" - }, - { - "subnetId": "subnet-0063111637bf712c5", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0d00b332c03516d18" - }, - { - "subnetId": "subnet-088c5a5fb054755a0", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0d00b332c03516d18" - }, - { - "subnetId": "subnet-04e31672cf5067b3a", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0d00b332c03516d18" - } - ] - } - ] - }, - "vpc-provider:account=050288538048:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0b23bece52194a736", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "050288538048", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0ec13be6310435b39", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0d870d42e89e18a82" - }, - { - "subnetId": "subnet-0d032a80d5756e27d", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0d870d42e89e18a82" - }, - { - "subnetId": "subnet-0433f5fb07e32f772", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0d870d42e89e18a82" - }, - { - "subnetId": "subnet-050acfc9c95a9188a", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0d870d42e89e18a82" - }, - { - "subnetId": "subnet-0225aa4de572a5f6b", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0d870d42e89e18a82" - }, - { - "subnetId": "subnet-0d3c855aa3922e887", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0d870d42e89e18a82" - } - ] - } - ] - }, - "vpc-provider:account=099736152523:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0f4537826c7901385", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "099736152523", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-03fd2462569d7bad2", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0c86b082945019da1" - }, - { - "subnetId": "subnet-0cc9c9e46fc9d266e", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0c86b082945019da1" - }, - { - "subnetId": "subnet-087ca9a561bfd1c57", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-0c86b082945019da1" - }, - { - "subnetId": "subnet-065b22949685416d4", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-0c86b082945019da1" - }, - { - "subnetId": "subnet-0066c514c9f5a8b0e", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-0c86b082945019da1" - }, - { - "subnetId": "subnet-0491770e01e6800a0", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-0c86b082945019da1" - } - ] - } - ] - }, - "vpc-provider:account=637397754108:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0f9547bd3488be803", - "vpcCidrBlock": "172.31.0.0/16", - "ownerAccountId": "637397754108", - "availabilityZones": [], - "subnetGroups": [ - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0b10504ae3bc266c3", - "cidr": "172.31.32.0/20", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-03ec4d91fc6fffdb6" - }, - { - "subnetId": "subnet-0edbeaf80a03d9a4e", - "cidr": "172.31.0.0/20", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-03ec4d91fc6fffdb6" - }, - { - "subnetId": "subnet-0b6c352a601a032c0", - "cidr": "172.31.80.0/20", - "availabilityZone": "us-east-1c", - "routeTableId": "rtb-03ec4d91fc6fffdb6" - }, - { - "subnetId": "subnet-0344d48399800383a", - "cidr": "172.31.16.0/20", - "availabilityZone": "us-east-1d", - "routeTableId": "rtb-03ec4d91fc6fffdb6" - }, - { - "subnetId": "subnet-0c9303ae23822d49e", - "cidr": "172.31.48.0/20", - "availabilityZone": "us-east-1e", - "routeTableId": "rtb-03ec4d91fc6fffdb6" - }, - { - "subnetId": "subnet-065922888cf7c0760", - "cidr": "172.31.64.0/20", - "availabilityZone": "us-east-1f", - "routeTableId": "rtb-03ec4d91fc6fffdb6" - } - ] - } - ] - } + "acknowledged-issue-numbers": [ 19836, 31885 ] } From bf6891804ad8894d070093570046129bbff71aa8 Mon Sep 17 00:00:00 2001 From: ford prior Date: Thu, 12 Dec 2024 12:44:10 -0500 Subject: [PATCH 26/26] Update plugin_stack.ts --- .tools/test/stacks/plugin/typescript/plugin_stack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tools/test/stacks/plugin/typescript/plugin_stack.ts b/.tools/test/stacks/plugin/typescript/plugin_stack.ts index 192e512f36e..42357ad94fa 100644 --- a/.tools/test/stacks/plugin/typescript/plugin_stack.ts +++ b/.tools/test/stacks/plugin/typescript/plugin_stack.ts @@ -314,7 +314,7 @@ class PluginStack extends cdk.Stack { // Define the Lambda function. const lambdaFunction = new lambda.Function(this, "BatchJobCompleteLambda", { - runtime: lambda.Runtime.PYTHON_3_8, + runtime: lambda.Runtime.PYTHON_3_9, handler: "export_logs.handler", role: executionRole, code: lambda.Code.fromAsset("lambda"),