From fc8a2da5184af97b1bc6c87e8dda4e4186abedad Mon Sep 17 00:00:00 2001 From: Michael Zalimeni Date: Tue, 9 Jul 2024 12:06:42 -0400 Subject: [PATCH] Backport of [NET-5622] build: consolidate Envoy version management to release/1.19.x (#21292) build: consolidate Envoy version management Manual backport of hashicorp/consul#21245 into release/1.19.x. Co-authored-by: sarahalsmiller <100602640+sarahalsmiller@users.noreply.github.com> --- .../workflows/reusable-get-envoy-versions.yml | 71 +++++++++++ .github/workflows/reusable-get-go-version.yml | 9 ++ .github/workflows/test-integrations.yml | 37 +++--- Makefile | 3 +- command/connect/envoy/envoy.go | 4 +- command/connect/envoy/envoy_test.go | 2 +- envoyextensions/xdscommon/ENVOY_VERSIONS | 14 ++ envoyextensions/xdscommon/envoy_versioning.go | 2 +- .../xdscommon/envoy_versioning_test.go | 120 ++++++------------ envoyextensions/xdscommon/proxysupport.go | 92 +++++++++++--- .../xdscommon/proxysupport_test.go | 51 ++++++++ test/integration/connect/envoy/helpers.bash | 10 -- test/integration/connect/envoy/run-tests.sh | 5 +- 13 files changed, 289 insertions(+), 131 deletions(-) create mode 100644 .github/workflows/reusable-get-envoy-versions.yml create mode 100644 envoyextensions/xdscommon/ENVOY_VERSIONS diff --git a/.github/workflows/reusable-get-envoy-versions.yml b/.github/workflows/reusable-get-envoy-versions.yml new file mode 100644 index 000000000000..1d40587cd773 --- /dev/null +++ b/.github/workflows/reusable-get-envoy-versions.yml @@ -0,0 +1,71 @@ +name: get-envoy-versions + +# Reads the canonical ENVOY_VERSIONS file for either the current branch or a specified version of Consul, +# and returns both the max and all supported Envoy versions. + +on: + workflow_call: + inputs: + ref: + description: | + The Consul ref/branch (e.g. release/1.18.x) for which to determine supported Envoy versions. + If not provided, the default actions/checkout value (current ref) is used. + type: string + outputs: + max-envoy-version: + description: The max supported Envoy version for the specified Consul version + value: ${{ jobs.get-envoy-versions.outputs.max-envoy-version }} + envoy-versions: + description: | + All supported Envoy versions for the specified Consul version (formatted as multiline string with one version + per line, in descending order) + value: ${{ jobs.get-envoy-versions.outputs.envoy-versions }} + envoy-versions-json: + description: | + All supported Envoy versions for the specified Consul version (formatted as JSON array) + value: ${{ jobs.get-envoy-versions.outputs.envoy-versions-json }} + +jobs: + get-envoy-versions: + name: "Determine supported Envoy versions" + runs-on: ubuntu-latest + outputs: + max-envoy-version: ${{ steps.get-envoy-versions.outputs.max-envoy-version }} + envoy-versions: ${{ steps.get-envoy-versions.outputs.envoy-versions }} + envoy-versions-json: ${{ steps.get-envoy-versions.outputs.envoy-versions-json }} + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + # If not set, will default to current branch. + ref: ${{ inputs.ref }} + - name: Determine Envoy versions + id: get-envoy-versions + # Note that this script assumes that the ENVOY_VERSIONS file is in the envoyextensions/xdscommon directory. + # If in the future this file moves between branches, we could introduce a workflow input for the path that + # defaults to the new value, and manually configure the old value as needed. + run: | + MAX_ENVOY_VERSION=$(cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr | head -n 1) + ENVOY_VERSIONS=$(cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr) + ENVOY_VERSIONS_JSON=$(echo -n '[' && echo "${ENVOY_VERSIONS}" | awk '{printf "\"%s\",", $0}' | sed 's/,$//' && echo -n ']') + + # Loop through each line of ENVOY_VERSIONS and compare it to the regex + while IFS= read -r version; do + if ! [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo 'Invalid version in ENVOY_VERSIONS: '$version' does not match the pattern ^[0-9]+\.[0-9]+\.[0-9]+$' + exit 1 + fi + done <<< "$ENVOY_VERSIONS" + if ! [[ $MAX_ENVOY_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo 'Invalid MAX_ENVOY_VERSION: '$MAX_ENVOY_VERSION' does not match the pattern ^[0-9]+\.[0-9]+\.[0-9]+$' + exit 1 + fi + + echo "Supported Envoy versions:" + echo "${ENVOY_VERSIONS}" + echo "envoy-versions<> $GITHUB_OUTPUT + echo "${ENVOY_VERSIONS}" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "Supported Envoy versions JSON: ${ENVOY_VERSIONS_JSON}" + echo "envoy-versions-json=${ENVOY_VERSIONS_JSON}" >> $GITHUB_OUTPUT + echo "Max supported Envoy version: ${MAX_ENVOY_VERSION}" + echo "max-envoy-version=${MAX_ENVOY_VERSION}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/reusable-get-go-version.yml b/.github/workflows/reusable-get-go-version.yml index 2fac43b9dac3..91e870c73cc6 100644 --- a/.github/workflows/reusable-get-go-version.yml +++ b/.github/workflows/reusable-get-go-version.yml @@ -2,6 +2,12 @@ name: get-go-version on: workflow_call: + inputs: + ref: + description: | + The Consul ref/branch (e.g. release/1.18.x) for which to determine the Go version. + If not provided, the default actions/checkout value (current ref) is used. + type: string outputs: go-version: description: "The Go version detected by this workflow" @@ -19,6 +25,9 @@ jobs: go-version-previous: ${{ steps.get-go-version.outputs.go-version-previous }} steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + # If not set, will default to current branch. + ref: ${{ inputs.ref }} - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index b106c30a53d4..6cad2eb924d3 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -63,6 +63,9 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + dev-build: needs: - setup @@ -269,28 +272,25 @@ jobs: - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 2 based on these values: - # envoy-version: ["1.29.5"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 2 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" @@ -299,6 +299,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -307,11 +308,10 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.29.5"] xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: - ENVOY_VERSION: ${{ matrix.envoy-version }} + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} XDS_TARGET: ${{ matrix.xds-target }} AWS_LAMBDA_REGION: us-west-2 steps: @@ -392,13 +392,14 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - dev-build permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. contents: read env: - ENVOY_VERSION: "1.29.5" - CONSUL_DATAPLANE_IMAGE: "docker.io/hashicorppreview/consul-dataplane:1.3-dev-ubi" + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} + CONSUL_DATAPLANE_IMAGE: "docker.io/hashicorppreview/consul-dataplane:1.5-dev-ubi" steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. @@ -511,7 +512,7 @@ jobs: strategy: fail-fast: false env: - DEPLOYER_CONSUL_DATAPLANE_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev" + DEPLOYER_CONSUL_DATAPLANE_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.5-dev" steps: - name: Checkout code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 diff --git a/Makefile b/Makefile index b4bf3807b9af..71dcbef07434 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,8 @@ CONSUL_IMAGE_VERSION?=latest # When changing the method of Go version detection, also update # version detection in CI workflows (reusable-get-go-version.yml). GOLANG_VERSION?=$(shell head -n 1 .go-version) -ENVOY_VERSION?='1.29.5' +# Takes the highest version from the ENVOY_VERSIONS file. +ENVOY_VERSION?=$(shell cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr | head -n 1) CONSUL_DATAPLANE_IMAGE := $(or $(CONSUL_DATAPLANE_IMAGE),"docker.io/hashicorppreview/consul-dataplane:1.3-dev-ubi") DEPLOYER_CONSUL_DATAPLANE_IMAGE := $(or $(DEPLOYER_CONSUL_DATAPLANE_IMAGE), "docker.io/hashicorppreview/consul-dataplane:1.3-dev") diff --git a/command/connect/envoy/envoy.go b/command/connect/envoy/envoy.go index 3489f1017a65..b974a525e9cd 100644 --- a/command/connect/envoy/envoy.go +++ b/command/connect/envoy/envoy.go @@ -1052,7 +1052,7 @@ func checkEnvoyVersionCompatibility(envoyVersion string, unsupportedList []strin // Next build the constraint string using the bounds, make sure that we are less than but not equal to // maxSupported since we will add 1. Need to add one to the max minor version so that we accept all patches - splitS := strings.Split(xdscommon.GetMaxEnvoyMinorVersion(), ".") + splitS := strings.Split(xdscommon.GetMaxEnvoyMajorVersion(), ".") minor, err := strconv.Atoi(splitS[1]) if err != nil { return envoyCompat{}, err @@ -1061,7 +1061,7 @@ func checkEnvoyVersionCompatibility(envoyVersion string, unsupportedList []strin maxSupported := fmt.Sprintf("%s.%d", splitS[0], minor) cs.Reset() - cs.WriteString(fmt.Sprintf(">= %s, < %s", xdscommon.GetMinEnvoyMinorVersion(), maxSupported)) + cs.WriteString(fmt.Sprintf(">= %s, < %s", xdscommon.GetMinEnvoyMajorVersion(), maxSupported)) constraints, err := version.NewConstraint(cs.String()) if err != nil { return envoyCompat{}, err diff --git a/command/connect/envoy/envoy_test.go b/command/connect/envoy/envoy_test.go index 6f4237af522d..64fd46c15883 100644 --- a/command/connect/envoy/envoy_test.go +++ b/command/connect/envoy/envoy_test.go @@ -1850,7 +1850,7 @@ func TestCheckEnvoyVersionCompatibility(t *testing.T) { }, { name: "supported-at-max", - envoyVersion: xdscommon.GetMaxEnvoyMinorVersion(), + envoyVersion: xdscommon.GetMaxEnvoyMajorVersion(), unsupportedList: xdscommon.UnsupportedEnvoyVersions, expectedCompat: envoyCompat{ isCompatible: true, diff --git a/envoyextensions/xdscommon/ENVOY_VERSIONS b/envoyextensions/xdscommon/ENVOY_VERSIONS new file mode 100644 index 000000000000..c604e79dc15f --- /dev/null +++ b/envoyextensions/xdscommon/ENVOY_VERSIONS @@ -0,0 +1,14 @@ +# This file represents the canonical list of supported Envoy versions for this version of Consul. +# +# Every line must contain a valid version number in the format "x.y.z" where x, y, and z are integers. +# All other lines must be comments beginning with a "#", or a blank line. +# +# Every prior "minor" version for a given "major" (x.y) version is implicitly supported unless excluded by +# `xdscommon.UnsupportedEnvoyVersions`. For example, 1.28.3 implies support for 1.28.0, 1.28.1, and 1.28.2. +# +# See https://www.consul.io/docs/connect/proxies/envoy#supported-versions for more information on Consul's Envoy +# version support. +1.29.5 +1.28.4 +1.27.6 +1.26.8 \ No newline at end of file diff --git a/envoyextensions/xdscommon/envoy_versioning.go b/envoyextensions/xdscommon/envoy_versioning.go index c5f9d4798c10..4b95e1bf2cd0 100644 --- a/envoyextensions/xdscommon/envoy_versioning.go +++ b/envoyextensions/xdscommon/envoy_versioning.go @@ -13,7 +13,7 @@ import ( var ( // minSupportedVersion is the oldest mainline version we support. This should always be // the zero'th point release of the last element of xdscommon.EnvoyVersions. - minSupportedVersion = version.Must(version.NewVersion(GetMinEnvoyMinorVersion())) + minSupportedVersion = version.Must(version.NewVersion(GetMinEnvoyMajorVersion())) specificUnsupportedVersions = []unsupportedVersion{} ) diff --git a/envoyextensions/xdscommon/envoy_versioning_test.go b/envoyextensions/xdscommon/envoy_versioning_test.go index cca8f88f789e..8b0b42fc8c6f 100644 --- a/envoyextensions/xdscommon/envoy_versioning_test.go +++ b/envoyextensions/xdscommon/envoy_versioning_test.go @@ -4,6 +4,8 @@ package xdscommon import ( + "fmt" + "slices" "testing" envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" @@ -70,99 +72,53 @@ func TestDetermineEnvoyVersionFromNode(t *testing.T) { } func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { - const ( - errTooOld = "is too old and is not supported by Consul" - ) + const errTooOld = "is too old and is not supported by Consul" type testcase struct { + name string expect SupportedProxyFeatures expectErr string } + var cases []testcase - // Just the bad versions - cases := map[string]testcase{ - "1.9.0": {expectErr: "Envoy 1.9.0 " + errTooOld}, - "1.10.0": {expectErr: "Envoy 1.10.0 " + errTooOld}, - "1.11.0": {expectErr: "Envoy 1.11.0 " + errTooOld}, - "1.12.0": {expectErr: "Envoy 1.12.0 " + errTooOld}, - "1.12.1": {expectErr: "Envoy 1.12.1 " + errTooOld}, - "1.12.2": {expectErr: "Envoy 1.12.2 " + errTooOld}, - "1.12.3": {expectErr: "Envoy 1.12.3 " + errTooOld}, - "1.12.4": {expectErr: "Envoy 1.12.4 " + errTooOld}, - "1.12.5": {expectErr: "Envoy 1.12.5 " + errTooOld}, - "1.12.6": {expectErr: "Envoy 1.12.6 " + errTooOld}, - "1.12.7": {expectErr: "Envoy 1.12.7 " + errTooOld}, - "1.13.0": {expectErr: "Envoy 1.13.0 " + errTooOld}, - "1.13.1": {expectErr: "Envoy 1.13.1 " + errTooOld}, - "1.13.2": {expectErr: "Envoy 1.13.2 " + errTooOld}, - "1.13.3": {expectErr: "Envoy 1.13.3 " + errTooOld}, - "1.13.4": {expectErr: "Envoy 1.13.4 " + errTooOld}, - "1.13.5": {expectErr: "Envoy 1.13.5 " + errTooOld}, - "1.13.6": {expectErr: "Envoy 1.13.6 " + errTooOld}, - "1.13.7": {expectErr: "Envoy 1.13.7 " + errTooOld}, - "1.14.0": {expectErr: "Envoy 1.14.0 " + errTooOld}, - "1.14.1": {expectErr: "Envoy 1.14.1 " + errTooOld}, - "1.14.2": {expectErr: "Envoy 1.14.2 " + errTooOld}, - "1.14.3": {expectErr: "Envoy 1.14.3 " + errTooOld}, - "1.14.4": {expectErr: "Envoy 1.14.4 " + errTooOld}, - "1.14.5": {expectErr: "Envoy 1.14.5 " + errTooOld}, - "1.14.6": {expectErr: "Envoy 1.14.6 " + errTooOld}, - "1.14.7": {expectErr: "Envoy 1.14.7 " + errTooOld}, - "1.15.0": {expectErr: "Envoy 1.15.0 " + errTooOld}, - "1.15.1": {expectErr: "Envoy 1.15.1 " + errTooOld}, - "1.15.2": {expectErr: "Envoy 1.15.2 " + errTooOld}, - "1.15.3": {expectErr: "Envoy 1.15.3 " + errTooOld}, - "1.15.4": {expectErr: "Envoy 1.15.4 " + errTooOld}, - "1.15.5": {expectErr: "Envoy 1.15.5 " + errTooOld}, - "1.16.1": {expectErr: "Envoy 1.16.1 " + errTooOld}, - "1.16.2": {expectErr: "Envoy 1.16.2 " + errTooOld}, - "1.16.3": {expectErr: "Envoy 1.16.3 " + errTooOld}, - "1.16.4": {expectErr: "Envoy 1.16.4 " + errTooOld}, - "1.16.5": {expectErr: "Envoy 1.16.5 " + errTooOld}, - "1.16.6": {expectErr: "Envoy 1.16.6 " + errTooOld}, - "1.17.4": {expectErr: "Envoy 1.17.4 " + errTooOld}, - "1.18.6": {expectErr: "Envoy 1.18.6 " + errTooOld}, - "1.19.5": {expectErr: "Envoy 1.19.5 " + errTooOld}, - "1.20.7": {expectErr: "Envoy 1.20.7 " + errTooOld}, - "1.21.5": {expectErr: "Envoy 1.21.5 " + errTooOld}, - "1.22.0": {expectErr: "Envoy 1.22.0 " + errTooOld}, - "1.22.1": {expectErr: "Envoy 1.22.1 " + errTooOld}, - "1.22.2": {expectErr: "Envoy 1.22.2 " + errTooOld}, - "1.22.3": {expectErr: "Envoy 1.22.3 " + errTooOld}, - "1.22.4": {expectErr: "Envoy 1.22.4 " + errTooOld}, - "1.22.5": {expectErr: "Envoy 1.22.5 " + errTooOld}, - "1.22.6": {expectErr: "Envoy 1.22.6 " + errTooOld}, - "1.22.7": {expectErr: "Envoy 1.22.7 " + errTooOld}, - "1.22.8": {expectErr: "Envoy 1.22.8 " + errTooOld}, - "1.22.9": {expectErr: "Envoy 1.22.9 " + errTooOld}, - "1.22.10": {expectErr: "Envoy 1.22.10 " + errTooOld}, - "1.22.11": {expectErr: "Envoy 1.22.11 " + errTooOld}, + // Bad versions. + minMajorVersion := version.Must(version.NewVersion(getMinEnvoyVersion())) + minMajorVersionMajorPart := minMajorVersion.Segments()[len(minMajorVersion.Segments())-2] + for major := 9; major < minMajorVersionMajorPart; major++ { + for minor := 0; minor < 10; minor++ { + cases = append(cases, testcase{ + name: version.Must(version.NewVersion(fmt.Sprintf("1.%d.%d", major, minor))).String(), + expectErr: errTooOld, + }) + } } - // Insert a bunch of valid versions. - // Populate feature flags here when appropriate. See consul 1.10.x for reference. - /* Example from 1.18 - for _, v := range []string{ - "1.18.0", "1.18.1", "1.18.2", "1.18.3", "1.18.4", "1.18.5", "1.18.6", - } { - cases[v] = testcase{expect: SupportedProxyFeatures{ - ForceLDSandCDSToAlwaysUseWildcardsOnReconnect: true, - }} - } - */ - for _, v := range []string{ - "1.26.0", "1.26.1", "1.26.2", "1.26.3", "1.26.4", "1.26.5", "1.26.6", "1.26.7", "1.26.8", - "1.27.0", "1.27.1", "1.27.2", "1.27.3", "1.27.4", "1.27.5", "1.27.6", - "1.28.0", "1.28.1", "1.28.2", "1.28.3", "1.28.4", - "1.29.0", "1.29.1", "1.29.2", "1.29.3", "1.29.4", "1.29.5", - } { - cases[v] = testcase{expect: SupportedProxyFeatures{}} + // Good versions. + // Sort ascending so test output is ordered like bad cases above. + var supportedVersionsAscending []string + supportedVersionsAscending = append(supportedVersionsAscending, EnvoyVersions...) + slices.Reverse(supportedVersionsAscending) + for _, v := range supportedVersionsAscending { + envoyVersion := version.Must(version.NewVersion(v)) + // e.g. this is 27 in 1.27.4 + versionMajorPart := envoyVersion.Segments()[len(envoyVersion.Segments())-2] + // e.g. this is 4 in 1.27.4 + versionMinorPart := envoyVersion.Segments()[len(envoyVersion.Segments())-1] + + // Create synthetic minor versions from .0 through the actual configured version. + for minor := 0; minor <= versionMinorPart; minor++ { + minorVersion := version.Must(version.NewVersion(fmt.Sprintf("1.%d.%d", versionMajorPart, minor))) + cases = append(cases, testcase{ + name: minorVersion.String(), + expect: SupportedProxyFeatures{}, + }) + } } - for name, tc := range cases { + for _, tc := range cases { tc := tc - t.Run(name, func(t *testing.T) { - sf, err := DetermineSupportedProxyFeaturesFromString(name) + t.Run(tc.name, func(t *testing.T) { + sf, err := DetermineSupportedProxyFeaturesFromString(tc.name) if tc.expectErr == "" { require.NoError(t, err) require.Equal(t, tc.expect, sf) diff --git a/envoyextensions/xdscommon/proxysupport.go b/envoyextensions/xdscommon/proxysupport.go index 77c1da0ad587..d0afcc7390fe 100644 --- a/envoyextensions/xdscommon/proxysupport.go +++ b/envoyextensions/xdscommon/proxysupport.go @@ -3,7 +3,64 @@ package xdscommon -import "strings" +import ( + _ "embed" + "fmt" + "slices" + "strconv" + "strings" +) + +// File containing the canonical range of supported Envoy versions for this version of Consul. +// This file should contain exactly one point release for each major release of Envoy, per line. +// All other contents must be blank lines or comments. Comments must be on their own line starting with '#'. +// +//go:embed ENVOY_VERSIONS +var envoyVersionsRaw string + +// initEnvoyVersions calls parseEnvoyVersions and panics if it returns an error. Used to set EnvoyVersions. +func initEnvoyVersions() []string { + versions, err := parseEnvoyVersions(envoyVersionsRaw) + if err != nil { + panic(err) + } + return versions +} + +// parseEnvoyVersions parses the ENVOY_VERSIONS file and returns a list of supported Envoy versions. +func parseEnvoyVersions(raw string) ([]string, error) { + lines := strings.Split(raw, "\n") + var versionLines []string + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue // skip empty lines and comments + } + + // Assume all remaining lines are valid Envoy versions in the format "X.Y.Z". + versionParts := strings.Split(trimmed, ".") + if len(versionParts) != 3 { + return nil, fmt.Errorf("invalid version in ENVOY_VERSIONS: %s", line) + } + for _, v := range versionParts { + if _, err := strconv.Atoi(v); err != nil { + return nil, fmt.Errorf("invalid version in ENVOY_VERSIONS: %s", line) + } + } + versionLines = append(versionLines, trimmed) + } + + // Ensure sorted in descending order. + // We do this here as well as tests because other code (e.g. Makefile) may depend on the order + // of these values, so we want early detection in case tests are not run before compilation. + if !slices.IsSortedFunc(versionLines, func(v1, v2 string) int { + return strings.Compare(v2, v1) + }) { + return nil, fmt.Errorf("ENVOY_VERSIONS must be sorted in descending order") + } + + return versionLines, nil +} // EnvoyVersions lists the latest officially supported versions of envoy. // @@ -11,12 +68,7 @@ import "strings" // each major release should be present. // // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions -var EnvoyVersions = []string{ - "1.29.5", - "1.28.4", - "1.27.6", - "1.26.8", -} +var EnvoyVersions = initEnvoyVersions() // UnsupportedEnvoyVersions lists any unsupported Envoy versions (mainly minor versions) that fall // within the range of EnvoyVersions above. @@ -27,18 +79,28 @@ var EnvoyVersions = []string{ // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions var UnsupportedEnvoyVersions = []string{} -// GetMaxEnvoyMinorVersion grabs the first value in EnvoyVersions and strips the patch number off in order -// to return the maximum supported Envoy minor version +// GetMaxEnvoyMajorVersion grabs the first value in EnvoyVersions and strips the last number off in order +// to return the maximum supported Envoy "major" version. // For example, if the input string is "1.14.1", the function would return "1.14". -func GetMaxEnvoyMinorVersion() string { - s := strings.Split(EnvoyVersions[0], ".") +func GetMaxEnvoyMajorVersion() string { + s := strings.Split(getMaxEnvoyVersion(), ".") return s[0] + "." + s[1] } -// GetMinEnvoyMinorVersion grabs the last value in EnvoyVersions and strips the patch number off in order -// to return the minimum supported Envoy minor version +// GetMinEnvoyMajorVersion grabs the last value in EnvoyVersions and strips the patch number off in order +// to return the minimum supported Envoy "major" version. // For example, if the input string is "1.12.1", the function would return "1.12". -func GetMinEnvoyMinorVersion() string { - s := strings.Split(EnvoyVersions[len(EnvoyVersions)-1], ".") +func GetMinEnvoyMajorVersion() string { + s := strings.Split(getMinEnvoyVersion(), ".") return s[0] + "." + s[1] } + +// getMaxEnvoyVersion returns the first (highest) value in EnvoyVersions. +func getMaxEnvoyVersion() string { + return EnvoyVersions[0] +} + +// getMinEnvoyVersion returns the last (lowest) value in EnvoyVersions. +func getMinEnvoyVersion() string { + return EnvoyVersions[len(EnvoyVersions)-1] +} diff --git a/envoyextensions/xdscommon/proxysupport_test.go b/envoyextensions/xdscommon/proxysupport_test.go index cc90b726c9d9..d61847834501 100644 --- a/envoyextensions/xdscommon/proxysupport_test.go +++ b/envoyextensions/xdscommon/proxysupport_test.go @@ -4,6 +4,7 @@ package xdscommon import ( + "slices" "sort" "testing" @@ -11,11 +12,17 @@ import ( "github.com/stretchr/testify/assert" ) +// TestProxySupportOrder tests that the values in EnvoyVersions are valid (X.Y.Z), contiguous by "major" (X.Y) version, +// and sorted in descending order. func TestProxySupportOrder(t *testing.T) { versions := make([]*version.Version, len(EnvoyVersions)) beforeSort := make([]*version.Version, len(EnvoyVersions)) for i, raw := range EnvoyVersions { v, _ := version.NewVersion(raw) + if v.Segments()[0] != 1 { + // If this fails, we need to add support for a new semver-major (x in x.y.z) version of Envoy + t.Fatalf("Expected major version to be 1, got: %v", v.Segments()[0]) + } versions[i] = v beforeSort[i] = v } @@ -30,4 +37,48 @@ func TestProxySupportOrder(t *testing.T) { for i := range EnvoyVersions { assert.True(t, versions[i].Equal(beforeSort[i])) } + + // Check that we have a continues set of versions + for i := 1; i < len(versions); i++ { + previousMajorVersion := getMajorVersion(versions[i-1]) + majorVersion := getMajorVersion(versions[i]) + assert.True(t, majorVersion == previousMajorVersion-1, + "Expected Envoy major version following %d.%d to be %d.%d, got %d.%d", + versions[i-1].Segments()[0], + previousMajorVersion, + versions[i-1].Segments()[0], + previousMajorVersion-1, + versions[i].Segments()[0], + majorVersion) + } +} + +func TestParseEnvoyVersions(t *testing.T) { + // Test with valid versions, comments, and blank lines + raw := "# Comment\n1.29.4\n\n# More\n# comments\n1.28.3\n\n1.27.5\n1.26.8\n\n" + expected := []string{"1.29.4", "1.28.3", "1.27.5", "1.26.8"} + + versions, err := parseEnvoyVersions(raw) + assert.NoError(t, err) + + if !slices.Equal(versions, expected) { + t.Fatalf("Expected %v, got: %v", expected, versions) + } + + // Test with invalid version + raw = "1.29.4\n1.26.8\nfoo" + + _, err = parseEnvoyVersions(raw) + assert.EqualError(t, err, "invalid version in ENVOY_VERSIONS: foo") + + // Test with out-of-order values + raw = "1.29.4\n1.26.8\n1.27.5" + + _, err = parseEnvoyVersions(raw) + assert.EqualError(t, err, "ENVOY_VERSIONS must be sorted in descending order") +} + +// getMajorVersion returns the "major" (Y in X.Y.Z) version of the given Envoy version. +func getMajorVersion(version *version.Version) int { + return version.Segments()[1] } diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index dad65089303f..3efcd38e82e6 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -182,16 +182,6 @@ function assert_envoy_version { echo "Got version=$VERSION" echo "Want version=$ENVOY_VERSION" - # 1.20.2, 1.19.3 and 1.18.6 are special snowflakes in that the version for - # the release is reported with a '-dev' suffix (eg 1.20.2-dev). - if [ "$ENVOY_VERSION" = "1.20.2" ]; then - ENVOY_VERSION="1.20.2-dev" - elif [ "$ENVOY_VERSION" = "1.19.3" ]; then - ENVOY_VERSION="1.19.3-dev" - elif [ "$ENVOY_VERSION" = "1.18.6" ]; then - ENVOY_VERSION="1.18.6-dev" - fi - echo $VERSION | grep "/$ENVOY_VERSION/" } diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index 1f825268b045..46dcb9965fe2 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -15,7 +15,10 @@ DEBUG=${DEBUG:-} XDS_TARGET=${XDS_TARGET:-server} # ENVOY_VERSION to run each test against -ENVOY_VERSION=${ENVOY_VERSION:-"1.29.5"} +if [[ -z "${ENVOY_VERSION:-}" ]]; then + echo "please set Envoy version via ENVOY_VERSION" + exit 1 +fi export ENVOY_VERSION export DOCKER_BUILDKIT=1