mirror of
				https://github.com/siderolabs/talos.git
				synced 2025-10-31 16:31:13 +01:00 
			
		
		
		
	Struct MetaValue has methods on both value and pointer receivers. Such usage is not recommended by the Go Documentation. Modifies the receiver usage. Variable config collides with imported package name. Renames the variable config. Removes a redundant alias. Empty slice declaration uses a literal. Replaces with nil slice declaration. Signed-off-by: Nanfei Chen <chennanfei@yeah.net> Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
		
			
				
	
	
		
			271 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			7.3 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 install
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/cosi-project/runtime/pkg/safe"
 | |
| 	"github.com/cosi-project/runtime/pkg/state"
 | |
| 	"google.golang.org/grpc"
 | |
| 	"google.golang.org/grpc/credentials/insecure"
 | |
| 	"google.golang.org/grpc/metadata"
 | |
| 
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/client"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/compatibility"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/constants"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/resources/k8s"
 | |
| 	"github.com/siderolabs/talos/pkg/machinery/role"
 | |
| 	"github.com/siderolabs/talos/pkg/version"
 | |
| )
 | |
| 
 | |
| // PreflightChecks runs the preflight checks.
 | |
| type PreflightChecks struct {
 | |
| 	disabled bool
 | |
| 	client   *client.Client
 | |
| 
 | |
| 	installerTalosVersion *compatibility.TalosVersion
 | |
| 	hostTalosVersion      *compatibility.TalosVersion
 | |
| }
 | |
| 
 | |
| // NewPreflightChecks initializes and returns the installation PreflightChecks.
 | |
| func NewPreflightChecks(ctx context.Context) (*PreflightChecks, error) {
 | |
| 	if _, err := os.Stat(constants.MachineSocketPath); err != nil {
 | |
| 		log.Printf("pre-flight checks disabled, as host Talos version is too old")
 | |
| 
 | |
| 		return &PreflightChecks{disabled: true}, nil //nolint:nilerr
 | |
| 	}
 | |
| 
 | |
| 	c, err := client.New(ctx,
 | |
| 		client.WithUnixSocket(constants.MachineSocketPath),
 | |
| 		client.WithGRPCDialOptions(grpc.WithTransportCredentials(insecure.NewCredentials())),
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error connecting to the machine service: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return &PreflightChecks{
 | |
| 		client: c,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // Close closes the client.
 | |
| func (checks *PreflightChecks) Close() error {
 | |
| 	if checks.disabled {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return checks.client.Close()
 | |
| }
 | |
| 
 | |
| // Run the checks, return the error if the check fails.
 | |
| func (checks *PreflightChecks) Run(ctx context.Context) error {
 | |
| 	if checks.disabled {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	log.Printf("running pre-flight checks")
 | |
| 
 | |
| 	// inject "fake" authorization
 | |
| 	ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(constants.APIAuthzRoleMetadataKey, string(role.Admin)))
 | |
| 
 | |
| 	for _, check := range []func(context.Context) error{
 | |
| 		checks.talosVersion,
 | |
| 		checks.kubernetesVersion,
 | |
| 	} {
 | |
| 		if err := check(ctx); err != nil {
 | |
| 			return fmt.Errorf("pre-flight checks failed: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	log.Printf("all pre-flight checks successful")
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (checks *PreflightChecks) talosVersion(ctx context.Context) error {
 | |
| 	resp, err := checks.client.Version(ctx)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error getting Talos version: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	hostVersion := unpack(resp.Messages)
 | |
| 
 | |
| 	log.Printf("host Talos version: %s", hostVersion.Version.Tag)
 | |
| 
 | |
| 	checks.hostTalosVersion, err = compatibility.ParseTalosVersion(hostVersion.Version)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error parsing host Talos version: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	checks.installerTalosVersion, err = compatibility.ParseTalosVersion(version.NewVersion())
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error parsing installer Talos version: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return checks.installerTalosVersion.UpgradeableFrom(checks.hostTalosVersion)
 | |
| }
 | |
| 
 | |
| type k8sVersions struct {
 | |
| 	kubelet           *compatibility.KubernetesVersion
 | |
| 	apiServer         *compatibility.KubernetesVersion
 | |
| 	scheduler         *compatibility.KubernetesVersion
 | |
| 	controllerManager *compatibility.KubernetesVersion
 | |
| }
 | |
| 
 | |
| //nolint:gocyclo
 | |
| func (versions *k8sVersions) gatherVersions(ctx context.Context, client *client.Client) error {
 | |
| 	kubeletSpec, err := safe.StateGet[*k8s.KubeletSpec](ctx, client.COSI, k8s.NewKubeletSpec(k8s.NamespaceName, k8s.KubeletID).Metadata())
 | |
| 	if err != nil && !state.IsNotFoundError(err) {
 | |
| 		return fmt.Errorf("error getting kubelet spec: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if kubeletSpec != nil {
 | |
| 		versions.kubelet, err = kubernetesVersionFromImageRef(kubeletSpec.TypedSpec().Image)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error parsing kubelet version: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	apiServerSpec, err := safe.StateGet[*k8s.APIServerConfig](ctx, client.COSI, k8s.NewAPIServerConfig().Metadata())
 | |
| 	if err != nil && !state.IsNotFoundError(err) {
 | |
| 		return fmt.Errorf("error getting API server spec: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if apiServerSpec != nil {
 | |
| 		versions.apiServer, err = kubernetesVersionFromImageRef(apiServerSpec.TypedSpec().Image)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error parsing API server version: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	schedulerSpec, err := safe.StateGet[*k8s.SchedulerConfig](ctx, client.COSI, k8s.NewSchedulerConfig().Metadata())
 | |
| 	if err != nil && !state.IsNotFoundError(err) {
 | |
| 		return fmt.Errorf("error getting scheduler spec: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if schedulerSpec != nil {
 | |
| 		versions.scheduler, err = kubernetesVersionFromImageRef(schedulerSpec.TypedSpec().Image)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error parsing scheduler version: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	controllerManagerSpec, err := safe.StateGet[*k8s.ControllerManagerConfig](ctx, client.COSI, k8s.NewControllerManagerConfig().Metadata())
 | |
| 	if err != nil && !state.IsNotFoundError(err) {
 | |
| 		return fmt.Errorf("error getting controller manager spec: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if controllerManagerSpec != nil {
 | |
| 		versions.controllerManager, err = kubernetesVersionFromImageRef(controllerManagerSpec.TypedSpec().Image)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error parsing controller manager version: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (versions *k8sVersions) checkCompatibility(target *compatibility.TalosVersion) error {
 | |
| 	for _, component := range []struct {
 | |
| 		name    string
 | |
| 		version *compatibility.KubernetesVersion
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "kubelet",
 | |
| 			version: versions.kubelet,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "kube-apiserver",
 | |
| 			version: versions.apiServer,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "kube-scheduler",
 | |
| 			version: versions.scheduler,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "kube-controller-manager",
 | |
| 			version: versions.controllerManager,
 | |
| 		},
 | |
| 	} {
 | |
| 		if component.version == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err := component.version.SupportedWith(target); err != nil {
 | |
| 			return fmt.Errorf("component %s version issue: %w", component.name, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (versions *k8sVersions) String() string {
 | |
| 	var components []string //nolint:prealloc
 | |
| 
 | |
| 	for _, component := range []struct {
 | |
| 		name    string
 | |
| 		version *compatibility.KubernetesVersion
 | |
| 	}{
 | |
| 		{
 | |
| 			name:    "kubelet",
 | |
| 			version: versions.kubelet,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "kube-apiserver",
 | |
| 			version: versions.apiServer,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "kube-scheduler",
 | |
| 			version: versions.scheduler,
 | |
| 		},
 | |
| 		{
 | |
| 			name:    "kube-controller-manager",
 | |
| 			version: versions.controllerManager,
 | |
| 		},
 | |
| 	} {
 | |
| 		if component.version == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		components = append(components, fmt.Sprintf("%s: %s", component.name, component.version))
 | |
| 	}
 | |
| 
 | |
| 	return strings.Join(components, ", ")
 | |
| }
 | |
| 
 | |
| func (checks *PreflightChecks) kubernetesVersion(ctx context.Context) error {
 | |
| 	var versions k8sVersions
 | |
| 
 | |
| 	if err := versions.gatherVersions(ctx, checks.client); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	log.Printf("host Kubernetes versions: %s", &versions)
 | |
| 
 | |
| 	return versions.checkCompatibility(checks.installerTalosVersion)
 | |
| }
 | |
| 
 | |
| func kubernetesVersionFromImageRef(ref string) (*compatibility.KubernetesVersion, error) {
 | |
| 	idx := strings.LastIndex(ref, ":v")
 | |
| 	if idx == -1 {
 | |
| 		return nil, fmt.Errorf("invalid image reference: %q", ref)
 | |
| 	}
 | |
| 
 | |
| 	return compatibility.ParseKubernetesVersion(ref[idx+2:])
 | |
| }
 | |
| 
 | |
| func unpack[T any](s []T) T {
 | |
| 	if len(s) != 1 {
 | |
| 		panic("unpack: slice length is not 1")
 | |
| 	}
 | |
| 
 | |
| 	return s[0]
 | |
| }
 |