Artifact repositories are often used to share software packages for use in builds and deployments. Java developers using Apache Maven use artifact repositories to share and reuse Maven packages. For example, one team might own a web service framework that is used by multiple other teams to build their own services. The framework team can publish the framework as a Maven package to an artifact repository, where new versions can be picked up by the service teams as they become available. This post explains how you can set up a continuous integration pipeline with AWS CodePipeline and AWS CodeBuild to deploy Maven artifacts to AWS CodeArtifact. CodeArtifact is a fully managed pay-as-you-go artifact repository service with support for software package managers and build tools like Maven, Gradle, npm, yarn, twine, and pip.

Solution overview

The pipeline we build is triggered each time a code change is pushed to the AWS CodeCommit repository. The code is compiled using the Java compiler, unit tested, and deployed to CodeArtifact. After the artifact is published, it can be consumed by developers working in applications that have a dependency on the artifact or by builds running in other pipelines. The following diagram illustrates this architecture.

Architecture diagram of the solution

 

All the components in this pipeline are fully managed and you don’t pay for idle capacity or have to manage any servers.

 

Prerequisites

This post assumes you have the following tools installed and configured:

 

Creating your resources

To create the CodeArtifact domain, CodeArtifact repository, CodeCommit, CodePipeline, CodeBuild, and associated resources, we use AWS CloudFormation. Save the provided CloudFormation template below as codeartifact-cicd-pipeline.yaml and create a stack:


---
Description: Code Artifact CI/CD Pipeline Parameters: GitRepoBranchName: Type: String Default: main Resources: ArtifactBucket: Type: AWS::S3::Bucket CodeArtifactDomain: Type: AWS::CodeArtifact::Domain Properties: DomainName: !Sub "${AWS::StackName}-domain" CodeArtifactRepository: Type: AWS::CodeArtifact::Repository Properties: DomainName: !GetAtt CodeArtifactDomain.Name RepositoryName: !Sub "${AWS::StackName}-repo" CodeRepository: Type: AWS::CodeCommit::Repository Properties: RepositoryDescription: Maven artifact code repository RepositoryName: !Sub "${AWS::StackName}-maven-artifact-repo" CodeBuildProject: Type: AWS::CodeBuild::Project Properties: Name: !Sub "${AWS::StackName}-CodeBuild" Artifacts: Type: CODEPIPELINE Environment: EnvironmentVariables: - Name: CODEARTIFACT_DOMAIN Type: PLAINTEXT Value: !GetAtt CodeArtifactDomain.Name - Name: CODEARTIFACT_REPO Type: PLAINTEXT Value: !GetAtt CodeArtifactRepository.Name Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0 ServiceRole: !GetAtt CodeBuildServiceRole.Arn Source: Type: CODEPIPELINE BuildSpec: buildspec.yaml Pipeline: Type: AWS::CodePipeline::Pipeline Properties: ArtifactStore: Type: S3 Location: !Ref ArtifactBucket RoleArn: !GetAtt CodePipelineServiceRole.Arn Stages: - Name: Source Actions: - Name: SourceAction ActionTypeId: Category: Source Owner: AWS Version: '1' Provider: CodeCommit OutputArtifacts: - Name: SourceBundle Configuration: BranchName: !Ref GitRepoBranchName RepositoryName: !GetAtt CodeRepository.Name RunOrder: '1' - Name: Deliver Actions: - Name: CodeBuild InputArtifacts: - Name: SourceBundle ActionTypeId: Category: Build Owner: AWS Version: '1' Provider: CodeBuild Configuration: ProjectName: !Ref CodeBuildProject RunOrder: '1' CodeBuildServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Sid: '' Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: CodePipelinePolicy PolicyDocument: Version: '2012-10-17' Statement: - Sid: CloudWatchLogsPolicy Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - "*" - Sid: CodeCommitPolicy Effect: Allow Action: - codecommit:GitPull Resource: - !GetAtt CodeRepository.Arn - Sid: S3GetObjectPolicy Effect: Allow Action: - s3:GetObject - s3:GetObjectVersion Resource: - !Sub "arn:aws:s3:::${ArtifactBucket}/*" - Sid: S3PutObjectPolicy Effect: Allow Action: - s3:PutObject Resource: - !Sub "arn:aws:s3:::${ArtifactBucket}/*" - Sid: BearerTokenPolicy Effect: Allow Action: - sts:GetServiceBearerToken Resource: "*" Condition: StringEquals: sts:AWSServiceName: codeartifact.amazonaws.com - Sid: CodeArtifactPolicy Effect: Allow Action: - codeartifact:GetAuthorizationToken Resource: - !Sub "arn:aws:codeartifact:${AWS::Region}:${AWS::AccountId}:domain/${CodeArtifactDomain.Name}" - Sid: CodeArtifactPackage Effect: Allow Action: - codeartifact:PublishPackageVersion - codeartifact:PutPackageMetadata - codeartifact:ReadFromRepository Resource: - !Sub "arn:aws:codeartifact:${AWS::Region}:${AWS::AccountId}:package/${CodeArtifactDomain.Name}/${CodeArtifactRepository.Name}/*" - Sid: CodeArtifactRepository Effect: Allow Action: - codeartifact:ReadFromRepository - codeartifact:GetRepositoryEndpoint Resource: - !Sub "arn:aws:codeartifact:${AWS::Region}:${AWS::AccountId}:repository/${CodeArtifactDomain.Name}/${CodeArtifactRepository.Name}" CodePipelineServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Sid: '' Effect: Allow Principal: Service: - codepipeline.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: CodePipelinePolicy PolicyDocument: Version: '2012-10-17' Statement: - Action: - s3:GetObject - s3:GetObjectVersion - s3:GetBucketVersioning Resource: !Sub "arn:aws:s3:::${ArtifactBucket}/*" Effect: Allow - Action: - s3:PutObject Resource: - !Sub "arn:aws:s3:::${ArtifactBucket}/*" Effect: Allow - Action: - codecommit:GetBranch - codecommit:GetCommit - codecommit:UploadArchive - codecommit:GetUploadArchiveStatus - codecommit:CancelUploadArchive Resource: - !GetAtt CodeRepository.Arn Effect: Allow - Action: - codebuild:StartBuild - codebuild:BatchGetBuilds Resource: - !GetAtt CodeBuildProject.Arn Effect: Allow - Action: - iam:PassRole Resource: "*" Effect: Allow
Outputs: CodePipelineArtifactBucket: Value: !Ref ArtifactBucket CodeRepositoryHttpCloneUrl: Value: !GetAtt CodeRepository.CloneUrlHttp CodeRepositorySshCloneUrl: Value: !GetAtt CodeRepository.CloneUrlSsh

aws cloudformation deploy \
  --stack-name codeartifact-pipeline \
  --template-file codeartifact-cicd-pipeline.yaml \
  --capabilities CAPABILITY_IAM

 

If you have a Maven project you want to use, you can use that. Otherwise, create a new one:


mvn archetype:generate \ -DgroupId=com.mycompany.app \ -DartifactId=my-app \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 -DinteractiveMode=false

 

Initialize a Git repository for the Maven project and add the CodeCommit repository that was created in the CloudFormation stack as a remote repository:


cd my-app
git init
CODECOMMIT_URL=$(aws cloudformation describe-stacks --stack-name codeartifact-pipeline --query "Stacks[0].Outputs[?OutputKey=='CodeRepositoryHttpCloneUrl'].OutputValue" --output text)
git remote add origin $CODECOMMIT_URL

 

Updating the POM file

The Maven project’s POM file needs to be updated with the distribution management section. This lets Maven know where to publish artifacts. Add the distributionManagement section inside the project element of the POM. Be sure to update the URL with the correct URL for the CodeArtifact repository you created earlier. You can find the CodeArtifact repository URL with the get-repository-endpoint CLI command:


aws codeartifact get-repository-endpoint --domain codeartifact-pipeline-domain  --repository codeartifact-pipeline-repo --format maven

 

Add the following to the Maven project’s pom.xml:


<distributionManagement> <repository> <id>codeartifact</id> <name>codeartifact</name> <url>Replace with the URL from the get-repository-endpoint command</url> </repository>
</distributionManagement>

Creating a settings.xml file

Maven needs credentials to use to authenticate with CodeArtifact when it performs the deployment. CodeArtifact uses temporary authorization tokens. To pass the token to Maven, a settings.xml file is created in the top level of the Maven project. During the deployment stage, Maven is instructed to use the settings.xml in the top level of the project instead of the settings.xml that normally resides in $HOME/.m2. Create a settings.xml in the top level of the Maven project with the following contents:


<settings> <servers> <server> <id>codeartifact</id> <username>aws</username> <password>${env.CODEARTIFACT_TOKEN}</password> </server> </servers>
</settings>

Creating the buildspec.yaml file

CodeBuild uses a build specification file with commands and related settings that are used during the build, test, and delivery of the artifact. In the build specification file, we specify the CodeBuild runtime to use pre-build actions (update AWS CLI), and build actions (Maven build, test, and deploy). When Maven is invoked, it is provided the path to the settings.xml created in the previous step, instead of the default in $HOME/.m2/settings.xml. Create the buildspec.yaml as shown in the following code:


version: 0.2 phases: install: runtime-versions: java: corretto11 pre_build: commands: - pip3 install awscli --upgrade --user build: commands: - export CODEARTIFACT_TOKEN=`aws codeartifact get-authorization-token --domain ${CODEARTIFACT_DOMAIN} --query authorizationToken --output text` - mvn -s settings.xml clean package deploy

 

Running the pipeline

The final step is to add the files in the Maven project to the Git repository and push the changes to CodeCommit. This triggers the pipeline to run. See the following code:


git checkout -b main
git add settings.xml buildspec.yaml pom.xml src
git commit -a -m "Initial commit"
git push --set-upstream origin main

 

Checking the pipeline

At this point, the pipeline starts to run. To check its progress, sign in to the AWS Management Console and choose the Region where you created the pipeline. On the CodePipeline console, open the pipeline that the CloudFormation stack created. The pipeline’s name is prefixed with the stack name. If you open the CodePipeline console before the pipeline is complete, you can watch each stage run (see the following screenshot).

CodePipeline Screenshot

If you see that the pipeline failed, you can choose the details in the action that failed for more information.

Checking for new artifacts published in CodeArtifact

When the pipeline is complete, you should be able to see the artifact in the CodeArtifact repository you created earlier. The artifact we published for this post is a Maven snapshot. CodeArtifact handles snapshots differently than release versions. For more information, see Use Maven snapshots. To find the artifact in CodeArtifact, complete the following steps:

  1. On the CodeArtifact console, choose Repositories.
  2. Choose the repository we created earlier named myrepo.
  3. Search for the package named my-app.
  4. Choose the my-app package from the search results.
    CodeArtifact Assets
  5. Choose the Dependencies tab to bring up a list of Maven dependencies that the Maven project depends on.CodeArtifact Dependencies

 

Cleaning up

To clean up the resources you created in this post, you need to remove them in the following order:


# Empty the CodePipeline S3 artifact bucket
CODEPIPELINE_BUCKET=$(aws cloudformation describe-stacks --stack-name codeartifact-pipeline --query "Stacks[0].Outputs[?OutputKey=='CodePipelineArtifactBucket'].OutputValue" --output text)
aws s3 rm s3://$CODEPIPELINE_BUCKET --recursive # Delete the CloudFormation stack
aws cloudformation delete-stack --stack-name codeartifact-pipeline

Conclusion

This post covered how to build a continuous integration pipeline to deliver Maven artifacts to AWS CodeArtifact. You can modify this solution for your specific needs. For more information about CodeArtifact or the other services used, see the following:

 

zhYTXsEwe6Q