diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7923eabe..f5e939aa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -230,4 +230,4 @@ jobs: draft: true tag: v${{ steps.get-version.outputs.tagged_version }} body: ${{ steps.changelog-reader.outputs.changes }} - artifacts: release/artifacts/* \ No newline at end of file + artifacts: release/artifacts/* diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 98b7004d..00000000 --- a/deploy.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash -# -# To get the skopeo dependency automatically, run with: -# -# $ nix run .#publish-docker-image -# -set -euo pipefail - -IMAGE_PATH=ghcr.io/hasura/ndc-mongodb - -if [ -z "${1+x}" ]; then - echo "Expected argument of the form refs/heads/ or refs/tags/." - echo "(In a Github workflow the variable github.ref has this format)" - exit 1 -fi - -github_ref="$1" - -# Assumes that the given ref is a branch name. Sets a tag for a docker image of -# the form: -# -# dev-main-20230601T1933-bffd555 -# --- ---- ------------- ------- -# ↑ ↑ ↑ ↑ -# prefix "dev" branch | commit hash -# | -# commit date & time (UTC) -# -# Additionally sets a branch tag assuming this is the latest tag for the given -# branch. The branch tag has the form: dev-main -function set_dev_tags { - local branch="$1" - local branch_prefix="dev-$branch" - local version - version=$( - TZ=UTC0 git show \ - --quiet \ - --date='format-local:%Y%m%dT%H%M' \ - --format="$branch_prefix-%cd-%h" - ) - export docker_tags=("$version" "$branch_prefix") -} - -# The Github workflow passes a ref of the form refs/heads/ or -# refs/tags/. This function sets an array of docker image tags based -# on either the given branch or tag name. -# -# If a tag name does not start with a "v" it is assumed to not be a release tag -# so the function sets an empty array. -# -# If the input does look like a release tag, set the tag name as the sole docker -# tag. -# -# If the input is a branch, set docker tags via `set_dev_tags`. -function set_docker_tags { - local input="$1" - if [[ $input =~ ^refs/tags/(v.*)$ ]]; then - local tag="${BASH_REMATCH[1]}" - export docker_tags=("$tag") - elif [[ $input =~ ^refs/heads/(.*)$ ]]; then - local branch="${BASH_REMATCH[1]}" - set_dev_tags "$branch" - else - export docker_tags=() - fi -} - -function maybe_publish { - local input="$1" - set_docker_tags "$input" - if [[ ${#docker_tags[@]} == 0 ]]; then - echo "The given ref, $input, was not a release tag or a branch - will not publish a docker image" - exit - fi - - echo "Will publish docker image with tags: ${docker_tags[*]}" - - nix build .#docker --print-build-logs # writes a tar file to ./result - ls -lh result - local image_archive - image_archive=docker-archive://"$(readlink -f result)" - skopeo inspect "$image_archive" - - for tag in "${docker_tags[@]}"; do - echo - echo "Pushing docker://$IMAGE_PATH:$tag" - skopeo copy "$image_archive" docker://"$IMAGE_PATH:$tag" - done -} - -maybe_publish "$github_ref" diff --git a/flake.nix b/flake.nix index 04b064e7..e197d363 100644 --- a/flake.nix +++ b/flake.nix @@ -152,23 +152,34 @@ }; }); - packages = eachSystem (pkgs: { + packages = eachSystem (pkgs: rec { default = pkgs.mongodb-connector; # Note: these outputs are overridden to build statically-linked mongodb-connector-x86_64-linux = pkgs.pkgsCross.x86_64-linux.mongodb-connector.override { staticallyLinked = true; }; mongodb-connector-aarch64-linux = pkgs.pkgsCross.aarch64-linux.mongodb-connector.override { staticallyLinked = true; }; - docker = pkgs.callPackage ./nix/docker.nix { inherit (pkgs) mongodb-connector; }; - - docker-x86_64-linux = pkgs.callPackage ./nix/docker.nix { - mongodb-connector = pkgs.pkgsCross.x86_64-linux.mongodb-connector; # Note: dynamically-linked - architecture = "amd64"; - }; - - docker-aarch64-linux = pkgs.callPackage ./nix/docker.nix { - mongodb-connector = pkgs.pkgsCross.aarch64-linux.mongodb-connector; # Note: dynamically-linked - architecture = "arm64"; + # Builds a docker image for the MongoDB connector for amd64 Linux. To + # get a multi-arch image run `publish-docker-image`. + docker-image-x86_64-linux = pkgs.pkgsCross.x86_64-linux.callPackage ./nix/docker-connector.nix { }; + + # Builds a docker image for the MongoDB connector for arm64 Linux. To + # get a multi-arch image run `publish-docker-image`. + docker-image-aarch64-linux = pkgs.pkgsCross.aarch64-linux.callPackage ./nix/docker-connector.nix { }; + + # Publish multi-arch docker image for the MongoDB connector to Github + # registry. This must be run with a get-ref argument to calculate image + # tags: + # + # $ nix run .#publish-docker-image + # + # You must be logged in to the docker registry. See the CI configuration + # in `.github/workflows/deploy.yml` where this command is run. + publish-docker-image = pkgs.callPackage ./scripts/publish-docker-image.nix { + docker-images = [ + docker-image-aarch64-linux + docker-image-x86_64-linux + ]; }; # CLI plugin packages with cross-compilation options @@ -180,12 +191,6 @@ mongodb-cli-plugin-docker = pkgs.callPackage ./nix/docker-cli-plugin.nix { }; mongodb-cli-plugin-docker-x86_64-linux = pkgs.pkgsCross.x86_64-linux.callPackage ./nix/docker-cli-plugin.nix { }; mongodb-cli-plugin-docker-aarch64-linux = pkgs.pkgsCross.aarch64-linux.callPackage ./nix/docker-cli-plugin.nix { }; - - publish-docker-image = pkgs.writeShellApplication { - name = "publish-docker-image"; - runtimeInputs = with pkgs; [ coreutils skopeo ]; - text = builtins.readFile ./deploy.sh; - }; }); # Export our nixpkgs package set, which has been extended with the diff --git a/nix/docker.nix b/nix/docker-connector.nix similarity index 91% rename from nix/docker.nix rename to nix/docker-connector.nix index d4639d61..04dcc744 100644 --- a/nix/docker.nix +++ b/nix/docker-connector.nix @@ -1,8 +1,6 @@ # This is a function that returns a derivation for a docker image. { mongodb-connector , dockerTools -, lib -, architecture ? null , name ? "ghcr.io/hasura/ndc-mongodb" # See config options at https://github.com/moby/docker-image-spec/blob/main/spec.md @@ -35,9 +33,6 @@ let "${config-directory}" = { }; }; } // extraConfig; - } - // lib.optionalAttrs (architecture != null) { - inherit architecture; }; in dockerTools.buildLayeredImage args diff --git a/scripts/publish-docker-image.nix b/scripts/publish-docker-image.nix new file mode 100644 index 00000000..06f126c7 --- /dev/null +++ b/scripts/publish-docker-image.nix @@ -0,0 +1,142 @@ +# This is run via a nix flake package: +# +# $ nix run .#publish-docker-image +# +# The script is automatically checked with shellcheck, and run with bash using +# a sensible set of options. +{ + # These arguments are passed explicitly + docker-images +, format ? "oci" +, registry ? { host = "ghcr.io"; repo = "hasura/ndc-mongodb"; } +, target-protocol ? "docker://" + + # These arguments are automatically populated from nixpkgs via `callPackage` +, buildah +, coreutils +, git +, writeShellApplication +}: +writeShellApplication { + name = "publish-docker-image"; + runtimeInputs = [ coreutils git buildah ]; + text = '' + # Nix uses the same dollar-braces interpolation syntax as bash so we escape $ as ''$ + if [ -z "''${1+x}" ]; then + echo "Expected argument of the form refs/heads/ or refs/tags/." + echo "(In a Github workflow the variable github.ref has this format)" + exit 1 + fi + + github_ref="$1" + + # Assumes that the given ref is a branch name. Sets a tag for a docker image of + # the form: + # + # dev-main-20230601T1933-bffd555 + # --- ---- ------------- ------- + # ↑ ↑ ↑ ↑ + # prefix "dev" branch | commit hash + # | + # commit date & time (UTC) + # + # Additionally sets a branch tag assuming this is the latest tag for the given + # branch. The branch tag has the form: dev-main + function set_dev_tags { + local branch="$1" + local branch_prefix="dev-$branch" + local version + version=$( + TZ=UTC0 git show \ + --quiet \ + --date='format-local:%Y%m%dT%H%M' \ + --format="$branch_prefix-%cd-%h" + ) + export docker_tags=("$version" "$branch_prefix") + } + + # The Github workflow passes a ref of the form refs/heads/ or + # refs/tags/. This function sets an array of docker image tags based + # on either the given branch or tag name. + # + # If a tag name does not start with a "v" it is assumed to not be a release tag + # so the function sets an empty array. + # + # If the input does look like a release tag, set the tag name as the sole docker + # tag. + # + # If the input is a branch, set docker tags via `set_dev_tags`. + function set_docker_tags { + local input="$1" + if [[ $input =~ ^refs/tags/(v.*)$ ]]; then + local tag="''${BASH_REMATCH[1]}" + export docker_tags=("$tag") + elif [[ $input =~ ^refs/heads/(.*)$ ]]; then + local branch="''${BASH_REMATCH[1]}" + set_dev_tags "$branch" + else + export docker_tags=() + fi + } + + # We are given separate docker images for each target architecture. Create + # a list manifest that combines the manifests of each individual image to + # produce a multi-arch image. + # + # The buildah steps are adapted from https://github.com/mirkolenz/flocken + function publish { + local manifestName="ndc-mongodb/list" + local datetimeNow + datetimeNow="$(TZ=UTC0 date --iso-8601=seconds)" + + if buildah manifest exists "$manifestName"; then + buildah manifest rm "$manifestName"; + fi + + local manifest + manifest=$(buildah manifest create "$manifestName") + + for image in ${builtins.toString docker-images}; do + local manifestOutput + manifestOutput=$(buildah manifest add "$manifest" "docker-archive:$image") + + local digest + digest=$(echo "$manifestOutput" | cut "-d " -f2) + + buildah manifest annotate \ + --annotation org.opencontainers.image.created="$datetimeNow" \ + --annotation org.opencontainers.image.revision="$(git rev-parse HEAD)" \ + "$manifest" "$digest" + done + + echo + echo "Multi-arch manifests:" + buildah manifest inspect "$manifest" + + for tag in "''${docker_tags[@]}"; + do + local image_dest="${target-protocol}${registry.host}/${registry.repo}:$tag" + echo + echo "Pushing $image_dest" + buildah manifest push --all \ + --format ${format} \ + "$manifest" \ + "$image_dest" + done + } + + function maybe_publish { + local input="$1" + set_docker_tags "$input" + if [[ ''${#docker_tags[@]} == 0 ]]; then + echo "The given ref, $input, was not a release tag or a branch - will not publish a docker image" + exit + fi + + echo "Will publish docker image with tags: ''${docker_tags[*]}" + publish + } + + maybe_publish "$github_ref" + ''; +}