cli: store upgrade files in versioned folders (#1929)

* upgrade versioning

* dont pass upgrade kind as boolean

* whitespace

* fix godot lint check

* clarify upgrade check directory suffix

* cli: dry-run Terraform migrations on `upgrade check` (#1942)

* dry-run Terraform migrations on upgrade check

* clean whole upgrade dir

* clean up check workspace after planning

* fix parsing

* extend upgrade check test

* rename unused parameters

* exclude false positives in test
This commit is contained in:
Moritz Sanft 2023-06-21 09:22:32 +02:00 committed by GitHub
parent f3c2198a9a
commit b25228d175
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 300 additions and 127 deletions

View file

@ -33,6 +33,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/edgelesssys/constellation/v2/internal/versions/components"
updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1"
"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -44,6 +45,16 @@ import (
"k8s.io/client-go/tools/clientcmd"
)
// UpgradeCmdKind is the kind of the upgrade command (check, apply).
type UpgradeCmdKind int
const (
// UpgradeCmdKindCheck corresponds to the upgrade check command.
UpgradeCmdKindCheck UpgradeCmdKind = iota
// UpgradeCmdKindApply corresponds to the upgrade apply command.
UpgradeCmdKindApply
)
// ErrInProgress signals that an upgrade is in progress inside the cluster.
var ErrInProgress = errors.New("upgrade in progress")
@ -85,10 +96,26 @@ type Upgrader struct {
outWriter io.Writer
tfUpgrader *upgrade.TerraformUpgrader
log debugLog
upgradeID string
}
// NewUpgrader returns a new Upgrader.
func NewUpgrader(ctx context.Context, outWriter io.Writer, log debugLog) (*Upgrader, error) {
func NewUpgrader(ctx context.Context, outWriter io.Writer, log debugLog, upgradeCmdKind UpgradeCmdKind) (*Upgrader, error) {
upgradeID := "upgrade-" + time.Now().Format("20060102150405") + "-" + strings.Split(uuid.New().String(), "-")[0]
if upgradeCmdKind == UpgradeCmdKindCheck {
// When performing an upgrade check, the upgrade directory will only be used temporarily to store the
// Terraform state. The directory is deleted after the check is finished.
// Therefore, add a tmp-suffix to the upgrade ID to indicate that the directory will be cleared after the check.
upgradeID += "-tmp"
}
u := &Upgrader{
imageFetcher: imagefetcher.New(),
outWriter: outWriter,
log: log,
upgradeID: upgradeID,
}
kubeConfig, err := clientcmd.BuildConfigFromFlags("", constants.AdminConfFilename)
if err != nil {
return nil, fmt.Errorf("building kubernetes config: %w", err)
@ -98,19 +125,22 @@ func NewUpgrader(ctx context.Context, outWriter io.Writer, log debugLog) (*Upgra
if err != nil {
return nil, fmt.Errorf("setting up kubernetes client: %w", err)
}
u.stableInterface = &stableClient{client: kubeClient}
// use unstructured client to avoid importing the operator packages
unstructuredClient, err := dynamic.NewForConfig(kubeConfig)
if err != nil {
return nil, fmt.Errorf("setting up custom resource client: %w", err)
}
u.dynamicInterface = &NodeVersionClient{client: unstructuredClient}
helmClient, err := helm.NewClient(kubectl.New(), constants.AdminConfFilename, constants.HelmNamespace, log)
if err != nil {
return nil, fmt.Errorf("setting up helm client: %w", err)
}
u.helmClient = helmClient
tfClient, err := terraform.New(ctx, filepath.Join(constants.UpgradeDir, constants.TerraformUpgradeWorkingDir))
tfClient, err := terraform.New(ctx, filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir))
if err != nil {
return nil, fmt.Errorf("setting up terraform client: %w", err)
}
@ -119,35 +149,28 @@ func NewUpgrader(ctx context.Context, outWriter io.Writer, log debugLog) (*Upgra
if err != nil {
return nil, fmt.Errorf("setting up terraform upgrader: %w", err)
}
u.tfUpgrader = tfUpgrader
return &Upgrader{
stableInterface: &stableClient{client: kubeClient},
dynamicInterface: &NodeVersionClient{client: unstructuredClient},
helmClient: helmClient,
imageFetcher: imagefetcher.New(),
outWriter: outWriter,
tfUpgrader: tfUpgrader,
log: log,
}, nil
return u, nil
}
// CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace.
// If the files that will be written during the upgrade already exist, it returns an error.
func (u *Upgrader) CheckTerraformMigrations(fileHandler file.Handler) error {
return u.tfUpgrader.CheckTerraformMigrations(fileHandler)
return u.tfUpgrader.CheckTerraformMigrations(fileHandler, u.upgradeID)
}
// CleanUpTerraformMigrations cleans up the Terraform migration workspace, for example when an upgrade is
// aborted by the user.
func (u *Upgrader) CleanUpTerraformMigrations(fileHandler file.Handler) error {
return u.tfUpgrader.CleanUpTerraformMigrations(fileHandler)
return u.tfUpgrader.CleanUpTerraformMigrations(fileHandler, u.upgradeID)
}
// PlanTerraformMigrations prepares the upgrade workspace and plans the Terraform migrations for the Constellation upgrade.
// If a diff exists, it's being written to the upgrader's output writer. It also returns
// a bool indicating whether a diff exists.
func (u *Upgrader) PlanTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (bool, error) {
return u.tfUpgrader.PlanTerraformMigrations(ctx, opts)
return u.tfUpgrader.PlanTerraformMigrations(ctx, opts, u.upgradeID)
}
// ApplyTerraformMigrations applies the migerations planned by PlanTerraformMigrations.
@ -155,12 +178,12 @@ func (u *Upgrader) PlanTerraformMigrations(ctx context.Context, opts upgrade.Ter
// In case of a successful upgrade, the output will be written to the specified file and the old Terraform directory is replaced
// By the new one.
func (u *Upgrader) ApplyTerraformMigrations(ctx context.Context, fileHandler file.Handler, opts upgrade.TerraformUpgradeOptions) error {
return u.tfUpgrader.ApplyTerraformMigrations(ctx, fileHandler, opts)
return u.tfUpgrader.ApplyTerraformMigrations(ctx, fileHandler, opts, u.upgradeID)
}
// UpgradeHelmServices upgrade helm services.
func (u *Upgrader) UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error {
return u.helmClient.Upgrade(ctx, config, timeout, allowDestructive)
return u.helmClient.Upgrade(ctx, config, timeout, allowDestructive, u.upgradeID)
}
// UpgradeNodeVersion upgrades the cluster's NodeVersion object and in turn triggers image & k8s version upgrades.
@ -526,7 +549,7 @@ func joinConfigMigration(existingConf *corev1.ConfigMap, attestVariant variant.V
}
type helmInterface interface {
Upgrade(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool) error
Upgrade(ctx context.Context, config *config.Config, timeout time.Duration, allowDestructive bool, upgradeID string) error
}
type debugLog interface {