2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-04-13 07:01:38 -04:00
package cloudcmd
import (
"context"
2023-03-20 08:33:04 -04:00
"encoding/base64"
2023-02-27 12:19:52 -05:00
"errors"
2022-04-13 07:01:38 -04:00
"fmt"
"io"
2023-03-20 08:33:04 -04:00
"net/http"
2022-10-05 03:11:30 -04:00
"net/url"
"os"
2022-12-07 05:48:54 -05:00
"path"
2022-10-13 11:38:38 -04:00
"regexp"
2022-09-26 09:52:31 -04:00
"runtime"
2022-10-05 03:11:30 -04:00
"strings"
2022-04-13 07:01:38 -04:00
2023-03-20 08:33:04 -04:00
"github.com/Azure/azure-sdk-for-go/profiles/latest/attestation/attestation"
azpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
2023-04-05 10:49:03 -04:00
2022-10-11 06:24:33 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
2022-10-05 03:11:30 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
2022-09-26 09:52:31 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
2022-11-14 12:18:58 -05:00
"github.com/edgelesssys/constellation/v2/internal/constants"
2023-05-23 03:17:27 -04:00
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
2023-03-29 08:04:37 -04:00
"github.com/edgelesssys/constellation/v2/internal/variant"
2022-04-13 07:01:38 -04:00
)
// Creator creates cloud resources.
type Creator struct {
2022-09-27 03:22:29 -04:00
out io . Writer
2022-11-22 12:47:08 -05:00
image imageFetcher
2022-10-26 09:57:00 -04:00
newTerraformClient func ( ctx context . Context ) ( terraformClient , error )
2022-10-05 03:11:30 -04:00
newLibvirtRunner func ( ) libvirtRunner
2022-11-22 12:47:08 -05:00
newRawDownloader func ( ) rawDownloader
2023-03-20 08:33:04 -04:00
policyPatcher PolicyPatcher
2022-04-13 07:01:38 -04:00
}
// NewCreator creates a new creator.
func NewCreator ( out io . Writer ) * Creator {
return & Creator {
2022-11-22 12:47:08 -05:00
out : out ,
2023-05-23 03:17:27 -04:00
image : imagefetcher . New ( ) ,
2022-10-26 09:57:00 -04:00
newTerraformClient : func ( ctx context . Context ) ( terraformClient , error ) {
2022-11-14 12:18:58 -05:00
return terraform . New ( ctx , constants . TerraformWorkingDir )
2022-04-13 07:01:38 -04:00
} ,
2022-10-05 03:11:30 -04:00
newLibvirtRunner : func ( ) libvirtRunner {
return libvirt . New ( )
} ,
2022-11-22 12:47:08 -05:00
newRawDownloader : func ( ) rawDownloader {
2023-05-23 03:17:27 -04:00
return imagefetcher . NewDownloader ( )
2022-11-22 12:47:08 -05:00
} ,
2023-03-20 08:33:04 -04:00
policyPatcher : policyPatcher { } ,
2022-04-13 07:01:38 -04:00
}
}
2023-04-14 08:15:07 -04:00
// CreateOptions are the options for creating a Constellation cluster.
type CreateOptions struct {
Provider cloudprovider . Provider
Config * config . Config
InsType string
ControlPlaneCount int
WorkerCount int
image string
TFLogLevel terraform . LogLevel
}
2022-04-13 07:01:38 -04:00
// Create creates the handed amount of instances and all the needed resources.
2023-04-14 08:15:07 -04:00
func ( c * Creator ) Create ( ctx context . Context , opts CreateOptions ) ( clusterid . File , error ) {
2023-05-23 03:17:27 -04:00
provider := opts . Config . GetProvider ( )
attestationVariant := opts . Config . GetAttestationConfig ( ) . GetVariant ( )
region := opts . Config . GetRegion ( )
image , err := c . image . FetchReference ( ctx , provider , attestationVariant , opts . Config . Image , region )
2022-11-22 12:47:08 -05:00
if err != nil {
return clusterid . File { } , fmt . Errorf ( "fetching image reference: %w" , err )
}
2023-04-14 08:15:07 -04:00
opts . image = image
2022-11-22 12:47:08 -05:00
2023-04-14 08:15:07 -04:00
switch opts . Provider {
2022-10-21 06:24:18 -04:00
case cloudprovider . AWS :
2022-10-26 09:57:00 -04:00
cl , err := c . newTerraformClient ( ctx )
2022-10-21 06:24:18 -04:00
if err != nil {
return clusterid . File { } , err
}
defer cl . RemoveInstaller ( )
2023-04-14 08:15:07 -04:00
return c . createAWS ( ctx , cl , opts )
2022-04-13 07:01:38 -04:00
case cloudprovider . GCP :
2022-10-26 09:57:00 -04:00
cl , err := c . newTerraformClient ( ctx )
2022-04-13 07:01:38 -04:00
if err != nil {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , err
2022-04-13 07:01:38 -04:00
}
2022-09-27 03:22:29 -04:00
defer cl . RemoveInstaller ( )
2023-04-14 08:15:07 -04:00
return c . createGCP ( ctx , cl , opts )
2022-04-13 07:01:38 -04:00
case cloudprovider . Azure :
2022-10-26 09:57:00 -04:00
cl , err := c . newTerraformClient ( ctx )
2022-04-13 07:01:38 -04:00
if err != nil {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , err
2022-04-13 07:01:38 -04:00
}
2022-10-06 05:52:19 -04:00
defer cl . RemoveInstaller ( )
2023-04-14 08:15:07 -04:00
return c . createAzure ( ctx , cl , opts )
2023-02-27 12:19:52 -05:00
case cloudprovider . OpenStack :
cl , err := c . newTerraformClient ( ctx )
if err != nil {
return clusterid . File { } , err
}
defer cl . RemoveInstaller ( )
2023-04-14 08:15:07 -04:00
return c . createOpenStack ( ctx , cl , opts )
2022-09-26 09:52:31 -04:00
case cloudprovider . QEMU :
2022-09-27 03:22:29 -04:00
if runtime . GOARCH != "amd64" || runtime . GOOS != "linux" {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , fmt . Errorf ( "creation of a QEMU based Constellation is not supported for %s/%s" , runtime . GOOS , runtime . GOARCH )
2022-09-27 03:22:29 -04:00
}
2022-10-26 09:57:00 -04:00
cl , err := c . newTerraformClient ( ctx )
2022-09-26 09:52:31 -04:00
if err != nil {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , err
2022-09-26 09:52:31 -04:00
}
defer cl . RemoveInstaller ( )
2022-10-05 03:11:30 -04:00
lv := c . newLibvirtRunner ( )
2023-04-14 08:15:07 -04:00
qemuOpts := qemuCreateOptions {
source : image ,
CreateOptions : opts ,
}
return c . createQEMU ( ctx , cl , lv , qemuOpts )
2022-04-13 07:01:38 -04:00
default :
2023-04-14 08:15:07 -04:00
return clusterid . File { } , fmt . Errorf ( "unsupported cloud provider: %s" , opts . Provider )
2022-04-13 07:01:38 -04:00
}
}
2023-04-14 08:15:07 -04:00
func ( c * Creator ) createAWS ( ctx context . Context , cl terraformClient , opts CreateOptions ) ( idFile clusterid . File , retErr error ) {
2022-12-07 05:48:54 -05:00
vars := terraform . AWSClusterVariables {
2022-10-21 06:24:18 -04:00
CommonVariables : terraform . CommonVariables {
2023-04-14 08:15:07 -04:00
Name : opts . Config . Name ,
CountControlPlanes : opts . ControlPlaneCount ,
CountWorkers : opts . WorkerCount ,
StateDiskSizeGB : opts . Config . StateDiskSizeGB ,
2022-10-21 06:24:18 -04:00
} ,
2023-04-14 08:15:07 -04:00
StateDiskType : opts . Config . Provider . AWS . StateDiskType ,
Region : opts . Config . Provider . AWS . Region ,
Zone : opts . Config . Provider . AWS . Zone ,
InstanceType : opts . InsType ,
AMIImageID : opts . image ,
IAMProfileControlPlane : opts . Config . Provider . AWS . IAMProfileControlPlane ,
IAMProfileWorkerNodes : opts . Config . Provider . AWS . IAMProfileWorkerNodes ,
Debug : opts . Config . IsDebugCluster ( ) ,
2022-10-21 06:24:18 -04:00
}
2022-12-07 05:48:54 -05:00
if err := cl . PrepareWorkspace ( path . Join ( "terraform" , strings . ToLower ( cloudprovider . AWS . String ( ) ) ) , & vars ) ; err != nil {
2022-11-15 08:00:44 -05:00
return clusterid . File { } , err
}
2023-04-14 08:15:07 -04:00
defer rollbackOnError ( c . out , & retErr , & rollbackerTerraform { client : cl } , opts . TFLogLevel )
tfOutput , err := cl . CreateCluster ( ctx , opts . TFLogLevel )
2022-10-21 06:24:18 -04:00
if err != nil {
return clusterid . File { } , err
}
return clusterid . File {
CloudProvider : cloudprovider . AWS ,
2023-01-19 04:41:07 -05:00
InitSecret : [ ] byte ( tfOutput . Secret ) ,
IP : tfOutput . IP ,
UID : tfOutput . UID ,
2022-10-21 06:24:18 -04:00
} , nil
}
2023-04-14 08:15:07 -04:00
func ( c * Creator ) createGCP ( ctx context . Context , cl terraformClient , opts CreateOptions ) ( idFile clusterid . File , retErr error ) {
2022-12-07 05:48:54 -05:00
vars := terraform . GCPClusterVariables {
2022-09-27 03:22:29 -04:00
CommonVariables : terraform . CommonVariables {
2023-04-14 08:15:07 -04:00
Name : opts . Config . Name ,
CountControlPlanes : opts . ControlPlaneCount ,
CountWorkers : opts . WorkerCount ,
StateDiskSizeGB : opts . Config . StateDiskSizeGB ,
2022-05-24 04:04:42 -04:00
} ,
2023-04-14 08:15:07 -04:00
Project : opts . Config . Provider . GCP . Project ,
Region : opts . Config . Provider . GCP . Region ,
Zone : opts . Config . Provider . GCP . Zone ,
CredentialsFile : opts . Config . Provider . GCP . ServiceAccountKeyPath ,
InstanceType : opts . InsType ,
StateDiskType : opts . Config . Provider . GCP . StateDiskType ,
ImageID : opts . image ,
Debug : opts . Config . IsDebugCluster ( ) ,
2022-04-13 07:01:38 -04:00
}
2022-12-07 05:48:54 -05:00
if err := cl . PrepareWorkspace ( path . Join ( "terraform" , strings . ToLower ( cloudprovider . GCP . String ( ) ) ) , & vars ) ; err != nil {
2022-11-15 08:00:44 -05:00
return clusterid . File { } , err
}
2023-04-14 08:15:07 -04:00
defer rollbackOnError ( c . out , & retErr , & rollbackerTerraform { client : cl } , opts . TFLogLevel )
tfOutput , err := cl . CreateCluster ( ctx , opts . TFLogLevel )
2022-10-11 06:24:33 -04:00
if err != nil {
return clusterid . File { } , err
2022-06-09 16:26:36 -04:00
}
2022-10-11 06:24:33 -04:00
return clusterid . File {
CloudProvider : cloudprovider . GCP ,
2023-01-19 04:41:07 -05:00
InitSecret : [ ] byte ( tfOutput . Secret ) ,
IP : tfOutput . IP ,
UID : tfOutput . UID ,
2022-10-11 06:24:33 -04:00
} , nil
2022-04-13 07:01:38 -04:00
}
2023-04-14 08:15:07 -04:00
func ( c * Creator ) createAzure ( ctx context . Context , cl terraformClient , opts CreateOptions ) ( idFile clusterid . File , retErr error ) {
2022-12-07 05:48:54 -05:00
vars := terraform . AzureClusterVariables {
2022-10-06 05:52:19 -04:00
CommonVariables : terraform . CommonVariables {
2023-04-14 08:15:07 -04:00
Name : opts . Config . Name ,
CountControlPlanes : opts . ControlPlaneCount ,
CountWorkers : opts . WorkerCount ,
StateDiskSizeGB : opts . Config . StateDiskSizeGB ,
2022-10-06 05:52:19 -04:00
} ,
2023-04-14 08:15:07 -04:00
Location : opts . Config . Provider . Azure . Location ,
ResourceGroup : opts . Config . Provider . Azure . ResourceGroup ,
UserAssignedIdentity : opts . Config . Provider . Azure . UserAssignedIdentity ,
InstanceType : opts . InsType ,
StateDiskType : opts . Config . Provider . Azure . StateDiskType ,
ImageID : opts . image ,
SecureBoot : * opts . Config . Provider . Azure . SecureBoot ,
2023-05-03 05:11:53 -04:00
CreateMAA : opts . Config . GetAttestationConfig ( ) . GetVariant ( ) . Equal ( variant . AzureSEVSNP { } ) ,
2023-04-14 08:15:07 -04:00
Debug : opts . Config . IsDebugCluster ( ) ,
2022-04-13 07:01:38 -04:00
}
2022-10-06 05:52:19 -04:00
2023-05-03 05:11:53 -04:00
vars . ConfidentialVM = opts . Config . GetAttestationConfig ( ) . GetVariant ( ) . Equal ( variant . AzureSEVSNP { } )
2023-03-29 08:04:37 -04:00
2022-10-12 11:00:59 -04:00
vars = normalizeAzureURIs ( vars )
2022-12-07 05:48:54 -05:00
if err := cl . PrepareWorkspace ( path . Join ( "terraform" , strings . ToLower ( cloudprovider . Azure . String ( ) ) ) , & vars ) ; err != nil {
2022-11-15 08:00:44 -05:00
return clusterid . File { } , err
}
2023-04-14 08:15:07 -04:00
defer rollbackOnError ( c . out , & retErr , & rollbackerTerraform { client : cl } , opts . TFLogLevel )
tfOutput , err := cl . CreateCluster ( ctx , opts . TFLogLevel )
2022-10-11 06:24:33 -04:00
if err != nil {
return clusterid . File { } , err
2022-04-13 07:01:38 -04:00
}
2023-03-20 08:33:04 -04:00
if vars . CreateMAA {
// Patch the attestation policy to allow the cluster to boot while having secure boot disabled.
if err := c . policyPatcher . Patch ( ctx , tfOutput . AttestationURL ) ; err != nil {
return clusterid . File { } , err
}
}
2022-10-11 06:24:33 -04:00
return clusterid . File {
2023-03-20 08:33:04 -04:00
CloudProvider : cloudprovider . Azure ,
IP : tfOutput . IP ,
InitSecret : [ ] byte ( tfOutput . Secret ) ,
UID : tfOutput . UID ,
AttestationURL : tfOutput . AttestationURL ,
2022-10-11 06:24:33 -04:00
} , nil
2022-04-13 07:01:38 -04:00
}
2022-09-26 09:52:31 -04:00
2023-03-20 08:33:04 -04:00
// PolicyPatcher interacts with Azure to update the attestation policy.
type PolicyPatcher interface {
Patch ( ctx context . Context , attestationURL string ) error
}
type policyPatcher struct { }
// Patch updates the attestation policy to the base64-encoded attestation policy JWT for the given attestation URL.
// https://learn.microsoft.com/en-us/azure/attestation/author-sign-policy#next-steps
func ( p policyPatcher ) Patch ( ctx context . Context , attestationURL string ) error {
// hacky way to update the MAA attestation policy. This should be changed as soon as either the Terraform provider supports it
// or the Go SDK gets updated to a recent API version.
// https://github.com/hashicorp/terraform-provider-azurerm/issues/20804
cred , err := azidentity . NewDefaultAzureCredential ( nil )
if err != nil {
return fmt . Errorf ( "retrieving default Azure credentials: %w" , err )
}
token , err := cred . GetToken ( ctx , azpolicy . TokenRequestOptions {
Scopes : [ ] string { "https://attest.azure.net/.default" } ,
} )
if err != nil {
return fmt . Errorf ( "retrieving token from default Azure credentials: %w" , err )
}
client := attestation . NewPolicyClient ( )
// azureGuest is the id for the "Azure VM" attestation type. Other types are documented here:
// https://learn.microsoft.com/en-us/rest/api/attestation/policy/set
req , err := client . SetPreparer ( ctx , attestationURL , "azureGuest" , p . encodeAttestationPolicy ( ) )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , token . Token ) )
if err != nil {
return fmt . Errorf ( "preparing request: %w" , err )
}
resp , err := client . Send ( req )
if err != nil {
return fmt . Errorf ( "sending request: %w" , err )
}
resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
return fmt . Errorf ( "updating attestation policy: unexpected status code: %s" , resp . Status )
}
return nil
}
// encodeAttestationPolicy encodes the base64-encoded attestation policy in the JWS format specified here:
// https://learn.microsoft.com/en-us/azure/attestation/author-sign-policy#creating-the-policy-file-in-json-web-signature-format
func ( p policyPatcher ) encodeAttestationPolicy ( ) string {
const policy = `
version = 1.0 ;
authorizationrules
{
[ type == "x-ms-azurevm-default-securebootkeysvalidated" , value == false ] = > deny ( ) ;
[ type == "x-ms-azurevm-debuggersdisabled" , value == false ] = > deny ( ) ;
// The line below was edited by the Constellation CLI. Do not edit manually.
//[type=="secureboot", value==false] => deny();
[ type == "x-ms-azurevm-signingdisabled" , value == false ] = > deny ( ) ;
[ type == "x-ms-azurevm-dbvalidated" , value == false ] = > deny ( ) ;
[ type == "x-ms-azurevm-dbxvalidated" , value == false ] = > deny ( ) ;
= > permit ( ) ;
} ;
issuancerules
{
} ; `
encodedPolicy := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( policy ) )
const header = ` { "alg":"none"} `
payload := fmt . Sprintf ( ` { "AttestationPolicy":"%s"} ` , encodedPolicy )
encodedHeader := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( header ) )
encodedPayload := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( payload ) )
return fmt . Sprintf ( "%s.%s." , encodedHeader , encodedPayload )
}
2022-10-13 11:38:38 -04:00
// The azurerm Terraform provider enforces its own convention of case sensitivity for Azure URIs which Azure's API itself does not enforce or, even worse, actually returns.
// Let's go loco with case insensitive Regexp here and fix the user input here to be compliant with this arbitrary design decision.
var (
caseInsensitiveSubscriptionsRegexp = regexp . MustCompile ( ` (?i)\/subscriptions\/ ` )
caseInsensitiveResourceGroupRegexp = regexp . MustCompile ( ` (?i)\/resourcegroups\/ ` )
caseInsensitiveProvidersRegexp = regexp . MustCompile ( ` (?i)\/providers\/ ` )
caseInsensitiveUserAssignedIdentitiesRegexp = regexp . MustCompile ( ` (?i)\/userassignedidentities\/ ` )
caseInsensitiveMicrosoftManagedIdentity = regexp . MustCompile ( ` (?i)\/microsoft.managedidentity\/ ` )
caseInsensitiveCommunityGalleriesRegexp = regexp . MustCompile ( ` (?i)\/communitygalleries\/ ` )
caseInsensitiveImagesRegExp = regexp . MustCompile ( ` (?i)\/images\/ ` )
caseInsensitiveVersionsRegExp = regexp . MustCompile ( ` (?i)\/versions\/ ` )
)
2022-10-12 11:00:59 -04:00
2022-12-07 05:48:54 -05:00
func normalizeAzureURIs ( vars terraform . AzureClusterVariables ) terraform . AzureClusterVariables {
2022-10-13 11:38:38 -04:00
vars . UserAssignedIdentity = caseInsensitiveSubscriptionsRegexp . ReplaceAllString ( vars . UserAssignedIdentity , "/subscriptions/" )
vars . UserAssignedIdentity = caseInsensitiveResourceGroupRegexp . ReplaceAllString ( vars . UserAssignedIdentity , "/resourceGroups/" )
vars . UserAssignedIdentity = caseInsensitiveProvidersRegexp . ReplaceAllString ( vars . UserAssignedIdentity , "/providers/" )
vars . UserAssignedIdentity = caseInsensitiveUserAssignedIdentitiesRegexp . ReplaceAllString ( vars . UserAssignedIdentity , "/userAssignedIdentities/" )
vars . UserAssignedIdentity = caseInsensitiveMicrosoftManagedIdentity . ReplaceAllString ( vars . UserAssignedIdentity , "/Microsoft.ManagedIdentity/" )
vars . ImageID = caseInsensitiveCommunityGalleriesRegexp . ReplaceAllString ( vars . ImageID , "/communityGalleries/" )
vars . ImageID = caseInsensitiveImagesRegExp . ReplaceAllString ( vars . ImageID , "/images/" )
vars . ImageID = caseInsensitiveVersionsRegExp . ReplaceAllString ( vars . ImageID , "/versions/" )
2022-10-12 11:00:59 -04:00
return vars
}
2023-04-14 08:15:07 -04:00
func ( c * Creator ) createOpenStack ( ctx context . Context , cl terraformClient , opts CreateOptions ) ( idFile clusterid . File , retErr error ) {
2023-02-27 12:19:52 -05:00
// TODO: Remove this once OpenStack is supported.
if os . Getenv ( "CONSTELLATION_OPENSTACK_DEV" ) != "1" {
return clusterid . File { } , errors . New ( "OpenStack isn't supported yet" )
}
2023-04-14 08:15:07 -04:00
if _ , hasOSAuthURL := os . LookupEnv ( "OS_AUTH_URL" ) ; ! hasOSAuthURL && opts . Config . Provider . OpenStack . Cloud == "" {
2023-02-27 12:19:52 -05:00
return clusterid . File { } , errors . New (
"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 " +
"or a cloud name for \"clouds.yaml\". " +
"See https://docs.openstack.org/openstacksdk/latest/user/config/configuration.html for more information" ,
)
}
vars := terraform . OpenStackClusterVariables {
CommonVariables : terraform . CommonVariables {
2023-04-14 08:15:07 -04:00
Name : opts . Config . Name ,
CountControlPlanes : opts . ControlPlaneCount ,
CountWorkers : opts . WorkerCount ,
StateDiskSizeGB : opts . Config . StateDiskSizeGB ,
2023-02-27 12:19:52 -05:00
} ,
2023-04-14 08:15:07 -04:00
Cloud : opts . Config . Provider . OpenStack . Cloud ,
AvailabilityZone : opts . Config . Provider . OpenStack . AvailabilityZone ,
FlavorID : opts . Config . Provider . OpenStack . FlavorID ,
2023-04-27 03:08:43 -04:00
FloatingIPPoolID : opts . Config . Provider . OpenStack . FloatingIPPoolID ,
StateDiskType : opts . Config . Provider . OpenStack . StateDiskType ,
2023-04-14 08:15:07 -04:00
ImageURL : opts . image ,
DirectDownload : * opts . Config . Provider . OpenStack . DirectDownload ,
OpenstackUserDomainName : opts . Config . Provider . OpenStack . UserDomainName ,
OpenstackUsername : opts . Config . Provider . OpenStack . Username ,
OpenstackPassword : opts . Config . Provider . OpenStack . Password ,
Debug : opts . Config . IsDebugCluster ( ) ,
2023-02-27 12:19:52 -05:00
}
if err := cl . PrepareWorkspace ( path . Join ( "terraform" , strings . ToLower ( cloudprovider . OpenStack . String ( ) ) ) , & vars ) ; err != nil {
return clusterid . File { } , err
}
2023-04-14 08:15:07 -04:00
defer rollbackOnError ( c . out , & retErr , & rollbackerTerraform { client : cl } , opts . TFLogLevel )
tfOutput , err := cl . CreateCluster ( ctx , opts . TFLogLevel )
2023-02-27 12:19:52 -05:00
if err != nil {
return clusterid . File { } , err
}
return clusterid . File {
CloudProvider : cloudprovider . OpenStack ,
IP : tfOutput . IP ,
InitSecret : [ ] byte ( tfOutput . Secret ) ,
UID : tfOutput . UID ,
} , nil
}
2023-04-14 08:15:07 -04:00
type qemuCreateOptions struct {
source string
CreateOptions
}
func ( c * Creator ) createQEMU ( ctx context . Context , cl terraformClient , lv libvirtRunner , opts qemuCreateOptions ) ( idFile clusterid . File , retErr error ) {
2022-11-15 08:00:44 -05:00
qemuRollbacker := & rollbackerQEMU { client : cl , libvirt : lv , createdWorkspace : false }
2023-04-14 08:15:07 -04:00
defer rollbackOnError ( c . out , & retErr , qemuRollbacker , opts . TFLogLevel )
2022-10-05 03:11:30 -04:00
2022-11-22 12:47:08 -05:00
// TODO: render progress bar
downloader := c . newRawDownloader ( )
2023-04-14 08:15:07 -04:00
imagePath , err := downloader . Download ( ctx , c . out , false , opts . source , opts . Config . Image )
2022-11-22 12:47:08 -05:00
if err != nil {
return clusterid . File { } , fmt . Errorf ( "download raw image: %w" , err )
}
2023-04-14 08:15:07 -04:00
libvirtURI := opts . Config . Provider . QEMU . LibvirtURI
2022-10-05 03:11:30 -04:00
libvirtSocketPath := "."
switch {
// if no libvirt URI is specified, start a libvirt container
case libvirtURI == "" :
2023-04-14 08:15:07 -04:00
if err := lv . Start ( ctx , opts . Config . Name , opts . Config . Provider . QEMU . LibvirtContainerImage ) ; err != nil {
2023-05-23 04:49:47 -04:00
return clusterid . File { } , fmt . Errorf ( "start libvirt container: %w" , err )
2022-10-05 03:11:30 -04:00
}
2022-10-07 03:38:43 -04:00
libvirtURI = libvirt . LibvirtTCPConnectURI
2022-10-05 03:11:30 -04:00
// socket for system URI should be in /var/run/libvirt/libvirt-sock
case libvirtURI == "qemu:///system" :
libvirtSocketPath = "/var/run/libvirt/libvirt-sock"
// socket for session URI should be in /run/user/<uid>/libvirt/libvirt-sock
case libvirtURI == "qemu:///session" :
libvirtSocketPath = fmt . Sprintf ( "/run/user/%d/libvirt/libvirt-sock" , os . Getuid ( ) )
// if a unix socket is specified we need to parse the URI to get the socket path
case strings . HasPrefix ( libvirtURI , "qemu+unix://" ) :
unixURI , err := url . Parse ( strings . TrimPrefix ( libvirtURI , "qemu+unix://" ) )
if err != nil {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , err
2022-10-05 03:11:30 -04:00
}
libvirtSocketPath = unixURI . Query ( ) . Get ( "socket" )
if libvirtSocketPath == "" {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , fmt . Errorf ( "socket path not specified in qemu+unix URI: %s" , libvirtURI )
2022-10-05 03:11:30 -04:00
}
}
metadataLibvirtURI := libvirtURI
if libvirtSocketPath != "." {
metadataLibvirtURI = "qemu:///system"
}
2022-09-27 03:22:29 -04:00
2022-10-12 11:00:59 -04:00
vars := terraform . QEMUVariables {
2022-09-27 03:22:29 -04:00
CommonVariables : terraform . CommonVariables {
2023-04-14 08:15:07 -04:00
Name : opts . Config . Name ,
CountControlPlanes : opts . ControlPlaneCount ,
CountWorkers : opts . WorkerCount ,
StateDiskSizeGB : opts . Config . StateDiskSizeGB ,
2022-09-26 09:52:31 -04:00
} ,
2023-05-16 08:13:10 -04:00
LibvirtURI : libvirtURI ,
LibvirtSocketPath : libvirtSocketPath ,
// TODO(malt3): auto select boot mode based on attestation variant.
// requires image info v2.
BootMode : "uefi" ,
2022-11-22 12:47:08 -05:00
ImagePath : imagePath ,
2023-04-14 08:15:07 -04:00
ImageFormat : opts . Config . Provider . QEMU . ImageFormat ,
CPUCount : opts . Config . Provider . QEMU . VCPUs ,
MemorySizeMiB : opts . Config . Provider . QEMU . Memory ,
MetadataAPIImage : opts . Config . Provider . QEMU . MetadataAPIImage ,
2022-10-05 03:11:30 -04:00
MetadataLibvirtURI : metadataLibvirtURI ,
2023-04-14 08:15:07 -04:00
NVRAM : opts . Config . Provider . QEMU . NVRAM ,
Firmware : opts . Config . Provider . QEMU . Firmware ,
2023-05-16 08:13:10 -04:00
// TODO(malt3) enable once we have a way to auto-select values for these
// requires image info v2.
// BzImagePath: placeholder,
// InitrdPath: placeholder,
// KernelCmdline: placeholder,
2022-09-26 09:52:31 -04:00
}
2022-12-07 05:48:54 -05:00
if err := cl . PrepareWorkspace ( path . Join ( "terraform" , strings . ToLower ( cloudprovider . QEMU . String ( ) ) ) , & vars ) ; err != nil {
2023-05-23 04:49:47 -04:00
return clusterid . File { } , fmt . Errorf ( "prepare workspace: %w" , err )
2022-11-15 08:00:44 -05:00
}
// Allow rollback of QEMU Terraform workspace from this point on
qemuRollbacker . createdWorkspace = true
2023-04-14 08:15:07 -04:00
tfOutput , err := cl . CreateCluster ( ctx , opts . TFLogLevel )
2022-10-11 06:24:33 -04:00
if err != nil {
2023-05-23 04:49:47 -04:00
return clusterid . File { } , fmt . Errorf ( "create cluster: %w" , err )
2022-09-26 09:52:31 -04:00
}
2022-10-11 06:24:33 -04:00
return clusterid . File {
CloudProvider : cloudprovider . QEMU ,
2023-01-19 04:41:07 -05:00
InitSecret : [ ] byte ( tfOutput . Secret ) ,
IP : tfOutput . IP ,
UID : tfOutput . UID ,
2022-10-11 06:24:33 -04:00
} , nil
2022-09-26 09:52:31 -04:00
}