constellation/cli/internal/cloudcmd/apply.go

163 lines
5.4 KiB
Go
Raw Normal View History

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudcmd
import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/constellation/state"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/internal/maa"
)
const (
// WithRollbackOnError indicates a rollback should be performed on error.
WithRollbackOnError RollbackBehavior = true
// WithoutRollbackOnError indicates a rollback should not be performed on error.
WithoutRollbackOnError RollbackBehavior = false
)
// RollbackBehavior is a boolean flag that indicates whether a rollback should be performed.
type RollbackBehavior bool
// Applier creates or updates cloud resources.
type Applier struct {
fileHandler file.Handler
imageFetcher imageFetcher
libvirtRunner libvirtRunner
rawDownloader rawDownloader
policyPatcher policyPatcher
terraformClient tfResourceClient
logLevel terraform.LogLevel
workingDir string
backupDir string
out io.Writer
}
// NewApplier creates a new Applier.
func NewApplier(
ctx context.Context, out io.Writer, workingDir, backupDir string,
logLevel terraform.LogLevel, fileHandler file.Handler,
) (*Applier, func(), error) {
tfClient, err := terraform.New(ctx, workingDir)
if err != nil {
return nil, nil, fmt.Errorf("setting up terraform client: %w", err)
}
return &Applier{
fileHandler: fileHandler,
imageFetcher: imagefetcher.New(),
libvirtRunner: libvirt.New(),
rawDownloader: imagefetcher.NewDownloader(),
policyPatcher: maa.NewAzurePolicyPatcher(),
terraformClient: tfClient,
logLevel: logLevel,
workingDir: workingDir,
backupDir: backupDir,
out: out,
}, tfClient.RemoveInstaller, nil
}
// Plan plans the given configuration and prepares the Terraform workspace.
func (a *Applier) Plan(ctx context.Context, conf *config.Config) (bool, error) {
vars, err := a.terraformApplyVars(ctx, conf)
if err != nil {
return false, fmt.Errorf("creating terraform variables: %w", err)
}
return plan(
ctx, a.terraformClient, a.fileHandler, a.out, a.logLevel, vars,
filepath.Join(constants.TerraformEmbeddedDir, strings.ToLower(conf.GetProvider().String())),
a.workingDir,
filepath.Join(a.backupDir, constants.TerraformUpgradeBackupDir),
)
}
// Apply applies the prepared configuration by creating or updating cloud resources.
func (a *Applier) Apply(
ctx context.Context, csp cloudprovider.Provider, attestation variant.Variant, withRollback RollbackBehavior,
) (infra state.Infrastructure, retErr error) {
if withRollback {
var rollbacker rollbacker
switch csp {
case cloudprovider.QEMU:
rollbacker = &rollbackerQEMU{client: a.terraformClient, libvirt: a.libvirtRunner}
default:
rollbacker = &rollbackerTerraform{client: a.terraformClient}
}
defer rollbackOnError(a.out, &retErr, rollbacker, a.logLevel)
}
infraState, err := a.terraformClient.ApplyCluster(ctx, csp, a.logLevel)
if err != nil {
return infraState, fmt.Errorf("terraform apply: %w", err)
}
if csp == cloudprovider.Azure && attestation.Equal(variant.AzureSEVSNP{}) && infraState.Azure != nil {
if err := a.policyPatcher.Patch(ctx, infraState.Azure.AttestationURL); err != nil {
return infraState, fmt.Errorf("patching policies: %w", err)
}
}
return infraState, nil
}
// RestoreWorkspace rolls back the existing workspace to the backup directory created when planning an action,
// and the user decides to not apply it.
// Note that this will not apply the restored state from the backup.
func (a *Applier) RestoreWorkspace() error {
return restoreBackup(a.fileHandler, a.workingDir, filepath.Join(a.backupDir, constants.TerraformUpgradeBackupDir))
}
// WorkingDirIsEmpty returns true if the working directory of the Applier is empty.
func (a *Applier) WorkingDirIsEmpty() (bool, error) {
return a.fileHandler.IsEmpty(a.workingDir)
}
func (a *Applier) terraformApplyVars(ctx context.Context, conf *config.Config) (terraform.Variables, error) {
imageRef, err := a.imageFetcher.FetchReference(
ctx,
conf.GetProvider(),
conf.GetAttestationConfig().GetVariant(),
terraform: Azure Marketplace image support (#2651) * terraform: add Azure marketplace variable Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * config: add Azure marketplace variable Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * cli: use Terraform variables from config Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * terraform: pass down marketplace variable * image: pad Azure images to 1GiB * terraform: add version attribute to marketplace image * semver: allow versions to be exported without prefix * cli: boolean var to use marketplace images * config: remove dive key * dev-docs: add instructions on how to use marketplace images * terraform: fix unit test * terraform: only fetch image for non-marketplace images * mpimage: refactor image selection Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [remove] increase minor version for image build Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * terraform: ignore changes to source_image_reference on upgrade * operator: add support for parsing Azure marketplace images Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * upgrade: fix imagefetcher call * docs: add info about azure marketplace * image: ensure more than 1GiB in size * image: test to pad to 2GiB * version: change back to v2.14.0-pre * image: GPT-conformant image size padding * [remove] increase version * mpimage: inline prefix func Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * ci: add marketplace image e2e test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [remove] register workflow * ci: fix workflow name * ci: only allow azure test * cli: add marketplace image input to interface * cli: fix argument passing * version: roll back to v2.14.0 * ci: add force-flag support * Update docs/docs/overview/license.md * Update dev-docs/workflows/marketplace-images.md Co-authored-by: Moritz Eckert <m1gh7ym0@gmail.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Moritz Eckert <m1gh7ym0@gmail.com> Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
2023-12-08 08:40:31 -05:00
conf.Image, conf.GetRegion(), conf.UseMarketplaceImage(),
)
if err != nil {
return nil, fmt.Errorf("fetching image reference: %w", err)
}
switch conf.GetProvider() {
case cloudprovider.AWS:
return awsTerraformVars(conf, imageRef), nil
case cloudprovider.Azure:
terraform: Azure Marketplace image support (#2651) * terraform: add Azure marketplace variable Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * config: add Azure marketplace variable Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * cli: use Terraform variables from config Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * terraform: pass down marketplace variable * image: pad Azure images to 1GiB * terraform: add version attribute to marketplace image * semver: allow versions to be exported without prefix * cli: boolean var to use marketplace images * config: remove dive key * dev-docs: add instructions on how to use marketplace images * terraform: fix unit test * terraform: only fetch image for non-marketplace images * mpimage: refactor image selection Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [remove] increase minor version for image build Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * terraform: ignore changes to source_image_reference on upgrade * operator: add support for parsing Azure marketplace images Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * upgrade: fix imagefetcher call * docs: add info about azure marketplace * image: ensure more than 1GiB in size * image: test to pad to 2GiB * version: change back to v2.14.0-pre * image: GPT-conformant image size padding * [remove] increase version * mpimage: inline prefix func Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * ci: add marketplace image e2e test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [remove] register workflow * ci: fix workflow name * ci: only allow azure test * cli: add marketplace image input to interface * cli: fix argument passing * version: roll back to v2.14.0 * ci: add force-flag support * Update docs/docs/overview/license.md * Update dev-docs/workflows/marketplace-images.md Co-authored-by: Moritz Eckert <m1gh7ym0@gmail.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Moritz Eckert <m1gh7ym0@gmail.com> Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
2023-12-08 08:40:31 -05:00
return azureTerraformVars(conf, imageRef)
case cloudprovider.GCP:
return gcpTerraformVars(conf, imageRef), nil
case cloudprovider.OpenStack:
return openStackTerraformVars(conf, imageRef)
case cloudprovider.QEMU:
return qemuTerraformVars(ctx, conf, imageRef, a.libvirtRunner, a.rawDownloader)
default:
return nil, fmt.Errorf("unsupported provider: %s", conf.GetProvider())
}
}
// policyPatcher interacts with the CSP (currently only applies for Azure) to update the attestation policy.
type policyPatcher interface {
Patch(ctx context.Context, attestationURL string) error
}