[aws-codepipeline] CodePipeline with Cross Account Deployment
See original GitHub issue❓ General Issue
We are creating a CodePipeline with CDK same as described in the official documentation. We have 2 stages which are Alpha and Prod where the resources of Alpha are created in developer account and Prod resources are created in Prod account which is a separate account.
The Question
I can successfully spin up a CodePipeline with including stages like;
Source --> (Lambda_Build & CFN_Build) --> Deploy_Alpha --> Deploy_Beta
.
Now the problem is I couldn’t manage this CodePipeline to deploy in our Prod account which is another action right after Deploy_Beta
stage. I also followed API Reference documentation which shows two alternative ways for dealing cross account deployments.
When I set account property for CloudFormationCreateUpdateStackAction
by giving Prod account id I am getting an error. Here is the CloudFormationCreateUpdateStackAction
including account parameter and the error stack trace.
StageProps.builder()
.stageName("Deploy_Prod")
.actions(
Arrays.asList(
CloudFormationCreateUpdateStackAction.Builder.create()
.adminPermissions(true)
.account("123123123123") // Prod account id
.actionName("Lambda_CFN_Prod_Deploy")
.templatePath(cdkBuildOutput.atPath("lambdaStackProd.template.json"))
.parameterOverrides(envToCfnCodeParametersCodeMap.get("prod").assign(lambdaBuildOutput.getS3Location()))
.extraInputs(Arrays.asList(lambdaBuildOutput))
.stackName("lambdaStackProd")
.build()
)
)
.build()
Exception in thread "main" software.amazon.jsii.JsiiException: Pipeline stack which uses cross-environment actions must have an explicitly set account
Error: Pipeline stack which uses cross-environment actions must have an explicitly set account
On the other hand, if I assign LamdaStack instances to a variable in my Main class and pass LambdaStack instance for prod into LambdaPipelineStack then I am getting circular dependency error while creating IRole.
IRole crossAccountAssumeRole = new Role(otherAccountStack, "CDKPipelineRole",
RoleProps.builder()
.assumedBy(new AccountPrincipal("214839824702")) // developer account
.roleName("CDKPipelineRole")
.build());
And this is the circular dependency error I am getting;
Exception in thread "main" software.amazon.jsii.JsiiException: 'lambdaStackPipeline' depends on 'lambdaStackProd' (dependency added using stack.addDependency()). Adding this dependency (lambdaStackProd -> lambdaStackPipeline/lambdaPipeline/Deploy_Prod/Lambda_CFN_Prod_Deploy/Role/Resource.Arn) would create a cyclic reference.
Error: 'lambdaStackPipeline' depends on 'lambdaStackProd' (dependency added using stack.addDependency()). Adding this dependency (lambdaStackProd -> lambdaStackPipeline/lambdaPipeline/Deploy_Prod/Lambda_CFN_Prod_Deploy/Role/Resource.Arn) would create a cyclic reference.
We also created an Assume Role from Prod account and added Dev account as Trusted entity with Role Name CDKPipelineRole
.
Please let me know if you need more information from my side. I really appreciate for your help. Thanks!
Environment
- CDK CLI Version: 1.52.0
- Module Version:
- Node.js Version: v14.5.0
- OS: OSX Catalina
- Language (Version): Java (8)
Other information
Here is my Main class to create LambdaStacks and LambdaPipelineStack
LambdaApiMain.java
public final class LambdaApiMain {
public static void main(final String args[]) {
final App app = new App();
Map<String, CfnParametersCode> envToCfnParameterCodeMap = new HashMap<>();
envToCfnParameterCodeMap.put("alfa", CfnParametersCode.fromCfnParameters());
envToCfnParameterCodeMap.put("prod", CfnParametersCode.fromCfnParameters());
new LambdaStack(app, "LambdaStackAlfa", null, envToCfnParameterCodeMap.get("alfa"));
new LambdaStack(app, "LambdaStackProd", null, envToCfnParameterCodeMap.get("prod"));
new LambdaPipelineStack(app, "LambdaStackPipeline", envToCfnParameterCodeMap, "lambda-api");
app.synth();
}
}
LambdaPipelineStack.java
public class LambdaPipelineStack extends Stack {
public LambdaPipelineStack(App app,
String id,
Map<String, CfnParametersCode> envToCfnCodeParametersCodeMap,
String repoName) {
this(app, id, null, envToCfnCodeParametersCodeMap, repoName);
}
public LambdaPipelineStack(App app,
String id,
StackProps stackProps,
Map<String, CfnParametersCode> envToCfnCodeParametersCodeMap,
String repoName) {
super(app, id, stackProps);
IRepository repository = Repository.fromRepositoryName(this, repoName, repoName);
IBucket bucket = Bucket.fromBucketName(this, "api-codepipeline", "api-codepipeline");
PipelineProject lambdaBuild = PipelineProject.Builder.create(this, "LambdaApiBuild")
.buildSpec(BuildSpec.fromSourceFilename("lambda-buildspec.yml"))
.environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_3_0).build())
.build();
PipelineProject cdkBuild = PipelineProject.Builder.create(this, "LambdaApiCDKBuild")
.buildSpec(BuildSpec.fromSourceFilename("cdk-buildspec.yml"))
.environment(BuildEnvironment.builder().buildImage(LinuxBuildImage.STANDARD_3_0).build())
.build();
Artifact sourceOutput = new Artifact();
Artifact cdkBuildOutput = new Artifact("CdkBuildOutput");
Artifact lambdaBuildOutput = new Artifact("LambdaBuildOutput");
Pipeline.Builder.create(this, "LambdaApiPipeline")
.stages(Arrays.asList(
StageProps.builder()
.stageName("Source")
.actions(
Arrays.asList(
CodeCommitSourceAction.Builder.create()
.actionName("Source")
.repository(repository)
.output(sourceOutput)
.build()
)
)
.build(),
StageProps.builder()
.stageName("Build")
.actions(
Arrays.asList(
CodeBuildAction.Builder.create()
.actionName("Lambda_Build")
.project(lambdaBuild)
.input(sourceOutput)
.outputs(Arrays.asList(lambdaBuildOutput))
.build(),
CodeBuildAction.Builder.create()
.actionName("CDK_Build")
.project(cdkBuild)
.input(sourceOutput)
.outputs(Arrays.asList(cdkBuildOutput))
.build()
)
)
.build(),
StageProps.builder()
.stageName("Deploy_Alpha")
.actions(
Arrays.asList(
CloudFormationCreateUpdateStackAction.Builder.create()
.actionName("Lambda_CFN_Alpha_Deploy")
.templatePath(cdkBuildOutput.atPath("lambdaStackAlfa.template.json"))
.adminPermissions(true)
.parameterOverrides(envToCfnCodeParametersCodeMap.get("alfa").assign(lambdaBuildOutput.getS3Location()))
.extraInputs(Arrays.asList(lambdaBuildOutput))
.stackName("lambdaStackAlfa")
.build()
)
)
.build(),
StageProps.builder()
.stageName("Deploy_Prod")
.actions(
Arrays.asList(
CloudFormationCreateUpdateStackAction.Builder.create()
.adminPermissions(true)
.account("123123123123") // Prod account id
.actionName("Lambda_CFN_Prod_Deploy")
.templatePath(cdkBuildOutput.atPath("lambdaStackProd.template.json"))
.parameterOverrides(envToCfnCodeParametersCodeMap.get("prod").assign(lambdaBuildOutput.getS3Location()))
.extraInputs(Arrays.asList(lambdaBuildOutput))
.stackName("lambdaStackProd")
.build()
)
)
.build()
)
)
.pipelineName("lambdaPipeline")
.artifactBucket(bucket)
.restartExecutionOnUpdate(true)
.build();
}
}
Issue Analytics
- State:
- Created 3 years ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
I’m glad 🙂.
I would have one Bucket per CodePipeline (whether created by the CodePipeline construct, or explicitly by you, doesn’t really matter).
Unfortunately, it’s not possible. The CodePipeline needs resources created in the other account to be able to deploy to it.
However, look into our new CDK Pipelines module - it takes a different approach with the support stacks (it asks you to create them explicitly using
cdk bootstrap
).I’ll resolve this issue, let me know if you need anything else from our side here.
Thanks, Adam
Hi @skinny85 I both tried with creating a bucket manually including a KMS or letting the pipeline to create their own. And it worked like a charm! I have two questions about following the best practices here;
What would be the preferred way to manage buckets if we will have multiple pipelines? Creating one main bucket or letting pipelines to create their own?
When deploying the stacks I see that cdk creates a support stack for the other account and we have to deploy it first. Then we need to deploy the main pipeline stack. Is there a way to combine these two operations into one? And is it also possible to make PipelineStack to handle this internally?
Thanks a lot again! I really appreciate for your help