Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

publish multi-arch docker image #19

Merged
merged 9 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,4 @@ jobs:
draft: true
tag: v${{ steps.get-version.outputs.tagged_version }}
body: ${{ steps.changelog-reader.outputs.changes }}
artifacts: release/artifacts/*
artifacts: release/artifacts/*
91 changes: 0 additions & 91 deletions deploy.sh

This file was deleted.

39 changes: 22 additions & 17 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 <git-ref>
#
# 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
Expand All @@ -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
Expand Down
5 changes: 0 additions & 5 deletions nix/docker.nix → nix/docker-connector.nix
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -35,9 +33,6 @@ let
"${config-directory}" = { };
};
} // extraConfig;
}
// lib.optionalAttrs (architecture != null) {
inherit architecture;
};
in
dockerTools.buildLayeredImage args
142 changes: 142 additions & 0 deletions scripts/publish-docker-image.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# This is run via a nix flake package:
#
# $ nix run .#publish-docker-image <git-ref>
#
# 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 = ''

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new kind of crime I had not previously considered.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, it's really convenient to be able to interpolate nix values into the script! Plus if you have the right editor setup the shell script syntax highlighting and indentation work correctly.

# 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/<branch name> or refs/tags/<tag name>."
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/<branch name> or
# refs/tags/<tag name>. 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"
'';
}
Loading