Andrey Smirnov 2f4b9d8d6d
feat: make machine configuration read-only in Talos (almost)
Talos shouldn't try to re-encode the machine config it was provided
with.

So add a `ReadonlyWrapper` around `*v1alpha1.Config` which makes sure
that raw config object is not available anymore (it's a private field),
but config accessors are available for read-only access.

Another thing that `ReadonlyWrapper` does is that it preserves the
original `[]byte` encoding of the config keeping it exactly same way as
it was loaded from file or read over the network.

Improved `talosctl edit mc` to preserve the config as it was submitted,
and preserve the edits on error from Talos (previously edits were lost).

`ReadonlyWrapper` is not used on config generation path though - config
there is represented by `*v1alpha.Config` and can be freely modified.

Why almost? Some parts of Talos (platform code) patch the machine
configuration with new data. We need to fix platforms to provide
networking configuration in a different way, but this will come with
other PRs later.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
2021-12-28 20:12:55 +03:00

69 lines
1.6 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package kubernetes
import (
"context"
"fmt"
"strings"
"github.com/coreos/go-semver/semver"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DetectLowestVersion returns lowest Kubernetes components versions in the cluster.
//nolint:gocyclo
func DetectLowestVersion(ctx context.Context, cluster UpgradeProvider, options UpgradeOptions) (string, error) {
k8sClient, err := cluster.K8sHelper(ctx)
if err != nil {
return "", fmt.Errorf("error building kubernetes client: %w", err)
}
apps := map[string]struct{}{
"kube-apiserver": {},
"kube-controller-manager": {},
"kube-proxy": {},
"kube-scheduler": {},
}
pods, err := k8sClient.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{})
if err != nil {
return "", err
}
var version *semver.Version
for _, pod := range pods.Items {
app := pod.GetObjectMeta().GetLabels()["k8s-app"]
if _, ok := apps[app]; !ok {
continue
}
for _, container := range pod.Spec.Containers {
if container.Name != app {
continue
}
parts := strings.Split(container.Image, ":")
if len(parts) == 1 {
continue
}
v, err := semver.NewVersion(strings.TrimLeft(parts[1], "v"))
if err != nil {
options.Log("failed to parse %s container version %s", app, err)
continue
}
if version == nil || v.LessThan(*version) {
version = v
}
}
}
return version.String(), nil
}