pkg/workflows/management/upgrade_management_components.go (199 lines of code) (raw):
package management
import (
"context"
"fmt"
"github.com/aws/eks-anywhere/pkg/cluster"
"github.com/aws/eks-anywhere/pkg/filewriter"
"github.com/aws/eks-anywhere/pkg/logger"
"github.com/aws/eks-anywhere/pkg/providers"
"github.com/aws/eks-anywhere/pkg/task"
"github.com/aws/eks-anywhere/pkg/types"
"github.com/aws/eks-anywhere/pkg/validations"
"github.com/aws/eks-anywhere/pkg/workflows"
"github.com/aws/eks-anywhere/pkg/workflows/interfaces"
v1releasealpha1 "github.com/aws/eks-anywhere/release/api/v1alpha1"
)
// UpgradeManagementComponentsWorkflow is a schema for upgrade management components.
type UpgradeManagementComponentsWorkflow struct {
clientFactory interfaces.ClientFactory
provider providers.Provider
clusterManager interfaces.ClusterManager
gitOpsManager interfaces.GitOpsManager
writer filewriter.FileWriter
capiManager interfaces.CAPIManager
eksdInstaller interfaces.EksdInstaller
eksdUpgrader interfaces.EksdUpgrader
}
// NewUpgradeManagementComponentsRunner builds a new UpgradeManagementCommponents construct.
func NewUpgradeManagementComponentsRunner(
clientFactory interfaces.ClientFactory,
provider providers.Provider,
capiManager interfaces.CAPIManager,
clusterManager interfaces.ClusterManager,
gitOpsManager interfaces.GitOpsManager,
writer filewriter.FileWriter,
eksdUpgrader interfaces.EksdUpgrader,
eksdInstaller interfaces.EksdInstaller,
) *UpgradeManagementComponentsWorkflow {
return &UpgradeManagementComponentsWorkflow{
clientFactory: clientFactory,
provider: provider,
clusterManager: clusterManager,
gitOpsManager: gitOpsManager,
writer: writer,
capiManager: capiManager,
eksdUpgrader: eksdUpgrader,
eksdInstaller: eksdInstaller,
}
}
// UMCValidator is a struct that holds a cluster and a kubectl executable.
// It is used to perform preflight validations on the cluster.
type UMCValidator struct {
cluster *types.Cluster
eksaRelease *v1releasealpha1.EKSARelease
kubectl validations.KubectlClient
skipValidations []string
}
// NewUMCValidator is a constructor function that creates a new instance of UMCValidator.
func NewUMCValidator(cluster *types.Cluster, eksaRelease *v1releasealpha1.EKSARelease, kubectl validations.KubectlClient, skipValidations []string) *UMCValidator {
return &UMCValidator{
cluster: cluster,
eksaRelease: eksaRelease,
kubectl: kubectl,
skipValidations: skipValidations,
}
}
// PreflightValidations is a method of the UMCValidator struct.
// It performs preflight validations on the cluster and returns a slice of Validation objects.
func (u *UMCValidator) PreflightValidations(ctx context.Context) []validations.Validation {
preflightValidations := []validations.Validation{
func() *validations.ValidationResult {
return &validations.ValidationResult{
Name: "control plane ready",
Remediation: fmt.Sprintf("ensure control plane nodes and pods for cluster %s are Ready", u.cluster.Name),
Err: u.kubectl.ValidateControlPlaneNodes(ctx, u.cluster, u.cluster.Name),
}
},
func() *validations.ValidationResult {
return &validations.ValidationResult{
Name: "cluster CRDs ready",
Remediation: "",
Err: u.kubectl.ValidateClustersCRD(ctx, u.cluster),
}
},
}
var skipVersionSkew bool
// When upgrading management components, the only preflight check that can be skipped
// is the EKS-A version skew validation. While the skipValidations flag accepts multiple
// values for consistency with the cluster upgrade command, only the EKS-A version skew
// check (validations.EksaVersionSkew) will be honored - all other skip requests will
// be ignored.
if len(u.skipValidations) != 0 {
for _, val := range u.skipValidations {
if val == validations.EksaVersionSkew {
skipVersionSkew = true
}
}
}
if !skipVersionSkew {
eksaVersionSkewValidation := func() *validations.ValidationResult {
return &validations.ValidationResult{
Name: "validate compatibility of management components version to cluster eksaVersion",
Remediation: "",
Err: validations.ValidateManagementComponentsVersionSkew(ctx, u.kubectl, u.cluster, u.eksaRelease),
}
}
preflightValidations = append(preflightValidations, eksaVersionSkewValidation)
}
return preflightValidations
}
// Run Upgrade implements upgrade functionality for management cluster's upgrade operation.
func (umc *UpgradeManagementComponentsWorkflow) Run(ctx context.Context, clusterSpec *cluster.Spec, managementCluster *types.Cluster, validator interfaces.Validator) error {
commandContext := &task.CommandContext{
ClientFactory: umc.clientFactory,
Provider: umc.provider,
ClusterManager: umc.clusterManager,
ManagementCluster: managementCluster,
ClusterSpec: clusterSpec,
Validations: validator,
Writer: umc.writer,
CAPIManager: umc.capiManager,
UpgradeChangeDiff: types.NewChangeDiff(),
GitOpsManager: umc.gitOpsManager,
EksdUpgrader: umc.eksdUpgrader,
EksdInstaller: umc.eksdInstaller,
}
return task.NewTaskRunner(&setupAndValidateMC{}, umc.writer).RunTask(ctx, commandContext)
}
type setupAndValidateMC struct{}
// Run setupAndValidate validates management cluster before upgrade process starts.
func (s *setupAndValidateMC) Run(ctx context.Context, commandContext *task.CommandContext) task.Task {
logger.Info("Performing setup and validations")
currentSpec, err := commandContext.ClusterManager.GetCurrentClusterSpec(ctx, commandContext.ManagementCluster, commandContext.ClusterSpec.Cluster.Name)
if err != nil {
commandContext.SetError(err)
return nil
}
commandContext.CurrentClusterSpec = currentSpec
runner := validations.NewRunner()
runner.Register(
func() *validations.ValidationResult {
return &validations.ValidationResult{
Name: fmt.Sprintf("%s provider setup and validation", commandContext.Provider.Name()),
Err: commandContext.Provider.SetupAndValidateUpgradeManagementComponents(ctx, commandContext.ClusterSpec),
}
},
)
runner.Register(commandContext.Validations.PreflightValidations(ctx)...)
err = runner.Run()
if err != nil {
commandContext.SetError(err)
return nil
}
return &upgradeCoreComponentsMC{
UpgradeChangeDiff: &types.ChangeDiff{},
}
}
func (s *setupAndValidateMC) Name() string {
return "validate"
}
func (s *setupAndValidateMC) Restore(_ context.Context, _ *task.CommandContext, _ *task.CompletedTask) (task.Task, error) {
return nil, nil
}
func (s *setupAndValidateMC) Checkpoint() *task.CompletedTask {
return &task.CompletedTask{
Checkpoint: nil,
}
}
// This struct is similar to upgradeCoreComponents, but its returned value is different in Run() function.
type upgradeCoreComponentsMC struct {
UpgradeChangeDiff *types.ChangeDiff
}
func (s *upgradeCoreComponentsMC) Name() string {
return "upgrade-core-components-mc"
}
func (s *upgradeCoreComponentsMC) Checkpoint() *task.CompletedTask {
return &task.CompletedTask{
Checkpoint: s.UpgradeChangeDiff,
}
}
func (s *upgradeCoreComponentsMC) Restore(_ context.Context, commandContext *task.CommandContext, completedTask *task.CompletedTask) (task.Task, error) {
s.UpgradeChangeDiff = &types.ChangeDiff{}
if err := task.UnmarshalTaskCheckpoint(completedTask.Checkpoint, s.UpgradeChangeDiff); err != nil {
return nil, err
}
commandContext.UpgradeChangeDiff = s.UpgradeChangeDiff
return &installNewComponentsMC{}, nil
}
func (s *upgradeCoreComponentsMC) Run(ctx context.Context, commandContext *task.CommandContext) task.Task {
if err := runUpgradeCoreComponents(ctx, commandContext); err != nil {
return &workflows.CollectMgmtClusterDiagnosticsTask{}
}
return &installNewComponentsMC{}
}
// This struct is similar to installNewComponents, but its returned value is different in Run() function.
type installNewComponentsMC struct{}
func (s *installNewComponentsMC) Run(ctx context.Context, commandContext *task.CommandContext) task.Task {
if err := runInstallNewComponents(ctx, commandContext); err != nil {
return &workflows.CollectMgmtClusterDiagnosticsTask{}
}
if commandContext.OriginalError == nil {
logger.MarkSuccess("Management components upgraded!")
}
return nil
}
func (s *installNewComponentsMC) Name() string {
return "install-new-eksa-version-components-mc"
}
func (s *installNewComponentsMC) Checkpoint() *task.CompletedTask {
return &task.CompletedTask{
Checkpoint: nil,
}
}
func (s *installNewComponentsMC) Restore(_ context.Context, _ *task.CommandContext, _ *task.CompletedTask) (task.Task, error) {
return nil, nil
}