diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e2915b7..c75d729a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,10 @@ jobs: docker_layer_caching: false - run: name: Install Dependencies - command: apk add make bash + command: apk add make bash curl jq + - run: + name: Update curl + command: apk --upgrade --no-cache add curl - run: name: Build Docker images command: make build diff --git a/Makefile b/Makefile index c6470a86..5d112b13 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,27 @@ ST2_VERSION ?= 3.4dev DOCKER_TAG ?= ${ST2_VERSION} +RELEASE_TAG_REGEX := [^dev]$$ SHELL := /bin/bash +# supported values of the TAG_UPDATE_FLAG +# 0 = no additional tags to be set +# 1 = add the major.minor tag +# 2 = add the tags major and major.minor +# 3 = add the tags major, major.minor and latest +TAG_UPDATE_FLAG := $(shell ./determine_needed_tags.sh st2 ${ST2_VERSION}) + +ifneq ($(shell echo ${ST2_VERSION} | grep -E "${RELEASE_TAG_REGEX}"), ) + RELEASE_VERSION := true + MAJOR := $(word 1, $(subst ., ,${ST2_VERSION})) + MINOR := $(word 2, $(subst ., ,${ST2_VERSION})) + PATCH := $(word 3, $(subst ., ,${ST2_VERSION})) +else + RELEASE_VERSION := false +endif + # Build all required images (st2 base image plus st2 components) .PHONY: build -build: +build: verify_tag_update_flag @docker build \ --pull \ --no-cache \ @@ -20,9 +37,30 @@ build: $$component/; \ echo -e "\033[32mSuccessfully built \033[1mstackstorm/$$component:${DOCKER_TAG}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ done +ifeq ($(RELEASE_VERSION), true) +ifeq ($(TAG_UPDATE_FLAG), 1) + for image in st2 st2*; do \ + docker tag stackstorm/$$image:${DOCKER_TAG} stackstorm/$$image:${MAJOR}.${MINOR}; \ + echo -e "\033[32mSuccessfully tagged \033[1mstackstorm/$$image:${DOCKER_TAG}\033[0m\033[32m with \033[1mstackstorm/$$image:${MAJOR}.${MINOR}\033[0m"; \ + done +else ifeq ($(TAG_UPDATE_FLAG), 2) + for image in st2 st2*; do \ + docker tag stackstorm/$$image:${DOCKER_TAG} stackstorm/$$image:${MAJOR}; \ + docker tag stackstorm/$$image:${DOCKER_TAG} stackstorm/$$image:${MAJOR}.${MINOR}; \ + echo -e "\033[32mSuccessfully tagged \033[1mstackstorm/$$image:${DOCKER_TAG}\033[0m\033[32m with \033[1mstackstorm/$$image:${MAJOR}\033[0m\033[32m and \033[1mstackstorm/$$image:${MAJOR}.${MINOR}\033[0m"; \ + done +else ifeq ($(TAG_UPDATE_FLAG), 3) + for image in st2 st2*; do \ + docker tag stackstorm/$$image:${DOCKER_TAG} stackstorm/$$image:${MAJOR}; \ + docker tag stackstorm/$$image:${DOCKER_TAG} stackstorm/$$image:${MAJOR}.${MINOR}; \ + docker tag stackstorm/$$image:${DOCKER_TAG} stackstorm/$$image:latest; \ + echo -e "\033[32mSuccessfully tagged \033[1mstackstorm/$$image:${DOCKER_TAG}\033[0m\033[32m with \033[1mstackstorm/$$image:${MAJOR}\033[0m\033[32m, \033[1mstackstorm/$$image:${MAJOR}.${MINOR}\033[32m and \033[1mstackstorm/$$image:latest\033[0m"; \ + done +endif +endif .PHONY: push -push: +push: verify_tag_update_flag docker push stackstorm/st2:${DOCKER_TAG}; @echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/st2:${DOCKER_TAG}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; @set -e; \ @@ -30,3 +68,36 @@ push: docker push stackstorm/$$component:${DOCKER_TAG}; \ echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$component:${DOCKER_TAG}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ done +ifeq ($(RELEASE_VERSION), true) +ifeq ($(TAG_UPDATE_FLAG), 1) + for image in st2 st2*; do \ + docker push stackstorm/$$image:${MAJOR}.${MINOR}; \ + echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$image:${MAJOR}.${MINOR}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ + done +else ifeq ($(TAG_UPDATE_FLAG), 2) + for image in st2 st2*; do \ + docker push stackstorm/$$image:${MAJOR}; \ + echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$image:${MAJOR}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ + docker push stackstorm/$$image:${MAJOR}.${MINOR}; \ + echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$image:${MAJOR}.${MINOR}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ + done +else ifeq ($(TAG_UPDATE_FLAG), 3) + for image in st2 st2*; do \ + docker push stackstorm/$$image:${MAJOR}; \ + echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$image:${MAJOR}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ + docker push stackstorm/$$image:${MAJOR}.${MINOR}; \ + echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$image:${MAJOR}.${MINOR}\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ + docker push stackstorm/$$image:latest; \ + echo -e "\033[32mSuccessfully pushed \033[1mstackstorm/$$image:latest\033[0m\033[32m Docker image for StackStorm version \033[1m${ST2_VERSION}\033[0m"; \ + done +endif +endif + +verify_tag_update_flag: +ifeq ($(RELEASE_VERSION), true) +ifneq ($(shell echo "${TAG_UPDATE_FLAG}" | grep -E "Error:"),) + @echo -e "Failed to identify the tags to be set." + @echo -e "\033[31mNo images were tagged due to an error when determining the correct tags: ${TAG_UPDATE_FLAG}\033[0m" + exit 1 +endif +endif diff --git a/determine_needed_tags.sh b/determine_needed_tags.sh new file mode 100755 index 00000000..a8917389 --- /dev/null +++ b/determine_needed_tags.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash + +# the first argument must be the name of the container, i.e. st2, st2actionrunner, st2stream +component=$1 +# the 2nd argument is the version of the current build and expects at least major.miinor to be provided +# i.e. 3.3.0, 3.3, 2.10.5 +build_version=$2 + +showHelp() { + echo "Use this script to determine the right tags to be attached to your new image." + echo + echo "Syntax: $0 st2component image_version" + echo "st2component = an valid image of an existing stackstorm component (i.e. st2, st2api, st2stream)" + echo "image_version = the st2 release used for your current build (i.e. 3.3.0)" + echo + echo "Possible output:" + echo "0 = no additional tags to be updated" + echo "1 = update the major.minor tag (i.e. 3.3)" + echo "2 = update the major and the major.minor tag (i.e. 3 and 3.3)" + echo "3 = update the major and the major.minor tag (i.e. 3 and 3.3) as well as the latest tag" + echo + echo "Run $0 -h|--help to show this usage information." +} + +case $1 in + -h|--help) + showHelp + exit + ;; +esac + +if [ "$#" -ne 2 ]; then + echo "Error: Missing or unexpected number of positional arguments. Expected: 2" + showHelp + exit 1 +fi + +# check for dependencies +missing_packages="" +for dep in curl jq; do + if ! which $dep > /dev/null; then + missing_packages+=" $dep" + fi +done + +if [ ! -z "${missing_packages}" ]; then + echo "Error: Requirement(s) not satisfied: ${missing_packages}" + exit 1 +fi + +if [[ ${build_version} =~ ^([0-9]+)\.([0-9]+).?([0-9]*)$ ]]; then + build_major=${BASH_REMATCH[1]} + build_minor=${BASH_REMATCH[2]} + build_patch=${BASH_REMATCH[3]} +else + echo "Error: The provided version ${build_version} does not have the expected format." + exit 1 +fi + +if [ -z ${build_patch} ]; then + build_patch=0 +fi + +tag_update_flag=0 +# possible values of the tag_update flag: +# 0 = no additional tags to be set +# (this applies just in case of builds for older releases while a newer minor or minor.patch is already available) +# 1 = add the major.minor tag +# (this applies, if the build is for is just for a new patch version) +# 2 = add the tags major and major.minor +# (this applies if the build is for a version equal to or greater than the latest minor of a major) +# 3 = add the tags latest in addition to the tags from #2 +# (this applies if the build with the major, minor and patch version being equal to or greater than the currently latest version +# or if the build is for a completely new st2 component) + +# check if there are already images for the given component available at dockerhub +dockerhub_registry_status_code=$(curl -sfL -o /dev/null -w "%{http_code}" https://registry.hub.docker.com/v1/repositories/stackstorm/${component}/tags) + +if [ ${dockerhub_registry_status_code} -eq 404 ]; then + # there is no repository stackstorm/${component} available at dockerhub -> this build is for a new st2 component + tag_update_flag=3 + exit +elif [ ${dockerhub_registry_status_code} -ne 200 ]; then + echo "Error: Unexpected HTTP statuscode for https://registry.hub.docker.com/v1/repositories/stackstorm/${component}/tags: ${dockerhub_registry_status_code}" + exit 1 +fi + +# dockerhub lists the tags in ascending order. 1st object = lowest tag; last object = highest tag or latest +docker_tags_json=$(curl -s https://registry.hub.docker.com/v1/repositories/stackstorm/${component}/tags) +readarray -t available_releases < <(echo $docker_tags_json | jq -r '.[] | select((.name | endswith("dev") | not) and (.name=="latest" | not)).name' | sort) + +# sort returns the list with the highest tag or "latest" as last item +# so change the value below to -2 when introducing the tag latest to get i.e. 3.3.0 instead of latest +latest_release=${available_releases[-1]} +latest_release_array=(${latest_release//\./ }) + +latest_major=${latest_release_array[0]} +latest_minor=${latest_release_array[1]} + +# validate the value stored as latest_release +if [[ ! ${latest_release} =~ ^([0-9]+)\.([0-9]+).?([0-9]*)$ ]]; then + echo "Error: Unexpected error. The latest release ${latest_release} does not match the expected format." +fi + +if [ ${build_version} == ${latest_release} ]; then + # building a release of the latest st2 version + tag_update_flag=3 +else + # building a release for a st2 version that does not match the latest version + if [ ${build_major} -le ${latest_major} ]; then + # building a release for an older major + readarray -t build_version_major_minor_matching_releases < <(echo $docker_tags_json | jq -r '.[] | select(.name | endswith("dev") | not) | select(.name | startswith("'"${build_major}.${build_minor}"'")).name') + if [ ${#build_version_major_minor_matching_releases[@]} -ge 1 ]; then + # at least one version matching the current builds major and minor version is available + latest_build_version_major_minor_matching_release=${build_version_major_minor_matching_releases[-1]} + latest_build_version_major_minor_matching_release_array=(${latest_build_version_major_minor_matching_release//\./ }) + latest_build_version_major_minor_matching_major=${latest_build_version_major_minor_matching_release_array[0]} + latest_build_version_major_minor_matching_minor=${latest_build_version_major_minor_matching_release_array[1]} + latest_build_version_major_minor_matching_patch=${latest_build_version_major_minor_matching_release_array[2]} + if [ ${build_minor} -eq ${latest_minor} ] && \ + [ ${build_minor} -eq ${latest_build_version_major_minor_matching_minor} ] && \ + [ ${build_patch} -ge ${latest_build_version_major_minor_matching_patch} ]; then + # building a release for a new or updated patch version of the current major.minor version + tag_update_flag=3 + elif [ ${build_minor} -eq ${latest_build_version_major_minor_matching_minor} ] && \ + [ ${build_patch} -ge ${latest_build_version_major_minor_matching_patch} ]; then + # building a release for a new or updated patch version of an old major.minor version + readarray -t build_version_major_matching_releases < <(echo $docker_tags_json | jq -r '.[] | select(.name | endswith("dev") | not) | select(.name | startswith("'"${build_major}"'")).name') + latest_build_version_major_matching_release=${build_version_major_matching_releases[-1]} + latest_build_version_major_matching_release_array=(${latest_build_version_major_matching_release//\./ }) + latest_build_version_major_matching_minor=${latest_build_version_major_matching_release_array[1]} + if [ ${build_minor} -ge ${latest_build_version_major_matching_minor} ]; then + # building a release for a new or updated patch version of the latest or a new minor version of the major release + tag_update_flag=2 + else + # building a release for an older minor version of the major release + tag_update_flag=1 + fi + elif [ ${build_minor} -lt ${latest_minor} ] && [ ${build_patch} -ge ${latest_build_version_major_minor_matching_patch} ]; then + # building a patch release for an older minor version + tag_update_flag=1 + fi + elif [ ${build_minor} -gt ${latest_minor} ]; then + # building a release of a new minor version of the current or an old major version + if [ ${build_major} -eq ${latest_major} ]; then + # building a release of a new minor for the latest major version + tag_update_flag=3 + else + # building a release for a new minor of an old major version + tag_update_flag=2 + fi + #else + # # building a release for an old, unreleased major version of st2 + # tag_update_flag=2 + fi + elif [ ${build_major} -gt ${latest_major} ]; then + # building a release for a new major version + tag_update_flag=3 + fi +fi + +echo $tag_update_flag