diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 24f4ad8..1dfec60 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -26,28 +26,46 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Normalize Harbor registry + id: registry + shell: bash + run: | + REGISTRY="${HARBOR_REGISTRY,,}" + REGISTRY="${REGISTRY#http://}" + REGISTRY="${REGISTRY#https://}" + while [ "${REGISTRY%/}" != "${REGISTRY}" ]; do + REGISTRY="${REGISTRY%/}" + done + if [[ ! "${REGISTRY}" =~ ^[a-z0-9.-]+(:[0-9]+)?$ ]]; then + echo "HARBOR_REGISTRY must contain only a registry host and optional port" >&2 + exit 1 + fi + echo "host=${REGISTRY}" >> "${GITHUB_OUTPUT}" + - name: Log in to Harbor uses: docker/login-action@v3 with: - registry: ${{ env.HARBOR_REGISTRY }} + registry: ${{ steps.registry.outputs.host }} username: ${{ secrets.HARBOR_USER }} password: ${{ secrets.HARBOR_PASSWORD }} - name: Compute image metadata id: vars env: + REGISTRY: ${{ steps.registry.outputs.host }} REF_NAME: ${{ gitea.ref_name }} REF_TYPE: ${{ gitea.ref_type }} SHA: ${{ gitea.sha }} run: | - IMAGE="${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}" + IMAGE="${REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}" SHORT_SHA="$(printf '%s' "${SHA}" | cut -c1-12)" TAGS="${IMAGE}:sha-${SHORT_SHA}" if [ "${REF_TYPE}" = "branch" ] && [ "${REF_NAME}" = "master" ]; then TAGS="${TAGS},${IMAGE}:latest" fi if [ "${REF_TYPE}" = "tag" ]; then - TAGS="${TAGS},${IMAGE}:${REF_NAME}" + RELEASE_TAG="$(printf '%s' "${REF_NAME}" | tr -c 'A-Za-z0-9_.-' '-' | cut -c1-128)" + TAGS="${TAGS},${IMAGE}:${RELEASE_TAG}" fi { echo "image=${IMAGE}" diff --git a/Dockerfile b/Dockerfile index 5140661..93f4184 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,6 @@ EXPOSE 27401 37401 USER rsmon-worker HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ - CMD ["/usr/local/bin/rsmon-worker", "health"] + CMD ["/usr/local/bin/rsmon-worker", "liveness"] ENTRYPOINT ["/usr/local/bin/rsmon-worker"] diff --git a/Makefile b/Makefile index 0206d10..c5273f1 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ build: test: RSMON_ENV=test CWD=$(CURDIR) go test \ + ./cmd/rsmon-worker \ ./internal/distworker \ ./internal/webapp \ ./internal/workercluster \ diff --git a/cmd/rsmon-worker/main.go b/cmd/rsmon-worker/main.go index 611efe1..8d39a22 100644 --- a/cmd/rsmon-worker/main.go +++ b/cmd/rsmon-worker/main.go @@ -68,6 +68,9 @@ func main() { if len(flag.Args()) > 0 && flag.Arg(0) == "health" { os.Exit(healthCheck()) } + if len(flag.Args()) > 0 && flag.Arg(0) == "liveness" { + os.Exit(livenessCheck()) + } log.Println("rsmon-worker starting...") @@ -586,3 +589,32 @@ func healthCheck() int { fmt.Println("health check ok") return 0 } + +func livenessCheck() int { + httpCfg := distworker.HTTPConfigFromEnv() + host := httpCfg.Host + switch host { + case "", "0.0.0.0": + host = "127.0.0.1" + case "::", "[::]": + host = "::1" + } + endpoint := "http://" + net.JoinHostPort(host, strconv.Itoa(httpCfg.Port)) + "/healthz" + return probeLiveness(endpoint) +} + +func probeLiveness(endpoint string) int { + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get(endpoint) + if err != nil { + fmt.Fprintf(os.Stderr, "liveness check failed: %v\n", err) + return 1 + } + defer resp.Body.Close() //nolint:errcheck + if resp.StatusCode != http.StatusOK { + fmt.Fprintf(os.Stderr, "liveness check failed: status %s\n", resp.Status) + return 1 + } + fmt.Println("liveness check ok") + return 0 +} diff --git a/cmd/rsmon-worker/main_test.go b/cmd/rsmon-worker/main_test.go new file mode 100644 index 0000000..63e18c5 --- /dev/null +++ b/cmd/rsmon-worker/main_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestProbeLiveness(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code int + want int + }{ + {name: "healthy", code: http.StatusOK, want: 0}, + {name: "unhealthy", code: http.StatusServiceUnavailable, want: 1}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(tt.code) + })) + defer srv.Close() + + if got := probeLiveness(srv.URL); got != tt.want { + t.Fatalf("probeLiveness() = %d, want %d", got, tt.want) + } + }) + } +}