mirror of
				https://github.com/siderolabs/talos.git
				synced 2025-10-31 08:21:25 +01:00 
			
		
		
		
	To be used in the `go-talos-support` module without importing the whole Talos repo. Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.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/machinery/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]
 | |
| }
 |