diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..671af37 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,127 @@ +name: Docker CI + +on: + pull_request: + push: + schedule: + - cron: '13 13 * * 3' + +jobs: + buildx: + strategy: + matrix: + include: + - dockerfile: debian + cache: ${{ github.ref != 'refs/heads/master' + && !startsWith(github.ref, 'refs/tags/docker/') }} + publish: ${{ github.event_name == 'push' + && (startsWith(github.ref, 'refs/tags/docker/') + || github.ref == 'refs/heads/master') }} + release: ${{ github.event_name == 'push' + && startsWith(github.ref, 'refs/tags/docker/') }} + - dockerfile: alpine + cache: ${{ github.ref != 'refs/heads/master' + && !startsWith(github.ref, 'refs/tags/docker/') }} + publish: ${{ github.event_name == 'push' + && (startsWith(github.ref, 'refs/tags/docker/') + || github.ref == 'refs/heads/master') }} + release: ${{ github.event_name == 'push' + && startsWith(github.ref, 'refs/tags/docker/') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-buildx-action@v1 + + - name: Detect correct Git ref for image build + id: gitref + uses: actions/github-script@v3 + with: + result-encoding: string + script: | + let ref = 'HEAD'; + if ('${{ github.ref }}'.startsWith('refs/tags/docker/')) { + ref = '${{ github.ref }}'.substring(17).split('-')[0]; + } + return ref; + + - name: Pre-build fresh Docker images cache + run: make docker.build.cache no-cache=yes + DOCKERFILE=${{ matrix.dockerfile }} + ref=${{ steps.gitref.outputs.result }} + working-directory: ./docker/coturn + if: ${{ !matrix.cache }} + + - uses: satackey/action-docker-layer-caching@v0.0.11 + with: + key: docker-${{ matrix.dockerfile }}-buildx-{hash} + restore-keys: docker-${{ matrix.dockerfile }}-buildx- + continue-on-error: true + timeout-minutes: 10 + if: ${{ matrix.cache }} + - name: Pre-build Docker images cache + run: make docker.build.cache no-cache=no + DOCKERFILE=${{ matrix.dockerfile }} + ref=${{ steps.gitref.outputs.result }} + working-directory: ./docker/coturn + if: ${{ matrix.cache }} + + - name: Test Docker images + run: | + # Enable experimental features of Docker Daemon to run multi-arch images. + echo "$(cat /etc/docker/daemon.json)" '{"experimental": true}' \ + | jq --slurp 'reduce .[] as $item ({}; . * $item)' \ + | sudo tee /etc/docker/daemon.json + sudo systemctl restart docker + + make npm.install + make test.docker platforms=@all build=yes DOCKERFILE=${{ matrix.dockerfile }} + working-directory: ./docker/coturn + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GCR_BOT_PAT }} + if: ${{ matrix.publish }} + - name: Login to Quay.io + uses: docker/login-action@v1 + with: + registry: quay.io + username: instrumentisto+bot + password: ${{ secrets.QUAYIO_ROBOT_TOKEN }} + if: ${{ matrix.publish }} + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: instrumentistobot + password: ${{ secrets.DOCKERHUB_BOT_PASS }} + if: ${{ matrix.publish }} + + - run: make docker.push DOCKERFILE=${{ matrix.dockerfile }} + working-directory: ./docker/coturn + if: ${{ matrix.publish }} + + # On GitHub Container Registry README is automatically updated on pushes. + - name: Update README on Quay.io + uses: christian-korneck/update-container-description-action@v1 + env: + DOCKER_APIKEY: ${{ secrets.QUAYIO_API_TOKEN }} + with: + provider: quay + destination_container_repo: quay.io/coturn/coturn + readme_file: docker/coturn/README.md + if: ${{ matrix.publish }} + - name: Update README on Docker Hub + uses: christian-korneck/update-container-description-action@v1 + env: + DOCKER_USER: ${{ secrets.DOCKERHUB_BOT_USER }} + DOCKER_PASS: ${{ secrets.DOCKERHUB_BOT_PASS }} + with: + provider: dockerhub + destination_container_repo: coturn/coturn + readme_file: docker/coturn/README.md + if: ${{ matrix.publish }} + + #TODO: release diff --git a/docker/coturn/Makefile b/docker/coturn/Makefile index 45b12d8..4967bdc 100644 --- a/docker/coturn/Makefile +++ b/docker/coturn/Makefile @@ -1,3 +1,164 @@ +############################### +# Common defaults/definitions # +############################### + +comma := , +empty := +space := $(empty) $(empty) + +# Checks two given strings for equality. +eq = $(if $(or $(1),$(2)),$(and $(findstring $(1),$(2)),\ + $(findstring $(2),$(1))),1) + + + + +###################### +# Project parameters # +###################### + +COTURN_VER ?= 4.5.2 +COTURN_MIN_VER = $(strip $(shell echo $(COTURN_VER) | cut -d '.' -f1,2)) +COTURN_MAJ_VER = $(strip $(shell echo $(COTURN_VER) | cut -d '.' -f1)) + +BUILD_REV ?= 0 + +NAMESPACES := coturn \ + ghcr.io/coturn +NAME := coturn +ALL_IMAGES := \ + debian:$(COTURN_VER)-r$(BUILD_REV)-debian,$(COTURN_VER)-debian,$(COTURN_MIN_VER)-debian,$(COTURN_MAJ_VER)-debian,debian,$(COTURN_VER)-r$(BUILD_REV),$(COTURN_VER),$(COTURN_MIN_VER),$(COTURN_MAJ_VER),latest \ + alpine:$(COTURN_VER)-r$(BUILD_REV)-alpine,$(COTURN_VER)-alpine,$(COTURN_MIN_VER)-alpine,$(COTURN_MAJ_VER)-alpine,alpine +# :,,,... + +# Default is first image from ALL_IMAGES list. +DOCKERFILE ?= $(word 1,$(subst :, ,$(word 1,$(ALL_IMAGES)))) +TAGS ?= $(word 1,$(subst |, ,\ + $(word 2,!$(subst $(DOCKERFILE):, ,$(subst $(space),|,$(ALL_IMAGES)))))) +VERSION ?= $(word 1,$(subst -, ,$(TAGS)))-$(word 2,$(strip \ + $(subst -, ,$(subst $(comma), ,$(TAGS))))) + +PLATFORMS ?= linux/amd64 \ + linux/arm64 \ + linux/arm/v6 \ + linux/arm/v7 \ + linux/ppc64le \ + linux/s390x +MAIN_PLATFORM ?= $(word 1,$(subst $(comma), ,$(PLATFORMS))) + + + + +########### +# Aliases # +########### + +image: docker.image + +push: docker.push + +release: git.release + +test: test.docker + + + + +################### +# Docker commands # +################### + +docker-namespaces = $(strip $(if $(call eq,$(namespaces),),\ + $(NAMESPACES),$(subst $(comma), ,$(namespaces)))) +docker-tags = $(strip $(if $(call eq,$(tags),),\ + $(TAGS),$(subst $(comma), ,$(tags)))) +docker-platforms = $(strip $(if $(call eq,$(platforms),),\ + $(PLATFORMS),$(subst $(comma), ,$(platforms)))) + +# Runs `docker buildx build` command allowing to customize it for the purpose of +# re-tagging or pushing. +define docker.buildx + $(eval dockerfile := $(strip $(1))) + $(eval namespace := $(strip $(2))) + $(eval tag := $(strip $(3))) + $(eval git-ref := $(strip $(4))) + $(eval platform := $(strip $(5))) + $(eval no-cache := $(strip $(6))) + $(eval args := $(strip $(7))) + cd ../../ && \ + docker buildx build --force-rm $(args) \ + --platform $(platform) \ + $(if $(call eq,$(no-cache),yes),--no-cache --pull,) \ + $(if $(call eq,$(git-ref),),,--build-arg git_ref=$(git-ref)) \ + -f docker/coturn/$(dockerfile)/Dockerfile \ + -t $(namespace)/$(NAME):$(tag) ./ +endef + + +# Pre-build cache for Docker image builds. +# +# WARNING: This command doesn't apply tag to the built Docker image, just +# creates a build cache. To produce a Docker image with a tag, use +# `docker.tag` command right after running this one. +# +# Usage: +# make docker.build.cache [DOCKERFILE=(debian|alpine)] +# [platforms=($(PLATFORMS)|[,...])] +# [no-cache=(no|yes)] +# [ref=] + +docker.build.cache: + $(call docker.buildx,$(DOCKERFILE),\ + instrumentisto,\ + build-cache,\ + $(ref),\ + $(shell echo "$(docker-platforms)" | tr -s '[:blank:]' ','),\ + $(no-cache),\ + --output 'type=image$(comma)push=false') + + +# Build Docker image on the given platform with the given tag. +# +# Usage: +# make docker.image [DOCKERFILE=(debian|alpine)] +# [tag=($(VERSION)|)] +# [platform=($(MAIN_PLATFORM)|)] +# [no-cache=(no|yes)] +# [ref=] + +docker.image: + $(call docker.buildx,$(DOCKERFILE),\ + instrumentisto,\ + $(if $(call eq,$(tag),),$(VERSION),$(tag)),\ + $(ref),\ + $(if $(call eq,$(platform),),$(MAIN_PLATFORM),$(platform)),\ + $(no-cache),\ + --load) + + +# Push Docker images to their repositories (container registries), +# along with the required multi-arch manifests. +# +# Usage: +# make docker.push [DOCKERFILE=(debian|alpine)] +# [namespaces=($(NAMESPACES)|[,...])] +# [tags=($(TAGS)|[,...])] +# [platforms=($(PLATFORMS)|[,...])] +# [ref=] + +docker.push: + $(foreach namespace,$(docker-namespaces),\ + $(foreach tag,$(docker-tags),\ + $(call docker.buildx,$(DOCKERFILE),\ + $(namespace),\ + $(tag),\ + $(ref),\ + $(shell echo "$(docker-platforms)" | tr -s '[:blank:]' ','),,\ + --push))) + + + + #################### # Testing commands # #################### @@ -8,17 +169,35 @@ # https://github.com/bats-core/bats-core # # Usage: -# make test.docker [tag=($(VERSION)|)] +# make test.docker +# [tag=($(VERSION)|)] +# [platforms=($(MAIN_PLATFORM)|@all|[,...])] +# [( [build=no] +# | build=yes [DOCKERFILE=(debian|alpine)] +# [ref=] )] +test-docker-platforms = $(strip $(if $(call eq,$(platforms),),$(MAIN_PLATFORM),\ + $(if $(call eq,$(platforms),@all),$(PLATFORMS),\ + $(docker-platforms)))) test.docker: ifeq ($(wildcard node_modules/.bin/bats),) @make npm.install endif - DOCKERFILE=$(DOCKERFILE) \ - IMAGE=coturn-debian \ + $(foreach platform,$(test-docker-platforms),\ + $(call test.docker.do,\ + $(if $(call eq,$(tag),),$(VERSION),$(tag)),\ + $(platform))) +define test.docker.do + $(eval tag := $(strip $(1))) + $(eval platform := $(strip $(2))) + $(if $(call eq,$(build),yes),\ + @make docker.image DOCKERFILE=$(DOCKERFILE) \ + no-cache=no tag=$(tag) platform=$(platform) ref=$(ref) ,) + IMAGE=instrumentisto/$(NAME):$(tag) PLATFORM=$(platform) \ node_modules/.bin/bats \ --timing $(if $(call eq,$(CI),),--pretty,--formatter tap) \ tests/main.bats +endef @@ -44,9 +223,33 @@ endif +################ +# Git commands # +################ + +# Release project version (apply version tag and push). +# +# Usage: +# make git.release [ver=($(VERSION)|)] + +git-release-tag = docker/$(strip $(if $(call eq,$(ver),),$(VERSION),$(ver))) + +git.release: +ifeq ($(shell git rev-parse $(git-release-tag) >/dev/null 2>&1 && echo "ok"),ok) + $(error "Git tag $(git-release-tag) already exists") +endif + git tag $(git-release-tag) + git push origin refs/tags/$(git-release-tag) + + + + ################## # .PHONY section # ################## -.PHONY: npm.install \ +.PHONY: image push release test \ + docker.build.cache docker.image docker.push \ + git.release \ + npm.install \ test.docker \ No newline at end of file diff --git a/docker/coturn/tests/main.bats b/docker/coturn/tests/main.bats index 5c96d48..d60a39c 100644 --- a/docker/coturn/tests/main.bats +++ b/docker/coturn/tests/main.bats @@ -12,7 +12,7 @@ } @test "Coturn has correct version" { - [ -z "$VERSION" ] && skip + [ -z "$COTURN_VER" ] && skip run docker run --rm --entrypoint sh $IMAGE -c \ "turnserver -o --log-file=stdout | grep 'Version Coturn' \ @@ -22,7 +22,7 @@ [ ! "$output" = '' ] actual="$output" - [ "$actual" = "$VERSION" ] + [ "$actual" = "$COTURN_VER" ] }