Skip to content

Commit

Permalink
cli: remove/refactor upgrade package (#2266)
Browse files Browse the repository at this point in the history
* Move IAM migration client to cloudcmd package

* Move Terraform Cluster upgrade client to cloudcmd package

* Use hcl for creating Terraform IAM variables files

* Unify terraform upgrade code

* Rename some cloudcmd files for better clarity

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
  • Loading branch information
daniel-weisse authored Aug 23, 2023
1 parent 3d5d291 commit 0a91180
Show file tree
Hide file tree
Showing 29 changed files with 1,197 additions and 1,194 deletions.
12 changes: 11 additions & 1 deletion cli/internal/cloudcmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ go_library(
srcs = [
"clients.go",
"cloudcmd.go",
"clusterupgrade.go",
"create.go",
"iam.go",
"iamupgrade.go",
"patch.go",
"rollback.go",
"serviceaccount.go",
"terminate.go",
"terraform.go",
"tfupgrade.go",
"tfvars.go",
"validators.go",
],
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd",
Expand All @@ -31,6 +34,7 @@ go_library(
"//internal/cloud/gcpshared",
"//internal/cloud/openstack",
"//internal/config",
"//internal/constants",
"//internal/file",
"//internal/imagefetcher",
"//internal/role",
Expand All @@ -45,11 +49,14 @@ go_test(
name = "cloudcmd_test",
srcs = [
"clients_test.go",
"clusterupgrade_test.go",
"create_test.go",
"iam_test.go",
"iamupgrade_test.go",
"patch_test.go",
"rollback_test.go",
"terminate_test.go",
"tfupgrade_test.go",
"validators_test.go",
],
embed = [":cloudcmd"],
Expand All @@ -60,6 +67,9 @@ go_test(
"//internal/cloud/cloudprovider",
"//internal/cloud/gcpshared",
"//internal/config",
"//internal/constants",
"//internal/file",
"@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",
Expand Down
16 changes: 16 additions & 0 deletions cli/internal/cloudcmd/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ type tfIAMClient interface {
ShowIAM(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error)
}

type tfUpgradePlanner interface {
ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error
Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error)
PrepareUpgradeWorkspace(embeddedPath, oldWorkingDir, backupDir string, vars terraform.Variables) error
}

type tfIAMUpgradeClient interface {
tfUpgradePlanner
ApplyIAM(ctx context.Context, csp cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.IAMOutput, error)
}

type tfClusterUpgradeClient interface {
tfUpgradePlanner
ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.ApplyOutput, error)
}

type libvirtRunner interface {
Start(ctx context.Context, containerName, imageName string) error
Stop(ctx context.Context) error
Expand Down
87 changes: 87 additions & 0 deletions cli/internal/cloudcmd/clusterupgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/

package cloudcmd

import (
"context"
"fmt"
"io"
"path/filepath"
"strings"

"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
)

// ClusterUpgrader is responsible for performing Terraform migrations on cluster upgrades.
type ClusterUpgrader struct {
tf tfClusterUpgradeClient
policyPatcher policyPatcher
fileHandler file.Handler
existingWorkspace string
upgradeWorkspace string
logLevel terraform.LogLevel
}

// NewClusterUpgrader initializes and returns a new ClusterUpgrader.
// existingWorkspace is the directory holding the existing Terraform resources.
// upgradeWorkspace is the directory to use for holding temporary files and resources required to apply the upgrade.
func NewClusterUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string,
logLevel terraform.LogLevel, fileHandler file.Handler,
) (*ClusterUpgrader, error) {
tfClient, err := terraform.New(ctx, filepath.Join(upgradeWorkspace, constants.TerraformUpgradeWorkingDir))
if err != nil {
return nil, fmt.Errorf("setting up terraform client: %w", err)
}

return &ClusterUpgrader{
tf: tfClient,
policyPatcher: NewAzurePolicyPatcher(),
fileHandler: fileHandler,
existingWorkspace: existingWorkspace,
upgradeWorkspace: upgradeWorkspace,
logLevel: logLevel,
}, nil
}

// PlanClusterUpgrade prepares the upgrade workspace and plans the possible Terraform migrations for Constellation's cluster resources (Loadbalancers, VMs, networks etc.).
// In case of possible migrations, the diff is written to outWriter and this function returns true.
func (u *ClusterUpgrader) PlanClusterUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider,
) (bool, error) {
return planUpgrade(
ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars,
filepath.Join("terraform", strings.ToLower(csp.String())),
u.existingWorkspace,
filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir),
)
}

// ApplyClusterUpgrade applies the Terraform migrations planned by PlanClusterUpgrade.
// On success, the workspace of the Upgrader replaces the existing Terraform workspace.
func (u *ClusterUpgrader) ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error) {
tfOutput, err := u.tf.ApplyCluster(ctx, csp, u.logLevel)
if err != nil {
return tfOutput, fmt.Errorf("terraform apply: %w", err)
}
if tfOutput.Azure != nil {
if err := u.policyPatcher.Patch(ctx, tfOutput.Azure.AttestationURL); err != nil {
return tfOutput, fmt.Errorf("patching policies: %w", err)
}
}

if err := moveUpgradeToCurrent(
u.fileHandler,
u.existingWorkspace,
filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeWorkingDir),
); err != nil {
return tfOutput, fmt.Errorf("promoting upgrade workspace to current workspace: %w", err)
}

return tfOutput, nil
}
203 changes: 203 additions & 0 deletions cli/internal/cloudcmd/clusterupgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/

package cloudcmd

import (
"context"
"io"
"path/filepath"
"testing"

"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPlanClusterUpgrade(t *testing.T) {
setUpFilesystem := func(existingFiles []string) file.Handler {
fs := afero.NewMemMapFs()
for _, f := range existingFiles {
require.NoError(t, afero.WriteFile(fs, f, []byte{}, 0o644))
}

return file.NewHandler(fs)
}

testCases := map[string]struct {
upgradeID string
tf *tfClusterUpgradeStub
fs file.Handler
want bool
wantErr bool
}{
"success no diff": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{},
fs: setUpFilesystem([]string{}),
},
"success diff": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{
planDiff: true,
},
fs: setUpFilesystem([]string{}),
want: true,
},
"prepare workspace error": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{
prepareWorkspaceErr: assert.AnError,
},
fs: setUpFilesystem([]string{}),
wantErr: true,
},
"plan error": {
tf: &tfClusterUpgradeStub{
planErr: assert.AnError,
},
fs: setUpFilesystem([]string{}),
wantErr: true,
},
"show plan error no diff": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{
showErr: assert.AnError,
},
fs: setUpFilesystem([]string{}),
},
"show plan error diff": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{
showErr: assert.AnError,
planDiff: true,
},
fs: setUpFilesystem([]string{}),
wantErr: true,
},
"workspace not clean": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{},
fs: setUpFilesystem([]string{filepath.Join(constants.UpgradeDir, "1234", constants.TerraformUpgradeBackupDir)}),
wantErr: true,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
require := require.New(t)

u := &ClusterUpgrader{
tf: tc.tf,
policyPatcher: stubPolicyPatcher{},
fileHandler: tc.fs,
upgradeWorkspace: filepath.Join(constants.UpgradeDir, tc.upgradeID),
existingWorkspace: "test",
logLevel: terraform.LogLevelDebug,
}

diff, err := u.PlanClusterUpgrade(context.Background(), io.Discard, &terraform.QEMUVariables{}, cloudprovider.Unknown)
if tc.wantErr {
require.Error(err)
} else {
require.NoError(err)
require.Equal(tc.want, diff)
}
})
}
}

func TestApplyClusterUpgrade(t *testing.T) {
setUpFilesystem := func(upgradeID string, existingFiles ...string) file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())

require.NoError(t,
fh.Write(
filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir, "someFile"),
[]byte("some content"),
))
for _, f := range existingFiles {
require.NoError(t, fh.Write(f, []byte("some content")))
}
return fh
}

testCases := map[string]struct {
upgradeID string
tf *tfClusterUpgradeStub
policyPatcher stubPolicyPatcher
fs file.Handler
wantErr bool
}{
"success": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{},
fs: setUpFilesystem("1234"),
policyPatcher: stubPolicyPatcher{},
},
"apply error": {
upgradeID: "1234",
tf: &tfClusterUpgradeStub{
applyErr: assert.AnError,
},
fs: setUpFilesystem("1234"),
policyPatcher: stubPolicyPatcher{},
wantErr: true,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := require.New(t)

tc.tf.file = tc.fs
u := &ClusterUpgrader{
tf: tc.tf,
policyPatcher: stubPolicyPatcher{},
fileHandler: tc.fs,
upgradeWorkspace: filepath.Join(constants.UpgradeDir, tc.upgradeID),
existingWorkspace: "test",
logLevel: terraform.LogLevelDebug,
}

_, err := u.ApplyClusterUpgrade(context.Background(), cloudprovider.Unknown)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
})
}
}

type tfClusterUpgradeStub struct {
file file.Handler
applyErr error
planErr error
planDiff bool
showErr error
prepareWorkspaceErr error
}

func (t *tfClusterUpgradeStub) Plan(_ context.Context, _ terraform.LogLevel) (bool, error) {
return t.planDiff, t.planErr
}

func (t *tfClusterUpgradeStub) ShowPlan(_ context.Context, _ terraform.LogLevel, _ io.Writer) error {
return t.showErr
}

func (t *tfClusterUpgradeStub) ApplyCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (terraform.ApplyOutput, error) {
return terraform.ApplyOutput{}, t.applyErr
}

func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _, _ string, _ terraform.Variables) error {
return t.prepareWorkspaceErr
}
Loading

0 comments on commit 0a91180

Please sign in to comment.