aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.5k stars 3.85k forks source link

ecs.ExternalService: enableExecuteCommand is shown as not supported (while in fact it is supported) #31181

Open hrko opened 3 weeks ago

hrko commented 3 weeks ago

Describe the bug

When creating an ExternalService, specifying the enableExecuteCommand: true option for the property will result in the following error when running cdk synth.

Error: Enable Execute Command options are not supported for External service

However, the enableExecuteCommand option is actually supported by the ExternalService as well, and can be manually enabled using the AWS CLI.

Regression Issue

Last Known Working CDK Version

No response

Expected Behavior

When creating an ExternalService, the enableExecuteCommand: true option can be set for the property.

Current Behavior

When creating an ExternalService, setting the enableExecuteCommand: true option to the property causes an error when executing cdk synth.

Reproduction Steps

A code snippet that reproduces the problem is like as follows:

    const ecsService = new ecs.ExternalService(this, 'ExternalService', {
      serviceName: "EcsAnywhereService",
      cluster: EcsAnywhereCluster,
      taskDefinition,
      desiredCount: 1,
      enableExecuteCommand: true,
    });
Click here to show entire stack code

```ts import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as iam from "aws-cdk-lib/aws-iam"; import { CfnOutput } from 'aws-cdk-lib/core'; export class PocEcsAnywhereStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new ec2.Vpc(this, "EcsAnywhereVPC", { ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), natGateways: 0, maxAzs: 2, subnetConfiguration: [ { cidrMask: 24, name: "EcsAnywherePublic", subnetType: ec2.SubnetType.PUBLIC, }, { cidrMask: 24, name: "EcsAnywherePrivate", subnetType: ec2.SubnetType.PRIVATE_ISOLATED, } ] } ) // ECS Cluster const EcsAnywhereCluster = new ecs.Cluster(this, "EcsAnywhereCluster", { vpc: vpc, clusterName: "EcsAnywhereCluster", }) // Task role const taskRole = new iam.Role(this, 'TaskRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), }); taskRole.addToPolicy(new iam.PolicyStatement({ actions: [ // for ECS Exec 'ssmmessages:CreateControlChannel', 'ssmmessages:CreateDataChannel', 'ssmmessages:OpenControlChannel', 'ssmmessages:OpenDataChannel', ], resources: ['*'], })); // ECS task definition const taskDefinition = new ecs.ExternalTaskDefinition(this, 'ExternalTaskDefinition', { // executionRole: taskExecutionRole, taskRole: taskRole, }); taskDefinition.addContainer('NginxContainer', { image: ecs.ContainerImage.fromRegistry( "public.ecr.aws/nginx/nginx:latest" ), cpu: 256, memoryLimitMiB: 512, containerName: "EcsAnywhereContainer", // for ECS Exec linuxParameters: new ecs.LinuxParameters(this, 'LinuxParameters', { initProcessEnabled: true, }), }) // ECS service const ecsService = new ecs.ExternalService(this, 'ExternalService', { serviceName: "EcsAnywhereService", cluster: EcsAnywhereCluster, taskDefinition, desiredCount: 1, enableExecuteCommand: true, // for ECS Exec }); // Instance role for ECS Anywhere const instance_iam_role = new iam.Role(this, 'EcsAnywhereInstanceRole', { roleName: "EcsAnywhereInstanceRole", assumedBy: new iam.ServicePrincipal("ssm.amazonaws.com"), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"), iam.ManagedPolicy.fromManagedPolicyArn(this, "EcsAnywhereEC2Policy", "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"), ] }) // Output new CfnOutput(this, "Step1RegisterExternalInstance", { description: "Create an Systems Manager activation pair", value: `aws ssm create-activation --iam-role ${instance_iam_role.roleName}`, exportName: "1-RegisterExternalInstance", }) const ecsAnywhereInstallScriptUrl = "https://amazon-ecs-agent.s3.amazonaws.com/ecs-anywhere-install-latest.sh" new CfnOutput(this, "Step2DownloadInstallationScript", { description: "On your VM, download installation script", value: `curl -o "ecs-anywhere-install.sh" "${ecsAnywhereInstallScriptUrl}" && sudo chmod +x ecs-anywhere-install.sh`, exportName: "2-DownloadInstallationScript", }); new CfnOutput(this, "Step3ExecuteScript", { description: "Run installation script on VM", value: `sudo ./ecs-anywhere-install.sh --region ${this.region} --cluster ${EcsAnywhereCluster.clusterName} --activation-id $ACTIVATION_ID --activation-code $ACTIVATION_CODE`, exportName: "3-ExecuteInstallationScript", }); } } ```

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.154.0 (build 0fc07f3)

Framework Version

No response

Node.js Version

v20.17.0

OS

Debian GNU/Linux 11 (bullseye)

Language

TypeScript

Language Version

No response

Other information

No response

ashishdhingra commented 3 weeks ago

Reproducible using customer code.

Error: Enable Execute Command options are not supported for External service
    at new ExternalService (/Users/testuser/dev/repros/cdk/cdktest/node_modules/aws-cdk-lib/aws-ecs/lib/external/external-service.js:1:2388)
    at new EcsAnywhereStack (/Users/testuser/dev/repros/cdk/cdktest/lib/ecs-anywhere-stack.ts:74:24)
    at Object.<anonymous> (/Users/testuser/dev/repros/cdk/cdktest/bin/cdktest.ts:30:1)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module.m._compile (/Users/testuser/dev/repros/cdk/cdktest/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/testuser/dev/repros/cdk/cdktest/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)

The exception is thrown due to validation check here.

Looking at the commit history, the validation is in place since last 3 years per commit https://github.com/aws/aws-cdk/commit/3592b26c5806cc31cd6ad0ebba32cbf4d09b9abf.

@hrko Good afternoon. Could you please point me to documentation or example which demonstrates that enableExecuteCommand is supported for ExternalService (this service essentially uses LaunchType.EXTERNAL)?

Thanks, Ashish

hrko commented 3 weeks ago

@ashishdhingra Thank you for your response.

As an example, extract a CfnService from an ExternalService and then enableExecuteCommand can be enabled for that CfnService. The example is shown in the following code:

     const ecsService = new ecs.ExternalService(this, 'ExternalService', {
       serviceName: "EcsAnywhereService",
       cluster: EcsAnywhereCluster,
       taskDefinition,
       desiredCount: 1,
     });
+    const cfnService = ecsService.node.defaultChild as ecs.CfnService;
+    cfnService.enableExecuteCommand = true;

Once the above example code is deployed, you can use aws ecs execute-command to connect to an container in the external service.

(I did not find either a statement in the documentation that ExternalService supports enableExecuteCommand, or that it does not.)

ashishdhingra commented 2 weeks ago

@hrko Thanks for sharing your inputs. I was able to run cdk deploy successfully after adding the escape hatch as you suggested, thereafter verifying enableExecuteCommand being set to true using AWS CLI command aws ecs describe-services --cluster EcsAnywhereCluster --region us-east-2 --services EcsAnywhereService. Also noticed that documentation at Amazon ECS clusters for the external launch type mentioning ECS Exec is supported on external instances.. So looks like the validation should be removed.

Thanks, Ashish