Remove state file

This commit is contained in:
katexochen 2022-10-11 12:24:33 +02:00 committed by Paul Meyer
parent 0d1fd8fb2a
commit 1556e239ca
28 changed files with 381 additions and 319 deletions

View File

@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
<!-- For now removed features. -->
- `endpoint` flag of `constellation init`. IP is now always taken from the `constellation-id.json` file.
- `constellation-state.json` file won't be created anymore. Resources are now managed through Terraform.
### Fixed

View File

@ -10,12 +10,10 @@ import (
"context"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/state"
)
type terraformClient interface {
GetState() state.ConstellationState
CreateCluster(ctx context.Context, name string, input terraform.Variables) error
CreateCluster(ctx context.Context, name string, input terraform.Variables) (string, error)
DestroyCluster(ctx context.Context) error
CleanUpWorkspace() error
RemoveInstaller()

View File

@ -11,7 +11,6 @@ import (
"testing"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/state"
"go.uber.org/goleak"
)
@ -23,7 +22,7 @@ func TestMain(m *testing.M) {
}
type stubTerraformClient struct {
state state.ConstellationState
ip string
cleanUpWorkspaceCalled bool
removeInstallerCalled bool
destroyClusterCalled bool
@ -32,12 +31,8 @@ type stubTerraformClient struct {
cleanUpWorkspaceErr error
}
func (c *stubTerraformClient) GetState() state.ConstellationState {
return c.state
}
func (c *stubTerraformClient) CreateCluster(ctx context.Context, name string, input terraform.Variables) error {
return c.createClusterErr
func (c *stubTerraformClient) CreateCluster(ctx context.Context, name string, input terraform.Variables) (string, error) {
return c.ip, c.createClusterErr
}
func (c *stubTerraformClient) DestroyCluster(ctx context.Context) error {

View File

@ -15,11 +15,11 @@ import (
"runtime"
"strings"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"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/state"
)
// Creator creates cloud resources.
@ -44,41 +44,41 @@ func NewCreator(out io.Writer) *Creator {
// Create creates the handed amount of instances and all the needed resources.
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int,
) (state.ConstellationState, error) {
) (clusterid.File, error) {
switch provider {
case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx, provider)
if err != nil {
return state.ConstellationState{}, err
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx, provider)
if err != nil {
return state.ConstellationState{}, err
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.QEMU:
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
return state.ConstellationState{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
}
cl, err := c.newTerraformClient(ctx, provider)
if err != nil {
return state.ConstellationState{}, err
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
lv := c.newLibvirtRunner()
return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount)
default:
return state.ConstellationState{}, fmt.Errorf("unsupported cloud provider: %s", provider)
return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
}
}
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
name, insType string, controlPlaneCount, workerCount int,
) (stat state.ConstellationState, retErr error) {
) (idFile clusterid.File, retErr error) {
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
vars := &terraform.GCPVariables{
@ -98,16 +98,20 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
Debug: config.IsDebugCluster(),
}
if err := cl.CreateCluster(ctx, name, vars); err != nil {
return state.ConstellationState{}, err
ip, err := cl.CreateCluster(ctx, name, vars)
if err != nil {
return clusterid.File{}, err
}
return cl.GetState(), nil
return clusterid.File{
CloudProvider: cloudprovider.GCP,
IP: ip,
}, nil
}
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
name, insType string, controlPlaneCount, workerCount int,
) (stat state.ConstellationState, retErr error) {
) (idFile clusterid.File, retErr error) {
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
vars := &terraform.AzureVariables{
@ -127,16 +131,20 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
Debug: config.IsDebugCluster(),
}
if err := cl.CreateCluster(ctx, name, vars); err != nil {
return state.ConstellationState{}, err
ip, err := cl.CreateCluster(ctx, name, vars)
if err != nil {
return clusterid.File{}, err
}
return cl.GetState(), nil
return clusterid.File{
CloudProvider: cloudprovider.Azure,
IP: ip,
}, nil
}
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, name string, config *config.Config,
controlPlaneCount, workerCount int,
) (stat state.ConstellationState, retErr error) {
) (idFile clusterid.File, retErr error) {
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerQEMU{client: cl, libvirt: lv})
libvirtURI := config.Provider.QEMU.LibvirtURI
@ -146,7 +154,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
// if no libvirt URI is specified, start a libvirt container
case libvirtURI == "":
if err := lv.Start(ctx, name, config.Provider.QEMU.LibvirtContainerImage); err != nil {
return state.ConstellationState{}, err
return clusterid.File{}, err
}
libvirtURI = libvirt.LibvirtTCPConnectURI
@ -162,11 +170,11 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
case strings.HasPrefix(libvirtURI, "qemu+unix://"):
unixURI, err := url.Parse(strings.TrimPrefix(libvirtURI, "qemu+unix://"))
if err != nil {
return state.ConstellationState{}, err
return clusterid.File{}, err
}
libvirtSocketPath = unixURI.Query().Get("socket")
if libvirtSocketPath == "" {
return state.ConstellationState{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI)
return clusterid.File{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI)
}
}
@ -192,9 +200,13 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
MetadataLibvirtURI: metadataLibvirtURI,
}
if err := cl.CreateCluster(ctx, name, vars); err != nil {
return state.ConstellationState{}, err
ip, err := cl.CreateCluster(ctx, name, vars)
if err != nil {
return clusterid.File{}, err
}
return cl.GetState(), nil
return clusterid.File{
CloudProvider: cloudprovider.QEMU,
IP: ip,
}, nil
}

View File

@ -15,23 +15,12 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert"
)
func TestCreator(t *testing.T) {
failOnNonAMD64 := (runtime.GOARCH != "amd64") || (runtime.GOOS != "linux")
wantGCPState := state.ConstellationState{
CloudProvider: cloudprovider.GCP.String(),
LoadBalancerIP: "192.0.2.1",
}
wantQEMUState := state.ConstellationState{
CloudProvider: cloudprovider.QEMU.String(),
LoadBalancerIP: "192.0.2.1",
}
ip := "192.0.2.1"
someErr := errors.New("failed")
testCases := map[string]struct {
@ -40,15 +29,13 @@ func TestCreator(t *testing.T) {
libvirt *stubLibvirtRunner
provider cloudprovider.Provider
config *config.Config
wantState state.ConstellationState
wantErr bool
wantRollback bool // Use only together with stubClients.
}{
"gcp": {
tfClient: &stubTerraformClient{state: wantGCPState},
provider: cloudprovider.GCP,
config: config.Default(),
wantState: wantGCPState,
tfClient: &stubTerraformClient{ip: ip},
provider: cloudprovider.GCP,
config: config.Default(),
},
"gcp newTerraformClient error": {
newTfClientErr: someErr,
@ -64,12 +51,11 @@ func TestCreator(t *testing.T) {
wantRollback: true,
},
"qemu": {
tfClient: &stubTerraformClient{state: wantQEMUState},
libvirt: &stubLibvirtRunner{},
provider: cloudprovider.QEMU,
config: config.Default(),
wantState: wantQEMUState,
wantErr: failOnNonAMD64,
tfClient: &stubTerraformClient{ip: ip},
libvirt: &stubLibvirtRunner{},
provider: cloudprovider.QEMU,
config: config.Default(),
wantErr: failOnNonAMD64,
},
"qemu newTerraformClient error": {
newTfClientErr: someErr,
@ -87,7 +73,7 @@ func TestCreator(t *testing.T) {
wantRollback: !failOnNonAMD64, // if we run on non-AMD64/linux, we don't get to a point where rollback is needed
},
"qemu start libvirt error": {
tfClient: &stubTerraformClient{state: wantQEMUState},
tfClient: &stubTerraformClient{ip: ip},
libvirt: &stubLibvirtRunner{startErr: someErr},
provider: cloudprovider.QEMU,
config: config.Default(),
@ -115,7 +101,7 @@ func TestCreator(t *testing.T) {
},
}
state, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3)
idFile, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3)
if tc.wantErr {
assert.Error(err)
@ -129,7 +115,8 @@ func TestCreator(t *testing.T) {
}
} else {
assert.NoError(err)
assert.Equal(tc.wantState, state)
assert.Equal(tc.provider, idFile.CloudProvider)
assert.Equal(ip, idFile.IP)
}
})
}

View File

@ -8,12 +8,11 @@ package cloudcmd
import (
"context"
"fmt"
"errors"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/state"
)
// Terminator deletes cloud provider resources.
@ -35,10 +34,18 @@ func NewTerminator() *Terminator {
}
// Terminate deletes the could provider resources defined in the constellation state.
func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error {
provider := cloudprovider.FromString(state.CloudProvider)
func (t *Terminator) Terminate(ctx context.Context, provider cloudprovider.Provider) (retErr error) {
if provider == cloudprovider.Unknown {
return fmt.Errorf("unknown cloud provider %s", state.CloudProvider)
return errors.New("unknown cloud provider")
}
if provider == cloudprovider.QEMU {
libvirt := t.newLibvirtRunner()
defer func() {
if retErr == nil {
retErr = libvirt.Stop(ctx)
}
}()
}
cl, err := t.newTerraformClient(ctx, provider)
@ -47,11 +54,6 @@ func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationSta
}
defer cl.RemoveInstaller()
if provider == cloudprovider.QEMU {
libvirt := t.newLibvirtRunner()
return t.terminateQEMU(ctx, cl, libvirt)
}
return t.terminateTerraform(ctx, cl)
}
@ -61,13 +63,3 @@ func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient)
}
return cl.CleanUpWorkspace()
}
func (t *Terminator) terminateQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner) error {
if err := cl.DestroyCluster(ctx); err != nil {
return err
}
if err := lv.Stop(ctx); err != nil {
return err
}
return cl.CleanUpWorkspace()
}

View File

@ -12,7 +12,6 @@ import (
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert"
)
@ -23,53 +22,52 @@ func TestTerminator(t *testing.T) {
tfClient terraformClient
newTfClientErr error
libvirt *stubLibvirtRunner
state state.ConstellationState
provider cloudprovider.Provider
wantErr bool
}{
"gcp": {
tfClient: &stubTerraformClient{},
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
provider: cloudprovider.GCP,
},
"gcp newTfClientErr": {
newTfClientErr: someErr,
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
provider: cloudprovider.GCP,
wantErr: true,
},
"gcp destroy cluster error": {
tfClient: &stubTerraformClient{destroyClusterErr: someErr},
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
provider: cloudprovider.GCP,
wantErr: true,
},
"gcp clean up workspace error": {
tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr},
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
provider: cloudprovider.GCP,
wantErr: true,
},
"qemu": {
tfClient: &stubTerraformClient{},
libvirt: &stubLibvirtRunner{},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()},
provider: cloudprovider.QEMU,
},
"qemu destroy cluster error": {
tfClient: &stubTerraformClient{destroyClusterErr: someErr},
libvirt: &stubLibvirtRunner{},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()},
provider: cloudprovider.QEMU,
wantErr: true,
},
"qemu clean up workspace error": {
tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr},
libvirt: &stubLibvirtRunner{},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()},
provider: cloudprovider.QEMU,
wantErr: true,
},
"qemu stop libvirt error": {
tfClient: &stubTerraformClient{},
libvirt: &stubLibvirtRunner{stopErr: someErr},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()},
provider: cloudprovider.QEMU,
wantErr: true,
},
"unknown cloud provider": {
state: state.ConstellationState{},
wantErr: true,
},
}
@ -87,7 +85,7 @@ func TestTerminator(t *testing.T) {
},
}
err := terminator.Terminate(context.Background(), tc.state)
err := terminator.Terminate(context.Background(), tc.provider)
if tc.wantErr {
assert.Error(err)
@ -96,7 +94,7 @@ func TestTerminator(t *testing.T) {
cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.destroyClusterCalled)
assert.True(cl.removeInstallerCalled)
if cloudprovider.FromString(tc.state.CloudProvider) == cloudprovider.QEMU {
if tc.provider == cloudprovider.QEMU {
assert.True(tc.libvirt.stopCalled)
}
}

View File

@ -0,0 +1,25 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package clusterid
import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
)
// File contains identifying information about a cluster.
type File struct {
// ClusterID is the unique identifier of the cluster.
ClusterID string `json:"clusterID,omitempty"`
// OwnerID is the unique identifier of the owner of the cluster.
OwnerID string `json:"ownerID,omitempty"`
// UID is the unique identifier of the cluster, used for infrastructure management.
UID string `json:"uid,omitempty"`
// CloudProvider is the cloud provider of the cluster.
CloudProvider cloudprovider.Provider `json:"cloudprovider,omitempty"`
// IP is the IP address the cluster can be reached at (often the load balancer).
IP string `json:"ip,omitempty"`
}

View File

@ -9,9 +9,9 @@ package cmd
import (
"context"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state"
)
type cloudCreator interface {
@ -21,9 +21,9 @@ type cloudCreator interface {
config *config.Config,
name, insType string,
coordCount, nodeCount int,
) (state.ConstellationState, error)
) (clusterid.File, error)
}
type cloudTerminator interface {
Terminate(context.Context, state.ConstellationState) error
Terminate(context.Context, cloudprovider.Provider) error
}

View File

@ -10,9 +10,9 @@ import (
"context"
"testing"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state"
"go.uber.org/goleak"
)
@ -25,7 +25,7 @@ func TestMain(m *testing.M) {
type stubCloudCreator struct {
createCalled bool
state state.ConstellationState
id clusterid.File
createErr error
}
@ -35,9 +35,10 @@ func (c *stubCloudCreator) Create(
config *config.Config,
name, insType string,
coordCount, nodeCount int,
) (state.ConstellationState, error) {
) (clusterid.File, error) {
c.createCalled = true
return c.state, c.createErr
c.id.CloudProvider = provider
return c.id, c.createErr
}
type stubCloudTerminator struct {
@ -45,7 +46,7 @@ type stubCloudTerminator struct {
terminateErr error
}
func (c *stubCloudTerminator) Terminate(context.Context, state.ConstellationState) error {
func (c *stubCloudTerminator) Terminate(context.Context, cloudprovider.Provider) error {
c.called = true
return c.terminateErr
}

View File

@ -15,7 +15,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
@ -118,17 +117,13 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
}
spinner.Start("Creating", false)
state, err := creator.Create(cmd.Context(), provider, config, flags.name, instanceType, flags.controllerCount, flags.workerCount)
idFile, err := creator.Create(cmd.Context(), provider, config, flags.name, instanceType, flags.controllerCount, flags.workerCount)
spinner.Stop()
if err != nil {
return err
}
if err := fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone); err != nil {
return err
}
if err := writeIPtoIDFile(fileHandler, state); err != nil {
if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone); err != nil {
return err
}
@ -195,9 +190,6 @@ type createFlags struct {
// checkDirClean checks if files of a previous Constellation are left in the current working dir.
func checkDirClean(fileHandler file.Handler) error {
if _, err := fileHandler.Stat(constants.StateFilename); !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("file '%s' already exists in working directory, run 'constellation terminate' before creating a new one", constants.StateFilename)
}
if _, err := fileHandler.Stat(constants.AdminConfFilename); !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("file '%s' already exists in working directory, run 'constellation terminate' before creating a new one", constants.AdminConfFilename)
}
@ -211,15 +203,6 @@ func checkDirClean(fileHandler file.Handler) error {
return nil
}
func writeIPtoIDFile(fileHandler file.Handler, state state.ConstellationState) error {
ip := state.LoadBalancerIP
if ip == "" {
return fmt.Errorf("bootstrapper ip not found")
}
idFile := clusterIDsFile{IP: ip}
return fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone)
}
func must(err error) {
if err != nil {
panic(err)

View File

@ -13,11 +13,11 @@ import (
"strings"
"testing"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"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"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -30,7 +30,7 @@ func TestCreate(t *testing.T) {
require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider)))
return fs
}
testState := state.ConstellationState{Name: "test", LoadBalancerIP: "192.0.2.1"}
idFile := clusterid.File{IP: "192.0.2.1"}
someErr := errors.New("failed")
testCases := map[string]struct {
@ -48,7 +48,7 @@ func TestCreate(t *testing.T) {
}{
"create": {
setupFs: fsWithDefaultConfig,
creator: &stubCloudCreator{state: testState},
creator: &stubCloudCreator{id: idFile},
provider: cloudprovider.GCP,
controllerCountFlag: intPtr(1),
workerCountFlag: intPtr(2),
@ -56,7 +56,7 @@ func TestCreate(t *testing.T) {
},
"interactive": {
setupFs: fsWithDefaultConfig,
creator: &stubCloudCreator{state: testState},
creator: &stubCloudCreator{id: idFile},
provider: cloudprovider.Azure,
controllerCountFlag: intPtr(2),
workerCountFlag: intPtr(1),
@ -119,21 +119,6 @@ func TestCreate(t *testing.T) {
controllerCountFlag: intPtr(3),
wantErr: true,
},
"old state in directory": {
setupFs: func(require *require.Assertions, csp cloudprovider.Provider) afero.Fs {
fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.Write(constants.StateFilename, []byte{1}, file.OptNone))
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), csp)))
return fs
},
creator: &stubCloudCreator{},
provider: cloudprovider.GCP,
controllerCountFlag: intPtr(1),
workerCountFlag: intPtr(1),
yesFlag: true,
wantErr: true,
},
"old adminConf in directory": {
setupFs: func(require *require.Assertions, csp cloudprovider.Provider) afero.Fs {
fs := afero.NewMemMapFs()
@ -237,11 +222,12 @@ func TestCreate(t *testing.T) {
assert.False(tc.creator.createCalled)
} else {
assert.True(tc.creator.createCalled)
var state state.ConstellationState
require.NoError(fileHandler.ReadJSON(constants.StateFilename, &state))
var idFile clusterIDsFile
require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile))
assert.Equal(state, testState)
var gotIDFile clusterid.File
require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &gotIDFile))
assert.Equal(gotIDFile, clusterid.File{
IP: idFile.IP,
CloudProvider: tc.provider,
})
}
}
})
@ -267,14 +253,9 @@ func TestCheckDirClean(t *testing.T) {
existingFiles: []string{constants.MasterSecretFilename},
wantErr: true,
},
"state file exists": {
fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.StateFilename},
wantErr: true,
},
"multiple exist": {
fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename, constants.StateFilename},
existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename},
wantErr: true,
},
}

View File

@ -1,13 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
type clusterIDsFile struct {
ClusterID string `json:"clusterID,omitempty"`
OwnerID string `json:"ownerID,omitempty"`
IP string `json:"ip,omitempty"`
}

View File

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/azureshared"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -48,7 +49,6 @@ func NewInitCmd() *cobra.Command {
RunE: runInitialize,
}
cmd.Flags().String("master-secret", "", "path to base64-encoded master secret")
cmd.Flags().String("endpoint", "", "endpoint of the bootstrapper, passed as HOST[:PORT]")
cmd.Flags().Bool("conformance", false, "enable conformance mode")
return cmd
}
@ -74,7 +74,7 @@ func runInitialize(cmd *cobra.Command, args []string) error {
func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer,
fileHandler file.Handler, helmLoader helmLoader, quotaChecker license.QuotaChecker, spinner spinnerInterf,
) error {
flags, err := evalFlagArgs(cmd, fileHandler)
flags, err := evalFlagArgs(cmd)
if err != nil {
return err
}
@ -84,6 +84,11 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
return fmt.Errorf("reading and validating config: %w", err)
}
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return fmt.Errorf("reading cluster ID file: %w", err)
}
k8sVersion, err := versions.NewValidK8sVersion(config.KubernetesVersion)
if err != nil {
return fmt.Errorf("validating kubernetes version: %w", err)
@ -142,13 +147,14 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
EnforceIdkeydigest: getEnforceIDKeyDigest(provider, config),
ConformanceMode: flags.conformance,
}
resp, err := initCall(cmd.Context(), newDialer(validator), flags.endpoint, req)
resp, err := initCall(cmd.Context(), newDialer(validator), idFile.IP, req)
spinner.Stop()
if err != nil {
return err
}
if err := writeOutput(resp, flags.endpoint, cmd.OutOrStdout(), fileHandler); err != nil {
idFile.CloudProvider = provider
if err := writeOutput(idFile, resp, cmd.OutOrStdout(), fileHandler); err != nil {
return err
}
@ -190,7 +196,7 @@ func (d *initDoer) Do(ctx context.Context) error {
return nil
}
func writeOutput(resp *initproto.InitResponse, ip string, wr io.Writer, fileHandler file.Handler) error {
func writeOutput(idFile clusterid.File, resp *initproto.InitResponse, wr io.Writer, fileHandler file.Handler) error {
fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n")
ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId)
@ -207,11 +213,9 @@ func writeOutput(resp *initproto.InitResponse, ip string, wr io.Writer, fileHand
return fmt.Errorf("writing kubeconfig: %w", err)
}
idFile := clusterIDsFile{
ClusterID: clusterID,
OwnerID: ownerID,
IP: ip,
}
idFile.OwnerID = ownerID
idFile.ClusterID = clusterID
if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptOverwrite); err != nil {
return fmt.Errorf("writing Constellation id file: %w", err)
}
@ -249,21 +253,11 @@ func getEnforceIDKeyDigest(provider cloudprovider.Provider, config *config.Confi
// evalFlagArgs gets the flag values and does preprocessing of these values like
// reading the content from file path flags and deriving other values from flag combinations.
func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, error) {
func evalFlagArgs(cmd *cobra.Command) (initFlags, error) {
masterSecretPath, err := cmd.Flags().GetString("master-secret")
if err != nil {
return initFlags{}, fmt.Errorf("parsing master-secret path flag: %w", err)
}
endpoint, err := cmd.Flags().GetString("endpoint")
if err != nil {
return initFlags{}, fmt.Errorf("parsing endpoint flag: %w", err)
}
if endpoint == "" {
endpoint, err = readIPFromIDFile(fileHandler)
if err != nil {
return initFlags{}, fmt.Errorf("getting bootstrapper endpoint: %w", err)
}
}
conformance, err := cmd.Flags().GetBool("conformance")
if err != nil {
return initFlags{}, fmt.Errorf("parsing autoscale flag: %w", err)
@ -275,7 +269,6 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro
return initFlags{
configPath: configPath,
endpoint: endpoint,
conformance: conformance,
masterSecretPath: masterSecretPath,
}, nil
@ -285,7 +278,6 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro
type initFlags struct {
configPath string
masterSecretPath string
endpoint string
conformance bool
}
@ -334,7 +326,7 @@ func readOrGenerateMasterSecret(writer io.Writer, fileHandler file.Handler, file
}
func readIPFromIDFile(fileHandler file.Handler) (string, error) {
var idFile clusterIDsFile
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return "", err
}

View File

@ -20,8 +20,8 @@ import (
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
@ -31,7 +31,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/grpc/testdialer"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/oid"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -48,18 +47,9 @@ func TestInitArgumentValidation(t *testing.T) {
}
func TestInitialize(t *testing.T) {
testGcpState := &state.ConstellationState{
CloudProvider: "GCP",
}
gcpServiceAccKey := &gcpshared.ServiceAccountKey{
Type: "service_account",
}
testAzureState := &state.ConstellationState{
CloudProvider: "Azure",
}
testQemuState := &state.ConstellationState{
CloudProvider: "QEMU",
}
testInitResp := &initproto.InitResponse{
Kubeconfig: []byte("kubeconfig"),
OwnerId: []byte("ownerID"),
@ -69,59 +59,51 @@ func TestInitialize(t *testing.T) {
someErr := errors.New("failed")
testCases := map[string]struct {
state *state.ConstellationState
idFile *clusterIDsFile
provider cloudprovider.Provider
idFile *clusterid.File
configMutator func(*config.Config)
serviceAccKey *gcpshared.ServiceAccountKey
helmLoader stubHelmLoader
initServerAPI *stubInitServer
endpointFlag string
masterSecretShouldExist bool
wantErr bool
}{
"initialize some gcp instances": {
state: testGcpState,
idFile: &clusterIDsFile{IP: "192.0.2.1"},
provider: cloudprovider.GCP,
idFile: &clusterid.File{IP: "192.0.2.1"},
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{initResp: testInitResp},
},
"initialize some azure instances": {
state: testAzureState,
idFile: &clusterIDsFile{IP: "192.0.2.1"},
provider: cloudprovider.Azure,
idFile: &clusterid.File{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initResp: testInitResp},
},
"initialize some qemu instances": {
state: testQemuState,
idFile: &clusterIDsFile{IP: "192.0.2.1"},
provider: cloudprovider.QEMU,
idFile: &clusterid.File{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initResp: testInitResp},
},
"initialize with endpoint flag": {
state: testGcpState,
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{initResp: testInitResp},
endpointFlag: "192.0.2.1",
},
"empty state": {
state: &state.ConstellationState{},
idFile: &clusterIDsFile{IP: "192.0.2.1"},
"empty id file": {
provider: cloudprovider.GCP,
idFile: &clusterid.File{},
initServerAPI: &stubInitServer{},
wantErr: true,
},
"neither endpoint flag nor id file": {
state: &state.ConstellationState{},
wantErr: true,
"no id file": {
provider: cloudprovider.GCP,
wantErr: true,
},
"init call fails": {
state: testGcpState,
idFile: &clusterIDsFile{IP: "192.0.2.1"},
provider: cloudprovider.GCP,
idFile: &clusterid.File{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initErr: someErr},
wantErr: true,
},
"fail missing enforced PCR": {
state: testGcpState,
idFile: &clusterIDsFile{IP: "192.0.2.1"},
provider: cloudprovider.GCP,
idFile: &clusterid.File{IP: "192.0.2.1"},
configMutator: func(c *config.Config) {
c.Provider.GCP.EnforcedMeasurements = append(c.Provider.GCP.EnforcedMeasurements, 10)
},
@ -158,22 +140,17 @@ func TestInitialize(t *testing.T) {
// Flags
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
if tc.endpointFlag != "" {
require.NoError(cmd.Flags().Set("endpoint", tc.endpointFlag))
}
// File system preparation
fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs)
config := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.FromString(tc.state.CloudProvider))
config := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.provider)
if tc.configMutator != nil {
tc.configMutator(config)
}
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone))
if tc.state != nil {
require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.state, file.OptNone))
}
if tc.idFile != nil {
tc.idFile.CloudProvider = tc.provider
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone))
}
if tc.serviceAccKey != nil {
@ -218,17 +195,22 @@ func TestWriteOutput(t *testing.T) {
ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId)
clusterID := base64.StdEncoding.EncodeToString(resp.ClusterId)
expectedIDFile := clusterIDsFile{
expectedIDFile := clusterid.File{
ClusterID: clusterID,
OwnerID: ownerID,
IP: "cluster-ip",
UID: "test-uid",
}
var out bytes.Buffer
testFs := afero.NewMemMapFs()
fileHandler := file.NewHandler(testFs)
err := writeOutput(resp, "cluster-ip", &out, fileHandler)
idFile := clusterid.File{
UID: "test-uid",
IP: "cluster-ip",
}
err := writeOutput(idFile, resp, &out, fileHandler)
assert.NoError(err)
// assert.Contains(out.String(), ownerID)
assert.Contains(out.String(), clusterID)
@ -241,7 +223,7 @@ func TestWriteOutput(t *testing.T) {
idsFile, err := afs.ReadFile(constants.ClusterIDsFileName)
assert.NoError(err)
var testIDFile clusterIDsFile
var testIDFile clusterid.File
err = json.Unmarshal(idsFile, &testIDFile)
assert.NoError(err)
assert.Equal(expectedIDFile, testIDFile)
@ -366,8 +348,7 @@ func TestAttestation(t *testing.T) {
OwnerId: []byte("ownerID"),
ClusterId: []byte("clusterID"),
}}
existingState := state.ConstellationState{CloudProvider: "QEMU"}
existingIDFile := &clusterIDsFile{IP: "192.0.2.4"}
existingIDFile := &clusterid.File{IP: "192.0.2.4", CloudProvider: cloudprovider.QEMU}
netDialer := testdialer.NewBufconnDialer()
newDialer := func(v *cloudcmd.Validator) *dialer.Dialer {
@ -404,7 +385,6 @@ func TestAttestation(t *testing.T) {
fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.WriteJSON(constants.StateFilename, existingState, file.OptNone))
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, existingIDFile, file.OptNone))
cfg := config.Default()

View File

@ -11,10 +11,10 @@ import (
"fmt"
"os"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"go.uber.org/multierr"
@ -45,14 +45,14 @@ func runDown(cmd *cobra.Command, args []string) error {
}
func checkForMiniCluster(fileHandler file.Handler) error {
var state state.ConstellationState
if err := fileHandler.ReadJSON(constants.StateFilename, &state); err != nil {
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return err
}
if cloudprovider.FromString(state.CloudProvider) != cloudprovider.QEMU {
if idFile.CloudProvider != cloudprovider.QEMU {
return errors.New("cluster is not a QEMU based Constellation")
}
if state.Name != "mini" {
if idFile.UID != "mini" {
return errors.New("cluster is not a mini Constellation cluster")
}

View File

@ -199,15 +199,14 @@ func prepareConfig(cmd *cobra.Command, fileHandler file.Handler) (*config.Config
// createMiniCluster creates a new cluster using the given config.
func createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config) error {
state, err := creator.Create(ctx, cloudprovider.QEMU, config, "mini", "", 1, 1)
idFile, err := creator.Create(ctx, cloudprovider.QEMU, config, "mini", "", 1, 1)
if err != nil {
return err
}
if err := fileHandler.WriteJSON(constants.StateFilename, state); err != nil {
return err
}
return writeIPtoIDFile(fileHandler, state)
idFile.UID = "mini" // use UID "mini" to identify mini constellation clusters.
return fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone)
}
// initializeMiniCluster initializes a QEMU cluster.

View File

@ -16,6 +16,7 @@ import (
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
@ -226,7 +227,7 @@ func TestParseRecoverFlags(t *testing.T) {
fileHandler := file.NewHandler(afero.NewMemMapFs())
if tc.writeIDFile {
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, &clusterIDsFile{IP: "192.0.2.42"}))
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, &clusterid.File{IP: "192.0.2.42"}))
}
flags, err := parseRecoverFlags(cmd, fileHandler)

View File

@ -16,9 +16,9 @@ import (
"go.uber.org/multierr"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
)
// NewTerminateCmd returns a new cobra.Command for the terminate command.
@ -45,13 +45,13 @@ func runTerminate(cmd *cobra.Command, args []string) error {
func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.Handler, spinner spinnerInterf,
) error {
var stat state.ConstellationState
if err := fileHandler.ReadJSON(constants.StateFilename, &stat); err != nil {
return fmt.Errorf("reading Constellation state: %w", err)
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return err
}
spinner.Start("Terminating", false)
err := terminator.Terminate(cmd.Context(), stat)
err := terminator.Terminate(cmd.Context(), idFile.CloudProvider)
spinner.Stop()
if err != nil {
return fmt.Errorf("terminating Constellation cluster: %w", err)
@ -60,10 +60,6 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.
cmd.Println("Your Constellation cluster was terminated successfully.")
var retErr error
if err := fileHandler.Remove(constants.StateFilename); err != nil {
retErr = multierr.Append(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.StateFilename))
}
if err := fileHandler.Remove(constants.AdminConfFilename); err != nil && !errors.Is(err, fs.ErrNotExist) {
retErr = multierr.Append(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.AdminConfFilename))
}

View File

@ -11,9 +11,10 @@ import (
"errors"
"testing"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -46,47 +47,46 @@ func TestTerminateCmdArgumentValidation(t *testing.T) {
}
func TestTerminate(t *testing.T) {
setupFs := func(require *require.Assertions, state state.ConstellationState) afero.Fs {
setupFs := func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone))
require.NoError(fileHandler.Write(constants.WGQuickConfigFilename, []byte{1, 2}, file.OptNone))
require.NoError(fileHandler.Write(constants.ClusterIDsFileName, []byte{1, 2}, file.OptNone))
require.NoError(fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone))
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone))
return fs
}
someErr := errors.New("failed")
testCases := map[string]struct {
state state.ConstellationState
setupFs func(*require.Assertions, state.ConstellationState) afero.Fs
idFile clusterid.File
setupFs func(*require.Assertions, clusterid.File) afero.Fs
terminator spyCloudTerminator
wantErr bool
}{
"success": {
state: state.ConstellationState{CloudProvider: "gcp"},
idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: setupFs,
terminator: &stubCloudTerminator{},
},
"files to remove do not exist": {
state: state.ConstellationState{CloudProvider: "gcp"},
setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs {
idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone))
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone))
return fs
},
terminator: &stubCloudTerminator{},
},
"terminate error": {
state: state.ConstellationState{CloudProvider: "gcp"},
idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: setupFs,
terminator: &stubCloudTerminator{terminateErr: someErr},
wantErr: true,
},
"missing state file": {
state: state.ConstellationState{CloudProvider: "gcp"},
setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs {
idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone))
@ -97,9 +97,9 @@ func TestTerminate(t *testing.T) {
wantErr: true,
},
"remove file fails": {
state: state.ConstellationState{CloudProvider: "gcp"},
setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs {
fs := setupFs(require, state)
idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := setupFs(require, idFile)
return afero.NewReadOnlyFs(fs)
},
terminator: &stubCloudTerminator{},
@ -117,7 +117,7 @@ func TestTerminate(t *testing.T) {
cmd.SetErr(&bytes.Buffer{})
require.NotNil(tc.setupFs)
fileHandler := file.NewHandler(tc.setupFs(require, tc.state))
fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile))
err := terminate(cmd, tc.terminator, fileHandler, nopSpinner{})
@ -126,8 +126,6 @@ func TestTerminate(t *testing.T) {
} else {
assert.NoError(err)
assert.True(tc.terminator.Called())
_, err := fileHandler.Stat(constants.StateFilename)
assert.Error(err)
_, err = fileHandler.Stat(constants.AdminConfFilename)
assert.Error(err)
_, err = fileHandler.Stat(constants.WGQuickConfigFilename)

View File

@ -17,6 +17,7 @@ import (
"strings"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/crypto"
@ -117,7 +118,7 @@ func parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handler) (verifyFlags
emptyEndpoint := endpoint == ""
emptyIDs := ownerID == "" && clusterID == ""
if emptyEndpoint || emptyIDs {
var idFile clusterIDsFile
var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err == nil {
if emptyEndpoint {
cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", constants.ClusterIDsFileName)

View File

@ -16,6 +16,7 @@ import (
"strconv"
"testing"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
@ -44,7 +45,7 @@ func TestVerify(t *testing.T) {
configFlag string
ownerIDFlag string
clusterIDFlag string
idFile *clusterIDsFile
idFile *clusterid.File
wantEndpoint string
wantErr bool
}{
@ -79,7 +80,7 @@ func TestVerify(t *testing.T) {
provider: cloudprovider.GCP,
clusterIDFlag: zeroBase64,
protoClient: &stubVerifyClient{},
idFile: &clusterIDsFile{IP: "192.0.2.1"},
idFile: &clusterid.File{IP: "192.0.2.1"},
wantEndpoint: "192.0.2.1:" + strconv.Itoa(constants.VerifyServiceNodePortGRPC),
},
"override endpoint from details file": {
@ -87,7 +88,7 @@ func TestVerify(t *testing.T) {
nodeEndpointFlag: "192.0.2.2:1234",
clusterIDFlag: zeroBase64,
protoClient: &stubVerifyClient{},
idFile: &clusterIDsFile{IP: "192.0.2.1"},
idFile: &clusterid.File{IP: "192.0.2.1"},
wantEndpoint: "192.0.2.2:1234",
},
"invalid endpoint": {
@ -106,7 +107,7 @@ func TestVerify(t *testing.T) {
provider: cloudprovider.GCP,
nodeEndpointFlag: "192.0.2.1:1234",
protoClient: &stubVerifyClient{},
idFile: &clusterIDsFile{OwnerID: zeroBase64},
idFile: &clusterid.File{OwnerID: zeroBase64},
wantEndpoint: "192.0.2.1:1234",
},
"config file not existing": {

View File

@ -12,7 +12,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/hashicorp/go-version"
install "github.com/hashicorp/hc-install"
"github.com/hashicorp/hc-install/fs"
@ -36,7 +35,6 @@ type Client struct {
provider cloudprovider.Provider
file file.Handler
state state.ConstellationState
remove func()
}
@ -58,43 +56,38 @@ func New(ctx context.Context, provider cloudprovider.Provider) (*Client, error)
}
// CreateCluster creates a Constellation cluster using Terraform.
func (c *Client) CreateCluster(ctx context.Context, name string, vars Variables) error {
func (c *Client) CreateCluster(ctx context.Context, name string, vars Variables) (string, error) {
if err := prepareWorkspace(c.file, c.provider); err != nil {
return err
return "", err
}
if err := c.tf.Init(ctx); err != nil {
return err
return "", err
}
if err := c.file.Write(terraformVarsFile, []byte(vars.String())); err != nil {
return err
return "", err
}
if err := c.tf.Apply(ctx); err != nil {
return err
return "", err
}
tfState, err := c.tf.Show(ctx)
if err != nil {
return err
return "", err
}
ipOutput, ok := tfState.Values.Outputs["ip"]
if !ok {
return errors.New("no IP output found")
return "", errors.New("no IP output found")
}
ip, ok := ipOutput.Value.(string)
if !ok {
return errors.New("invalid type in IP output: not a string")
}
c.state = state.ConstellationState{
Name: name,
CloudProvider: c.provider.String(),
LoadBalancerIP: ip,
return "", errors.New("invalid type in IP output: not a string")
}
return nil
return ip, nil
}
// DestroyInstances destroys a Constellation cluster using Terraform.
@ -132,11 +125,6 @@ func (c *Client) CleanUpWorkspace() error {
return nil
}
// GetState returns the state of the cluster.
func (c *Client) GetState() state.ConstellationState {
return c.state
}
// GetExecutable returns a Terraform executable either from the local filesystem,
// or downloads the latest version fulfilling the version constraint.
func GetExecutable(ctx context.Context, workingDir string) (terraform *tfexec.Terraform, remove func(), err error) {

View File

@ -127,13 +127,14 @@ func TestCreateCluster(t *testing.T) {
file: file.NewHandler(tc.fs),
}
err := c.CreateCluster(context.Background(), "test", tc.vars)
ip, err := c.CreateCluster(context.Background(), "test", tc.vars)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal("192.0.2.100", ip)
})
}
}

View File

@ -6,7 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only
package cloudprovider
import "strings"
import (
"encoding/json"
"strings"
)
//go:generate stringer -type=Provider
@ -21,6 +24,21 @@ const (
QEMU
)
// MarshalJSON marshals the Provider to JSON string.
func (p Provider) MarshalJSON() ([]byte, error) {
return json.Marshal(p.String())
}
// UnmarshalJSON unmarshals the Provider from JSON string.
func (p *Provider) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*p = FromString(s)
return nil
}
// FromString returns cloud provider from string.
func FromString(s string) Provider {
s = strings.ToLower(s)

View File

@ -0,0 +1,143 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudprovider
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMarshalJSON(t *testing.T) {
testCases := map[string]struct {
input Provider
want []byte
}{
"unknown": {
input: Unknown,
want: []byte("\"Unknown\""),
},
"aws": {
input: AWS,
want: []byte("\"AWS\""),
},
"azure": {
input: Azure,
want: []byte("\"Azure\""),
},
"gcp": {
input: GCP,
want: []byte("\"GCP\""),
},
"qemu": {
input: QEMU,
want: []byte("\"QEMU\""),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
b, err := tc.input.MarshalJSON()
assert.NoError(err)
assert.Equal(tc.want, b)
})
}
}
func TestUnmarshalJSON(t *testing.T) {
testCases := map[string]struct {
input []byte
want Provider
wantErr bool
}{
"empty": {
input: []byte{},
wantErr: true,
},
"unknown": {
input: []byte("\"unknown\""),
want: Unknown,
},
"aws": {
input: []byte("\"aws\""),
want: AWS,
},
"azure": {
input: []byte("\"azure\""),
want: Azure,
},
"gcp": {
input: []byte("\"gcp\""),
want: GCP,
},
"qemu": {
input: []byte("\"qemu\""),
want: QEMU,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
var p Provider
err := p.UnmarshalJSON(tc.input)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, p)
}
})
}
}
func TestFromString(t *testing.T) {
testCases := map[string]struct {
input string
want Provider
}{
"empty": {
input: "",
want: Unknown,
},
"unknown": {
input: "unknown",
want: Unknown,
},
"aws": {
input: "aws",
want: AWS,
},
"azure": {
input: "azure",
want: Azure,
},
"gcp": {
input: "gcp",
want: GCP,
},
"qemu": {
input: "qemu",
want: QEMU,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
p := FromString(tc.input)
assert.Equal(tc.want, p)
})
}
}

View File

@ -58,7 +58,6 @@ const (
//
// Filenames.
//
StateFilename = "constellation-state.json"
ClusterIDsFileName = "constellation-id.json"
ConfigFilename = "constellation-conf.yaml"
LicenseFilename = "constellation.license"

View File

@ -1,15 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package state
// ConstellationState is the state of a Constellation.
type ConstellationState struct {
Name string `json:"name,omitempty"`
UID string `json:"uid,omitempty"`
CloudProvider string `json:"cloudprovider,omitempty"`
LoadBalancerIP string `json:"bootstrapperhost,omitempty"`
}