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 ### Removed
<!-- For now removed features. --> <!-- For now removed features. -->
- `endpoint` flag of `constellation init`. IP is now always taken from the `constellation-id.json` file. - `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 ### Fixed

View File

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

View File

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

View File

@ -15,11 +15,11 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state"
) )
// Creator creates cloud resources. // 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. // 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, 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 { switch provider {
case cloudprovider.GCP: case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx, provider) cl, err := c.newTerraformClient(ctx, provider)
if err != nil { if err != nil {
return state.ConstellationState{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount) return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.Azure: case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx, provider) cl, err := c.newTerraformClient(ctx, provider)
if err != nil { if err != nil {
return state.ConstellationState{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount) return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.QEMU: case cloudprovider.QEMU:
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { 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) cl, err := c.newTerraformClient(ctx, provider)
if err != nil { if err != nil {
return state.ConstellationState{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
lv := c.newLibvirtRunner() lv := c.newLibvirtRunner()
return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount) return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount)
default: 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, func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
name, insType string, controlPlaneCount, workerCount int, 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}) defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
vars := &terraform.GCPVariables{ vars := &terraform.GCPVariables{
@ -98,16 +98,20 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
Debug: config.IsDebugCluster(), Debug: config.IsDebugCluster(),
} }
if err := cl.CreateCluster(ctx, name, vars); err != nil { ip, err := cl.CreateCluster(ctx, name, vars)
return state.ConstellationState{}, err 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, func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
name, insType string, controlPlaneCount, workerCount int, 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}) defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
vars := &terraform.AzureVariables{ vars := &terraform.AzureVariables{
@ -127,16 +131,20 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
Debug: config.IsDebugCluster(), Debug: config.IsDebugCluster(),
} }
if err := cl.CreateCluster(ctx, name, vars); err != nil { ip, err := cl.CreateCluster(ctx, name, vars)
return state.ConstellationState{}, err 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, func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, name string, config *config.Config,
controlPlaneCount, workerCount int, 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}) defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerQEMU{client: cl, libvirt: lv})
libvirtURI := config.Provider.QEMU.LibvirtURI 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 // if no libvirt URI is specified, start a libvirt container
case libvirtURI == "": case libvirtURI == "":
if err := lv.Start(ctx, name, config.Provider.QEMU.LibvirtContainerImage); err != nil { if err := lv.Start(ctx, name, config.Provider.QEMU.LibvirtContainerImage); err != nil {
return state.ConstellationState{}, err return clusterid.File{}, err
} }
libvirtURI = libvirt.LibvirtTCPConnectURI libvirtURI = libvirt.LibvirtTCPConnectURI
@ -162,11 +170,11 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
case strings.HasPrefix(libvirtURI, "qemu+unix://"): case strings.HasPrefix(libvirtURI, "qemu+unix://"):
unixURI, err := url.Parse(strings.TrimPrefix(libvirtURI, "qemu+unix://")) unixURI, err := url.Parse(strings.TrimPrefix(libvirtURI, "qemu+unix://"))
if err != nil { if err != nil {
return state.ConstellationState{}, err return clusterid.File{}, err
} }
libvirtSocketPath = unixURI.Query().Get("socket") libvirtSocketPath = unixURI.Query().Get("socket")
if libvirtSocketPath == "" { 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, MetadataLibvirtURI: metadataLibvirtURI,
} }
if err := cl.CreateCluster(ctx, name, vars); err != nil { ip, err := cl.CreateCluster(ctx, name, vars)
return state.ConstellationState{}, err 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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestCreator(t *testing.T) { func TestCreator(t *testing.T) {
failOnNonAMD64 := (runtime.GOARCH != "amd64") || (runtime.GOOS != "linux") failOnNonAMD64 := (runtime.GOARCH != "amd64") || (runtime.GOOS != "linux")
ip := "192.0.2.1"
wantGCPState := state.ConstellationState{
CloudProvider: cloudprovider.GCP.String(),
LoadBalancerIP: "192.0.2.1",
}
wantQEMUState := state.ConstellationState{
CloudProvider: cloudprovider.QEMU.String(),
LoadBalancerIP: "192.0.2.1",
}
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
@ -40,15 +29,13 @@ func TestCreator(t *testing.T) {
libvirt *stubLibvirtRunner libvirt *stubLibvirtRunner
provider cloudprovider.Provider provider cloudprovider.Provider
config *config.Config config *config.Config
wantState state.ConstellationState
wantErr bool wantErr bool
wantRollback bool // Use only together with stubClients. wantRollback bool // Use only together with stubClients.
}{ }{
"gcp": { "gcp": {
tfClient: &stubTerraformClient{state: wantGCPState}, tfClient: &stubTerraformClient{ip: ip},
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
config: config.Default(), config: config.Default(),
wantState: wantGCPState,
}, },
"gcp newTerraformClient error": { "gcp newTerraformClient error": {
newTfClientErr: someErr, newTfClientErr: someErr,
@ -64,11 +51,10 @@ func TestCreator(t *testing.T) {
wantRollback: true, wantRollback: true,
}, },
"qemu": { "qemu": {
tfClient: &stubTerraformClient{state: wantQEMUState}, tfClient: &stubTerraformClient{ip: ip},
libvirt: &stubLibvirtRunner{}, libvirt: &stubLibvirtRunner{},
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
config: config.Default(), config: config.Default(),
wantState: wantQEMUState,
wantErr: failOnNonAMD64, wantErr: failOnNonAMD64,
}, },
"qemu newTerraformClient error": { "qemu newTerraformClient error": {
@ -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 wantRollback: !failOnNonAMD64, // if we run on non-AMD64/linux, we don't get to a point where rollback is needed
}, },
"qemu start libvirt error": { "qemu start libvirt error": {
tfClient: &stubTerraformClient{state: wantQEMUState}, tfClient: &stubTerraformClient{ip: ip},
libvirt: &stubLibvirtRunner{startErr: someErr}, libvirt: &stubLibvirtRunner{startErr: someErr},
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
config: config.Default(), 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 { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -129,7 +115,8 @@ func TestCreator(t *testing.T) {
} }
} else { } else {
assert.NoError(err) 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 ( import (
"context" "context"
"fmt" "errors"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/state"
) )
// Terminator deletes cloud provider resources. // Terminator deletes cloud provider resources.
@ -35,10 +34,18 @@ func NewTerminator() *Terminator {
} }
// Terminate deletes the could provider resources defined in the constellation state. // Terminate deletes the could provider resources defined in the constellation state.
func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error { func (t *Terminator) Terminate(ctx context.Context, provider cloudprovider.Provider) (retErr error) {
provider := cloudprovider.FromString(state.CloudProvider)
if provider == cloudprovider.Unknown { 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) cl, err := t.newTerraformClient(ctx, provider)
@ -47,11 +54,6 @@ func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationSta
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
if provider == cloudprovider.QEMU {
libvirt := t.newLibvirtRunner()
return t.terminateQEMU(ctx, cl, libvirt)
}
return t.terminateTerraform(ctx, cl) return t.terminateTerraform(ctx, cl)
} }
@ -61,13 +63,3 @@ func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient)
} }
return cl.CleanUpWorkspace() 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" "testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -23,53 +22,52 @@ func TestTerminator(t *testing.T) {
tfClient terraformClient tfClient terraformClient
newTfClientErr error newTfClientErr error
libvirt *stubLibvirtRunner libvirt *stubLibvirtRunner
state state.ConstellationState provider cloudprovider.Provider
wantErr bool wantErr bool
}{ }{
"gcp": { "gcp": {
tfClient: &stubTerraformClient{}, tfClient: &stubTerraformClient{},
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, provider: cloudprovider.GCP,
}, },
"gcp newTfClientErr": { "gcp newTfClientErr": {
newTfClientErr: someErr, newTfClientErr: someErr,
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, provider: cloudprovider.GCP,
wantErr: true, wantErr: true,
}, },
"gcp destroy cluster error": { "gcp destroy cluster error": {
tfClient: &stubTerraformClient{destroyClusterErr: someErr}, tfClient: &stubTerraformClient{destroyClusterErr: someErr},
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, provider: cloudprovider.GCP,
wantErr: true, wantErr: true,
}, },
"gcp clean up workspace error": { "gcp clean up workspace error": {
tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr}, tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr},
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, provider: cloudprovider.GCP,
wantErr: true, wantErr: true,
}, },
"qemu": { "qemu": {
tfClient: &stubTerraformClient{}, tfClient: &stubTerraformClient{},
libvirt: &stubLibvirtRunner{}, libvirt: &stubLibvirtRunner{},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, provider: cloudprovider.QEMU,
}, },
"qemu destroy cluster error": { "qemu destroy cluster error": {
tfClient: &stubTerraformClient{destroyClusterErr: someErr}, tfClient: &stubTerraformClient{destroyClusterErr: someErr},
libvirt: &stubLibvirtRunner{}, libvirt: &stubLibvirtRunner{},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, provider: cloudprovider.QEMU,
wantErr: true, wantErr: true,
}, },
"qemu clean up workspace error": { "qemu clean up workspace error": {
tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr}, tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr},
libvirt: &stubLibvirtRunner{}, libvirt: &stubLibvirtRunner{},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, provider: cloudprovider.QEMU,
wantErr: true, wantErr: true,
}, },
"qemu stop libvirt error": { "qemu stop libvirt error": {
tfClient: &stubTerraformClient{}, tfClient: &stubTerraformClient{},
libvirt: &stubLibvirtRunner{stopErr: someErr}, libvirt: &stubLibvirtRunner{stopErr: someErr},
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, provider: cloudprovider.QEMU,
wantErr: true, wantErr: true,
}, },
"unknown cloud provider": { "unknown cloud provider": {
state: state.ConstellationState{},
wantErr: true, 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 { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -96,7 +94,7 @@ func TestTerminator(t *testing.T) {
cl := tc.tfClient.(*stubTerraformClient) cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.destroyClusterCalled) assert.True(cl.destroyClusterCalled)
assert.True(cl.removeInstallerCalled) assert.True(cl.removeInstallerCalled)
if cloudprovider.FromString(tc.state.CloudProvider) == cloudprovider.QEMU { if tc.provider == cloudprovider.QEMU {
assert.True(tc.libvirt.stopCalled) 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 ( import (
"context" "context"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state"
) )
type cloudCreator interface { type cloudCreator interface {
@ -21,9 +21,9 @@ type cloudCreator interface {
config *config.Config, config *config.Config,
name, insType string, name, insType string,
coordCount, nodeCount int, coordCount, nodeCount int,
) (state.ConstellationState, error) ) (clusterid.File, error)
} }
type cloudTerminator interface { type cloudTerminator interface {
Terminate(context.Context, state.ConstellationState) error Terminate(context.Context, cloudprovider.Provider) error
} }

View File

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

View File

@ -15,7 +15,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -118,17 +117,13 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler,
} }
spinner.Start("Creating", false) 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() spinner.Stop()
if err != nil { if err != nil {
return err return err
} }
if err := fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone); err != nil { if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone); err != nil {
return err
}
if err := writeIPtoIDFile(fileHandler, state); err != nil {
return err return err
} }
@ -195,9 +190,6 @@ type createFlags struct {
// checkDirClean checks if files of a previous Constellation are left in the current working dir. // checkDirClean checks if files of a previous Constellation are left in the current working dir.
func checkDirClean(fileHandler file.Handler) error { 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) { 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) 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 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) { func must(err error) {
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -13,11 +13,11 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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))) require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider)))
return fs return fs
} }
testState := state.ConstellationState{Name: "test", LoadBalancerIP: "192.0.2.1"} idFile := clusterid.File{IP: "192.0.2.1"}
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
@ -48,7 +48,7 @@ func TestCreate(t *testing.T) {
}{ }{
"create": { "create": {
setupFs: fsWithDefaultConfig, setupFs: fsWithDefaultConfig,
creator: &stubCloudCreator{state: testState}, creator: &stubCloudCreator{id: idFile},
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
controllerCountFlag: intPtr(1), controllerCountFlag: intPtr(1),
workerCountFlag: intPtr(2), workerCountFlag: intPtr(2),
@ -56,7 +56,7 @@ func TestCreate(t *testing.T) {
}, },
"interactive": { "interactive": {
setupFs: fsWithDefaultConfig, setupFs: fsWithDefaultConfig,
creator: &stubCloudCreator{state: testState}, creator: &stubCloudCreator{id: idFile},
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
controllerCountFlag: intPtr(2), controllerCountFlag: intPtr(2),
workerCountFlag: intPtr(1), workerCountFlag: intPtr(1),
@ -119,21 +119,6 @@ func TestCreate(t *testing.T) {
controllerCountFlag: intPtr(3), controllerCountFlag: intPtr(3),
wantErr: true, 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": { "old adminConf in directory": {
setupFs: func(require *require.Assertions, csp cloudprovider.Provider) afero.Fs { setupFs: func(require *require.Assertions, csp cloudprovider.Provider) afero.Fs {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
@ -237,11 +222,12 @@ func TestCreate(t *testing.T) {
assert.False(tc.creator.createCalled) assert.False(tc.creator.createCalled)
} else { } else {
assert.True(tc.creator.createCalled) assert.True(tc.creator.createCalled)
var state state.ConstellationState var gotIDFile clusterid.File
require.NoError(fileHandler.ReadJSON(constants.StateFilename, &state)) require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &gotIDFile))
var idFile clusterIDsFile assert.Equal(gotIDFile, clusterid.File{
require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile)) IP: idFile.IP,
assert.Equal(state, testState) CloudProvider: tc.provider,
})
} }
} }
}) })
@ -267,14 +253,9 @@ func TestCheckDirClean(t *testing.T) {
existingFiles: []string{constants.MasterSecretFilename}, existingFiles: []string{constants.MasterSecretFilename},
wantErr: true, wantErr: true,
}, },
"state file exists": {
fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.StateFilename},
wantErr: true,
},
"multiple exist": { "multiple exist": {
fileHandler: file.NewHandler(afero.NewMemMapFs()), fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename, constants.StateFilename}, existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename},
wantErr: true, 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/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "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/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/azureshared" "github.com/edgelesssys/constellation/v2/internal/azureshared"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -48,7 +49,6 @@ func NewInitCmd() *cobra.Command {
RunE: runInitialize, RunE: runInitialize,
} }
cmd.Flags().String("master-secret", "", "path to base64-encoded master secret") 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") cmd.Flags().Bool("conformance", false, "enable conformance mode")
return cmd 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, func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer,
fileHandler file.Handler, helmLoader helmLoader, quotaChecker license.QuotaChecker, spinner spinnerInterf, fileHandler file.Handler, helmLoader helmLoader, quotaChecker license.QuotaChecker, spinner spinnerInterf,
) error { ) error {
flags, err := evalFlagArgs(cmd, fileHandler) flags, err := evalFlagArgs(cmd)
if err != nil { if err != nil {
return err 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) 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) k8sVersion, err := versions.NewValidK8sVersion(config.KubernetesVersion)
if err != nil { if err != nil {
return fmt.Errorf("validating kubernetes version: %w", err) 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), EnforceIdkeydigest: getEnforceIDKeyDigest(provider, config),
ConformanceMode: flags.conformance, 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() spinner.Stop()
if err != nil { if err != nil {
return err 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 return err
} }
@ -190,7 +196,7 @@ func (d *initDoer) Do(ctx context.Context) error {
return nil 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") fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n")
ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId) 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) return fmt.Errorf("writing kubeconfig: %w", err)
} }
idFile := clusterIDsFile{ idFile.OwnerID = ownerID
ClusterID: clusterID, idFile.ClusterID = clusterID
OwnerID: ownerID,
IP: ip,
}
if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptOverwrite); err != nil { if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptOverwrite); err != nil {
return fmt.Errorf("writing Constellation id file: %w", err) 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 // 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. // 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") masterSecretPath, err := cmd.Flags().GetString("master-secret")
if err != nil { if err != nil {
return initFlags{}, fmt.Errorf("parsing master-secret path flag: %w", err) 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") conformance, err := cmd.Flags().GetBool("conformance")
if err != nil { if err != nil {
return initFlags{}, fmt.Errorf("parsing autoscale flag: %w", err) 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{ return initFlags{
configPath: configPath, configPath: configPath,
endpoint: endpoint,
conformance: conformance, conformance: conformance,
masterSecretPath: masterSecretPath, masterSecretPath: masterSecretPath,
}, nil }, nil
@ -285,7 +278,6 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro
type initFlags struct { type initFlags struct {
configPath string configPath string
masterSecretPath string masterSecretPath string
endpoint string
conformance bool conformance bool
} }
@ -334,7 +326,7 @@ func readOrGenerateMasterSecret(writer io.Writer, fileHandler file.Handler, file
} }
func readIPFromIDFile(fileHandler file.Handler) (string, error) { func readIPFromIDFile(fileHandler file.Handler) (string, error) {
var idFile clusterIDsFile var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return "", err return "", err
} }

View File

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

View File

@ -11,10 +11,10 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/multierr" "go.uber.org/multierr"
@ -45,14 +45,14 @@ func runDown(cmd *cobra.Command, args []string) error {
} }
func checkForMiniCluster(fileHandler file.Handler) error { func checkForMiniCluster(fileHandler file.Handler) error {
var state state.ConstellationState var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.StateFilename, &state); err != nil { if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return err return err
} }
if cloudprovider.FromString(state.CloudProvider) != cloudprovider.QEMU { if idFile.CloudProvider != cloudprovider.QEMU {
return errors.New("cluster is not a QEMU based Constellation") 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") 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. // createMiniCluster creates a new cluster using the given config.
func createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config) error { 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 { if err != nil {
return err 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. // initializeMiniCluster initializes a QEMU cluster.

View File

@ -16,6 +16,7 @@ import (
"time" "time"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "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/disk-mapper/recoverproto"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
@ -226,7 +227,7 @@ func TestParseRecoverFlags(t *testing.T) {
fileHandler := file.NewHandler(afero.NewMemMapFs()) fileHandler := file.NewHandler(afero.NewMemMapFs())
if tc.writeIDFile { 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) flags, err := parseRecoverFlags(cmd, fileHandler)

View File

@ -16,9 +16,9 @@ import (
"go.uber.org/multierr" "go.uber.org/multierr"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "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/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
) )
// NewTerminateCmd returns a new cobra.Command for the terminate command. // 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, func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.Handler, spinner spinnerInterf,
) error { ) error {
var stat state.ConstellationState var idFile clusterid.File
if err := fileHandler.ReadJSON(constants.StateFilename, &stat); err != nil { if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil {
return fmt.Errorf("reading Constellation state: %w", err) return err
} }
spinner.Start("Terminating", false) spinner.Start("Terminating", false)
err := terminator.Terminate(cmd.Context(), stat) err := terminator.Terminate(cmd.Context(), idFile.CloudProvider)
spinner.Stop() spinner.Stop()
if err != nil { if err != nil {
return fmt.Errorf("terminating Constellation cluster: %w", err) 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.") cmd.Println("Your Constellation cluster was terminated successfully.")
var retErr error 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) { 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)) 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" "errors"
"testing" "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/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -46,47 +47,46 @@ func TestTerminateCmdArgumentValidation(t *testing.T) {
} }
func TestTerminate(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() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) 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.WGQuickConfigFilename, []byte{1, 2}, file.OptNone))
require.NoError(fileHandler.Write(constants.ClusterIDsFileName, []byte{1, 2}, file.OptNone)) require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone))
require.NoError(fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone))
return fs return fs
} }
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
state state.ConstellationState idFile clusterid.File
setupFs func(*require.Assertions, state.ConstellationState) afero.Fs setupFs func(*require.Assertions, clusterid.File) afero.Fs
terminator spyCloudTerminator terminator spyCloudTerminator
wantErr bool wantErr bool
}{ }{
"success": { "success": {
state: state.ConstellationState{CloudProvider: "gcp"}, idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: setupFs, setupFs: setupFs,
terminator: &stubCloudTerminator{}, terminator: &stubCloudTerminator{},
}, },
"files to remove do not exist": { "files to remove do not exist": {
state: state.ConstellationState{CloudProvider: "gcp"}, idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs { setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone)) require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone))
return fs return fs
}, },
terminator: &stubCloudTerminator{}, terminator: &stubCloudTerminator{},
}, },
"terminate error": { "terminate error": {
state: state.ConstellationState{CloudProvider: "gcp"}, idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: setupFs, setupFs: setupFs,
terminator: &stubCloudTerminator{terminateErr: someErr}, terminator: &stubCloudTerminator{terminateErr: someErr},
wantErr: true, wantErr: true,
}, },
"missing state file": { "missing state file": {
state: state.ConstellationState{CloudProvider: "gcp"}, idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs { setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) fileHandler := file.NewHandler(fs)
require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone))
@ -97,9 +97,9 @@ func TestTerminate(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"remove file fails": { "remove file fails": {
state: state.ConstellationState{CloudProvider: "gcp"}, idFile: clusterid.File{CloudProvider: cloudprovider.GCP},
setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs { setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs {
fs := setupFs(require, state) fs := setupFs(require, idFile)
return afero.NewReadOnlyFs(fs) return afero.NewReadOnlyFs(fs)
}, },
terminator: &stubCloudTerminator{}, terminator: &stubCloudTerminator{},
@ -117,7 +117,7 @@ func TestTerminate(t *testing.T) {
cmd.SetErr(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{})
require.NotNil(tc.setupFs) 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{}) err := terminate(cmd, tc.terminator, fileHandler, nopSpinner{})
@ -126,8 +126,6 @@ func TestTerminate(t *testing.T) {
} else { } else {
assert.NoError(err) assert.NoError(err)
assert.True(tc.terminator.Called()) assert.True(tc.terminator.Called())
_, err := fileHandler.Stat(constants.StateFilename)
assert.Error(err)
_, err = fileHandler.Stat(constants.AdminConfFilename) _, err = fileHandler.Stat(constants.AdminConfFilename)
assert.Error(err) assert.Error(err)
_, err = fileHandler.Stat(constants.WGQuickConfigFilename) _, err = fileHandler.Stat(constants.WGQuickConfigFilename)

View File

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

View File

@ -16,6 +16,7 @@ import (
"strconv" "strconv"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
@ -44,7 +45,7 @@ func TestVerify(t *testing.T) {
configFlag string configFlag string
ownerIDFlag string ownerIDFlag string
clusterIDFlag string clusterIDFlag string
idFile *clusterIDsFile idFile *clusterid.File
wantEndpoint string wantEndpoint string
wantErr bool wantErr bool
}{ }{
@ -79,7 +80,7 @@ func TestVerify(t *testing.T) {
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
clusterIDFlag: zeroBase64, clusterIDFlag: zeroBase64,
protoClient: &stubVerifyClient{}, 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), wantEndpoint: "192.0.2.1:" + strconv.Itoa(constants.VerifyServiceNodePortGRPC),
}, },
"override endpoint from details file": { "override endpoint from details file": {
@ -87,7 +88,7 @@ func TestVerify(t *testing.T) {
nodeEndpointFlag: "192.0.2.2:1234", nodeEndpointFlag: "192.0.2.2:1234",
clusterIDFlag: zeroBase64, clusterIDFlag: zeroBase64,
protoClient: &stubVerifyClient{}, protoClient: &stubVerifyClient{},
idFile: &clusterIDsFile{IP: "192.0.2.1"}, idFile: &clusterid.File{IP: "192.0.2.1"},
wantEndpoint: "192.0.2.2:1234", wantEndpoint: "192.0.2.2:1234",
}, },
"invalid endpoint": { "invalid endpoint": {
@ -106,7 +107,7 @@ func TestVerify(t *testing.T) {
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
nodeEndpointFlag: "192.0.2.1:1234", nodeEndpointFlag: "192.0.2.1:1234",
protoClient: &stubVerifyClient{}, protoClient: &stubVerifyClient{},
idFile: &clusterIDsFile{OwnerID: zeroBase64}, idFile: &clusterid.File{OwnerID: zeroBase64},
wantEndpoint: "192.0.2.1:1234", wantEndpoint: "192.0.2.1:1234",
}, },
"config file not existing": { "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/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
install "github.com/hashicorp/hc-install" install "github.com/hashicorp/hc-install"
"github.com/hashicorp/hc-install/fs" "github.com/hashicorp/hc-install/fs"
@ -36,7 +35,6 @@ type Client struct {
provider cloudprovider.Provider provider cloudprovider.Provider
file file.Handler file file.Handler
state state.ConstellationState
remove func() remove func()
} }
@ -58,43 +56,38 @@ func New(ctx context.Context, provider cloudprovider.Provider) (*Client, error)
} }
// CreateCluster creates a Constellation cluster using Terraform. // 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 { if err := prepareWorkspace(c.file, c.provider); err != nil {
return err return "", err
} }
if err := c.tf.Init(ctx); err != nil { if err := c.tf.Init(ctx); err != nil {
return err return "", err
} }
if err := c.file.Write(terraformVarsFile, []byte(vars.String())); err != nil { if err := c.file.Write(terraformVarsFile, []byte(vars.String())); err != nil {
return err return "", err
} }
if err := c.tf.Apply(ctx); err != nil { if err := c.tf.Apply(ctx); err != nil {
return err return "", err
} }
tfState, err := c.tf.Show(ctx) tfState, err := c.tf.Show(ctx)
if err != nil { if err != nil {
return err return "", err
} }
ipOutput, ok := tfState.Values.Outputs["ip"] ipOutput, ok := tfState.Values.Outputs["ip"]
if !ok { if !ok {
return errors.New("no IP output found") return "", errors.New("no IP output found")
} }
ip, ok := ipOutput.Value.(string) ip, ok := ipOutput.Value.(string)
if !ok { if !ok {
return errors.New("invalid type in IP output: not a string") return "", errors.New("invalid type in IP output: not a string")
}
c.state = state.ConstellationState{
Name: name,
CloudProvider: c.provider.String(),
LoadBalancerIP: ip,
} }
return nil return ip, nil
} }
// DestroyInstances destroys a Constellation cluster using Terraform. // DestroyInstances destroys a Constellation cluster using Terraform.
@ -132,11 +125,6 @@ func (c *Client) CleanUpWorkspace() error {
return nil 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, // GetExecutable returns a Terraform executable either from the local filesystem,
// or downloads the latest version fulfilling the version constraint. // or downloads the latest version fulfilling the version constraint.
func GetExecutable(ctx context.Context, workingDir string) (terraform *tfexec.Terraform, remove func(), err error) { 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), 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 { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
assert.NoError(err) 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 package cloudprovider
import "strings" import (
"encoding/json"
"strings"
)
//go:generate stringer -type=Provider //go:generate stringer -type=Provider
@ -21,6 +24,21 @@ const (
QEMU 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. // FromString returns cloud provider from string.
func FromString(s string) Provider { func FromString(s string) Provider {
s = strings.ToLower(s) 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. // Filenames.
// //
StateFilename = "constellation-state.json"
ClusterIDsFileName = "constellation-id.json" ClusterIDsFileName = "constellation-id.json"
ConfigFilename = "constellation-conf.yaml" ConfigFilename = "constellation-conf.yaml"
LicenseFilename = "constellation.license" 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"`
}