From a74d9b4fbc8f6507bd8836b3869877d4fa64c90a Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Sat, 4 Feb 2023 03:18:27 +0100 Subject: [PATCH] feat: add docker images Borrows the most minimal set of conventions from Kubo and IPFS Cluster: - git tags are translated to Docker tags with the same names - every commit on main branch is tagged 'main-YYYY-DD-MM-commit' --- .github/workflows/docker-image.yml | 49 ++++++++++++++++++++ .gitignore | 10 +++++ Dockerfile | 72 ++++++++++++++++++++++++++++++ docker/entrypoint.sh | 18 ++++++++ docker/get-docker-tags.sh | 56 +++++++++++++++++++++++ go.mod | 4 +- go.sum | 4 +- main.go | 34 ++++++++++++++ 8 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/docker-image.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100755 docker/entrypoint.sh create mode 100755 docker/get-docker-tags.sh diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..276fdbe --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,49 @@ +name: Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - 'main' + tags: + - 'v*' + +jobs: + push_to_registry: + if: github.repository == 'ipfs/bifrost-gateway' || github.event_name == 'workflow_dispatch' + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + IMAGE_NAME: ipfs/bifrost-gateway + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Get tags + id: tags + run: | + echo "value<> $GITHUB_OUTPUT + ./docker/get-docker-tags.sh "$(date -u +%F)" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + shell: bash + + - name: Log in to Docker Hub + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + username: ${{ vars.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build Docker image and publish to Docker Hub + uses: docker/build-push-action@v2 + with: + platforms: linux/amd64,linux/arm/v7,linux/arm64/v8 + context: . + push: true + file: ./Dockerfile + tags: "${{ steps.tags.outputs.value }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56164e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.swp +*.out +*.coverprofile +*.test +*.orig +*~ + +.tarball + +bifrost-gateway diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d853bc3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +FROM golang:1.19-buster AS builder +MAINTAINER IPFS Stewards + +# This dockerfile builds and runs bifrost-gateway + +ENV GOPATH /go +ENV SRC_PATH $GOPATH/src/github.com/ipfs/bifrost-gateway +ENV GO111MODULE on +ENV GOPROXY https://proxy.golang.org + +ENV SUEXEC_VERSION v0.2 +ENV TINI_VERSION v0.19.0 +RUN set -eux; \ + dpkgArch="$(dpkg --print-architecture)"; \ + case "${dpkgArch##*-}" in \ + "amd64" | "armhf" | "arm64") tiniArch="tini-static-$dpkgArch" ;;\ + *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \ + esac; \ + cd /tmp \ + && git clone https://github.com/ncopa/su-exec.git \ + && cd su-exec \ + && git checkout -q $SUEXEC_VERSION \ + && make su-exec-static \ + && cd /tmp \ + && wget -q -O tini https://github.com/krallin/tini/releases/download/$TINI_VERSION/$tiniArch \ + && chmod +x tini + +# Get the TLS CA certificates, they're not provided by busybox. +RUN apt-get update && apt-get install -y ca-certificates + +COPY --chown=1000:users go.* $SRC_PATH/ +WORKDIR $SRC_PATH +RUN go mod download + +COPY --chown=1000:users . $SRC_PATH +RUN git config --global --add safe.directory /go/src/github.com/ipfs/bifrost-gateway +RUN go install + + +#------------------------------------------------------ +FROM busybox:1-glibc +MAINTAINER IPFS Stewards + +ENV GOPATH /go +ENV SRC_PATH /go/src/github.com/ipfs/bifrost-gateway +ENV BIFROST_GATEWAY_PATH /data/bifrost-gateway + +EXPOSE 9094 +EXPOSE 9095 +EXPOSE 9096 + +COPY --from=builder $GOPATH/bin/bifrost-gateway /usr/local/bin/bifrost-gateway +COPY --from=builder $SRC_PATH/docker/entrypoint.sh /usr/local/bin/entrypoint.sh +COPY --from=builder /tmp/su-exec/su-exec-static /sbin/su-exec +COPY --from=builder /tmp/tini /sbin/tini +COPY --from=builder /etc/ssl/certs /etc/ssl/certs + +RUN mkdir -p $BIFROST_GATEWAY_PATH && \ + adduser -D -h $BIFROST_GATEWAY_PATH -u 1000 -G users ipfs && \ + chown ipfs:users $BIFROST_GATEWAY_PATH + +VOLUME $BIFROST_GATEWAY_PATH +ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"] + +# TODO: allow overriding below via env? +CMD [ \ + "--saturn-orchestrator", "https://orchestrator.strn.pl/nodes/nearby", \ + "--saturn-logger", "https://twb3qukm2i654i3tnvx36char40aymqq.lambda-url.us-west-2.on.aws", \ + "--kubo-rpc", "https://node0.delegate.ipfs.io", "--kubo-rpc", "https://node1.delegate.ipfs.io", "--kubo-rpc", "https://node2.delegate.ipfs.io", "--kubo-rpc", "https://node3.delegate.ipfs.io", \ + "--gateway-port", "8081", \ + "--metrics-port", "8041" \ +] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..36bf32b --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -e +user=ipfs + +if [ -n "$DOCKER_DEBUG" ]; then + set -x +fi + +if [ `id -u` -eq 0 ]; then + echo "Changing user to $user" + exec su-exec "$user" "$0" $@ +fi + +# Only ipfs user can get here +bifrost-gateway --version + +exec bifrost-gateway $@ diff --git a/docker/get-docker-tags.sh b/docker/get-docker-tags.sh new file mode 100755 index 0000000..e14b3a4 --- /dev/null +++ b/docker/get-docker-tags.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# get-docker-tags.sh produces Docker tags for the current build +# +# Usage: +# ./get-docker-tags.sh [git tag name] +# +# Example: +# +# # get tag for the main branch +# ./get-docker-tags.sh $(date -u +%F) testingsha main +# +# # get tag for a release tag +# ./get-docker-tags.sh $(date -u +%F) testingsha release v0.5.0 +# +# # Serving suggestion in CI +# ./get-docker-tags.sh $(date -u +%F) "$CI_SHA1" "$CI_BRANCH" "$CI_TAG" +# +set -euo pipefail + +if [[ $# -lt 1 ]] ; then + echo 'At least 1 arg required.' + echo 'Usage:' + echo './get-docker-tags.sh [git commit sha1] [git branch name] [git tag name]' + exit 1 +fi + +BUILD_NUM=$1 +GIT_SHA1=${2:-$(git rev-parse HEAD)} +GIT_SHA1_SHORT=$(echo "$GIT_SHA1" | cut -c 1-7) +GIT_BRANCH=${3:-$(git symbolic-ref -q --short HEAD || echo "unknown")} +GIT_TAG=${4:-$(git describe --tags --exact-match 2> /dev/null || echo "")} + +IMAGE_NAME=${IMAGE_NAME:-ipfs/bifrost-gateway} + +echoImageName () { + local IMAGE_TAG=$1 + echo "$IMAGE_NAME:$IMAGE_TAG" +} + +if [[ $GIT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc ]]; then + echoImageName "$GIT_TAG" + +elif [[ $GIT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echoImageName "$GIT_TAG" + echoImageName "latest" + echoImageName "release" # see: https://github.com/ipfs/go-ipfs/issues/3999#issuecomment-742228981 + +elif [ "$GIT_BRANCH" = "main" ]; then + echoImageName "main-${BUILD_NUM}-${GIT_SHA1_SHORT}" + echoImageName "main-latest" + +else + echo "Nothing to do. No docker tag defined for branch: $GIT_BRANCH, tag: $GIT_TAG" + +fi diff --git a/go.mod b/go.mod index 74f2ca7..dfac8c6 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/protocol/bifrost-gateway +module github.com/ipfs/bifrost-gateway go 1.19 @@ -12,7 +12,7 @@ require ( github.com/ipfs/go-ipfs-exchange-offline v0.3.0 github.com/ipfs/go-ipld-format v0.4.0 github.com/ipfs/go-ipns v0.3.0 - github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1 + github.com/ipfs/go-libipfs v0.4.1-0.20230208030004-93dd0a02a18b github.com/ipfs/go-merkledag v0.9.0 github.com/ipfs/go-namesys v0.7.0 github.com/ipfs/go-path v0.3.0 diff --git a/go.sum b/go.sum index dc3b3a5..e0327fb 100644 --- a/go.sum +++ b/go.sum @@ -392,8 +392,8 @@ github.com/ipfs/go-ipld-legacy v0.1.1 h1:BvD8PEuqwBHLTKqlGFTHSwrwFOMkVESEvwIYwR2 github.com/ipfs/go-ipld-legacy v0.1.1/go.mod h1:8AyKFCjgRPsQFf15ZQgDB8Din4DML/fOmKZkkFkrIEg= github.com/ipfs/go-ipns v0.3.0 h1:ai791nTgVo+zTuq2bLvEGmWP1M0A6kGTXUsgv/Yq67A= github.com/ipfs/go-ipns v0.3.0/go.mod h1:3cLT2rbvgPZGkHJoPO1YMJeh6LtkxopCkKFcio/wE24= -github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1 h1:drLEvJmJM0UrXvQfMF84EqSeGSXl5Elk/PAcc0XnNb4= -github.com/ipfs/go-libipfs v0.4.1-0.20230207021459-1a932f7bb3c1/go.mod h1:XKRXmSlJ32qlpxGN+mdGJJXUl/Z055etN1xpMEaANQ8= +github.com/ipfs/go-libipfs v0.4.1-0.20230208030004-93dd0a02a18b h1:Vuib04xACEG9UXmdbg+q2kojDDlSBe1vSyPW77Q1Y1U= +github.com/ipfs/go-libipfs v0.4.1-0.20230208030004-93dd0a02a18b/go.mod h1:XKRXmSlJ32qlpxGN+mdGJJXUl/Z055etN1xpMEaANQ8= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= diff --git a/main.go b/main.go index 2a77dad..0f1f2f3 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + _ "embed" "errors" "fmt" "log" @@ -8,6 +9,7 @@ import ( "net/http" "os" "os/signal" + "runtime/debug" "strconv" "strings" "sync" @@ -42,6 +44,7 @@ func init() { var rootCmd = &cobra.Command{ Use: "bifrost-gateway", + Version: buildVersion(), CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, Short: "IPFS Gateway implementation for https://github.com/protocol/bifrost-infra", RunE: func(cmd *cobra.Command, args []string) error { @@ -69,7 +72,9 @@ var rootCmd = &cobra.Command{ defer wg.Done() log.Printf("Path gateway listening on http://127.0.0.1:%d", gatewayPort) + log.Printf(" Smoke test (JPG): http://127.0.0.1:%d/ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", gatewayPort) log.Printf("Subdomain gateway listening on dweb.link and http://localhost:%d", gatewayPort) + log.Printf(" Smoke test (Subdomain+DNSLink+UnixFS+HAMT): http://localhost:%d/ipns/en.wikipedia-on-ipfs.org/", gatewayPort) log.Printf("Legacy RPC at /api/v0 provided by %s", strings.Join(kuboRPC, " ")) err := gatewaySrv.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { @@ -170,3 +175,32 @@ func newAPIHandler(endpoints []string) http.Handler { http.Redirect(w, r, endpoint+r.URL.Path+"?"+r.URL.RawQuery, http.StatusFound) }) } + +func buildVersion() string { + var revision string + var day string + var dirty bool + + info, ok := debug.ReadBuildInfo() + if !ok { + return "(unknown)" + } + for _, kv := range info.Settings { + switch kv.Key { + case "vcs.revision": + revision = kv.Value[:7] + case "vcs.time": + t, _ := time.Parse(time.RFC3339, kv.Value) + day = t.UTC().Format("2006-01-02") + case "vcs.modified": + dirty = kv.Value == "true" + } + } + if dirty { + revision += "-dirty" + } + if revision != "" { + return day + "-" + revision + } + return "(unknown)" +}