Skip to content

Commit

Permalink
fix(ecs): new arn format not supported (under feature flag) (aws#18140)
Browse files Browse the repository at this point in the history
AWS has changed the [ARN format for ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-account-settings.html#ecs-resource-ids). Currently, CDK doesn't return the correct values/ARNs if the new ARN format is used in ECS.

Changed methods:
- `Ec2Service.fromEc2ServiceAttributes()`
- `Ec2Service.fromEc2ServiceArn()`
- `FargateService.fromFargateServiceAttributes()`
- `FargateService.fromFargateServiceArn()`

The logic automatically detects whether the old or new format is used. The format cannot be recognized automatically for tokenized values. Therefore the feature flag `ECS_ARN_FORMAT_INCLUDES_CLUSTER_NAME` is introduced, which controls whether the old or the new format should be used.

In `Ec2Service.fromEc2ServiceAttributes()` and `FargateService.fromFargateServiceAttributes()` an ARN is created. In these methods the feature flag be considered to construct the ARN in the correct format.

Closes aws#16634.
Closes aws#18137.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*

cr: https://code.amazon.com/reviews/CR-73367009
  • Loading branch information
jumic authored and Theron Mansilla committed Jul 28, 2022
1 parent 82e8100 commit e0288b9
Show file tree
Hide file tree
Showing 7 changed files with 616 additions and 19 deletions.
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,37 @@ There are two higher-level constructs available which include a load balancer fo
- `LoadBalancedFargateService`
- `LoadBalancedEc2Service`

### Import existing services

`Ec2Service` and `FargateService` provide methods to import existing EC2/Fargate services.
The ARN of the existing service has to be specified to import the service.

Since AWS has changed the [ARN format for ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-account-settings.html#ecs-resource-ids),
feature flag `@aws-cdk/aws-ecs:arnFormatIncludesClusterName` must be enabled to use the new ARN format.
The feature flag changes behavior for the entire CDK project. Therefore it is not possible to mix the old and the new format in one CDK project.

```tss
declare const cluster: ecs.Cluster;
// Import service from EC2 service attributes
const service = ecs.Ec2Service.fromEc2ServiceAttributes(stack, 'EcsService', {
serviceArn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service',
cluster,
});
// Import service from EC2 service ARN
const service = ecs.Ec2Service.fromEc2ServiceArn(stack, 'EcsService', 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service');
// Import service from Fargate service attributes
const service = ecs.FargateService.fromFargateServiceAttributes(stack, 'EcsService', {
serviceArn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service',
cluster,
});
// Import service from Fargate service ARN
const service = ecs.FargateService.fromFargateServiceArn(stack, 'EcsService', 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service');
```

## Task Auto-Scaling

You can configure the task count of a service to match demand. Task auto-scaling is
Expand Down
29 changes: 26 additions & 3 deletions packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArnFormat, Resource, Stack } from '@aws-cdk/core';
import { ArnFormat, FeatureFlags, Fn, Resource, Stack, Token } from '@aws-cdk/core';
import { ECS_ARN_FORMAT_INCLUDES_CLUSTER_NAME } from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import { IBaseService } from '../base/base-service';
import { ICluster } from '../cluster';
Expand Down Expand Up @@ -32,22 +33,25 @@ export function fromServiceAttributes(scope: Construct, id: string, attrs: Servi
throw new Error('You can only specify either serviceArn or serviceName.');
}

const newArnFormat = FeatureFlags.of(scope).isEnabled(ECS_ARN_FORMAT_INCLUDES_CLUSTER_NAME);

const stack = Stack.of(scope);
let name: string;
let arn: string;
if (attrs.serviceName) {
name = attrs.serviceName as string;
const resourceName = newArnFormat ? `${attrs.cluster.clusterName}/${attrs.serviceName}` : attrs.serviceName as string;
arn = stack.formatArn({
partition: stack.partition,
service: 'ecs',
region: stack.region,
account: stack.account,
resource: 'service',
resourceName: name,
resourceName,
});
} else {
arn = attrs.serviceArn as string;
name = stack.splitArn(arn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string;
name = extractServiceNameFromArn(scope, arn);
}
class Import extends Resource implements IBaseService {
public readonly serviceArn = arn;
Expand All @@ -58,3 +62,22 @@ export function fromServiceAttributes(scope: Construct, id: string, attrs: Servi
environmentFromArn: arn,
});
}

export function extractServiceNameFromArn(scope: Construct, arn: string): string {
const newArnFormat = FeatureFlags.of(scope).isEnabled(ECS_ARN_FORMAT_INCLUDES_CLUSTER_NAME);
const stack = Stack.of(scope);

if (Token.isUnresolved(arn)) {
if (newArnFormat) {
const components = Fn.split(':', arn);
const lastComponents = Fn.split('/', Fn.select(5, components));
return Fn.select(2, lastComponents);
} else {
return stack.splitArn(arn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string;
}
} else {
const resourceName = stack.splitArn(arn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string;
const resourceNameSplit = resourceName.split('/');
return resourceNameSplit.length === 1 ? resourceName : resourceNameSplit[1];
}
}
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import { ArnFormat, Lazy, Resource, Stack } from '@aws-cdk/core';
import { Lazy, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service';
import { fromServiceAttributes } from '../base/from-service-attributes';
import { fromServiceAttributes, extractServiceNameFromArn } from '../base/from-service-attributes';
import { NetworkMode, TaskDefinition } from '../base/task-definition';
import { ICluster } from '../cluster';
import { CfnService } from '../ecs.generated';
Expand Down Expand Up @@ -128,7 +128,7 @@ export class Ec2Service extends BaseService implements IEc2Service {
public static fromEc2ServiceArn(scope: Construct, id: string, ec2ServiceArn: string): IEc2Service {
class Import extends Resource implements IEc2Service {
public readonly serviceArn = ec2ServiceArn;
public readonly serviceName = Stack.of(scope).splitArn(ec2ServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string;
public readonly serviceName = extractServiceNameFromArn(this, ec2ServiceArn);
}
return new Import(scope, id);
}
Expand Down
5 changes: 2 additions & 3 deletions packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as ec2 from '@aws-cdk/aws-ec2';
import * as cdk from '@aws-cdk/core';
import { ArnFormat } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { BaseService, BaseServiceOptions, DeploymentControllerType, IBaseService, IService, LaunchType } from '../base/base-service';
import { fromServiceAttributes } from '../base/from-service-attributes';
import { fromServiceAttributes, extractServiceNameFromArn } from '../base/from-service-attributes';
import { TaskDefinition } from '../base/task-definition';
import { ICluster } from '../cluster';

Expand Down Expand Up @@ -105,7 +104,7 @@ export class FargateService extends BaseService implements IFargateService {
public static fromFargateServiceArn(scope: Construct, id: string, fargateServiceArn: string): IFargateService {
class Import extends cdk.Resource implements IFargateService {
public readonly serviceArn = fargateServiceArn;
public readonly serviceName = cdk.Stack.of(scope).splitArn(fargateServiceArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string;
public readonly serviceName = extractServiceNameFromArn(this, fargateServiceArn);
}
return new Import(scope, id);
}
Expand Down
Loading

0 comments on commit e0288b9

Please sign in to comment.