Recently, I’ve been using GitHub Actions for my company’s internal project release process, and it’s been a great experience.

The main goal of this article is to help newcomers to GitHub Actions to quickly build automated tests and package and push Docker images.

Create project

This article focuses on Go as an example, but of course other languages are similar and have little to do with the language itself.

Here we start by creating a project on GitHub and writing a few simple pieces of code main.go.

1
2
3
4
5
6
7
var version = "0.0.1"
func GetVersion() string {
	return version
}
func main() {
	fmt.Println(GetVersion())
}

The content is very simple, it just prints the version number; it is accompanied by a unit test main_test.go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func TestGetVersion1(t *testing.T) {
	tests := []struct {
		name string
		want string
	}{
		{name: "test1", want: "0.0.1"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := GetVersion(); got != tt.want {
				t.Errorf("GetVersion() = %v, want %v", got, tt.want)
			}
		})
	}
}

We can execute go test to run the unit test.

1
2
3
$ go test                          
PASS
ok      github.com/crossoverJie/go-docker       1.729s

Automated Testing

Of course, the above process can be fully automated using Actions.

First, we need to create a .github/workflows/.yml* configuration file in the root path of the project, adding the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
name: go-docker
on: push
jobs:
  test:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags')
    steps:
      - uses: actions/checkout@v2
      - name: Run Unit Tests
        run: go test

To briefly explain.

  • name needless to say, is to create a noun for the current workflow.

  • on refers to the event under which it is triggered, in this case when the code is pushed, more event definitions can be found in the official documentation.

  • jobs defines the tasks, and here there is only one task named test.

This task is run in the ubuntu-latest environment and is only run when the main branch is pushed or when a tag is pushed.

It will run using actions/checkout@v2, an action wrapped by someone else, but of course using the official pull code action provided here.

  • Based on this logic, we have the flexibility to share and use other people’s actions to simplify the process, and this is where GitHub Action is very scalable.

The final run is to run your own command, which naturally triggers the unit test.

  • If it is Java, you can change it to mvn test.

It is very convenient to automatically run unit tests whenever we push code on the main branch or when code from other branches is merged in.

The results are consistent with our local runs.

Auto Publish

Next, consider automating the packaging of Docker images and uploading them to Docker Hub; to do this, first create the Dockerfile.

1
2
3
4
5
6
7
8
9
FROM golang:1.15 AS builder
ARG VERSION=0.0.10
WORKDIR /go/src/app
COPY main.go .
RUN go build -o main -ldflags="-X 'main.version=${VERSION}'" main.go
FROM debian:stable-slim
COPY --from=builder /go/src/app/main /go/bin/main
ENV PATH="/go/bin:${PATH}"
CMD ["main"]

Here the use of ldflags can be passed during the compilation of some parameters into the packaging program, such as packaging time, go version, git version, etc..

The VERSION is simply passed into the main.version variable so that it can be retrieved at runtime.

1
2
3
docker build -t go-docker:last .
docker run --rm go-docker:0.0.10
0.0.10

Then continue writing docker.yml to add automatic packaging of Docker and pushing to the docker hub.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
deploy:
    runs-on: ubuntu-latest
    needs: test
    if: startsWith(github.ref, 'refs/tags')
    steps:
      - name: Extract Version
        id: version_step
        run: |
          echo "##[set-output name=version;]VERSION=${GITHUB_REF#$"refs/tags/v"}"
          echo "##[set-output name=version_tag;]$GITHUB_REPOSITORY:${GITHUB_REF#$"refs/tags/v"}"
          echo "##[set-output name=latest_tag;]$GITHUB_REPOSITORY:latest"
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKER_USER_NAME }}
          password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
      - name: PrepareReg Names
        id: read-docker-image-identifiers
        run: |
          echo VERSION_TAG=$(echo ${{ steps.version_step.outputs.version_tag }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
          echo LASTEST_TAG=$(echo ${{ steps.version_step.outputs.latest_tag  }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
      - name: Build and push Docker images
        id: docker_build
        uses: docker/build-push-action@v2.3.0
        with:
          push: true
          tags: |
            ${{env.VERSION_TAG}}
            ${{env.LASTEST_TAG}}
          build-args: |
            ${{steps.version_step.outputs.version}}

A deploy job has been added.

1
2
needs: test
if: startsWith(github.ref, 'refs/tags')

The condition to run is that the single test process of the previous step is running and a new tag is generated before the subsequent steps are triggered.

1
name: Login to DockerHub

In this step we need to log in to DockerHub, so first we need to configure the user_name and access_token of the hub in the GitHub project.

Once configured, the variable can be used in the action.

Here we use the official login action (docker/login-action) provided by docker.

One thing to note is that we need to change the image name to lowercase, otherwise it will fail to upload, for example, if my name has an uppercase J letter, it will report an error when uploading directly.

So perform this step to convert to lowercase before uploading.

Finally, use these two variables to upload to Docker Hub.

In the future, Action will automatically perform the single test, build, and upload process as soon as we tag it.

Summary

GitHub Actions is very flexible, and most of the features you need are readily available in the marketplace.

For example, you can use ssh to log in to your own server and execute commands or scripts, so there’s a lot of room for imagination.

It’s like building blocks, and you can use it flexibly to accomplish your needs.


Reference https://crossoverjie.top/2021/03/26/go/github-actions/