From df3588ae2ed355b75c5caef2286fe2e476e34516 Mon Sep 17 00:00:00 2001 From: Michael Karbowiak Date: Fri, 4 Oct 2024 23:49:48 +0200 Subject: [PATCH] Fix build pipeline and ensure the docker container can build properly --- .docker/Dockerfile | 29 +++++++++ .github/dependabot.yml | 15 +++++ .github/workflows/pipeline.yml | 76 +++++++++++++++++++++++ .gitignore | 4 ++ docker-compose.yml | 13 ++++ kubernetes.yaml | 107 +++++++++++++++++++++++++++++++++ src/go.mod | 7 ++- src/go.sum | 11 +--- src/helpers/image.go | 8 ++- src/proxy/esi.go | 2 - 10 files changed, 257 insertions(+), 15 deletions(-) create mode 100644 .docker/Dockerfile create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/pipeline.yml create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 kubernetes.yaml diff --git a/.docker/Dockerfile b/.docker/Dockerfile new file mode 100644 index 0000000..af2a848 --- /dev/null +++ b/.docker/Dockerfile @@ -0,0 +1,29 @@ +# Build stage +FROM golang:1.23-alpine AS builder + +# Install necessary build tools and WebP libraries +RUN apk add --no-cache gcc musl-dev libwebp-dev + +# Set the working directory +WORKDIR /app + +# Copy the source code +COPY /src /app + +# Build the application +ENV CGO_ENABLED=1 +RUN go build -o esi-imageproxy + +# Final stage +FROM alpine:latest + +RUN apk add --no-cache libwebp + +# Set the working directory +WORKDIR /app + +# Copy the binary from the builder stage +COPY --from=builder /app/esi-imageproxy . + +# Set the command to run the binary +CMD ["./esi-imageproxy"] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..71fc33f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..0d10103 --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,76 @@ +name: Pipeline + +on: + push: + branches: ["master"] + tags: ["v*.*.*"] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-container: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Get short commit hash + id: short_commit + run: echo "::set-output name=hash::$(echo ${GITHUB_SHA::8})" + + - name: Checkout repository + uses: actions/checkout@v4 + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + latest + ${{ steps.short_commit.outputs.hash }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + with: + context: . + file: .docker/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + #platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..382c568 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/settings.json +.idea/ +.env +.DS_Store diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..00bc8fc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + esi-proxy: + image: ghcr.io/eve-kill/esi-imageproxy:latest + ports: + - "9501:9501" + networks: + - proxy + +networks: + proxy: + driver: bridge diff --git a/kubernetes.yaml b/kubernetes.yaml new file mode 100644 index 0000000..af9057f --- /dev/null +++ b/kubernetes.yaml @@ -0,0 +1,107 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: esi-imageproxy + labels: + app: esi-imageproxy +spec: + replicas: 3 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + selector: + matchLabels: + app: esi-imageproxy + template: + metadata: + labels: + app: esi-imageproxy + spec: + containers: + - name: esi-imageproxy + image: ghcr.io/eve-kill/esi-imageproxy:latest + imagePullPolicy: Always + ports: + - containerPort: 9501 + resources: + requests: + memory: "256Mi" + cpu: "500m" + limits: + memory: "2048Mi" + cpu: "1" + livenessProbe: + httpGet: + path: /healthz + port: 9501 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /readyz + port: 9501 + initialDelaySeconds: 30 + periodSeconds: 10 + terminationGracePeriodSeconds: 30 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + +--- +apiVersion: v1 +kind: Service +metadata: + name: esi-imageproxy +spec: + selector: + app: esi-imageproxy + ports: + - protocol: TCP + port: 9501 + targetPort: 9501 + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: esi-imageproxy-ingress + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-http-version: "1.1" + nginx.ingress.kubernetes.io/proxy-keepalive: "on" + nginx.ingress.kubernetes.io/proxy-keepalive-timeout: "60s" + nginx.ingress.kubernetes.io/proxy-keepalive-requests: "100" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "10s" + nginx.ingress.kubernetes.io/proxy-read-timeout: "15s" + nginx.ingress.kubernetes.io/proxy-send-timeout: "15s" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/proxy-buffering: "on" + nginx.ingress.kubernetes.io/proxy-buffers-number: "4" + nginx.ingress.kubernetes.io/proxy-buffer-size: "8k" + nginx.ingress.kubernetes.io/proxy-max-temp-file-size: "1024m" + nginx.ingress.kubernetes.io/proxy-request-buffering: "on" + nginx.ingress.kubernetes.io/proxy-response-buffering: "on" + nginx.ingress.kubernetes.io/proxy-gzip: "on" + nginx.ingress.kubernetes.io/proxy-gzip-min-length: "256" + nginx.ingress.kubernetes.io/proxy-gzip-types: "application/json application/javascript text/css text/html text/xml text/plain" +spec: + rules: + - host: your.domain.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: esi-imageproxy + port: + number: 9501 + tls: + - hosts: + - your.domain.com + secretName: esi-imageproxy-tls diff --git a/src/go.mod b/src/go.mod index 091aef4..670844b 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,9 +3,12 @@ module github.com/eve-kill/esi-imageproxy go 1.23.1 require ( - github.com/chai2010/webp v1.1.1 // indirect + github.com/kolesa-team/go-webp v1.0.4 + github.com/patrickmn/go-cache v2.1.0+incompatible +) + +require ( github.com/disintegration/imaging v1.6.2 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect ) diff --git a/src/go.sum b/src/go.sum index 6dca134..19b9c73 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,14 +1,7 @@ -github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk= -github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ= +github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/src/helpers/image.go b/src/helpers/image.go index c99674c..33f9f95 100644 --- a/src/helpers/image.go +++ b/src/helpers/image.go @@ -10,7 +10,8 @@ import ( "strings" "time" - "github.com/chai2010/webp" + "github.com/kolesa-team/go-webp/encoder" + "github.com/kolesa-team/go-webp/webp" ) // CacheOptimizedImages optimizes and caches images in WebP, PNG, and JPEG formats. @@ -19,7 +20,10 @@ func CacheOptimizedImages(cache *Cache, cacheKey string, img image.Image, header // Optimize and cache WebP webpBuffer := new(bytes.Buffer) - if err := webp.Encode(webpBuffer, img, &webp.Options{Lossless: false, Quality: 80}); err != nil { + options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, 80) + if err != nil { + log.Printf("Error creating WebP encoder options: %v", err) + } else if err := webp.Encode(webpBuffer, img, options); err != nil { log.Printf("Error encoding WebP: %v", err) } else { cache.Set(cacheKey+"-webp", CacheItem{ diff --git a/src/proxy/esi.go b/src/proxy/esi.go index 2df7da7..3c6b340 100644 --- a/src/proxy/esi.go +++ b/src/proxy/esi.go @@ -68,7 +68,6 @@ func HandleRequest(proxy *httputil.ReverseProxy, cache *helpers.Cache) http.Hand // Check if the optimized image is in the cache if _, found := cache.Get(preferredCacheKey); found { - log.Printf("Cache HIT for %s (%s)", r.URL.Path, preferredFormat) helpers.ServeOptimizedImage(w, r, cache, cacheKey) return } @@ -85,7 +84,6 @@ func HandleRequest(proxy *httputil.ReverseProxy, cache *helpers.Cache) http.Hand // Check if the response status is OK if recorder.status != http.StatusOK { - http.Error(w, "Failed to fetch image", recorder.status) return }