|
| 1 | +--- |
| 2 | +title: CodeBuild |
| 3 | +linkTitle: CodeBuild |
| 4 | +description: > |
| 5 | + Get started with CodeBuild on LocalStack |
| 6 | +tags: ["Pro image"] |
| 7 | +--- |
| 8 | + |
| 9 | +## Introduction |
| 10 | + |
| 11 | +AWS CodeBuild is a fully managed continuous integration service that compiles source code, runs tests, and produces software packages that are ready to deploy. |
| 12 | +It is part of the [AWS Developer Tools suite](https://aws.amazon.com/products/developer-tools/) and integrates with other AWS services to provide an end-to-end development pipeline. |
| 13 | + |
| 14 | +LocalStack supports the emulation of most of the CodeBuild operations. |
| 15 | +The supported operations are listed on the [API coverage page]({{< ref "coverage_codebuild" >}}). |
| 16 | + |
| 17 | +AWS CodeBuild emulation is powered by the [AWS CodeBuild agent](https://docs.aws.amazon.com/codebuild/latest/userguide/use-codebuild-agent.html). |
| 18 | + |
| 19 | +## Getting Started |
| 20 | + |
| 21 | +This tutorial will show you how to use AWS CodeBuild to test and build a deployable version of a Java executable. |
| 22 | + |
| 23 | +It assumes basic knowledge of the [`awslocal`](https://github.com/localstack/awscli-local) wrapper, Apache Maven, and Java. |
| 24 | + |
| 25 | +### Create the source code |
| 26 | + |
| 27 | +In the first step, we have to create the project that we want to build with AWS CodeBuild. |
| 28 | + |
| 29 | +In an empty directory, we need to re-create the following structure: |
| 30 | + |
| 31 | +```bash |
| 32 | +root-directory-name |
| 33 | +├── pom.xml |
| 34 | +└── src |
| 35 | + ├── main |
| 36 | + │ └── java |
| 37 | + │ └── MessageUtil.java |
| 38 | + └── test |
| 39 | + └── java |
| 40 | + └── TestMessageUtil.java |
| 41 | +``` |
| 42 | + |
| 43 | +Let us walk through these files. |
| 44 | +`MessageUtil.java` contains the entire logic of this small application. |
| 45 | +It does nothing more than print a salutation message. |
| 46 | +Create a `MessageUtil.java` file and save it into the `src/main/java` directory. |
| 47 | + |
| 48 | +```java |
| 49 | +public class MessageUtil { |
| 50 | + private String message; |
| 51 | + |
| 52 | + public MessageUtil(String message) { |
| 53 | + this.message = message; |
| 54 | + } |
| 55 | + |
| 56 | + public String printMessage() { |
| 57 | + System.out.println(message); |
| 58 | + return message; |
| 59 | + } |
| 60 | + |
| 61 | + public String salutationMessage() { |
| 62 | + message = "Hi!" + message; |
| 63 | + System.out.println(message); |
| 64 | + return message; |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +Every build needs to be tested. |
| 70 | +Therefore, create the `TestMessageUtil.java` file in the `src/test/java` directory. |
| 71 | + |
| 72 | +```java |
| 73 | +import org.junit.Test; |
| 74 | +import org.junit.Ignore; |
| 75 | +import static org.junit.Assert.assertEquals; |
| 76 | + |
| 77 | +public class TestMessageUtil { |
| 78 | + |
| 79 | + String message = "Robert"; |
| 80 | + MessageUtil messageUtil = new MessageUtil(message); |
| 81 | + |
| 82 | + @Test |
| 83 | + public void testPrintMessage() { |
| 84 | + System.out.println("Inside testPrintMessage()"); |
| 85 | + assertEquals(message,messageUtil.printMessage()); |
| 86 | + } |
| 87 | + |
| 88 | + @Test |
| 89 | + public void testSalutationMessage() { |
| 90 | + System.out.println("Inside testSalutationMessage()"); |
| 91 | + message = "Hi!" + "Robert"; |
| 92 | + assertEquals(message,messageUtil.salutationMessage()); |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +This small suite simply verifies that the greeting message is built correctly. |
| 98 | + |
| 99 | +Finally, we need a `pom.xml` file to instruct Maven about what to build and which artifact needs to be produced. |
| 100 | +Create this file at the root of your directory. |
| 101 | + |
| 102 | +```xml |
| 103 | +<project xmlns="http://maven.apache.org/POM/4.0.0" |
| 104 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 105 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> |
| 106 | + <modelVersion>4.0.0</modelVersion> |
| 107 | + <groupId>org.example</groupId> |
| 108 | + <artifactId>messageUtil</artifactId> |
| 109 | + <version>1.0</version> |
| 110 | + <packaging>jar</packaging> |
| 111 | + <name>Message Utility Java Sample App</name> |
| 112 | + <dependencies> |
| 113 | + <dependency> |
| 114 | + <groupId>junit</groupId> |
| 115 | + <artifactId>junit</artifactId> |
| 116 | + <version>4.11</version> |
| 117 | + <scope>test</scope> |
| 118 | + </dependency> |
| 119 | + </dependencies> |
| 120 | + <build> |
| 121 | + <plugins> |
| 122 | + <plugin> |
| 123 | + <groupId>org.apache.maven.plugins</groupId> |
| 124 | + <artifactId>maven-compiler-plugin</artifactId> |
| 125 | + <version>3.8.0</version> |
| 126 | + </plugin> |
| 127 | + </plugins> |
| 128 | + </build> |
| 129 | +</project> |
| 130 | +``` |
| 131 | + |
| 132 | +With the following configuration, Maven will compile the `java` files into a executable jar and run the specified tests. |
| 133 | + |
| 134 | +### Create the buildspec file |
| 135 | + |
| 136 | +Now that we have our project set up, we need to create a `buildspec` file. |
| 137 | +A `buildspec` file is a collection of settings and commands, specified in YAML format, that tells AWS CodeBuild how to run a build. |
| 138 | + |
| 139 | +Create this `buildspec.yml` file in the root directory. |
| 140 | + |
| 141 | +```yaml |
| 142 | +version: 0.2 |
| 143 | + |
| 144 | +phases: |
| 145 | + install: |
| 146 | + runtime-versions: |
| 147 | + java: corretto11 |
| 148 | + pre_build: |
| 149 | + commands: |
| 150 | + - echo Nothing to do in the pre_build phase... |
| 151 | + build: |
| 152 | + commands: |
| 153 | + - echo Build started on `date` |
| 154 | + - mvn install |
| 155 | + post_build: |
| 156 | + commands: |
| 157 | + - echo Build completed on `date` |
| 158 | +artifacts: |
| 159 | + files: |
| 160 | + - target/messageUtil-1.0.jar |
| 161 | +``` |
| 162 | +
|
| 163 | +In this file we can observe how the build will be executed. |
| 164 | +First, we define a runtime version. |
| 165 | +Then, we run a `mvn install` command in the build phase which does both the compilation and the testing. |
| 166 | +The pre and post build phases do not do much in this example, but can be used for various things, like install some software needed for the build itself. |
| 167 | + |
| 168 | +A full specification of a `buildspec` file can be found in the [AWS CodeBuild docs](https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html). |
| 169 | + |
| 170 | +### Create input and output buckets |
| 171 | + |
| 172 | +Now we have to create two S3 buckets: |
| 173 | +- one bucket that stores the source we just created, that will be the source of the AWS CodeBuild build |
| 174 | +- one bucket where the output of the build, i.e., the JAR file, will be stored. |
| 175 | + |
| 176 | +Create the buckets with the following commands: |
| 177 | + |
| 178 | +{{< command >}} |
| 179 | +$ awslocal s3 mb s3://codebuild-demo-input |
| 180 | +<disable-copy> |
| 181 | +make_bucket: codebuild-demo-input |
| 182 | +</disable-copy> |
| 183 | +{{< /command >}} |
| 184 | + |
| 185 | +{{< command >}} |
| 186 | +$ awslocal s3 mb s3://codebuild-demo-output |
| 187 | +<disable-copy> |
| 188 | +make_bucket: codebuild-demo-output |
| 189 | +{{< /command >}} |
| 190 | + |
| 191 | +Finally, zip the content of the source code directory and upload it to the created source bucket. |
| 192 | +With a UNIX system, you can simply use the `zip` utility: |
| 193 | +{{< command >}} |
| 194 | +$ zip -r MessageUtil.zip <source-directory> |
| 195 | +{{< /command >}} |
| 196 | + |
| 197 | +Then, upload `MessageUtil.zip` to the `codebuild-demo-input` bucket with the following command: |
| 198 | + |
| 199 | +{{< command >}} |
| 200 | +$ awslocal s3 cp MessageUtil.zip s3://codebuild-demo-input |
| 201 | +{{< /command >}} |
| 202 | + |
| 203 | +### Configuring IAM |
| 204 | + |
| 205 | +To properly work, AWS CodeBuild needs access to other AWS services, e.g., to retrieve the source code from a S3 bucket. |
| 206 | +Create a `create-role.json` file with following content: |
| 207 | + |
| 208 | +```json |
| 209 | +{ |
| 210 | + "Version": "2012-10-17", |
| 211 | + "Statement": [ |
| 212 | + { |
| 213 | + "Effect": "Allow", |
| 214 | + "Principal": { |
| 215 | + "Service": "codebuild.amazonaws.com" |
| 216 | + }, |
| 217 | + "Action": "sts:AssumeRole" |
| 218 | + } |
| 219 | + ] |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +Then, run the following command to create the necessary IAM role: |
| 224 | +{{< command >}} |
| 225 | +$ awslocal iam create-role --role-name CodeBuildServiceRole --assume-role-policy-document file://create-role.json |
| 226 | +{{< /command >}} |
| 227 | + |
| 228 | +From the command's response, keep note of the role ARN: |
| 229 | +it will be needed to create the CodeBuild project later on. |
| 230 | + |
| 231 | +Let us now define a policy for the created role. |
| 232 | +Create a `put-role-policy.json` file with the following content: |
| 233 | + |
| 234 | +```json |
| 235 | +{ |
| 236 | + "Version": "2012-10-17", |
| 237 | + "Statement": [ |
| 238 | + { |
| 239 | + "Sid": "CloudWatchLogsPolicy", |
| 240 | + "Effect": "Allow", |
| 241 | + "Action": [ |
| 242 | + "logs:CreateLogGroup", |
| 243 | + "logs:CreateLogStream", |
| 244 | + "logs:PutLogEvents" |
| 245 | + ], |
| 246 | + "Resource": "*" |
| 247 | + }, |
| 248 | + { |
| 249 | + "Sid": "CodeCommitPolicy", |
| 250 | + "Effect": "Allow", |
| 251 | + "Action": [ |
| 252 | + "codecommit:GitPull" |
| 253 | + ], |
| 254 | + "Resource": "*" |
| 255 | + }, |
| 256 | + { |
| 257 | + "Sid": "S3GetObjectPolicy", |
| 258 | + "Effect": "Allow", |
| 259 | + "Action": [ |
| 260 | + "s3:GetObject", |
| 261 | + "s3:GetObjectVersion" |
| 262 | + ], |
| 263 | + "Resource": "*" |
| 264 | + }, |
| 265 | + { |
| 266 | + "Sid": "S3PutObjectPolicy", |
| 267 | + "Effect": "Allow", |
| 268 | + "Action": [ |
| 269 | + "s3:PutObject" |
| 270 | + ], |
| 271 | + "Resource": "*" |
| 272 | + }, |
| 273 | + { |
| 274 | + "Sid": "S3BucketIdentity", |
| 275 | + "Effect": "Allow", |
| 276 | + "Action": [ |
| 277 | + "s3:GetBucketAcl", |
| 278 | + "s3:GetBucketLocation" |
| 279 | + ], |
| 280 | + "Resource": "*" |
| 281 | + } |
| 282 | + ] |
| 283 | +} |
| 284 | +``` |
| 285 | + |
| 286 | +Finally, assign the policy to the role with the following command: |
| 287 | + |
| 288 | +{{< command >}} |
| 289 | +$ awslocal put-role-policy --role-name CodeBuildServiceRole --policy-name CodeBuildServiceRolePolicy --policy-document file://put-role-policy.json |
| 290 | +{{< /command >}} |
| 291 | + |
| 292 | +### Create the build project |
| 293 | + |
| 294 | +We now need to create a build project, containing all the information about how to run a build, where to get the source code, and where to place the output. |
| 295 | + |
| 296 | +You can use the CLI to generate the skeleton of the `CreateBuild` request, which you can later modify. |
| 297 | +Save the output of the following command to a file named `create-project.json`. |
| 298 | + |
| 299 | +{{< command >}} |
| 300 | +$ awslocal codebuild create-project --generate-cli-skeleton |
| 301 | +{{< /command >}} |
| 302 | + |
| 303 | +From the generated file, change the source and the artifact location to match the S3 bucket names you just created. |
| 304 | +Similarly, fill in the ARN of the CodeBuild service role. |
| 305 | + |
| 306 | +```json {hl_lines=[5,9,16]} |
| 307 | +{ |
| 308 | + "name": "codebuild-demo-project", |
| 309 | + "source": { |
| 310 | + "type": "S3", |
| 311 | + "location": "codebuild-demo-input" |
| 312 | + }, |
| 313 | + "artifacts": { |
| 314 | + "type": "S3", |
| 315 | + "location": "codebuild-demo-output" |
| 316 | + }, |
| 317 | + "environment": { |
| 318 | + "type": "LINUX_CONTAINER", |
| 319 | + "image": "aws/codebuild/standard:5.0", |
| 320 | + "computeType": "BUILD_GENERAL1_SMALL" |
| 321 | + }, |
| 322 | + "serviceRole": "service-role-arn" |
| 323 | +} |
| 324 | +``` |
| 325 | + |
| 326 | +Now create the project with the following command: |
| 327 | + |
| 328 | +{{< command >}} |
| 329 | +$ awslocal codebuild create-project --cli-input-json file://create-project.json |
| 330 | +{{< /command >}} |
| 331 | + |
| 332 | +You have now created a CodeBuild project called `codebuild-demo-project` that uses the S3 buckets you just created as source and artifact. |
| 333 | + |
| 334 | +{{< callout >}} |
| 335 | +LocalStack does not allow to customize the build environment. |
| 336 | +Depending on the host architecture, the build will be executed an Amazon Linux container, version `3.0.x` and `5.0.x`, respectively for the ARM and the x86 architecture. |
| 337 | +{{< /callout >}} |
| 338 | + |
| 339 | +### Run the build |
| 340 | + |
| 341 | +In this final step, you can now execute your build with the following command: |
| 342 | + |
| 343 | +{{< command >}} |
| 344 | +$ awslocal codebuild start-build --project-name codebuild-demo-project |
| 345 | +{{< /command >}} |
| 346 | + |
| 347 | +Make note of the `id` information given in output, since it can be used to query the status of the build. |
| 348 | +If you inspect the running containers (e.g., with the `docker ps -a` command), you will notice a container with the `localstack-codebuild` prefix (followed by the build ID), which CodeBuild started to execute the build. |
| 349 | +This container will be responsible to start a Docker compose stack that executes the actual build. |
| 350 | + |
| 351 | +As said, you can inspect the status of the build with the following command: |
| 352 | + |
| 353 | +{{< command >}} |
| 354 | +$ awslocal codebuild batch-get-builds --ids <build-id> |
| 355 | +{{< /command >}} |
| 356 | + |
| 357 | +The command returns a list of builds. |
| 358 | +A build has a `buildStatus` attribute that will be set to `SUCCEEDED` if the build correctly terminates. |
| 359 | + |
| 360 | +{{< callout >}} |
| 361 | +Each build goes through different phases, each of them having a start and end time, as well as a status. |
| 362 | +LocalStack does not provided such granular information. |
| 363 | +Currently, it reports only the final status of the build. |
| 364 | +{{< /callout >}} |
| 365 | + |
| 366 | +Once the build is completed, you can verify that the JAR artifact has been uploaded to the correct S3 bucket with the following command: |
| 367 | + |
| 368 | +{{< command >}} |
| 369 | +$ awslocal s3 ls://codebuild-demo-output |
| 370 | +{{< /command >}} |
| 371 | + |
| 372 | +## Limitations |
| 373 | + |
| 374 | +- CodeBuild currently only supports S3, NO_SOURCE, and CODEPIPELINE as [project source](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_ProjectSource.html). |
| 375 | +- CodeBuild only uses Amazon Linux as the build environment for a build project. |
| 376 | +- Communication with the LocalStack container within the build environment is possible only via the host network, by using the Gateway IP address (typically 172.17.0.1) or `host.docker.internal` if running on MacOS. |
0 commit comments