feat(ecs): add BaseService.fromServiceArnWithCluster() for use in CodePipeline#18530
feat(ecs): add BaseService.fromServiceArnWithCluster() for use in CodePipeline#18530mergify[bot] merged 13 commits intoaws:masterfrom
BaseService.fromServiceArnWithCluster() for use in CodePipeline#18530Conversation
skinny85
left a comment
There was a problem hiding this comment.
Thanks for the contribution @tobytipton!
I thought (and researched!) the issue some more, and I think I came to the following conclusion.
The ideal experience we want is:
// the name fromServiceNewFormatArn() is just a proposal, of course
const myBaseService: IBaseService = ???.fromServiceNewFormatArn(this, 'Service',
'arn:aws:ecs:us-west-2:123456:service/my-cluster/my-service');This way, myBaseService can be passed into EcsDeployAction, and the only thing we need is the ARN of the ECS Service in the new format to make that happen, no more bullshit with VPCs, etc.
Of course, the question is what class should be substituted for ???.
Like we discussed in #18059, we can't really make that happen in FargateService.fromFargateServiceArn(), nor in Ec2Service.fromEc2ServiceArn(), because those return IService, and changing that can have other consequences.
But, I think I found another great candidate - BaseService!
So, if we add that method there, I think this will solve the CodePipeline usecase that we're after here.
However, if you agree we should go with this new static method in BaseService, I think what we want in Cluster is actually fromClusterName(), not fromClusterArn(), because that will save us the hassle of re-building the ARN, just for it to be parsed again.
What do you think about this direction @tobytipton?
(Of course, we should validate in ???.fromServiceNewFormatArn() that the ARN is indeed in the new ECS format)
| const clusterName = 'my-cluster'; | ||
| const region = 'service-region'; | ||
| const account = 'service-account'; | ||
| const clusterArn = `arn:aws:ecs:${region}:${account}:service/${clusterName}`; |
There was a problem hiding this comment.
This is not the Cluster ARN (it should be arn:aws:ecs:<region>:<account>:cluster/<cluster-name>).
| */ | ||
| public static fromClusterArn(scope: Construct, id: string, clusterArn: string): ICluster { | ||
| const stack = Stack.of(scope); | ||
| const clusterName = stack.splitArn(clusterArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName as string; |
There was a problem hiding this comment.
Instead of this cast, let's just handle the case when resourceName is undefined (throw an exception).
See here for similar code in other modules.
This will also require a separate unit test.
Pull request has been modified.
At first thought The IBaseService requires an cluster as an ICluster, which would mean I think Also I would wonder if the name If you are good with the |
Every resource has the account and region of the Stack they are part of available, so constructing the ARN will not be an issue. We have many
Not sure what you mean. The method you added in this PR already deals with
Yes, let's add the |
You are referencing using the scope to generate the Arn, like this correct? Which is leveraging the scope to generate the clusterArn. In the case where we have a pipeline which is cross region/account the scope would typically be related to the pipeline itself which would result in the clusterArn to have the pipeline region/account rather than the expect service region/account. The idea would be in the pipeline stack there would be something like Which means the scope would be related to the pipeline stack not the stack which has the env for the service-region and service-account. Which is why I am thinking leveraging |
|
Yes, you are correct @tobytipton. My bad. Let's leave |
|
Updated with |
skinny85
left a comment
There was a problem hiding this comment.
Looks great @tobytipton! I have some minor comments on the tests and docs, but the functionality itself is great. This will make it sooo much easier to use the EcsDeployAction in the CDK - awesome work!
| export abstract class BaseService extends Resource | ||
| implements IBaseService, elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget, elb.ILoadBalancerTarget { | ||
| /** | ||
| * Import an existing ECS/Fargate Service using the service cluster format. |
There was a problem hiding this comment.
Let's add a note in the docs that the service ARN must be in the "new" format, with maybe a link to the AWS docs about it.
| if (resourceName.split('/').length !== 2) { | ||
| throw new Error(`resource name ${resourceName} from service ARN: ${serviceArn} is not using the ARN cluster format`); | ||
| } | ||
| const clusterName = resourceName.split('/')[0]; | ||
| const serviceName = resourceName.split('/')[1]; |
There was a problem hiding this comment.
We're repeating the resourceName.split('/') expression 3 times here.
Let's assign it to a local variable instead.
| public readonly cluster = cluster; | ||
|
|
||
| } |
There was a problem hiding this comment.
Weird formatting here:
| public readonly cluster = cluster; | |
| } | |
| public readonly cluster = cluster; | |
| } | |
| } | ||
|
|
||
| /** | ||
| * Import an existing cluster to the stack from the cluster ARN. |
There was a problem hiding this comment.
Let's add a note in these docs that the Cluster returned does not allow accessing the vpc property.
|
|
||
| test('allows setting enable execute command', () => { | ||
| // GIVEN | ||
| const stack = new cdk.Stack(); |
There was a problem hiding this comment.
What do you think of adding a simple test in the @aws-cdk/aws-codepipeline-actions module for this? We already have a test file for the EcsDeployAction class.
There was a problem hiding this comment.
Added the test, did it for cross account and region.
| test('throws error when import ECS Cluster without resource name in arn', () => { | ||
| // GIVEN | ||
| const stack = new cdk.Stack(); | ||
| const clusterArn = 'arn:aws:ecs:service-region:service-account:cluster'; |
There was a problem hiding this comment.
Again, I would probably inline this variable.
| const clusterName = 'my-cluster'; | ||
| const region = 'service-region'; | ||
| const account = 'service-account'; | ||
| const clusterArn = `arn:aws:ecs:${region}:${account}:cluster/${clusterName}`; |
There was a problem hiding this comment.
I would also inline this variable.
| describe('When import an ECS Service', () => { | ||
| test('with serviceArnWithCluster', () => { | ||
| // GIVEN | ||
| const stack = new cdk.Stack(); |
There was a problem hiding this comment.
Since we're writing brand-new tests here, let's make them beautiful by extracting this const stack = new cdk.Stack(); line that is repeated in every test to a beforeEach() method.
| //THEN | ||
| expect(() => { | ||
| ecs.BaseService.fromServiceArnWithCluster(stack, 'Service', serviceArn); | ||
| }).toThrowError(`resource name ${serviceName} from service ARN`); |
There was a problem hiding this comment.
Kind of a weird error message to assert here, but OK (I would probably write an assertion on the "is not using the ARN cluster format" part of the error message, seems more relevant to this test).
|
The build should succeed when you update from |
Pull request has been modified.
| actionName: 'DeployAction', | ||
| service: service, | ||
| input: buildOutput, | ||
| }), |
There was a problem hiding this comment.
We might want to add an note in here that for cross accounts you will want to provide the role, because if not a role name will be generated for you which doesn't actually get created, what do you think?
There was a problem hiding this comment.
Hmm, not sure I understand what you mean...?
If the Action is cross-account, a new Role, with a well-known name, will be generated for you in the correct account (if you don't specify the role property).
There was a problem hiding this comment.
If you don't provide role on the ECSDeployAction a roleArn will be provided in the code pipeline actions for you.
If you are deploying across accounts, that role will most likely not exist in the other account.
From my ecs deploy action test.
RoleArn: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':iam::service-account:role/pipelinestack-support-serloyecsactionrole49867f847238c85af7c0',
],
],
In order for the deployment to work I would need to create an IAM Role with the name pipelinestack-support-serloyecsactionrole49867f847238c85af7c0 in the service account, in order for the pipeline to be able to deploy.
In a self-mutating CDK pipeline the self mutation will actually fail because the account doesn't exist so it can't update the Policy for the KMS key, bucket and Pipeline Role's Policy. I would guess an normal pipeline might have the same issue as well.
Providing an IAM role which is already created in the other account resolves that, typically we have been leveraging the CDK bootstrap deploy role and adding permissions to deploy ECS matching this is typically how we have been working around this. Write up from other PR on role.
There was a problem hiding this comment.
I mean, that's not just some random Role name that we just make up 🙂. There is another Stack in the application that gets generated, in the target account, that contains that Role, and the Pipeline Stack depends on that Stack, so cdk deploy PipelineStack should correctly include it, deploy it first, and then everything should work.
If it doesn't, that's a bug we need to fix.
However, it's fine if you want to mention the Role in the ReadMe.
There was a problem hiding this comment.
Interesting, I guess I never noticed that other support stack being created for the service account, to create that role. I have see the other stack which is in the other region from the pipeline which gets generated in the pipeline account. I added a little blurb about the role, I know I have seen issues with the CDK pipeline when adding a new region having an issue if the role referenced hasn't been created.
skinny85
left a comment
There was a problem hiding this comment.
Looks great @tobytipton. I only have a few comments on the wording of the docs, but I'll just fix those myself, and merge this in. Awesome work!
Pull request has been modified.
BaseService.fromServiceArnWithCluster() for use in CodePipeline
|
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
|
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
* origin/master: (31 commits) feat(iotevents): add DetectorModel L2 Construct (aws#18049) feat(ecs): add `BaseService.fromServiceArnWithCluster()` for use in CodePipeline (aws#18530) docs(s3): remove vestigial documentation (aws#18604) chore(cli): LogMonitor test fails randomly due to Date.now() (aws#18601) chore(codedeploy): migrate tests to use the Assertions module (aws#18585) docs(stepfunctions): fix typo (aws#18583) chore(eks-legacy): migrate tests to `assertions` (aws#18596) fix(cli): hotswap should wait for lambda's `updateFunctionCode` to complete (aws#18536) fix(apigatewayv2): websocket api: allow all methods in grant manage connections (aws#18544) chore(dynamodb): migrate tests to assertions (aws#18560) fix(aws-apigateway): cross region authorizer ref (aws#18444) feat(lambda-nodejs): Allow setting mainFields for esbuild (aws#18569) docs(cfnspec): update CloudFormation documentation (aws#18587) feat(assertions): support for conditions (aws#18577) fix(ecs-patterns): Fix Network Load Balancer Port assignments in ECS Patterns (aws#18157) chore(region-info): ap-southeast-3 (Jakarta) ELBV2_ACCOUNT (aws#18300) fix(pipelines): CodeBuild projects are hard to tell apart (aws#18492) fix(ecs): only works in 'aws' partition (aws#18496) chore(app-delivery): migrate unit tests to Assertions (aws#18574) chore: migrate kaizen3031593's modules to assertions (aws#18205) ...
…odePipeline (aws#18530) This adds support for importing a ECS Cluster via the Arn, and not requiring the VPC or Security Groups. This will generate an ICluster which can be used in `Ec2Service.fromEc2ServiceAttributes()` and `FargateService.fromFargateServiceAttributes()` to get an `IBaseService` which can be used in the `EcsDeployAction` to allow for cross account/region deployments in CodePipelines. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…odePipeline (aws#18530) This adds support for importing a ECS Cluster via the Arn, and not requiring the VPC or Security Groups. This will generate an ICluster which can be used in `Ec2Service.fromEc2ServiceAttributes()` and `FargateService.fromFargateServiceAttributes()` to get an `IBaseService` which can be used in the `EcsDeployAction` to allow for cross account/region deployments in CodePipelines. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
This adds support for importing a ECS Cluster via the Arn, and not requiring the VPC or Security Groups.
This will generate an ICluster which can be used in
Ec2Service.fromEc2ServiceAttributes()andFargateService.fromFargateServiceAttributes()to get anIBaseServicewhich can be used in theEcsDeployActionto allow for cross account/region deployments in CodePipelines.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license