diff --git a/.github/workflows/operatorBuildAndDeploy.yml b/.github/workflows/operatorBuildAndDeploy.yml index 943427897..6a38b99a1 100644 --- a/.github/workflows/operatorBuildAndDeploy.yml +++ b/.github/workflows/operatorBuildAndDeploy.yml @@ -40,35 +40,42 @@ jobs: run: | export PATH=$GOROOT/bin:$GOPATH/bin:$PATH mage operator:testGenerateClient - - name: Build docker - standard and ubi images + - name: Unit Tests + run: | + export PATH=$GOROOT/bin:$GOPATH/bin:$PATH + mage operator:testGo + - name: Login to GitHub Package Registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u "${{ github.actor }}" --password-stdin + - name: Login to ECR env: + AWS_ACCESS_KEY_ID: ${{ secrets.ECR_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_SECRET }} + run: $(aws ecr get-login --no-include-email --region us-east-1) + - name: Setup Buildx + id: buildx + uses: crazy-max/ghaction-docker-buildx@v3 + - name: Cache Docker layers + uses: actions/cache@v2 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Build and Push Docker + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' + env: + ECR_REPO: ${{ secrets.ECR_REPO }} PR_REF: ${{ github.event.pull_request.head.ref }} - MO_BASE_OS: 'registry.access.redhat.com/ubi7/ubi-minimal:7.8' + GITHUB_REPO_OWNER: ${{ github.repository_owner }} run: | if [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then export MO_BRANCH=${PR_REF} else export MO_BRANCH="master" - fi; - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - mage operator:testAndBuild - - name: Deploy to ECR - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' - env: - MO_ECR_ID: ${{ secrets.ECR_ID }} - MO_ECR_SECRET: ${{ secrets.ECR_SECRET }} - MO_ECR_REPO: ${{ secrets.ECR_REPO }} - run: | - export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - export MO_TAGS=$(cat ./build/tagsToPush.txt) - mage operator:deployToECR - - name: Deploy to GH Packages - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'datastax/cass-operator' - env: - MO_GH_USR: 'datastax/cass-operator' - MO_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MO_GH_PKG_REPO: 'datastax/cass-operator/operator' - run: | + fi + export PATH=$GOROOT/bin:$GOPATH/bin:$PATH - export MO_TAGS=$(cat ./build/tagsToPush.txt) - mage operator:deployToGHPackages + export GITHUB_REPO_URL="https://github.com/${{ github.repository }}" + + ./scripts/build-push-images.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..fe58bac87 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Docker Release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + release_cass_operator: + name: Release Cass Operator + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Login Skopeo DockerHub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | skopeo login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin docker.io + - name: Login Skopeo ECR + env: + AWS_ACCESS_KEY_ID: ${{ secrets.ECR_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_SECRET }} + run: aws ecr get-login-password --region us-east-1 | skopeo login -u AWS --password-stdin ${ECR_REPO} + - name: Publish to Dockerhub + env: + ECR_REPO: ${{ secrets.ECR_REPO }} + run: | + ./scripts/push-release.sh diff --git a/buildsettings.yaml b/buildsettings.yaml index adce58794..c3fed3878 100644 --- a/buildsettings.yaml +++ b/buildsettings.yaml @@ -19,4 +19,4 @@ dev: images: - datastax/dse-server:6.8.3 - datastax/cass-config-builder:1.0.3 - - datastax/cassandra-mgmtapi-3_11_7:v0.1.12 + - datastax/cassandra-mgmtapi-3_11_7:v0.1.13 diff --git a/mage/docker/lib.go b/mage/docker/lib.go index f19721c7a..fa86d003a 100644 --- a/mage/docker/lib.go +++ b/mage/docker/lib.go @@ -20,6 +20,7 @@ type DockerCmd struct { Args []string ConfigDir string Input string + Env map[string]string } func (cmd DockerCmd) ToCliArgs() []string { @@ -50,55 +51,55 @@ func FromArgsInput(args []string, in string) DockerCmd { } func exec(cmd DockerCmd, - withInput func(string, string, ...string) error, - withoutInput func(string, ...string) error) error { + withInput func(map[string]string, string, string, ...string) error, + withoutInput func(map[string]string, string, ...string) error) error { var err error args := cmd.ToCliArgs() if cmd.Input != "" { - err = withInput("docker", cmd.Input, args...) + err = withInput(cmd.Env, "docker", cmd.Input, args...) } else { - err = withoutInput("docker", args...) + err = withoutInput(cmd.Env, "docker", args...) } return err } func output(cmd DockerCmd, - withInput func(string, string, ...string) (string, error), - withoutInput func(string, ...string) (string, error)) (string, error) { + withInput func(map[string]string, string, string, ...string) (string, error), + withoutInput func(map[string]string, string, ...string) (string, error)) (string, error) { var err error var out string args := cmd.ToCliArgs() if cmd.Input != "" { - out, err = withInput("docker", cmd.Input, args...) + out, err = withInput(cmd.Env, "docker", cmd.Input, args...) } else { - out, err = withoutInput("docker", args...) + out, err = withoutInput(cmd.Env, "docker", args...) } return out, err } func (cmd DockerCmd) Exec() error { - return exec(cmd, shutil.RunWithInput, shutil.Run) + return exec(cmd, shutil.RunWithEnvWithInput, shutil.RunWithEnv) } func (cmd DockerCmd) ExecV() error { - return exec(cmd, shutil.RunVWithInput, shutil.RunV) + return exec(cmd, shutil.RunVWithEnvWithInput, shutil.RunVWithEnv) } func (cmd DockerCmd) ExecVPanic() { - err := exec(cmd, shutil.RunVWithInput, shutil.RunV) + err := exec(cmd, shutil.RunVWithEnvWithInput, shutil.RunVWithEnv) mageutil.PanicOnError(err) } func (cmd DockerCmd) Output() (string, error) { - return output(cmd, shutil.OutputWithInput, shutil.Output) + return output(cmd, shutil.OutputWithEnvWithInput, shutil.OutputWithEnv) } func (cmd DockerCmd) OutputPanic() string { - out, err := output(cmd, shutil.OutputWithInput, shutil.Output) + out, err := output(cmd, shutil.OutputWithEnvWithInput, shutil.OutputWithEnv) mageutil.PanicOnError(err) return out } @@ -165,7 +166,12 @@ func Build(contextDir string, targetStage string, dockerFilePath string, namesAn args = append(args, "--build-arg") args = append(args, x) } - return FromArgs(args) + c := FromArgs(args) + if c.Env == nil { + c.Env = map[string]string{} + } + c.Env["DOCKER_BUILDKIT"] = "1" + return c } func Login(configDir string, user string, pw string, repo string) DockerCmd { diff --git a/mage/operator/lib.go b/mage/operator/lib.go index af4804ecd..12ca163c0 100644 --- a/mage/operator/lib.go +++ b/mage/operator/lib.go @@ -24,6 +24,8 @@ import ( const ( dockerBase = "./operator/docker/base/Dockerfile" + cassOperatorTarget = "cass-operator" + cassOperatorUbiTarget = "cass-operator-ubi" dockerUbi = "./operator/docker/ubi/Dockerfile" rootBuildDir = "./build" sdkBuildDir = "operator/build" @@ -376,10 +378,10 @@ func calcVersionAndTags(version FullVersion, ubiBase bool) (string, []string) { return versionedTag, tagsToPush } -func runDockerBuild(versionedTag string, dockerTags []string, extraBuildArgs []string, dockerfile string) { +func runDockerBuild(versionedTag string, dockerTags []string, extraBuildArgs []string, target string) { buildArgs := []string{fmt.Sprintf("VERSION_STAMP=%s", versionedTag)} buildArgs = append(buildArgs, extraBuildArgs...) - dockerutil.Build(".", "", dockerfile, dockerTags, buildArgs).ExecVPanic() + dockerutil.Build(".", target, dockerBase, dockerTags, buildArgs).ExecVPanic() } func runGoBuild(version string) { @@ -395,6 +397,18 @@ func runGoBuild(version string) { os.Chdir("..") } +func PrintVersion() { + settings := cfgutil.ReadBuildSettings() + fmt.Printf("%v\n", settings.Version) +} + +func PrintFullVersion() { + settings := cfgutil.ReadBuildSettings() + git := getGitData() + version := calcFullVersion(settings, git) + fmt.Printf("%v\n", version) +} + // Builds operator go code. // // By default, a dev version will be stamped into @@ -446,13 +460,13 @@ func BuildDocker() { //build regular docker image versionedTag, dockerTags := calcVersionAndTags(version, false) - runDockerBuild(versionedTag, dockerTags, nil, dockerBase) + runDockerBuild(versionedTag, dockerTags, nil, cassOperatorTarget) if baseOs := os.Getenv(EnvBaseOs); baseOs != "" { //build ubi docker image args := []string{fmt.Sprintf("BASE_OS=%s", baseOs)} ubiVersionedTag, ubiDockerTags := calcVersionAndTags(version, true) - runDockerBuild(ubiVersionedTag, ubiDockerTags, args, dockerUbi) + runDockerBuild(ubiVersionedTag, ubiDockerTags, args, cassOperatorUbiTarget) dockerTags = append(dockerTags, ubiDockerTags...) } diff --git a/mage/sh/lib.go b/mage/sh/lib.go index 4ee2082c2..4ea9c9dbf 100644 --- a/mage/sh/lib.go +++ b/mage/sh/lib.go @@ -8,6 +8,7 @@ import ( "io" "os" "os/exec" + "fmt" "strings" "github.com/datastax/cass-operator/mage/util" @@ -21,11 +22,16 @@ import ( // to print output, if not, output will be hidden. // Stderr will work as normal func Run(cmd string, args ...string) error { + return RunWithEnv(nil, cmd, args...) +} + +func RunWithEnv(env map[string]string, cmd string, args ...string) error { var output io.Writer if mg.Verbose() { output = os.Stdout } - _, err := sh.Exec(nil, output, os.Stderr, cmd, args...) + + _, err := sh.Exec(env, output, os.Stderr, cmd, args...) return err } @@ -42,14 +48,22 @@ func RunPanic(cmd string, args ...string) { // Run command and print any output to stdout/stderr func RunV(cmd string, args ...string) error { - _, err := sh.Exec(nil, os.Stdout, os.Stderr, cmd, args...) + return RunVWithEnv(nil, cmd, args...) +} + +func RunVWithEnv(env map[string]string, cmd string, args ...string) error { + _, err := sh.Exec(env, os.Stdout, os.Stderr, cmd, args...) return err } // Run command and print any output to stdout/stderr // Will automatically panic on error func RunVPanic(cmd string, args ...string) { - err := RunV(cmd, args...) + RunVPanicWithEnv(nil, cmd, args...) +} + +func RunVPanicWithEnv(env map[string]string, cmd string, args ...string) { + err := RunVWithEnv(env, cmd, args...) mageutil.PanicOnError(err) } @@ -70,8 +84,12 @@ func RunVCapture(cmd string, args ...string) (string, string, error) { // Returns output from stdout. // stderr gets used as normal here func Output(cmd string, args ...string) (string, error) { + return OutputWithEnv(nil, cmd, args...) +} + +func OutputWithEnv(env map[string]string, cmd string, args ...string) (string, error) { buf := &bytes.Buffer{} - _, err := sh.Exec(nil, buf, os.Stderr, cmd, args...) + _, err := sh.Exec(env, buf, os.Stderr, cmd, args...) return strings.TrimSuffix(buf.String(), "\n"), err } @@ -83,8 +101,13 @@ func OutputPanic(cmd string, args ...string) string { return out } -func cmdWithStdIn(cmd string, in string, args ...string) *exec.Cmd { +func cmdWithStdIn(env map[string]string, cmd string, in string, args ...string) *exec.Cmd { + envArray := []string{} + for k, v := range env { + envArray = append(envArray, fmt.Sprintf("%s=%s", k, v)) + } c := exec.Command(cmd, args...) + c.Env = envArray buffer := bytes.Buffer{} buffer.Write([]byte(in)) c.Stdin = &buffer @@ -92,7 +115,11 @@ func cmdWithStdIn(cmd string, in string, args ...string) *exec.Cmd { } func RunWithInput(cmd string, in string, args ...string) error { - c := cmdWithStdIn(cmd, in, args...) + return RunWithEnvWithInput(nil, cmd, in, args...) +} + +func RunWithEnvWithInput(env map[string]string, cmd string, in string, args ...string) error { + c := cmdWithStdIn(nil, cmd, in, args...) var output io.Writer if mg.Verbose() { output = os.Stdout @@ -103,17 +130,30 @@ func RunWithInput(cmd string, in string, args ...string) error { } func RunVWithInput(cmd string, in string, args ...string) error { - c := cmdWithStdIn(cmd, in, args...) + return RunVWithEnvWithInput(nil, cmd, in, args...) +} + +func RunVWithEnvWithInput(env map[string]string, cmd string, in string, args...string) error { + c := cmdWithStdIn(env, cmd, in, args...) c.Stdout = os.Stdout c.Stderr = os.Stderr return c.Run() } func OutputWithInput(cmd string, in string, args ...string) (string, error) { + return OutputWithEnvWithInput(nil, cmd, in, args...) +} + +func OutputWithEnvWithInput(env map[string]string, cmd string, in string, args ...string) (string, error) { + envArray := []string{} + for k, v := range env { + envArray = append(envArray, fmt.Sprintf("%s=%s", k, v)) + } c := exec.Command(cmd, args...) + c.Env = envArray buffer := bytes.Buffer{} buffer.Write([]byte(in)) c.Stdin = &buffer out, err := c.Output() return string(out), err -} +} \ No newline at end of file diff --git a/operator/docker/base/Dockerfile b/operator/docker/base/Dockerfile index 3d2a8fdb3..819998ae2 100644 --- a/operator/docker/base/Dockerfile +++ b/operator/docker/base/Dockerfile @@ -2,17 +2,27 @@ # NOTE: When building this image, there is an assumption that you are in the top level directory of the repository. # $ docker build . -f operator/Dockerfile -t operator ########## + +# This arg is only used when building UBI images; however, if it does not have +# a value, the build will fail even if not using a UBI target. To get around this +# we give the argument a dummy value so that an arg need not be provided, but it +# will still fail if building a UBI image without providing an explicit value. +# The value is prefixed with 'datastax/' to ensure it points to an image that we +# can ensure does not exist. +ARG BASE_OS=datastax/doesnotexist + # "builder" compiles and tests the code # This stage name is important to the Cloud Platform CI/CD infrastructure, and should be preserved -FROM golang:1.13-stretch as builder +FROM --platform=${BUILDPLATFORM} golang:1.13-stretch as builder # Disable cgo - this makes static binaries that will work on an Alpine image ENV CGO_ENABLED=0 ENV GOPROXY=https://proxy.golang.org,https://gocenter.io,direct # Target os and arch -ENV GOOS linux -ENV GOARCH amd64 +ARG TARGETARCH +ENV GOOS=linux +ENV GOARCH=${TARGETARCH} ENV GOPATH=/go @@ -45,8 +55,7 @@ RUN MO_VERSION=${VERSION_STAMP} mage operator:buildGo # Second stage # Produce a smaller image than the one used to build the code -FROM alpine:3.9 - +FROM alpine:3.9 as cass-operator ENV GOPATH=/go RUN mkdir -p /var/lib/cass-operator/ @@ -54,6 +63,57 @@ RUN touch /var/lib/cass-operator/base_os WORKDIR /go # All we need from the builder image is operator executable -COPY --from=builder /cass-operator/build/bin/cass-operator-linux-amd64 bin/operator +ARG TARGETARCH +COPY --from=builder /cass-operator/build/bin/cass-operator-linux-${TARGETARCH} bin/operator CMD [ "/go/bin/operator" ] + +# UBI Image + + + +############################################################# + +FROM ${BASE_OS} AS builder-ubi + +# Update the builder layer and create user +RUN microdnf update && rm -rf /var/cache/yum && \ + microdnf install shadow-utils && microdnf clean all && \ + useradd -r -s /bin/false -U -G root cassandra + +############################################################# +FROM ${BASE_OS} AS cass-operator-ubi + +ARG BASE_OS +ARG VERSION_STAMP=DEV + +LABEL maintainer="DataStax, Inc " +LABEL name="cass-operator" +LABEL vendor="DataStax, Inc" +LABEL release="${VERSION_STAMP}" +LABEL summary="DataStax Kubernetes Operator for Apache Cassandra " +LABEL description="The DataStax Kubernetes Operator for Apache Cassandra®. This operator handles the provisioning and day to day management of Apache Cassandra based clusters. Features include configuration deployment, node remediation, and automatic upgrades." + +# Update the builder layer and create user +RUN microdnf update && rm -rf /var/cache/yum && \ + microdnf install procps-ng && microdnf clean all + +# Copy user accounts information +COPY --from=builder-ubi /etc/passwd /etc/passwd +COPY --from=builder-ubi /etc/shadow /etc/shadow +COPY --from=builder-ubi /etc/group /etc/group +COPY --from=builder-ubi /etc/gshadow /etc/gshadow + +# Copy operator binary +COPY --from=cass-operator /go/bin/operator /operator +COPY ./operator/docker/ubi/LICENSE /licenses/ + +RUN mkdir -p /var/lib/cass-operator/ +RUN echo ${BASE_OS} > /var/lib/cass-operator/base_os + +RUN chown cassandra:root /operator && \ + chmod 0555 /operator + +USER cassandra:root + +ENTRYPOINT ["/operator"] diff --git a/operator/docker/ubi/Dockerfile b/operator/docker/ubi/Dockerfile deleted file mode 100644 index 7bed555ca..000000000 --- a/operator/docker/ubi/Dockerfile +++ /dev/null @@ -1,49 +0,0 @@ -ARG BASE_OS -FROM datastax/cass-operator:latest AS base - -############################################################# - -FROM ${BASE_OS} AS builder - -# Update the builder layer and create user -RUN microdnf update && rm -rf /var/cache/yum && \ - microdnf install shadow-utils && microdnf clean all && \ - useradd -r -s /bin/false -U -G root cassandra - -############################################################# -FROM ${BASE_OS} - -ARG BASE_OS -ARG VERSION_STAMP=DEV - -LABEL maintainer="DataStax, Inc " -LABEL name="cass-operator" -LABEL vendor="DataStax, Inc" -LABEL release="${VERSION_STAMP}" -LABEL summary="DataStax Kubernetes Operator for Apache Cassandra " -LABEL description="The DataStax Kubernetes Operator for Apache Cassandra®. This operator handles the provisioning and day to day management of Apache Cassandra based clusters. Features include configuration deployment, node remediation, and automatic upgrades." - -# Update the builder layer and create user -RUN microdnf update && rm -rf /var/cache/yum && \ - microdnf install procps-ng && microdnf clean all - -# Copy user accounts information -COPY --from=builder /etc/passwd /etc/passwd -COPY --from=builder /etc/shadow /etc/shadow -COPY --from=builder /etc/group /etc/group -COPY --from=builder /etc/gshadow /etc/gshadow - -# Copy operator binary -COPY --from=base /go/bin/operator /operator -COPY ./operator/docker/ubi/LICENSE /licenses/ - -RUN mkdir -p /var/lib/cass-operator/ -RUN echo ${BASE_OS} > /var/lib/cass-operator/base_os - -RUN chown cassandra:root /operator && \ - chmod 0555 /operator - -USER cassandra:root - - -ENTRYPOINT ["/operator"] \ No newline at end of file diff --git a/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types_test.go b/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types_test.go index a4ed05bae..b2e23c975 100644 --- a/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types_test.go +++ b/operator/pkg/apis/cassandra/v1beta1/cassandradatacenter_types_test.go @@ -40,7 +40,7 @@ func Test_makeImage(t *testing.T) { serverType: "cassandra", serverVersion: "3.11.7", }, - want: "datastax/cassandra-mgmtapi-3_11_7:v0.1.12", + want: "datastax/cassandra-mgmtapi-3_11_7:v0.1.13", errString: "", }, { diff --git a/operator/pkg/images/images.go b/operator/pkg/images/images.go index ae9eae5f5..2bd0af17a 100644 --- a/operator/pkg/images/images.go +++ b/operator/pkg/images/images.go @@ -68,7 +68,7 @@ const ( var imageLookupMap map[Image]string = map[Image]string { Cassandra_3_11_6: "datastax/cassandra-mgmtapi-3_11_6:v0.1.5", - Cassandra_3_11_7: "datastax/cassandra-mgmtapi-3_11_7:v0.1.12", + Cassandra_3_11_7: "datastax/cassandra-mgmtapi-3_11_7:v0.1.13", Cassandra_4_0_0: "datastax/cassandra-mgmtapi-4_0_0:v0.1.12", UBICassandra_3_11_6: "datastax/cassandra:3.11.6-ubi7", diff --git a/scripts/build-push-images.sh b/scripts/build-push-images.sh new file mode 100755 index 000000000..86c377a0d --- /dev/null +++ b/scripts/build-push-images.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash + +# Expected ENV variable inputs: +# +# ECR_REPO - the ECR registry (this was misnamed in our GitHub secrets) +# GITHUB_REPO_URL - the GitHub repository url (i.e. https://github.com/datastax/cass-operator) +# GITHUB_REPO_OWNER - the owner of the repository (i.e. datastax), this is useful for forks +# GITHUB_SHA - the git SHA of the current checkout +# MO_BRANCH - set appropriately for the current branch +# +# Also, PATH should be set appropriately for Go +# + +set -e + +VERSION="$(mage operator:printVersion)" +FULL_VERSION="$(mage operator:printFullVersion)" +VERSION_STAMP="${GITHUB_REPO_URL}:${FULL_VERSION}" + +ECR_REPOSITORY="${ECR_REPO}/datastax/cass-operator" +GH_REPOSITORY="docker.pkg.github.com/${GITHUB_REPO_OWNER}/cass-operator/operator" + +ECR_TAGS=() +ECR_UBI_TAGS=() +GH_TAGS=() +GH_UBI_TAGS=() +GH_ARM64_TAGS=() + +for t in "${FULL_VERSION}" "${GITHUB_SHA}" "latest"; do + ECR_TAGS+=(--tag "${ECR_REPOSITORY}:${t}") + ECR_UBI_TAGS+=(--tag "${ECR_REPOSITORY}:${t}-ubi") + + GH_TAGS+=(--tag "${GH_REPOSITORY}:${t}") + GH_UBI_TAGS+=(--tag "${GH_REPOSITORY}:${t}-ubi") + GH_ARM64_TAGS+=(--tag "${GH_REPOSITORY}:${t}-arm64") +done + +LABELS=( + --label "org.label-schema.schema-version=1.0" + --label "org.label-schema.vcs-ref=$GITHUB_SHA" + --label "org.label-schema.vcs-url=$GITHUB_REPO_URL" + --label "org.label-schema.version=$VERSION" +) + +COMMON_ARGS=( + "${LABELS[@]}" + --file operator/docker/base/Dockerfile + --cache-from "type=local,src=/tmp/.buildx-cache" + --cache-to "type=local,dest=/tmp/.buildx-cache" +) + +STANDARD_ARGS=( + "${COMMON_ARGS[@]}" + --label "release=${VERSION_STAMP}" + --build-arg "VERSION_STAMP=${VERSION_STAMP}" + --target cass-operator +) + +UBI_ARGS=( + "${COMMON_ARGS[@]}" + --label "release=${VERSION_STAMP}-ubi" + --build-arg "VERSION_STAMP=${VERSION_STAMP}-ubi" + --build-arg "BASE_OS=registry.access.redhat.com/ubi7/ubi-minimal:7.8" + --target cass-operator-ubi +) + +# Build and push standard images + +docker buildx build \ + --push \ + "${STANDARD_ARGS[@]}" \ + "${ECR_TAGS[@]}" \ + --platform linux/amd64,linux/arm64 \ + . + + +# Build and push UBI images + +docker buildx build \ + --push \ + "${UBI_ARGS[@]}" \ + "${ECR_UBI_TAGS[@]}" \ + --platform linux/amd64 \ + . + + +# Workaround for GH packages + +docker buildx build \ + --load \ + "${STANDARD_ARGS[@]}" \ + "${GH_TAGS[@]}" \ + --platform linux/amd64 \ + . + +docker buildx build \ + --load \ + "${STANDARD_ARGS[@]}" \ + "${GH_ARM64_TAGS[@]}" \ + --platform linux/arm64 \ + . + +docker buildx build \ + --load \ + "${UBI_ARGS[@]}" \ + "${GH_UBI_TAGS[@]}" \ + --platform linux/amd64 \ + . + +TAGS_TO_PUSH=("${GH_ARM64_TAGS[@]}" "${GH_TAGS[@]}" "${GH_UBI_TAGS[@]}") +echo "Pushing tags: " "${TAGS_TO_PUSH[@]}" + +# Note: Every even index of TAGS_TO_PUSH will be the string '--tag' +# so we skip over those while looping. + +for ((x=1; x<${#TAGS_TO_PUSH[@]}; x=x+2)); do + docker push "${TAGS_TO_PUSH[x]}" +done diff --git a/scripts/push-release.sh b/scripts/push-release.sh new file mode 100755 index 000000000..bf7dd4c91 --- /dev/null +++ b/scripts/push-release.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -e + +# Expected ENV variable inputs: +# +# ECR_REPO - the ECR registry (this was misnamed in our GitHub secrets) +# GITHUB_REF - the git ref of the tag +# GITHUB_SHA - the git SHA of the current checkout +# + +GIT_TAG="${GITHUB_REF##*/}" +VERSION=${GIT_TAG#v} # strip the initial 'v' from the tag to get the version + +DOCKERHUB_REPOSITORY="datastax/cass-operator" +ECR_REPOSITORY="${ECR_REPO}/datastax/cass-operator" + +# Get the version label of the ECR image so that we can double +# check that it makes sense for this tag name. +VERSION_PATH='.Labels["org.label-schema.version"]' +LABEL_VERSION="$(skopeo inspect "docker://${ECR_REPOSITORY}:${GITHUB_SHA}" | jq "$VERSION_PATH" --raw-output)" +LABEL_VERSION_UBI="$(skopeo inspect "docker://${ECR_REPOSITORY}:${GITHUB_SHA}-ubi" | jq "$VERSION_PATH" --raw-output)" + +# Sanity check. This should never happen. +if ! [ "$LABEL_VERSION" = "$LABEL_VERSION_UBI" ]; then + echo "Standard and UBI images were not labeled with the same version" + exit 1 +fi + +# Ensure the image has a version appropriate for this tag +# to prevent confusion. +# +# There are two checks in the following if-statement. The +# first handles the case of a standard release where +# LABEL_VERSION will contain a "-release" suffix that we +# generally do not include in the tag. The second handles +# the case of non-standard releases (a release candidate, +# alpha, etc.) where we _would_ expect to see the relevant +# suffix in the tag name. +if ! [ "v${LABEL_VERSION}" = "${GIT_TAG}-release" ] && ! [ "v${LABEL_VERSION}" = "${GIT_TAG}" ]; then + echo "Git tag $GIT_TAG does not align with version number ${LABEL_VERSION}" + exit 1 +fi + +# Tag images for DockerHub and push them +for t in "$VERSION" "latest"; do + skopeo copy --all "docker://${ECR_REPOSITORY}:${GITHUB_SHA}" "docker://${DOCKERHUB_REPOSITORY}:${t}" + skopeo copy --all "docker://${ECR_REPOSITORY}:${GITHUB_SHA}-ubi" "docker://${DOCKERHUB_REPOSITORY}:${t}-ubi" +done