mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-06 08:15:48 -04:00
cli: unify cloudcmd
create and upgrade code (#2513)
* Unify cloudcmd create and upgrade code * Make libvirt runner code a bit more idempotent --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
e8cf0f59bd
commit
625dc26644
33 changed files with 1041 additions and 1120 deletions
371
cli/internal/cloudcmd/apply_test.go
Normal file
371
cli/internal/cloudcmd/apply_test.go
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package cloudcmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||
)
|
||||
|
||||
func TestApplier(t *testing.T) {
|
||||
t.Setenv("CONSTELLATION_OPENSTACK_DEV", "1")
|
||||
failOnNonAMD64 := (runtime.GOARCH != "amd64") || (runtime.GOOS != "linux")
|
||||
ip := "192.0.2.1"
|
||||
configWithProvider := func(provider cloudprovider.Provider) *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.RemoveProviderAndAttestationExcept(provider)
|
||||
return cfg
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
tfClient tfResourceClient
|
||||
newTfClientErr error
|
||||
libvirt *stubLibvirtRunner
|
||||
provider cloudprovider.Provider
|
||||
config *config.Config
|
||||
policyPatcher *stubPolicyPatcher
|
||||
wantErr bool
|
||||
wantRollback bool // Use only together with stubClients.
|
||||
wantTerraformRollback bool // When libvirt fails, don't call into Terraform.
|
||||
}{
|
||||
"gcp": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
provider: cloudprovider.GCP,
|
||||
config: configWithProvider(cloudprovider.GCP),
|
||||
},
|
||||
"gcp create cluster error": {
|
||||
tfClient: &stubTerraformClient{applyClusterErr: assert.AnError},
|
||||
provider: cloudprovider.GCP,
|
||||
config: configWithProvider(cloudprovider.GCP),
|
||||
wantErr: true,
|
||||
wantRollback: true,
|
||||
wantTerraformRollback: true,
|
||||
},
|
||||
"azure": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
provider: cloudprovider.Azure,
|
||||
config: configWithProvider(cloudprovider.Azure),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
},
|
||||
"azure trusted launch": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
provider: cloudprovider.Azure,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.RemoveProviderAndAttestationExcept(cloudprovider.Azure)
|
||||
cfg.Attestation = config.AttestationConfig{
|
||||
AzureTrustedLaunch: &config.AzureTrustedLaunch{},
|
||||
}
|
||||
return cfg
|
||||
}(),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
},
|
||||
"azure new policy patch error": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
provider: cloudprovider.Azure,
|
||||
config: configWithProvider(cloudprovider.Azure),
|
||||
policyPatcher: &stubPolicyPatcher{assert.AnError},
|
||||
wantErr: true,
|
||||
},
|
||||
"azure create cluster error": {
|
||||
tfClient: &stubTerraformClient{applyClusterErr: assert.AnError},
|
||||
provider: cloudprovider.Azure,
|
||||
config: configWithProvider(cloudprovider.Azure),
|
||||
policyPatcher: &stubPolicyPatcher{},
|
||||
wantErr: true,
|
||||
wantRollback: true,
|
||||
wantTerraformRollback: true,
|
||||
},
|
||||
"openstack": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
libvirt: &stubLibvirtRunner{},
|
||||
provider: cloudprovider.OpenStack,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.RemoveProviderAndAttestationExcept(cloudprovider.OpenStack)
|
||||
cfg.Provider.OpenStack.Cloud = "testcloud"
|
||||
return cfg
|
||||
}(),
|
||||
},
|
||||
"openstack without clouds.yaml": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
libvirt: &stubLibvirtRunner{},
|
||||
provider: cloudprovider.OpenStack,
|
||||
config: configWithProvider(cloudprovider.OpenStack),
|
||||
wantErr: true,
|
||||
},
|
||||
"openstack create cluster error": {
|
||||
tfClient: &stubTerraformClient{applyClusterErr: assert.AnError},
|
||||
libvirt: &stubLibvirtRunner{},
|
||||
provider: cloudprovider.OpenStack,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.RemoveProviderAndAttestationExcept(cloudprovider.OpenStack)
|
||||
cfg.Provider.OpenStack.Cloud = "testcloud"
|
||||
return cfg
|
||||
}(),
|
||||
wantErr: true,
|
||||
wantRollback: true,
|
||||
wantTerraformRollback: true,
|
||||
},
|
||||
"qemu": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
libvirt: &stubLibvirtRunner{},
|
||||
provider: cloudprovider.QEMU,
|
||||
config: configWithProvider(cloudprovider.QEMU),
|
||||
wantErr: failOnNonAMD64,
|
||||
},
|
||||
"qemu create cluster error": {
|
||||
tfClient: &stubTerraformClient{applyClusterErr: assert.AnError},
|
||||
libvirt: &stubLibvirtRunner{},
|
||||
provider: cloudprovider.QEMU,
|
||||
config: configWithProvider(cloudprovider.QEMU),
|
||||
wantErr: true,
|
||||
wantRollback: !failOnNonAMD64, // if we run on non-AMD64/linux, we don't get to a point where rollback is needed
|
||||
wantTerraformRollback: true,
|
||||
},
|
||||
"qemu start libvirt error": {
|
||||
tfClient: &stubTerraformClient{ip: ip},
|
||||
libvirt: &stubLibvirtRunner{startErr: assert.AnError},
|
||||
provider: cloudprovider.QEMU,
|
||||
config: configWithProvider(cloudprovider.QEMU),
|
||||
wantRollback: !failOnNonAMD64,
|
||||
wantTerraformRollback: false,
|
||||
wantErr: true,
|
||||
},
|
||||
"unknown provider": {
|
||||
tfClient: &stubTerraformClient{},
|
||||
provider: cloudprovider.Unknown,
|
||||
config: func() *config.Config {
|
||||
cfg := config.Default()
|
||||
cfg.RemoveProviderAndAttestationExcept(cloudprovider.AWS)
|
||||
cfg.Provider.AWS = nil
|
||||
return cfg
|
||||
}(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
applier := &Applier{
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
imageFetcher: &stubImageFetcher{
|
||||
reference: "some-image",
|
||||
},
|
||||
terraformClient: tc.tfClient,
|
||||
libvirtRunner: tc.libvirt,
|
||||
rawDownloader: &stubRawDownloader{
|
||||
destination: "some-destination",
|
||||
},
|
||||
policyPatcher: tc.policyPatcher,
|
||||
logLevel: terraform.LogLevelNone,
|
||||
workingDir: "test",
|
||||
backupDir: "test-backup",
|
||||
out: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
diff, err := applier.Plan(context.Background(), tc.config)
|
||||
if err != nil {
|
||||
assert.True(tc.wantErr, "unexpected error: %s", err)
|
||||
return
|
||||
}
|
||||
assert.False(diff)
|
||||
|
||||
idFile, err := applier.Apply(context.Background(), tc.provider, true)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
if tc.wantRollback {
|
||||
cl := tc.tfClient.(*stubTerraformClient)
|
||||
if tc.wantTerraformRollback {
|
||||
assert.True(cl.destroyCalled)
|
||||
}
|
||||
assert.True(cl.cleanUpWorkspaceCalled)
|
||||
if tc.provider == cloudprovider.QEMU {
|
||||
assert.True(tc.libvirt.stopCalled)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(ip, idFile.ClusterEndpoint)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlan(t *testing.T) {
|
||||
setUpFilesystem := func(existingFiles []string) file.Handler {
|
||||
fs := file.NewHandler(afero.NewMemMapFs())
|
||||
require.NoError(t, fs.Write("test/terraform.tfstate", []byte{}, file.OptMkdirAll))
|
||||
for _, f := range existingFiles {
|
||||
require.NoError(t, fs.Write(f, []byte{}))
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
upgradeID string
|
||||
tf *stubTerraformClient
|
||||
fs file.Handler
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
"success no diff": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{},
|
||||
fs: setUpFilesystem([]string{}),
|
||||
},
|
||||
"success diff": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{
|
||||
planDiff: true,
|
||||
},
|
||||
fs: setUpFilesystem([]string{}),
|
||||
want: true,
|
||||
},
|
||||
"prepare workspace error": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{
|
||||
prepareWorkspaceErr: assert.AnError,
|
||||
},
|
||||
fs: setUpFilesystem([]string{}),
|
||||
wantErr: true,
|
||||
},
|
||||
"plan error": {
|
||||
tf: &stubTerraformClient{
|
||||
planErr: assert.AnError,
|
||||
},
|
||||
fs: setUpFilesystem([]string{}),
|
||||
wantErr: true,
|
||||
},
|
||||
"show plan error no diff": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{
|
||||
showPlanErr: assert.AnError,
|
||||
},
|
||||
fs: setUpFilesystem([]string{}),
|
||||
},
|
||||
"show plan error diff": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{
|
||||
showPlanErr: assert.AnError,
|
||||
planDiff: true,
|
||||
},
|
||||
fs: setUpFilesystem([]string{}),
|
||||
wantErr: true,
|
||||
},
|
||||
"workspace not clean": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{},
|
||||
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 := &Applier{
|
||||
terraformClient: tc.tf,
|
||||
policyPatcher: stubPolicyPatcher{},
|
||||
fileHandler: tc.fs,
|
||||
imageFetcher: &stubImageFetcher{reference: "some-image"},
|
||||
rawDownloader: &stubRawDownloader{destination: "some-destination"},
|
||||
libvirtRunner: &stubLibvirtRunner{},
|
||||
logLevel: terraform.LogLevelDebug,
|
||||
backupDir: filepath.Join(constants.UpgradeDir, tc.upgradeID),
|
||||
workingDir: "test",
|
||||
out: io.Discard,
|
||||
}
|
||||
|
||||
cfg := config.Default()
|
||||
cfg.RemoveProviderAndAttestationExcept(cloudprovider.QEMU)
|
||||
|
||||
diff, err := u.Plan(context.Background(), cfg)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
} else {
|
||||
require.NoError(err)
|
||||
require.Equal(tc.want, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
upgradeID string
|
||||
tf *stubTerraformClient
|
||||
policyPatcher stubPolicyPatcher
|
||||
fs file.Handler
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{},
|
||||
policyPatcher: stubPolicyPatcher{},
|
||||
},
|
||||
"apply error": {
|
||||
upgradeID: "1234",
|
||||
tf: &stubTerraformClient{
|
||||
applyClusterErr: assert.AnError,
|
||||
},
|
||||
policyPatcher: stubPolicyPatcher{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
u := &Applier{
|
||||
terraformClient: tc.tf,
|
||||
logLevel: terraform.LogLevelDebug,
|
||||
libvirtRunner: &stubLibvirtRunner{},
|
||||
policyPatcher: stubPolicyPatcher{},
|
||||
fileHandler: tc.fs,
|
||||
backupDir: filepath.Join(constants.UpgradeDir, tc.upgradeID),
|
||||
workingDir: "test",
|
||||
out: io.Discard,
|
||||
}
|
||||
|
||||
_, err := u.Apply(context.Background(), cloudprovider.QEMU, WithoutRollbackOnError)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubPolicyPatcher struct {
|
||||
patchErr error
|
||||
}
|
||||
|
||||
func (s stubPolicyPatcher) Patch(_ context.Context, _ string) error {
|
||||
return s.patchErr
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue