You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ho...@apache.org on 2021/04/02 19:57:22 UTC

[solr-operator] branch main updated: Scripts to generate, test and upload release artifacts. (#248)

This is an automated email from the ASF dual-hosted git repository.

houston pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 2908704  Scripts to generate, test and upload release artifacts. (#248)
2908704 is described below

commit 29087047f6c97f0d4fc86d3c177f766c99bd9279
Author: Houston Putman <ho...@apache.org>
AuthorDate: Fri Apr 2 15:57:16 2021 -0400

    Scripts to generate, test and upload release artifacts. (#248)
    
    This will generate release artifacts for:
    - The helm chart
    - The CRDs
    - The source-code
    
    A make target exists that will clean the repo, prepare it, build the docker image and then build all other release artifacts.
    
    There is also code for smoke testing and uploading the release files to release locations.
---
 .github/workflows/tests.yaml                       |   3 +-
 .gitignore                                         |  10 +-
 Makefile                                           |  65 ++++---
 build/Dockerfile                                   |   2 +-
 hack/release/artifacts/build_helm.sh               |  95 ++++++++++
 hack/release/artifacts/bundle_source.sh            |  83 +++++++++
 hack/release/artifacts/create_artifacts.sh         | 101 +++++++++++
 hack/release/artifacts/create_crds.sh              |  98 +++++++++++
 hack/release/build_helm.sh                         |  20 ---
 hack/release/setup_release.sh                      |  47 -----
 hack/release/smoke_test/smoke_test.sh              |  87 ++++++++++
 hack/release/smoke_test/test_cluster.sh            | 191 +++++++++++++++++++++
 hack/release/smoke_test/test_source.sh             | 109 ++++++++++++
 hack/release/smoke_test/verify_all.sh              | 116 +++++++++++++
 hack/release/smoke_test/verify_docker.sh           |  92 ++++++++++
 hack/release/upload/upload_crds.sh                 | 114 ++++++++++++
 hack/release/upload/upload_helm.sh                 | 120 +++++++++++++
 hack/release/version/change_suffix.sh              |  61 +++++--
 hack/release/version/propagate_version.sh          |  47 ++++-
 .../version/remove_version_specific_info.sh        |  56 ++++++
 hack/release/version/update_version.sh             |  50 +++++-
 helm/solr-operator/Chart.yaml                      |   8 +-
 22 files changed, 1455 insertions(+), 120 deletions(-)

diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index df5ea96..b0a6713 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -51,4 +51,5 @@ jobs:
 
       # Test
       - run: make lint
-      - run: make test
\ No newline at end of file
+      - run: make test
+      - run: make check-git
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e09df23..f816073 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,5 +28,11 @@ release-artifacts
 config/manager/kustomization.yaml
 
 # Helm dependency files
-helm/solr-operator/Chart.lock
-helm/solr-operator/charts
+helm/*/Chart.lock
+helm/*/charts
+
+# Directory to test generated files
+generated-check
+
+# Python for the release wizard
+venv
diff --git a/Makefile b/Makefile
index cef1bc4..f439f15 100644
--- a/Makefile
+++ b/Makefile
@@ -42,6 +42,9 @@ tag:
 clean:
 	rm -rf ./bin
 	rm -rf ./release-artifacts
+	rm -rf ./helm/solr-operator/charts ./helm/solr-operator/Chart.lock
+	rm -rf ./cover.out
+	rm -rf ./generated-check
 
 mod-tidy:
 	export GO111MODULE=on; go mod tidy
@@ -51,10 +54,8 @@ mod-tidy:
 
 install-dependencies: .install-dependencies mod-tidy
 
-release: clean prepare helm-dependency-build
-	VERSION=${VERSION} bash hack/release/update_versions.sh
-	VERSION=${VERSION} bash hack/release/build_helm.sh
-	VERSION=${VERSION} bash hack/release/setup_release.sh
+build-release-artifacts: clean prepare docker-build
+	./hack/release/artifacts/create_artifacts.sh -d $(or $(ARTIFACTS_DIR),release-artifacts)
 
 ###
 # Building
@@ -64,11 +65,11 @@ release: clean prepare helm-dependency-build
 prepare: fmt generate manifests fetch-licenses-list mod-tidy
 
 # Build solr-operator binary
-build: generate vet
+build: generate
 	BIN=solr-operator GIT_SHA=${GIT_SHA} ARCH=${ARCH} GOOS=${GOOS} ./build/build.sh
 
 # Run against the configured Kubernetes cluster in ~/.kube/config
-run: generate fmt vet manifests
+run: generate fmt manifests
 	go run ./main.go
 
 # Install CRDs into a cluster
@@ -93,15 +94,11 @@ manifests:
 fmt:
 	go fmt ./...
 
-# Run go vet against code
-vet:
-	go vet ./...
-
-# Run go vet against code
+# Fetch the list of license types
 fetch-licenses-list:
 	go-licenses csv . | grep -v -E "solr-operator" | sort > dependency_licenses.csv
 
-# Run go vet against code
+# Fetch all licenses
 fetch-licenses-full:
 	go-licenses save . --save_path licenses --force
 
@@ -111,7 +108,7 @@ fetch-licenses-full:
 
 check: lint test
 
-lint: check-format check-licenses check-manifests check-generated check-helm check-mod
+lint: check-mod vet check-format check-licenses check-manifests check-generated check-helm
 
 check-format:
 	./hack/check_format.sh
@@ -122,23 +119,51 @@ check-licenses:
 	@echo "Check list of dependency licenses"
 	go-licenses csv . 2>/dev/null | grep -v -E "solr-operator" | sort | diff dependency_licenses.csv -
 
-check-manifests: manifests
+check-manifests:
+	rm -rf generated-check
+	mkdir -p generated-check
+	mv config generated-check/existing-config; cp -r generated-check/existing-config config
+	mv helm generated-check/existing-helm; cp -r generated-check/existing-helm helm
+	make manifests
+	mv config generated-check/config; mv generated-check/existing-config config
+	mv helm generated-check/helm; mv generated-check/existing-helm helm
 	@echo "Check to make sure the manifests are up to date"
-	git diff --exit-code -- config helm/solr-operator/crds
-
-check-generated: generate
+	diff --recursive config generated-check/config
+	diff --recursive helm generated-check/helm
+
+check-generated:
+	rm -rf generated-check
+	mkdir -p generated-check
+	cp -r api generated-check/existing-api
+	make generate
+	mv api generated-check/api; mv generated-check/existing-api api
 	@echo "Check to make sure the generated code is up to date"
-	git diff --exit-code -- api/*/zz_generated.deepcopy.go
+	diff --recursive api generated-check/api
 
 check-helm:
 	helm lint helm/solr-operator
 
 check-mod:
+	rm -rf generated-check
+	mkdir -p generated-check/existing-go-mod generated-check/go-mod
+	cp go.* generated-check/existing-go-mod/.
+	make mod-tidy
+	cp go.* generated-check/go-mod/.
+	mv generated-check/existing-go-mod/go.* .
 	@echo "Check to make sure the go mod info is up to date"
-	git diff --exit-code -- go.mod go.sum
+	diff go.mod generated-check/go-mod/go.mod
+	diff go.sum generated-check/go-mod/go.sum
+
+# Run go vet against code
+vet:
+	go vet ./...
+
+check-git:
+	@echo "Check to make sure the repo does not have uncommitted code"
+	git diff --exit-code
 
 # Run tests
-test: generate vet manifests
+test:
 	go test ./... -coverprofile cover.out
 
 
diff --git a/build/Dockerfile b/build/Dockerfile
index 84553d2..6fc4b5f 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -35,7 +35,7 @@ COPY controllers/ controllers/
 ARG GIT_SHA
 
 # Build
-RUN CGO_ENABLED=0 make fetch-licenses-full build
+RUN CGO_ENABLED=0 GIT_SHA="${GIT_SHA}" make fetch-licenses-full build
 
 # =============================================================================
 # Copy the controller-manager into a thin image
diff --git a/hack/release/artifacts/build_helm.sh b/hack/release/artifacts/build_helm.sh
new file mode 100755
index 0000000..bba321d
--- /dev/null
+++ b/hack/release/artifacts/build_helm.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/artifacts/build_helm.sh [-h] [-v VERSION] [-a APACHE_ID] -d ARTIFACTS_DIR
+
+Build the helm chart & repo.
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator (Optional, will default to project version)
+    -d  Base directory of the staged artifacts.
+    -a  Apache ID, to find the GPG key when signing the helm chart (Optional)
+EOF
+}
+
+OPTIND=1
+while getopts hv:d:a: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        a)  APACHE_ID=$OPTARG
+            ;;
+        d)  ARTIFACTS_DIR=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  VERSION=$(make version)
+fi
+if [[ -z "${ARTIFACTS_DIR:-}" ]]; then
+  echo "Specify an base artifact directory -d, or through the ARTIFACTS_DIR env var" >&2 && exit 1
+fi
+
+HELM_RELEASE_DIR="${ARTIFACTS_DIR}/helm-charts"
+echo "Packaging helm chart for version ${VERSION} at: ${HELM_RELEASE_DIR}"
+
+# Setup directory
+mkdir -p "${HELM_RELEASE_DIR}"
+rm -rf "${HELM_RELEASE_DIR}"/*
+
+# Package and Index the helm charts, create release artifacts to upload in GithubRelease
+
+helm dependency build helm/solr-operator
+
+SIGNING_INFO=()
+CREATED_SECURE_RING=false
+if [[ -n "${APACHE_ID:-}" ]]; then
+  # First generate the temporary secret key ring
+  if [[ ! -f "/.gnupg/secring.gpg" ]]; then
+    gpg --export-secret-keys >~/.gnupg/secring.gpg
+    CREATED_SECURE_RING=true
+  fi
+
+  SIGNING_INFO=(--sign --key "${APACHE_ID}@apache.org" --keyring ~/.gnupg/secring.gpg)
+fi
+
+helm package -u helm/* --app-version "${VERSION}" --version "${VERSION#v}" -d "${HELM_RELEASE_DIR}" "${SIGNING_INFO[@]}"
+
+if [[ ${CREATED_SECURE_RING} ]]; then
+  # Remove the temporary secret key ring
+  rm ~/.gnupg/secring.gpg
+fi
+
+helm repo index "${HELM_RELEASE_DIR}"
diff --git a/hack/release/artifacts/bundle_source.sh b/hack/release/artifacts/bundle_source.sh
new file mode 100755
index 0000000..3f797e1
--- /dev/null
+++ b/hack/release/artifacts/bundle_source.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/artifacts/bundle_source.sh [-h] [-v VERSION] -d ARTIFACTS_DIR
+
+Setup the source release artifact.
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator (Optional, will default to project version)
+    -d  Base directory of the staged artifacts.
+EOF
+}
+
+OPTIND=1
+while getopts hv:d: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        d)  ARTIFACTS_DIR=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  VERSION=$(make version)
+fi
+if [[ -z "${ARTIFACTS_DIR:-}" ]]; then
+  echo "Specify an base artifact directory -d, or through the ARTIFACTS_DIR env var" >&2 && exit 1
+fi
+
+echo "Bundling source release for the Solr Operator ${VERSION} at '${ARTIFACTS_DIR}/solr-operator-${VERSION}.tgz'"
+
+TAR="${TAR:=tar}"
+if ! ("${TAR}" --version | grep "GNU tar"); then
+  TAR="gtar"
+  if ! ("${TAR}" --version | grep "GNU tar"); then
+    printf "\nMust have a GNU tar installed. If on OSX, then please download gnu-tar. It is available via brew.\nThe GNU version of Tar must be available at either 'tar' or 'gtar'.\n"
+    exit 1
+  fi
+fi
+
+# Setup directory
+mkdir -p "${ARTIFACTS_DIR}"
+rm -rf "${ARTIFACTS_DIR}"/*.tgz*
+
+# Package all of the code into a source release
+
+COPYFILE_DISABLE=true "${TAR}" --exclude-vcs --exclude-vcs-ignores \
+  --exclude .asf.yaml \
+  --exclude .github \
+  --transform "s,^,solr-operator-${VERSION}/," \
+  -czf "${ARTIFACTS_DIR}/solr-operator-${VERSION}.tgz" .
diff --git a/hack/release/artifacts/create_artifacts.sh b/hack/release/artifacts/create_artifacts.sh
new file mode 100755
index 0000000..aef96fd
--- /dev/null
+++ b/hack/release/artifacts/create_artifacts.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/artifacts/create_artifacts.sh [-h] [-v VERSION] [-g GPG_KEY] [-a APACHE_ID] -d ARTIFACTS_DIR
+
+Setup the release of all artifacts, then create signatures.
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator (Optional, will default to project version)
+    -d  Base directory of the staged artifacts.
+    -g  GPG Key to use when signing artifacts (Optional)
+    -a  Apache ID, to use when signing the helm chart (Optional)
+EOF
+}
+
+OPTIND=1
+while getopts hv:g:a:d: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        g)  GPG_KEY=$OPTARG
+            ;;
+        a)  APACHE_ID=$OPTARG
+            ;;
+        d)  ARTIFACTS_DIR=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  VERSION=$(make version)
+fi
+if [[ -z "${ARTIFACTS_DIR:-}" ]]; then
+  echo "Specify an base artifact directory -d, or through the ARTIFACTS_DIR env var" >&2 && exit 1
+fi
+
+GPG_USER=()
+if [[ -n "${GPG_KEY:-}" ]]; then
+  GPG_USER=(-u "${GPG_KEY}")
+fi
+APACHE_ID_PASS_THROUGH=()
+if [[ -n "${APACHE_ID:-}" ]]; then
+  APACHE_ID_PASS_THROUGH=(-a "${APACHE_ID}")
+fi
+
+echo "Setting up Solr Operator ${VERSION} release artifacts at '${ARTIFACTS_DIR}'"
+
+./hack/release/artifacts/bundle_source.sh -d "${ARTIFACTS_DIR}" -v "${VERSION}"
+./hack/release/artifacts/create_crds.sh -d "${ARTIFACTS_DIR}" -v "${VERSION}"
+./hack/release/artifacts/build_helm.sh -d "${ARTIFACTS_DIR}" -v "${VERSION}" "${APACHE_ID_PASS_THROUGH[@]}"
+
+# Generate signature and checksum for every file
+(
+  cd "${ARTIFACTS_DIR}"
+
+  for artifact_directory in $(find * -type d); do
+    (
+      cd "${artifact_directory}"
+
+      for artifact in $(find * -type f ! \( -name '*.asc' -o -name '*.sha512' -o -name '*.prov' \) ); do
+        if [ ! -f "${artifact}.asc" ]; then
+          gpg "${GPG_USER[@]}" -ab "${artifact}"
+        fi
+        if [ ! -f "${artifact}.sha512" ]; then
+          sha512sum -b "${artifact}" > "${artifact}.sha512"
+        fi
+      done
+    )
+  done
+)
diff --git a/hack/release/artifacts/create_crds.sh b/hack/release/artifacts/create_crds.sh
new file mode 100755
index 0000000..527275e
--- /dev/null
+++ b/hack/release/artifacts/create_crds.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/artifacts/create_crds.sh [-h] [-v VERSION] -d ARTIFACTS_DIR
+
+Setup the release of the CRDs.
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator (Optional, will default to project version)
+    -d  Base directory of the staged artifacts.
+EOF
+}
+
+OPTIND=1
+while getopts hv:d: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        d)  ARTIFACTS_DIR=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  VERSION=$(make version)
+fi
+if [[ -z "${ARTIFACTS_DIR:-}" ]]; then
+  echo "Specify an base artifact directory -d, or through the ARTIFACTS_DIR env var" >&2 && exit 1
+fi
+
+echo "Setting up Solr Operator ${VERSION} CRDs at ${ARTIFACTS_DIR}/crds."
+
+# Setup directory
+mkdir -p "${ARTIFACTS_DIR}"/crds
+rm -rf "${ARTIFACTS_DIR}"/crds/*
+
+# Create Release CRD files
+{
+  cat hack/headers/header.yaml.txt
+  printf "\n"
+} > "${ARTIFACTS_DIR}/crds/all.yaml"
+for filename in config/crd/bases/*.yaml; do
+    output_file=${filename#"config/crd/bases/solr.apache.org_"}
+    # Create individual file with Apache Header
+    {
+      cat hack/headers/header.yaml.txt;
+      printf "\n"
+      cat "${filename}";
+    } > "${ARTIFACTS_DIR}/crds/${output_file}"
+
+    # Add to aggregate file
+    cat "${filename}" >> "${ARTIFACTS_DIR}/crds/all.yaml"
+done
+
+# Fetch the correct dependency Zookeeper CRD, package with other CRDS
+{
+  cat hack/headers/zookeeper-operator-header.yaml.txt;
+  printf "\n\n---\n"
+  curl -sL "https://raw.githubusercontent.com/pravega/zookeeper-operator/v0.2.9/deploy/crds/zookeeper.pravega.io_zookeeperclusters_crd.yaml"
+} > "${ARTIFACTS_DIR}/crds/zookeeperclusters.yaml"
+
+# Package all Solr and Dependency CRDs
+{
+  cat "${ARTIFACTS_DIR}/crds/all.yaml"
+  printf "\n"
+  cat "${ARTIFACTS_DIR}/crds/zookeeperclusters.yaml"
+} > "${ARTIFACTS_DIR}/crds/all-with-dependencies.yaml"
diff --git a/hack/release/build_helm.sh b/hack/release/build_helm.sh
deleted file mode 100755
index d0d6a54..0000000
--- a/hack/release/build_helm.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-# exit immediately when a command fails
-set -e
-# only exit with zero if all commands of the pipeline exit successfully
-set -o pipefail
-# error on unset variables
-set -u
-
-echo "Packaging helm chart for version ${VERSION}"
-
-# Package and Index the helm charts, create release artifacts to upload in GithubRelease
-mkdir -p release-artifacts
-
-rm -rf release-artifacts/*
-
-helm package -u helm/* --app-version "${VERSION}" --version "${VERSION#v}" -d release-artifacts/
-
-helm repo index release-artifacts/ --merge docs/charts/index.yaml
-
-mv release-artifacts/index.yaml docs/charts/index.yaml
diff --git a/hack/release/setup_release.sh b/hack/release/setup_release.sh
deleted file mode 100755
index b06d721..0000000
--- a/hack/release/setup_release.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env bash
-# exit immediately when a command fails
-set -e
-# only exit with zero if all commands of the pipeline exit successfully
-set -o pipefail
-# error on unset variables
-set -u
-
-echo "Setting up Release ${VERSION}, making last commit."
-
-mkdir -p release-artifacts/crds
-
-# Create Release CRD files
-{
-  cat hack/headers/header.yaml.txt
-  printf "\n"
-} > release-artifacts/crds/all.yaml
-for filename in config/crd/bases/*.yaml; do
-    output_file=${filename#"config/crd/bases/solr.apache.org_"}
-    # Create individual file with Apache Header
-    {
-      cat hack/headers/header.yaml.txt;
-      printf "\n"
-      cat "${filename}";
-    } > "release-artifacts/crds/${output_file}"
-
-    # Add to aggregate file
-    cat "${filename}" >> release-artifacts/crds/all.yaml
-done
-
-# Fetch the correct dependency Zookeeper CRD, package with other CRDS
-{
-  cat hack/headers/zookeeper-operator-header.yaml.txt;
-  printf "\n\n---\n"
-  curl -sL "https://raw.githubusercontent.com/pravega/zookeeper-operator/v0.2.9/deploy/crds/zookeeper.pravega.io_zookeeperclusters_crd.yaml"
-} > release-artifacts/crds/zookeeperclusters.yaml
-
-# Package all Solr and Dependency CRDs
-{
-  cat release-artifacts/crds/all.yaml
-  printf "\n"
-  cat release-artifacts/crds/zookeeperclusters.yaml
-} > release-artifacts/crds/all-with-dependencies.yaml
-
-#git add helm config docs
-
-#git commit -asm "Cutting release version ${VERSION} of the Solr Operator"
diff --git a/hack/release/smoke_test/smoke_test.sh b/hack/release/smoke_test/smoke_test.sh
new file mode 100755
index 0000000..084b9ba
--- /dev/null
+++ b/hack/release/smoke_test/smoke_test.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/smoke_test/smoke_test.sh [-h] [-i IMAGE] [-s GIT_SHA] -v VERSION -l LOCATION
+
+Smoke test the Solr Operator release artifacts.
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator
+    -i  Solr Operator Docker image to use  (Optional, defaults to apache/solr-operator:<version>)
+    -s  GitSHA of the last commit for this version of Solr (Optional, check will not happen if not provided)
+    -l  Base location of the staged artifacts. Can be a URL or relative or absolute file path.
+EOF
+}
+
+OPTIND=1
+while getopts hv:i:l:s: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        s)  GIT_SHA=$OPTARG
+            ;;
+        i)  IMAGE=$OPTARG
+            ;;
+        l)  LOCATION=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  echo "Specify a project version through -v, or through the VERSION env var" >&2 && exit 1
+fi
+if [[ -z "${IMAGE:-}" ]]; then
+  IMAGE="apache/solr-operator:${VERSION}"
+fi
+if [[ -z "${LOCATION:-}" ]]; then
+  echo "Specify an base artifact location -l, or through the LOCATION env var" >&2 && exit 1
+fi
+
+PULL_PASS_THROUGH=""
+# If LOCATION is a URL, we want to pull the Docker image
+if (echo "${LOCATION}" | grep -E "http"); then
+  PULL_PASS_THROUGH=" -p"
+fi
+
+GIT_SHA_PASS_THROUGH=()
+if [[ -n "${GIT_SHA:-}" ]]; then
+  GIT_SHA_PASS_THROUGH=(-s "${GIT_SHA}")
+fi
+
+./hack/release/smoke_test/verify_all.sh -v "${VERSION}" -l "${LOCATION}"
+./hack/release/smoke_test/verify_docker.sh -v "${VERSION}" -i "${IMAGE}""${PULL_PASS_THROUGH}" "${GIT_SHA_PASS_THROUGH[@]}"
+./hack/release/smoke_test/test_source.sh -v "${VERSION}" -l "${LOCATION}"
+./hack/release/smoke_test/test_cluster.sh -v "${VERSION}" -i "${IMAGE}" -l "${LOCATION}"
+
+printf "\n\n********************\nSuccessfully smoke tested the Solr Operator %s!\n" "${VERSION}"
diff --git a/hack/release/smoke_test/test_cluster.sh b/hack/release/smoke_test/test_cluster.sh
new file mode 100755
index 0000000..8b70bed
--- /dev/null
+++ b/hack/release/smoke_test/test_cluster.sh
@@ -0,0 +1,191 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/smoke_test/test_cluster.sh [-h] [-i IMAGE] -v VERSION -l LOCATION
+
+Test the release candidate in a Kind cluster
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator
+    -i  Solr Operator docker image to use  (Optional, defaults to apache/solr-operator:<version>)
+    -l  Base location of the staged artifacts. Can be a URL or relative or absolute file path.
+EOF
+}
+
+OPTIND=1
+while getopts hv:i:l: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        i)  IMAGE=$OPTARG
+            ;;
+        l)  LOCATION=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  echo "Specify a project version through -v, or through the VERSION env var" >&2 && exit 1
+fi
+if [[ -z "${IMAGE:-}" ]]; then
+  IMAGE="apache/solr-operator:${VERSION}"
+fi
+if [[ -z "${LOCATION:-}" ]]; then
+  echo "Specify an base artifact location -l, or through the LOCATION env var" >&2 && exit 1
+fi
+
+# If LOCATION is not a URL, then get the absolute path
+if ! (echo "${LOCATION}" | grep -E "http"); then
+  LOCATION=$(cd "${LOCATION}"; pwd)
+  LOCATION=${LOCATION%%/}
+
+  HELM_CHART="${LOCATION}/helm-charts/solr-operator-${VERSION#v}.tgz"
+else
+  # If LOCATION is a URL, then we want to make sure we have the up-to-date docker image.
+  docker pull "${IMAGE}"
+
+  # Add the Test Helm Repo
+  helm repo add --force-update "apache-solr-test-${VERSION}" "${LOCATION}/helm-charts"
+
+  HELM_CHART="apache-solr-test-${VERSION}/solr-operator"
+fi
+
+if ! (which kind); then
+  echo "Install Kind (Kubernetes in Docker)"
+  GO111MODULE="on" go install sigs.k8s.io/kind@v0.10.0
+fi
+
+CLUSTER_NAME="solr-operator-${VERSION}-rc"
+KUBE_CONTEXT="kind-${CLUSTER_NAME}"
+
+if (kind get clusters | grep "${CLUSTER_NAME}"); then
+  printf "Delete cluster, so the test starts with a clean slate.\n\n"
+  kind delete clusters "${CLUSTER_NAME}"
+fi
+
+echo "Create test Kubernetes cluster in Kind. This will allow us to test the CRDs, Helm chart and the Docker image."
+kind create cluster --name "${CLUSTER_NAME}"
+
+# Load the docker image into the cluster
+kind load docker-image --name "${CLUSTER_NAME}" "${IMAGE}"
+
+# First generate the temporary public key ring
+gpg --export >~/.gnupg/pubring.gpg
+
+# Install the Solr Operator
+kubectl create -f "${LOCATION}/crds/all-with-dependencies.yaml" || kubectl replace -f "${LOCATION}/crds/all-with-dependencies.yaml"
+helm install --kube-context "${KUBE_CONTEXT}" --verify solr-operator "${HELM_CHART}" --set image.tag="${IMAGE##*:}" --set image.repository="${IMAGE%%:*}" --set image.pullPolicy="Never"
+
+printf "\nInstall a test Solr Cluster\n"
+cat <<EOF | kubectl apply -f -
+apiVersion: solr.apache.org/v1beta1
+kind: SolrCloud
+metadata:
+  name: example
+spec:
+  replicas: 3
+  solrImage:
+    tag: 8.7.0
+  solrJavaMem: "-Xms1g -Xmx3g"
+  customSolrKubeOptions:
+    podOptions:
+      resources:
+        limits:
+          memory: "1G"
+        requests:
+          cpu: "65m"
+          memory: "156Mi"
+  zookeeperRef:
+    provided:
+      persistence:
+        spec:
+          resources:
+            requests:
+              storage: "5Gi"
+      replicas: 1
+EOF
+
+# Wait for solrcloud to be ready
+printf '\nWait for all 3 Solr nodes to become ready.\n\n'
+grep -q "3              3       3            3" <(exec kubectl get solrcloud example -w); kill $!
+
+# Expose the common Solr service to localhost
+kubectl port-forward service/example-solrcloud-common 8983:80 || true &
+sleep 2
+
+printf "\nCheck the admin URL to make sure it works\n"
+curl --silent "http://localhost:8983/solr/admin/info/system" | grep '"status":0' > /dev/null
+
+printf "\nCreating a test collection\n"
+curl --silent "http://localhost:8983/solr/admin/collections?action=CREATE&name=smoke-test&replicationFactor=2&numShards=1" | grep '"status":0' > /dev/null
+
+printf "\nQuery the test collection, test for 0 docs\n"
+curl --silent "http://localhost:8983/solr/smoke-test/select" | grep '\"numFound\":0' > /dev/null
+
+printf "\nCreate a Solr Prometheus Exporter to expose metrics for the Solr Cloud\n"
+cat <<EOF | kubectl apply -f -
+apiVersion: solr.apache.org/v1beta1
+kind: SolrPrometheusExporter
+metadata:
+  name: example
+spec:
+  solrReference:
+    cloud:
+      name: "example"
+  numThreads: 4
+  image:
+    tag: 8.7.0
+EOF
+
+printf "\nWait for the Solr Prometheus Exporter to be ready\n"
+sleep 5
+kubectl rollout status deployment/example-solr-metrics
+
+# Expose the Solr Prometheus Exporter service to localhost
+kubectl port-forward service/example-solr-metrics 8984:80 || true &
+sleep 15
+
+printf "\nQuery the prometheus exporter, test for 'http://example-solrcloud-0.example-solrcloud-headless.default:8983/solr' URL being scraped.\n"
+curl --silent "http://localhost:8984/metrics" | grep http://example-solrcloud-0.example-solrcloud-headless.default:8983/solr > /dev/null
+
+# If LOCATION is a URL, then remove the helm repo at the end
+if (echo "${LOCATION}" | grep -E "http"); then
+  helm repo remove "apache-solr-test-${VERSION}"
+fi
+
+
+echo "Delete test Kind Kubernetes cluster."
+kind delete clusters "${CLUSTER_NAME}"
+
+printf "\n********************\nLocal end-to-end cluster test successfully run!\n\n"
diff --git a/hack/release/smoke_test/test_source.sh b/hack/release/smoke_test/test_source.sh
new file mode 100755
index 0000000..683e406
--- /dev/null
+++ b/hack/release/smoke_test/test_source.sh
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/smoke_test/test_source.sh [-h] -v VERSION -l LOCATION
+
+Test the source bundle artifact
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator
+    -l  Base location of the staged artifacts. Can be a URL or relative or absolute file path.
+EOF
+}
+
+OPTIND=1
+while getopts hv:l: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        l)  LOCATION=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  echo "Specify a project version through -v, or through the VERSION env var" >&2 && exit 1
+fi
+if [[ -z "${LOCATION:-}" ]]; then
+  echo "Specify an base artifact location -l, or through the LOCATION env var" >&2 && exit 1
+fi
+
+TMP_DIR=$(mktemp -d --tmpdir "solr-operator-smoke-test-source-XXXXXXXX")
+
+# If LOCATION is not a URL, then get the absolute path
+if ! (echo "${LOCATION}" | grep -E "http"); then
+  LOCATION=$(cd "${LOCATION}"; pwd)
+fi
+
+echo "Download source artifact, verify and run 'make check'"
+# Do all logic in temporary directory
+(
+  cd "${TMP_DIR}"
+
+  if (echo "${LOCATION}" | grep -E "http"); then
+    # Download source
+    wget "${LOCATION}/solr-operator-${VERSION}.tgz"
+
+    # Pull docker image, since we are working with remotely staged artifacts
+    docker pull "apache/solr-operator:${TAG}"
+  else
+    cp "${LOCATION}/solr-operator-${VERSION}.tgz" .
+  fi
+
+  # Unpack the source code
+  tar -xzf "solr-operator-${VERSION}.tgz"
+  cd "solr-operator-${VERSION}"
+
+  # Install the dependencies
+  make install-dependencies
+
+  # Run the checks
+  make check
+
+  # Check the version
+  FOUND_VERSION=$(make version)
+  if [[ "$FOUND_VERSION" != "${VERSION}" ]]; then
+    error "Version in source release should be ${VERSION}, but found ${FOUND_VERSION}"
+    exit 1
+  fi
+
+  # Check the License & Notice info
+  ls "LICENSE"
+  ls "NOTICE"
+)
+
+# Delete temporary source directory
+rm -rf "${TMP_DIR}"
+
+printf "\n********************\nSource verification successful!\n\n"
diff --git a/hack/release/smoke_test/verify_all.sh b/hack/release/smoke_test/verify_all.sh
new file mode 100755
index 0000000..7d85fde
--- /dev/null
+++ b/hack/release/smoke_test/verify_all.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/smoke_test/verify_all.sh [-h] -v VERSION -l LOCATION
+
+Verify checksums and signatures of all release artifacts.
+Check that the docker image contains the necessary LICENSE and NOTICE.
+
+    -h  Display this help and exit
+    -v  Version of the Solr Operator
+    -l  Base location of the staged artifacts. Can be a URL or relative or absolute file path.
+EOF
+}
+
+OPTIND=1
+while getopts hv:l: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        l)  LOCATION=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  echo "Specify a project version through -v, or through the VERSION env var" >&2 && exit 1
+fi
+if [[ -z "${LOCATION:-}" ]]; then
+  echo "Specify an base artifact location -l, or through the LOCATION env var" >&2 && exit 1
+fi
+
+TMP_DIR=$(mktemp -d --tmpdir "solr-operator-smoke-test-source-XXXXXX")
+
+# If LOCATION is not a URL, then get the absolute path
+if ! (echo "${LOCATION}" | grep -E "http://"); then
+  LOCATION=$(cd "${LOCATION}"; pwd)
+fi
+
+echo "Import Solr Keys"
+curl -sL0 "https://dist.apache.org/repos/dist/release/solr/KEYS" | gpg2 --import --quiet
+
+# First generate the temporary public key ring
+gpg --export >~/.gnupg/pubring.gpg
+
+echo "Download all artifacts and verify signatures"
+# Do all logic in temporary directory
+(
+  cd "${TMP_DIR}"
+
+  if (echo "${LOCATION}" | grep -E "http"); then
+    # Download Source files from the staged location
+    wget -r -np -nH -nd --level=1 "${LOCATION}/"
+
+    # Download Helm files from the staged location
+    wget -r -np -nH -nd --level=1 -P "helm-charts" "${LOCATION}/helm-charts/"
+
+    # Download CRD files from the staged location
+    wget -r -np -nH -nd --level=1 -P "crds" "${LOCATION}/crds/"
+  else
+    cp -r "${LOCATION}/"* .
+  fi
+
+  for artifact_directory in $(find * -type d); do
+    (
+      cd "${artifact_directory}"
+
+      for artifact in $(find * -type f ! \( -name '*.asc' -o -name '*.sha512' -o -name '*.prov' \) ); do
+        sha512sum -c "${artifact}.sha512" \
+          || { echo "Invalid sha512 for ${artifact}. Aborting!"; exit 1; }
+        gpg2 --verify "${artifact}.asc" "${artifact}" \
+          || { echo "Invalid signature for ${artifact}. Aborting!"; exit 1; }
+      done
+
+      # If a helm chart has a provenance file, verify it
+      if [[ -f "${artifact}.prov" ]]; then
+        helm verify "${artifact}"
+      fi
+    )
+  done
+)
+
+# Delete temporary source download directory
+rm -rf "${TMP_DIR}"
+
+printf "\n********************\nSuccessfully verified all artifacts!\n\n"
diff --git a/hack/release/smoke_test/verify_docker.sh b/hack/release/smoke_test/verify_docker.sh
new file mode 100755
index 0000000..7b7af92
--- /dev/null
+++ b/hack/release/smoke_test/verify_docker.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/smoke_test/verify_docker.sh [-h] [-p] [-i IMAGE] [-s GIT_SHA] -v VERSION
+
+Verify the Solr Operator Docker image.
+
+    -h  Display this help and exit
+    -p  Pull Docker image before verifying (Optional, defaults to false)
+    -v  Version of the Solr Operator
+    -s  GitSHA of the last commit for this version of Solr (Optional, check will not happen if not provided)
+    -i  Name of the docker image to verify (Optional, defaults to apache/solr-operator:<version>)
+EOF
+}
+
+OPTIND=1
+while getopts hpv:i:s: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        s)  GIT_SHA=$OPTARG
+            ;;
+        i)  IMAGE=$OPTARG
+            ;;
+        p)  PULL_FIRST=true
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${VERSION:-}" ]]; then
+  echo "Specify a project version through -v, or through the VERSION env var" >&2 && exit 1
+fi
+if [[ -z "${IMAGE:-}" ]]; then
+  IMAGE="apache/solr-operator:${VERSION}"
+fi
+
+# Pull the image, if requested
+if [[ ${PULL_FIRST:-} ]]; then
+  docker pull "apache/solr-operator:${IMAGE}"
+fi
+
+echo "Verify the Docker image ${IMAGE}"
+
+# Check for the LICENSE and NOTICE info in the image
+docker run --rm --entrypoint='sh' "${IMAGE}" -c "ls /etc/licenses/LICENSE ; ls /etc/licenses/NOTICE; ls /etc/licenses/dependencies/*"
+
+# Check for Version and other information
+docker run --rm -it --entrypoint='sh' "${IMAGE}" -c "/solr-operator || true" | grep "solr-operator Version: ${VERSION}" \
+  || {
+     echo "Could not find correct Version in Operator startup logs: ${VERSION}" >&2;
+     exit 1
+    }
+if [[ -n "${GIT_SHA:-}" ]]; then
+  docker run --rm -it --entrypoint='sh' "${IMAGE}" -c "/solr-operator || true" | grep "solr-operator Git SHA: ${GIT_SHA}" \
+    || {
+     echo "Could not find correct Git SHA in Operator startup logs: ${GIT_SHA}" >&2;
+     exit 1
+    }
+fi
+
+printf "\n********************\nDocker verification successful!\n\n"
diff --git a/hack/release/upload/upload_crds.sh b/hack/release/upload/upload_crds.sh
new file mode 100755
index 0000000..71ba06b
--- /dev/null
+++ b/hack/release/upload/upload_crds.sh
@@ -0,0 +1,114 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/upload/upload_crds.sh [-h] -a APACHE_ID -v VERSION -c CRDS_URL -r RELEASE_URL
+
+Upload the CRDs from the artifacts release to a convenience location.
+
+    -h  Display this help and exit
+    -a  Apache ID
+    -c  Convenience location to upload the released Solr Operator CRDs. Do NOT include the version at the end. (The actual location, not the solr.apache.org/operator/downloads/crds redirect)
+    -r  URL of the Official Solr Operator release artifacts (do not include the 'helm-charts' suffix).
+    -v  Version of this Solr Operator release.
+EOF
+}
+
+OPTIND=1
+while getopts ha:c:r:v: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        a)  APACHE_ID=$OPTARG
+            ;;
+        c)  CRDS_URL=$OPTARG
+            ;;
+        r)  RELEASE_URL=$OPTARG
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${CRDS_URL:-}" ]]; then
+  echo "Specify a convenience CRDs URL through -c, or through the CRDS_URL env var" >&2 && exit 1
+fi
+if [[ -z "${RELEASE_URL:-}" ]]; then
+  echo "Specify a release url of the Solr Operator artifacts via -r, or through the RELEASE_URL env var" >&2 && exit 1
+fi
+if [[ -z "${VERSION:-}" ]]; then
+  echo "Specify a release version of the Solr Operator via -v, or through the VERSION env var" >&2 && exit 1
+fi
+if [[ -z "${APACHE_ID:-}" ]]; then
+  echo "Specify your Apache ID for uploading via -a, or through the APACHE_ID env var" >&2 && exit 1
+fi
+
+
+CRDS_FULL_URL="${CRDS_URL%%/}/${VERSION}"
+
+echo "Pulling CRDs from the staged url and uploading to release location ${CRDS_FULL_URL}."
+
+# Put in a sub-shell so that the Password can't leak
+(
+  WITH_APACHE_ID=()
+  if [[ -n "${APACHE_ID:-}" ]]; then
+    printf "\nProvide your Apache Password, as it will be used to upload the CRDs to the apache nightlies server: "
+    IFS= read -rs APACHE_PASSWORD < /dev/tty
+    printf "\n\n"
+
+    WITH_APACHE_ID=(-u "${APACHE_ID}:${APACHE_PASSWORD}")
+    APACHE_PASSWORD=""
+  fi
+
+  TMP_DIR=$(mktemp -d --tmpdir "solr-operator-release-crds-XXXXXXXX")
+
+  # Do all logic in temporary directory
+  (
+    cd "${TMP_DIR}"
+
+    # Download CRD files from the staged location
+    wget -r -np -nH -nd --level=1 -A "*.yaml*" "${RELEASE_URL}/crds/"
+
+    # Create base release directory for CRDs
+    curl "${WITH_APACHE_ID[@]}" -X MKCOL "${CRDS_FULL_URL}"
+
+    # Upload each CRD file
+    for filename in *; do
+      curl "${WITH_APACHE_ID[@]}" -T "${filename}" "${CRDS_FULL_URL}/"
+    done
+  )
+
+  # Clear password, just in case
+  WITH_APACHE_ID=()
+
+  # Delete temporary artifact directory
+  rm -rf "${TMP_DIR}"
+)
diff --git a/hack/release/upload/upload_helm.sh b/hack/release/upload/upload_helm.sh
new file mode 100755
index 0000000..e194b5a
--- /dev/null
+++ b/hack/release/upload/upload_helm.sh
@@ -0,0 +1,120 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/upload/upload_helm.sh [-h] [-g GPG_KEY] -a APACHE_ID -c CHART_REPO -r RELEASE_URL
+
+Upload the Helm Chart from staging to release.
+
+    -h  Display this help and exit
+    -c  Location of the Official Solr Operator Helm Chart repository. (The actual location, not the solr.apache.org/charts redirect)
+    -r  URL of the Official Solr Operator release artifacts (do not include the 'helm-charts' suffix).
+    -a  Apache ID
+    -g  GPG Key to sign the new helm index file (Optional)
+EOF
+}
+
+OPTIND=1
+while getopts ha:g:c:r: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        a)  APACHE_ID=$OPTARG
+            ;;
+        g)  GPG_KEY=$OPTARG
+            ;;
+        c)  CHART_REPO=$OPTARG
+            ;;
+        r)  RELEASE_URL=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+if [[ -z "${CHART_REPO:-}" ]]; then
+  echo "Specify a Helm Chart repository to upload to via -c, or through the CHART_REPO env var" >&2 && exit 1
+fi
+if [[ -z "${RELEASE_URL:-}" ]]; then
+  echo "Specify a release url of the Solr Operator artifacts via -r, or through the RELEASE_URL env var" >&2 && exit 1
+fi
+if [[ -z "${APACHE_ID:-}" ]]; then
+  echo "Specify your Apache ID for uploading via -a, or through the APACHE_ID env var" >&2 && exit 1
+fi
+
+GPG_USER=()
+if [[ -n "${GPG_KEY:-}" ]]; then
+  GPG_USER=(-u "${GPG_KEY}")
+fi
+
+echo "Pulling Helm chart from the staged url and uploading to release Helm repo. ${CHART_REPO}"
+
+# Put in a sub-shell so that the Password can't leak
+(
+  WITH_APACHE_ID=()
+  if [[ -n "${APACHE_ID:-}" ]]; then
+    printf "\nProvide your Apache Password, as it will be used to upload the CRDs to the apache nightlies server: "
+    IFS= read -rs APACHE_PASSWORD < /dev/tty
+    printf "\n\n"
+
+    WITH_APACHE_ID=(-u "${APACHE_ID}:${APACHE_PASSWORD}")
+    APACHE_PASSWORD=""
+  fi
+
+  TMP_DIR=$(mktemp -d --tmpdir "solr-operator-release-helm-XXXXXXXX")
+
+  # Do all logic in temporary directory
+  (
+    cd "${TMP_DIR}"
+
+    # Pull Helm charts from staged location
+    wget -r -np -nH -nd --level=1 -A "*.tgz*" "${RELEASE_URL}/helm-charts"
+
+    # Pull the official Helm repo index.yaml
+    wget "${CHART_REPO}/index.yaml"
+
+    # Add the newly released Helm chart to the index
+    helm repo index . --merge "index.yaml"
+
+    # Generate signature and checksum for new index file
+    gpg "${GPG_USER[@]}" -ab "index.yaml"
+    sha512sum -b "index.yaml" > "index.yaml.sha512"
+
+    # Upload the newly released Helm charts
+    for filename in *; do
+      curl "${WITH_APACHE_ID[@]}" -T "${filename}" "${CHART_REPO}/"
+    done
+  )
+
+  # Clear password, just in case
+  WITH_APACHE_ID=()
+
+  # Delete temporary artifact directory
+  rm -rf "${TMP_DIR}"
+)
diff --git a/hack/release/version/change_suffix.sh b/hack/release/version/change_suffix.sh
index d1184cf..eccd5bf 100755
--- a/hack/release/version/change_suffix.sh
+++ b/hack/release/version/change_suffix.sh
@@ -1,4 +1,19 @@
 #!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 # exit immediately when a command fails
 set -e
 # only exit with zero if all commands of the pipeline exit successfully
@@ -6,27 +21,41 @@ set -o pipefail
 # error on unset variables
 set -u
 
+show_help() {
+cat << EOF
+Usage: ./hack/release/version/change_suffix.sh [-h] [-s SUFFIX]
+
+Change the Version Suffix of the project.
+If a suffix is not provided, the project suffix will flip between "" and "prerelease", depending on the current value.
 
-###
-# Change the Version Suffix of the project.
-# Use:
-#   ./hack/release/version/change_suffix.sh
-#           (This will remove the suffix if one exists, or change it to "prerelease" if empty)
-#   VERSION_SUFFIX=new-suffix ./hack/release/version/change_suffix.sh
-#   ./hack/release/version/change_suffix.sh new-suffix
-###
+    -h  Display this help and exit
+    -s  New version suffix for the project. (Optional)
+EOF
+}
 
+OPTIND=1
+while getopts hs: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        s)  VERSION_SUFFIX=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
 
 if [[ -z "${VERSION_SUFFIX:-}" ]]; then
-  if [[ -z "${1:-}" ]]; then
-    EXISTING_VERSION_SUFFIX="$(cat version/version.go | grep -E 'VersionSuffix([[:space:]]+)string' | grep -o '["''].*["'']' | xargs)"
-    if [[ -z "${EXISTING_VERSION_SUFFIX}" ]]; then
-      export VERSION_SUFFIX="prerelease"
-    else
-      export VERSION_SUFFIX=""
-    fi
+  EXISTING_VERSION_SUFFIX="$(cat version/version.go | grep -E 'VersionSuffix([[:space:]]+)string' | grep -o '["''].*["'']' | xargs)"
+  if [[ -z "${EXISTING_VERSION_SUFFIX}" ]]; then
+    export VERSION_SUFFIX="prerelease"
   else
-    export VERSION_SUFFIX="$1"
+    export VERSION_SUFFIX=""
   fi
 fi
 
diff --git a/hack/release/version/propagate_version.sh b/hack/release/version/propagate_version.sh
index 0e8e400..01fe035 100755
--- a/hack/release/version/propagate_version.sh
+++ b/hack/release/version/propagate_version.sh
@@ -1,4 +1,19 @@
 #!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 # exit immediately when a command fails
 set -e
 # only exit with zero if all commands of the pipeline exit successfully
@@ -6,9 +21,30 @@ set -o pipefail
 # error on unset variables
 set -u
 
-###
-# Increase Version across the project to reflect what exists in version/version.go
-###
+show_help() {
+cat << EOF
+Usage: ./hack/release/version/propagate_version.sh [-h]
+
+Make sure all files in the project reflect the version currently set in: version/version.go
+
+    -h  Display this help and exit
+EOF
+}
+
+OPTIND=1
+while getopts h opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
 
 # Get full version string
 VERSION="$(cat version/version.go | grep -E 'Version([[:space:]]+)string' | grep -o '["''].*["'']' | xargs)"
@@ -24,10 +60,15 @@ awk -i inplace '$1 == "repository:" { tag = ($2 == "apache/solr-operator") }
 tag && $1 == "tag:"{$1 = "  " $1; $2 = "'"${VERSION}"'"} 1' helm/solr-operator/values.yaml
 
 # Update Helm Chart.yaml
+IS_PRE_RELEASE="false"
+if [[ "${VERSION_SUFFIX}" =~ .*prerelease ]]; then
+  IS_PRE_RELEASE="true"
+fi
 {
   cat helm/solr-operator/Chart.yaml | \
   awk '$0 ~ /^v/ && $1 == "version:"{$1 = $1; $2 = "'"${VERSION#v}"'"} 1' | \
   awk '$0 ~ /^a/ && $1 == "appVersion:"{$1 = $1; $2 = "'"${VERSION}"'"} 1' | \
+  awk '$1 == "artifacthub.io/prerelease:"{$1 = "  "$1; $2 = "\"'"${IS_PRE_RELEASE}"'\""} 1' | \
   sed -E "s|image: apache/solr-operator:(.*)|image: apache/solr-operator:${VERSION}|g"
 } > helm/solr-operator/Chart.yaml.tmp && mv helm/solr-operator/Chart.yaml.tmp helm/solr-operator/Chart.yaml
 
diff --git a/hack/release/version/remove_version_specific_info.sh b/hack/release/version/remove_version_specific_info.sh
new file mode 100755
index 0000000..77d5240
--- /dev/null
+++ b/hack/release/version/remove_version_specific_info.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# exit immediately when a command fails
+set -e
+# only exit with zero if all commands of the pipeline exit successfully
+set -o pipefail
+# error on unset variables
+set -u
+
+show_help() {
+cat << EOF
+Usage: ./hack/release/version/remove_version_specific_info.sh [-h]
+
+Remove information in the project specific to a previous version.
+Requires:
+ * yq
+
+    -h  Display this help and exit
+EOF
+}
+
+OPTIND=1
+while getopts h opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
+
+echo "Removing information specific to the previous release"
+
+# Reset ArtifactHub changelog in Chart.yaml
+yq -i eval '.annotations."artifacthub.io/changes" |= "- Change 1
+- Change 2
+"' helm/solr-operator/Chart.yaml
diff --git a/hack/release/version/update_version.sh b/hack/release/version/update_version.sh
index d089c16..68d83db 100755
--- a/hack/release/version/update_version.sh
+++ b/hack/release/version/update_version.sh
@@ -1,4 +1,19 @@
 #!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 # exit immediately when a command fails
 set -e
 # only exit with zero if all commands of the pipeline exit successfully
@@ -6,15 +21,36 @@ set -o pipefail
 # error on unset variables
 set -u
 
-###
-# Change the Version of the project
-# Use:
-#   VERSION=v1.2.3 ./hack/release/version/update_version.sh
-#   ./hack/release/version/update_version.sh v1.2.3
-###
+show_help() {
+cat << EOF
+Usage: ./hack/release/version/update_version.sh [-h] -v VERSION
+
+Change the Version of the project.
+
+    -h  Display this help and exit
+    -v  New version for the project.
+EOF
+}
+
+OPTIND=1
+while getopts hv: opt; do
+    case $opt in
+        h)
+            show_help
+            exit 0
+            ;;
+        v)  VERSION=$OPTARG
+            ;;
+        *)
+            show_help >&2
+            exit 1
+            ;;
+    esac
+done
+shift "$((OPTIND-1))"   # Discard the options and sentinel --
 
 if [[ -z "${VERSION:-}" ]]; then
-  export VERSION="$1"
+  echo "Specify a new project version through -v, or through the VERSION env var" >&2 && exit 1
 fi
 
 echo "Updating the latest version throughout the repo to: ${VERSION}"
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index 5cfac85..6e3a224 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -34,17 +34,19 @@ maintainers:
   - name: Houston Putman
     email: houston@apache.org
 icon: https://solr.apache.org/theme/images/identity/Solr_Logo_on_white.png
-
 dependencies:
   - name: 'zookeeper-operator'
     version: 0.2.9
     repository: https://charts.pravega.io
     condition: zookeeper-operator.install
-
 annotations:
   artifacthub.io/operator: "true"
   artifacthub.io/operatorCapabilities: Seamless Upgrades
-  artifacthub.io/prerelease: "false"
+  artifacthub.io/prerelease: "true"
+  # Add change log for a single release here, only single-layered bulleted list allowed.
+  artifacthub.io/changes: |
+    - Change 1
+    - Change 2
   artifacthub.io/images: |
     - name: solr-operator
       image: apache/solr-operator:v0.3.0-prerelease