cli: remove TF ApplyOutput dependency in CLI (#2323)

This commit is contained in:
Adrian Stobbe 2023-09-25 17:10:23 +02:00 committed by GitHub
parent 322c4aad10
commit 4680882708
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 174 deletions

View File

@ -33,7 +33,7 @@ type tfCommonClient interface {
type tfResourceClient interface { type tfResourceClient interface {
tfCommonClient tfCommonClient
ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.ApplyOutput, error) ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (state.Infrastructure, error)
ShowInfrastructure(ctx context.Context, provider cloudprovider.Provider) (state.Infrastructure, error) ShowInfrastructure(ctx context.Context, provider cloudprovider.Provider) (state.Infrastructure, error)
} }
@ -56,7 +56,7 @@ type tfIAMUpgradeClient interface {
type tfClusterUpgradeClient interface { type tfClusterUpgradeClient interface {
tfUpgradePlanner tfUpgradePlanner
ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.ApplyOutput, error) ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (state.Infrastructure, error)
} }
type libvirtRunner interface { type libvirtRunner interface {

View File

@ -45,12 +45,12 @@ type stubTerraformClient struct {
showErr error showErr error
} }
func (c *stubTerraformClient) ApplyCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (terraform.ApplyOutput, error) { func (c *stubTerraformClient) ApplyCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (state.Infrastructure, error) {
return terraform.ApplyOutput{ return state.Infrastructure{
IP: c.ip, ClusterEndpoint: c.ip,
Secret: c.initSecret, InitSecret: c.initSecret,
UID: c.uid, UID: c.uid,
Azure: &terraform.AzureApplyOutput{ Azure: &state.Azure{
AttestationURL: c.attestationURL, AttestationURL: c.attestationURL,
}, },
}, c.createClusterErr }, c.createClusterErr

View File

@ -13,6 +13,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/cli/internal/state"
"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/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -70,16 +71,16 @@ func (u *ClusterUpgrader) RestoreClusterWorkspace() error {
// ApplyClusterUpgrade applies the Terraform migrations planned by PlanClusterUpgrade. // ApplyClusterUpgrade applies the Terraform migrations planned by PlanClusterUpgrade.
// On success, the workspace of the Upgrader replaces the existing Terraform workspace. // On success, the workspace of the Upgrader replaces the existing Terraform workspace.
func (u *ClusterUpgrader) ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error) { func (u *ClusterUpgrader) ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (state.Infrastructure, error) {
tfOutput, err := u.tf.ApplyCluster(ctx, csp, u.logLevel) infraState, err := u.tf.ApplyCluster(ctx, csp, u.logLevel)
if err != nil { if err != nil {
return tfOutput, fmt.Errorf("terraform apply: %w", err) return infraState, fmt.Errorf("terraform apply: %w", err)
} }
if tfOutput.Azure != nil { if infraState.Azure != nil {
if err := u.policyPatcher.Patch(ctx, tfOutput.Azure.AttestationURL); err != nil { if err := u.policyPatcher.Patch(ctx, infraState.Azure.AttestationURL); err != nil {
return tfOutput, fmt.Errorf("patching policies: %w", err) return infraState, fmt.Errorf("patching policies: %w", err)
} }
} }
return tfOutput, nil return infraState, nil
} }

View File

@ -12,6 +12,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/state"
"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/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -194,8 +195,8 @@ func (t *tfClusterUpgradeStub) ShowPlan(_ context.Context, _ terraform.LogLevel,
return t.showErr return t.showErr
} }
func (t *tfClusterUpgradeStub) ApplyCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (terraform.ApplyOutput, error) { func (t *tfClusterUpgradeStub) ApplyCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (state.Infrastructure, error) {
return terraform.ApplyOutput{}, t.applyErr return state.Infrastructure{}, t.applyErr
} }
func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error { func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error {

View File

@ -80,20 +80,20 @@ func (c *Creator) Create(ctx context.Context, opts CreateOptions) (state.Infrast
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
var tfOutput terraform.ApplyOutput var infraState state.Infrastructure
switch opts.Provider { switch opts.Provider {
case cloudprovider.AWS: case cloudprovider.AWS:
tfOutput, err = c.createAWS(ctx, cl, opts) infraState, err = c.createAWS(ctx, cl, opts)
case cloudprovider.GCP: case cloudprovider.GCP:
tfOutput, err = c.createGCP(ctx, cl, opts) infraState, err = c.createGCP(ctx, cl, opts)
case cloudprovider.Azure: case cloudprovider.Azure:
tfOutput, err = c.createAzure(ctx, cl, opts) infraState, err = c.createAzure(ctx, cl, opts)
case cloudprovider.OpenStack: case cloudprovider.OpenStack:
tfOutput, err = c.createOpenStack(ctx, cl, opts) infraState, err = c.createOpenStack(ctx, cl, opts)
case cloudprovider.QEMU: case cloudprovider.QEMU:
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
return state.Infrastructure{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH) return state.Infrastructure{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
@ -104,7 +104,7 @@ func (c *Creator) Create(ctx context.Context, opts CreateOptions) (state.Infrast
CreateOptions: opts, CreateOptions: opts,
} }
tfOutput, err = c.createQEMU(ctx, cl, lv, qemuOpts) infraState, err = c.createQEMU(ctx, cl, lv, qemuOpts)
default: default:
return state.Infrastructure{}, fmt.Errorf("unsupported cloud provider: %s", opts.Provider) return state.Infrastructure{}, fmt.Errorf("unsupported cloud provider: %s", opts.Provider)
} }
@ -112,46 +112,46 @@ func (c *Creator) Create(ctx context.Context, opts CreateOptions) (state.Infrast
if err != nil { if err != nil {
return state.Infrastructure{}, fmt.Errorf("creating cluster: %w", err) return state.Infrastructure{}, fmt.Errorf("creating cluster: %w", err)
} }
return terraform.ConvertToInfrastructure(tfOutput), nil return infraState, nil
} }
func (c *Creator) createAWS(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput terraform.ApplyOutput, retErr error) { func (c *Creator) createAWS(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput state.Infrastructure, retErr error) {
vars := awsTerraformVars(opts.Config, opts.image) vars := awsTerraformVars(opts.Config, opts.image)
tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.AWS, vars, c.out, opts.TFLogLevel) tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.AWS, vars, c.out, opts.TFLogLevel)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
return tfOutput, nil return tfOutput, nil
} }
func (c *Creator) createGCP(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput terraform.ApplyOutput, retErr error) { func (c *Creator) createGCP(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput state.Infrastructure, retErr error) {
vars := gcpTerraformVars(opts.Config, opts.image) vars := gcpTerraformVars(opts.Config, opts.image)
tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.GCP, vars, c.out, opts.TFLogLevel) tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.GCP, vars, c.out, opts.TFLogLevel)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
return tfOutput, nil return tfOutput, nil
} }
func (c *Creator) createAzure(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput terraform.ApplyOutput, retErr error) { func (c *Creator) createAzure(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput state.Infrastructure, retErr error) {
vars := azureTerraformVars(opts.Config, opts.image) vars := azureTerraformVars(opts.Config, opts.image)
tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.Azure, vars, c.out, opts.TFLogLevel) tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.Azure, vars, c.out, opts.TFLogLevel)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
if vars.GetCreateMAA() { if vars.GetCreateMAA() {
// Patch the attestation policy to allow the cluster to boot while having secure boot disabled. // Patch the attestation policy to allow the cluster to boot while having secure boot disabled.
if tfOutput.Azure == nil { if tfOutput.Azure == nil {
return terraform.ApplyOutput{}, errors.New("no Terraform Azure output found") return state.Infrastructure{}, errors.New("no Terraform Azure output found")
} }
if err := c.policyPatcher.Patch(ctx, tfOutput.Azure.AttestationURL); err != nil { if err := c.policyPatcher.Patch(ctx, tfOutput.Azure.AttestationURL); err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
} }
@ -189,12 +189,12 @@ func normalizeAzureURIs(vars *terraform.AzureClusterVariables) *terraform.AzureC
return vars return vars
} }
func (c *Creator) createOpenStack(ctx context.Context, cl tfResourceClient, opts CreateOptions) (tfOutput terraform.ApplyOutput, retErr error) { func (c *Creator) createOpenStack(ctx context.Context, cl tfResourceClient, opts CreateOptions) (infraState state.Infrastructure, retErr error) {
if os.Getenv("CONSTELLATION_OPENSTACK_DEV") != "1" { if os.Getenv("CONSTELLATION_OPENSTACK_DEV") != "1" {
return terraform.ApplyOutput{}, errors.New("Constellation must be fine-tuned to your OpenStack deployment. Please create an issue or contact Edgeless Systems at https://edgeless.systems/contact/") return state.Infrastructure{}, errors.New("Constellation must be fine-tuned to your OpenStack deployment. Please create an issue or contact Edgeless Systems at https://edgeless.systems/contact/")
} }
if _, hasOSAuthURL := os.LookupEnv("OS_AUTH_URL"); !hasOSAuthURL && opts.Config.Provider.OpenStack.Cloud == "" { if _, hasOSAuthURL := os.LookupEnv("OS_AUTH_URL"); !hasOSAuthURL && opts.Config.Provider.OpenStack.Cloud == "" {
return terraform.ApplyOutput{}, errors.New( return state.Infrastructure{}, errors.New(
"neither environment variable OS_AUTH_URL nor cloud name for \"clouds.yaml\" is set. OpenStack authentication requires a set of " + "neither environment variable OS_AUTH_URL nor cloud name for \"clouds.yaml\" is set. OpenStack authentication requires a set of " +
"OS_* environment variables that are typically sourced into the current shell with an openrc file " + "OS_* environment variables that are typically sourced into the current shell with an openrc file " +
"or a cloud name for \"clouds.yaml\". " + "or a cloud name for \"clouds.yaml\". " +
@ -204,23 +204,23 @@ func (c *Creator) createOpenStack(ctx context.Context, cl tfResourceClient, opts
vars := openStackTerraformVars(opts.Config, opts.image) vars := openStackTerraformVars(opts.Config, opts.image)
tfOutput, err := runTerraformCreate(ctx, cl, cloudprovider.OpenStack, vars, c.out, opts.TFLogLevel) infraState, err := runTerraformCreate(ctx, cl, cloudprovider.OpenStack, vars, c.out, opts.TFLogLevel)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
return tfOutput, nil return infraState, nil
} }
func runTerraformCreate(ctx context.Context, cl tfResourceClient, provider cloudprovider.Provider, vars terraform.Variables, outWriter io.Writer, loglevel terraform.LogLevel) (output terraform.ApplyOutput, retErr error) { func runTerraformCreate(ctx context.Context, cl tfResourceClient, provider cloudprovider.Provider, vars terraform.Variables, outWriter io.Writer, loglevel terraform.LogLevel) (output state.Infrastructure, retErr error) {
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(provider.String())), vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(provider.String())), vars); err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
defer rollbackOnError(outWriter, &retErr, &rollbackerTerraform{client: cl}, loglevel) defer rollbackOnError(outWriter, &retErr, &rollbackerTerraform{client: cl}, loglevel)
tfOutput, err := cl.ApplyCluster(ctx, provider, loglevel) tfOutput, err := cl.ApplyCluster(ctx, provider, loglevel)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
return tfOutput, nil return tfOutput, nil
@ -231,7 +231,7 @@ type qemuCreateOptions struct {
CreateOptions CreateOptions
} }
func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvirtRunner, opts qemuCreateOptions) (tfOutput terraform.ApplyOutput, retErr error) { func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvirtRunner, opts qemuCreateOptions) (tfOutput state.Infrastructure, retErr error) {
qemuRollbacker := &rollbackerQEMU{client: cl, libvirt: lv, createdWorkspace: false} qemuRollbacker := &rollbackerQEMU{client: cl, libvirt: lv, createdWorkspace: false}
defer rollbackOnError(c.out, &retErr, qemuRollbacker, opts.TFLogLevel) defer rollbackOnError(c.out, &retErr, qemuRollbacker, opts.TFLogLevel)
@ -239,7 +239,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvir
downloader := c.newRawDownloader() downloader := c.newRawDownloader()
imagePath, err := downloader.Download(ctx, c.out, false, opts.source, opts.Config.Image) imagePath, err := downloader.Download(ctx, c.out, false, opts.source, opts.Config.Image)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, fmt.Errorf("download raw image: %w", err) return state.Infrastructure{}, fmt.Errorf("download raw image: %w", err)
} }
libvirtURI := opts.Config.Provider.QEMU.LibvirtURI libvirtURI := opts.Config.Provider.QEMU.LibvirtURI
@ -249,7 +249,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvir
// 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, opts.Config.Name, opts.Config.Provider.QEMU.LibvirtContainerImage); err != nil { if err := lv.Start(ctx, opts.Config.Name, opts.Config.Provider.QEMU.LibvirtContainerImage); err != nil {
return terraform.ApplyOutput{}, fmt.Errorf("start libvirt container: %w", err) return state.Infrastructure{}, fmt.Errorf("start libvirt container: %w", err)
} }
libvirtURI = libvirt.LibvirtTCPConnectURI libvirtURI = libvirt.LibvirtTCPConnectURI
@ -265,11 +265,11 @@ func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvir
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 terraform.ApplyOutput{}, err return state.Infrastructure{}, err
} }
libvirtSocketPath = unixURI.Query().Get("socket") libvirtSocketPath = unixURI.Query().Get("socket")
if libvirtSocketPath == "" { if libvirtSocketPath == "" {
return terraform.ApplyOutput{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI) return state.Infrastructure{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI)
} }
} }
@ -285,7 +285,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvir
} }
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.QEMU.String())), vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.QEMU.String())), vars); err != nil {
return terraform.ApplyOutput{}, fmt.Errorf("prepare workspace: %w", err) return state.Infrastructure{}, fmt.Errorf("prepare workspace: %w", err)
} }
// Allow rollback of QEMU Terraform workspace from this point on // Allow rollback of QEMU Terraform workspace from this point on
@ -293,7 +293,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvir
tfOutput, err = cl.ApplyCluster(ctx, opts.Provider, opts.TFLogLevel) tfOutput, err = cl.ApplyCluster(ctx, opts.Provider, opts.TFLogLevel)
if err != nil { if err != nil {
return terraform.ApplyOutput{}, fmt.Errorf("create cluster: %w", err) return state.Infrastructure{}, fmt.Errorf("create cluster: %w", err)
} }
return tfOutput, nil return tfOutput, nil

View File

@ -318,15 +318,14 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, conf *config.Conf
} }
} }
u.log.Debugf("Applying Terraform migrations") u.log.Debugf("Applying Terraform migrations")
tfOutput, err := u.clusterUpgrader.ApplyClusterUpgrade(cmd.Context(), conf.GetProvider()) infraState, err := u.clusterUpgrader.ApplyClusterUpgrade(cmd.Context(), conf.GetProvider())
res = terraform.ConvertToInfrastructure(tfOutput)
if err != nil { if err != nil {
return res, fmt.Errorf("applying terraform migrations: %w", err) return infraState, fmt.Errorf("applying terraform migrations: %w", err)
} }
// Apply possible updates to cluster ID file // Apply possible updates to cluster ID file
if err := updateClusterIDFile(tfOutput, u.fileHandler); err != nil { if err := updateClusterIDFile(infraState, u.fileHandler); err != nil {
return res, fmt.Errorf("merging cluster ID files: %w", err) return infraState, fmt.Errorf("merging cluster ID files: %w", err)
} }
cmd.Printf("Terraform migrations applied successfully and output written to: %s\n"+ cmd.Printf("Terraform migrations applied successfully and output written to: %s\n"+
@ -588,15 +587,15 @@ func parseUpgradeApplyFlags(cmd *cobra.Command) (upgradeApplyFlags, error) {
}, nil }, nil
} }
func updateClusterIDFile(tfOutput terraform.ApplyOutput, fileHandler file.Handler) error { func updateClusterIDFile(infraState state.Infrastructure, fileHandler file.Handler) error {
newIDFile := clusterid.File{ newIDFile := clusterid.File{
InitSecret: []byte(tfOutput.Secret), InitSecret: []byte(infraState.InitSecret),
IP: tfOutput.IP, IP: infraState.ClusterEndpoint,
APIServerCertSANs: tfOutput.APIServerCertSANs, APIServerCertSANs: infraState.APIServerCertSANs,
UID: tfOutput.UID, UID: infraState.UID,
} }
if tfOutput.Azure != nil { if infraState.Azure != nil {
newIDFile.AttestationURL = tfOutput.Azure.AttestationURL newIDFile.AttestationURL = infraState.Azure.AttestationURL
} }
idFile := &clusterid.File{} idFile := &clusterid.File{}
@ -650,6 +649,6 @@ type kubernetesUpgrader interface {
type clusterUpgrader interface { type clusterUpgrader interface {
PlanClusterUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error) PlanClusterUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error)
ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error) ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (state.Infrastructure, error)
RestoreClusterWorkspace() error RestoreClusterWorkspace() error
} }

View File

@ -277,8 +277,8 @@ func (u stubTerraformUpgrader) PlanClusterUpgrade(_ context.Context, _ io.Writer
return u.terraformDiff, u.planTerraformErr return u.terraformDiff, u.planTerraformErr
} }
func (u stubTerraformUpgrader) ApplyClusterUpgrade(_ context.Context, _ cloudprovider.Provider) (terraform.ApplyOutput, error) { func (u stubTerraformUpgrader) ApplyClusterUpgrade(_ context.Context, _ cloudprovider.Provider) (state.Infrastructure, error) {
return terraform.ApplyOutput{}, u.applyTerraformErr return state.Infrastructure{}, u.applyTerraformErr
} }
func (u stubTerraformUpgrader) RestoreClusterWorkspace() error { func (u stubTerraformUpgrader) RestoreClusterWorkspace() error {
@ -294,9 +294,9 @@ func (m *mockTerraformUpgrader) PlanClusterUpgrade(ctx context.Context, w io.Wri
return args.Bool(0), args.Error(1) return args.Bool(0), args.Error(1)
} }
func (m *mockTerraformUpgrader) ApplyClusterUpgrade(ctx context.Context, provider cloudprovider.Provider) (terraform.ApplyOutput, error) { func (m *mockTerraformUpgrader) ApplyClusterUpgrade(ctx context.Context, provider cloudprovider.Provider) (state.Infrastructure, error) {
args := m.Called(ctx, provider) args := m.Called(ctx, provider)
return args.Get(0).(terraform.ApplyOutput), args.Error(1) return args.Get(0).(state.Infrastructure), args.Error(1)
} }
func (m *mockTerraformUpgrader) RestoreClusterWorkspace() error { func (m *mockTerraformUpgrader) RestoreClusterWorkspace() error {

View File

@ -173,96 +173,58 @@ func (c *Client) ShowIAM(ctx context.Context, provider cloudprovider.Provider) (
// ShowInfrastructure reads the state of Constellation cluster resources from Terraform. // ShowInfrastructure reads the state of Constellation cluster resources from Terraform.
func (c *Client) ShowInfrastructure(ctx context.Context, provider cloudprovider.Provider) (state.Infrastructure, error) { func (c *Client) ShowInfrastructure(ctx context.Context, provider cloudprovider.Provider) (state.Infrastructure, error) {
tfOutput, err := c.ShowCluster(ctx, provider)
if err != nil {
return state.Infrastructure{}, err
}
return ConvertToInfrastructure(tfOutput), nil
}
// ConvertToInfrastructure converts the Terraform output of a cluster creation or apply operation to a state.Infrastructure.
func ConvertToInfrastructure(applyOutput ApplyOutput) state.Infrastructure {
var infra state.Infrastructure
infra.UID = applyOutput.UID
infra.ClusterEndpoint = applyOutput.IP
infra.InitSecret = applyOutput.Secret
infra.APIServerCertSANs = applyOutput.APIServerCertSANs
if applyOutput.Azure != nil {
infra.Azure = &state.Azure{
ResourceGroup: applyOutput.Azure.ResourceGroup,
SubscriptionID: applyOutput.Azure.SubscriptionID,
UserAssignedIdentity: applyOutput.Azure.UserAssignedIdentity,
NetworkSecurityGroupName: applyOutput.Azure.NetworkSecurityGroupName,
LoadBalancerName: applyOutput.Azure.LoadBalancerName,
AttestationURL: applyOutput.Azure.AttestationURL,
}
}
if applyOutput.GCP != nil {
infra.GCP = &state.GCP{
ProjectID: applyOutput.GCP.ProjectID,
IPCidrNode: applyOutput.GCP.IPCidrNode,
IPCidrPod: applyOutput.GCP.IPCidrPod,
}
}
return infra
}
// ShowCluster reads the state of Constellation cluster resources from Terraform.
func (c *Client) ShowCluster(ctx context.Context, provider cloudprovider.Provider) (ApplyOutput, error) {
tfState, err := c.tf.Show(ctx) tfState, err := c.tf.Show(ctx)
if err != nil { if err != nil {
return ApplyOutput{}, fmt.Errorf("terraform show: %w", err) return state.Infrastructure{}, fmt.Errorf("terraform show: %w", err)
} }
if tfState.Values == nil { if tfState.Values == nil {
return ApplyOutput{}, errors.New("terraform show: no values returned") return state.Infrastructure{}, errors.New("terraform show: no values returned")
} }
ipOutput, ok := tfState.Values.Outputs["ip"] ipOutput, ok := tfState.Values.Outputs["ip"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no IP output found") return state.Infrastructure{}, errors.New("no IP output found")
} }
ip, ok := ipOutput.Value.(string) ip, ok := ipOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in IP output: not a string") return state.Infrastructure{}, errors.New("invalid type in IP output: not a string")
} }
apiServerCertSANsOutput, ok := tfState.Values.Outputs["api_server_cert_sans"] apiServerCertSANsOutput, ok := tfState.Values.Outputs["api_server_cert_sans"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no api_server_cert_sans output found") return state.Infrastructure{}, errors.New("no api_server_cert_sans output found")
} }
apiServerCertSANsUntyped, ok := apiServerCertSANsOutput.Value.([]any) apiServerCertSANsUntyped, ok := apiServerCertSANsOutput.Value.([]any)
if !ok { if !ok {
return ApplyOutput{}, fmt.Errorf("invalid type in api_server_cert_sans output: %s is not a list of elements", apiServerCertSANsOutput.Type.FriendlyName()) return state.Infrastructure{}, fmt.Errorf("invalid type in api_server_cert_sans output: %s is not a list of elements", apiServerCertSANsOutput.Type.FriendlyName())
} }
apiServerCertSANs, err := toStringSlice(apiServerCertSANsUntyped) apiServerCertSANs, err := toStringSlice(apiServerCertSANsUntyped)
if err != nil { if err != nil {
return ApplyOutput{}, fmt.Errorf("convert api_server_cert_sans output: %w", err) return state.Infrastructure{}, fmt.Errorf("convert api_server_cert_sans output: %w", err)
} }
secretOutput, ok := tfState.Values.Outputs["initSecret"] secretOutput, ok := tfState.Values.Outputs["initSecret"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no initSecret output found") return state.Infrastructure{}, errors.New("no initSecret output found")
} }
secret, ok := secretOutput.Value.(string) secret, ok := secretOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in initSecret output: not a string") return state.Infrastructure{}, errors.New("invalid type in initSecret output: not a string")
} }
uidOutput, ok := tfState.Values.Outputs["uid"] uidOutput, ok := tfState.Values.Outputs["uid"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no uid output found") return state.Infrastructure{}, errors.New("no uid output found")
} }
uid, ok := uidOutput.Value.(string) uid, ok := uidOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in uid output: not a string") return state.Infrastructure{}, errors.New("invalid type in uid output: not a string")
} }
res := ApplyOutput{ res := state.Infrastructure{
IP: ip, ClusterEndpoint: ip,
APIServerCertSANs: apiServerCertSANs, APIServerCertSANs: apiServerCertSANs,
Secret: secret, InitSecret: secret,
UID: uid, UID: uid,
} }
@ -270,32 +232,32 @@ func (c *Client) ShowCluster(ctx context.Context, provider cloudprovider.Provide
case cloudprovider.GCP: case cloudprovider.GCP:
gcpProjectOutput, ok := tfState.Values.Outputs["project"] gcpProjectOutput, ok := tfState.Values.Outputs["project"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no project output found") return state.Infrastructure{}, errors.New("no project output found")
} }
gcpProject, ok := gcpProjectOutput.Value.(string) gcpProject, ok := gcpProjectOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in project output: not a string") return state.Infrastructure{}, errors.New("invalid type in project output: not a string")
} }
cidrNodesOutput, ok := tfState.Values.Outputs["ip_cidr_nodes"] cidrNodesOutput, ok := tfState.Values.Outputs["ip_cidr_nodes"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no ip_cidr_nodes output found") return state.Infrastructure{}, errors.New("no ip_cidr_nodes output found")
} }
cidrNodes, ok := cidrNodesOutput.Value.(string) cidrNodes, ok := cidrNodesOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in ip_cidr_nodes output: not a string") return state.Infrastructure{}, errors.New("invalid type in ip_cidr_nodes output: not a string")
} }
cidrPodsOutput, ok := tfState.Values.Outputs["ip_cidr_pods"] cidrPodsOutput, ok := tfState.Values.Outputs["ip_cidr_pods"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no ip_cidr_pods output found") return state.Infrastructure{}, errors.New("no ip_cidr_pods output found")
} }
cidrPods, ok := cidrPodsOutput.Value.(string) cidrPods, ok := cidrPodsOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in ip_cidr_pods output: not a string") return state.Infrastructure{}, errors.New("invalid type in ip_cidr_pods output: not a string")
} }
res.GCP = &GCPApplyOutput{ res.GCP = &state.GCP{
ProjectID: gcpProject, ProjectID: gcpProject,
IPCidrNode: cidrNodes, IPCidrNode: cidrNodes,
IPCidrPod: cidrPods, IPCidrPod: cidrPods,
@ -303,57 +265,57 @@ func (c *Client) ShowCluster(ctx context.Context, provider cloudprovider.Provide
case cloudprovider.Azure: case cloudprovider.Azure:
attestationURLOutput, ok := tfState.Values.Outputs["attestationURL"] attestationURLOutput, ok := tfState.Values.Outputs["attestationURL"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no attestationURL output found") return state.Infrastructure{}, errors.New("no attestationURL output found")
} }
attestationURL, ok := attestationURLOutput.Value.(string) attestationURL, ok := attestationURLOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in attestationURL output: not a string") return state.Infrastructure{}, errors.New("invalid type in attestationURL output: not a string")
} }
azureUAMIOutput, ok := tfState.Values.Outputs["user_assigned_identity_client_id"] azureUAMIOutput, ok := tfState.Values.Outputs["user_assigned_identity_client_id"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no user_assigned_identity_client_id output found") return state.Infrastructure{}, errors.New("no user_assigned_identity_client_id output found")
} }
azureUAMI, ok := azureUAMIOutput.Value.(string) azureUAMI, ok := azureUAMIOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in user_assigned_identity_client_id output: not a string") return state.Infrastructure{}, errors.New("invalid type in user_assigned_identity_client_id output: not a string")
} }
rgOutput, ok := tfState.Values.Outputs["resource_group"] rgOutput, ok := tfState.Values.Outputs["resource_group"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no resource_group output found") return state.Infrastructure{}, errors.New("no resource_group output found")
} }
rg, ok := rgOutput.Value.(string) rg, ok := rgOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in resource_group output: not a string") return state.Infrastructure{}, errors.New("invalid type in resource_group output: not a string")
} }
subscriptionOutput, ok := tfState.Values.Outputs["subscription_id"] subscriptionOutput, ok := tfState.Values.Outputs["subscription_id"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no subscription_id output found") return state.Infrastructure{}, errors.New("no subscription_id output found")
} }
subscriptionID, ok := subscriptionOutput.Value.(string) subscriptionID, ok := subscriptionOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in subscription_id output: not a string") return state.Infrastructure{}, errors.New("invalid type in subscription_id output: not a string")
} }
networkSGNameOutput, ok := tfState.Values.Outputs["network_security_group_name"] networkSGNameOutput, ok := tfState.Values.Outputs["network_security_group_name"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no network_security_group_name output found") return state.Infrastructure{}, errors.New("no network_security_group_name output found")
} }
networkSGName, ok := networkSGNameOutput.Value.(string) networkSGName, ok := networkSGNameOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in network_security_group_name output: not a string") return state.Infrastructure{}, errors.New("invalid type in network_security_group_name output: not a string")
} }
loadBalancerNameOutput, ok := tfState.Values.Outputs["loadbalancer_name"] loadBalancerNameOutput, ok := tfState.Values.Outputs["loadbalancer_name"]
if !ok { if !ok {
return ApplyOutput{}, errors.New("no loadbalancer_name output found") return state.Infrastructure{}, errors.New("no loadbalancer_name output found")
} }
loadBalancerName, ok := loadBalancerNameOutput.Value.(string) loadBalancerName, ok := loadBalancerNameOutput.Value.(string)
if !ok { if !ok {
return ApplyOutput{}, errors.New("invalid type in loadbalancer_name output: not a string") return state.Infrastructure{}, errors.New("invalid type in loadbalancer_name output: not a string")
} }
res.Azure = &AzureApplyOutput{ res.Azure = &state.Azure{
ResourceGroup: rg, ResourceGroup: rg,
SubscriptionID: subscriptionID, SubscriptionID: subscriptionID,
UserAssignedIdentity: azureUAMI, UserAssignedIdentity: azureUAMI,
@ -386,11 +348,11 @@ func (c *Client) PrepareUpgradeWorkspace(path, backupDir string, vars Variables)
} }
// ApplyCluster applies the Terraform configuration of the workspace to create or upgrade a Constellation cluster. // ApplyCluster applies the Terraform configuration of the workspace to create or upgrade a Constellation cluster.
func (c *Client) ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel LogLevel) (ApplyOutput, error) { func (c *Client) ApplyCluster(ctx context.Context, provider cloudprovider.Provider, logLevel LogLevel) (state.Infrastructure, error) {
if err := c.apply(ctx, logLevel); err != nil { if err := c.apply(ctx, logLevel); err != nil {
return ApplyOutput{}, err return state.Infrastructure{}, err
} }
return c.ShowCluster(ctx, provider) return c.ShowInfrastructure(ctx, provider)
} }
// ApplyIAM applies the Terraform configuration of the workspace to create or upgrade an IAM configuration. // ApplyIAM applies the Terraform configuration of the workspace to create or upgrade an IAM configuration.
@ -547,35 +509,6 @@ type StateMigration struct {
Hook func(ctx context.Context, tfClient TFMigrator) error Hook func(ctx context.Context, tfClient TFMigrator) error
} }
// ApplyOutput contains the Terraform output values of a cluster creation
// or apply operation.
type ApplyOutput struct {
IP string
APIServerCertSANs []string
Secret string
UID string
GCP *GCPApplyOutput
Azure *AzureApplyOutput
}
// AzureApplyOutput contains the Terraform output values of a terraform apply operation on Microsoft Azure.
type AzureApplyOutput struct {
ResourceGroup string
SubscriptionID string
NetworkSecurityGroupName string
LoadBalancerName string
UserAssignedIdentity string
// AttestationURL is the URL of the attestation provider.
AttestationURL string
}
// GCPApplyOutput contains the Terraform output values of a terraform apply operation on GCP.
type GCPApplyOutput struct {
ProjectID string
IPCidrNode string
IPCidrPod string
}
// IAMOutput contains the output information of the Terraform IAM operations. // IAMOutput contains the output information of the Terraform IAM operations.
type IAMOutput struct { type IAMOutput struct {
GCP GCPIAMOutput GCP GCPIAMOutput

View File

@ -449,18 +449,18 @@ func TestCreateCluster(t *testing.T) {
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
require.NoError(c.PrepareWorkspace(path, tc.vars)) require.NoError(c.PrepareWorkspace(path, tc.vars))
tfOutput, err := c.ApplyCluster(context.Background(), tc.provider, LogLevelDebug) infraState, err := c.ApplyCluster(context.Background(), tc.provider, LogLevelDebug)
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", tfOutput.IP) assert.Equal("192.0.2.100", infraState.ClusterEndpoint)
assert.Equal("initSecret", tfOutput.Secret) assert.Equal("initSecret", infraState.InitSecret)
assert.Equal("12345abc", tfOutput.UID) assert.Equal("12345abc", infraState.UID)
if tc.provider == cloudprovider.Azure { if tc.provider == cloudprovider.Azure {
assert.Equal(tc.expectedAttestationURL, tfOutput.Azure.AttestationURL) assert.Equal(tc.expectedAttestationURL, infraState.Azure.AttestationURL)
} }
}) })
} }