Creating CI/CD pipeline using triggers of google cloud for web app written in Spring Boot(java) and React.js
Tags: java, javascript, googlecloud, react
Published: 2020-07-14 12:22:56

Hello all😊👋
This is my first post so bear with me.

In this article, I am going to tell you guys how I was able to automate the testing, building and deployment(on GCP **APP ENGINE**) of my web app written in **React** and **Spring Boot** ( code base on GitHub) using google cloud trigger.

## Contents of this Article
1) Directory structure I used.
2) Configuring GCP trigger to listen to GitHub commits of a repository.
3) Understanding different configuration files required.
4) Deploying frontend and backend as different service to GCP APP Engine
5) Serving frontend and backend from two different services on the same domain.


### 1) Directory structure:
  Below is the directory structure I am using to fulfil my requirement of testing, building and deploying **UI** and **Server** on a GitHub commit.

![Alt root directory structure](https://dev-to-uploads.s3.amazonaws.com/i/kbrnlfr4czd5epu903ht.PNG)


### 2) Configuring GCP trigger to listen to GitHub commits of a repository

* Go to your GCP console
* Create a new project named web-app, you can give any name but here I will use web-app
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yh15frg5axvxirphuhex.PNG)

* Once project is created, select that project and go to triggers as mentioned in below.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kvnr88fsda7tvi42bfn8.PNG)

* Once you are in triggers page you will see **Connect Repository** as shown below, this connect the trigger to a particular branch(my case it was a master branch) of GitHub repository and once connected it will listen to GitHub commits on that branch.

Below screenshots explains the steps to connect a branch from GitHub to your GCP projects trigger.

a)![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wm23pxq4v3k30s9ueh0s.PNG)


b)![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fo1h380fiwb5eokr9khc.PNG)

c) First, you have to **add a new account**, once the GitHub account is added then click on **Edit repositories on GitHub**, which will redirect you to GitHub where it will ask you for repositories to select for which trigger will listen.
d) Once Trigger created you can see the details of the trigger.
Currently below are the configuration of my trigger
       * Event: Push to any branch
       * Status: Enabled
       * Build Configuration: Auto-Detected
You can edit these configurations
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dw45io0j0eh7czv6l9qd.PNG)

Finally, your trigger is connected to your GitHub repo but before we commit anything to this repository we need to understand the configuration file required to test, build and deploy our app to **APP Engine**.


### 3) Understanding different configuration files required.
We need to create few scripts which will be picked up by GCP triggers to build and deploy our app to GCP **APP ENGINE**

* **cloudbuild.yaml**: It is picked up and executed by GCP triggers on every GitHub commit. It should be present in the root of our project directory. 

* **app.yaml**: This is the file used for deploying our web-app to GCP **APP ENGINE** based on the configurations specified in it. 

### 4) Deploying frontend and backend as different service to GCP APP Engine
[Google recommends using microservices within an App engine](https://cloud.google.com/appengine/docs/standard/java/microservices-on-app-engine) project instead of building one monolith that serves all requests. So I’m going to have a front end service that uses the Node.js runtime and a back end service that uses the Java runtime.


* **Deploying Backend**(Spring Boot java application) as new service

For deploying Backend java application we will use docker.
we will follow the below steps
a) First, create a Docker image using below docker file
b) push that image to GCP **Container Registry**
c) Deploying that image to GCP **APP ENGINE** using below app.yaml file

Below is my Docker file(present in server/Dockerfile)
```
FROM openjdk:8-alpine
VOLUME /tmp
ADD target/webapp-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
```

app.yaml file for backend deployment (present in server/src/main/appengine)

```
env: flex
service: backend
instance_class: F1
handlers:
- url: /.*
  script: this field is required, but ignored
```


* **Deploying frontend** as a new service: 
     I am going to use Express.js to host my static files, below is the code to serve static files
```
const express = require('express');

const path = require('path');

const app = express();

// This code makes sure that every request that matches a static file in the
// build folder, it serves that file.
app.use(express.static(path.join(__dirname, 'build')));

// This code makes sure that any request that does not matches a static file
// in the build folder, will just serve index.html.
app.get('/*', (req, res) => {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

// Starting the server
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
});
```
After this, we will create the app.yaml file UI folder as shown below, But remember that package.json, start scripts should be **node app.js** where app.js is having server-side code for serving static files.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m6i624l0fv2bfsi4fa96.PNG)

app.yaml file to deploy node.js application to host our static files
```
runtime: nodejs
# new service named default is created where frontend will hosted
service: default
env: flex
instance_class: F1
threadsafe: true
handlers:
  - url: /
    static_files: build/index.html
    upload: build/index.html
  - url: /
    static_dir: build
```
### 5) Serving frontend and backend from two different services on the same domain.
To let Google’s load balancer decide what microservice needs to handle what request, you can define a [dispatch.yaml](https://cloud.google.com/appengine/docs/standard/nodejs/reference/dispatch-yaml) file to [overwrite App Engine’s default routing rules](https://cloud.google.com/appengine/docs/standard/python/how-requests-are-routed#routing_with_a_dispatch_file). This has to be done after all independent services have started. My dispatch.yaml file looks like this:
```
dispatch:
# Route the URLs that point to the java backend to backend service
  - url: "*/test/v1/*"
    service: backend
# Route all other urls to the React.js frontend
  - url: "*/*"
    service: default
```

Below is the final cloudbuild.yaml file(present in root directory of project) for frontend and backend steps to be executed by trigger
```
# below are the spteps executed by trigger
steps:
# frontend deployment
# Step 1: For installing dependencies
  - name: "gcr.io/cloud-builders/npm"
    dir: 'ui'
    args: ["install"]

# Step 2: For creating optinimized build
  - name: "gcr.io/cloud-builders/npm"
    dir: 'ui'
    args: ["run", "build"]

# Step 3: This step will execute app.yaml in your ui folder and deploy your app based on the configuration specified
  - name: "gcr.io/cloud-builders/gcloud"
    dir: 'ui'
    args: ["app", "deploy"]
# timeout specified for this step as deployment may take more that default time(10min)
    timeout: "30m0s" 

# backend deployment
# Step 4: Running maven tests
  - name: maven:3-jdk-8
    entrypoint: mvn
    dir: 'server'
    args: ["test"]

# Step 5: Running **mvn clean install** and skipping test cases
  - name: maven:3-jdk-8
    entrypoint: mvn
    dir: 'server'
    args: ["clean", "install", "-Dmaven.test.skip=true"]

# Step 6: Creating docker image using docker file present in server folder
  - name: docker
    dir: 'server'
    args: ["build", "-t", "gcr.io/web-app/webapp3", "."]

# Step 7: pushing docker image to GCP Container Registry
  - name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/web-app/webapp3"]

# Step 8: Deploying this image using app.yaml present in **server/src/main/appengine** to GCP **APP ENGINE**
  - name: 'gcr.io/cloud-builders/gcloud'
    dir: 'server/src/main/appengine'
    args: ['app', 'deploy', "--image-url=gcr.io/web-app/webapp3"]
    timeout: "30m0s"

# Step 9: This step is make sure that dispatch.yaml file is deployed once all the services are started
  # dispatch.yaml deployment
  - name: "gcr.io/cloud-builders/gcloud"
    args: ["app", "deploy", "dispatch.yaml"]
    timeout: "30m0s"
timeout: "100m0s"
images: ["gcr.io/web-app/webapp3"]

```


once all services are started you can go to GCP **APP ENGINE** and see the deployed services and path the dispatch routes like below:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6rspv286sz8q7t7a6z07.PNG)


####Now you can go ahead and commit to your GitHub repository.
Go to you **Cloud build**->**History** and you see can build started.

once build is successfully completed then below is the screenshot you can see.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/v55ps8oqiogos9abekrr.PNG)





##Hurray you have successfully deployed you web-app to google cloud.

If you have come this far reading this article, please provide your valuable comment or feedback so that I can improve next time. 

Happy Reading !!