From cc779a1933e22e550acd8fba5480aed4615272c5 Mon Sep 17 00:00:00 2001 From: Michel Laterman <82832767+michel-laterman@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:39:42 -0700 Subject: [PATCH] Relax version checks for snapshot builds (#3039) --- Makefile | 8 ++-- README.md | 4 +- ...lax-version-checks-in-snapshot-builds.yaml | 36 ++++++++++++++++ internal/pkg/api/handleCheckin_test.go | 9 ++++ internal/pkg/api/userAgent.go | 12 ------ internal/pkg/api/userAgent_test.go | 10 +---- internal/pkg/api/verConst.go | 24 +++++++++++ internal/pkg/api/verConst_snapshot.go | 41 +++++++++++++++++++ internal/pkg/api/verConst_snapshot_test.go | 41 +++++++++++++++++++ 9 files changed, 160 insertions(+), 25 deletions(-) create mode 100644 changelog/fragments/1697230491-Relax-version-checks-in-snapshot-builds.yaml create mode 100644 internal/pkg/api/verConst.go create mode 100644 internal/pkg/api/verConst_snapshot.go create mode 100644 internal/pkg/api/verConst_snapshot_test.go diff --git a/Makefile b/Makefile index 8a8495a39..819412cf1 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ list-platforms: ## - Show the possible PLATFORMS .PHONY: local local: ## - Build local binary for local environment (bin/fleet-server) @printf "${CMD_COLOR_ON} Build binaries using local go installation\n${CMD_COLOR_OFF}" - go build $(if $(DEV),-tags="dev",) -gcflags="${GCFLAGS}" -ldflags="${LDFLAGS}" -o ./bin/fleet-server . + go build $(if $(SNAPSHOT),-tags="snapshot",) -gcflags="${GCFLAGS}" -ldflags="${LDFLAGS}" -o ./bin/fleet-server . @printf "${CMD_COLOR_ON} Binaries in ./bin/\n${CMD_COLOR_OFF}" .PHONY: $(COVER_TARGETS) @@ -84,7 +84,7 @@ $(COVER_TARGETS): cover-%: ## - Build a binary with the -cover flag for integrat $(eval $@_GO_ARCH := $(lastword $(subst /, ,$(lastword $(subst cover-, ,$@))))) $(eval $@_ARCH := $(TARGET_ARCH_$($@_GO_ARCH))) $(eval $@_BUILDMODE:= $(BUILDMODE_$($@_OS)_$($@_GO_ARCH))) - GOOS=$($@_OS) GOARCH=$($@_GO_ARCH) go build $(if $(DEV),-tags="dev",) -cover -coverpkg=./... -gcflags="${GCFLAGS}" -ldflags="${LDFLAGS}" $($@_BUILDMODE) -o build/cover/fleet-server-$(VERSION)-$($@_OS)-$($@_ARCH)/fleet-server$(if $(filter windows,$($@_OS)),.exe,) . + GOOS=$($@_OS) GOARCH=$($@_GO_ARCH) go build $(if $(SNAPSHOT),-tags="snapshot",) -cover -coverpkg=./... -gcflags="${GCFLAGS}" -ldflags="${LDFLAGS}" $($@_BUILDMODE) -o build/cover/fleet-server-$(VERSION)-$($@_OS)-$($@_ARCH)/fleet-server$(if $(filter windows,$($@_OS)),.exe,) . .PHONY: clean clean: ## - Clean up build artifacts @@ -198,7 +198,7 @@ $(PLATFORM_TARGETS): release-%: $(eval $@_GO_ARCH := $(lastword $(subst /, ,$(lastword $(subst release-, ,$@))))) $(eval $@_ARCH := $(TARGET_ARCH_$($@_GO_ARCH))) $(eval $@_BUILDMODE:= $(BUILDMODE_$($@_OS)_$($@_GO_ARCH))) - GOOS=$($@_OS) GOARCH=$($@_GO_ARCH) go build $(if $(DEV),-tags="dev",) -gcflags="${GCFLAGS}" -ldflags="${LDFLAGS}" $($@_BUILDMODE) -o build/binaries/fleet-server-$(VERSION)-$($@_OS)-$($@_ARCH)/fleet-server . + GOOS=$($@_OS) GOARCH=$($@_GO_ARCH) go build $(if $(SNAPSHOT),-tags="snapshot",) -gcflags="${GCFLAGS}" -ldflags="${LDFLAGS}" $($@_BUILDMODE) -o build/binaries/fleet-server-$(VERSION)-$($@_OS)-$($@_ARCH)/fleet-server . @$(MAKE) OS=$($@_OS) ARCH=$($@_ARCH) package-target .PHONY: build-docker @@ -208,6 +208,7 @@ build-docker: --build-arg=GCFLAGS="${GCFLAGS}" \ --build-arg=LDFLAGS="${LDFLAGS}" \ --build-arg=DEV="$(DEV)" \ + --build-arg=SNAPSHOT="$(SNAPSHOT)" \ --build-arg=VERSION="$(VERSION)" \ -t $(DOCKER_IMAGE):$(DOCKER_IMAGE_TAG)$(if $(DEV),-dev,) . @@ -220,6 +221,7 @@ build-and-push-docker: --build-arg=GCFLAGS="${GCFLAGS}" \ --build-arg=LDFLAGS="${LDFLAGS}" \ --build-arg=DEV="$(DEV)" \ + --build-arg=SNAPSHOT="$(SNAPSHOT)" \ --build-arg=VERSION="$(VERSION)" \ -t $(DOCKER_IMAGE):$(DOCKER_IMAGE_TAG)$(if $(DEV),-dev,) . diff --git a/README.md b/README.md index 6f2c5eb19..f2e67922d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,9 @@ GOOS=darwin GOARCH=amd64 go build -tags="dev" -gcflags="all=-N -l" -ldflags="-X Change `release-darwin/amd64` to `release-YOUR_OS/platform`. Run `make list-platforms` to check out the possible values. -The `SNAPSHOT` flag sets the snapshot version flag. +The `SNAPSHOT` flag sets the snapshot version flag and relaxes client version checks. +When `SNAPSHOT` is set we allow clients of the next version to communicate with fleet-server. +For example, if fleet-server is running version `8.11.0` on a `SNAPSHOT` build, clients can communiate with versions up to `8.12.0`. ### Docker build diff --git a/changelog/fragments/1697230491-Relax-version-checks-in-snapshot-builds.yaml b/changelog/fragments/1697230491-Relax-version-checks-in-snapshot-builds.yaml new file mode 100644 index 000000000..70a00de45 --- /dev/null +++ b/changelog/fragments/1697230491-Relax-version-checks-in-snapshot-builds.yaml @@ -0,0 +1,36 @@ +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: enhancement + +# Change summary; a 80ish characters long description of the change. +summary: Relax version checks in snapshot builds + +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. +description: | + Relax version checking fleet-server does to client communication when built with SNAPSHOT=true. + Versions allowed will be <=X.Y+1.Z, that is to say snapshots will allow the next minor version to communicate. + This is done to avoid the chicken and egg prpoblem we face in automated testing when releasing new minor versions. + + +# Affected component; a word indicating the component this changeset affects. +component: + +# PR URL; optional; the PR number that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +#pr: https://github.com/owner/repo/1234 + +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +issue: 2960 diff --git a/internal/pkg/api/handleCheckin_test.go b/internal/pkg/api/handleCheckin_test.go index 50dd09a1d..3e579027c 100644 --- a/internal/pkg/api/handleCheckin_test.go +++ b/internal/pkg/api/handleCheckin_test.go @@ -28,6 +28,7 @@ import ( testcache "github.com/elastic/fleet-server/v7/internal/pkg/testing/cache" testlog "github.com/elastic/fleet-server/v7/internal/pkg/testing/log" + "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -578,3 +579,11 @@ func BenchmarkParallel_CheckinT_writeResponse(b *testing.B) { } }) } + +func mustBuildConstraints(verStr string) version.Constraints { + con, err := BuildVersionConstraint(verStr) + if err != nil { + panic(err) + } + return con +} diff --git a/internal/pkg/api/userAgent.go b/internal/pkg/api/userAgent.go index db3a31831..992b98c64 100644 --- a/internal/pkg/api/userAgent.go +++ b/internal/pkg/api/userAgent.go @@ -7,7 +7,6 @@ package api import ( "context" "errors" - "fmt" "math" "strconv" "strings" @@ -29,17 +28,6 @@ var ( ErrUnsupportedVersion = errors.New("version is not supported") ) -// BuildVersionConstraint turns the version into a constraint to ensure that the connecting Elastic Agent's are -// a supported version. -func BuildVersionConstraint(verStr string) (version.Constraints, error) { - ver, err := version.NewVersion(verStr) - if err != nil { - return nil, err - } - verStr = maximizePatch(ver) - return version.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinVersion, verStr)) -} - // maximizePatch turns the version into a string that has the patch value set to the maximum integer. // // Used to allow the Elastic Agent to be at a higher patch version than the Fleet Server, but require that the diff --git a/internal/pkg/api/userAgent_test.go b/internal/pkg/api/userAgent_test.go index edabb917d..9106d08de 100644 --- a/internal/pkg/api/userAgent_test.go +++ b/internal/pkg/api/userAgent_test.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -//go:build !integration +//go:build !integration && !snapshot package api @@ -121,11 +121,3 @@ func TestValidateUserAgent(t *testing.T) { }) } } - -func mustBuildConstraints(verStr string) version.Constraints { - con, err := BuildVersionConstraint(verStr) - if err != nil { - panic(err) - } - return con -} diff --git a/internal/pkg/api/verConst.go b/internal/pkg/api/verConst.go new file mode 100644 index 000000000..6ff1beb8b --- /dev/null +++ b/internal/pkg/api/verConst.go @@ -0,0 +1,24 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build !snapshot + +package api + +import ( + "fmt" + + "github.com/hashicorp/go-version" +) + +// BuildVersionConstraint turns the version into a constraint to ensure that the connecting Elastic Agent's are +// a supported version. +func BuildVersionConstraint(verStr string) (version.Constraints, error) { + ver, err := version.NewVersion(verStr) + if err != nil { + return nil, err + } + verStr = maximizePatch(ver) + return version.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinVersion, verStr)) +} diff --git a/internal/pkg/api/verConst_snapshot.go b/internal/pkg/api/verConst_snapshot.go new file mode 100644 index 000000000..9478b476a --- /dev/null +++ b/internal/pkg/api/verConst_snapshot.go @@ -0,0 +1,41 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build snapshot + +package api + +import ( + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/go-version" +) + +// BuildVersionConstraint turns the version into a constraint to ensure that the connecting Elastic Agent's are +// a supported version. +// For snapshot builds we allow the minor version to be newer in order to allow automated testing to proceed. +func BuildVersionConstraint(verStr string) (version.Constraints, error) { + ver, err := version.NewVersion(verStr) + if err != nil { + return nil, err + } + verStr = bumpMinor(ver) + return version.NewConstraint(fmt.Sprintf(">= %s, <= %s", MinVersion, verStr)) +} + +// bumpMinor returns a version string where 1 is added to the minor version +func bumpMinor(ver *version.Version) string { + segments := ver.Segments() + if len(segments) < 2 { + return ver.String() + } + segments[1] += 1 + strs := make([]string, 0, len(segments)) + for _, seg := range segments { + strs = append(strs, strconv.Itoa(seg)) + } + return strings.Join(strs, ".") +} diff --git a/internal/pkg/api/verConst_snapshot_test.go b/internal/pkg/api/verConst_snapshot_test.go new file mode 100644 index 000000000..12b980820 --- /dev/null +++ b/internal/pkg/api/verConst_snapshot_test.go @@ -0,0 +1,41 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build !integration && snapshot + +package api + +import ( + "testing" + + "github.com/hashicorp/go-version" + "github.com/stretchr/testify/require" +) + +func TestVersionConstraint(t *testing.T) { + tests := []struct { + version string + succeed bool + }{{ + version: "8.0.0", + succeed: true, + }, { + version: "8.1.0", + succeed: true, + }, { + version: "8.2.0", + succeed: false, + }} + + for _, tc := range tests { + t.Run(tc.version, func(t *testing.T) { + vc, err := BuildVersionConstraint("8.0.0") + require.NoError(t, err) + ver, err := version.NewVersion(tc.version) + require.NoError(t, err) + + require.Equalf(t, tc.succeed, vc.Check(ver), "vc is %s", vc.String()) + }) + } +}