constellation/cli/internal/cloudcmd/tfvars.go
Daniel Weiße 0a911806d1
cli: remove/refactor upgrade package (#2266)
* Move IAM migration client to cloudcmd package

* Move Terraform Cluster upgrade client to cloudcmd package

* Use hcl for creating Terraform IAM variables files

* Unify terraform upgrade code

* Rename some cloudcmd files for better clarity

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
2023-08-23 10:35:42 +02:00

258 lines
10 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cloudcmd
import (
"fmt"
"path/filepath"
"strings"
"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/file"
"github.com/edgelesssys/constellation/v2/internal/role"
)
// TerraformUpgradeVars returns variables required to execute the Terraform scripts.
func TerraformUpgradeVars(conf *config.Config) (terraform.Variables, error) {
// Note that we don't pass any real image as imageRef, as we ignore changes to the image in the terraform.
// The image is updates via our operator.
// Still, the terraform variable verification must accept the values.
// For AWS, we enforce some basic constraints on the image variable.
// For Azure, the provider enforces the format below.
// For GCP, any placeholder works.
var vars terraform.Variables
switch conf.GetProvider() {
case cloudprovider.AWS:
vars = awsTerraformVars(conf, "ami-placeholder")
case cloudprovider.Azure:
vars = azureTerraformVars(conf, "/communityGalleries/myGalleryName/images/myImageName/versions/latest")
case cloudprovider.GCP:
vars = gcpTerraformVars(conf, "placeholder")
default:
return nil, fmt.Errorf("unsupported provider: %s", conf.GetProvider())
}
return vars, nil
}
// TerraformIAMUpgradeVars returns variables required to execute IAM upgrades with Terraform.
func TerraformIAMUpgradeVars(conf *config.Config, fileHandler file.Handler) (terraform.Variables, error) {
// Load the tfvars of the existing IAM workspace.
// Ideally we would only load values from the config file, but this currently does not hold all values required.
// This should be refactored in the future.
oldVarBytes, err := fileHandler.Read(filepath.Join(constants.TerraformIAMWorkingDir, "terraform.tfvars"))
if err != nil {
return nil, fmt.Errorf("reading existing IAM workspace: %w", err)
}
var vars terraform.Variables
switch conf.GetProvider() {
case cloudprovider.AWS:
var oldVars terraform.AWSIAMVariables
if err := terraform.VariablesFromBytes(oldVarBytes, &oldVars); err != nil {
return nil, fmt.Errorf("parsing existing IAM workspace: %w", err)
}
vars = awsTerraformIAMVars(conf, oldVars)
case cloudprovider.Azure:
var oldVars terraform.AzureIAMVariables
if err := terraform.VariablesFromBytes(oldVarBytes, &oldVars); err != nil {
return nil, fmt.Errorf("parsing existing IAM workspace: %w", err)
}
vars = azureTerraformIAMVars(conf, oldVars)
case cloudprovider.GCP:
var oldVars terraform.GCPIAMVariables
if err := terraform.VariablesFromBytes(oldVarBytes, &oldVars); err != nil {
return nil, fmt.Errorf("parsing existing IAM workspace: %w", err)
}
vars = gcpTerraformIAMVars(conf, oldVars)
default:
return nil, fmt.Errorf("unsupported provider: %s", conf.GetProvider())
}
return vars, nil
}
// awsTerraformVars provides variables required to execute the Terraform scripts.
// It should be the only place to declare the AWS variables.
func awsTerraformVars(conf *config.Config, imageRef string) *terraform.AWSClusterVariables {
nodeGroups := make(map[string]terraform.AWSNodeGroup)
for groupName, group := range conf.NodeGroups {
nodeGroups[groupName] = terraform.AWSNodeGroup{
Role: role.FromString(group.Role).TFString(),
StateDiskSizeGB: group.StateDiskSizeGB,
InitialCount: group.InitialCount,
Zone: group.Zone,
InstanceType: group.InstanceType,
DiskType: group.StateDiskType,
}
}
return &terraform.AWSClusterVariables{
Name: conf.Name,
NodeGroups: nodeGroups,
Region: conf.Provider.AWS.Region,
Zone: conf.Provider.AWS.Zone,
AMIImageID: imageRef,
IAMProfileControlPlane: conf.Provider.AWS.IAMProfileControlPlane,
IAMProfileWorkerNodes: conf.Provider.AWS.IAMProfileWorkerNodes,
Debug: conf.IsDebugCluster(),
EnableSNP: conf.GetAttestationConfig().GetVariant().Equal(variant.AWSSEVSNP{}),
CustomEndpoint: conf.CustomEndpoint,
}
}
func awsTerraformIAMVars(conf *config.Config, oldVars terraform.AWSIAMVariables) *terraform.AWSIAMVariables {
return &terraform.AWSIAMVariables{
Region: conf.Provider.AWS.Region,
Prefix: oldVars.Prefix,
}
}
// azureTerraformVars provides variables required to execute the Terraform scripts.
// It should be the only place to declare the Azure variables.
func azureTerraformVars(conf *config.Config, imageRef string) *terraform.AzureClusterVariables {
nodeGroups := make(map[string]terraform.AzureNodeGroup)
for groupName, group := range conf.NodeGroups {
zones := strings.Split(group.Zone, ",")
if len(zones) == 0 || (len(zones) == 1 && zones[0] == "") {
zones = nil
}
nodeGroups[groupName] = terraform.AzureNodeGroup{
Role: role.FromString(group.Role).TFString(),
InitialCount: group.InitialCount,
InstanceType: group.InstanceType,
DiskSizeGB: group.StateDiskSizeGB,
DiskType: group.StateDiskType,
Zones: zones,
}
}
vars := &terraform.AzureClusterVariables{
Name: conf.Name,
NodeGroups: nodeGroups,
Location: conf.Provider.Azure.Location,
ImageID: imageRef,
CreateMAA: toPtr(conf.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})),
Debug: toPtr(conf.IsDebugCluster()),
ConfidentialVM: toPtr(conf.GetAttestationConfig().GetVariant().Equal(variant.AzureSEVSNP{})),
SecureBoot: conf.Provider.Azure.SecureBoot,
UserAssignedIdentity: conf.Provider.Azure.UserAssignedIdentity,
ResourceGroup: conf.Provider.Azure.ResourceGroup,
CustomEndpoint: conf.CustomEndpoint,
}
vars = normalizeAzureURIs(vars)
return vars
}
func azureTerraformIAMVars(conf *config.Config, oldVars terraform.AzureIAMVariables) *terraform.AzureIAMVariables {
return &terraform.AzureIAMVariables{
Region: conf.Provider.Azure.Location,
ServicePrincipal: oldVars.ServicePrincipal,
ResourceGroup: conf.Provider.Azure.ResourceGroup,
}
}
// gcpTerraformVars provides variables required to execute the Terraform scripts.
// It should be the only place to declare the GCP variables.
func gcpTerraformVars(conf *config.Config, imageRef string) *terraform.GCPClusterVariables {
nodeGroups := make(map[string]terraform.GCPNodeGroup)
for groupName, group := range conf.NodeGroups {
nodeGroups[groupName] = terraform.GCPNodeGroup{
Role: role.FromString(group.Role).TFString(),
StateDiskSizeGB: group.StateDiskSizeGB,
InitialCount: group.InitialCount,
Zone: group.Zone,
InstanceType: group.InstanceType,
DiskType: group.StateDiskType,
}
}
return &terraform.GCPClusterVariables{
Name: conf.Name,
NodeGroups: nodeGroups,
Project: conf.Provider.GCP.Project,
Region: conf.Provider.GCP.Region,
Zone: conf.Provider.GCP.Zone,
ImageID: imageRef,
Debug: conf.IsDebugCluster(),
CustomEndpoint: conf.CustomEndpoint,
}
}
func gcpTerraformIAMVars(conf *config.Config, oldVars terraform.GCPIAMVariables) *terraform.GCPIAMVariables {
return &terraform.GCPIAMVariables{
Project: conf.Provider.GCP.Project,
Region: conf.Provider.GCP.Region,
Zone: conf.Provider.GCP.Zone,
ServiceAccountID: oldVars.ServiceAccountID,
}
}
// openStackTerraformVars provides variables required to execute the Terraform scripts.
// It should be the only place to declare the OpenStack variables.
func openStackTerraformVars(conf *config.Config, imageRef string) *terraform.OpenStackClusterVariables {
nodeGroups := make(map[string]terraform.OpenStackNodeGroup)
for groupName, group := range conf.NodeGroups {
nodeGroups[groupName] = terraform.OpenStackNodeGroup{
Role: role.FromString(group.Role).TFString(),
StateDiskSizeGB: group.StateDiskSizeGB,
InitialCount: group.InitialCount,
FlavorID: group.InstanceType,
Zone: group.Zone,
StateDiskType: group.StateDiskType,
}
}
return &terraform.OpenStackClusterVariables{
Name: conf.Name,
Cloud: toPtr(conf.Provider.OpenStack.Cloud),
FloatingIPPoolID: conf.Provider.OpenStack.FloatingIPPoolID,
ImageURL: imageRef,
DirectDownload: *conf.Provider.OpenStack.DirectDownload,
OpenstackUserDomainName: conf.Provider.OpenStack.UserDomainName,
OpenstackUsername: conf.Provider.OpenStack.Username,
OpenstackPassword: conf.Provider.OpenStack.Password,
Debug: conf.IsDebugCluster(),
NodeGroups: nodeGroups,
CustomEndpoint: conf.CustomEndpoint,
}
}
// qemuTerraformVars provides variables required to execute the Terraform scripts.
// It should be the only place to declare the QEMU variables.
func qemuTerraformVars(conf *config.Config, imageRef string, libvirtURI, libvirtSocketPath, metadataLibvirtURI string) *terraform.QEMUVariables {
nodeGroups := make(map[string]terraform.QEMUNodeGroup)
for groupName, group := range conf.NodeGroups {
nodeGroups[groupName] = terraform.QEMUNodeGroup{
Role: role.FromString(group.Role).TFString(),
InitialCount: group.InitialCount,
DiskSize: group.StateDiskSizeGB,
CPUCount: conf.Provider.QEMU.VCPUs,
MemorySize: conf.Provider.QEMU.Memory,
}
}
return &terraform.QEMUVariables{
Name: conf.Name,
LibvirtURI: libvirtURI,
LibvirtSocketPath: libvirtSocketPath,
// TODO(malt3): auto select boot mode based on attestation variant.
// requires image info v2.
BootMode: "uefi",
ImagePath: imageRef,
ImageFormat: conf.Provider.QEMU.ImageFormat,
NodeGroups: nodeGroups,
Machine: "q35", // TODO(elchead): make configurable AB#3225
MetadataAPIImage: conf.Provider.QEMU.MetadataAPIImage,
MetadataLibvirtURI: metadataLibvirtURI,
NVRAM: conf.Provider.QEMU.NVRAM,
// TODO(malt3) enable once we have a way to auto-select values for these
// requires image info v2.
// BzImagePath: placeholder,
// InitrdPath: placeholder,
// KernelCmdline: placeholder,
}
}