Skip to content
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

pipelines : Enable custom role configuration #27605

Open
2 tasks
supunasp opened this issue Oct 19, 2023 · 6 comments
Open
2 tasks

pipelines : Enable custom role configuration #27605

supunasp opened this issue Oct 19, 2023 · 6 comments
Labels
@aws-cdk/pipelines CDK Pipelines library effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2

Comments

@supunasp
Copy link

Describe the feature

I have developed a CodePipeline as follows.
image

But when I deploy the pipeline, cdk will create another 4 roles. Is there a way to stop generating those roles and tell CDK to reuse existing ones provided ?

  • AWS:${cdk-codepipeline-XX/UpdatePipeline/SelfMutation/Role}
  • AWS:${cdk-codepipeline-XX/Assets/FileRole}
  • AWS:${cdk-codepipeline-XX/Pipeline/Source/source-change/CodePipelineActionRole}
  • AWS:${cdk-codepipeline-XX/CodeBuildActionRole}

Use Case

We have around 25+ lambda functions and each of them currently having build job where CodeBuildRole is shared. We will be creating this new pipeline for all of them. Which means there will be around 100+ roles created.
I need to use a pre-existing service role created by our security team instead auto generating ones. Our security team identifies this as another vulnerability.

Proposed Solution

CodePipeline can be configured as this. Solution would be to introduce role instead of rolePolicy in CodeBuildOptions

CodePipeline codePipeline = new CodePipeline(
                this,
                codePipelineId,
                CodePipelineProps.builder()
                        .pipelineName(codePipelineId)
                        .selfMutation(Boolean.TRUE)
                        .role(codePipelineRole)
                        .synth(synthStep)
                        .crossAccountKeys(Boolean.TRUE)
                        .artifactBucket(PipelineUtils.getArtifactBucket(this, config))
                        .synthCodeBuildDefaults(
                                CodeBuildOptions
                                        .builder()
                                        .cache(codebuildCache)
                                        .role(synthRole)
                                        .buildEnvironment(buildEnvironment)
                                        .build()
                        )
                        .codeBuildDefaults(
                                CodeBuildOptions
                                        .builder()
                                        .cache(codebuildCache)
                                        .role(codeBuildRole)
                                        .buildEnvironment(buildEnvironment)
                                        .build()
                        )
                        .selfMutationCodeBuildDefaults(
                                CodeBuildOptions
                                        .builder()
                                        .cache(codebuildCache)
                                        .role(selfMutationRole)
                                        .buildEnvironment(buildEnvironment)
                                        .build()
                        )
                        .assetPublishingCodeBuildDefaults(
                                CodeBuildOptions
                                        .builder()
                                        .cache(codebuildCache)
                                        .role(assetPublishingRole)
                                        .buildEnvironment(buildEnvironment)
                                        .build()
                        )
                        .build()
        );

Other Information

I have tried overriding the roles using CfnResource. (L1 construct). I was able to override some roles. I was not able to override the role to But the roles are still getting generated. I couldn't find a way to stop generating the role.

                CfnResource pipelineCfn = (CfnResource) codePipeline.getPipeline().getNode()
                .getDefaultChild();

        if (pipelineCfn != null) {
            //  AWS:${cdk-codepipeline-XX/Pipeline/Source/source-change/CodePipelineActionRole}
            pipelineCfn.addPropertyOverride(
                    "Stages.0.Actions.0.RoleArn",
                    config.getCodePipelineActionRole()
            );
            // AWS:${cdk-codepipeline-XX/UpdatePipeline/SelfMutation/Role}
            pipelineCfn.addPropertyOverride(
                    "Stages.2.Actions.0.RoleArn",
                    config.getCodePipelineSelfMutationRole()
            );
            //AWS:${cdk-codepipeline-XX/Assets/FileRole}
            pipelineCfn.addPropertyOverride(
                    "Stages.3.Actions.0.RoleArn",
                    config.getCodePipelineAssetsFileRole()
            );
        }

        CfnResource selfMutationProjectCfn = (CfnResource) codePipeline.getSelfMutationProject()
                .getNode()
                .getDefaultChild();

        if (selfMutationProjectCfn != null) {
            // replace service role for AWS:${cdk-codepipeline-XX/UpdatePipeline/SelfMutation/Role}
            selfMutationProjectCfn.addPropertyOverride(
                    "ServiceRole",
                    config.getCodePipelineSelfMutationRole()
            );
        }

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

CDK version used

software.amazon.awscdk: aws-cdk-lib: 2.99.1

Environment details (OS name and version, etc.)

AWS Amazon Linux 2 & Java

@supunasp supunasp added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Oct 19, 2023
@github-actions github-actions bot added the @aws-cdk/aws-codepipeline Related to AWS CodePipeline label Oct 19, 2023
@pahud pahud self-assigned this Oct 20, 2023
@pahud
Copy link
Contributor

pahud commented Oct 20, 2023

Makes sense. But I believe it's still possible to override. Can you share minimal reproducible code snippets with us that provisions the pipeline with all the four roles?

@pahud pahud added p2 response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Oct 20, 2023
@pahud pahud removed their assignment Oct 20, 2023
@pahud pahud removed the needs-triage This issue or PR still needs to be triaged. label Oct 20, 2023
@github-actions
Copy link

This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.

@github-actions github-actions bot added the closing-soon This issue will automatically close in 4 days unless further comments are made. label Oct 22, 2023
@supunasp
Copy link
Author

supunasp commented Oct 23, 2023

Hi @pahud ,
Here is the total code.

Repository will have following structure

Repo

  • cdk
    • CDK App
  • lambda
    • lambda code

CDK App

import software.amazon.awscdk.App;
import software.amazon.awscdk.Environment;
import software.amazon.awscdk.StackProps;

public class CdkApp {
    public static void main(final String[] args) {
        App app = new App();

        String repoName = "cdk-java11-lambda";
        Environment environment = makeEnv();
        new CodePipelineStack(
                app,
                repoName + "-codepipeline-stack",
                StackProps
                        .builder()
                        .stackName(repoName + "-codepipeline-stack")
                        .env(environment)
                        .build(),
                repoName
        );

        app.synth();
    }

    public static Environment makeEnv() {
        return Environment.builder()
                .account("XXXX")
                .region("ap-southeast-2")
                .build();
    }

}

CodePipelineStack

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import software.amazon.awscdk.CfnResource;
import software.amazon.awscdk.Environment;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.StageProps;
import software.amazon.awscdk.pipelines.CodeBuildOptions;
import software.amazon.awscdk.pipelines.CodeBuildStep;
import software.amazon.awscdk.pipelines.CodeBuildStepProps;
import software.amazon.awscdk.pipelines.CodeCommitSourceOptions;
import software.amazon.awscdk.pipelines.CodePipeline;
import software.amazon.awscdk.pipelines.CodePipelineProps;
import software.amazon.awscdk.pipelines.CodePipelineSource;
import software.amazon.awscdk.services.codebuild.BucketCacheOptions;
import software.amazon.awscdk.services.codebuild.BuildEnvironment;
import software.amazon.awscdk.services.codebuild.BuildEnvironmentVariable;
import software.amazon.awscdk.services.codebuild.BuildEnvironmentVariableType;
import software.amazon.awscdk.services.codebuild.BuildSpec;
import software.amazon.awscdk.services.codebuild.Cache;
import software.amazon.awscdk.services.codebuild.ComputeType;
import software.amazon.awscdk.services.codebuild.IProject;
import software.amazon.awscdk.services.codebuild.LinuxBuildImage;
import software.amazon.awscdk.services.codecommit.IRepository;
import software.amazon.awscdk.services.codecommit.Repository;
import software.amazon.awscdk.services.codepipeline.actions.CodeCommitTrigger;
import software.amazon.awscdk.services.iam.FromRoleArnOptions;
import software.amazon.awscdk.services.iam.IRole;
import software.amazon.awscdk.services.iam.PolicyStatement;
import software.amazon.awscdk.services.iam.Role;
import software.amazon.awscdk.services.kms.IKey;
import software.amazon.awscdk.services.kms.Key;
import software.amazon.awscdk.services.s3.Bucket;
import software.amazon.awscdk.services.s3.BucketAttributes;
import software.amazon.awscdk.services.s3.IBucket;
import software.constructs.Construct;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;


@Slf4j
public class CodePipelineStack extends Stack {

    public CodePipelineStack(final Construct scope,
                             final String id,
                             final StackProps props,
                             final String repoName) {
        super(scope, id, props);

        initStack(repoName, props.getEnv());
    }

    private void initStack(final String repoName, final Environment env) {

        IRepository codeCommitRepository = getRepository(this, repoName);
        BuildEnvironment buildEnvironment = getBuildEnvironment();
        IRole codeBuildRole = getCodeBuildDeveloperRole(this);
        IRole codeBuildActionRole = getCodeBuildActionRole(this);
        IRole codePipelineRole = getCodePipelineDeveloperRole(this);
        IRole codePipelineEventRole = getCodePipelineEventsRole(this);
        Cache codebuildCache = getCacheBucket(this, repoName);

        PolicyStatement policyStatement = getPolicyStatement();

        //synth action
        String codeBuildName = "cdk-" + repoName + "-codebuild";
        CodeBuildStep synthStep = new CodeBuildStep(
                codeBuildName,
                CodeBuildStepProps
                        .builder()
                        .projectName(codeBuildName)
                        .cache(codebuildCache)
                        .input(
                                CodePipelineSource.codeCommit(
                                        codeCommitRepository,
                                        "master",
                                        CodeCommitSourceOptions
                                                .builder()
                                                .eventRole(codePipelineEventRole)
                                                .actionName("commit")
                                                .codeBuildCloneOutput(Boolean.TRUE)
                                                .trigger(CodeCommitTrigger.NONE)
                                                .build()
                                )
                        )
                        .partialBuildSpec(getCacheBuildSpec())
                        .installCommands(getInstallCommands())
                        .commands(getBuildCommands())
                        .primaryOutputDirectory("${CODEBUILD_SRC_DIR}/cdk/cdk.out")
                        .buildEnvironment(buildEnvironment)
                        .actionRole(codeBuildActionRole)
                        .role(codeBuildRole)
                        .rolePolicyStatements(List.of(policyStatement))
                        .build()
        );
        // Create the pipeline
        String codePipelineId = "cdk-codepipeline-" + repoName;
        CodePipeline codePipeline = new CodePipeline(
                this,
                codePipelineId,
                CodePipelineProps.builder()
                        .pipelineName(codePipelineId)
                        .selfMutation(Boolean.TRUE)
                        .role(codePipelineRole)
                        .synth(synthStep)
                        .crossAccountKeys(Boolean.TRUE)
                        .artifactBucket(getArtifactBucket(this))
                        .synthCodeBuildDefaults(
                                getCodeBuildDefaults(codebuildCache, policyStatement,
                                        buildEnvironment)
                        )
                        .codeBuildDefaults(
                                getCodeBuildDefaults(codebuildCache, policyStatement,
                                        buildEnvironment)
                        )
                        .selfMutationCodeBuildDefaults(
                                getCodeBuildDefaults(codebuildCache, policyStatement,
                                        buildEnvironment)
                        )
                        .assetPublishingCodeBuildDefaults(
                                getCodeBuildDefaults(codebuildCache, policyStatement,
                                        buildEnvironment)
                        )
                        .build()
        );

        // DEV deploy
        String devFunctionName = "dev-" + repoName;

        codePipeline.addStage(
                new LambdaPipelineStage(
                        this,
                        repoName + "-dev-deploy",
                        StageProps
                                .builder()
                                .stageName(repoName + "-dev-deploy")
                                .env(env)
                                .build(),
                        devFunctionName,
                        Constants.DEVELOPMENT_ENV

                )
        );

        // Test deploy
        codePipeline.addStage(
                new LambdaPipelineStage(
                        this,
                        repoName + "-test-deploy",
                        StageProps
                                .builder()
                                .stageName(repoName + "-test-deploy")
                                .env(env)
                                .build(),
                        repoName,
                        Constants.TEST_ENV

                )
        );

        codePipeline.buildPipeline();

        clearAutoGeneratedRoles(codePipeline);


    }

    @NotNull
    private static List<String> getInstallCommands() {
        return List.of(
                "export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token "
                        + "--domain XXXXX --query authorizationToken --output text`",
                "npm install -g aws-cdk"
        );
    }

    @NotNull
    private static List<String> getBuildCommands() {
        return List.of(
                "echo Build started on `date`",
                "git checkout master",
                //release prepare
                "sh ./release.sh"
        );
    }

    private void clearAutoGeneratedRoles(final CodePipeline codePipeline) {
        CfnResource pipelineCfn = getDefaultChild(codePipeline.getPipeline());

        overrideStageRoles(pipelineCfn);

        CfnResource selfMutationProjectCfn = getDefaultChild(codePipeline.getSelfMutationProject());

        if (selfMutationProjectCfn != null) {
            // replace service role for AWS:${cdk-codepipeline-XX/UpdatePipeline/SelfMutation/Role}
            selfMutationProjectCfn.addPropertyOverride(
                    "ServiceRole",
                    "arn:aws:iam::XXXXXXX:role/CodePipelineSelfMutationRole"
            );
        }
    }

    private static void overrideStageRoles(final CfnResource pipelineCfn) {
        if (pipelineCfn != null) {
            // AWS:${cdk-codepipeline-XX/Pipeline/Source/source-change/CodePipelineActionRole}
            pipelineCfn.addPropertyOverride(
                    "Stages.0.Actions.0.RoleArn",
                    "arn:aws:iam::XXXXXXX:role/CodePipelineActionRole"
            );
            // AWS:${cdk-codepipeline-XX/UpdatePipeline/SelfMutation/Role}
            pipelineCfn.addPropertyOverride(
                    "Stages.2.Actions.0.RoleArn",
                    "arn:aws:iam::XXXXXXX:role/CodePipelineSelfMutationRole"
            );
            //AWS:${cdk-codepipeline-XX/Assets/FileRole}
            pipelineCfn.addPropertyOverride(
                    "Stages.3.Actions.0.RoleArn",
                    "arn:aws:iam::XXXXXXX:role/CodePipelineAssetsFileRole"
            );
        }
    }

    public static BuildEnvironmentVariable getPlainTextEnvVariable(final String value) {
        return BuildEnvironmentVariable
                .builder()
                .value(value)
                .type(BuildEnvironmentVariableType.PLAINTEXT)
                .build();
    }

    public static BuildEnvironmentVariable getSecretEnvVariable(final String value) {
        return BuildEnvironmentVariable
                .builder()
                .value(value)
                .type(BuildEnvironmentVariableType.SECRETS_MANAGER)
                .build();
    }

    public static BuildEnvironment getBuildEnvironment() {

        return BuildEnvironment.builder()
                .buildImage(LinuxBuildImage.AMAZON_LINUX_2_5)
                .computeType(ComputeType.MEDIUM)
                .privileged(Boolean.TRUE)
                .environmentVariables(getCodeBuildUserDetails())
                .build();
    }

    /**
     * tree map is required to maintain order. otherwise pipeline will go on loop
     *
     * @return Map of env variables
     */
    @NotNull
    private static Map<String, BuildEnvironmentVariable> getCodeBuildUserDetails() {

        Map<String, BuildEnvironmentVariable> variableMap = new TreeMap<>();
        variableMap.put(
                "AWS_ACCESS_KEY_ID",
                getSecretEnvVariable(
                        "arn:aws:secretsmanager:ap-southeast-2:XXXXXXX:secret:XXXXXXX::")
        );
        variableMap.put(
                "AWS_SECRET_ACCESS_KEY",
                getSecretEnvVariable(
                        "arn:aws:secretsmanager:ap-southeast-2:XXXXXXX:secret:XXXXXXX::")
        );
        variableMap.put(
                "AWS_DEFAULT_REGION",
                getPlainTextEnvVariable("ap-southeast-2")
        );
        return variableMap;
    }

    @NotNull
    public static FromRoleArnOptions getDefaultRoleArnOptions() {
        return FromRoleArnOptions
                .builder()
                .mutable(Boolean.FALSE)
                .build();
    }

    @NotNull
    public static IRepository getRepository(final Construct scope, final String repoName) {
        return Repository.fromRepositoryName(
                scope, repoName, repoName);
    }

    @NotNull
    public static Cache getCacheBucket(final Construct scope, final String repoName) {
        return Cache.bucket(
                Bucket.fromBucketName(
                        scope,
                        "build-cache",
                        "code-build-cache"
                ),
                BucketCacheOptions
                        .builder()
                        .prefix(repoName)
                        .build()
        );
    }

    public static IBucket getArtifactBucket(final Construct scope) {
        return Bucket.fromBucketAttributes(
                scope,
                "artifact-bucket",
                BucketAttributes
                        .builder()
                        .bucketName("cdk-pipeline-artifacts")
                        .encryptionKey(getBucketEncryptionKey(scope))
                        .build()
        );
    }

    public static IKey getBucketEncryptionKey(final Construct scope) {

        return Key.fromKeyArn(
                scope,
                "artifact-bucket-key",
                "arn:aws:kms:ap-southeast-2:XXXXXXX:key/XXXX"
        );
    }

    @NotNull
    public static BuildSpec getCacheBuildSpec() {
        return BuildSpec.fromObject(
                Map.of(
                        "cache",
                        Map.of(
                                "paths", List.of("/root/.m2/**/*")
                        )
                )
        );
    }

    public static PolicyStatement getPolicyStatement() {
        return PolicyStatement.Builder
                .create()
                .actions(List.of("sts:AssumeRole"))
                .resources(List.of("arn:aws:iam::XXXXXXX:role/cdk-*"))
                .build();
    }

    @NotNull
    public static IRole getCodeBuildDeveloperRole(final Construct scope) {
        return Role.fromRoleArn(
                scope,
                "CodeBuildDeveloperRole",
                "arn:aws:iam::XXXXXXX:role/CodeBuildDeveloperRole",
                getDefaultRoleArnOptions()
        );
    }

    @NotNull
    public static IRole getCodeBuildActionRole(final Construct scope) {
        return Role.fromRoleArn(
                scope,
                "CodeBuildActionRole",
                "arn:aws:iam::XXXXXXX:role/CodeBuildActionRole",
                getDefaultRoleArnOptions()
        );
    }

    @NotNull
    public static IRole getCodePipelineDeveloperRole(final Construct scope) {
        return Role.fromRoleArn(
                scope,
                "CodePipelineDeveloperRole",
                "arn:aws:iam::XXXXXXX:role/CodePipelineDeveloperRole",
                getDefaultRoleArnOptions()
        );
    }

    @NotNull
    public static IRole getCodePipelineEventsRole(final Construct scope) {
        return Role.fromRoleArn(
                scope,
                "CodePipelineEventsRole",
                "arn:aws:iam::XXXXXXX:role/CodePipelineEventsRole",
                getDefaultRoleArnOptions()
        );
    }

    @NotNull
    public static CodeBuildOptions getCodeBuildDefaults(final Cache codebuildCache,
                                                        final PolicyStatement policyStatement,
                                                        final BuildEnvironment buildEnvironment) {
        return CodeBuildOptions
                .builder()
                .cache(codebuildCache)
                .rolePolicy(List.of(policyStatement))
                .buildEnvironment(buildEnvironment)
                .build();
    }

    @Nullable
    public static CfnResource getDefaultChild(final Construct construct) {
        return (CfnResource) construct.getNode()
                .getDefaultChild();
    }

    @Nullable
    public static CfnResource getDefaultChild(final IProject project) {
        return (CfnResource) project.getNode()
                .getDefaultChild();
    }
}

Stage

import lombok.extern.slf4j.Slf4j;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.Stage;
import software.amazon.awscdk.StageProps;
import software.constructs.Construct;

@Slf4j
public class LambdaPipelineStage extends Stage {
    public LambdaPipelineStage(final Construct scope,
                               final String id,
                               final StageProps props,
                               final String functionName,
                               final String environment
    ) {
        super(scope, id, props);

        LambdaStack stack = new LambdaStack(
                this,
                functionName,
                StackProps
                        .builder()
                        .stackName(functionName)
                        .env(props.getEnv())
                        .build(),
                functionName,
                environment
        );
        log.info("Stack for {} is {}", environment, stack.getStackName());

    }
}

Lambda Stack

import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.iam.FromRoleArnOptions;
import software.amazon.awscdk.services.iam.IRole;
import software.amazon.awscdk.services.iam.Role;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.IFunction;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.lambda.Version;
import software.amazon.awscdk.services.lambda.VersionProps;
import software.constructs.Construct;

@Slf4j
public class LambdaStack extends Stack {

    public LambdaStack(final Construct scope,
                       final String id,
                       final StackProps props,
                       final String functionName,
                       final String environment) {
        super(scope, id, props);

        initStack(functionName, environment);
    }

    private void initStack(final String functionName,
                           final String environment) {

        String artifactId = getValueFromContext(this, Constants.ARTIFACT_ID);
        String snapShotVersion = getValueFromContext(this, Constants.SNAPSHOT_VERSION);
        String releaseVersion = getValueFromContext(this, Constants.RELEASE_VERSION);

        String handler = "com.XXXXXXX.cdk.CdkLambdaHandler::handleRequest";

        Code code = Code.fromAsset(
                "../lambda/target/" + artifactId + "-" + releaseVersion + ".zip"
        );

        IRole lambdaRole = getLambdaRole();
        IFunction lambdaFunction = Function.Builder.create(this, functionName)
                .functionName(functionName)
                .runtime(Runtime.JAVA_11)
                .code(code)
                .handler(handler)
                .role(lambdaRole)
                .build();

        log.info("Lambda function for {} is {}", environment, lambdaFunction.getFunctionName());
        // Create a Lambda version using the Maven version
        String version;
        if (Constants.DEVELOPMENT_ENV.equals(environment)) {
            version = snapShotVersion;
        } else {
            version = releaseVersion;
        }
        Version lambdaVersion = new Version(
                this,
                functionName + "-" + version,
                VersionProps.builder()
                        .lambda(lambdaFunction)
                        .description(version)
                        .removalPolicy(RemovalPolicy.RETAIN)
                        .build()
        );

        log.info("Lambda function version for {} in {} is {}",
                lambdaFunction.getFunctionName(), environment, lambdaVersion.getVersion());

    }

    @NotNull
    private IRole getLambdaRole() {
        return Role.fromRoleArn(
                this,
                "lambda-role",
                "arn:aws:iam::XXXXXXX:role/lambda-role",
                FromRoleArnOptions
                        .builder()
                        .mutable(Boolean.FALSE)
                        .build()
        );
    }

    @NotNull
    public static String getValueFromContext(final Construct scope, final String variableKey) {
        return (String) scope.getNode().tryGetContext(variableKey);
    }
}

release.sh - this will do a mvn release prepare and will do cdk diff and cdk synth

#!/bin/sh

function getProperty {
   PROP_KEY=$1
   PROP_VALUE=`cat $PROPERTY_FILE | grep "$PROP_KEY" | cut -d'=' -f2`
   echo $PROP_VALUE
}

PROPERTY_FILE=release.properties
# export values
ARTIFACT_ID=$(mvn -f lambda/pom.xml org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=project.artifactId -q -DforceStdout)
GROUP_ID=$(mvn -f lambda/pom.xml org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=project.groupId -q -DforceStdout)
SNAPSHOT_VERSION=$(mvn -f lambda/pom.xml org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=project.version -q -DforceStdout)

echo "test build before release prepare"
mvn clean test

echo "release prepare"
mvn release:prepare -Passembly-zip -Dmaven.test.skip=true

VERSION_ID=$(getProperty "project.rel.${GROUP_ID}\\\\:${ARTIFACT_ID}")

echo "uploading release to lib-release-local : ${VERSION_ID}"
mvn deploy:deploy-file \
  -DgroupId=$GROUP_ID \
  -DartifactId=$ARTIFACT_ID \
  -Dversion="${VERSION_ID}" \
  -Dpackaging=zip \
  -Dfile="lambda/target/${ARTIFACT_ID}-${VERSION_ID}.zip" \
  -DrepositoryId=release-local \
  -Durl=https://XXXXXXX.d.codeartifact.ap-southeast-2.amazonaws.com/maven/release-local/

echo "uploading snapshot to lib-snapshot-local : ${SNAPSHOT_VERSION}"
mvn deploy:deploy-file \
  -DgroupId=$GROUP_ID \
  -DartifactId=$ARTIFACT_ID \
  -Dversion="${SNAPSHOT_VERSION}" \
  -Dpackaging=zip \
  -Dfile="lambda/target/${ARTIFACT_ID}-${VERSION_ID}.zip" \
  -DrepositoryId=snapshot-local \
  -Durl=https://XXXXXXX.d.codeartifact.ap-southeast-2.amazonaws.com/maven/snapshot-local/

echo "release clean"
mvn release:clean

echo "cdk diff and synth"
cd ${CODEBUILD_SRC_DIR}/cdk

cdk diff -c ARTIFACT_ID=${ARTIFACT_ID} -c SNAPSHOT_VERSION=${SNAPSHOT_VERSION} -c RELEASE_VERSION=${VERSION_ID}

cdk synth -c ARTIFACT_ID=${ARTIFACT_ID} -c SNAPSHOT_VERSION=${SNAPSHOT_VERSION} -c RELEASE_VERSION=${VERSION_ID}

@supunasp
Copy link
Author

supunasp commented Oct 23, 2023

Here is the repo with code
https://github.com/supunasp/cdk-java11-lambda

@github-actions github-actions bot removed closing-soon This issue will automatically close in 4 days unless further comments are made. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Oct 23, 2023
@pahud
Copy link
Contributor

pahud commented Nov 17, 2023

Thank you. Yes unfortunately cdk pipelines does not allow to specify existing roles for all the 4 roles and it's very tricky to override it. I am making it a p1 feature request now.

@pahud pahud added p1 effort/medium Medium work item – several days of effort needs-review @aws-cdk/pipelines CDK Pipelines library and removed p2 labels Nov 17, 2023
@pahud pahud changed the title CodePipeline : Enable custom role configuration pipelines : Enable custom role configuration Nov 17, 2023
@pahud pahud removed the @aws-cdk/aws-codepipeline Related to AWS CodePipeline label Nov 17, 2023
@pahud
Copy link
Contributor

pahud commented Nov 20, 2023

Hi @supunasp

After some deep diving I was able to print out the role ARNs with CDK in typescript:

Given the pipeline as below:

    const pipeline = new pipelines.CodePipeline(this, 'MyPipeline', {
      synth: new pipelines.ShellStep('Synth', {
        // Use a connection created using the AWS console to authenticate to GitHub
        // Other sources are available.
      input: pipelines.CodePipelineSource.connection(repoString, repoBranch, {
          connectionArn, // Created using the AWS console * });',
        }),
        commands: [
          'yarn install --frozen-lockfile',
          // 'yarn build',
          'npx cdk synth',
        ],
      }),
    });

When I cdk diff I can see 5 roles to be created:

image

And I was able to print out the 4 role ARNs with a helper function:

    pipeline.buildPipeline();

    // Pipeline Role
    new CfnOutput(this, 'PipelineRoleArn', { value: pipeline.pipeline.role.roleArn });

    const stackName = Stack.of(this).stackName;
    const _repoString = repoString.replace('/', '_');

    // MyPipeline/Pipeline/Source/${_repoString}/CodePipelineActionRole
    const codePipelineActionRole = this.findRolebyPath(`${stackName}/Pipeline/Source/${_repoString}/CodePipelineActionRole`, pipeline);
    new CfnOutput(this, 'CodePipelineSourceActionRole', { value: codePipelineActionRole.roleArn });

    // MyPipeline/Pipeline/Build/Synth/CdkBuildProject/Role
    const cdkBuildSynthRole = this.findRolebyPath(`${stackName}/Pipeline/Build/Synth/CdkBuildProject/Role`, pipeline);
    new CfnOutput(this, 'CdkBuildSynthRoleArn', { value: cdkBuildSynthRole.roleArn });

    // MyPipeline/UpdatePipeline/SelfMutation/Role
    const selfMutationRole = this.findRolebyPath(`${stackName}/UpdatePipeline/SelfMutation/Role`, pipeline);
    new CfnOutput(this, 'SelfMutationRoleArn', { value: selfMutationRole.roleArn });

    // CodeBuildActionRole
    const actionRole = pipeline.node.tryFindChild('CodeBuildActionRole') as iam.Role;
    new CfnOutput(this, 'CodeBuildActionRoleArn', { value: actionRole.roleArn });

And the helper function as below:

  private findRolebyPath(path: string, findFrom: Construct): iam.Role {
    const split = path.split('/');
    let found: Construct = findFrom;
    for (var i=1; i<split.length-1; i++) {
      console.log('finding ', split[i]);
      found = found.node.tryFindChild(split[i]) as Construct;
    };

    return found.node.tryFindChild(split[split.length-1]) as iam.Role;
  }

And the output

Outputs:
PipelineStack.CdkBuildSynthRoleArn = arn:aws:iam::123456789012:role/PipelineStack-MyPipelineBuildSynthCdkBuildProjectRo-qT0tCSkxxs8l
PipelineStack.CodeBuildActionRoleArn = arn:aws:iam::123456789012:role/PipelineStack-MyPipelineCodeBuildActionRole1AA6A14D-I1Vim6qJxcGB
PipelineStack.CodePipelineSourceActionRole = arn:aws:iam::123456789012:role/PipelineStack-MyPipelineSourcepahuddemopipelineCode-cTXAMDMmqTbq
PipelineStack.PipelineRoleArn = arn:aws:iam::123456789012:role/PipelineStack-MyPipelineRoleA7BAB7EA-SBbvUx8cmKit
PipelineStack.SelfMutationRoleArn = arn:aws:iam::123456789012:role/PipelineStack-MyPipelineUpdatePipelineSelfMutationR-C8JqaIZuNc6e

With that being said, I think it's still possible to override the 5 roles with escape hatches so you can reuse your existing custom roles.

I am making it a p2 feature request again as this seems to be a workaround but we still welcome any pull requests to allow custom role definition in the construct props.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/pipelines CDK Pipelines library effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2
Projects
None yet
Development

No branches or pull requests

3 participants