---
# System prepended metadata

title: 'Github action practice ( Angular, Spring Boot, Containerication, gmail notify)'
tags: [github action, CI/CD, Angular, Java, spring boot]

---

# Github action practice ( Angular, Spring Boot, Containerication, gmail notify)


## What is Github action?

Coming soon

## How to use Github action?

Coming soon

## Getting hands dirty with Github action

### Environment 

Before we start,        
build a front-end project with angular.
And a back-end project with spring boot.

My front-end project environment is:

- npm: 8.19.1
- node: v19.4.0
- @angular/cli: 16.2.14
- Angular: 16.2.12
- Bootstrap: 5.0.1 [Bootstrap](https://www.npmjs.com/package/bootstrap/v/5.0.1)
- ngx-bootstrap: 10.2.0 [ngx-bootstrap](https://www.npmjs.com/package/ngx-bootstrap/v/10.2.0)
- bootstrap-icons: 1.11.3 [bootstrap-icons](https://www.npmjs.com/package/ngx-bootstrap-icons)
- @types/bootstrap: 5.2.10 

My back-end project environment is:     

- Java 17
- Maven 4.0.0
- Spring Boot 3.3.1
- lombok
- Swagger 2.0.2
- h2database

My folder structure is:

```
ninja-ddd-practice
├── ninja-frontend
│   ├── src
│   └── ...
├── ninja-backend
│   ├── src
│   └── ...
```

### Plan

Let's list the what we want to do with Github action.

- Cache front-end project dependencies to speed up the build (TODO)
- Build front-end project
- Unit test front-end project (TODO)
- SonarCloud analysis to make sure the front-end project code quality (TODO)
- Snyk test to make sure the front-end project security (TODO)
- Build front-end project image
- Push front-end project image to docker hub
- Cache back-end project dependencies to speed up the build (TODO)
- Build back-end project
- Unit test back-end project (TODO)
- SonarCloud analysis to make sure the back-end project code quality (TODO)
- Snyk test to make sure the back-end project security (TODO)
- Build back-end project image
- Push back-end project image to docker hub
- Deploy front-end on ??? (TODO)
- Health check front-end (TODO)
- Deploy back-end on ??? (TODO)
- Health check back-end (TODO)
- Send notification to email

### Dockerize front-end project

Create a Dockerfile in the front-end project root directory.
Our front-end will be served by nginx.

```Dockerfile
# 使用Node.js作為基礎鏡像
FROM node:19.4.0 as build

# 設置工作的目錄
WORKDIR /app

# 複製package.json和package-lock.json
COPY package*.json ./

# 安裝依賴，好孩子不會用--legacy-peer-deps，會好好找到相容的版本
RUN npm install --legacy-peer-deps

# 複製所有源代碼
COPY . .

# 構建應用
# RUN npm run build -- --prod # 不太確定，但好像這個指令是舊版的
# 新版的指令
RUN npm run build -- --configuration production

# 使用nginx作為最終鏡像
FROM nginx:stable-alpine

# 從build階段複製構建結果到nginx的html目錄
COPY --from=build /app/dist/ninja-frontend /usr/share/nginx/html/

# 暴露80端口
EXPOSE 80

# 啟動nginx
CMD ["nginx", "-g", "daemon off;"]
```

### Dockerize back-end project

Create a Dockerfile in the back-end project root directory.

```Dockerfile
# Use the official OpenJDK image, which is based on Linux Alpine
FROM openjdk:17-jdk-slim
# Set the working directory in the container
WORKDIR /app
# Copy the packaged jar file into the container
COPY target/*.jar app.jar
# Run the jar file
ENTRYPOINT ["java","-jar","/app/app.jar"]
# Expose the port
EXPOSE 8080
```

### Github action workflow

Create a `.github/workflows` directory in the ninja-ddd-practice git root directory.
Create a `main.yml` file in the `.github/workflows` directory.

Let's break down the workflow file.

1. Base

```yaml
# Name of the workflow
name: Ninja-ddd-practice CI/CD Pipeline

# When the workflow will be triggered
on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
```

2. Job section

```yaml
# Define the jobs that will be executed
jobs:
```

3. Build, This job will build the front-end and back-end project.

```yaml
# Define this job name is build
  build:
#   Define the runner for this job will run on ubuntu-latest
#   outputs is used to pass data between jobs, here we create a version output, let after jobs can use it.
#   steps is a list of tasks that will be executed in this job
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.VERSION }}

    steps:
#    Checkout the code from the repository，ˋ這是一個官方的action，使在 workflow 中，檢出我們在 github repository 中的程式碼。其實就是 git clone 我們的程式碼到 runner 中。讓後續的流程都可以使用這些程式碼。
    - uses: actions/checkout@v2

#   Generate version number，這個步驟是用來生成版本號的，我們會在這個步驟中生成一個版本號，這個版本號是由日期、github run number、github sha 組成的。讓我們在後續 image tag 中使用。
#   id 是這個步驟的唯一標識，可以在後續的步驟中使用這個 id 來引用這個步驟的輸出。
#   >> $GITHUB_OUTPUT 的意思是將結果寫入到一個名為 GITHUB_OUTPUT 的變數中。
    - name: Generate version number
      id: version
      run: |
        echo "VERSION=$(date +'%Y.%m.%d')-${GITHUB_RUN_NUMBER}-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT

#   Set up JDK 17，這個步驟是用來設置 JDK 17 的，我們會在這個步驟中設置 JDK 17，讓我們可以在後續的步驟中使用 JDK 17 來編譯我們的程式碼。
#   distribution 是這個步驟的參數，這個參數是用來指定我們使用的 JDK 的發行版，這裡我們使用的是 AdoptOpenJDK。其他也有 oracle、zulu 等。
    - name: Set up JDK 17
      uses: actions/setup-java@v2
      with:
        java-version: '17'
        distribution: 'adopt'
    
#   Build ninja-backend，我們會在這個步驟中編譯 ninja-backend。
#   要注意的是 cd 進我們的 root directory 後，我們需要先給 mvnw 檔案權限，不然會報錯。
#   DskipTests 是用來跳過測試的，因為我們只是要編譯，不需要跑測試。之後我會再來修改他，加上測試。
    - name: Build ninja-backend
      run: |
        cd ./ninja-backend/  
        chmod +x mvnw
        ./mvnw clean package -DskipTests
    
#   Build ninja-frontend，我們會在這個步驟中編譯 ninja-frontend。
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '19.4.0'
    
#   安裝依賴，其實在我本機上直接 npm install 是可以的，但是在 github action 中，會報錯，所以我才加上 --legacy-peer-deps。原因未知。
    - name: Build ninja-frontend
      run: |
        cd ./ninja-frontend  
        npm install --legacy-peer-deps
        npm run build -- --configuration production
    
#   這段配置的作用是將 ninja-backend/target 和 ninja-frontend/dist 這兩個目錄中的所有文件上傳到 GitHub 的工件存儲，並將這些文件打包成一個名為 dist 的工件。
#   | 這個符號是用來分隔多個路徑的，這樣我們就可以將多個路徑的文件一起上傳。
    - name: Archive production artifacts
      uses: actions/upload-artifact@v2
      with:
        name: dist
        path: |
          ninja-backend/target
          ninja-frontend/dist
```

4. Push image, This job will push the front-end and back-end project image to docker hub.

```yaml
# Define this job name is push
# needs means this job will run after the build job. In normal case, if the build job failed, this job will not run.
  push:
    needs: build
    runs-on: ubuntu-latest
    
#  steps is a list of tasks that will be executed in this job
#  Use `actions/checkout@v2` to checkout the code from the repository, to get the code that we need to build the image.
    steps:
    - uses: actions/checkout@v2
    
# In previous job, we use `actions/upload-artifact@v2` to upload the build result to github artifact and name it dist.
# In this job, we use `actions/download-artifact@v2` to download the artifact.
    - name: Download artifacts
      uses: actions/download-artifact@v2
      with:
        name: dist

# Log in to Docker Hub
# We need to set the github secrets in the repository settings, so we can use it in the workflow.
    - name: Log in to Docker Hub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_HUB_USERNAME }}
        password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
    
# Build and push backend Docker image
# In previous job, we generate a version number, we can use it here by `${{ needs.build.outputs.version }}`.
    - name: Build and push backend Docker image
      uses: docker/build-push-action@v2
      with:
        context: ./ninja-backend
        push: true
        tags: |
          vincentkai/ninja-backend:latest
          vincentkai/ninja-backend:${{ needs.build.outputs.version }}
    
# Build and push frontend Docker image
    - name: Build and push frontend Docker image
      uses: docker/build-push-action@v2
      with:
        context: ./ninja-frontend
        push: true
        tags: |
          vincentkai/ninja-frontend:latest
          vincentkai/ninja-frontend:${{ needs.build.outputs.version }}
```

- In the `Log in to Docker Hub` step, there are two secrets `DOCKER_HUB_USERNAME` and `DOCKER_HUB_ACCESS_TOKEN` that we need to set in the repository settings.
    1. Go to your docker hub account settings. Click on `Personal access tokens` and generate a new token. Copy the token.
    ![image](https://hackmd.io/_uploads/SyTZaEeiC.png)
    ![image](https://hackmd.io/_uploads/rkNzTNeo0.png)

    2. Go to your repository settings. Click on `Secrets and variables` > `Actions`. Down the page, you will see Repository secrets. Click on `New repository secret`. Add `DOCKER_HUB_USERNAME` and `DOCKER_HUB_ACCESS_TOKEN` with your docker hub username and token. Then you can use it in the workflow.
    ![image](https://hackmd.io/_uploads/ryM7pEejR.png)


5. notify, This job will send an email notification.

There are many ways to send the notification, here I will show you how to send an email notification.

```yaml
# Define this job name is notify
# This job will run after the build and push job.
# In previous job, we said if the build job failed, the push job will not run. But I want to receive the notification whether the job failed or not. So I use if: always(). It means this job will always run.
# `if` has many options, like `always()`, `success()`, `failure()`, `cancelled()`, `changed()`, `github.event_name == 'push'`, `github.event_name == 'pull_request'`, etc.
  notify:
    needs: [build, push]
    runs-on: ubuntu-latest
    if: always()


    steps:
#   dawidd6/action-send-mail is an action that can send an email notification.
#   Here has repository secrets `EMAIL_USER` and `EMAIL_PASS` that we need to set in the repository settings.
#   `github.repository` is the repository name.
#   `job.status` is the job status, like success, failure, cancelled.
#   `github.event.head_commit.message` is the last commit message.
#   `github.server_url` is the github server url.
#   `github.run_id` is the github run id. We combine these information to build the actions run url, let the receiver can check the details.
    - name: Send email notification
      uses: dawidd6/action-send-mail@v2
      with:
        server_address: smtp.gmail.com
        server_port: 465
        username: ${{secrets.EMAIL_USER}}
        password: ${{secrets.EMAIL_PASS}}
        subject: GitHub Actions job result for ${{github.repository}}
        body: |
          Job in ${{github.repository}} finished with status ${{job.status}}.
          
          Build job status: ${{ needs.build.result }}
          Push job status: ${{ needs.push.result }}
          
          Commit message: ${{ github.event.head_commit.message }}
          
          See details here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        to: mister33221@gmail.com
        from: GitHub Actions
```

- In the `Send email notification` step, there are two secrets `EMAIL_USER` and `EMAIL_PASS` that we need to set in the repository settings.
    1. Here we use gmail, so we need to get the gmail access token.
    2. Go to your gmail account home page, use search bar to search `App passwords 應用程式密碼`. Generate a new password for the app. Copy the password.
    ![image](https://hackmd.io/_uploads/S1kyyHliR.png)

    3. Go to your repository settings. Click on `Secrets and variables` > `Actions`. Down the page, you will see Repository secrets. Click on `New repository secret`. Add `EMAIL_USER` and `EMAIL_PASS` with your gmail address and password. Then you can use it in the workflow.

### Test the workflow

- In the repositorys action tab you will see the jobs running.
![image](https://hackmd.io/_uploads/HyPQyrxoA.png)
- After the jobs done. We will receive the mail.
![image](https://hackmd.io/_uploads/BypCJBgoC.png)


## Conclusion

As always, I put my code on my [Github](https://github.com/mister33221/ninja-ddd-practice.git). You can check it out.