cli: use Semver type to represent microservice versions (#2125)

Previously we used strings to pass microservice versions. This invited
bugs due to missing input validation.
This commit is contained in:
Otto Bittner 2023-07-25 14:20:25 +02:00 committed by GitHub
parent 2d3999440d
commit 1d5a8283e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 612 additions and 318 deletions

View File

@ -33,7 +33,7 @@ func run(issuer atls.Issuer, openDevice vtpm.TPMOpenFunc, fileHandler file.Handl
) { ) {
defer cloudLogger.Close() defer cloudLogger.Close()
log.With(zap.String("version", constants.VersionInfo())).Infof("Starting bootstrapper") log.With(zap.String("version", constants.BinaryVersion().String())).Infof("Starting bootstrapper")
cloudLogger.Disclose("bootstrapper started running...") cloudLogger.Disclose("bootstrapper started running...")
uuid, err := getDiskUUID() uuid, err := getDiskUUID()

View File

@ -157,6 +157,7 @@ go_test(
"//internal/kms/uri", "//internal/kms/uri",
"//internal/license", "//internal/license",
"//internal/logger", "//internal/logger",
"//internal/semver",
"//internal/versions", "//internal/versions",
"//operators/constellation-node-operator/api/v1alpha1", "//operators/constellation-node-operator/api/v1alpha1",
"//verify/verifyproto", "//verify/verifyproto",

View File

@ -86,7 +86,7 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
return err return err
} }
if !flags.force { if !flags.force {
if err := validateCLIandConstellationVersionAreEqual(constants.VersionInfo(), conf.Image, conf.MicroserviceVersion); err != nil { if err := validateCLIandConstellationVersionAreEqual(constants.BinaryVersion(), conf.Image, conf.MicroserviceVersion); err != nil {
return err return err
} }
} }
@ -301,7 +301,7 @@ func must(err error) {
} }
// validateCLIandConstellationVersionAreEqual checks if the image and microservice version are equal (down to patch level) to the CLI version. // validateCLIandConstellationVersionAreEqual checks if the image and microservice version are equal (down to patch level) to the CLI version.
func validateCLIandConstellationVersionAreEqual(cliVersion, imageVersion, microserviceVersion string) error { func validateCLIandConstellationVersionAreEqual(cliVersion semver.Semver, imageVersion string, microserviceVersion semver.Semver) error {
parsedImageVersion, err := versionsapi.NewVersionFromShortPath(imageVersion, versionsapi.VersionKindImage) parsedImageVersion, err := versionsapi.NewVersionFromShortPath(imageVersion, versionsapi.VersionKindImage)
if err != nil { if err != nil {
return fmt.Errorf("parsing image version: %w", err) return fmt.Errorf("parsing image version: %w", err)
@ -312,21 +312,11 @@ func validateCLIandConstellationVersionAreEqual(cliVersion, imageVersion, micros
return fmt.Errorf("parsing image semantical version: %w", err) return fmt.Errorf("parsing image semantical version: %w", err)
} }
semMicro, err := semver.New(microserviceVersion) if !cliVersion.MajorMinorEqual(semImage) {
if err != nil { return fmt.Errorf("image version %q does not match the major and minor version of the cli version %q", semImage.String(), cliVersion.String())
return fmt.Errorf("parsing microservice version: %w", err)
} }
if cliVersion.Compare(microserviceVersion) != 0 {
semCLI, err := semver.New(cliVersion) return fmt.Errorf("cli version %q does not match microservice version %q", cliVersion.String(), microserviceVersion.String())
if err != nil {
return fmt.Errorf("parsing binary version: %w", err)
}
if !semCLI.MajorMinorEqual(semImage) {
return fmt.Errorf("image version %q does not match the major and minor version of the cli version %q", semImage.String(), semCLI.String())
}
if semCLI.Compare(semMicro) != 0 {
return fmt.Errorf("cli version %q does not match microservice version %q", semCLI.String(), semMicro.String())
} }
return nil return nil
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -273,52 +274,52 @@ func TestCheckDirClean(t *testing.T) {
func TestValidateCLIandConstellationVersionCompatibility(t *testing.T) { func TestValidateCLIandConstellationVersionCompatibility(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
imageVersion string imageVersion string
microServiceVersion string microServiceVersion consemver.Semver
cliVersion string cliVersion consemver.Semver
wantErr bool wantErr bool
}{ }{
"empty": { "empty": {
imageVersion: "", imageVersion: "",
microServiceVersion: "", microServiceVersion: consemver.Semver{},
cliVersion: "", cliVersion: consemver.Semver{},
wantErr: true, wantErr: true,
}, },
"invalid when image < CLI": { "invalid when image < CLI": {
imageVersion: "v2.7.1", imageVersion: "v2.7.1",
microServiceVersion: "v2.8.0", microServiceVersion: consemver.NewFromInt(2, 8, 0, ""),
cliVersion: "v2.8.0", cliVersion: consemver.NewFromInt(2, 8, 0, ""),
wantErr: true, wantErr: true,
}, },
"invalid when microservice < CLI": { "invalid when microservice < CLI": {
imageVersion: "v2.8.0", imageVersion: "v2.8.0",
microServiceVersion: "v2.7.1", microServiceVersion: consemver.NewFromInt(2, 7, 1, ""),
cliVersion: "v2.8.0", cliVersion: consemver.NewFromInt(2, 8, 0, ""),
wantErr: true, wantErr: true,
}, },
"valid release version": { "valid release version": {
imageVersion: "v2.9.0", imageVersion: "v2.9.0",
microServiceVersion: "v2.9.0", microServiceVersion: consemver.NewFromInt(2, 9, 0, ""),
cliVersion: "2.9.0", cliVersion: consemver.NewFromInt(2, 9, 0, ""),
}, },
"valid pre-version": { "valid pre-version": {
imageVersion: "ref/main/stream/nightly/v2.9.0-pre.0.20230626150512-0a36ce61719f", imageVersion: "ref/main/stream/nightly/v2.9.0-pre.0.20230626150512-0a36ce61719f",
microServiceVersion: "v2.9.0-pre.0.20230626150512-0a36ce61719f", microServiceVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
cliVersion: "2.9.0-pre.0.20230626150512-0a36ce61719f", cliVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
}, },
"image version suffix need not be equal to CLI version": { "image version suffix need not be equal to CLI version": {
imageVersion: "ref/main/stream/nightly/v2.9.0-pre.0.19990626150512-9z36ce61799z", imageVersion: "ref/main/stream/nightly/v2.9.0-pre.0.19990626150512-9z36ce61799z",
microServiceVersion: "v2.9.0-pre.0.20230626150512-0a36ce61719f", microServiceVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
cliVersion: "2.9.0-pre.0.20230626150512-0a36ce61719f", cliVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
}, },
"image version can have different patch version": { "image version can have different patch version": {
imageVersion: "ref/main/stream/nightly/v2.9.1-pre.0.19990626150512-9z36ce61799z", imageVersion: "ref/main/stream/nightly/v2.9.1-pre.0.19990626150512-9z36ce61799z",
microServiceVersion: "v2.9.0-pre.0.20230626150512-0a36ce61719f", microServiceVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
cliVersion: "2.9.0-pre.0.20230626150512-0a36ce61719f", cliVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
}, },
"microService version suffix must be equal to CLI version": { "microService version suffix must be equal to CLI version": {
imageVersion: "ref/main/stream/nightly/v2.9.0-pre.0.20230626150512-0a36ce61719f", imageVersion: "ref/main/stream/nightly/v2.9.0-pre.0.20230626150512-0a36ce61719f",
microServiceVersion: "v2.9.0-pre.0.19990626150512-9z36ce61799z", microServiceVersion: consemver.NewFromInt(2, 9, 0, "pre.0.19990626150512-9z36ce61799z"),
cliVersion: "2.9.0-pre.0.20230626150512-0a36ce61719f", cliVersion: consemver.NewFromInt(2, 9, 0, "pre.0.20230626150512-0a36ce61719f"),
wantErr: true, wantErr: true,
}, },
} }

View File

@ -124,7 +124,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V
return err return err
} }
if !flags.force { if !flags.force {
if err := validateCLIandConstellationVersionAreEqual(constants.VersionInfo(), conf.Image, conf.MicroserviceVersion); err != nil { if err := validateCLIandConstellationVersionAreEqual(constants.BinaryVersion(), conf.Image, conf.MicroserviceVersion); err != nil {
return err return err
} }
} }

View File

@ -592,7 +592,7 @@ func (m *stubMerger) kubeconfigEnvVar() string {
func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, csp cloudprovider.Provider) *config.Config { func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, csp cloudprovider.Provider) *config.Config {
t.Helper() t.Helper()
conf.Image = "v" + constants.VersionInfo() conf.Image = constants.BinaryVersion().String()
conf.Name = "kubernetes" conf.Name = "kubernetes"
switch csp { switch csp {

View File

@ -12,6 +12,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/helm" "github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/attestation/variant"
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1" updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -117,7 +118,7 @@ func TestStatus(t *testing.T) {
}, },
}, },
helmClient: stubHelmClient{ helmClient: stubHelmClient{
serviceVersions: helm.NewServiceVersions("v1.0.0", "v1.0.0", "v1.1.0", "v1.1.0"), serviceVersions: helm.NewServiceVersions(consemver.NewFromInt(1, 0, 0, ""), consemver.NewFromInt(1, 0, 0, ""), consemver.NewFromInt(1, 1, 0, ""), consemver.NewFromInt(1, 1, 0, "")),
}, },
nodeVersion: updatev1alpha1.NodeVersion{ nodeVersion: updatev1alpha1.NodeVersion{
Spec: updatev1alpha1.NodeVersionSpec{ Spec: updatev1alpha1.NodeVersionSpec{
@ -167,7 +168,7 @@ func TestStatus(t *testing.T) {
}, },
}, },
helmClient: stubHelmClient{ helmClient: stubHelmClient{
serviceVersions: helm.NewServiceVersions("v1.0.0", "v1.0.0", "v1.1.0", "v1.1.0"), serviceVersions: helm.NewServiceVersions(consemver.NewFromInt(1, 0, 0, ""), consemver.NewFromInt(1, 0, 0, ""), consemver.NewFromInt(1, 1, 0, ""), consemver.NewFromInt(1, 1, 0, "")),
}, },
nodeVersion: updatev1alpha1.NodeVersion{ nodeVersion: updatev1alpha1.NodeVersion{
Spec: updatev1alpha1.NodeVersionSpec{ Spec: updatev1alpha1.NodeVersionSpec{

View File

@ -33,7 +33,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher" "github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
conSemver "github.com/edgelesssys/constellation/v2/internal/semver" consemver "github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/siderolabs/talos/pkg/machinery/config/encoder" "github.com/siderolabs/talos/pkg/machinery/config/encoder"
@ -93,7 +93,7 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
cosign: sigstore.CosignVerifier{}, cosign: sigstore.CosignVerifier{},
rekor: rekor, rekor: rekor,
flags: flags, flags: flags,
cliVersion: compatibility.EnsurePrefixV(constants.VersionInfo()), cliVersion: constants.BinaryVersion(),
log: log, log: log,
versionsapi: versionfetcher, versionsapi: versionfetcher,
}, },
@ -202,9 +202,9 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand
// Filter versions to only include upgrades // Filter versions to only include upgrades
newServices := supported.service newServices := supported.service
if err := compatibility.IsValidUpgrade(current.service, supported.service); err != nil { if err := supported.service.IsUpgradeTo(current.service); err != nil {
newServices = "" newServices = consemver.Semver{}
u.log.Debugf("No valid service upgrades are available from %q to %q. The minor version can only drift by 1.\n", current.service, supported.service) u.log.Debugf("No valid service upgrades are available from %q to %q. The minor version can only drift by 1.\n", current.service.String(), supported.service.String())
} }
newKubernetes := filterK8sUpgrades(current.k8s, supported.k8s) newKubernetes := filterK8sUpgrades(current.k8s, supported.k8s)
@ -352,8 +352,8 @@ type collector interface {
newImages(ctx context.Context, version string) ([]versionsapi.Version, error) newImages(ctx context.Context, version string) ([]versionsapi.Version, error)
newMeasurements(ctx context.Context, csp cloudprovider.Provider, attestationVariant variant.Variant, images []versionsapi.Version) (map[string]measurements.M, error) newMeasurements(ctx context.Context, csp cloudprovider.Provider, attestationVariant variant.Variant, images []versionsapi.Version) (map[string]measurements.M, error)
newerVersions(ctx context.Context, allowedVersions []string) ([]versionsapi.Version, error) newerVersions(ctx context.Context, allowedVersions []string) ([]versionsapi.Version, error)
newCLIVersions(ctx context.Context) ([]string, error) newCLIVersions(ctx context.Context) ([]consemver.Semver, error)
filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []string, currentK8sVersion string) ([]string, error) filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []consemver.Semver, currentK8sVersion string) ([]consemver.Semver, error)
} }
type versionCollector struct { type versionCollector struct {
@ -366,7 +366,7 @@ type versionCollector struct {
rekor rekorVerifier rekor rekorVerifier
flags upgradeCheckFlags flags upgradeCheckFlags
versionsapi versionFetcher versionsapi versionFetcher
cliVersion string cliVersion consemver.Semver
log debugLog log debugLog
} }
@ -382,10 +382,10 @@ func (v *versionCollector) newMeasurements(ctx context.Context, csp cloudprovide
} }
type currentVersionInfo struct { type currentVersionInfo struct {
service string service consemver.Semver
image string image string
k8s string k8s string
cli string cli consemver.Semver
} }
func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionInfo, error) { func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionInfo, error) {
@ -418,20 +418,18 @@ func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionI
} }
type supportedVersionInfo struct { type supportedVersionInfo struct {
service string service consemver.Semver
image []versionsapi.Version image []versionsapi.Version
k8s []string k8s []string
// CLI versions including those incompatible with the current Kubernetes version. // CLI versions including those incompatible with the current Kubernetes version.
cli []string cli []consemver.Semver
// CLI versions compatible with the current Kubernetes version. // CLI versions compatible with the current Kubernetes version.
compatibleCLI []string compatibleCLI []consemver.Semver
} }
// supportedVersions returns slices of supported versions. // supportedVersions returns slices of supported versions.
func (v *versionCollector) supportedVersions(ctx context.Context, version, currentK8sVersion string) (supportedVersionInfo, error) { func (v *versionCollector) supportedVersions(ctx context.Context, version, currentK8sVersion string) (supportedVersionInfo, error) {
k8sVersions := versions.SupportedK8sVersions() k8sVersions := versions.SupportedK8sVersions()
// Each CLI comes with a set of services that have the same version as the CLI.
serviceVersion := compatibility.EnsurePrefixV(constants.VersionInfo())
imageVersions, err := v.newImages(ctx, version) imageVersions, err := v.newImages(ctx, version)
if err != nil { if err != nil {
@ -447,7 +445,8 @@ func (v *versionCollector) supportedVersions(ctx context.Context, version, curre
} }
return supportedVersionInfo{ return supportedVersionInfo{
service: serviceVersion, // Each CLI comes with a set of services that have the same version as the CLI.
service: constants.BinaryVersion(),
image: imageVersions, image: imageVersions,
k8s: k8sVersions, k8s: k8sVersions,
cli: cliVersions, cli: cliVersions,
@ -462,7 +461,7 @@ func (v *versionCollector) newImages(ctx context.Context, version string) ([]ver
// additionally, we allow updates to the next minor version (e.g. 0.1.0 -> 0.2.0) // additionally, we allow updates to the next minor version (e.g. 0.1.0 -> 0.2.0)
// if the CLI minor version is newer than the cluster minor version // if the CLI minor version is newer than the cluster minor version
currentImageMinorVer := semver.MajorMinor(version) currentImageMinorVer := semver.MajorMinor(version)
currentCLIMinorVer := semver.MajorMinor(v.cliVersion) currentCLIMinorVer := semver.MajorMinor(v.cliVersion.String())
nextImageMinorVer, err := compatibility.NextMinorVersion(currentImageMinorVer) nextImageMinorVer, err := compatibility.NextMinorVersion(currentImageMinorVer)
if err != nil { if err != nil {
return nil, fmt.Errorf("calculating next image minor version: %w", err) return nil, fmt.Errorf("calculating next image minor version: %w", err)
@ -522,15 +521,15 @@ func (v *versionCollector) newerVersions(ctx context.Context, allowedVersions []
} }
type versionUpgrade struct { type versionUpgrade struct {
newServices string newServices consemver.Semver
newImages map[string]measurements.M newImages map[string]measurements.M
newKubernetes []string newKubernetes []string
newCLI []string newCLI []consemver.Semver
newCompatibleCLI []string newCompatibleCLI []consemver.Semver
currentServices string currentServices consemver.Semver
currentImage string currentImage string
currentKubernetes string currentKubernetes string
currentCLI string currentCLI consemver.Semver
} }
func (v *versionUpgrade) buildString() (string, error) { func (v *versionUpgrade) buildString() (string, error) {
@ -560,7 +559,7 @@ func (v *versionUpgrade) buildString() (string, error) {
fmt.Fprintln(&upgradeMsg, "") fmt.Fprintln(&upgradeMsg, "")
} }
if v.newServices != "" { if v.newServices != (consemver.Semver{}) {
upgradeMsg.WriteString(fmt.Sprintf(" Services: %s --> %s\n", v.currentServices, v.newServices)) upgradeMsg.WriteString(fmt.Sprintf(" Services: %s --> %s\n", v.currentServices, v.newServices))
} }
@ -572,12 +571,12 @@ func (v *versionUpgrade) buildString() (string, error) {
} }
// no upgrades available // no upgrades available
if v.newServices == "" && len(v.newImages) == 0 { if v.newServices == (consemver.Semver{}) && len(v.newImages) == 0 {
if len(v.newCompatibleCLI) > 0 { if len(v.newCompatibleCLI) > 0 {
result.WriteString(fmt.Sprintf("Newer CLI versions that are compatible with your cluster are: %s\n", strings.Join(v.newCompatibleCLI, " "))) result.WriteString(fmt.Sprintf("Newer CLI versions that are compatible with your cluster are: %s\n", strings.Join(consemver.ToStrings(v.newCompatibleCLI), " ")))
return result.String(), nil return result.String(), nil
} else if len(v.newCLI) > 0 { } else if len(v.newCLI) > 0 {
result.WriteString(fmt.Sprintf("There are newer CLIs available (%s), however, you need to upgrade your cluster's Kubernetes version first.\n", strings.Join(v.newCLI, " "))) result.WriteString(fmt.Sprintf("There are newer CLIs available (%s), however, you need to upgrade your cluster's Kubernetes version first.\n", strings.Join(consemver.ToStrings(v.newCLI), " ")))
return result.String(), nil return result.String(), nil
} }
} }
@ -589,7 +588,7 @@ func (v *versionUpgrade) buildString() (string, error) {
func (v *versionUpgrade) writeConfig(conf *config.Config, fileHandler file.Handler, configPath string) error { func (v *versionUpgrade) writeConfig(conf *config.Config, fileHandler file.Handler, configPath string) error {
// can't sort image map because maps are unsorted. services is only one string, k8s versions are sorted. // can't sort image map because maps are unsorted. services is only one string, k8s versions are sorted.
if v.newServices != "" { if v.newServices != (consemver.Semver{}) {
conf.MicroserviceVersion = v.newServices conf.MicroserviceVersion = v.newServices
} }
if len(v.newKubernetes) > 0 { if len(v.newKubernetes) > 0 {
@ -685,16 +684,12 @@ type versionFetcher interface {
} }
// newCLIVersions returns a list of versions of the CLI which are a valid upgrade. // newCLIVersions returns a list of versions of the CLI which are a valid upgrade.
func (v *versionCollector) newCLIVersions(ctx context.Context) ([]string, error) { func (v *versionCollector) newCLIVersions(ctx context.Context) ([]consemver.Semver, error) {
cliVersion, err := conSemver.New(constants.VersionInfo())
if err != nil {
return nil, fmt.Errorf("parsing current CLI version: %w", err)
}
list := versionsapi.List{ list := versionsapi.List{
Ref: v.flags.ref, Ref: v.flags.ref,
Stream: v.flags.stream, Stream: v.flags.stream,
Granularity: versionsapi.GranularityMajor, Granularity: versionsapi.GranularityMajor,
Base: fmt.Sprintf("v%d", cliVersion.Major), Base: fmt.Sprintf("v%d", constants.BinaryVersion().Major()),
Kind: versionsapi.VersionKindCLI, Kind: versionsapi.VersionKindCLI,
} }
minorList, err := v.versionsapi.FetchVersionList(ctx, list) minorList, err := v.versionsapi.FetchVersionList(ctx, list)
@ -704,7 +699,11 @@ func (v *versionCollector) newCLIVersions(ctx context.Context) ([]string, error)
var patchVersions []string var patchVersions []string
for _, version := range minorList.Versions { for _, version := range minorList.Versions {
if err := compatibility.IsValidUpgrade(v.cliVersion, version); err != nil { target, err := consemver.New(version)
if err != nil {
return nil, fmt.Errorf("parsing version %s: %w", version, err)
}
if err := target.IsUpgradeTo(v.cliVersion); err != nil {
v.log.Debugf("Skipping incompatible minor version %q: %s", version, err) v.log.Debugf("Skipping incompatible minor version %q: %s", version, err)
continue continue
} }
@ -722,24 +721,29 @@ func (v *versionCollector) newCLIVersions(ctx context.Context) ([]string, error)
patchVersions = append(patchVersions, patchList.Versions...) patchVersions = append(patchVersions, patchList.Versions...)
} }
semver.Sort(patchVersions) out, err := consemver.NewSlice(patchVersions)
if err != nil {
return nil, fmt.Errorf("parsing versions: %w", err)
}
return patchVersions, nil consemver.Sort(out)
return out, nil
} }
// filterCompatibleCLIVersions filters a list of CLI versions which are compatible with the current Kubernetes version. // filterCompatibleCLIVersions filters a list of CLI versions which are compatible with the current Kubernetes version.
func (v *versionCollector) filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []string, currentK8sVersion string) ([]string, error) { func (v *versionCollector) filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []consemver.Semver, currentK8sVersion string) ([]consemver.Semver, error) {
// filter out invalid upgrades and versions which are not compatible with the current Kubernetes version // filter out invalid upgrades and versions which are not compatible with the current Kubernetes version
var compatibleVersions []string var compatibleVersions []consemver.Semver
for _, version := range cliPatchVersions { for _, version := range cliPatchVersions {
if err := compatibility.IsValidUpgrade(v.cliVersion, version); err != nil { if err := version.IsUpgradeTo(v.cliVersion); err != nil {
v.log.Debugf("Skipping incompatible patch version %q: %s", version, err) v.log.Debugf("Skipping incompatible patch version %q: %s", version, err)
continue continue
} }
req := versionsapi.CLIInfo{ req := versionsapi.CLIInfo{
Ref: v.flags.ref, Ref: v.flags.ref,
Stream: v.flags.stream, Stream: v.flags.stream,
Version: version, Version: version.String(),
} }
info, err := v.versionsapi.FetchCLIInfo(ctx, req) info, err := v.versionsapi.FetchCLIInfo(ctx, req)
if err != nil { if err != nil {
@ -753,7 +757,7 @@ func (v *versionCollector) filterCompatibleCLIVersions(ctx context.Context, cliP
} }
} }
semver.Sort(compatibleVersions) consemver.Sort(compatibleVersions)
return compatibleVersions, nil return compatibleVersions, nil
} }

View File

@ -25,6 +25,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -40,30 +41,30 @@ func TestBuildString(t *testing.T) {
}{ }{
"update everything": { "update everything": {
upgrade: versionUpgrade{ upgrade: versionUpgrade{
newServices: "v2.5.0", newServices: consemver.NewFromInt(2, 5, 0, ""),
newImages: map[string]measurements.M{ newImages: map[string]measurements.M{
"v2.5.0": measurements.DefaultsFor(cloudprovider.QEMU, variant.QEMUVTPM{}), "v2.5.0": measurements.DefaultsFor(cloudprovider.QEMU, variant.QEMUVTPM{}),
}, },
newKubernetes: []string{"v1.24.12", "v1.25.6"}, newKubernetes: []string{"v1.24.12", "v1.25.6"},
newCLI: []string{"v2.5.0", "v2.6.0"}, newCLI: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
currentServices: "v2.4.0", currentServices: consemver.NewFromInt(2, 4, 0, ""),
currentImage: "v2.4.0", currentImage: "v2.4.0",
currentKubernetes: "v1.24.5", currentKubernetes: "v1.24.5",
currentCLI: "v2.4.0", currentCLI: consemver.NewFromInt(2, 4, 0, ""),
}, },
expected: "The following updates are available with this CLI:\n Kubernetes: v1.24.5 --> v1.24.12 v1.25.6\n Images:\n v2.4.0 --> v2.5.0\n Includes these measurements:\n 4:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 8:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 9:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 11:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 12:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 13:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 15:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n \n Services: v2.4.0 --> v2.5.0\n", expected: "The following updates are available with this CLI:\n Kubernetes: v1.24.5 --> v1.24.12 v1.25.6\n Images:\n v2.4.0 --> v2.5.0\n Includes these measurements:\n 4:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 8:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 9:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 11:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 12:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 13:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 15:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n \n Services: v2.4.0 --> v2.5.0\n",
}, },
"cli incompatible with K8s": { "cli incompatible with K8s": {
upgrade: versionUpgrade{ upgrade: versionUpgrade{
newCLI: []string{"v2.5.0", "v2.6.0"}, newCLI: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
currentCLI: "v2.4.0", currentCLI: consemver.NewFromInt(2, 4, 0, ""),
}, },
expected: "There are newer CLIs available (v2.5.0 v2.6.0), however, you need to upgrade your cluster's Kubernetes version first.\n", expected: "There are newer CLIs available (v2.5.0 v2.6.0), however, you need to upgrade your cluster's Kubernetes version first.\n",
}, },
"cli compatible with K8s": { "cli compatible with K8s": {
upgrade: versionUpgrade{ upgrade: versionUpgrade{
newCompatibleCLI: []string{"v2.5.0", "v2.6.0"}, newCompatibleCLI: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
currentCLI: "v2.4.0", currentCLI: consemver.NewFromInt(2, 4, 0, ""),
}, },
expected: "Newer CLI versions that are compatible with your cluster are: v2.5.0 v2.6.0\n", expected: "Newer CLI versions that are compatible with your cluster are: v2.5.0 v2.6.0\n",
}, },
@ -76,14 +77,14 @@ func TestBuildString(t *testing.T) {
}, },
"no upgrades": { "no upgrades": {
upgrade: versionUpgrade{ upgrade: versionUpgrade{
newServices: "", newServices: consemver.Semver{},
newImages: map[string]measurements.M{}, newImages: map[string]measurements.M{},
newKubernetes: []string{}, newKubernetes: []string{},
newCLI: []string{}, newCLI: []consemver.Semver{},
currentServices: "v2.5.0", currentServices: consemver.NewFromInt(2, 5, 0, ""),
currentImage: "v2.5.0", currentImage: "v2.5.0",
currentKubernetes: "v1.25.6", currentKubernetes: "v1.25.6",
currentCLI: "v2.5.0", currentCLI: consemver.NewFromInt(2, 5, 0, ""),
}, },
expected: "You are up to date.\n", expected: "You are up to date.\n",
}, },
@ -227,18 +228,18 @@ func TestUpgradeCheck(t *testing.T) {
Kind: versionsapi.VersionKindImage, Kind: versionsapi.VersionKindImage,
} }
collector := stubVersionCollector{ collector := stubVersionCollector{
supportedServicesVersions: "v2.5.0", supportedServicesVersions: consemver.NewFromInt(2, 5, 0, ""),
supportedImages: []versionsapi.Version{v2_3}, supportedImages: []versionsapi.Version{v2_3},
supportedImageVersions: map[string]measurements.M{ supportedImageVersions: map[string]measurements.M{
"v2.3.0": measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVES{}), "v2.3.0": measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVES{}),
}, },
supportedK8sVersions: []string{"v1.24.5", "v1.24.12", "v1.25.6"}, supportedK8sVersions: []string{"v1.24.5", "v1.24.12", "v1.25.6"},
currentServicesVersions: "v2.4.0", currentServicesVersions: consemver.NewFromInt(2, 4, 0, ""),
currentImageVersion: "v2.4.0", currentImageVersion: "v2.4.0",
currentK8sVersion: "v1.24.5", currentK8sVersion: "v1.24.5",
currentCLIVersion: "v2.4.0", currentCLIVersion: consemver.NewFromInt(2, 4, 0, ""),
images: []versionsapi.Version{v2_5}, images: []versionsapi.Version{v2_5},
newCLIVersionsList: []string{"v2.5.0", "v2.6.0"}, newCLIVersionsList: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
} }
testCases := map[string]struct { testCases := map[string]struct {
@ -305,18 +306,18 @@ func TestUpgradeCheck(t *testing.T) {
} }
type stubVersionCollector struct { type stubVersionCollector struct {
supportedServicesVersions string supportedServicesVersions consemver.Semver
supportedImages []versionsapi.Version supportedImages []versionsapi.Version
supportedImageVersions map[string]measurements.M supportedImageVersions map[string]measurements.M
supportedK8sVersions []string supportedK8sVersions []string
supportedCLIVersions []string supportedCLIVersions []consemver.Semver
currentServicesVersions string currentServicesVersions consemver.Semver
currentImageVersion string currentImageVersion string
currentK8sVersion string currentK8sVersion string
currentCLIVersion string currentCLIVersion consemver.Semver
images []versionsapi.Version images []versionsapi.Version
newCLIVersionsList []string newCLIVersionsList []consemver.Semver
newCompatibleCLIVersionsList []string newCompatibleCLIVersionsList []consemver.Semver
someErr error someErr error
} }
@ -350,11 +351,11 @@ func (s *stubVersionCollector) newerVersions(_ context.Context, _ []string) ([]v
return s.images, nil return s.images, nil
} }
func (s *stubVersionCollector) newCLIVersions(_ context.Context) ([]string, error) { func (s *stubVersionCollector) newCLIVersions(_ context.Context) ([]consemver.Semver, error) {
return s.newCLIVersionsList, nil return s.newCLIVersionsList, nil
} }
func (s *stubVersionCollector) filterCompatibleCLIVersions(_ context.Context, _ []string, _ string) ([]string, error) { func (s *stubVersionCollector) filterCompatibleCLIVersions(_ context.Context, _ []consemver.Semver, _ string) ([]consemver.Semver, error) {
return s.newCompatibleCLIVersionsList, nil return s.newCompatibleCLIVersionsList, nil
} }
@ -408,7 +409,7 @@ func TestNewCLIVersions(t *testing.T) {
} }
verCollector := func(minorList, patchList versionsapi.List, verListErr error) versionCollector { verCollector := func(minorList, patchList versionsapi.List, verListErr error) versionCollector {
return versionCollector{ return versionCollector{
cliVersion: "v0.1.0", cliVersion: consemver.NewFromInt(0, 1, 0, ""),
versionsapi: stubVersionFetcher{ versionsapi: stubVersionFetcher{
minorList: minorList, minorList: minorList,
patchList: patchList, patchList: patchList,
@ -451,7 +452,7 @@ func TestFilterCompatibleCLIVersions(t *testing.T) {
someErr := errors.New("some error") someErr := errors.New("some error")
verCollector := func(cliInfoErr error) versionCollector { verCollector := func(cliInfoErr error) versionCollector {
return versionCollector{ return versionCollector{
cliVersion: "v0.1.0", cliVersion: consemver.NewFromInt(0, 1, 0, ""),
versionsapi: stubVersionFetcher{ versionsapi: stubVersionFetcher{
cliInfoErr: cliInfoErr, cliInfoErr: cliInfoErr,
}, },
@ -460,16 +461,16 @@ func TestFilterCompatibleCLIVersions(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
verCollector versionCollector verCollector versionCollector
cliPatchVersions []string cliPatchVersions []consemver.Semver
wantErr bool wantErr bool
}{ }{
"works": { "works": {
verCollector: verCollector(nil), verCollector: verCollector(nil),
cliPatchVersions: []string{"v0.1.1"}, cliPatchVersions: []consemver.Semver{consemver.NewFromInt(0, 1, 1, "")},
}, },
"cli info error": { "cli info error": {
verCollector: verCollector(someErr), verCollector: verCollector(someErr),
cliPatchVersions: []string{"v0.1.1"}, cliPatchVersions: []consemver.Semver{consemver.NewFromInt(0, 1, 1, "")},
wantErr: true, wantErr: true,
}, },
} }

View File

@ -35,7 +35,7 @@ func runVersion(cmd *cobra.Command, _ []string) {
commit, state, date, goVersion, compiler, platform = parseStamp() commit, state, date, goVersion, compiler, platform = parseStamp()
} }
cmd.Printf("Version:\t%s (%s)\n", constants.VersionInfo(), constants.VersionBuild) cmd.Printf("Version:\t%s (%s)\n", constants.BinaryVersion().String(), constants.VersionBuild)
cmd.Printf("GitCommit:\t%s\n", commit) cmd.Printf("GitCommit:\t%s\n", commit)
cmd.Printf("GitTreeState:\t%s\n", state) cmd.Printf("GitTreeState:\t%s\n", state)
cmd.Printf("BuildDate:\t%s\n", date) cmd.Printf("BuildDate:\t%s\n", date)

View File

@ -30,7 +30,7 @@ func TestVersionCmd(t *testing.T) {
s, err := io.ReadAll(b) s, err := io.ReadAll(b)
assert.NoError(err) assert.NoError(err)
assert.Contains(string(s), constants.VersionInfo()) assert.Contains(string(s), constants.BinaryVersion().String())
} }
func TestParseBuildInfo(t *testing.T) { func TestParseBuildInfo(t *testing.T) {

View File

@ -459,6 +459,7 @@ go_test(
"//internal/deploy/helm", "//internal/deploy/helm",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
"//internal/semver",
"@com_github_pkg_errors//:errors", "@com_github_pkg_errors//:errors",
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",

View File

@ -77,7 +77,7 @@ func NewClient(client crdClient, kubeConfigPath, helmNamespace string, log debug
return &Client{kubectl: client, fs: fileHandler, actions: actions{config: actionConfig}, log: log}, nil return &Client{kubectl: client, fs: fileHandler, actions: actions{config: actionConfig}, log: log}, nil
} }
func (c *Client) shouldUpgrade(releaseName, newVersion string, force bool) error { func (c *Client) shouldUpgrade(releaseName string, newVersion semver.Semver, force bool) error {
currentVersion, err := c.currentVersion(releaseName) currentVersion, err := c.currentVersion(releaseName)
if err != nil { if err != nil {
return fmt.Errorf("getting version for %s: %w", releaseName, err) return fmt.Errorf("getting version for %s: %w", releaseName, err)
@ -88,14 +88,15 @@ func (c *Client) shouldUpgrade(releaseName, newVersion string, force bool) error
// This may break for cert-manager or cilium if we decide to upgrade more than one minor version at a time. // This may break for cert-manager or cilium if we decide to upgrade more than one minor version at a time.
// Leaving it as is since it is not clear to me what kind of sanity check we could do. // Leaving it as is since it is not clear to me what kind of sanity check we could do.
if !force { if !force {
if err := compatibility.IsValidUpgrade(currentVersion, newVersion); err != nil { if err := newVersion.IsUpgradeTo(currentVersion); err != nil {
return err return err
} }
} }
cliVersion := constants.BinaryVersion()
// at this point we conclude that the release should be upgraded. check that this CLI supports the upgrade. // at this point we conclude that the release should be upgraded. check that this CLI supports the upgrade.
if releaseName == constellationOperatorsInfo.releaseName || releaseName == constellationServicesInfo.releaseName { if releaseName == constellationOperatorsInfo.releaseName || releaseName == constellationServicesInfo.releaseName {
if compatibility.EnsurePrefixV(constants.VersionInfo()) != compatibility.EnsurePrefixV(newVersion) { if cliVersion.Compare(newVersion) != 0 {
return fmt.Errorf("this CLI only supports microservice version %s for upgrading", constants.VersionInfo()) return fmt.Errorf("this CLI only supports microservice version %s for upgrading", cliVersion.String())
} }
} }
c.log.Debugf("Upgrading %s from %s to %s", releaseName, currentVersion, newVersion) c.log.Debugf("Upgrading %s from %s to %s", releaseName, currentVersion, newVersion)
@ -118,13 +119,17 @@ func (c *Client) Upgrade(ctx context.Context, config *config.Config, idFile clus
} }
// define target version the chart is upgraded to // define target version the chart is upgraded to
var upgradeVersion string var upgradeVersion semver.Semver
if info == constellationOperatorsInfo || info == constellationServicesInfo { if info == constellationOperatorsInfo || info == constellationServicesInfo {
// ensure that the services chart has the same version as the CLI // ensure that the services chart has the same version as the CLI
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo())) updateVersions(chart, constants.BinaryVersion())
upgradeVersion = config.MicroserviceVersion upgradeVersion = config.MicroserviceVersion
} else { } else {
upgradeVersion = chart.Metadata.Version chartVersion, err := semver.New(chart.Metadata.Version)
if err != nil {
return fmt.Errorf("parsing chart version: %w", err)
}
upgradeVersion = chartVersion
} }
var invalidUpgrade *compatibility.InvalidUpgradeError var invalidUpgrade *compatibility.InvalidUpgradeError
@ -222,49 +227,51 @@ func (c *Client) Versions() (ServiceVersions, error) {
} }
res := ServiceVersions{ res := ServiceVersions{
cilium: compatibility.EnsurePrefixV(ciliumVersion), cilium: ciliumVersion,
certManager: compatibility.EnsurePrefixV(certManagerVersion), certManager: certManagerVersion,
constellationOperators: compatibility.EnsurePrefixV(operatorsVersion), constellationOperators: operatorsVersion,
constellationServices: compatibility.EnsurePrefixV(servicesVersion), constellationServices: servicesVersion,
awsLBController: awsLBVersion,
} }
if awsLBVersion != "" { if awsLBVersion != (semver.Semver{}) {
res.awsLBController = compatibility.EnsurePrefixV(awsLBVersion) res.awsLBController = awsLBVersion
} }
return res, nil return res, nil
} }
// currentVersion returns the version of the currently installed helm release. // currentVersion returns the version of the currently installed helm release.
func (c *Client) currentVersion(release string) (string, error) { func (c *Client) currentVersion(release string) (semver.Semver, error) {
rel, err := c.actions.listAction(release) rel, err := c.actions.listAction(release)
if err != nil { if err != nil {
return "", err return semver.Semver{}, err
} }
if len(rel) == 0 { if len(rel) == 0 {
return "", errReleaseNotFound return semver.Semver{}, errReleaseNotFound
} }
if len(rel) > 1 { if len(rel) > 1 {
return "", fmt.Errorf("multiple releases found for %s", release) return semver.Semver{}, fmt.Errorf("multiple releases found for %s", release)
} }
if rel[0] == nil || rel[0].Chart == nil || rel[0].Chart.Metadata == nil { if rel[0] == nil || rel[0].Chart == nil || rel[0].Chart.Metadata == nil {
return "", fmt.Errorf("received invalid release %s", release) return semver.Semver{}, fmt.Errorf("received invalid release %s", release)
} }
return rel[0].Chart.Metadata.Version, nil return semver.New(rel[0].Chart.Metadata.Version)
} }
// ServiceVersions bundles the versions of all services that are part of Constellation. // ServiceVersions bundles the versions of all services that are part of Constellation.
type ServiceVersions struct { type ServiceVersions struct {
cilium string cilium semver.Semver
certManager string certManager semver.Semver
constellationOperators string constellationOperators semver.Semver
constellationServices string constellationServices semver.Semver
awsLBController string awsLBController semver.Semver
} }
// NewServiceVersions returns a new ServiceVersions struct. // NewServiceVersions returns a new ServiceVersions struct.
func NewServiceVersions(cilium, certManager, constellationOperators, constellationServices string) ServiceVersions { func NewServiceVersions(cilium, certManager, constellationOperators, constellationServices semver.Semver) ServiceVersions {
return ServiceVersions{ return ServiceVersions{
cilium: cilium, cilium: cilium,
certManager: certManager, certManager: certManager,
@ -274,22 +281,22 @@ func NewServiceVersions(cilium, certManager, constellationOperators, constellati
} }
// Cilium returns the version of the Cilium release. // Cilium returns the version of the Cilium release.
func (s ServiceVersions) Cilium() string { func (s ServiceVersions) Cilium() semver.Semver {
return s.cilium return s.cilium
} }
// CertManager returns the version of the cert-manager release. // CertManager returns the version of the cert-manager release.
func (s ServiceVersions) CertManager() string { func (s ServiceVersions) CertManager() semver.Semver {
return s.certManager return s.certManager
} }
// ConstellationOperators returns the version of the constellation-operators release. // ConstellationOperators returns the version of the constellation-operators release.
func (s ServiceVersions) ConstellationOperators() string { func (s ServiceVersions) ConstellationOperators() semver.Semver {
return s.constellationOperators return s.constellationOperators
} }
// ConstellationServices returns the version of the constellation-services release. // ConstellationServices returns the version of the constellation-services release.
func (s ServiceVersions) ConstellationServices() string { func (s ServiceVersions) ConstellationServices() semver.Semver {
return s.constellationServices return s.constellationServices
} }
@ -385,12 +392,8 @@ func (c *Client) applyMigrations(ctx context.Context, releaseName string, values
if err != nil { if err != nil {
return fmt.Errorf("getting %s version: %w", releaseName, err) return fmt.Errorf("getting %s version: %w", releaseName, err)
} }
currentV, err := semver.New(current)
if err != nil {
return fmt.Errorf("parsing current version: %w", err)
}
if currentV.Major == 2 && currentV.Minor == 8 { if current.Major() == 2 && current.Minor() == 8 {
// Rename/change the following function to implement any necessary migrations. // Rename/change the following function to implement any necessary migrations.
return migrateFrom2_8(ctx, values, conf, c.kubectl) return migrateFrom2_8(ctx, values, conf, c.kubectl)
} }

View File

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
@ -49,7 +50,9 @@ func TestShouldUpgrade(t *testing.T) {
chart, err := loadChartsDir(helmFS, certManagerInfo.path) chart, err := loadChartsDir(helmFS, certManagerInfo.path)
require.NoError(err) require.NoError(err)
err = client.shouldUpgrade(certManagerInfo.releaseName, chart.Metadata.Version, false) chartVersion, err := semver.New(chart.Metadata.Version)
require.NoError(err)
err = client.shouldUpgrade(certManagerInfo.releaseName, chartVersion, false)
if tc.wantError { if tc.wantError {
tc.assertCorrectError(t, err) tc.assertCorrectError(t, err)
return return

View File

@ -25,10 +25,10 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/helm/imageversion" "github.com/edgelesssys/constellation/v2/cli/internal/helm/imageversion"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/deploy/helm" "github.com/edgelesssys/constellation/v2/internal/deploy/helm"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
) )
@ -175,15 +175,15 @@ func (i *ChartLoader) loadRelease(info chartInfo, helmWaitMode helm.WaitMode) (h
case certManagerInfo.releaseName: case certManagerInfo.releaseName:
values = i.loadCertManagerValues() values = i.loadCertManagerValues()
case constellationOperatorsInfo.releaseName: case constellationOperatorsInfo.releaseName:
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo())) updateVersions(chart, constants.BinaryVersion())
values = i.loadOperatorsValues() values = i.loadOperatorsValues()
case constellationServicesInfo.releaseName: case constellationServicesInfo.releaseName:
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo())) updateVersions(chart, constants.BinaryVersion())
values = i.loadConstellationServicesValues() values = i.loadConstellationServicesValues()
case awsLBControllerInfo.releaseName: case awsLBControllerInfo.releaseName:
values = i.loadAWSLBControllerValues() values = i.loadAWSLBControllerValues()
case csiInfo.releaseName: case csiInfo.releaseName:
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo())) updateVersions(chart, constants.BinaryVersion())
values = i.loadCSIValues() values = i.loadCSIValues()
} }
@ -378,19 +378,19 @@ func extendConstellationServicesValues(
} }
// updateVersions changes all versions of direct dependencies that are set to "0.0.0" to newVersion. // updateVersions changes all versions of direct dependencies that are set to "0.0.0" to newVersion.
func updateVersions(chart *chart.Chart, newVersion string) { func updateVersions(chart *chart.Chart, newVersion semver.Semver) {
chart.Metadata.Version = newVersion chart.Metadata.Version = newVersion.String()
selectedDeps := chart.Metadata.Dependencies selectedDeps := chart.Metadata.Dependencies
for i := range selectedDeps { for i := range selectedDeps {
if selectedDeps[i].Version == "0.0.0" { if selectedDeps[i].Version == "0.0.0" {
selectedDeps[i].Version = newVersion selectedDeps[i].Version = newVersion.String()
} }
} }
deps := chart.Dependencies() deps := chart.Dependencies()
for i := range deps { for i := range deps {
if deps[i].Metadata.Version == "0.0.0" { if deps[i].Metadata.Version == "0.0.0" {
deps[i].Metadata.Version = newVersion deps[i].Metadata.Version = newVersion.String()
} }
} }
} }

View File

@ -52,7 +52,7 @@ func main() {
flag.Parse() flag.Parse()
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity)) log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With(zap.String("version", constants.VersionInfo()), zap.String("cloudProvider", *csp)). log.With(zap.String("version", constants.BinaryVersion().String()), zap.String("cloudProvider", *csp)).
Infof("Starting disk-mapper") Infof("Starting disk-mapper")
// set up quote issuer for aTLS connections // set up quote issuer for aTLS connections

View File

@ -12,6 +12,7 @@ go_library(
deps = [ deps = [
"//internal/constants", "//internal/constants",
"//internal/logger", "//internal/logger",
"//internal/semver",
"@sh_helm_helm_v3//pkg/action", "@sh_helm_helm_v3//pkg/action",
"@sh_helm_helm_v3//pkg/cli", "@sh_helm_helm_v3//pkg/cli",
], ],

View File

@ -14,41 +14,42 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/semver"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/cli"
) )
func servicesVersion(t *testing.T) (string, error) { func servicesVersion(t *testing.T) (semver.Semver, error) {
t.Helper() t.Helper()
log := logger.NewTest(t) log := logger.NewTest(t)
settings := cli.New() settings := cli.New()
settings.KubeConfig = "constellation-admin.conf" settings.KubeConfig = "constellation-admin.conf"
actionConfig := &action.Configuration{} actionConfig := &action.Configuration{}
if err := actionConfig.Init(settings.RESTClientGetter(), constants.HelmNamespace, "secret", log.Infof); err != nil { if err := actionConfig.Init(settings.RESTClientGetter(), constants.HelmNamespace, "secret", log.Infof); err != nil {
return "", fmt.Errorf("initializing config: %w", err) return semver.Semver{}, fmt.Errorf("initializing config: %w", err)
} }
return currentVersion(actionConfig, "constellation-services") return currentVersion(actionConfig, "constellation-services")
} }
func currentVersion(cfg *action.Configuration, release string) (string, error) { func currentVersion(cfg *action.Configuration, release string) (semver.Semver, error) {
action := action.NewList(cfg) action := action.NewList(cfg)
action.Filter = release action.Filter = release
rel, err := action.Run() rel, err := action.Run()
if err != nil { if err != nil {
return "", err return semver.Semver{}, err
} }
if len(rel) == 0 { if len(rel) == 0 {
return "", fmt.Errorf("release %s not found", release) return semver.Semver{}, fmt.Errorf("release %s not found", release)
} }
if len(rel) > 1 { if len(rel) > 1 {
return "", fmt.Errorf("multiple releases found for %s", release) return semver.Semver{}, fmt.Errorf("multiple releases found for %s", release)
} }
if rel[0] == nil || rel[0].Chart == nil || rel[0].Chart.Metadata == nil { if rel[0] == nil || rel[0].Chart == nil || rel[0].Chart.Metadata == nil {
return "", fmt.Errorf("received invalid release %s", release) return semver.Semver{}, fmt.Errorf("received invalid release %s", release)
} }
return rel[0].Chart.Metadata.Version, nil return semver.New(rel[0].Chart.Metadata.Version)
} }

View File

@ -293,11 +293,13 @@ func writeUpgradeConfig(require *require.Assertions, image string, kubernetes st
require.NoError(err) require.NoError(err)
} }
var microserviceVersion string var microserviceVersion semver.Semver
if microservices == "" { if microservices == "" {
microserviceVersion = defaultConfig.MicroserviceVersion microserviceVersion = defaultConfig.MicroserviceVersion
} else { } else {
microserviceVersion = microservices version, err := semver.New(microservices)
require.NoError(err)
microserviceVersion = version
} }
log.Printf("Setting K8s version: %s\n", kubernetesVersion.String()) log.Printf("Setting K8s version: %s\n", kubernetesVersion.String())
@ -330,10 +332,8 @@ func runUpgradeCheck(require *require.Assertions, cli, targetKubernetes string)
require.Contains(string(stdout), targetKubernetes, fmt.Sprintf("Expected Kubernetes version %s in output.", targetKubernetes)) require.Contains(string(stdout), targetKubernetes, fmt.Sprintf("Expected Kubernetes version %s in output.", targetKubernetes))
} }
cliVersion, err := semver.New(constants.VersionInfo())
require.NoError(err)
require.Contains(string(stdout), "Services:") require.Contains(string(stdout), "Services:")
require.Contains(string(stdout), fmt.Sprintf("--> %s", cliVersion.String())) require.Contains(string(stdout), fmt.Sprintf("--> %s", constants.BinaryVersion().String()))
log.Println(string(stdout)) log.Println(string(stdout))
} }
@ -403,7 +403,7 @@ func testStatusEventuallyWorks(t *testing.T, cli string, timeout time.Duration)
}, timeout, time.Minute) }, timeout, time.Minute)
} }
func testMicroservicesEventuallyHaveVersion(t *testing.T, wantMicroserviceVersion string, timeout time.Duration) { func testMicroservicesEventuallyHaveVersion(t *testing.T, wantMicroserviceVersion semver.Semver, timeout time.Duration) {
require.Eventually(t, func() bool { require.Eventually(t, func() bool {
version, err := servicesVersion(t) version, err := servicesVersion(t)
if err != nil { if err != nil {
@ -460,7 +460,7 @@ func testNodesEventuallyHaveVersion(t *testing.T, k *kubernetes.Clientset, targe
type versionContainer struct { type versionContainer struct {
imageRef string imageRef string
kubernetes semver.Semver kubernetes semver.Semver
microservices string microservices semver.Semver
} }
// runCommandWithSeparateOutputs runs the given command while separating buffers for // runCommandWithSeparateOutputs runs the given command while separating buffers for

View File

@ -29,6 +29,7 @@ go_library(
"//internal/config/instancetypes", "//internal/config/instancetypes",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/semver",
"//internal/versions", "//internal/versions",
"@com_github_go_playground_locales//en", "@com_github_go_playground_locales//en",
"@com_github_go_playground_universal_translator//:universal-translator", "@com_github_go_playground_universal_translator//:universal-translator",

View File

@ -40,10 +40,10 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config/imageversion" "github.com/edgelesssys/constellation/v2/internal/config/imageversion"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
) )
@ -63,7 +63,7 @@ type Config struct {
Version string `yaml:"version" validate:"eq=v3"` Version string `yaml:"version" validate:"eq=v3"`
// description: | // description: |
// Machine image version used to create Constellation nodes. // Machine image version used to create Constellation nodes.
Image string `yaml:"image" validate:"required,version_compatibility"` Image string `yaml:"image" validate:"required,image_compatibility"`
// description: | // description: |
// Name of the cluster. // Name of the cluster.
Name string `yaml:"name" validate:"valid_name,required"` Name string `yaml:"name" validate:"valid_name,required"`
@ -75,7 +75,7 @@ type Config struct {
KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"` KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
// description: | // description: |
// Microservice version to be installed into the cluster. Defaults to the version of the CLI. // Microservice version to be installed into the cluster. Defaults to the version of the CLI.
MicroserviceVersion string `yaml:"microserviceVersion" validate:"required,version_compatibility"` MicroserviceVersion semver.Semver `yaml:"microserviceVersion" validate:"required"`
// description: | // description: |
// DON'T USE IN PRODUCTION: enable debug mode and use debug images. // DON'T USE IN PRODUCTION: enable debug mode and use debug images.
DebugCluster *bool `yaml:"debugCluster" validate:"required"` DebugCluster *bool `yaml:"debugCluster" validate:"required"`
@ -315,7 +315,7 @@ func Default() *Config {
Version: Version3, Version: Version3,
Image: defaultImage, Image: defaultImage,
Name: defaultName, Name: defaultName,
MicroserviceVersion: compatibility.EnsurePrefixV(constants.VersionInfo()), MicroserviceVersion: constants.BinaryVersion(),
KubernetesVersion: string(versions.Default), KubernetesVersion: string(versions.Default),
StateDiskSizeGB: 30, StateDiskSizeGB: 30,
DebugCluster: toPtr(false), DebugCluster: toPtr(false),
@ -725,7 +725,7 @@ func (c *Config) Validate(force bool) error {
return err return err
} }
if err := validate.RegisterTranslation("version_compatibility", trans, registerVersionCompatibilityError, translateVersionCompatibilityError); err != nil { if err := validate.RegisterTranslation("image_compatibility", trans, registerImageCompatibilityError, translateImageCompatibilityError); err != nil {
return err return err
} }
@ -750,7 +750,7 @@ func (c *Config) Validate(force bool) error {
if force { if force {
versionCompatibilityValidator = returnsTrue versionCompatibilityValidator = returnsTrue
} }
if err := validate.RegisterValidation("version_compatibility", versionCompatibilityValidator); err != nil { if err := validate.RegisterValidation("image_compatibility", versionCompatibilityValidator); err != nil {
return err return err
} }
@ -800,6 +800,18 @@ func (c *Config) Validate(force bool) error {
validate.RegisterStructValidation(validateMeasurement, measurements.Measurement{}) validate.RegisterStructValidation(validateMeasurement, measurements.Measurement{})
validate.RegisterStructValidation(validateAttestation, AttestationConfig{}) validate.RegisterStructValidation(validateAttestation, AttestationConfig{})
if !force {
// Validating MicroserviceVersion separately is required since it is a custom type.
// The validation pkg we use does not allow accessing the field name during struct validation.
// Because of this we can't print the offending field name in the error message, resulting in
// suboptimal UX. Adding the field name to the struct validation of Semver would make it
// impossible to use Semver for other fields.
if err := validateMicroserviceVersion(constants.BinaryVersion(), c.MicroserviceVersion); err != nil {
msg := "microserviceVersion: " + msgFromCompatibilityError(err, constants.BinaryVersion().String(), c.MicroserviceVersion.String())
return &ValidationError{validationErrMsgs: []string{msg}}
}
}
err := validate.Struct(c) err := validate.Struct(c)
if err == nil { if err == nil {
return nil return nil

View File

@ -61,7 +61,7 @@ func init() {
ConfigDoc.Fields[4].Description = "Kubernetes version to be installed into the cluster." ConfigDoc.Fields[4].Description = "Kubernetes version to be installed into the cluster."
ConfigDoc.Fields[4].Comments[encoder.LineComment] = "Kubernetes version to be installed into the cluster." ConfigDoc.Fields[4].Comments[encoder.LineComment] = "Kubernetes version to be installed into the cluster."
ConfigDoc.Fields[5].Name = "microserviceVersion" ConfigDoc.Fields[5].Name = "microserviceVersion"
ConfigDoc.Fields[5].Type = "string" ConfigDoc.Fields[5].Type = "Semver"
ConfigDoc.Fields[5].Note = "" ConfigDoc.Fields[5].Note = ""
ConfigDoc.Fields[5].Description = "Microservice version to be installed into the cluster. Defaults to the version of the CLI." ConfigDoc.Fields[5].Description = "Microservice version to be installed into the cluster. Defaults to the version of the CLI."
ConfigDoc.Fields[5].Comments[encoder.LineComment] = "Microservice version to be installed into the cluster. Defaults to the version of the CLI." ConfigDoc.Fields[5].Comments[encoder.LineComment] = "Microservice version to be installed into the cluster. Defaults to the version of the CLI."

View File

@ -140,7 +140,7 @@ func TestNew(t *testing.T) {
func modifyConfigForAzureToPassValidate(c *Config) { func modifyConfigForAzureToPassValidate(c *Config) {
c.RemoveProviderAndAttestationExcept(cloudprovider.Azure) c.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
c.Image = "v" + constants.VersionInfo() c.Image = constants.BinaryVersion().String()
c.Provider.Azure.SubscriptionID = "11111111-1111-1111-1111-111111111111" c.Provider.Azure.SubscriptionID = "11111111-1111-1111-1111-111111111111"
c.Provider.Azure.TenantID = "11111111-1111-1111-1111-111111111111" c.Provider.Azure.TenantID = "11111111-1111-1111-1111-111111111111"
c.Provider.Azure.Location = "westus" c.Provider.Azure.Location = "westus"
@ -308,13 +308,25 @@ func TestValidate(t *testing.T) {
cnf.Image = "" cnf.Image = ""
ver, err := semver.New(versions.SupportedK8sVersions()[0]) ver, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(t, err) require.NoError(t, err)
ver.Patch = ver.Patch - 1 ver = semver.NewFromInt(ver.Major(), ver.Minor(), ver.Patch()-1, "")
cnf.KubernetesVersion = ver.String() cnf.KubernetesVersion = ver.String()
return cnf return cnf
}(), }(),
wantErr: true, wantErr: true,
wantErrCount: defaultErrCount, wantErrCount: defaultErrCount,
}, },
"microservices violate version drift": {
cnf: func() *Config {
cnf := Default()
cnf.Image = ""
cliVersion := constants.BinaryVersion()
cnf.MicroserviceVersion = semver.NewFromInt(cliVersion.Major()+2, cliVersion.Minor(), cliVersion.Patch(), "")
return cnf
}(),
wantErr: true,
// This is a very different value from the other error counts because of the way we are checking MicroserviceVersions.
wantErrCount: 1,
},
"v0 is one error": { "v0 is one error": {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
@ -350,7 +362,7 @@ func TestValidate(t *testing.T) {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
cnf.RemoveProviderAndAttestationExcept(cloudprovider.Azure) cnf.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
cnf.Image = "v" + constants.VersionInfo() cnf.Image = constants.BinaryVersion().String()
az := cnf.Provider.Azure az := cnf.Provider.Azure
az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab" az.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
az.TenantID = "01234567-0123-0123-0123-0123456789ab" az.TenantID = "01234567-0123-0123-0123-0123456789ab"
@ -411,7 +423,7 @@ func TestValidate(t *testing.T) {
cnf: func() *Config { cnf: func() *Config {
cnf := Default() cnf := Default()
cnf.RemoveProviderAndAttestationExcept(cloudprovider.GCP) cnf.RemoveProviderAndAttestationExcept(cloudprovider.GCP)
cnf.Image = "v" + constants.VersionInfo() cnf.Image = constants.BinaryVersion().String()
gcp := cnf.Provider.GCP gcp := cnf.Provider.GCP
gcp.Region = "test-region" gcp.Region = "test-region"
gcp.Project = "test-project" gcp.Project = "test-project"

View File

@ -11,5 +11,6 @@ go_library(
"//internal/attestation/variant", "//internal/attestation/variant",
"//internal/config", "//internal/config",
"//internal/file", "//internal/file",
"//internal/semver",
], ],
) )

View File

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/semver"
) )
const ( const (
@ -28,11 +29,11 @@ const (
// Config defines configuration used by CLI. // Config defines configuration used by CLI.
type Config struct { type Config struct {
Version string `yaml:"version" validate:"eq=v2"` Version string `yaml:"version" validate:"eq=v2"`
Image string `yaml:"image" validate:"required,version_compatibility"` Image string `yaml:"image" validate:"required,image_compatibility"`
Name string `yaml:"name" validate:"valid_name,required"` Name string `yaml:"name" validate:"valid_name,required"`
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"` StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"` KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
MicroserviceVersion string `yaml:"microserviceVersion" validate:"required,version_compatibility"` MicroserviceVersion string `yaml:"microserviceVersion" validate:"required"`
DebugCluster *bool `yaml:"debugCluster" validate:"required"` DebugCluster *bool `yaml:"debugCluster" validate:"required"`
AttestationVariant string `yaml:"attestationVariant,omitempty" validate:"valid_attestation_variant"` AttestationVariant string `yaml:"attestationVariant,omitempty" validate:"valid_attestation_variant"`
Provider ProviderConfig `yaml:"provider" validate:"dive"` Provider ProviderConfig `yaml:"provider" validate:"dive"`
@ -195,6 +196,11 @@ func V2ToV3(path string, fileHandler file.Handler) error {
return fmt.Errorf("reading config file %s using v2 format: %w", path, err) return fmt.Errorf("reading config file %s using v2 format: %w", path, err)
} }
microserviceVersion, err := semver.New(cfgV2.MicroserviceVersion)
if err != nil {
return fmt.Errorf("parsing microservice version: %w", err)
}
// Migrate to new format // Migrate to new format
var cfgV3 config.Config var cfgV3 config.Config
cfgV3.Version = config.Version3 cfgV3.Version = config.Version3
@ -202,7 +208,7 @@ func V2ToV3(path string, fileHandler file.Handler) error {
cfgV3.Name = cfgV2.Name cfgV3.Name = cfgV2.Name
cfgV3.StateDiskSizeGB = cfgV2.StateDiskSizeGB cfgV3.StateDiskSizeGB = cfgV2.StateDiskSizeGB
cfgV3.KubernetesVersion = cfgV2.KubernetesVersion cfgV3.KubernetesVersion = cfgV2.KubernetesVersion
cfgV3.MicroserviceVersion = cfgV2.MicroserviceVersion cfgV3.MicroserviceVersion = microserviceVersion
cfgV3.DebugCluster = cfgV2.DebugCluster cfgV3.DebugCluster = cfgV2.DebugCluster
switch { switch {

View File

@ -27,6 +27,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes" "github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions"
) )
@ -86,11 +87,11 @@ func translateInvalidK8sVersionError(ut ut.Translator, fe validator.FieldError)
configured = compatibility.EnsurePrefixV(configured) configured = compatibility.EnsurePrefixV(configured)
switch { switch {
case !semver.IsValid(configured): case !semver.IsValid(configured):
errorMsg = "The configured version is not a valid semantic version" errorMsg = "The configured version is not a valid semantic version\n"
case semver.Compare(configured, minVersion) == -1: case semver.Compare(configured, minVersion) == -1:
errorMsg = fmt.Sprintf("The configured version %s is older than the oldest version supported by this CLI: %s", configured, minVersion) errorMsg = fmt.Sprintf("The configured version %s is older than the oldest version supported by this CLI: %s\n", configured, minVersion)
case semver.Compare(configured, maxVersion) == 1: case semver.Compare(configured, maxVersion) == 1:
errorMsg = fmt.Sprintf("The configured version %s is newer than the newest version supported by this CLI: %s", configured, maxVersion) errorMsg = fmt.Sprintf("The configured version %s is newer than the newest version supported by this CLI: %s\n", configured, maxVersion)
} }
errorMsg = errorMsg + fmt.Sprintf("Supported versions: %s", strings.Join(validVersionsSorted, " ")) errorMsg = errorMsg + fmt.Sprintf("Supported versions: %s", strings.Join(validVersionsSorted, " "))
@ -492,44 +493,21 @@ func K8sVersionFromMajorMinor(version string) string {
} }
} }
func registerVersionCompatibilityError(ut ut.Translator) error { func registerImageCompatibilityError(ut ut.Translator) error {
return ut.Add("version_compatibility", "{0} specifies an invalid version: {1}", true) return ut.Add("image_compatibility", "{0} specifies an invalid version: {1}", true)
}
func translateVersionCompatibilityError(ut ut.Translator, fe validator.FieldError) string {
binaryVersion := constants.VersionInfo()
err := validateVersionCompatibilityHelper(binaryVersion, fe.Field(), fe.Value().(string))
var msg string
switch {
case errors.Is(err, compatibility.ErrSemVer):
msg = fmt.Sprintf("configured version (%s) does not adhere to SemVer syntax", fe.Value().(string))
case errors.Is(err, compatibility.ErrMajorMismatch):
msg = fmt.Sprintf("the CLI's major version (%s) has to match your configured major version (%s). Use --force to ignore the version mismatch.", constants.VersionInfo(), fe.Value().(string))
case errors.Is(err, compatibility.ErrMinorDrift):
msg = fmt.Sprintf("the CLI's minor version (%s) and the configured version (%s) are more than one minor version apart. Use --force to ignore the version mismatch.", constants.VersionInfo(), fe.Value().(string))
case errors.Is(err, compatibility.ErrOutdatedCLI):
msg = fmt.Sprintf("the CLI's version (%s) is older than the configured version (%s). Use --force to ignore the version mismatch.", constants.VersionInfo(), fe.Value().(string))
default:
msg = err.Error()
}
t, _ := ut.T("version_compatibility", fe.Field(), msg)
return t
} }
// Check that the validated field and the CLI version are not more than one minor version apart. // Check that the validated field and the CLI version are not more than one minor version apart.
func validateVersionCompatibility(fl validator.FieldLevel) bool { func validateVersionCompatibility(fl validator.FieldLevel) bool {
binaryVersion := constants.VersionInfo() binaryVersion := constants.BinaryVersion()
if err := validateVersionCompatibilityHelper(binaryVersion, fl.FieldName(), fl.Field().String()); err != nil { if err := validateImageCompatibilityHelper(binaryVersion, fl.FieldName(), fl.Field().String()); err != nil {
return false return false
} }
return true return true
} }
func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVersion string) error { func validateImageCompatibilityHelper(binaryVersion consemver.Semver, fieldName, configuredVersion string) error {
if fieldName == "image" { if fieldName == "image" {
imageVersion, err := versionsapi.NewVersionFromShortPath(configuredVersion, versionsapi.VersionKindImage) imageVersion, err := versionsapi.NewVersionFromShortPath(configuredVersion, versionsapi.VersionKindImage)
if err != nil { if err != nil {
@ -538,15 +516,51 @@ func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVers
configuredVersion = imageVersion.Version configuredVersion = imageVersion.Version
} }
if fieldName == "microserviceVersion" { return compatibility.BinaryWith(binaryVersion.String(), configuredVersion)
cliVersion := compatibility.EnsurePrefixV(binaryVersion) }
serviceVersion := compatibility.EnsurePrefixV(configuredVersion)
if semver.Compare(cliVersion, serviceVersion) == -1 { func translateImageCompatibilityError(ut ut.Translator, fe validator.FieldError) string {
return fmt.Errorf("the CLI's version (%s) is older than the configured version (%s)", cliVersion, serviceVersion) binaryVersion := constants.BinaryVersion()
err := validateImageCompatibilityHelper(binaryVersion, fe.Field(), fe.Value().(string))
msg := msgFromCompatibilityError(err, binaryVersion.String(), fe.Value().(string))
t, _ := ut.T("image_compatibility", fe.Field(), msg)
return t
}
// msgFromCompatibilityError translates compatibility errors into user-facing error messages.
func msgFromCompatibilityError(err error, binaryVersion, fieldValue string) string {
switch {
case errors.Is(err, compatibility.ErrSemVer):
return fmt.Sprintf("configured version (%s) does not adhere to SemVer syntax", fieldValue)
case errors.Is(err, compatibility.ErrMajorMismatch):
return fmt.Sprintf("the CLI's major version (%s) has to match your configured major version (%s). Use --force to ignore the version mismatch.", binaryVersion, fieldValue)
case errors.Is(err, compatibility.ErrMinorDrift):
return fmt.Sprintf("the CLI's minor version (%s) and the configured version (%s) are more than one minor version apart. Use --force to ignore the version mismatch.", binaryVersion, fieldValue)
case errors.Is(err, compatibility.ErrOutdatedCLI):
return fmt.Sprintf("the CLI's version (%s) is older than the configured version (%s). Use --force to ignore the version mismatch.", binaryVersion, fieldValue)
default:
return err.Error()
} }
}
func validateMicroserviceVersion(binaryVersion, version consemver.Semver) error {
// Major versions always have to match.
if binaryVersion.Major() != version.Major() {
return compatibility.ErrMajorMismatch
}
// Allow newer CLIs (for upgrades), but dissallow newer service versions.
if binaryVersion.Compare(version) == -1 {
return compatibility.ErrOutdatedCLI
}
// Abort if minor version drift between CLI and versionA value is greater than 1.
if binaryVersion.Minor()-version.Minor() > 1 {
return compatibility.ErrMinorDrift
} }
return compatibility.BinaryWith(binaryVersion, configuredVersion) return nil
} }
func returnsTrue(_ validator.FieldLevel) bool { func returnsTrue(_ validator.FieldLevel) bool {

View File

@ -9,26 +9,27 @@ package config
import ( import (
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// TestValidateVersionCompatibilityHelper checks that basic version and image short paths are correctly validated. // TestValidateVersionCompatibilityHelper checks that basic version and image short paths are correctly validated.
func TestValidateVersionCompatibilityHelper(t *testing.T) { func TestValidateVersionCompatibilityHelper(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
cli string cli semver.Semver
target string target string
wantError bool wantError bool
}{ }{
"full version works": { "full version works": {
cli: "0.1.0", cli: semver.NewFromInt(0, 1, 0, ""),
target: "v0.0.0", target: "v0.0.0",
}, },
"short path works": { "short path works": {
cli: "0.1.0", cli: semver.NewFromInt(0, 1, 0, ""),
target: "ref/main/stream/debug/v0.0.0-pre.0.20230109121528-d24fac00f018", target: "ref/main/stream/debug/v0.0.0-pre.0.20230109121528-d24fac00f018",
}, },
"minor version difference > 1": { "minor version difference > 1": {
cli: "0.0.0", cli: semver.NewFromInt(0, 0, 0, ""),
target: "ref/main/stream/debug/v0.2.0-pre.0.20230109121528-d24fac00f018", target: "ref/main/stream/debug/v0.2.0-pre.0.20230109121528-d24fac00f018",
wantError: true, wantError: true,
}, },
@ -38,7 +39,43 @@ func TestValidateVersionCompatibilityHelper(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
err := validateVersionCompatibilityHelper(tc.cli, "image", tc.target) err := validateImageCompatibilityHelper(tc.cli, "image", tc.target)
if tc.wantError {
assert.Error(err)
return
}
assert.NoError(err)
})
}
}
func TestValidateMicroserviceVersion(t *testing.T) {
testCases := map[string]struct {
cli semver.Semver
services semver.Semver
wantError bool
}{
"success": {
cli: semver.NewFromInt(0, 1, 0, ""),
services: semver.NewFromInt(0, 0, 0, ""),
},
"minor version difference > 1": {
cli: semver.NewFromInt(0, 0, 0, ""),
services: semver.NewFromInt(0, 2, 0, "pre.0.20230109121528-d24fac00f018"),
wantError: true,
},
"major version difference": {
cli: semver.NewFromInt(0, 0, 0, ""),
services: semver.NewFromInt(1, 0, 0, ""),
wantError: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
err := validateMicroserviceVersion(tc.cli, tc.services)
if tc.wantError { if tc.wantError {
assert.Error(err) assert.Error(err)
return return

View File

@ -17,4 +17,5 @@ go_library(
"timestamp": "{STABLE_STAMP_TIME}", "timestamp": "{STABLE_STAMP_TIME}",
"versionInfo": "{STABLE_STAMP_VERSION}", "versionInfo": "{STABLE_STAMP_VERSION}",
}, },
deps = ["//internal/semver"],
) )

View File

@ -11,7 +11,10 @@ Constants should never be overwritable by command line flags or configuration fi
package constants package constants
import ( import (
"fmt"
"time" "time"
"github.com/edgelesssys/constellation/v2/internal/semver"
) )
const ( const (
@ -231,9 +234,15 @@ b92PDCpM7FZAINQF88s1TZS/HmRXYk62UJ4eqPduvUnJmXhNikhLbMi6fw==
` `
) )
// VersionInfo returns the version of a binary. // BinaryVersion returns the version of this Binary.
func VersionInfo() string { func BinaryVersion() semver.Semver {
return versionInfo version, err := semver.New(versionInfo)
if err != nil {
// This is not user input, unrecoverable, should never happen.
panic(fmt.Sprintf("parsing embedded version information: %s", err))
}
return version
} }
// Timestamp returns the commit timestamp of a binary. // Timestamp returns the commit timestamp of a binary.

View File

@ -7,7 +7,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/semver", importpath = "github.com/edgelesssys/constellation/v2/internal/semver",
visibility = ["//:__subpackages__"], visibility = ["//:__subpackages__"],
deps = [ deps = [
"//internal/constants", "//internal/compatibility",
"@org_golang_x_mod//semver", "@org_golang_x_mod//semver",
], ],
) )
@ -19,5 +19,6 @@ go_test(
deps = [ deps = [
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require", "@com_github_stretchr_testify//require",
"@in_gopkg_yaml_v3//:yaml_v3",
], ],
) )

View File

@ -6,24 +6,37 @@ SPDX-License-Identifier: AGPL-3.0-only
/* /*
Package semver provides functionality to parse and process semantic versions, as they are used in multiple components of Constellation. Package semver provides functionality to parse and process semantic versions, as they are used in multiple components of Constellation.
The official [semantic versioning specification] disallows leading "v" prefixes.
However, the Constellation config uses the "v" prefix for versions to make version strings more recognizable.
This package bridges the gap between Go's semver pkg (doesn't allow "v" prefix) and the Constellation config (requires "v" prefix).
[semantic versioning specification]: https://semver.org/
*/ */
package semver package semver
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/compatibility"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
// Sort sorts a list of semantic version strings using [ByVersion].
func Sort(list []Semver) {
sort.Sort(byVersion(list))
}
// Semver represents a semantic version. // Semver represents a semantic version.
type Semver struct { type Semver struct {
Major int major int
Minor int minor int
Patch int patch int
Prerelease string prerelease string
} }
// New returns a Version from a string. // New returns a Version from a string.
@ -47,18 +60,72 @@ func New(version string) (Semver, error) {
} }
return Semver{ return Semver{
Major: major, major: major,
Minor: minor, minor: minor,
Patch: patch, patch: patch,
Prerelease: pre, prerelease: pre,
}, nil }, nil
} }
// NewFromInt constructs a new Semver from three integers and prerelease string: MAJOR.MINOR.PATCH-PRERELEASE.
func NewFromInt(major, minor, patch int, prerelease string) Semver {
return Semver{
major: major,
minor: minor,
patch: patch,
prerelease: prerelease,
}
}
// NewSlice returns a slice of Semver from a slice of strings.
func NewSlice(in []string) ([]Semver, error) {
var out []Semver
for _, version := range in {
semVersion, err := New(version)
if err != nil {
return nil, fmt.Errorf("parsing version %s: %w", version, err)
}
out = append(out, semVersion)
}
return out, nil
}
// ToStrings converts a slice of Semver to a slice of strings.
func ToStrings(in []Semver) []string {
var out []string
for _, v := range in {
out = append(out, v.String())
}
return out
}
// Major returns the major version of the object.
func (v Semver) Major() int {
return v.major
}
// Minor returns the minor version of the object.
func (v Semver) Minor() int {
return v.minor
}
// Patch returns the patch version of the object.
func (v Semver) Patch() int {
return v.patch
}
// Prerelease returns the prerelease section of the object.
func (v Semver) Prerelease() string {
return v.prerelease
}
// String returns the string representation of the version. // String returns the string representation of the version.
func (v Semver) String() string { func (v Semver) String() string {
version := fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) version := fmt.Sprintf("v%d.%d.%d", v.major, v.minor, v.patch)
if v.Prerelease != "" { if v.prerelease != "" {
return fmt.Sprintf("%s-%s", version, v.Prerelease) return fmt.Sprintf("%s-%s", version, v.prerelease)
} }
return version return version
} }
@ -70,29 +137,51 @@ func (v Semver) Compare(other Semver) int {
// MajorMinorEqual returns if the major and minor version of two versions are equal. // MajorMinorEqual returns if the major and minor version of two versions are equal.
func (v Semver) MajorMinorEqual(other Semver) bool { func (v Semver) MajorMinorEqual(other Semver) bool {
return v.Major == other.Major && v.Minor == other.Minor return v.major == other.major && v.minor == other.minor
} }
// IsUpgradeTo returns if a version is an upgrade to another version. // IsUpgradeTo returns if a version is an upgrade to another version.
// It checks if the version of v is greater than the version of other and allows a drift of at most one minor version. // It checks if the version of v is greater than the version of other and allows a drift of at most one minor version.
func (v Semver) IsUpgradeTo(other Semver) bool { func (v Semver) IsUpgradeTo(other Semver) error {
return v.Compare(other) > 0 && v.Major == other.Major && v.Minor-other.Minor <= 1 if v.Compare(other) <= 0 {
} return compatibility.NewInvalidUpgradeError(v.String(), other.String(), errors.New("current version newer than or equal to new version"))
}
// CompatibleWithBinary returns if a version is compatible version of the current built binary. if v.major != other.major {
// It checks if the version of the binary is equal or greater than the current version and allows a drift of at most one minor version. return compatibility.NewInvalidUpgradeError(v.String(), other.String(), compatibility.ErrMajorMismatch)
func (v Semver) CompatibleWithBinary() bool {
binaryVersion, err := New(constants.VersionInfo())
if err != nil {
return false
} }
return v.Compare(binaryVersion) == 0 || binaryVersion.IsUpgradeTo(v) if v.minor-other.minor > 1 {
return compatibility.NewInvalidUpgradeError(v.String(), other.String(), compatibility.ErrMinorDrift)
}
return nil
} }
// NextMinor returns the next minor version in the format "vMAJOR.MINOR". // NextMinor returns the next minor version in the format "vMAJOR.MINOR+1".
func (v Semver) NextMinor() string { func (v Semver) NextMinor() string {
return fmt.Sprintf("v%d.%d", v.Major, v.Minor+1) return fmt.Sprintf("v%d.%d", v.major, v.minor+1)
}
// MarshalYAML implements the yaml.Marshaller interface.
func (v Semver) MarshalYAML() (any, error) {
return v.String(), nil
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (v *Semver) UnmarshalYAML(unmarshal func(any) error) error {
var raw string
if err := unmarshal(&raw); err != nil {
return fmt.Errorf("unmarshalling to string: %w", err)
}
version, err := New(raw)
if err != nil {
return fmt.Errorf("parsing semantic version: %w", err)
}
*v = version
return nil
} }
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
@ -115,3 +204,20 @@ func (v *Semver) UnmarshalJSON(data []byte) error {
*v = version *v = version
return nil return nil
} }
// byVersion implements [sort.Interface] for sorting semantic version strings.
// Copied from Go's semver pkg with minimal modification.
// https://cs.opensource.google/go/x/mod/+/master:semver/semver.go
type byVersion []Semver
func (vs byVersion) Len() int { return len(vs) }
func (vs byVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
func (vs byVersion) Less(i, j int) bool {
cmp := vs[i].Compare(vs[j])
if cmp != 0 {
return cmp < 0
}
// if versions are equal, sort by lexicographic order
return vs[i].String() < vs[j].String()
}

View File

@ -7,20 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only
package semver package semver
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
) )
var ( var (
v1_18_0 = Semver{Major: 1, Minor: 18, Patch: 0} v1_18_0 = Semver{major: 1, minor: 18, patch: 0}
v1_18_0Pre = Semver{Major: 1, Minor: 18, Patch: 0, Prerelease: "pre"} v1_18_0Pre = Semver{major: 1, minor: 18, patch: 0, prerelease: "pre"}
v1_18_0PreExtra = Semver{Major: 1, Minor: 18, Patch: 0, Prerelease: "pre.1"} v1_18_0PreExtra = Semver{major: 1, minor: 18, patch: 0, prerelease: "pre.1"}
v1_19_0 = Semver{Major: 1, Minor: 19, Patch: 0} v1_19_0 = Semver{major: 1, minor: 19, patch: 0}
v1_18_1 = Semver{Major: 1, Minor: 18, Patch: 1} v1_18_1 = Semver{major: 1, minor: 18, patch: 1}
v1_20_0 = Semver{Major: 1, Minor: 20, Patch: 0} v1_20_0 = Semver{major: 1, minor: 20, patch: 0}
v2_0_0 = Semver{Major: 2, Minor: 0, Patch: 0} v2_0_0 = Semver{major: 2, minor: 0, patch: 0}
) )
func TestNewVersion(t *testing.T) { func TestNewVersion(t *testing.T) {
@ -32,19 +34,19 @@ func TestNewVersion(t *testing.T) {
"valid version": { "valid version": {
version: "v1.18.0", version: "v1.18.0",
want: Semver{ want: Semver{
Major: 1, major: 1,
Minor: 18, minor: 18,
Patch: 0, patch: 0,
}, },
wantErr: false, wantErr: false,
}, },
"valid version prerelease": { "valid version prerelease": {
version: "v1.18.0-pre+yyyymmddhhmmss-abcdefabcdef", version: "v1.18.0-pre+yyyymmddhhmmss-abcdefabcdef",
want: Semver{ want: Semver{
Major: 1, major: 1,
Minor: 18, minor: 18,
Patch: 0, patch: 0,
Prerelease: "pre", prerelease: "pre",
}, },
wantErr: false, wantErr: false,
}, },
@ -53,27 +55,27 @@ func TestNewVersion(t *testing.T) {
"add prefix": { "add prefix": {
version: "1.18.0", version: "1.18.0",
want: Semver{ want: Semver{
Major: 1, major: 1,
Minor: 18, minor: 18,
Patch: 0, patch: 0,
}, },
wantErr: false, wantErr: false,
}, },
"only major.minor": { "only major.minor": {
version: "v1.18", version: "v1.18",
want: Semver{ want: Semver{
Major: 1, major: 1,
Minor: 18, minor: 18,
Patch: 0, patch: 0,
}, },
wantErr: false, wantErr: false,
}, },
"only major": { "only major": {
version: "v1", version: "v1",
want: Semver{ want: Semver{
Major: 1, major: 1,
Minor: 0, minor: 0,
Patch: 0, patch: 0,
}, },
wantErr: false, wantErr: false,
}, },
@ -218,58 +220,57 @@ func TestCanUpgrade(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
version1 Semver version1 Semver
version2 Semver version2 Semver
want bool wantUpgrade bool
wantErr bool
}{ }{
"equal": { "equal": {
version1: v1_18_0, version1: v1_18_0,
version2: v1_18_0, version2: v1_18_0,
want: false, wantUpgrade: false,
}, },
"patch less than": { "patch less than": {
version1: v1_18_0, version1: v1_18_0,
version2: v1_18_1, version2: v1_18_1,
want: true, wantUpgrade: true,
}, },
"minor less then": { "minor less then": {
version1: v1_18_0, version1: v1_18_0,
version2: v1_19_0, version2: v1_19_0,
want: true, wantUpgrade: true,
}, },
"minor too big drift": { "minor too big drift": {
version1: v1_18_0, version1: v1_18_0,
version2: v1_20_0, version2: v1_20_0,
want: false, wantUpgrade: false,
}, },
"major too big drift": { "major too big drift": {
version1: v1_18_0, version1: v1_18_0,
version2: v2_0_0, version2: v2_0_0,
want: false, wantUpgrade: false,
}, },
"greater than": { "greater than": {
version1: v1_18_1, version1: v1_18_1,
version2: v1_18_0, version2: v1_18_0,
want: false, wantUpgrade: false,
}, },
"prerelease less than": { "prerelease less than": {
version1: v1_18_0Pre, version1: v1_18_0Pre,
version2: v1_18_0, version2: v1_18_0,
want: true, wantUpgrade: true,
}, },
"prerelease greater than": { "prerelease greater than": {
version1: v1_18_0, version1: v1_18_0,
version2: v1_18_0Pre, version2: v1_18_0Pre,
want: false, wantUpgrade: false,
}, },
"prerelease equal": { "prerelease equal": {
version1: v1_18_0Pre, version1: v1_18_0Pre,
version2: v1_18_0Pre, version2: v1_18_0Pre,
want: false, wantUpgrade: false,
}, },
"prerelease extra": { "prerelease extra": {
version1: v1_18_0Pre, version1: v1_18_0Pre,
version2: v1_18_0PreExtra, version2: v1_18_0PreExtra,
want: true, wantUpgrade: true,
}, },
} }
@ -277,7 +278,7 @@ func TestCanUpgrade(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
assert.Equal(tc.want, tc.version2.IsUpgradeTo(tc.version1)) assert.Equal(tc.wantUpgrade, tc.version2.IsUpgradeTo(tc.version1) == nil)
}) })
} }
} }
@ -304,3 +305,89 @@ func TestNextMinor(t *testing.T) {
}) })
} }
} }
func TestVersionMarshalYAML(t *testing.T) {
testCases := map[string]struct {
version Semver
want string
}{
"simple": {
version: Semver{
major: 1,
minor: 18,
patch: 0,
prerelease: "",
},
want: "v1.18.0\n",
},
"with prerelease": {
version: Semver{
major: 1,
minor: 18,
patch: 0,
prerelease: "pre",
},
want: "v1.18.0-pre\n",
},
"empty semver": {
version: Semver{},
want: "v0.0.0\n",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
marshalled, err := yaml.Marshal(tc.version)
require.NoError(t, err)
require.Equal(t, tc.want, string(marshalled))
var unmarshalled Semver
err = yaml.Unmarshal(marshalled, &unmarshalled)
require.NoError(t, err)
require.Equal(t, tc.version, unmarshalled)
})
}
}
func TestVersionUnmarshalYAML(t *testing.T) {
testCases := map[string]struct {
version []byte
want Semver
wantError bool
}{
"empty string": {
version: []byte(""),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
var actual Semver
err := yaml.Unmarshal(tc.version, &actual)
if tc.wantError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.want.Compare(actual), 0, fmt.Sprintf("expected %s, got %s", tc.want, actual))
})
}
}
func TestSort(t *testing.T) {
testCases := map[string]struct {
input []Semver
want []Semver
}{
"": {
input: []Semver{NewFromInt(2, 0, 0, ""), NewFromInt(0, 0, 0, ""), NewFromInt(1, 5, 0, "aa"), NewFromInt(1, 5, 0, "bb"), NewFromInt(1, 0, 0, "")},
want: []Semver{NewFromInt(0, 0, 0, ""), NewFromInt(1, 0, 0, ""), NewFromInt(1, 5, 0, "aa"), NewFromInt(1, 5, 0, "bb"), NewFromInt(2, 0, 0, "")},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
Sort(tc.input)
require.Equal(t, tc.want, tc.input, fmt.Sprintf("expected %s, got %s", tc.want, tc.input))
})
}
}

View File

@ -49,7 +49,7 @@ func main() {
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity)) log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With( log.With(
zap.String("version", constants.VersionInfo()), zap.String("version", constants.BinaryVersion().String()),
zap.String("cloudProvider", *provider), zap.String("cloudProvider", *provider),
zap.String("attestationVariant", *attestationVariant), zap.String("attestationVariant", *attestationVariant),
).Infof("Constellation Node Join Service") ).Infof("Constellation Node Join Service")

View File

@ -34,7 +34,7 @@ func main() {
flag.Parse() flag.Parse()
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity)) log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With(zap.String("version", constants.VersionInfo())). log.With(zap.String("version", constants.BinaryVersion().String())).
Infof("Constellation Key Management Service") Infof("Constellation Key Management Service")
// read master secret and salt // read master secret and salt

View File

@ -26,7 +26,7 @@ func main() {
flag.Parse() flag.Parse()
log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity)) log := logger.New(logger.JSONLog, logger.VerbosityFromInt(*verbosity))
log.With(zap.String("version", constants.VersionInfo()), zap.String("attestationVariant", *attestationVariant)). log.With(zap.String("version", constants.BinaryVersion().String()), zap.String("attestationVariant", *attestationVariant)).
Infof("Constellation Verification Service") Infof("Constellation Verification Service")
variant, err := variant.FromString(*attestationVariant) variant, err := variant.FromString(*attestationVariant)