Skip to content

Commit

Permalink
build: consolidate Envoy version management
Browse files Browse the repository at this point in the history
Simplify Envoy version management by consolidating all runtime, build,
and CI sources of Envoy versions into a single plaintext file.

The goal of this change is to avoid common mistakes missing an update of
some Envoy versions (both in general and due to release branch
inconsistency), and enable automated Envoy version updates in the
future.
  • Loading branch information
zalimeni committed Jun 18, 2024
1 parent a1ee7cb commit 6f12bb7
Show file tree
Hide file tree
Showing 13 changed files with 288 additions and 130 deletions.
71 changes: 71 additions & 0 deletions .github/workflows/reusable-get-envoy-versions.yml
Original file line number Diff line number Diff line change
@@ -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<<EOF" >> $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
9 changes: 9 additions & 0 deletions .github/workflows/reusable-get-go-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
35 changes: 18 additions & 17 deletions .github/workflows/test-integrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -299,6 +299,7 @@ jobs:
needs:
- setup
- get-go-version
- get-envoy-versions
- generate-envoy-job-matrices
- dev-build
permissions:
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
4 changes: 2 additions & 2 deletions command/connect/envoy/envoy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion command/connect/envoy/envoy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions envoyextensions/xdscommon/ENVOY_VERSIONS
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion envoyextensions/xdscommon/envoy_versioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
)
Expand Down
120 changes: 38 additions & 82 deletions envoyextensions/xdscommon/envoy_versioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package xdscommon

import (
"fmt"
"slices"
"testing"

envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 6f12bb7

Please sign in to comment.