From 550fec426152ef0a7f3d495d6388ae8fb5b2445b Mon Sep 17 00:00:00 2001 From: Robert Fratto Date: Tue, 19 Mar 2024 13:42:21 -0400 Subject: [PATCH] Implement standardized versioning (#44) * build: normalize version strings Normalize version strings so they are consistently reported as vVERSION, where VERSION is a valid semantic version string. Custom versions that are not valid semvers are returned as is, and empty strings are returned as v0.0.0. * tools/gen-versioned-files: move version file to root as VERSION * docs: update developer docs for managing VERSION file * tools/image-tag: report consistent build information This commit updates tools/image-tag to always report consistent build information, with escape hatches to force the reported version: 1. If RELEASE_TAG is set (which happens via Drone pipelines), then the script emits the value of the RELEASE_TAG environment variable. 2. If a build is being performed against a Git tag, then the script emits the value of that Git tag. 3. Finally, if neither of the above are true, the version from the VERSION file is used, followed by the prerelease being set to `devel` and the short SHA added as build metadata. Provided healthy workflwos where RELEASE_TAG and Git tags are always semantic versions, this guarantees that versions reported by builds are consistent and can be parsed properly. --- VERSION | 23 +++++++++++++ .../release/1-create-release-branch.md | 4 +++ .../release/3-update-version-in-code.md | 26 ++++++++------- docs/sources/_index.md | 2 +- internal/build/build.go | 21 ++++++++++++ internal/build/build_test.go | 33 +++++++++++++++++++ tools/gen-versioned-files/agent-version.txt | 1 - .../gen-versioned-files.sh | 14 ++++---- tools/image-tag | 33 +++++++++++++------ 9 files changed, 126 insertions(+), 31 deletions(-) create mode 100644 VERSION create mode 100644 internal/build/build_test.go delete mode 100644 tools/gen-versioned-files/agent-version.txt diff --git a/VERSION b/VERSION new file mode 100644 index 0000000000..18bdb6741c --- /dev/null +++ b/VERSION @@ -0,0 +1,23 @@ +# This file is used to determine the semantic version for the build of Grafana +# Alloy being constructed is. +# +# For builds produced against release branches, the major and minor version +# indicated in this file match the release branch. For example, for the branch +# release/v1.0, this file will report v1.0.0. The patch version indicated by this +# file syncs with the patch release being planned. For example, if a v1.0.1 +# patch release is planned, this file will report v1.0.1 before that release is +# produced. +# +# For builds produced against main branches, the major and minor version +# reported by this file matches the next minor or major version to be released. +# For example, if v1.0.0 was just released, this file (in the main branch) will +# report v1.1.0, the next release planned. +# +# The string in this file MUST be a valid semantic version prefixed with "v", +# without any pre-release or build information. +# +# This file is ignored when building binaries from a Git tag. +# +# Lines starting with "#" and blank lines are ignored. + +v1.0.0 diff --git a/docs/developer/release/1-create-release-branch.md b/docs/developer/release/1-create-release-branch.md index 5e7a3c6508..b34e8806ef 100644 --- a/docs/developer/release/1-create-release-branch.md +++ b/docs/developer/release/1-create-release-branch.md @@ -24,3 +24,7 @@ Patch Releases for that major pr minor version of the agent. > **NOTE**: Don't create any other branches that are prefixed with `release` when creating PRs or those branches will collide with our automated release build publish rules. + +3. Open a PR against `main` to update the VERSION file at the root of the + repository to the next minor release planned. For example, if you have just + created `release/v1.0`, then VERSION should be updated to `v1.1.0`. diff --git a/docs/developer/release/3-update-version-in-code.md b/docs/developer/release/3-update-version-in-code.md index 6384973cc4..7a3c9cdc3b 100644 --- a/docs/developer/release/3-update-version-in-code.md +++ b/docs/developer/release/3-update-version-in-code.md @@ -22,18 +22,6 @@ The project must be updated to reference the upcoming release tag whenever a new 2. Move the unreleased changes we want to add to the release branch from `Main (unreleased)` to `VERSION (YYYY-MM-DD)`. - 3. Update appropriate places in the codebase that have the previous version with the new version determined above. - - First update `tools/gen-versioned-files/agent-version.txt` with the new `VERSION` and run: - - ``` - make generate-versioned-files - ``` - - Next, commit the changes (including those to `tools/gen-versioned-files/agent-version.txt`, as a workflow will use this version to ensure that the templates and generated files are in sync). - - * Do **not** update the `operations/helm` directory. It is updated independently from Agent releases. - 3. Create a PR to merge to main (must be merged before continuing). - Release Candidate example PR [here](https://github.com/grafana/agent/pull/3065) @@ -50,6 +38,20 @@ The project must be updated to reference the upcoming release tag whenever a new Delete the `Main (unreleased)` header and anything underneath it as part of the cherry-pick. Alternatively, do it after the cherry-pick is completed. +6. **If you are creating a patch release,** ensure that the file called `VERSION` in your branch matches the version being published, without any release candidate or build information: + + > **NOTE**: Only perform this step for patch releases, and make sure that + > the change is not pushed to the main branch. + + After updating `VERSION`, run: + + ```bash + make generate-versioned-files + ``` + + Next, commit the changes (including those to `VERSION`, as a workflow will use this version to ensure that the templates and generated files are in sync). + + 6. Create a PR to merge to `release/VERSION_PREFIX` (must be merged before continuing). - Release Candidate example PR [here](https://github.com/grafana/agent/pull/3066) diff --git a/docs/sources/_index.md b/docs/sources/_index.md index a751d5bbed..e13b013f8f 100644 --- a/docs/sources/_index.md +++ b/docs/sources/_index.md @@ -4,7 +4,7 @@ title: Grafana Alloy description: Grafana Alloy is a flexible, performant, vendor-neutral, telemetry collector weight: 350 cascade: - ALLOY_RELEASE: $ALLOY_VERSION + ALLOY_RELEASE: v1.0.0 OTEL_VERSION: v0.87.0 PRODUCT_NAME: Grafana Alloy PRODUCT_ROOT_NAME: Alloy diff --git a/internal/build/build.go b/internal/build/build.go index 3e3b1bce12..43201285c4 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -1,6 +1,9 @@ package build import ( + "strings" + + "github.com/blang/semver/v4" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/version" ) @@ -18,9 +21,27 @@ var ( ) func init() { + Version = normalizeVersion(Version) injectVersion() } +// normalizeVersion normalizes the version string to always contain a "v" +// prefix. If version cannot be parsed as a semantic version, version is returned unmodified. +// +// if version is empty, normalizeVersion returns "v0.0.0". +func normalizeVersion(version string) string { + version = strings.TrimSpace(version) + if version == "" { + return "v0.0.0" + } + + parsed, err := semver.ParseTolerant(version) + if err != nil { + return version + } + return "v" + parsed.String() +} + func injectVersion() { version.Version = Version version.Revision = Revision diff --git a/internal/build/build_test.go b/internal/build/build_test.go new file mode 100644 index 0000000000..9ba75abacc --- /dev/null +++ b/internal/build/build_test.go @@ -0,0 +1,33 @@ +package build + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_normalizeVersion(t *testing.T) { + tt := []struct { + input string + expect string + }{ + {"", "v0.0.0"}, + {"v1.2.3", "v1.2.3"}, + {"1.2.3", "v1.2.3"}, + {"1.2.3+SHA", "v1.2.3+SHA"}, + {"v1.2.3+SHA", "v1.2.3+SHA"}, + {"1.2.3-rc.1", "v1.2.3-rc.1"}, + {"v1.2.3-rc.1", "v1.2.3-rc.1"}, + {"1.2.3-rc.1+SHA", "v1.2.3-rc.1+SHA"}, + {"v1.2.3-rc.1+SHA", "v1.2.3-rc.1+SHA"}, + {"not_semver", "not_semver"}, + } + + for _, tc := range tt { + actual := normalizeVersion(tc.input) + assert.Equal(t, tc.expect, actual, + "Expected %q to normalize to %q, got %q", + tc.input, tc.expect, actual, + ) + } +} diff --git a/tools/gen-versioned-files/agent-version.txt b/tools/gen-versioned-files/agent-version.txt deleted file mode 100644 index 6b97562039..0000000000 --- a/tools/gen-versioned-files/agent-version.txt +++ /dev/null @@ -1 +0,0 @@ -v0.40.3 diff --git a/tools/gen-versioned-files/gen-versioned-files.sh b/tools/gen-versioned-files/gen-versioned-files.sh index fff14df68d..140fa6fe12 100755 --- a/tools/gen-versioned-files/gen-versioned-files.sh +++ b/tools/gen-versioned-files/gen-versioned-files.sh @@ -1,20 +1,20 @@ #!/bin/sh -AGENT_VERSION=$(cat ./tools/gen-versioned-files/agent-version.txt | tr -d '\n') +ALLOY_VERSION=$(sed -e '/^#/d' -e '/^$/d' VERSION | tr -d '\n') -if [ -z "$AGENT_VERSION" ]; then - echo "AGENT_VERSION can't be found. Are you running this from the repo root?" +if [ -z "$ALLOY_VERSION" ]; then + echo "ALLOY_VERSION can't be found. Are you running this from the repo root?" exit 1 fi -versionMatcher='^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$' +versionMatcher='^v[0-9]+\.[0-9]+\.[0-9]$' -if ! echo "$AGENT_VERSION" | grep -Eq "$versionMatcher"; then - echo "AGENT_VERSION env var is not in the correct format. It should be in the format of vX.Y.Z or vX.Y.Z-rc.N" +if ! echo "$ALLOY_VERSION" | grep -Eq "$versionMatcher"; then + echo "ALLOY_VERSION env var is not in the correct format. It should be in the format of vX.Y.Z" exit 1 fi templates=$(find . -type f -name "*.t" -not -path "./.git/*") for template in $templates; do echo "Generating ${template%.t}" - sed -e "s/\$AGENT_VERSION/$AGENT_VERSION/g" < "$template" > "${template%.t}" + sed -e "s/\$ALLOY_VERSION/$ALLOY_VERSION/g" < "$template" > "${template%.t}" done diff --git a/tools/image-tag b/tools/image-tag index 389a1bd7e3..e85affbf59 100755 --- a/tools/image-tag +++ b/tools/image-tag @@ -1,21 +1,34 @@ #!/usr/bin/env bash +# +# image-tag determines which version to embed into a built image. +# +# It prefers the following in precedence order: +# +# 1. RELEASE_TAG environment variable +# 2. The Git tag of the current commit (if any) +# 3. The version in the VERSION file, suffixed with -devel plus build +# information. set -o errexit set -o pipefail +VERSION=$(sed -e '/^#/d' -e '/^$/d' VERSION | tr -d '\n') +DETECTED_TAG=$(git describe --match 'v*' --exact-match 2>/dev/null || echo -n "") + if [ ! -z "${RELEASE_TAG}" ]; then echo ${RELEASE_TAG} exit 0 +elif [ ! -z "${DETECTED_TAG}" ]; then + echo ${DETECTED_TAG} + exit 0 fi set -o nounset -WIP=$(git diff --quiet || echo '-WIP') -BRANCH=$(git rev-parse --abbrev-ref HEAD | sed 's#/#-#g') - -# When 7 chars are not enough to be unique, git automatically uses more. -# We are forcing to 7 here, as we are doing for grafana/grafana as well. -SHA=$(git rev-parse --short=7 HEAD | head -c7) - -# If this is a tag, use it, otherwise use - -TAG=$(git describe --exact-match 2> /dev/null || echo "${BRANCH}-${SHA}") -echo ${TAG}${WIP} +if [[ -z $(git status -s) ]]; then + # There are no changes; report version as VERSION-devel+SHA. + SHA=$(git rev-parse --short HEAD) + echo ${VERSION}-devel+${SHA} +else + # Git is dirty; tag as VERSION-devel+wip. + echo ${VERSION}-devel+wip +fi