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()
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...")
uuid, err := getDiskUUID()

View File

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

View File

@ -86,7 +86,7 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
return err
}
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
}
}
@ -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.
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)
if err != nil {
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)
}
semMicro, err := semver.New(microserviceVersion)
if err != nil {
return fmt.Errorf("parsing microservice version: %w", err)
if !cliVersion.MajorMinorEqual(semImage) {
return fmt.Errorf("image version %q does not match the major and minor version of the cli version %q", semImage.String(), cliVersion.String())
}
semCLI, err := semver.New(cliVersion)
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())
if cliVersion.Compare(microserviceVersion) != 0 {
return fmt.Errorf("cli version %q does not match microservice version %q", cliVersion.String(), microserviceVersion.String())
}
return nil
}

View File

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

View File

@ -124,7 +124,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V
return err
}
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
}
}

View File

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

View File

@ -12,6 +12,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -117,7 +118,7 @@ func TestStatus(t *testing.T) {
},
},
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{
Spec: updatev1alpha1.NodeVersionSpec{
@ -167,7 +168,7 @@ func TestStatus(t *testing.T) {
},
},
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{
Spec: updatev1alpha1.NodeVersionSpec{

View File

@ -33,7 +33,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"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/versions"
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
@ -93,7 +93,7 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
cosign: sigstore.CosignVerifier{},
rekor: rekor,
flags: flags,
cliVersion: compatibility.EnsurePrefixV(constants.VersionInfo()),
cliVersion: constants.BinaryVersion(),
log: log,
versionsapi: versionfetcher,
},
@ -202,9 +202,9 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand
// Filter versions to only include upgrades
newServices := supported.service
if err := compatibility.IsValidUpgrade(current.service, supported.service); err != nil {
newServices = ""
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)
if err := supported.service.IsUpgradeTo(current.service); err != nil {
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.String(), supported.service.String())
}
newKubernetes := filterK8sUpgrades(current.k8s, supported.k8s)
@ -352,8 +352,8 @@ type collector interface {
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)
newerVersions(ctx context.Context, allowedVersions []string) ([]versionsapi.Version, error)
newCLIVersions(ctx context.Context) ([]string, error)
filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []string, currentK8sVersion string) ([]string, error)
newCLIVersions(ctx context.Context) ([]consemver.Semver, error)
filterCompatibleCLIVersions(ctx context.Context, cliPatchVersions []consemver.Semver, currentK8sVersion string) ([]consemver.Semver, error)
}
type versionCollector struct {
@ -366,7 +366,7 @@ type versionCollector struct {
rekor rekorVerifier
flags upgradeCheckFlags
versionsapi versionFetcher
cliVersion string
cliVersion consemver.Semver
log debugLog
}
@ -382,10 +382,10 @@ func (v *versionCollector) newMeasurements(ctx context.Context, csp cloudprovide
}
type currentVersionInfo struct {
service string
service consemver.Semver
image string
k8s string
cli string
cli consemver.Semver
}
func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionInfo, error) {
@ -418,20 +418,18 @@ func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionI
}
type supportedVersionInfo struct {
service string
service consemver.Semver
image []versionsapi.Version
k8s []string
// CLI versions including those incompatible with the current Kubernetes version.
cli []string
cli []consemver.Semver
// CLI versions compatible with the current Kubernetes version.
compatibleCLI []string
compatibleCLI []consemver.Semver
}
// supportedVersions returns slices of supported versions.
func (v *versionCollector) supportedVersions(ctx context.Context, version, currentK8sVersion string) (supportedVersionInfo, error) {
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)
if err != nil {
@ -447,7 +445,8 @@ func (v *versionCollector) supportedVersions(ctx context.Context, version, curre
}
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,
k8s: k8sVersions,
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)
// if the CLI minor version is newer than the cluster minor version
currentImageMinorVer := semver.MajorMinor(version)
currentCLIMinorVer := semver.MajorMinor(v.cliVersion)
currentCLIMinorVer := semver.MajorMinor(v.cliVersion.String())
nextImageMinorVer, err := compatibility.NextMinorVersion(currentImageMinorVer)
if err != nil {
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 {
newServices string
newServices consemver.Semver
newImages map[string]measurements.M
newKubernetes []string
newCLI []string
newCompatibleCLI []string
currentServices string
newCLI []consemver.Semver
newCompatibleCLI []consemver.Semver
currentServices consemver.Semver
currentImage string
currentKubernetes string
currentCLI string
currentCLI consemver.Semver
}
func (v *versionUpgrade) buildString() (string, error) {
@ -560,7 +559,7 @@ func (v *versionUpgrade) buildString() (string, error) {
fmt.Fprintln(&upgradeMsg, "")
}
if v.newServices != "" {
if v.newServices != (consemver.Semver{}) {
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
if v.newServices == "" && len(v.newImages) == 0 {
if v.newServices == (consemver.Semver{}) && len(v.newImages) == 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
} 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
}
}
@ -589,7 +588,7 @@ func (v *versionUpgrade) buildString() (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.
if v.newServices != "" {
if v.newServices != (consemver.Semver{}) {
conf.MicroserviceVersion = v.newServices
}
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.
func (v *versionCollector) newCLIVersions(ctx context.Context) ([]string, error) {
cliVersion, err := conSemver.New(constants.VersionInfo())
if err != nil {
return nil, fmt.Errorf("parsing current CLI version: %w", err)
}
func (v *versionCollector) newCLIVersions(ctx context.Context) ([]consemver.Semver, error) {
list := versionsapi.List{
Ref: v.flags.ref,
Stream: v.flags.stream,
Granularity: versionsapi.GranularityMajor,
Base: fmt.Sprintf("v%d", cliVersion.Major),
Base: fmt.Sprintf("v%d", constants.BinaryVersion().Major()),
Kind: versionsapi.VersionKindCLI,
}
minorList, err := v.versionsapi.FetchVersionList(ctx, list)
@ -704,7 +699,11 @@ func (v *versionCollector) newCLIVersions(ctx context.Context) ([]string, error)
var patchVersions []string
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)
continue
}
@ -722,24 +721,29 @@ func (v *versionCollector) newCLIVersions(ctx context.Context) ([]string, error)
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.
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
var compatibleVersions []string
var compatibleVersions []consemver.Semver
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)
continue
}
req := versionsapi.CLIInfo{
Ref: v.flags.ref,
Stream: v.flags.stream,
Version: version,
Version: version.String(),
}
info, err := v.versionsapi.FetchCLIInfo(ctx, req)
if err != nil {
@ -753,7 +757,7 @@ func (v *versionCollector) filterCompatibleCLIVersions(ctx context.Context, cliP
}
}
semver.Sort(compatibleVersions)
consemver.Sort(compatibleVersions)
return compatibleVersions, nil
}

View File

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

View File

@ -35,7 +35,7 @@ func runVersion(cmd *cobra.Command, _ []string) {
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("GitTreeState:\t%s\n", state)
cmd.Printf("BuildDate:\t%s\n", date)

View File

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

View File

@ -459,6 +459,7 @@ go_test(
"//internal/deploy/helm",
"//internal/file",
"//internal/logger",
"//internal/semver",
"@com_github_pkg_errors//:errors",
"@com_github_spf13_afero//:afero",
"@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
}
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)
if err != nil {
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.
// Leaving it as is since it is not clear to me what kind of sanity check we could do.
if !force {
if err := compatibility.IsValidUpgrade(currentVersion, newVersion); err != nil {
if err := newVersion.IsUpgradeTo(currentVersion); err != nil {
return err
}
}
cliVersion := constants.BinaryVersion()
// 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 compatibility.EnsurePrefixV(constants.VersionInfo()) != compatibility.EnsurePrefixV(newVersion) {
return fmt.Errorf("this CLI only supports microservice version %s for upgrading", constants.VersionInfo())
if cliVersion.Compare(newVersion) != 0 {
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)
@ -118,13 +119,17 @@ func (c *Client) Upgrade(ctx context.Context, config *config.Config, idFile clus
}
// define target version the chart is upgraded to
var upgradeVersion string
var upgradeVersion semver.Semver
if info == constellationOperatorsInfo || info == constellationServicesInfo {
// 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
} 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
@ -222,49 +227,51 @@ func (c *Client) Versions() (ServiceVersions, error) {
}
res := ServiceVersions{
cilium: compatibility.EnsurePrefixV(ciliumVersion),
certManager: compatibility.EnsurePrefixV(certManagerVersion),
constellationOperators: compatibility.EnsurePrefixV(operatorsVersion),
constellationServices: compatibility.EnsurePrefixV(servicesVersion),
cilium: ciliumVersion,
certManager: certManagerVersion,
constellationOperators: operatorsVersion,
constellationServices: servicesVersion,
awsLBController: awsLBVersion,
}
if awsLBVersion != "" {
res.awsLBController = compatibility.EnsurePrefixV(awsLBVersion)
if awsLBVersion != (semver.Semver{}) {
res.awsLBController = awsLBVersion
}
return res, nil
}
// 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)
if err != nil {
return "", err
return semver.Semver{}, err
}
if len(rel) == 0 {
return "", errReleaseNotFound
return semver.Semver{}, errReleaseNotFound
}
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 {
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.
type ServiceVersions struct {
cilium string
certManager string
constellationOperators string
constellationServices string
awsLBController string
cilium semver.Semver
certManager semver.Semver
constellationOperators semver.Semver
constellationServices semver.Semver
awsLBController semver.Semver
}
// 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{
cilium: cilium,
certManager: certManager,
@ -274,22 +281,22 @@ func NewServiceVersions(cilium, certManager, constellationOperators, constellati
}
// Cilium returns the version of the Cilium release.
func (s ServiceVersions) Cilium() string {
func (s ServiceVersions) Cilium() semver.Semver {
return s.cilium
}
// CertManager returns the version of the cert-manager release.
func (s ServiceVersions) CertManager() string {
func (s ServiceVersions) CertManager() semver.Semver {
return s.certManager
}
// ConstellationOperators returns the version of the constellation-operators release.
func (s ServiceVersions) ConstellationOperators() string {
func (s ServiceVersions) ConstellationOperators() semver.Semver {
return s.constellationOperators
}
// ConstellationServices returns the version of the constellation-services release.
func (s ServiceVersions) ConstellationServices() string {
func (s ServiceVersions) ConstellationServices() semver.Semver {
return s.constellationServices
}
@ -385,12 +392,8 @@ func (c *Client) applyMigrations(ctx context.Context, releaseName string, values
if err != nil {
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.
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/config"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v3/pkg/chart"
@ -49,7 +50,9 @@ func TestShouldUpgrade(t *testing.T) {
chart, err := loadChartsDir(helmFS, certManagerInfo.path)
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 {
tc.assertCorrectError(t, err)
return

View File

@ -25,10 +25,10 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/helm/imageversion"
"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/constants"
"github.com/edgelesssys/constellation/v2/internal/deploy/helm"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions"
)
@ -175,15 +175,15 @@ func (i *ChartLoader) loadRelease(info chartInfo, helmWaitMode helm.WaitMode) (h
case certManagerInfo.releaseName:
values = i.loadCertManagerValues()
case constellationOperatorsInfo.releaseName:
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo()))
updateVersions(chart, constants.BinaryVersion())
values = i.loadOperatorsValues()
case constellationServicesInfo.releaseName:
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo()))
updateVersions(chart, constants.BinaryVersion())
values = i.loadConstellationServicesValues()
case awsLBControllerInfo.releaseName:
values = i.loadAWSLBControllerValues()
case csiInfo.releaseName:
updateVersions(chart, compatibility.EnsurePrefixV(constants.VersionInfo()))
updateVersions(chart, constants.BinaryVersion())
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.
func updateVersions(chart *chart.Chart, newVersion string) {
chart.Metadata.Version = newVersion
func updateVersions(chart *chart.Chart, newVersion semver.Semver) {
chart.Metadata.Version = newVersion.String()
selectedDeps := chart.Metadata.Dependencies
for i := range selectedDeps {
if selectedDeps[i].Version == "0.0.0" {
selectedDeps[i].Version = newVersion
selectedDeps[i].Version = newVersion.String()
}
}
deps := chart.Dependencies()
for i := range deps {
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()
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")
// set up quote issuer for aTLS connections

View File

@ -12,6 +12,7 @@ go_library(
deps = [
"//internal/constants",
"//internal/logger",
"//internal/semver",
"@sh_helm_helm_v3//pkg/action",
"@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/logger"
"github.com/edgelesssys/constellation/v2/internal/semver"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli"
)
func servicesVersion(t *testing.T) (string, error) {
func servicesVersion(t *testing.T) (semver.Semver, error) {
t.Helper()
log := logger.NewTest(t)
settings := cli.New()
settings.KubeConfig = "constellation-admin.conf"
actionConfig := &action.Configuration{}
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")
}
func currentVersion(cfg *action.Configuration, release string) (string, error) {
func currentVersion(cfg *action.Configuration, release string) (semver.Semver, error) {
action := action.NewList(cfg)
action.Filter = release
rel, err := action.Run()
if err != nil {
return "", err
return semver.Semver{}, err
}
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 {
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 {
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)
}
var microserviceVersion string
var microserviceVersion semver.Semver
if microservices == "" {
microserviceVersion = defaultConfig.MicroserviceVersion
} else {
microserviceVersion = microservices
version, err := semver.New(microservices)
require.NoError(err)
microserviceVersion = version
}
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))
}
cliVersion, err := semver.New(constants.VersionInfo())
require.NoError(err)
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))
}
@ -403,7 +403,7 @@ func testStatusEventuallyWorks(t *testing.T, cli string, timeout time.Duration)
}, 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 {
version, err := servicesVersion(t)
if err != nil {
@ -460,7 +460,7 @@ func testNodesEventuallyHaveVersion(t *testing.T, k *kubernetes.Clientset, targe
type versionContainer struct {
imageRef string
kubernetes semver.Semver
microservices string
microservices semver.Semver
}
// runCommandWithSeparateOutputs runs the given command while separating buffers for

View File

@ -29,6 +29,7 @@ go_library(
"//internal/config/instancetypes",
"//internal/constants",
"//internal/file",
"//internal/semver",
"//internal/versions",
"@com_github_go_playground_locales//en",
"@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/variant"
"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/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions"
)
@ -63,7 +63,7 @@ type Config struct {
Version string `yaml:"version" validate:"eq=v3"`
// description: |
// 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: |
// Name of the cluster.
Name string `yaml:"name" validate:"valid_name,required"`
@ -75,7 +75,7 @@ type Config struct {
KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
// description: |
// 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: |
// DON'T USE IN PRODUCTION: enable debug mode and use debug images.
DebugCluster *bool `yaml:"debugCluster" validate:"required"`
@ -315,7 +315,7 @@ func Default() *Config {
Version: Version3,
Image: defaultImage,
Name: defaultName,
MicroserviceVersion: compatibility.EnsurePrefixV(constants.VersionInfo()),
MicroserviceVersion: constants.BinaryVersion(),
KubernetesVersion: string(versions.Default),
StateDiskSizeGB: 30,
DebugCluster: toPtr(false),
@ -725,7 +725,7 @@ func (c *Config) Validate(force bool) error {
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
}
@ -750,7 +750,7 @@ func (c *Config) Validate(force bool) error {
if force {
versionCompatibilityValidator = returnsTrue
}
if err := validate.RegisterValidation("version_compatibility", versionCompatibilityValidator); err != nil {
if err := validate.RegisterValidation("image_compatibility", versionCompatibilityValidator); err != nil {
return err
}
@ -800,6 +800,18 @@ func (c *Config) Validate(force bool) error {
validate.RegisterStructValidation(validateMeasurement, measurements.Measurement{})
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)
if err == 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].Comments[encoder.LineComment] = "Kubernetes version to be installed into the cluster."
ConfigDoc.Fields[5].Name = "microserviceVersion"
ConfigDoc.Fields[5].Type = "string"
ConfigDoc.Fields[5].Type = "Semver"
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].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) {
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.TenantID = "11111111-1111-1111-1111-111111111111"
c.Provider.Azure.Location = "westus"
@ -308,13 +308,25 @@ func TestValidate(t *testing.T) {
cnf.Image = ""
ver, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(t, err)
ver.Patch = ver.Patch - 1
ver = semver.NewFromInt(ver.Major(), ver.Minor(), ver.Patch()-1, "")
cnf.KubernetesVersion = ver.String()
return cnf
}(),
wantErr: true,
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": {
cnf: func() *Config {
cnf := Default()
@ -350,7 +362,7 @@ func TestValidate(t *testing.T) {
cnf: func() *Config {
cnf := Default()
cnf.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
cnf.Image = "v" + constants.VersionInfo()
cnf.Image = constants.BinaryVersion().String()
az := cnf.Provider.Azure
az.SubscriptionID = "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 := Default()
cnf.RemoveProviderAndAttestationExcept(cloudprovider.GCP)
cnf.Image = "v" + constants.VersionInfo()
cnf.Image = constants.BinaryVersion().String()
gcp := cnf.Provider.GCP
gcp.Region = "test-region"
gcp.Project = "test-project"

View File

@ -11,5 +11,6 @@ go_library(
"//internal/attestation/variant",
"//internal/config",
"//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/config"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/semver"
)
const (
@ -28,11 +29,11 @@ const (
// Config defines configuration used by CLI.
type Config struct {
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"`
StateDiskSizeGB int `yaml:"stateDiskSizeGB" validate:"min=0"`
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"`
AttestationVariant string `yaml:"attestationVariant,omitempty" validate:"valid_attestation_variant"`
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)
}
microserviceVersion, err := semver.New(cfgV2.MicroserviceVersion)
if err != nil {
return fmt.Errorf("parsing microservice version: %w", err)
}
// Migrate to new format
var cfgV3 config.Config
cfgV3.Version = config.Version3
@ -202,7 +208,7 @@ func V2ToV3(path string, fileHandler file.Handler) error {
cfgV3.Name = cfgV2.Name
cfgV3.StateDiskSizeGB = cfgV2.StateDiskSizeGB
cfgV3.KubernetesVersion = cfgV2.KubernetesVersion
cfgV3.MicroserviceVersion = cfgV2.MicroserviceVersion
cfgV3.MicroserviceVersion = microserviceVersion
cfgV3.DebugCluster = cfgV2.DebugCluster
switch {

View File

@ -27,6 +27,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
"github.com/edgelesssys/constellation/v2/internal/constants"
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions"
)
@ -86,11 +87,11 @@ func translateInvalidK8sVersionError(ut ut.Translator, fe validator.FieldError)
configured = compatibility.EnsurePrefixV(configured)
switch {
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:
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:
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, " "))
@ -492,44 +493,21 @@ func K8sVersionFromMajorMinor(version string) string {
}
}
func registerVersionCompatibilityError(ut ut.Translator) error {
return ut.Add("version_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
func registerImageCompatibilityError(ut ut.Translator) error {
return ut.Add("image_compatibility", "{0} specifies an invalid version: {1}", true)
}
// Check that the validated field and the CLI version are not more than one minor version apart.
func validateVersionCompatibility(fl validator.FieldLevel) bool {
binaryVersion := constants.VersionInfo()
if err := validateVersionCompatibilityHelper(binaryVersion, fl.FieldName(), fl.Field().String()); err != nil {
binaryVersion := constants.BinaryVersion()
if err := validateImageCompatibilityHelper(binaryVersion, fl.FieldName(), fl.Field().String()); err != nil {
return false
}
return true
}
func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVersion string) error {
func validateImageCompatibilityHelper(binaryVersion consemver.Semver, fieldName, configuredVersion string) error {
if fieldName == "image" {
imageVersion, err := versionsapi.NewVersionFromShortPath(configuredVersion, versionsapi.VersionKindImage)
if err != nil {
@ -538,15 +516,51 @@ func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVers
configuredVersion = imageVersion.Version
}
if fieldName == "microserviceVersion" {
cliVersion := compatibility.EnsurePrefixV(binaryVersion)
serviceVersion := compatibility.EnsurePrefixV(configuredVersion)
if semver.Compare(cliVersion, serviceVersion) == -1 {
return fmt.Errorf("the CLI's version (%s) is older than the configured version (%s)", cliVersion, serviceVersion)
return compatibility.BinaryWith(binaryVersion.String(), configuredVersion)
}
func translateImageCompatibilityError(ut ut.Translator, fe validator.FieldError) string {
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()
}
}
return compatibility.BinaryWith(binaryVersion, configuredVersion)
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 nil
}
func returnsTrue(_ validator.FieldLevel) bool {

View File

@ -9,26 +9,27 @@ package config
import (
"testing"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/stretchr/testify/assert"
)
// TestValidateVersionCompatibilityHelper checks that basic version and image short paths are correctly validated.
func TestValidateVersionCompatibilityHelper(t *testing.T) {
testCases := map[string]struct {
cli string
cli semver.Semver
target string
wantError bool
}{
"full version works": {
cli: "0.1.0",
cli: semver.NewFromInt(0, 1, 0, ""),
target: "v0.0.0",
},
"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",
},
"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",
wantError: true,
},
@ -38,7 +39,43 @@ func TestValidateVersionCompatibilityHelper(t *testing.T) {
t.Run(name, func(t *testing.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 {
assert.Error(err)
return

View File

@ -17,4 +17,5 @@ go_library(
"timestamp": "{STABLE_STAMP_TIME}",
"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
import (
"fmt"
"time"
"github.com/edgelesssys/constellation/v2/internal/semver"
)
const (
@ -231,9 +234,15 @@ b92PDCpM7FZAINQF88s1TZS/HmRXYk62UJ4eqPduvUnJmXhNikhLbMi6fw==
`
)
// VersionInfo returns the version of a binary.
func VersionInfo() string {
return versionInfo
// BinaryVersion returns the version of this Binary.
func BinaryVersion() semver.Semver {
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.

View File

@ -7,7 +7,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/semver",
visibility = ["//:__subpackages__"],
deps = [
"//internal/constants",
"//internal/compatibility",
"@org_golang_x_mod//semver",
],
)
@ -19,5 +19,6 @@ go_test(
deps = [
"@com_github_stretchr_testify//assert",
"@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.
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
import (
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"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.
type Semver struct {
Major int
Minor int
Patch int
Prerelease string
major int
minor int
patch int
prerelease string
}
// New returns a Version from a string.
@ -47,18 +60,72 @@ func New(version string) (Semver, error) {
}
return Semver{
Major: major,
Minor: minor,
Patch: patch,
Prerelease: pre,
major: major,
minor: minor,
patch: patch,
prerelease: pre,
}, 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.
func (v Semver) String() string {
version := fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.Prerelease != "" {
return fmt.Sprintf("%s-%s", version, v.Prerelease)
version := fmt.Sprintf("v%d.%d.%d", v.major, v.minor, v.patch)
if v.prerelease != "" {
return fmt.Sprintf("%s-%s", version, v.prerelease)
}
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.
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.
// 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 {
return v.Compare(other) > 0 && v.Major == other.Major && v.Minor-other.Minor <= 1
func (v Semver) IsUpgradeTo(other Semver) error {
if v.Compare(other) <= 0 {
return compatibility.NewInvalidUpgradeError(v.String(), other.String(), errors.New("current version newer than or equal to new version"))
}
if v.major != other.major {
return compatibility.NewInvalidUpgradeError(v.String(), other.String(), compatibility.ErrMajorMismatch)
}
// CompatibleWithBinary returns if a version is compatible version of the current built binary.
// 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.
func (v Semver) CompatibleWithBinary() bool {
binaryVersion, err := New(constants.VersionInfo())
if err != nil {
return false
if v.minor-other.minor > 1 {
return compatibility.NewInvalidUpgradeError(v.String(), other.String(), compatibility.ErrMinorDrift)
}
return v.Compare(binaryVersion) == 0 || binaryVersion.IsUpgradeTo(v)
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 {
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.
@ -115,3 +204,20 @@ func (v *Semver) UnmarshalJSON(data []byte) error {
*v = version
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
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
var (
v1_18_0 = Semver{Major: 1, Minor: 18, Patch: 0}
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_19_0 = Semver{Major: 1, Minor: 19, Patch: 0}
v1_18_1 = Semver{Major: 1, Minor: 18, Patch: 1}
v1_20_0 = Semver{Major: 1, Minor: 20, Patch: 0}
v2_0_0 = Semver{Major: 2, Minor: 0, 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_0PreExtra = Semver{major: 1, minor: 18, patch: 0, prerelease: "pre.1"}
v1_19_0 = Semver{major: 1, minor: 19, patch: 0}
v1_18_1 = Semver{major: 1, minor: 18, patch: 1}
v1_20_0 = Semver{major: 1, minor: 20, patch: 0}
v2_0_0 = Semver{major: 2, minor: 0, patch: 0}
)
func TestNewVersion(t *testing.T) {
@ -32,19 +34,19 @@ func TestNewVersion(t *testing.T) {
"valid version": {
version: "v1.18.0",
want: Semver{
Major: 1,
Minor: 18,
Patch: 0,
major: 1,
minor: 18,
patch: 0,
},
wantErr: false,
},
"valid version prerelease": {
version: "v1.18.0-pre+yyyymmddhhmmss-abcdefabcdef",
want: Semver{
Major: 1,
Minor: 18,
Patch: 0,
Prerelease: "pre",
major: 1,
minor: 18,
patch: 0,
prerelease: "pre",
},
wantErr: false,
},
@ -53,27 +55,27 @@ func TestNewVersion(t *testing.T) {
"add prefix": {
version: "1.18.0",
want: Semver{
Major: 1,
Minor: 18,
Patch: 0,
major: 1,
minor: 18,
patch: 0,
},
wantErr: false,
},
"only major.minor": {
version: "v1.18",
want: Semver{
Major: 1,
Minor: 18,
Patch: 0,
major: 1,
minor: 18,
patch: 0,
},
wantErr: false,
},
"only major": {
version: "v1",
want: Semver{
Major: 1,
Minor: 0,
Patch: 0,
major: 1,
minor: 0,
patch: 0,
},
wantErr: false,
},
@ -218,58 +220,57 @@ func TestCanUpgrade(t *testing.T) {
testCases := map[string]struct {
version1 Semver
version2 Semver
want bool
wantErr bool
wantUpgrade bool
}{
"equal": {
version1: v1_18_0,
version2: v1_18_0,
want: false,
wantUpgrade: false,
},
"patch less than": {
version1: v1_18_0,
version2: v1_18_1,
want: true,
wantUpgrade: true,
},
"minor less then": {
version1: v1_18_0,
version2: v1_19_0,
want: true,
wantUpgrade: true,
},
"minor too big drift": {
version1: v1_18_0,
version2: v1_20_0,
want: false,
wantUpgrade: false,
},
"major too big drift": {
version1: v1_18_0,
version2: v2_0_0,
want: false,
wantUpgrade: false,
},
"greater than": {
version1: v1_18_1,
version2: v1_18_0,
want: false,
wantUpgrade: false,
},
"prerelease less than": {
version1: v1_18_0Pre,
version2: v1_18_0,
want: true,
wantUpgrade: true,
},
"prerelease greater than": {
version1: v1_18_0,
version2: v1_18_0Pre,
want: false,
wantUpgrade: false,
},
"prerelease equal": {
version1: v1_18_0Pre,
version2: v1_18_0Pre,
want: false,
wantUpgrade: false,
},
"prerelease extra": {
version1: v1_18_0Pre,
version2: v1_18_0PreExtra,
want: true,
wantUpgrade: true,
},
}
@ -277,7 +278,7 @@ func TestCanUpgrade(t *testing.T) {
t.Run(name, func(t *testing.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.With(
zap.String("version", constants.VersionInfo()),
zap.String("version", constants.BinaryVersion().String()),
zap.String("cloudProvider", *provider),
zap.String("attestationVariant", *attestationVariant),
).Infof("Constellation Node Join Service")

View File

@ -34,7 +34,7 @@ func main() {
flag.Parse()
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")
// read master secret and salt

View File

@ -26,7 +26,7 @@ func main() {
flag.Parse()
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")
variant, err := variant.FromString(*attestationVariant)