-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ecs): add BaseService.fromServiceArnWithCluster()
for use in CodePipeline
#18530
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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!
@@ -315,6 +315,44 @@ export interface IBaseService extends IService { | |||
*/ | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird formatting here:
public readonly cluster = cluster; | |
} | |
public readonly cluster = cluster; | |
} | |
@@ -105,6 +105,38 @@ export class Cluster extends Resource implements ICluster { | |||
return new ImportedCluster(scope, id, attrs); | |||
} | |||
|
|||
/** | |||
* Import an existing cluster to the stack from the cluster ARN. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a note in these docs that the Cluster returned does not allow accessing the vpc
property.
ecs.FargateService.fromServiceArnWithCluster(stack, 'FargateService', serviceArn); | ||
}).toThrowError(`resource name ${serviceName} from service ARN`); | ||
}); | ||
|
||
test('allows setting enable execute command', () => { | ||
// GIVEN | ||
const stack = new cdk.Stack(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 anIBaseService
which can be used in theEcsDeployAction
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