cli: move helm and terraform out of kubernetes package (#2222)

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-08-16 09:59:32 +02:00 committed by GitHub
parent f270e91724
commit ed0bfd9d41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 370 additions and 376 deletions

View File

@ -8,8 +8,6 @@ package cmd
import (
"errors"
"fmt"
"strings"
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/cli/internal/upgrade"
@ -18,7 +16,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/google/uuid"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
@ -70,7 +67,8 @@ func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error {
if err != nil {
return err
}
upgradeID := "iam-" + time.Now().Format("20060102150405") + "-" + strings.Split(uuid.New().String(), "-")[0]
upgradeID := generateUpgradeID(upgradeCmdKindIAM)
iamMigrateCmd, err := upgrade.NewIAMMigrateCmd(cmd.Context(), constants.TerraformIAMWorkingDir, constants.UpgradeDir, upgradeID, conf.GetProvider(), terraform.LogLevelDebug)
if err != nil {
return fmt.Errorf("setting up IAM migration command: %w", err)
@ -86,10 +84,10 @@ func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error {
if err != nil {
return err
}
err = migrator.applyMigration(cmd, constants.UpgradeDir, file.NewHandler(afero.NewOsFs()), iamMigrateCmd, yes)
if err != nil {
if err := migrator.applyMigration(cmd, constants.UpgradeDir, file.NewHandler(afero.NewOsFs()), iamMigrateCmd, yes); err != nil {
return fmt.Errorf("applying IAM migration: %w", err)
}
cmd.Println("IAM profile successfully applied.")
return nil
}

View File

@ -7,6 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only
package cmd
import (
"strings"
"time"
"github.com/google/uuid"
"github.com/spf13/cobra"
)
@ -23,3 +27,31 @@ func NewUpgradeCmd() *cobra.Command {
cmd.AddCommand(newUpgradeApplyCmd())
return cmd
}
// 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
// upgradeCmdKindIAM corresponds to the IAM upgrade command.
upgradeCmdKindIAM
)
func generateUpgradeID(kind upgradeCmdKind) string {
upgradeID := time.Now().Format("20060102150405") + "-" + strings.Split(uuid.New().String(), "-")[0]
switch kind {
case 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 = "upgrade-" + upgradeID + "-tmp"
case upgradeCmdKindApply:
upgradeID = "upgrade-" + upgradeID
case upgradeCmdKindIAM:
upgradeID = "iam-" + upgradeID
}
return upgradeID
}

View File

@ -28,6 +28,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/rogpeppe/go-internal/diff"
"github.com/spf13/afero"
@ -64,33 +65,54 @@ func runUpgradeApply(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("creating logger: %w", err)
}
defer log.Sync()
fileHandler := file.NewHandler(afero.NewOsFs())
upgrader, err := kubernetes.NewUpgrader(
cmd.Context(), cmd.OutOrStdout(),
constants.UpgradeDir, constants.AdminConfFilename,
fileHandler, log, kubernetes.UpgradeCmdKindApply,
)
fileHandler := file.NewHandler(afero.NewOsFs())
upgradeID := generateUpgradeID(upgradeCmdKindApply)
kubeUpgrader, err := kubernetes.NewUpgrader(cmd.OutOrStdout(), constants.AdminConfFilename, log)
if err != nil {
return err
}
helmUpgrader, err := helm.NewUpgradeClient(kubectl.New(), constants.UpgradeDir, constants.AdminConfFilename, constants.HelmNamespace, log)
if err != nil {
return fmt.Errorf("setting up helm client: %w", err)
}
configFetcher := attestationconfigapi.NewFetcher()
tfClient, err := terraform.New(cmd.Context(), constants.TerraformWorkingDir)
// Set up two Terraform clients. They need to be configured with different workspaces
// One for upgrading existing resources
tfUpgrader, err := terraform.New(cmd.Context(), filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir))
if err != nil {
return fmt.Errorf("setting up terraform client: %w", err)
}
// And one for showing existing resources
tfShower, err := terraform.New(cmd.Context(), constants.TerraformWorkingDir)
if err != nil {
return fmt.Errorf("setting up terraform client: %w", err)
}
applyCmd := upgradeApplyCmd{upgrader: upgrader, log: log, configFetcher: configFetcher, clusterShower: tfClient, fileHandler: fileHandler}
applyCmd := upgradeApplyCmd{
helmUpgrader: helmUpgrader,
kubeUpgrader: kubeUpgrader,
terraformUpgrader: upgrade.NewTerraformUpgrader(tfUpgrader, cmd.OutOrStdout(), fileHandler, upgradeID),
configFetcher: configFetcher,
clusterShower: tfShower,
fileHandler: fileHandler,
log: log,
}
return applyCmd.upgradeApply(cmd)
}
type upgradeApplyCmd struct {
upgrader cloudUpgrader
configFetcher attestationconfigapi.Fetcher
clusterShower clusterShower
fileHandler file.Handler
log debugLog
helmUpgrader helmUpgrader
kubeUpgrader kubernetesUpgrader
terraformUpgrader terraformUpgrader
configFetcher attestationconfigapi.Fetcher
clusterShower clusterShower
fileHandler file.Handler
log debugLog
}
func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command) error {
@ -131,7 +153,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command) error {
}
if idFile.MeasurementSalt == nil {
// TODO(elchead): remove after 2.10, since 2.9 does not yet save it in the idfile
measurementSalt, err := u.upgrader.GetMeasurementSalt(cmd.Context())
measurementSalt, err := u.kubeUpgrader.GetMeasurementSalt(cmd.Context())
if err != nil {
return fmt.Errorf("getting join-config: %w", err)
}
@ -161,7 +183,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command) error {
// - fallback endpoint
// - custom (user-provided) endpoint
sans := append([]string{idFile.IP, conf.CustomEndpoint}, idFile.APIServerCertSANs...)
if err := u.upgrader.ExtendClusterConfigCertSANs(cmd.Context(), sans); err != nil {
if err := u.kubeUpgrader.ExtendClusterConfigCertSANs(cmd.Context(), sans); err != nil {
return fmt.Errorf("extending cert SANs: %w", err)
}
@ -177,7 +199,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command) error {
return fmt.Errorf("upgrading services: %w", err)
}
err = u.upgrader.UpgradeNodeVersion(cmd.Context(), conf, flags.force)
err = u.kubeUpgrader.UpgradeNodeVersion(cmd.Context(), conf, flags.force)
switch {
case errors.Is(err, kubernetes.ErrInProgress):
cmd.PrintErrln("Skipping image and Kubernetes upgrades. Another upgrade is in progress.")
@ -214,7 +236,7 @@ func (u *upgradeApplyCmd) migrateTerraform(
) (res terraform.ApplyOutput, err error) {
u.log.Debugf("Planning Terraform migrations")
if err := u.upgrader.CheckTerraformMigrations(constants.UpgradeDir); err != nil {
if err := u.terraformUpgrader.CheckTerraformMigrations(constants.UpgradeDir); err != nil {
return res, fmt.Errorf("checking workspace: %w", err)
}
@ -242,7 +264,7 @@ func (u *upgradeApplyCmd) migrateTerraform(
// u.upgrader.AddManualStateMigration(migration)
// }
hasDiff, err := u.upgrader.PlanTerraformMigrations(cmd.Context(), opts)
hasDiff, err := u.terraformUpgrader.PlanTerraformMigrations(cmd.Context(), opts)
if err != nil {
return res, fmt.Errorf("planning terraform migrations: %w", err)
}
@ -257,14 +279,14 @@ func (u *upgradeApplyCmd) migrateTerraform(
}
if !ok {
cmd.Println("Aborting upgrade.")
if err := u.upgrader.CleanUpTerraformMigrations(constants.UpgradeDir); err != nil {
if err := u.terraformUpgrader.CleanUpTerraformMigrations(constants.UpgradeDir); err != nil {
return res, fmt.Errorf("cleaning up workspace: %w", err)
}
return res, fmt.Errorf("aborted by user")
}
}
u.log.Debugf("Applying Terraform migrations")
tfOutput, err := u.upgrader.ApplyTerraformMigrations(cmd.Context(), opts)
tfOutput, err := u.terraformUpgrader.ApplyTerraformMigrations(cmd.Context(), opts)
if err != nil {
return tfOutput, fmt.Errorf("applying terraform migrations: %w", err)
}
@ -277,7 +299,9 @@ func (u *upgradeApplyCmd) migrateTerraform(
cmd.Printf("Terraform migrations applied successfully and output written to: %s\n"+
"A backup of the pre-upgrade state has been written to: %s\n",
flags.pf.PrefixPath(constants.ClusterIDsFilename), flags.pf.PrefixPath(filepath.Join(opts.UpgradeWorkspace, u.upgrader.GetUpgradeID(), constants.TerraformUpgradeBackupDir)))
flags.pf.PrefixPath(constants.ClusterIDsFilename),
flags.pf.PrefixPath(filepath.Join(opts.UpgradeWorkspace, u.terraformUpgrader.UpgradeID(), constants.TerraformUpgradeBackupDir)),
)
} else {
u.log.Debugf("No Terraform diff detected")
}
@ -327,7 +351,7 @@ func validK8sVersion(cmd *cobra.Command, version string, yes bool) (validVersion
// confirmIfUpgradeAttestConfigHasDiff checks if the locally configured measurements are different from the cluster's measurements.
// If so the function will ask the user to confirm (if --yes is not set).
func (u *upgradeApplyCmd) confirmIfUpgradeAttestConfigHasDiff(cmd *cobra.Command, newConfig config.AttestationCfg, flags upgradeApplyFlags) error {
clusterAttestationConfig, err := u.upgrader.GetClusterAttestationConfig(cmd.Context(), newConfig.GetVariant())
clusterAttestationConfig, err := u.kubeUpgrader.GetClusterAttestationConfig(cmd.Context(), newConfig.GetVariant())
if err != nil {
return fmt.Errorf("getting cluster attestation config: %w", err)
}
@ -357,10 +381,10 @@ func (u *upgradeApplyCmd) confirmIfUpgradeAttestConfigHasDiff(cmd *cobra.Command
}
}
// TODO(elchead): move this outside this function to remove the side effect.
if err := u.upgrader.BackupConfigMap(cmd.Context(), constants.JoinConfigMap); err != nil {
if err := u.kubeUpgrader.BackupConfigMap(cmd.Context(), constants.JoinConfigMap); err != nil {
return fmt.Errorf("backing up join-config: %w", err)
}
if err := u.upgrader.UpdateAttestationConfig(cmd.Context(), newConfig); err != nil {
if err := u.kubeUpgrader.UpdateAttestationConfig(cmd.Context(), newConfig); err != nil {
return fmt.Errorf("updating attestation config: %w", err)
}
return nil
@ -375,7 +399,11 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(cmd *cobra.Command, conf *config.
if err != nil {
return fmt.Errorf("getting service account URI: %w", err)
}
err = u.upgrader.UpgradeHelmServices(cmd.Context(), conf, idFile, flags.upgradeTimeout, helm.DenyDestructive, flags.force, flags.conformance, flags.helmWaitMode, secret, serviceAccURI, validK8sVersion, tfOutput)
err = u.helmUpgrader.Upgrade(
cmd.Context(), conf, idFile,
flags.upgradeTimeout, helm.DenyDestructive, flags.force, u.terraformUpgrader.UpgradeID(),
flags.conformance, flags.helmWaitMode, secret, serviceAccURI, validK8sVersion, tfOutput,
)
if errors.Is(err, helm.ErrConfirmationMissing) {
if !flags.yes {
cmd.PrintErrln("WARNING: Upgrading cert-manager will destroy all custom resources you have manually created that are based on the current version of cert-manager.")
@ -388,7 +416,11 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(cmd *cobra.Command, conf *config.
return nil
}
}
err = u.upgrader.UpgradeHelmServices(cmd.Context(), conf, idFile, flags.upgradeTimeout, helm.AllowDestructive, flags.force, flags.conformance, flags.helmWaitMode, secret, serviceAccURI, validK8sVersion, tfOutput)
err = u.helmUpgrader.Upgrade(
cmd.Context(), conf, idFile,
flags.upgradeTimeout, helm.AllowDestructive, flags.force, u.terraformUpgrader.UpgradeID(),
flags.conformance, flags.helmWaitMode, secret, serviceAccURI, validK8sVersion, tfOutput,
)
}
return err
@ -470,17 +502,27 @@ type upgradeApplyFlags struct {
helmWaitMode helm.WaitMode
}
type cloudUpgrader interface {
type kubernetesUpgrader interface {
UpgradeNodeVersion(ctx context.Context, conf *config.Config, force bool) error
UpgradeHelmServices(ctx context.Context, config *config.Config, idFile clusterid.File, timeout time.Duration, allowDestructive bool, force bool, conformance bool, helmWaitMode helm.WaitMode, masterSecret uri.MasterSecret, serviceAccURI string, validK8sVersion versions.ValidK8sVersion, tfOutput terraform.ApplyOutput) error
ExtendClusterConfigCertSANs(ctx context.Context, alternativeNames []string) error
GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, error)
UpdateAttestationConfig(ctx context.Context, newAttestConfig config.AttestationCfg) error
GetMeasurementSalt(ctx context.Context) ([]byte, error)
BackupConfigMap(ctx context.Context, name string) error
}
type helmUpgrader interface {
Upgrade(
ctx context.Context, config *config.Config, idFile clusterid.File, timeout time.Duration,
allowDestructive, force bool, upgradeID string, conformance bool, helmWaitMode helm.WaitMode,
masterSecret uri.MasterSecret, serviceAccURI string, validK8sVersion versions.ValidK8sVersion, tfOutput terraform.ApplyOutput,
) error
}
type terraformUpgrader interface {
PlanTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (bool, error)
ApplyTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (terraform.ApplyOutput, error)
CheckTerraformMigrations(upgradeWorkspace string) error
CleanUpTerraformMigrations(upgradeWorkspace string) error
GetUpgradeID() string
BackupConfigMap(ctx context.Context, name string) error
UpgradeID() string
}

View File

@ -9,7 +9,6 @@ package cmd
import (
"bytes"
"context"
"errors"
"testing"
"time"
@ -32,87 +31,106 @@ import (
)
func TestUpgradeApply(t *testing.T) {
someErr := errors.New("some error")
testCases := map[string]struct {
upgrader *stubUpgrader
helmUpgrader *stubHelmUpgrader
kubeUpgrader *stubKubernetesUpgrader
terraformUpgrader *stubTerraformUpgrader
wantErr bool
yesFlag bool
dontWantJoinConfigBackup bool
stdin string
}{
"success": {
upgrader: &stubUpgrader{currentConfig: config.DefaultForAzureSEVSNP()},
yesFlag: true,
kubeUpgrader: &stubKubernetesUpgrader{currentConfig: config.DefaultForAzureSEVSNP()},
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{},
yesFlag: true,
},
"nodeVersion some error": {
upgrader: &stubUpgrader{
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
nodeVersionErr: someErr,
nodeVersionErr: assert.AnError,
},
wantErr: true,
yesFlag: true,
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{},
wantErr: true,
yesFlag: true,
},
"nodeVersion in progress error": {
upgrader: &stubUpgrader{
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
nodeVersionErr: kubernetes.ErrInProgress,
},
yesFlag: true,
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{},
yesFlag: true,
},
"helm other error": {
upgrader: &stubUpgrader{
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
helmErr: someErr,
},
wantErr: true,
yesFlag: true,
helmUpgrader: &stubHelmUpgrader{err: assert.AnError},
terraformUpgrader: &stubTerraformUpgrader{},
wantErr: true,
yesFlag: true,
},
"check terraform error": {
upgrader: &stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
checkTerraformErr: someErr,
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
},
wantErr: true,
yesFlag: true,
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{checkTerraformErr: assert.AnError},
wantErr: true,
yesFlag: true,
},
"abort": {
upgrader: &stubUpgrader{
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
terraformDiff: true,
},
wantErr: true,
stdin: "no\n",
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{terraformDiff: true},
wantErr: true,
stdin: "no\n",
},
"clean terraform error": {
upgrader: &stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
cleanTerraformErr: someErr,
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
},
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{
cleanTerraformErr: assert.AnError,
terraformDiff: true,
},
wantErr: true,
stdin: "no\n",
},
"plan terraform error": {
upgrader: &stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
planTerraformErr: someErr,
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
},
wantErr: true,
yesFlag: true,
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{planTerraformErr: assert.AnError},
wantErr: true,
yesFlag: true,
},
"apply terraform error": {
upgrader: &stubUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
applyTerraformErr: someErr,
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
},
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{
applyTerraformErr: assert.AnError,
terraformDiff: true,
},
wantErr: true,
yesFlag: true,
},
"do no backup join-config when remote attestation config is the same": {
upgrader: &stubUpgrader{
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: fakeAzureAttestationConfigFromCluster(context.Background(), t, cloudprovider.Azure),
},
helmUpgrader: &stubHelmUpgrader{},
terraformUpgrader: &stubTerraformUpgrader{},
yesFlag: true,
dontWantJoinConfigBackup: true,
},
@ -141,7 +159,15 @@ func TestUpgradeApply(t *testing.T) {
require.NoError(handler.WriteJSON(constants.ClusterIDsFilename, clusterid.File{}))
require.NoError(handler.WriteJSON(constants.MasterSecretFilename, uri.MasterSecret{}))
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t), configFetcher: stubAttestationFetcher{}, clusterShower: &stubShowCluster{}, fileHandler: handler}
upgrader := upgradeApplyCmd{
kubeUpgrader: tc.kubeUpgrader,
helmUpgrader: tc.helmUpgrader,
terraformUpgrader: tc.terraformUpgrader,
log: logger.NewTest(t),
configFetcher: stubAttestationFetcher{},
clusterShower: &stubShowCluster{},
fileHandler: handler,
}
err := upgrader.upgradeApply(cmd)
if tc.wantErr {
@ -149,70 +175,79 @@ func TestUpgradeApply(t *testing.T) {
return
}
assert.NoError(err)
assert.Equal(!tc.dontWantJoinConfigBackup, tc.upgrader.backupWasCalled)
assert.Equal(!tc.dontWantJoinConfigBackup, tc.kubeUpgrader.backupWasCalled)
})
}
}
type stubUpgrader struct {
currentConfig config.AttestationCfg
nodeVersionErr error
helmErr error
type stubHelmUpgrader struct {
err error
}
func (u stubHelmUpgrader) Upgrade(
_ context.Context, _ *config.Config, _ clusterid.File, _ time.Duration, _, _ bool, _ string, _ bool,
_ helm.WaitMode, _ uri.MasterSecret, _ string, _ versions.ValidK8sVersion, _ terraform.ApplyOutput,
) error {
return u.err
}
type stubKubernetesUpgrader struct {
backupWasCalled bool
nodeVersionErr error
currentConfig config.AttestationCfg
}
func (u stubKubernetesUpgrader) GetMeasurementSalt(_ context.Context) ([]byte, error) {
return []byte{}, nil
}
func (u *stubKubernetesUpgrader) BackupConfigMap(_ context.Context, _ string) error {
u.backupWasCalled = true
return nil
}
func (u stubKubernetesUpgrader) UpgradeNodeVersion(_ context.Context, _ *config.Config, _ bool) error {
return u.nodeVersionErr
}
func (u stubKubernetesUpgrader) UpdateAttestationConfig(_ context.Context, _ config.AttestationCfg) error {
return nil
}
func (u stubKubernetesUpgrader) GetClusterAttestationConfig(_ context.Context, _ variant.Variant) (config.AttestationCfg, error) {
return u.currentConfig, nil
}
func (u stubKubernetesUpgrader) ExtendClusterConfigCertSANs(_ context.Context, _ []string) error {
return nil
}
type stubTerraformUpgrader struct {
terraformDiff bool
planTerraformErr error
checkTerraformErr error
applyTerraformErr error
cleanTerraformErr error
backupWasCalled bool
}
func (u stubUpgrader) GetMeasurementSalt(_ context.Context) ([]byte, error) {
return []byte{}, nil
}
func (u stubUpgrader) GetUpgradeID() string {
return "test-upgrade"
}
func (u *stubUpgrader) BackupConfigMap(_ context.Context, _ string) error {
u.backupWasCalled = true
return nil
}
func (u stubUpgrader) UpgradeNodeVersion(_ context.Context, _ *config.Config, _ bool) error {
return u.nodeVersionErr
}
func (u stubUpgrader) UpgradeHelmServices(_ context.Context, _ *config.Config, _ clusterid.File, _ time.Duration, _, _, _ bool, _ helm.WaitMode, _ uri.MasterSecret, _ string, _ versions.ValidK8sVersion, _ terraform.ApplyOutput) error {
return u.helmErr
}
func (u stubUpgrader) UpdateAttestationConfig(_ context.Context, _ config.AttestationCfg) error {
return nil
}
func (u stubUpgrader) GetClusterAttestationConfig(_ context.Context, _ variant.Variant) (config.AttestationCfg, error) {
return u.currentConfig, nil
}
func (u stubUpgrader) CheckTerraformMigrations(_ string) error {
func (u stubTerraformUpgrader) CheckTerraformMigrations(_ string) error {
return u.checkTerraformErr
}
func (u stubUpgrader) CleanUpTerraformMigrations(_ string) error {
func (u stubTerraformUpgrader) CleanUpTerraformMigrations(_ string) error {
return u.cleanTerraformErr
}
func (u stubUpgrader) PlanTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (bool, error) {
func (u stubTerraformUpgrader) PlanTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (bool, error) {
return u.terraformDiff, u.planTerraformErr
}
func (u stubUpgrader) ApplyTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (terraform.ApplyOutput, error) {
func (u stubTerraformUpgrader) ApplyTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (terraform.ApplyOutput, error) {
return terraform.ApplyOutput{}, u.applyTerraformErr
}
func (u stubUpgrader) ExtendClusterConfigCertSANs(_ context.Context, _ []string) error {
return nil
func (u stubTerraformUpgrader) UpgradeID() string {
return "test-upgrade"
}
func fakeAzureAttestationConfigFromCluster(ctx context.Context, t *testing.T, provider cloudprovider.Provider) config.AttestationCfg {

View File

@ -12,6 +12,7 @@ import (
"fmt"
"io"
"net/http"
"path/filepath"
"sort"
"strings"
@ -71,11 +72,14 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
}
fileHandler := file.NewHandler(afero.NewOsFs())
checker, err := kubernetes.NewUpgrader(
cmd.Context(), cmd.OutOrStdout(),
constants.UpgradeDir, constants.AdminConfFilename,
fileHandler, log, kubernetes.UpgradeCmdKindCheck,
)
upgradeID := generateUpgradeID(upgradeCmdKindCheck)
tfClient, err := terraform.New(cmd.Context(), filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir))
if err != nil {
return fmt.Errorf("setting up terraform client: %w", err)
}
kubeChecker, err := kubernetes.NewUpgrader(cmd.OutOrStdout(), constants.AdminConfFilename, log)
if err != nil {
return fmt.Errorf("setting up Kubernetes upgrader: %w", err)
}
@ -89,7 +93,7 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
canUpgradeCheck: featureset.CanUpgradeCheck,
collect: &versionCollector{
writer: cmd.OutOrStderr(),
checker: checker,
kubeChecker: kubeChecker,
verListFetcher: versionfetcher,
fileHandler: fileHandler,
client: http.DefaultClient,
@ -99,8 +103,8 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
log: log,
versionsapi: versionfetcher,
},
checker: checker,
log: log,
terraformChecker: upgrade.NewTerraformUpgrader(tfClient, cmd.OutOrStdout(), fileHandler, upgradeID),
log: log,
}
return up.upgradeCheck(cmd, fileHandler, attestationconfigapi.NewFetcher(), flags)
@ -143,10 +147,10 @@ func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) {
}
type upgradeCheckCmd struct {
canUpgradeCheck bool
collect collector
checker upgradeChecker
log debugLog
canUpgradeCheck bool
collect collector
terraformChecker terraformChecker
log debugLog
}
// upgradePlan plans an upgrade of a Constellation cluster.
@ -212,7 +216,7 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand
// u.upgrader.AddManualStateMigration(migration)
// }
if err := u.checker.CheckTerraformMigrations(constants.UpgradeDir); err != nil {
if err := u.terraformChecker.CheckTerraformMigrations(constants.UpgradeDir); err != nil {
return fmt.Errorf("checking workspace: %w", err)
}
@ -233,12 +237,12 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand
cmd.Println("The following Terraform migrations are available with this CLI:")
// Check if there are any Terraform migrations
hasDiff, err := u.checker.PlanTerraformMigrations(cmd.Context(), opts)
hasDiff, err := u.terraformChecker.PlanTerraformMigrations(cmd.Context(), opts)
if err != nil {
return fmt.Errorf("planning terraform migrations: %w", err)
}
defer func() {
if err := u.checker.CleanUpTerraformMigrations(constants.UpgradeDir); err != nil {
if err := u.terraformChecker.CleanUpTerraformMigrations(constants.UpgradeDir); err != nil {
u.log.Debugf("Failed to clean up Terraform migrations: %v", err)
}
}()
@ -323,7 +327,7 @@ type collector interface {
type versionCollector struct {
writer io.Writer
checker upgradeChecker
kubeChecker kubernetesChecker
verListFetcher versionListFetcher
fileHandler file.Handler
client *http.Client
@ -382,12 +386,12 @@ func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionI
return currentVersionInfo{}, fmt.Errorf("getting service versions: %w", err)
}
imageVersion, err := getCurrentImageVersion(ctx, v.checker)
imageVersion, err := getCurrentImageVersion(ctx, v.kubeChecker)
if err != nil {
return currentVersionInfo{}, fmt.Errorf("getting image version: %w", err)
}
k8sVersion, err := getCurrentKubernetesVersion(ctx, v.checker)
k8sVersion, err := getCurrentKubernetesVersion(ctx, v.kubeChecker)
if err != nil {
return currentVersionInfo{}, fmt.Errorf("getting Kubernetes version: %w", err)
}
@ -588,7 +592,7 @@ func (v *versionUpgrade) writeConfig(conf *config.Config, fileHandler file.Handl
// getCurrentImageVersion retrieves the semantic version of the image currently installed in the cluster.
// If the cluster is not using a release image, an error is returned.
func getCurrentImageVersion(ctx context.Context, checker upgradeChecker) (string, error) {
func getCurrentImageVersion(ctx context.Context, checker kubernetesChecker) (string, error) {
imageVersion, err := checker.CurrentImage(ctx)
if err != nil {
return "", err
@ -602,7 +606,7 @@ func getCurrentImageVersion(ctx context.Context, checker upgradeChecker) (string
}
// getCurrentKubernetesVersion retrieves the semantic version of Kubernetes currently installed in the cluster.
func getCurrentKubernetesVersion(ctx context.Context, checker upgradeChecker) (string, error) {
func getCurrentKubernetesVersion(ctx context.Context, checker kubernetesChecker) (string, error) {
k8sVersion, err := checker.CurrentKubernetesVersion(ctx)
if err != nil {
return "", err
@ -745,9 +749,12 @@ type upgradeCheckFlags struct {
terraformLogLevel terraform.LogLevel
}
type upgradeChecker interface {
type kubernetesChecker interface {
CurrentImage(ctx context.Context) (string, error)
CurrentKubernetesVersion(ctx context.Context) (string, error)
}
type terraformChecker interface {
PlanTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (bool, error)
CheckTerraformMigrations(upgradeWorkspace string) error
CleanUpTerraformMigrations(upgradeWorkspace string) error

View File

@ -110,22 +110,22 @@ func TestBuildString(t *testing.T) {
func TestGetCurrentImageVersion(t *testing.T) {
testCases := map[string]struct {
stubUpgradeChecker stubUpgradeChecker
wantErr bool
stubKubernetesChecker stubKubernetesChecker
wantErr bool
}{
"valid version": {
stubUpgradeChecker: stubUpgradeChecker{
stubKubernetesChecker: stubKubernetesChecker{
image: "v1.0.0",
},
},
"invalid version": {
stubUpgradeChecker: stubUpgradeChecker{
stubKubernetesChecker: stubKubernetesChecker{
image: "invalid",
},
wantErr: true,
},
"GetCurrentImage error": {
stubUpgradeChecker: stubUpgradeChecker{
stubKubernetesChecker: stubKubernetesChecker{
err: errors.New("error"),
},
wantErr: true,
@ -136,7 +136,7 @@ func TestGetCurrentImageVersion(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
version, err := getCurrentImageVersion(context.Background(), tc.stubUpgradeChecker)
version, err := getCurrentImageVersion(context.Background(), tc.stubKubernetesChecker)
if tc.wantErr {
assert.Error(err)
return
@ -215,19 +215,19 @@ func TestUpgradeCheck(t *testing.T) {
testCases := map[string]struct {
collector stubVersionCollector
csp cloudprovider.Provider
checker stubUpgradeChecker
checker stubTerraformChecker
cliVersion string
wantError bool
}{
"upgrades gcp": {
collector: collector,
checker: stubUpgradeChecker{},
checker: stubTerraformChecker{},
csp: cloudprovider.GCP,
cliVersion: "v1.0.0",
},
"terraform err": {
collector: collector,
checker: stubUpgradeChecker{
checker: stubTerraformChecker{
err: assert.AnError,
},
csp: cloudprovider.GCP,
@ -245,10 +245,10 @@ func TestUpgradeCheck(t *testing.T) {
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg))
checkCmd := upgradeCheckCmd{
canUpgradeCheck: true,
collect: &tc.collector,
checker: tc.checker,
log: logger.NewTest(t),
canUpgradeCheck: true,
collect: &tc.collector,
terraformChecker: tc.checker,
log: logger.NewTest(t),
}
cmd := newUpgradeCheckCmd()
@ -317,31 +317,35 @@ func (s *stubVersionCollector) filterCompatibleCLIVersions(_ context.Context, _
return s.newCompatibleCLIVersionsList, nil
}
type stubUpgradeChecker struct {
type stubKubernetesChecker struct {
image string
k8sVersion string
tfDiff bool
err error
}
func (u stubUpgradeChecker) CurrentImage(context.Context) (string, error) {
return u.image, u.err
func (s stubKubernetesChecker) CurrentImage(context.Context) (string, error) {
return s.image, s.err
}
func (u stubUpgradeChecker) CurrentKubernetesVersion(context.Context) (string, error) {
return u.k8sVersion, u.err
func (s stubKubernetesChecker) CurrentKubernetesVersion(context.Context) (string, error) {
return s.k8sVersion, s.err
}
func (u stubUpgradeChecker) PlanTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (bool, error) {
return u.tfDiff, u.err
type stubTerraformChecker struct {
tfDiff bool
err error
}
func (u stubUpgradeChecker) CheckTerraformMigrations(_ string) error {
return u.err
func (s stubTerraformChecker) PlanTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (bool, error) {
return s.tfDiff, s.err
}
func (u stubUpgradeChecker) CleanUpTerraformMigrations(_ string) error {
return u.err
func (s stubTerraformChecker) CheckTerraformMigrations(_ string) error {
return s.err
}
func (s stubTerraformChecker) CleanUpTerraformMigrations(_ string) error {
return s.err
}
func TestNewCLIVersions(t *testing.T) {

View File

@ -11,25 +11,17 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/kubernetes",
visibility = ["//cli:__subpackages__"],
deps = [
"//cli/internal/clusterid",
"//cli/internal/helm",
"//cli/internal/terraform",
"//cli/internal/upgrade",
"//internal/api/versionsapi",
"//internal/attestation/variant",
"//internal/cloud/cloudprovider",
"//internal/compatibility",
"//internal/config",
"//internal/constants",
"//internal/file",
"//internal/imagefetcher",
"//internal/kms/uri",
"//internal/kubernetes",
"//internal/kubernetes/kubectl",
"//internal/versions",
"//internal/versions/components",
"//operators/constellation-node-operator/api/v1alpha1",
"@com_github_google_uuid//:uuid",
"@io_k8s_api//core/v1:core",
"@io_k8s_apimachinery//pkg/api/errors",
"@io_k8s_apimachinery//pkg/apis/meta/v1:meta",

View File

@ -12,30 +12,20 @@ import (
"errors"
"fmt"
"io"
"path/filepath"
"sort"
"strings"
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/cli/internal/upgrade"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl"
"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"
@ -49,16 +39,6 @@ import (
"sigs.k8s.io/yaml"
)
// 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")
@ -91,69 +71,36 @@ func (e *applyError) Error() string {
type Upgrader struct {
stableInterface StableInterface
dynamicInterface DynamicInterface
helmClient helmInterface
imageFetcher imageFetcher
outWriter io.Writer
tfUpgrader *upgrade.TerraformUpgrader
log debugLog
upgradeID string
}
// NewUpgrader returns a new Upgrader.
func NewUpgrader(
ctx context.Context, outWriter io.Writer, upgradeWorkspace, kubeConfigPath string,
fileHandler file.Handler, 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,
}
func NewUpgrader(outWriter io.Writer, kubeConfigPath string, log debugLog) (*Upgrader, error) {
kubeClient, err := newClient(kubeConfigPath)
if err != nil {
return nil, err
}
u.stableInterface = &stableClient{client: kubeClient}
// use unstructured client to avoid importing the operator packages
kubeConfig, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
if err != nil {
return nil, fmt.Errorf("building kubernetes config: %w", err)
}
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.NewUpgradeClient(kubectl.New(), upgradeWorkspace, kubeConfigPath, 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(upgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir))
if err != nil {
return nil, fmt.Errorf("setting up terraform client: %w", err)
}
tfUpgrader, err := upgrade.NewTerraformUpgrader(tfClient, outWriter, fileHandler)
if err != nil {
return nil, fmt.Errorf("setting up terraform upgrader: %w", err)
}
u.tfUpgrader = tfUpgrader
return u, nil
return &Upgrader{
imageFetcher: imagefetcher.New(),
outWriter: outWriter,
log: log,
stableInterface: &stableClient{client: kubeClient},
dynamicInterface: &NodeVersionClient{client: unstructuredClient},
}, nil
}
// GetMeasurementSalt returns the measurementSalt from the join-config.
@ -169,47 +116,6 @@ func (u *Upgrader) GetMeasurementSalt(ctx context.Context) ([]byte, error) {
return salt, nil
}
// GetUpgradeID returns the upgrade ID.
func (u *Upgrader) GetUpgradeID() string {
return u.upgradeID
}
// 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(upgradeWorkspace string) error {
return u.tfUpgrader.CheckTerraformMigrations(upgradeWorkspace, u.upgradeID, constants.TerraformUpgradeBackupDir)
}
// CleanUpTerraformMigrations cleans up the Terraform migration workspace, for example when an upgrade is
// aborted by the user.
func (u *Upgrader) CleanUpTerraformMigrations(upgradeWorkspace string) error {
return u.tfUpgrader.CleanUpTerraformMigrations(upgradeWorkspace, 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, u.upgradeID)
}
// ApplyTerraformMigrations applies the migrations planned by PlanTerraformMigrations.
// If PlanTerraformMigrations has not been executed before, it will return an error.
// 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, opts upgrade.TerraformUpgradeOptions) (terraform.ApplyOutput, error) {
return u.tfUpgrader.ApplyTerraformMigrations(ctx, opts, u.upgradeID)
}
// UpgradeHelmServices upgrade helm services.
func (u *Upgrader) UpgradeHelmServices(ctx context.Context, config *config.Config, idFile clusterid.File, timeout time.Duration,
allowDestructive bool, force bool, conformance bool, helmWaitMode helm.WaitMode, masterSecret uri.MasterSecret, serviceAccURI string,
validK8sVersion versions.ValidK8sVersion, output terraform.ApplyOutput,
) error {
return u.helmClient.Upgrade(ctx, config, idFile, timeout, allowDestructive, force, u.upgradeID, conformance,
helmWaitMode, masterSecret, serviceAccURI, validK8sVersion, output)
}
// UpgradeNodeVersion upgrades the cluster's NodeVersion object and in turn triggers image & k8s version upgrades.
// The versions set in the config are validated against the versions running in the cluster.
func (u *Upgrader) UpgradeNodeVersion(ctx context.Context, conf *config.Config, force bool) error {
@ -569,10 +475,6 @@ func upgradeInProgress(nodeVersion updatev1alpha1.NodeVersion) bool {
return false
}
type helmInterface interface {
Upgrade(ctx context.Context, config *config.Config, idFile clusterid.File, timeout time.Duration, allowDestructive, force bool, upgradeID string, conformance bool, helmWaitMode helm.WaitMode, masterSecret uri.MasterSecret, serviceAccURI string, validK8sVersion versions.ValidK8sVersion, output terraform.ApplyOutput) error
}
type debugLog interface {
Debugf(format string, args ...any)
Sync()

View File

@ -21,24 +21,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
)
// NewTerraformUpgrader returns a new TerraformUpgrader.
func NewTerraformUpgrader(tfClient tfResourceClient, outWriter io.Writer, fileHandler file.Handler) (*TerraformUpgrader, error) {
return &TerraformUpgrader{
tf: tfClient,
policyPatcher: cloudcmd.NewAzurePolicyPatcher(),
outWriter: outWriter,
fileHandler: fileHandler,
}, nil
}
// TerraformUpgrader is responsible for performing Terraform migrations on cluster upgrades.
type TerraformUpgrader struct {
tf tfResourceClient
policyPatcher policyPatcher
outWriter io.Writer
fileHandler file.Handler
}
// TerraformUpgradeOptions are the options used for the Terraform upgrade.
type TerraformUpgradeOptions struct {
// LogLevel is the log level used for Terraform.
@ -51,55 +33,43 @@ type TerraformUpgradeOptions struct {
UpgradeWorkspace string
}
// CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace.
func checkTerraformMigrations(file file.Handler, upgradeWorkspace, upgradeID, upgradeSubDir string) error {
var existingFiles []string
filesToCheck := []string{
filepath.Join(upgradeWorkspace, upgradeID, upgradeSubDir),
}
// TerraformUpgrader is responsible for performing Terraform migrations on cluster upgrades.
type TerraformUpgrader struct {
tf tfResourceClient
policyPatcher policyPatcher
outWriter io.Writer
fileHandler file.Handler
upgradeID string
}
for _, f := range filesToCheck {
if err := checkFileExists(file, &existingFiles, f); err != nil {
return fmt.Errorf("checking terraform migrations: %w", err)
}
// NewTerraformUpgrader returns a new TerraformUpgrader.
func NewTerraformUpgrader(tfClient tfResourceClient, outWriter io.Writer, fileHandler file.Handler, upgradeID string,
) *TerraformUpgrader {
return &TerraformUpgrader{
tf: tfClient,
policyPatcher: cloudcmd.NewAzurePolicyPatcher(),
outWriter: outWriter,
fileHandler: fileHandler,
upgradeID: upgradeID,
}
if len(existingFiles) > 0 {
return fmt.Errorf("file(s) %s already exist", strings.Join(existingFiles, ", "))
}
return 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 *TerraformUpgrader) CheckTerraformMigrations(upgradeWorkspace, upgradeID, upgradeSubDir string) error {
return checkTerraformMigrations(u.fileHandler, upgradeWorkspace, upgradeID, upgradeSubDir)
}
// checkFileExists checks whether a file exists and adds it to the existingFiles slice if it does.
func checkFileExists(fileHandler file.Handler, existingFiles *[]string, filename string) error {
_, err := fileHandler.Stat(filename)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("checking %s: %w", filename, err)
}
return nil
}
*existingFiles = append(*existingFiles, filename)
return nil
func (u *TerraformUpgrader) CheckTerraformMigrations(upgradeWorkspace string) error {
return checkTerraformMigrations(u.fileHandler, upgradeWorkspace, u.upgradeID, constants.TerraformUpgradeBackupDir)
}
// 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 *TerraformUpgrader) PlanTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions, upgradeID string) (bool, error) {
func (u *TerraformUpgrader) PlanTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions) (bool, error) {
// Prepare the new Terraform workspace and backup the old one
err := u.tf.PrepareUpgradeWorkspace(
filepath.Join("terraform", strings.ToLower(opts.CSP.String())),
opts.TFWorkspace,
filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir),
filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeBackupDir),
filepath.Join(opts.UpgradeWorkspace, u.upgradeID, constants.TerraformUpgradeWorkingDir),
filepath.Join(opts.UpgradeWorkspace, u.upgradeID, constants.TerraformUpgradeBackupDir),
opts.Vars,
)
if err != nil {
@ -122,24 +92,15 @@ func (u *TerraformUpgrader) PlanTerraformMigrations(ctx context.Context, opts Te
// CleanUpTerraformMigrations cleans up the Terraform migration workspace, for example when an upgrade is
// aborted by the user.
func (u *TerraformUpgrader) CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string) error {
return CleanUpTerraformMigrations(upgradeWorkspace, upgradeID, u.fileHandler)
}
// CleanUpTerraformMigrations cleans up the Terraform upgrade directory.
func CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string, fileHandler file.Handler) error {
upgradeDir := filepath.Join(upgradeWorkspace, upgradeID)
if err := fileHandler.RemoveAll(upgradeDir); err != nil {
return fmt.Errorf("cleaning up file %s: %w", upgradeDir, err)
}
return nil
func (u *TerraformUpgrader) CleanUpTerraformMigrations(upgradeWorkspace string) error {
return CleanUpTerraformMigrations(upgradeWorkspace, u.upgradeID, u.fileHandler)
}
// ApplyTerraformMigrations applies the migrations planned by PlanTerraformMigrations.
// If PlanTerraformMigrations has not been executed before, it will return an error.
// 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 *TerraformUpgrader) ApplyTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions, upgradeID string) (terraform.ApplyOutput, error) {
func (u *TerraformUpgrader) ApplyTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions) (terraform.ApplyOutput, error) {
tfOutput, err := u.tf.CreateCluster(ctx, opts.CSP, opts.LogLevel)
if err != nil {
return tfOutput, fmt.Errorf("terraform apply: %w", err)
@ -154,17 +115,64 @@ func (u *TerraformUpgrader) ApplyTerraformMigrations(ctx context.Context, opts T
}
if err := u.fileHandler.CopyDir(
filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir),
filepath.Join(opts.UpgradeWorkspace, u.upgradeID, constants.TerraformUpgradeWorkingDir),
opts.TFWorkspace,
); err != nil {
return tfOutput, fmt.Errorf("replacing old terraform directory with new one: %w", err)
}
if err := u.fileHandler.RemoveAll(filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir)); err != nil {
if err := u.fileHandler.RemoveAll(filepath.Join(opts.UpgradeWorkspace, u.upgradeID, constants.TerraformUpgradeWorkingDir)); err != nil {
return tfOutput, fmt.Errorf("removing terraform upgrade directory: %w", err)
}
return tfOutput, nil
}
// UpgradeID returns the ID of the upgrade.
func (u *TerraformUpgrader) UpgradeID() string {
return u.upgradeID
}
// CleanUpTerraformMigrations cleans up the Terraform upgrade directory.
func CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string, fileHandler file.Handler) error {
upgradeDir := filepath.Join(upgradeWorkspace, upgradeID)
if err := fileHandler.RemoveAll(upgradeDir); err != nil {
return fmt.Errorf("cleaning up file %s: %w", upgradeDir, err)
}
return nil
}
// CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace.
func checkTerraformMigrations(file file.Handler, upgradeWorkspace, upgradeID, upgradeSubDir string) error {
var existingFiles []string
filesToCheck := []string{
filepath.Join(upgradeWorkspace, upgradeID, upgradeSubDir),
}
for _, f := range filesToCheck {
if err := checkFileExists(file, &existingFiles, f); err != nil {
return fmt.Errorf("checking terraform migrations: %w", err)
}
}
if len(existingFiles) > 0 {
return fmt.Errorf("file(s) %s already exist", strings.Join(existingFiles, ", "))
}
return nil
}
// checkFileExists checks whether a file exists and adds it to the existingFiles slice if it does.
func checkFileExists(fileHandler file.Handler, existingFiles *[]string, filename string) error {
_, err := fileHandler.Stat(filename)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("checking %s: %w", filename, err)
}
return nil
}
*existingFiles = append(*existingFiles, filename)
return nil
}
type tfClientCommon interface {
ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error
Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error)

View File

@ -23,13 +23,6 @@ import (
)
func TestCheckTerraformMigrations(t *testing.T) {
upgrader := func(fileHandler file.Handler) *TerraformUpgrader {
u, err := NewTerraformUpgrader(&stubTerraformClient{}, bytes.NewBuffer(nil), fileHandler)
require.NoError(t, err)
return u
}
workspace := func(existingFiles []string) file.Handler {
fs := afero.NewMemMapFs()
for _, f := range existingFiles {
@ -57,8 +50,9 @@ func TestCheckTerraformMigrations(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
u := upgrader(tc.workspace)
err := u.CheckTerraformMigrations(constants.UpgradeDir, tc.upgradeID, constants.TerraformUpgradeBackupDir)
u := NewTerraformUpgrader(&stubTerraformClient{}, bytes.NewBuffer(nil), tc.workspace, tc.upgradeID)
err := u.CheckTerraformMigrations(constants.UpgradeDir)
if tc.wantErr {
require.Error(t, err)
return
@ -70,12 +64,6 @@ func TestCheckTerraformMigrations(t *testing.T) {
}
func TestPlanTerraformMigrations(t *testing.T) {
upgrader := func(tf tfResourceClient, fileHandler file.Handler) *TerraformUpgrader {
u, err := NewTerraformUpgrader(tf, bytes.NewBuffer(nil), fileHandler)
require.NoError(t, err)
return u
}
workspace := func(existingFiles []string) file.Handler {
fs := afero.NewMemMapFs()
for _, f := range existingFiles {
@ -142,7 +130,7 @@ func TestPlanTerraformMigrations(t *testing.T) {
t.Run(name, func(t *testing.T) {
require := require.New(t)
u := upgrader(tc.tf, tc.workspace)
u := NewTerraformUpgrader(tc.tf, bytes.NewBuffer(nil), tc.workspace, tc.upgradeID)
opts := TerraformUpgradeOptions{
LogLevel: terraform.LogLevelDebug,
@ -150,7 +138,7 @@ func TestPlanTerraformMigrations(t *testing.T) {
Vars: &terraform.QEMUVariables{},
}
diff, err := u.PlanTerraformMigrations(context.Background(), opts, tc.upgradeID)
diff, err := u.PlanTerraformMigrations(context.Background(), opts)
if tc.wantErr {
require.Error(err)
} else {
@ -162,13 +150,6 @@ func TestPlanTerraformMigrations(t *testing.T) {
}
func TestApplyTerraformMigrations(t *testing.T) {
upgrader := func(tf tfResourceClient, fileHandler file.Handler) *TerraformUpgrader {
u, err := NewTerraformUpgrader(tf, bytes.NewBuffer(nil), fileHandler)
require.NoError(t, err)
return u
}
fileHandler := func(upgradeID string, existingFiles ...string) file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())
@ -211,7 +192,7 @@ func TestApplyTerraformMigrations(t *testing.T) {
t.Run(name, func(t *testing.T) {
require := require.New(t)
u := upgrader(tc.tf, tc.fs)
u := NewTerraformUpgrader(tc.tf, bytes.NewBuffer(nil), tc.fs, tc.upgradeID)
opts := TerraformUpgradeOptions{
LogLevel: terraform.LogLevelDebug,
@ -221,7 +202,7 @@ func TestApplyTerraformMigrations(t *testing.T) {
UpgradeWorkspace: constants.UpgradeDir,
}
_, err := u.ApplyTerraformMigrations(context.Background(), opts, tc.upgradeID)
_, err := u.ApplyTerraformMigrations(context.Background(), opts)
if tc.wantErr {
require.Error(err)
} else {
@ -232,13 +213,6 @@ func TestApplyTerraformMigrations(t *testing.T) {
}
func TestCleanUpTerraformMigrations(t *testing.T) {
upgrader := func(fileHandler file.Handler) *TerraformUpgrader {
u, err := NewTerraformUpgrader(&stubTerraformClient{}, bytes.NewBuffer(nil), fileHandler)
require.NoError(t, err)
return u
}
workspace := func(existingFiles []string) file.Handler {
fs := afero.NewMemMapFs()
for _, f := range existingFiles {
@ -299,9 +273,9 @@ func TestCleanUpTerraformMigrations(t *testing.T) {
require := require.New(t)
workspace := workspace(tc.workspaceFiles)
u := upgrader(workspace)
u := NewTerraformUpgrader(&stubTerraformClient{}, bytes.NewBuffer(nil), workspace, tc.upgradeID)
err := u.CleanUpTerraformMigrations(constants.UpgradeDir, tc.upgradeID)
err := u.CleanUpTerraformMigrations(constants.UpgradeDir)
if tc.wantErr {
require.Error(err)
return