mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-21 04:34:22 -04:00
cli: fix upgrades when using outdated Kubernetes patch version (#2718)
* Fix missing image for Constellation operators in our Helm charts if the desired Kubernetes patch version is no longer supported (but Kubernetes upgrades are skipped) * Correctly unmarshal Kubernetes Components list if the list uses an old format --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
724ee44466
commit
a1f67d0884
5 changed files with 130 additions and 1 deletions
|
@ -41,6 +41,7 @@ import (
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
xsemver "golang.org/x/mod/semver"
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
)
|
)
|
||||||
|
@ -545,9 +546,19 @@ func (a *applyCmd) validateInputs(cmd *cobra.Command, configFetcher attestationc
|
||||||
return nil, nil, fmt.Errorf("aborted by user")
|
return nil, nil, fmt.Errorf("aborted by user")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.flags.skipPhases.add(skipK8sPhase)
|
a.flags.skipPhases.add(skipK8sPhase)
|
||||||
a.log.Debugf("Outdated Kubernetes version accepted, Kubernetes upgrade will be skipped")
|
a.log.Debugf("Outdated Kubernetes version accepted, Kubernetes upgrade will be skipped")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validVersionString, err := versions.ResolveK8sPatchVersion(xsemver.MajorMinor(string(conf.KubernetesVersion)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("resolving Kubernetes patch version: %w", err)
|
||||||
|
}
|
||||||
|
validVersion, err = versions.NewValidK8sVersion(validVersionString, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("parsing Kubernetes version: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if versions.IsPreviewK8sVersion(validVersion) {
|
if versions.IsPreviewK8sVersion(validVersion) {
|
||||||
cmd.PrintErrf("Warning: Constellation with Kubernetes %s is still in preview. Use only for evaluation purposes.\n", validVersion)
|
cmd.PrintErrf("Warning: Constellation with Kubernetes %s is still in preview. Use only for evaluation purposes.\n", validVersion)
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -291,6 +292,7 @@ func TestValidateInputs(t *testing.T) {
|
||||||
stdin string
|
stdin string
|
||||||
flags applyFlags
|
flags applyFlags
|
||||||
wantPhases skipPhases
|
wantPhases skipPhases
|
||||||
|
assert func(require *require.Assertions, assert *assert.Assertions, conf *config.Config, stateFile *state.State)
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"[upgrade] gcp: all files exist": {
|
"[upgrade] gcp: all files exist": {
|
||||||
|
@ -396,6 +398,28 @@ func TestValidateInputs(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantPhases: newPhases(skipInfrastructurePhase, skipImagePhase, skipK8sPhase),
|
wantPhases: newPhases(skipInfrastructurePhase, skipImagePhase, skipK8sPhase),
|
||||||
},
|
},
|
||||||
|
"[upgrade] k8s patch version no longer supported, user confirms to skip k8s and continue upgrade. Valid K8s patch version is used in config afterwards": {
|
||||||
|
createConfig: func(require *require.Assertions, fh file.Handler) {
|
||||||
|
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP)
|
||||||
|
|
||||||
|
// use first version in list (oldest) as it should never have a patch version
|
||||||
|
versionParts := strings.Split(versions.SupportedK8sVersions()[0], ".")
|
||||||
|
versionParts[len(versionParts)-1] = "0"
|
||||||
|
cfg.KubernetesVersion = versions.ValidK8sVersion(strings.Join(versionParts, "."))
|
||||||
|
require.NoError(fh.WriteYAML(constants.ConfigFilename, cfg))
|
||||||
|
},
|
||||||
|
createState: postInitState(cloudprovider.GCP),
|
||||||
|
createMasterSecret: defaultMasterSecret,
|
||||||
|
createAdminConfig: defaultAdminConfig,
|
||||||
|
createTfState: defaultTfState,
|
||||||
|
stdin: "y\n",
|
||||||
|
wantPhases: newPhases(skipInitPhase, skipK8sPhase),
|
||||||
|
assert: func(require *require.Assertions, assert *assert.Assertions, conf *config.Config, stateFile *state.State) {
|
||||||
|
assert.NotEmpty(conf.KubernetesVersion)
|
||||||
|
_, err := versions.NewValidK8sVersion(string(conf.KubernetesVersion), true)
|
||||||
|
assert.NoError(err)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
|
@ -423,7 +447,7 @@ func TestValidateInputs(t *testing.T) {
|
||||||
flags: tc.flags,
|
flags: tc.flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err := a.validateInputs(cmd, &stubAttestationFetcher{})
|
conf, state, err := a.validateInputs(cmd, &stubAttestationFetcher{})
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -434,6 +458,10 @@ func TestValidateInputs(t *testing.T) {
|
||||||
t.Log(cfgErr.LongMessage())
|
t.Log(cfgErr.LongMessage())
|
||||||
}
|
}
|
||||||
assert.Equal(tc.wantPhases, a.flags.skipPhases)
|
assert.Equal(tc.wantPhases, a.flags.skipPhases)
|
||||||
|
|
||||||
|
if tc.assert != nil {
|
||||||
|
tc.assert(require, assert, conf, state)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||||
|
load("//bazel/go:go_test.bzl", "go_test")
|
||||||
load("//bazel/proto:rules.bzl", "write_go_proto_srcs")
|
load("//bazel/proto:rules.bzl", "write_go_proto_srcs")
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
|
@ -30,3 +31,13 @@ write_go_proto_srcs(
|
||||||
go_proto_library = ":components_go_proto",
|
go_proto_library = ":components_go_proto",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "components_test",
|
||||||
|
srcs = ["components_test.go"],
|
||||||
|
embed = [":components"],
|
||||||
|
deps = [
|
||||||
|
"@com_github_stretchr_testify//assert",
|
||||||
|
"@com_github_stretchr_testify//require",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
@ -8,6 +8,7 @@ package components
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -16,6 +17,53 @@ import (
|
||||||
// Components is a list of Kubernetes components.
|
// Components is a list of Kubernetes components.
|
||||||
type Components []*Component
|
type Components []*Component
|
||||||
|
|
||||||
|
type legacyComponent struct {
|
||||||
|
URL string `json:"URL,omitempty"`
|
||||||
|
Hash string `json:"Hash,omitempty"`
|
||||||
|
InstallPath string `json:"InstallPath,omitempty"`
|
||||||
|
Extract bool `json:"Extract,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements a custom JSON unmarshaler to ensure backwards compatibility
|
||||||
|
// with older components lists which had a different format for all keys.
|
||||||
|
func (c *Components) UnmarshalJSON(b []byte) error {
|
||||||
|
var legacyComponents []*legacyComponent
|
||||||
|
if err := json.Unmarshal(b, &legacyComponents); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var components []*Component
|
||||||
|
if err := json.Unmarshal(b, &components); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(legacyComponents) != len(components) {
|
||||||
|
return errors.New("failed to unmarshal data: inconsistent number of components in list") // just a check, should never happen
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a value is not set in the new format,
|
||||||
|
// it might have been set in the old format.
|
||||||
|
// In this case, we copy the value from the old format.
|
||||||
|
comps := make(Components, len(components))
|
||||||
|
for idx := 0; idx < len(components); idx++ {
|
||||||
|
comps[idx] = components[idx]
|
||||||
|
if comps[idx].Url == "" {
|
||||||
|
comps[idx].Url = legacyComponents[idx].URL
|
||||||
|
}
|
||||||
|
if comps[idx].Hash == "" {
|
||||||
|
comps[idx].Hash = legacyComponents[idx].Hash
|
||||||
|
}
|
||||||
|
if comps[idx].InstallPath == "" {
|
||||||
|
comps[idx].InstallPath = legacyComponents[idx].InstallPath
|
||||||
|
}
|
||||||
|
if !comps[idx].Extract {
|
||||||
|
comps[idx].Extract = legacyComponents[idx].Extract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = comps
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetHash returns the hash over all component hashes.
|
// GetHash returns the hash over all component hashes.
|
||||||
func (c Components) GetHash() string {
|
func (c Components) GetHash() string {
|
||||||
sha := sha256.New()
|
sha := sha256.New()
|
||||||
|
|
31
internal/versions/components/components_test.go
Normal file
31
internal/versions/components/components_test.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package components
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalComponents(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
legacyFormat := `[{"URL":"https://example.com/foo.tar.gz","Hash":"1234567890","InstallPath":"/foo","Extract":true}]`
|
||||||
|
newFormat := `[{"url":"https://example.com/foo.tar.gz","hash":"1234567890","install_path":"/foo","extract":true}]`
|
||||||
|
|
||||||
|
var fromLegacy Components
|
||||||
|
require.NoError(json.Unmarshal([]byte(legacyFormat), &fromLegacy))
|
||||||
|
|
||||||
|
var fromNew Components
|
||||||
|
require.NoError(json.Unmarshal([]byte(newFormat), &fromNew))
|
||||||
|
|
||||||
|
assert.Equal(fromLegacy, fromNew)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue