[Java] [CodePipeline x Elastic Beanstalk] CI/CD Java application to Elastic Beanstalk with CodePipeline Part 1

10 minute read

It is a hands-on to CI/CD a Java application (obtaining a value from a database and returning the result in JSON format) to Elastic Beanstalk with CodePipeline.

Because it’s long

The content is divided into 3 articles. The work time is assumed to be around 3 hours in total, excluding preparations. Each article takes about 1 hour.

Environment

  • OS: Windows10
  • IDE: Eclipse 2020-03
  • JDK: Amazon Correto 8
  • Framework: Spring Boot
  • AWS -CodePipeline -CodeCommit -CodeBuild -CodeDeploy -Elastic Beantalk -Java SE (Java 8 version 2.10.8)
    → You can follow the same procedure for different platform branches (Corretto 11, etc.). -RDS: MySQL Community Edition (Version 8.0.17)

Image figure

It is a rough image of the contents of this procedure. When you push the locally edited code to Commit → CodeCommit, CodePipeline builds a CI/CD mechanism that automatically builds and deploys Elastic Beanstalk, which is the application execution environment. image.png

Advance preparation

① If Eclipse is not installed, Pleiades All in One Eclipse Download (Release 2020-06) | MergeDoc Project Please download the latest version (as of August 3, 2020). With STS and Lombok pre-configured, you can start developing with Spring Boot right away.

② The JDK assumes Amazon Correto 8. Download Amazon Corretto 8 | aws Please download and install it, and set Eclipse.

③ To be able to use CodeCommit, How to use CodeCommit of AWS. From where to make access keys. | Qiita Please prepare in advance by referring to.

Procedure

1. Creating a Java (Spring Boot) application

First, create a Java (Spring Boot) application in Eclipse. Based on the store ID, it is very simple to get detailed store information. The structure of the package is as follows.

sample-eb-java
     |
    src/main/java
     |----jp.co.sample_eb_java
     | |---- app
     | | |---- controller
     | | |---- response
     | |
     | |---- domain
     | | |---- service
     | | |---- repository
     | | |---- model
     | |
     | |---- exception
     |
     |
    src/main/resources
     |----application.yml
     |
    Buildfile
    Procfile
    build.gradle
    .ebextensions

...The following is omitted

[1] Creating a project

(1) After opening Eclipse, click “File” (1)> “New” (2)> “Project” (3). image.png

(2) When the wizard starts up, select “Spring Starter Project” (1) and click “Next” (2). image.png

(3) Set as the following capture (1) and click “Next” (2). image.png

(4)Check Lombok, MySQL Driver, Spring Data JPA, Spring Web (if not found, please search from the search box) and press “Next” (②) image.png

[2] Change/create setting file

Next, change and create the configuration file.

(1) Rename application.properties to application.yml and describe as follows.

  • You can leave application.properties as it is, but we did so based on the recent trends (?). image.png

application.yml


spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${DB_HOST:host name}:${DB_PORT:port number}/${DB_NAME:database name}?serverTimezone=JST
    username: ${DB_USERNAME:username}
    password: ${DB_PASSWORD:password}
  jpa:
    database: MYSQL
    hibernate.ddl-auto: none
server:
  port: ${SERVER_PORT:server port number}

I will change the Japanese part later, so leave it as it is.

(2) Create Buildfile in the root directory and describe as follows.

Buildfile


build: ./gradlew assemble

(3) Create Procfile in the root directory and describe as follows.

Procfile


web: java -jar build/libs/sample-eb-java.jar

(4) Add to build.gradle. Find the part before the addition and add “bootJar.archiveName = “sample-eb-java.jar””.

build.gradle (before adding)


configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

build.gradle (after adding)


configurations {
compileOnly {
extendsFrom annotationProcessor
}
bootJar.archiveName = "sample-eb-java.jar"
}

[3] Source code creation

Next, the source code is created. The package structure is as at the beginning of this chapter (1. Creating a Java (Spring Boot) application), but I think that it is troublesome to go back to the top, so I will attach a capture. image.png

(1) First, Entity class. Although it will appear later, the contents correspond to the shop_informations table created in the database.

ShopInformation.java


package jp.co.sample_eb_java.domain.model;

import java.io.Serializable;
import javax.persistence.*;import lombok.Getter;
import lombok.Setter;


/**
 * Entity class linked to the store information table
 *
 * @author CHI-3
 *
 */
@Entity
@Getter
@Setter
@Table(name="shop_informations")
@NamedQuery(name="ShopInformation.findAll", query="SELECT s FROM ShopInformation s")
public class ShopInformation implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="shop_id")
private Integer shopId;

private String access;

private String address;

@Column(name="business_hour")
private String businessHour;

@Column(name="regular_holiday")
private String regular Holiday;

@Column(name="shop_name")
private String shopName;

private String tel;

@Column(name="zip_code")
private String zipCode;

public ShopInformation() {
}

}

(2) Next, Repository interface. Interface used for table operations.

ShopInformationRepository.java


package jp.co.sample_eb_java.domain.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import jp.co.sample_eb_java.domain.model.ShopInformation;

/**
 * Repository interface that handles store information
 *
 * @author CHI-3
 *
 */
@Repository
public interface ShopInformationRepository extends JpaRepository<ShopInformation, Integer>{

/**
* Get store information linked to store ID
*
* @param shopId Store ID
* @return store information
*/
public ShopInformation findByShopId(Integer shopId);

}

(3) Next, Service class. Provides the logic.

ShopInformationService.java


package jp.co.sample_eb_java.domain.service;

import java.util.Optional;

import org.springframework.stereotype.Service;

import jp.co.sample_eb_java.domain.model.ShopInformation;
import jp.co.sample_eb_java.domain.repository.ShopInformationRepository;
import lombok.RequiredArgsConstructor;

/**
 * Service class for acquiring store information
 *
 * @author CHI-3
 *
 */
@Service
@RequiredArgsConstructor
public class ShopInformationService {

private final ShopInformationRepository shopInformationRepository;

/**
* Get store information
*
* @param shopId Store ID
* @return store information
* @throws Exception
*/
public ShopInformation getShopInformation(Integer shopId) throws Exception{
// Get store information: Throw exception if target store does not exist
ShopInformation shopInformation = Optional.ofNullable(shopInformationRepository.findByShopId(shopId)).orElseThrow(Exception::new);
return shopInformation;
}

}

(4) Next, Response class. We will mold the value to be returned.

ShopInformationResponse.java


package jp.co.sample_eb_java.app.response;

import jp.co.sample_eb_java.domain.model.ShopInformation;
import lombok.Builder;
import lombok.Getter;

/**
 * Response class for store information acquisition
 * @author CHI-3
 *
 */
@Getter
@Builder
public class ShopInformationResponse {
 /** store information */
private ShopInformation shopInformation;
}

(5) Next is the Controller class. @RestController is used to return the value in JSON format.

ShopInformationController.java


package jp.co.sample_eb_java.app.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import jp.co.sample_eb_java.app.response.ShopInformationResponse;
import jp.co.sample_eb_java.domain.model.ShopInformation;
import jp.co.sample_eb_java.domain.service.ShopInformationService;
import lombok.RequiredArgsConstructor;

/**
 * API to get store information
 *
 * @author CHI-3
 *
 */
@RestController
@RequiredArgsConstructor
public class ShopInformationController {

private final ShopInformationService shopInformationService;

/**
* Get store information
*
* @param shopId Store ID
* @return response entity (store information)
* @throws Exception
*/
@GetMapping("/shop-information/{shopId}")
public ResponseEntity<ShopInformationResponse> getShopInformation(@PathVariable("shopId") Integer shopId) throws Exception{
ShopInformation shopInformation = shopInformationService.getShopInformation(shopId);
ShopInformationResponse shopInformationResponse = ShopInformationResponse.builder().shopInformation(shopInformation).build();
return new ResponseEntity<>(shopInformationResponse, HttpStatus.OK);
}

}

(6) Finally, ExceptionHandler. This class handles error handling. This time, when requesting a store ID that does not exist, a 400 error (Bad Request) will be returned.

ExceptionHandler.java


package jp.co.sample_eb_java.exception;


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import lombok.extern.slf4j.Slf4j;

/**
 * Exception handler
 *
 * @author CHI-3
 *
 */
@ControllerAdvice
@Slf4j
public class ExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @org.springframework.web.bind.annotation.ExceptionHandler({Exception.class})
    public @ResponseBody
    ResponseEntity<Object> handleError(final Exception e) {
        log.info("call ExceptionHandler", e);return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
}

[4] Create jar file

(1) Create a jar file for creating the Elastic Beantalk environment. Move to the project root directory and execute the build command. If the arrangement is as follows, it will be as follows. image.png

Execution command


> cd C:\pleiades-2020-03\workspace\sample-eb-java
> gradlew build

OK if the result is “BUILD SUCCESS FUL”!

(2) Confirm that the jar file is created under build/libs. (This is used when creating the environment.) image.png

2. Creating a Git repository

Enable Git management for the project created in step 1. Create a local repository on the project created in step 1 and synchronize with a remote repository.

[1] Create local repository

(1) Open the command prompt or GitBash, move (cd) to the root directory of the project created in 1, and then initialize the repository (execute the following command).

> git init

It is OK if a .git folder is created in the root directory of the project. image.png

(2) Rewrite .gitignore before committing. With the default description, necessary functions such as STS are not subject to commit, and they may not work afterwards, which may cause problems. Change the description as follows. Items that do not need to be committed, such as class files and lock files, are excluded.

.gitignore


bin/*
.lock

(3) Commit the edited contents. It is OK if you execute the following commands in order.

> git add.
> git commit -m "first commit"

The “first commit” part (commit message) can be any content, but let’s make it a message that shows the edited content (same below).

(4) Edit .gitignore again. Describe what is required as a function, but is not subject to local commit.

.gitignore



# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse
# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,eclipse

### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders

# External tool builders
.externalToolBuilders/

# Locally stored "Eclipse launch configurations"
*.launch

# PyDev specific (Python IDE for Eclipse)
*.pydevproject

# CDT-specific (C/C++ Development Tooling)
.cproject

# CDT- autotools
.autotools

# Java annotation processor (APT)
.factorypath

# PDT-specific (PHP Development Tools)
.buildpath

# sbteclipse plugin
.target

# Tern plugin
.tern-project

# TeXlipse plugin
.texlipse

# STS (Spring Tool Suite)
.springBeans

# Code Recommenders
.recommenders/

# Annotation Processing
.apt_generated/
.apt_generated_test/

# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet

# Uncomment this line if you wish to ignore the project description file.
# Generally, this file would be tracked if it contains build/dependency configurations:
#.project

### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/

### Java ###
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

### Gradle ###
.gradle
build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Cache of project
.gradletasknamecache

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

### Gradle Patch ###
**/build/

# End of https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse

Was created as a condition.

Once you’ve made your changes, commit it.

> git add .gitignore
> git commit -m "fix .gitignore"

(5) Change the authority of gradlew. [Important] If you skip this step, an error will occur during build/deploy, so be sure to do so.

First, check the permissions of gradlew.

> git ls-files -s gradlew
100644 fbd7c515832dab7b01092e80db76e5e03fe32d29 0 gradlew

If you keep the above, you have only read permission, so execute the following command and give execute permission.

> git update-index --add --chmod=+x gradlew

After execution is complete, let’s check the permissions again. As shown below, it is OK if the last 3 digits of the first 6 digits are 755.

> git ls-files -s gradlew
100755 fbd7c515832dab7b01092e80db76e5e03fe32d29 0 gradlew

Commit your changes.

> git add gradlew
> git commit -m "fix permission of gradlew"

[2] Create remote repository

(1) Log in to the AWS Management Console, search for “CodeCommit” from “Services” (1), and click (2). image.png

(2) After transitioning to the CodeCommit page, click “Create Repository”. image.png

(3) Give the same repository name as the project created in step 1 (1) and click “Create”. image.png

[3] Sync local and remote repositories

(1) When the creation of the remote repository is completed, the following page will be displayed. Click “URL Clone” (1)> “HTTPS Clone” (2). image.png

(2) Open command prompt or GitBash and execute the following command in the project root directory.

> URI copied with "HTTPS clone" from git remote add origin (1)

#### **`(3) Push contents to master of remote repository.`**
> git push -u origin master

(4) In the console, confirm that the contents of the local repository are reflected in the remote repository. image.png

About the sequel

The continuation is

At.

  1. Creating an Elastic Beanstalk environment, 4. Set up the database connection.

Change log

  • 2020/08/13: Added “[4] Create jar file” to procedure “1. Create Java (Spring Boot) application”.

Reference

environment

  • [Java SE platform history aws](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/platforms/platform-history-javase.html)

Procedure

1. Creating a Java (Spring Boot) application

  • [SpringBoot+Vue.js+ElementUI+Firebase introduction to master management application Qiita](https://qiita.com/shunp/items/abea7fa01e7a664c85da)

2. Creating a Git repository

  • [[ GitHub] Until you “Push” the repository created locally to the remote! Qiita](https://qiita.com/Futo_Horio/items/4d669f695680bc13d5fa)
  • [git: How to check file permissions on git memos Qiita](https://qiita.com/k-shimoji/items/a393bc2e1b79a2d965e3)
  • [Changing the execute permission (permission) of the file managed by Git Maguma Git Note](https://maku77.github.io/git/file-permission.html)