mirror of https://github.com/knative/docs.git
789 lines
30 KiB
Bash
789 lines
30 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Copyright 2018 The Knative Authors
|
|
#
|
|
# Licensed 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.
|
|
|
|
# This is a helper script for Knative release scripts.
|
|
# See README.md for instructions on how to use it.
|
|
|
|
source "$(dirname "${BASH_SOURCE[0]}")/library.sh"
|
|
|
|
# Organization name in GitHub; defaults to Knative.
|
|
readonly ORG_NAME="${ORG_NAME:-knative}"
|
|
|
|
# GitHub upstream.
|
|
readonly REPO_UPSTREAM="https://github.com/${ORG_NAME}/${REPO_NAME}"
|
|
|
|
# GCRs for Knative releases.
|
|
readonly NIGHTLY_GCR="gcr.io/knative-nightly/github.com/${ORG_NAME}/${REPO_NAME}"
|
|
readonly RELEASE_GCR="gcr.io/knative-releases/github.com/${ORG_NAME}/${REPO_NAME}"
|
|
|
|
# Signing identities for knative releases.
|
|
readonly NIGHTLY_SIGNING_IDENTITY="signer@knative-nightly.iam.gserviceaccount.com"
|
|
readonly RELEASE_SIGNING_IDENTITY="signer@knative-releases.iam.gserviceaccount.com"
|
|
|
|
# Simple banner for logging purposes.
|
|
# Parameters: $* - message to display.
|
|
function banner() {
|
|
subheader "$*"
|
|
}
|
|
|
|
# Copy the given files to the $RELEASE_GCS_BUCKET bucket's "latest" directory.
|
|
# If $TAG is not empty, also copy them to $RELEASE_GCS_BUCKET bucket's "previous" directory.
|
|
# Parameters: $1..$n - files to copy.
|
|
function publish_to_gcs() {
|
|
function verbose_gsutil_cp {
|
|
local DEST="gs://${RELEASE_GCS_BUCKET}/$1/"
|
|
shift
|
|
echo "Publishing [$@] to ${DEST}"
|
|
gsutil -m cp $@ "${DEST}"
|
|
}
|
|
# Before publishing the files, cleanup the `latest` dir if it exists.
|
|
local latest_dir="gs://${RELEASE_GCS_BUCKET}/latest"
|
|
if [[ -n "$(gsutil ls "${latest_dir}" 2> /dev/null)" ]]; then
|
|
echo "Cleaning up '${latest_dir}' first"
|
|
gsutil -m rm "${latest_dir}"/**
|
|
fi
|
|
verbose_gsutil_cp latest $@
|
|
[[ -n ${TAG} ]] && verbose_gsutil_cp previous/"${TAG}" $@
|
|
}
|
|
|
|
# These are global environment variables.
|
|
SKIP_TESTS=0
|
|
PRESUBMIT_TEST_FAIL_FAST=1
|
|
export TAG_RELEASE=0
|
|
PUBLISH_RELEASE=0
|
|
PUBLISH_TO_GITHUB=0
|
|
export TAG=""
|
|
BUILD_COMMIT_HASH=""
|
|
BUILD_YYYYMMDD=""
|
|
BUILD_TIMESTAMP=""
|
|
BUILD_TAG=""
|
|
RELEASE_VERSION=""
|
|
RELEASE_NOTES=""
|
|
RELEASE_BRANCH=""
|
|
RELEASE_GCS_BUCKET="knative-nightly/${REPO_NAME}"
|
|
RELEASE_DIR=""
|
|
export KO_FLAGS="-P --platform=all"
|
|
VALIDATION_TESTS="./test/presubmit-tests.sh"
|
|
ARTIFACTS_TO_PUBLISH=""
|
|
FROM_NIGHTLY_RELEASE=""
|
|
FROM_NIGHTLY_RELEASE_GCS=""
|
|
SIGNING_IDENTITY=""
|
|
APPLE_CODESIGN_KEY=""
|
|
APPLE_NOTARY_API_KEY=""
|
|
APPLE_CODESIGN_PASSWORD_FILE=""
|
|
export KO_DOCKER_REPO="gcr.io/knative-nightly"
|
|
# Build stripped binary to reduce size
|
|
export GOFLAGS="-ldflags=-s -ldflags=-w"
|
|
export GITHUB_TOKEN=""
|
|
readonly IMAGES_REFS_FILE="${IMAGES_REFS_FILE:-$(mktemp -d)/images_refs.txt}"
|
|
|
|
# Convenience function to run the GitHub CLI tool `gh`.
|
|
# Parameters: $1..$n - arguments to gh.
|
|
function gh_tool() {
|
|
go_run github.com/cli/cli/v2/cmd/gh@v2.65.0 "$@"
|
|
}
|
|
|
|
# Shortcut to "git push" that handles authentication.
|
|
# Parameters: $1..$n - arguments to "git push <repo>".
|
|
function git_push() {
|
|
local repo_url="${REPO_UPSTREAM}"
|
|
local git_args=$@
|
|
# Subshell (parentheses) used to prevent GITHUB_TOKEN from printing in log
|
|
(set +x;
|
|
[[ -n "${GITHUB_TOKEN}}" ]] && repo_url="${repo_url/:\/\//:\/\/${GITHUB_TOKEN}@}";
|
|
git push "${repo_url}" ${git_args} )
|
|
}
|
|
|
|
# Return the major+minor version of a release.
|
|
# For example, "v0.2.1" returns "0.2"
|
|
# Parameters: $1 - release version label.
|
|
function major_minor_version() {
|
|
local release="${1//v/}"
|
|
local tokens=(${release//\./ })
|
|
echo "${tokens[0]}.${tokens[1]}"
|
|
}
|
|
|
|
# Return the short commit SHA from a release tag.
|
|
# For example, "v20010101-deadbeef" returns "deadbeef".
|
|
function hash_from_tag() {
|
|
local tokens=(${1//-/ })
|
|
echo "${tokens[1]}"
|
|
}
|
|
|
|
# Setup the repository upstream, if not set.
|
|
function setup_upstream() {
|
|
# hub and checkout need the upstream URL to be set
|
|
local upstream="$(git remote get-url upstream)"
|
|
echo "Remote upstream URL is '${upstream}'"
|
|
if [[ -z "${upstream}" ]]; then
|
|
echo "Setting remote upstream URL to '${REPO_UPSTREAM}'"
|
|
git remote add upstream "${REPO_UPSTREAM}"
|
|
fi
|
|
}
|
|
|
|
# Fetch the release branch, so we can check it out.
|
|
function setup_branch() {
|
|
[[ -z "${RELEASE_BRANCH}" ]] && return
|
|
git fetch "${REPO_UPSTREAM}" "${RELEASE_BRANCH}:upstream/${RELEASE_BRANCH}"
|
|
}
|
|
|
|
# Setup version, branch and release notes for a auto release.
|
|
function prepare_auto_release() {
|
|
echo "Auto release requested"
|
|
TAG_RELEASE=1
|
|
PUBLISH_RELEASE=1
|
|
|
|
git fetch --all || abort "error fetching branches/tags from remote"
|
|
# Support two different formats for tags
|
|
# - knative-v1.0.0
|
|
# - v1.0.0
|
|
local tags="$(git tag | cut -d '-' -f2 | cut -d 'v' -f2 | cut -d '.' -f1-2 | sort -V | uniq)"
|
|
local branches="$( { (git branch -r | grep upstream/release-) ; (git branch | grep release-); } | cut -d '-' -f2 | sort -V | uniq)"
|
|
|
|
echo "Versions released (from tags): [" "${tags}" "]"
|
|
echo "Versions released (from branches): [" "${branches}" "]"
|
|
|
|
local release_number=""
|
|
for i in ${branches}; do
|
|
release_number="${i}"
|
|
for j in ${tags}; do
|
|
if [[ "${i}" == "${j}" ]]; then
|
|
release_number=""
|
|
fi
|
|
done
|
|
done
|
|
|
|
if [[ -z "${release_number}" ]]; then
|
|
echo "*** No new release will be generated, as no new branches exist"
|
|
exit 0
|
|
fi
|
|
|
|
RELEASE_VERSION="${release_number}.0"
|
|
RELEASE_BRANCH="release-${release_number}"
|
|
echo "Will create release ${RELEASE_VERSION} from branch ${RELEASE_BRANCH}"
|
|
# If --release-notes not used, add a placeholder
|
|
if [[ -z "${RELEASE_NOTES}" ]]; then
|
|
RELEASE_NOTES="$(mktemp)"
|
|
echo "[add release notes here]" > "${RELEASE_NOTES}"
|
|
fi
|
|
}
|
|
|
|
# Setup version, branch and release notes for a "dot" release.
|
|
function prepare_dot_release() {
|
|
echo "Dot release requested"
|
|
TAG_RELEASE=1
|
|
PUBLISH_RELEASE=1
|
|
git fetch --all || abort "error fetching branches/tags from remote"
|
|
# List latest release
|
|
local releases # don't combine with the line below, or $? will be 0
|
|
# Support tags in two formats
|
|
# - knative-v1.0.0
|
|
# - v1.0.0
|
|
releases="$(gh_tool release list --json tagName --jq '.[].tagName' | cut -d '-' -f2)"
|
|
echo "Current releases are: ${releases}"
|
|
[[ $? -eq 0 ]] || abort "cannot list releases"
|
|
# If --release-branch passed, restrict to that release
|
|
if [[ -n "${RELEASE_BRANCH}" ]]; then
|
|
local version_filter="v${RELEASE_BRANCH##release-}"
|
|
echo "Dot release will be generated for ${version_filter}"
|
|
releases="$(echo "${releases}" | grep ^${version_filter})"
|
|
fi
|
|
local last_version="$(echo "${releases}" | grep '^v[0-9]\+\.[0-9]\+\.[0-9]\+$' | sort -r -V | head -1)"
|
|
[[ -n "${last_version}" ]] || abort "no previous release exist"
|
|
local major_minor_version=""
|
|
if [[ -z "${RELEASE_BRANCH}" ]]; then
|
|
echo "Last release is ${last_version}"
|
|
# Determine branch
|
|
major_minor_version="$(major_minor_version "${last_version}")"
|
|
RELEASE_BRANCH="release-${major_minor_version}"
|
|
echo "Last release branch is ${RELEASE_BRANCH}"
|
|
else
|
|
major_minor_version="${RELEASE_BRANCH##release-}"
|
|
fi
|
|
[[ -n "${major_minor_version}" ]] || abort "cannot get release major/minor version"
|
|
# Ensure there are new commits in the branch, otherwise we don't create a new release
|
|
setup_branch
|
|
# Use the original tag (ie. potentially with a knative- prefix) when determining the last version commit sha
|
|
local github_tag="$(gh_tool release list --json tagName --jq '.[].tagName' | grep "${last_version}")"
|
|
local last_release_commit="$(git rev-list -n 1 "${github_tag}")"
|
|
local last_release_commit_filtered="$(git rev-list --invert-grep --grep "\[skip-dot-release\]" -n 1 "${github_tag}")"
|
|
local release_branch_commit="$(git rev-list -n 1 upstream/"${RELEASE_BRANCH}")"
|
|
local release_branch_commit_filtered="$(git rev-list --invert-grep --grep "\[skip-dot-release\]" -n 1 upstream/"${RELEASE_BRANCH}")"
|
|
[[ -n "${last_release_commit}" ]] || abort "cannot get last release commit"
|
|
[[ -n "${release_branch_commit}" ]] || abort "cannot get release branch last commit"
|
|
echo "Version ${last_version} is at commit ${last_release_commit}. Comparing using ${last_release_commit_filtered}. If it is different is because commits with the [skip-dot-release] flag in their commit body are not being considered."
|
|
echo "Branch ${RELEASE_BRANCH} is at commit ${release_branch_commit}. Comparing using ${release_branch_commit_filtered}. If it is different is because commits with the [skip-dot-release] flag in their commit body are not being considered."
|
|
if [[ "${last_release_commit_filtered}" == "${release_branch_commit_filtered}" ]]; then
|
|
echo "*** Branch ${RELEASE_BRANCH} has no new cherry-picks (ignoring commits with [skip-dot-release]) since release ${last_version}."
|
|
echo "*** No dot release will be generated, as no changes exist"
|
|
exit 0
|
|
fi
|
|
# Create new release version number
|
|
local last_build="$(patch_version "${last_version}")"
|
|
RELEASE_VERSION="${major_minor_version}.$(( last_build + 1 ))"
|
|
echo "Will create release ${RELEASE_VERSION} at commit ${release_branch_commit}"
|
|
# If --release-notes not used, copy from the latest release
|
|
if [[ -z "${RELEASE_NOTES}" ]]; then
|
|
RELEASE_NOTES="$(mktemp)"
|
|
gh_tool release view "${github_tag}" --json "body" --jq '.body' > "${RELEASE_NOTES}"
|
|
echo "Release notes from ${last_version} copied to ${RELEASE_NOTES}"
|
|
fi
|
|
}
|
|
|
|
# Setup source nightly image for a release.
|
|
function prepare_from_nightly_release() {
|
|
echo "Release from nightly requested"
|
|
SKIP_TESTS=1
|
|
if [[ "${FROM_NIGHTLY_RELEASE}" == "latest" ]]; then
|
|
echo "Finding the latest nightly release"
|
|
find_latest_nightly "${NIGHTLY_GCR}" || abort "cannot find the latest nightly release"
|
|
echo "Latest nightly is ${FROM_NIGHTLY_RELEASE}"
|
|
fi
|
|
readonly FROM_NIGHTLY_RELEASE_GCS="gs://knative-nightly/${REPO_NAME}/previous/${FROM_NIGHTLY_RELEASE}"
|
|
gsutil ls -d "${FROM_NIGHTLY_RELEASE_GCS}" > /dev/null \
|
|
|| abort "nightly release ${FROM_NIGHTLY_RELEASE} doesn't exist"
|
|
}
|
|
|
|
# Build a release from an existing nightly one.
|
|
function build_from_nightly_release() {
|
|
banner "Building the release"
|
|
echo "Fetching manifests from nightly"
|
|
local yamls_dir="$(mktemp -d)"
|
|
gsutil -m cp -r "${FROM_NIGHTLY_RELEASE_GCS}/*" "${yamls_dir}" || abort "error fetching manifests"
|
|
# Update references to release GCR
|
|
for yaml in ${yamls_dir}/*.yaml; do
|
|
sed -i -e "s#${NIGHTLY_GCR}#${RELEASE_GCR}#" "${yaml}"
|
|
done
|
|
ARTIFACTS_TO_PUBLISH="$(find "${yamls_dir}" -name '*.yaml' -printf '%p ')"
|
|
echo "Copying nightly images"
|
|
copy_nightly_images_to_release_gcr "${NIGHTLY_GCR}" "${FROM_NIGHTLY_RELEASE}"
|
|
# Create a release branch from the nightly release tag.
|
|
local commit="$(hash_from_tag "${FROM_NIGHTLY_RELEASE}")"
|
|
echo "Creating release branch ${RELEASE_BRANCH} at commit ${commit}"
|
|
git checkout -b "${RELEASE_BRANCH}" "${commit}" || abort "cannot create branch"
|
|
git_push upstream "${RELEASE_BRANCH}" || abort "cannot push branch"
|
|
}
|
|
|
|
# Build a release from source.
|
|
function build_from_source() {
|
|
run_validation_tests ${VALIDATION_TESTS}
|
|
banner "Building the release"
|
|
build_release
|
|
# Do not use `||` above or any error will be swallowed.
|
|
if [[ $? -ne 0 ]]; then
|
|
abort "error building the release"
|
|
fi
|
|
sign_release || abort "error signing the release"
|
|
}
|
|
|
|
function get_images_in_yamls() {
|
|
rm -rf "$IMAGES_REFS_FILE"
|
|
echo "Assembling a list of image refences to sign"
|
|
# shellcheck disable=SC2068
|
|
for file in $@; do
|
|
[[ "${file##*.}" != "yaml" ]] && continue
|
|
echo "Inspecting ${file}"
|
|
while read -r image; do
|
|
echo "$image" >> "$IMAGES_REFS_FILE"
|
|
done < <(grep -oh "\S*${KO_DOCKER_REPO}\S*" "${file}")
|
|
done
|
|
if [[ -f "$IMAGES_REFS_FILE" ]]; then
|
|
sort -uo "$IMAGES_REFS_FILE" "$IMAGES_REFS_FILE" # Remove duplicate entries
|
|
fi
|
|
}
|
|
|
|
# Finds a checksums file within the given list of artifacts (space delimited)
|
|
# Parameters: $n - artifact files
|
|
function find_checksums_file() {
|
|
for arg in "$@"; do
|
|
# kinda dirty hack needed as we pass $ARTIFACTS_TO_PUBLISH in space
|
|
# delimiter variable, which is vulnerable to all sorts of argument quoting
|
|
while read -r file; do
|
|
if [[ "${file}" == *"checksums.txt" ]]; then
|
|
echo "${file}"
|
|
return 0
|
|
fi
|
|
done < <(echo "$arg" | tr ' ' '\n')
|
|
done
|
|
warning "cannot find checksums file"
|
|
}
|
|
|
|
# Build a release from source.
|
|
function sign_release() {
|
|
if (( ! IS_PROW )); then # This function can't be run by devs on their laptops
|
|
return 0
|
|
fi
|
|
get_images_in_yamls "${ARTIFACTS_TO_PUBLISH}"
|
|
local checksums_file
|
|
checksums_file="$(find_checksums_file "${ARTIFACTS_TO_PUBLISH}")"
|
|
|
|
if ! [[ -f "${checksums_file}" ]]; then
|
|
echo '>> No checksums file found, generating one'
|
|
checksums_file="$(mktemp -d)/checksums.txt"
|
|
for file in ${ARTIFACTS_TO_PUBLISH}; do
|
|
pushd "$(dirname "$file")" >/dev/null
|
|
sha256sum "$(basename "$file")" >> "${checksums_file}"
|
|
popd >/dev/null
|
|
done
|
|
ARTIFACTS_TO_PUBLISH="${ARTIFACTS_TO_PUBLISH} ${checksums_file}"
|
|
fi
|
|
|
|
# Notarizing mac binaries needs to be done before cosign as it changes the checksum values
|
|
# of the darwin binaries
|
|
if [ -n "${APPLE_CODESIGN_KEY}" ] && [ -n "${APPLE_CODESIGN_PASSWORD_FILE}" ] && [ -n "${APPLE_NOTARY_API_KEY}" ]; then
|
|
banner "Notarizing macOS Binaries for the release"
|
|
local macos_artifacts
|
|
declare -a macos_artifacts=()
|
|
while read -r file; do
|
|
if echo "$file" | grep -q "darwin"; then
|
|
macos_artifacts+=("${file}")
|
|
rcodesign sign "${file}" --p12-file="${APPLE_CODESIGN_KEY}" \
|
|
--code-signature-flags=runtime \
|
|
--p12-password-file="${APPLE_CODESIGN_PASSWORD_FILE}"
|
|
fi
|
|
done < <(echo "${ARTIFACTS_TO_PUBLISH}" | tr ' ' '\n')
|
|
if [[ -z "${macos_artifacts[*]}" ]]; then
|
|
warning "No macOS binaries found, skipping notarization"
|
|
else
|
|
local zip_file
|
|
zip_file="$(mktemp -d)/files.zip"
|
|
zip "$zip_file" -@ < <(printf "%s\n" "${macos_artifacts[@]}")
|
|
rcodesign notary-submit "$zip_file" --api-key-path="${APPLE_NOTARY_API_KEY}" --wait
|
|
true > "${checksums_file}" # Clear the checksums file
|
|
for file in ${ARTIFACTS_TO_PUBLISH}; do
|
|
if echo "$file" | grep -q "checksums.txt"; then
|
|
continue # Don't checksum the checksums file
|
|
fi
|
|
pushd "$(dirname "$file")" >/dev/null
|
|
sha256sum "$(basename "$file")" >> "${checksums_file}"
|
|
popd >/dev/null
|
|
done
|
|
echo "🧮 Post Notarization Checksum:"
|
|
cat "$checksums_file"
|
|
fi
|
|
fi
|
|
|
|
ID_TOKEN=$(gcloud auth print-identity-token --audiences=sigstore \
|
|
--include-email \
|
|
--impersonate-service-account="${SIGNING_IDENTITY}")
|
|
echo "Signing Images with the identity ${SIGNING_IDENTITY}"
|
|
## Sign the images with cosign
|
|
if [[ -f "$IMAGES_REFS_FILE" ]]; then
|
|
COSIGN_EXPERIMENTAL=1 cosign sign $(cat "$IMAGES_REFS_FILE") \
|
|
--recursive --identity-token="${ID_TOKEN}"
|
|
cp "${IMAGES_REFS_FILE}" "${ARTIFACTS}"
|
|
if [ -n "${ATTEST_IMAGES:-}" ]; then # Temporary Feature Gate
|
|
provenance-generator --clone-log=/logs/clone.json \
|
|
--image-refs="$IMAGES_REFS_FILE" --output=attestation.json
|
|
mkdir -p "${ARTIFACTS}" && cp attestation.json "${ARTIFACTS}"
|
|
COSIGN_EXPERIMENTAL=1 cosign attest $(cat "$IMAGES_REFS_FILE") \
|
|
--recursive --identity-token="${ID_TOKEN}" \
|
|
--predicate=attestation.json --type=slsaprovenance
|
|
fi
|
|
fi
|
|
|
|
echo "Signing checksums with the identity ${SIGNING_IDENTITY}"
|
|
COSIGN_EXPERIMENTAL=1 cosign sign-blob "$checksums_file" \
|
|
--output-signature="${checksums_file}.sig" \
|
|
--output-certificate="${checksums_file}.pem" \
|
|
--identity-token="${ID_TOKEN}"
|
|
ARTIFACTS_TO_PUBLISH="${ARTIFACTS_TO_PUBLISH} ${checksums_file}.sig ${checksums_file}.pem"
|
|
}
|
|
|
|
# Copy tagged images from the nightly GCR to the release GCR, tagging them 'latest'.
|
|
# This is a recursive function, first call must pass $NIGHTLY_GCR as first parameter.
|
|
# Parameters: $1 - GCR to recurse into.
|
|
# $2 - tag to be used to select images to copy.
|
|
function copy_nightly_images_to_release_gcr() {
|
|
for entry in $(gcloud --format="value(name)" container images list --repository="$1"); do
|
|
copy_nightly_images_to_release_gcr "${entry}" "$2"
|
|
# Copy each image with the given nightly tag
|
|
for x in $(gcloud --format="value(tags)" container images list-tags "${entry}" --filter="tags=$2" --limit=1); do
|
|
local path="${entry/${NIGHTLY_GCR}}" # Image "path" (remove GCR part)
|
|
local dst="${RELEASE_GCR}${path}:latest"
|
|
gcloud container images add-tag "${entry}:$2" "${dst}" || abort "error copying image"
|
|
done
|
|
done
|
|
}
|
|
|
|
# Recurse into GCR and find the nightly tag of the first `latest` image found.
|
|
# Parameters: $1 - GCR to recurse into.
|
|
function find_latest_nightly() {
|
|
for entry in $(gcloud --format="value(name)" container images list --repository="$1"); do
|
|
find_latest_nightly "${entry}" && return 0
|
|
for tag in $(gcloud --format="value(tags)" container images list-tags "${entry}" \
|
|
--filter="tags=latest" --limit=1); do
|
|
local tags=( ${tag//,/ } )
|
|
# Skip if more than one nightly tag, as we don't know what's the latest.
|
|
if [[ ${#tags[@]} -eq 2 ]]; then
|
|
local nightly_tag="${tags[@]/latest}" # Remove 'latest' tag
|
|
FROM_NIGHTLY_RELEASE="${nightly_tag// /}" # Remove spaces
|
|
return 0
|
|
fi
|
|
done
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Parses flags and sets environment variables accordingly.
|
|
function parse_flags() {
|
|
local has_gcr_flag=0
|
|
local has_gcs_flag=0
|
|
local has_dir_flag=0
|
|
local is_dot_release=0
|
|
local is_auto_release=0
|
|
|
|
cd "${REPO_ROOT_DIR}"
|
|
while [[ $# -ne 0 ]]; do
|
|
local parameter=$1
|
|
case ${parameter} in
|
|
--skip-tests) SKIP_TESTS=1 ;;
|
|
--tag-release) TAG_RELEASE=1 ;;
|
|
--notag-release) TAG_RELEASE=0 ;;
|
|
--publish) PUBLISH_RELEASE=1 ;;
|
|
--nopublish) PUBLISH_RELEASE=0 ;;
|
|
--dot-release) is_dot_release=1 ;;
|
|
--auto-release) is_auto_release=1 ;;
|
|
--from-latest-nightly) FROM_NIGHTLY_RELEASE=latest ;;
|
|
*)
|
|
[[ $# -ge 2 ]] || abort "missing parameter after $1"
|
|
shift
|
|
case ${parameter} in
|
|
--github-token)
|
|
[[ ! -f "$1" ]] && abort "file $1 doesn't exist"
|
|
local old="$(set +o)"
|
|
# Prevent GITHUB_TOKEN from printing in log
|
|
set +x
|
|
# Remove any trailing newline/space from token
|
|
# ^ (That's not what echo -n does, it just doesn't *add* a newline, but I'm leaving it)
|
|
GITHUB_TOKEN="$(echo -n $(cat "$1"))"
|
|
[[ -n "${GITHUB_TOKEN}" ]] || abort "file $1 is empty"
|
|
eval "$old" # restore xtrace status
|
|
;;
|
|
--release-gcr)
|
|
KO_DOCKER_REPO=$1
|
|
SIGNING_IDENTITY=$RELEASE_SIGNING_IDENTITY
|
|
has_gcr_flag=1
|
|
;;
|
|
--release-gcs)
|
|
RELEASE_GCS_BUCKET=$1
|
|
SIGNING_IDENTITY=$RELEASE_SIGNING_IDENTITY
|
|
RELEASE_DIR=""
|
|
has_gcs_flag=1
|
|
;;
|
|
--release-dir)
|
|
RELEASE_DIR=$1
|
|
RELEASE_GCS_BUCKET=""
|
|
has_dir_flag=1
|
|
;;
|
|
--version)
|
|
[[ $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || abort "version format must be '[0-9].[0-9].[0-9]'"
|
|
RELEASE_VERSION=$1
|
|
;;
|
|
--branch)
|
|
[[ $1 =~ ^release-[0-9]+\.[0-9]+$ ]] || abort "branch name must be 'release-[0-9].[0-9]'"
|
|
RELEASE_BRANCH=$1
|
|
;;
|
|
--release-notes)
|
|
[[ ! -f "$1" ]] && abort "file $1 doesn't exist"
|
|
RELEASE_NOTES=$1
|
|
;;
|
|
--from-nightly)
|
|
[[ $1 =~ ^v[0-9]+-[0-9a-f]+$ ]] || abort "nightly tag must be 'vYYYYMMDD-commithash'"
|
|
FROM_NIGHTLY_RELEASE=$1
|
|
;;
|
|
--apple-codesign-key)
|
|
APPLE_CODESIGN_KEY=$1
|
|
;;
|
|
--apple-codesign-password-file)
|
|
APPLE_CODESIGN_PASSWORD_FILE=$1
|
|
;;
|
|
--apple-notary-api-key)
|
|
APPLE_NOTARY_API_KEY=$1
|
|
;;
|
|
*) abort "unknown option ${parameter}" ;;
|
|
esac
|
|
esac
|
|
shift
|
|
done
|
|
|
|
(( has_gcs_flag )) && (( has_dir_flag )) && abort "cannot have both --release-gcs and --release-dir set simultaneously"
|
|
[[ -n "${RELEASE_GCS_BUCKET}" && -n "${RELEASE_DIR}" ]] && abort "cannot have both GCS and release directory set"
|
|
|
|
# Do auto release unless release is forced
|
|
if (( is_auto_release )); then
|
|
(( is_dot_release )) && abort "cannot have both --dot-release and --auto-release set simultaneously"
|
|
[[ -n "${RELEASE_VERSION}" ]] && abort "cannot have both --version and --auto-release set simultaneously"
|
|
[[ -n "${RELEASE_BRANCH}" ]] && abort "cannot have both --branch and --auto-release set simultaneously"
|
|
[[ -n "${FROM_NIGHTLY_RELEASE}" ]] && abort "cannot have --auto-release with a nightly source"
|
|
setup_upstream
|
|
prepare_auto_release
|
|
fi
|
|
|
|
# Setup source nightly image
|
|
if [[ -n "${FROM_NIGHTLY_RELEASE}" ]]; then
|
|
(( is_dot_release )) && abort "dot releases are built from source"
|
|
[[ -z "${RELEASE_VERSION}" ]] && abort "release version must be specified with --version"
|
|
# TODO(adrcunha): "dot" releases from release branches require releasing nightlies
|
|
# for such branches, which we don't do yet.
|
|
[[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.0$ ]] || abort "version format must be 'X.Y.0'"
|
|
RELEASE_BRANCH="release-$(major_minor_version "${RELEASE_VERSION}")"
|
|
prepare_from_nightly_release
|
|
setup_upstream
|
|
fi
|
|
|
|
# Setup dot releases
|
|
if (( is_dot_release )); then
|
|
setup_upstream
|
|
prepare_dot_release
|
|
fi
|
|
|
|
# Update KO_DOCKER_REPO and KO_FLAGS if we're not publishing.
|
|
if (( ! PUBLISH_RELEASE )); then
|
|
(( has_gcr_flag )) && echo "Not publishing the release, GCR flag is ignored"
|
|
(( has_gcs_flag )) && echo "Not publishing the release, GCS flag is ignored"
|
|
KO_DOCKER_REPO="ko.local"
|
|
RELEASE_GCS_BUCKET=""
|
|
[[ -z "${RELEASE_DIR}" ]] && RELEASE_DIR="${REPO_ROOT_DIR}"
|
|
fi
|
|
|
|
# Set signing identity for cosign, it would already be set to the RELEASE one if the release-gcr/release-gcs flags are set
|
|
if [[ -z "${SIGNING_IDENTITY}" ]]; then
|
|
SIGNING_IDENTITY="${NIGHTLY_SIGNING_IDENTITY}"
|
|
fi
|
|
|
|
[[ -z "${RELEASE_GCS_BUCKET}" && -z "${RELEASE_DIR}" ]] && abort "--release-gcs or --release-dir must be used"
|
|
if [[ -n "${RELEASE_DIR}" ]]; then
|
|
mkdir -p "${RELEASE_DIR}" || abort "cannot create release dir '${RELEASE_DIR}'"
|
|
fi
|
|
|
|
# Get the commit, excluding any tags but keeping the "dirty" flag
|
|
BUILD_COMMIT_HASH="$(git describe --always --dirty --match '^$')"
|
|
[[ -n "${BUILD_COMMIT_HASH}" ]] || abort "error getting the current commit"
|
|
BUILD_YYYYMMDD="$(date -u +%Y%m%d)"
|
|
BUILD_TIMESTAMP="$(date -u '+%Y-%m-%d %H:%M:%S')"
|
|
BUILD_TAG="v${BUILD_YYYYMMDD}-${BUILD_COMMIT_HASH}"
|
|
|
|
(( TAG_RELEASE )) && TAG="${BUILD_TAG}"
|
|
[[ -n "${RELEASE_VERSION}" ]] && TAG="v${RELEASE_VERSION}"
|
|
[[ -n "${RELEASE_VERSION}" && -n "${RELEASE_BRANCH}" ]] && (( PUBLISH_RELEASE )) && PUBLISH_TO_GITHUB=1
|
|
|
|
readonly BUILD_COMMIT_HASH
|
|
readonly BUILD_YYYYMMDD
|
|
readonly BUILD_TIMESTAMP
|
|
readonly BUILD_TAG
|
|
readonly SKIP_TESTS
|
|
readonly TAG_RELEASE
|
|
readonly PUBLISH_RELEASE
|
|
readonly PUBLISH_TO_GITHUB
|
|
readonly TAG
|
|
readonly RELEASE_VERSION
|
|
readonly RELEASE_NOTES
|
|
readonly RELEASE_BRANCH
|
|
readonly RELEASE_GCS_BUCKET
|
|
readonly RELEASE_DIR
|
|
readonly VALIDATION_TESTS
|
|
readonly FROM_NIGHTLY_RELEASE
|
|
readonly SIGNING_IDENTITY
|
|
}
|
|
|
|
# Run tests (unless --skip-tests was passed). Conveniently displays a banner indicating so.
|
|
# Parameters: $1 - executable that runs the tests.
|
|
function run_validation_tests() {
|
|
(( SKIP_TESTS )) && return
|
|
banner "Running release validation tests"
|
|
# Unset KO_DOCKER_REPO and restore it after the tests are finished.
|
|
# This will allow the tests to define their own KO_DOCKER_REPO.
|
|
local old_docker_repo="${KO_DOCKER_REPO}"
|
|
unset KO_DOCKER_REPO
|
|
# Run tests.
|
|
if ! $1; then
|
|
banner "Release validation tests failed, aborting"
|
|
abort "release validation tests failed"
|
|
fi
|
|
export KO_DOCKER_REPO="${old_docker_repo}"
|
|
}
|
|
|
|
# Publishes the generated artifacts to directory, GCS, GitHub, etc.
|
|
# Parameters: $1..$n - files to add to the release.
|
|
function publish_artifacts() {
|
|
(( ! PUBLISH_RELEASE )) && return
|
|
if [[ -n "${RELEASE_DIR}" ]]; then
|
|
cp "${ARTIFACTS_TO_PUBLISH}" "${RELEASE_DIR}" || abort "cannot copy release to '${RELEASE_DIR}'"
|
|
fi
|
|
[[ -n "${RELEASE_GCS_BUCKET}" ]] && publish_to_gcs "${ARTIFACTS_TO_PUBLISH}"
|
|
publish_to_github "${ARTIFACTS_TO_PUBLISH}"
|
|
set_latest_to_highest_semver
|
|
banner "New release published successfully"
|
|
}
|
|
|
|
# Sets the github release with the highest semver to 'latest'
|
|
function set_latest_to_highest_semver() {
|
|
if ! (( PUBLISH_TO_GITHUB )); then
|
|
return 0
|
|
fi
|
|
echo "Setting latest release to highest semver"
|
|
|
|
local last_version release_id # don't combine with assignment else $? will be 0
|
|
|
|
last_version="$(gh_tool release list --json tagName --jq '.[].tagName' | cut -d'-' -f2 | grep '^v[0-9]\+\.[0-9]\+\.[0-9]\+$'| sort -r -V | head -1)"
|
|
if ! [[ $? -eq 0 ]]; then
|
|
abort "cannot list releases"
|
|
fi
|
|
|
|
gh_tool release edit "knative-${last_version}" --latest > /dev/null || abort "error setting $last_version to 'latest'"
|
|
echo "Github release ${last_version} set as 'latest'"
|
|
}
|
|
|
|
# Entry point for a release script.
|
|
function main() {
|
|
parse_flags "$@"
|
|
|
|
# Checkout specific branch, if necessary
|
|
local current_branch
|
|
current_branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
if [[ -n "${RELEASE_BRANCH}" && -z "${FROM_NIGHTLY_RELEASE}" && "${current_branch}" != "${RELEASE_BRANCH}" ]]; then
|
|
setup_upstream
|
|
setup_branch
|
|
# When it runs in Prow, the origin is identical with upstream, and previous
|
|
# fetch already fetched release-* branches, so no need to `checkout -b`
|
|
if (( IS_PROW )); then
|
|
git checkout "${RELEASE_BRANCH}" || abort "cannot checkout branch ${RELEASE_BRANCH}"
|
|
else
|
|
git checkout -b "${RELEASE_BRANCH}" upstream/"${RELEASE_BRANCH}" || abort "cannot checkout branch ${RELEASE_BRANCH}"
|
|
fi
|
|
# HACK HACK HACK
|
|
# Rerun the release script from the release branch. Fixes https://github.com/knative/test-infra/issues/1262
|
|
./hack/release.sh "$@"
|
|
exit "$?"
|
|
fi
|
|
|
|
function_exists build_release || abort "function 'build_release()' not defined"
|
|
[[ -x ${VALIDATION_TESTS} ]] || abort "test script '${VALIDATION_TESTS}' doesn't exist"
|
|
|
|
banner "Environment variables"
|
|
env
|
|
# Log what will be done and where.
|
|
banner "Release configuration"
|
|
if which gcloud &>/dev/null ; then
|
|
echo "- gcloud user: $(gcloud config get-value core/account)"
|
|
fi
|
|
echo "- Go path: ${GOPATH}"
|
|
echo "- Repository root: ${REPO_ROOT_DIR}"
|
|
echo "- Destination GCR: ${KO_DOCKER_REPO}"
|
|
(( SKIP_TESTS )) && echo "- Tests will NOT be run" || echo "- Tests will be run"
|
|
if (( TAG_RELEASE )); then
|
|
echo "- Artifacts will be tagged '${TAG}'"
|
|
# We want to add git tags to the container images built by ko
|
|
KO_FLAGS+=" --tags=latest,${TAG}"
|
|
else
|
|
echo "- Artifacts WILL NOT be tagged"
|
|
fi
|
|
if (( PUBLISH_RELEASE )); then
|
|
local dst="${RELEASE_DIR}"
|
|
[[ -z "${dst}" ]] && dst="${RELEASE_GCS_BUCKET}"
|
|
echo "- Release WILL BE published to '${dst}'"
|
|
else
|
|
echo "- Release will not be published"
|
|
fi
|
|
if (( PUBLISH_TO_GITHUB )); then
|
|
echo "- Release WILL BE published to GitHub"
|
|
fi
|
|
if [[ -n "${FROM_NIGHTLY_RELEASE}" ]]; then
|
|
echo "- Release will be A COPY OF '${FROM_NIGHTLY_RELEASE}' nightly"
|
|
else
|
|
echo "- Release will be BUILT FROM SOURCE"
|
|
[[ -n "${RELEASE_BRANCH}" ]] && echo "- Release will be built from branch '${RELEASE_BRANCH}'"
|
|
fi
|
|
[[ -n "${RELEASE_NOTES}" ]] && echo "- Release notes are generated from '${RELEASE_NOTES}'"
|
|
|
|
if [[ -n "${FROM_NIGHTLY_RELEASE}" ]]; then
|
|
build_from_nightly_release
|
|
else
|
|
set -e -o pipefail
|
|
build_from_source
|
|
set +e +o pipefail
|
|
fi
|
|
[[ -z "${ARTIFACTS_TO_PUBLISH}" ]] && abort "no artifacts were generated"
|
|
# Ensure no empty file will be published.
|
|
for artifact in ${ARTIFACTS_TO_PUBLISH}; do
|
|
[[ -s ${artifact} ]] || abort "Artifact ${artifact} is empty"
|
|
done
|
|
echo "New release built successfully"
|
|
publish_artifacts
|
|
}
|
|
|
|
# Publishes a new release on GitHub, also git tagging it (unless this is not a versioned release).
|
|
# Parameters: $1..$n - files to add to the release.
|
|
function publish_to_github() {
|
|
(( PUBLISH_TO_GITHUB )) || return 0
|
|
local title="${TAG}"
|
|
local attachments=()
|
|
local description="$(mktemp)"
|
|
local attachments_dir="$(mktemp -d)"
|
|
local commitish=""
|
|
local target_branch=""
|
|
local github_tag="knative-${TAG}"
|
|
|
|
# Copy files to a separate dir
|
|
# shellcheck disable=SC2068
|
|
for artifact in $@; do
|
|
cp ${artifact} "${attachments_dir}"/
|
|
attachments+=("${artifact}#$(basename ${artifact})")
|
|
done
|
|
echo -e "${title}\n" > "${description}"
|
|
if [[ -n "${RELEASE_NOTES}" ]]; then
|
|
cat "${RELEASE_NOTES}" >> "${description}"
|
|
fi
|
|
|
|
# Include a tag for the go module version
|
|
#
|
|
# v1.0.0 = v0.27.0
|
|
# v1.0.1 = v0.27.1
|
|
# v1.1.1 = v0.28.1
|
|
#
|
|
# See: https://github.com/knative/hack/pull/97
|
|
if [[ "$TAG" == "v1"* ]]; then
|
|
local release_minor=$(minor_version $TAG)
|
|
local go_module_version="v0.$(( release_minor + 27 )).$(patch_version $TAG)"
|
|
git tag -a "${go_module_version}" -m "${title}"
|
|
git_push tag "${go_module_version}"
|
|
else
|
|
# Pre-1.0 - use the tag as the release tag
|
|
github_tag="${TAG}"
|
|
fi
|
|
|
|
git tag -a "${github_tag}" -m "${title}"
|
|
git_push tag "${github_tag}"
|
|
|
|
[[ -n "${RELEASE_BRANCH}" ]] && target_branch="--target=${RELEASE_BRANCH}"
|
|
for i in {2..0}; do
|
|
# shellcheck disable=SC2068
|
|
gh_tool release create \
|
|
"${github_tag}" \
|
|
--title "${title}" \
|
|
--notes-file "${description}" \
|
|
"${target_branch}" \
|
|
${attachments[@]} && return 0
|
|
|
|
if [[ "${i}" -gt 0 ]]; then
|
|
echo "Error publishing the release, retrying in 15s..."
|
|
sleep 15
|
|
fi
|
|
done
|
|
abort "Cannot publish release to GitHub"
|
|
}
|