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"
"fmt"
"io"
2022-10-05 03:11:30 -04:00
"net/url"
"os"
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
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-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-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-04-13 07:01:38 -04:00
}
// NewCreator creates a new creator.
func NewCreator ( out io . Writer ) * Creator {
return & Creator {
out : out ,
2022-10-26 09:57:00 -04:00
newTerraformClient : func ( ctx context . Context ) ( terraformClient , error ) {
return terraform . New ( ctx )
2022-04-13 07:01:38 -04:00
} ,
2022-10-05 03:11:30 -04:00
newLibvirtRunner : func ( ) libvirtRunner {
return libvirt . New ( )
} ,
2022-04-13 07:01:38 -04:00
}
}
// Create creates the handed amount of instances and all the needed resources.
2022-06-29 09:26:29 -04:00
func ( c * Creator ) Create ( ctx context . Context , provider cloudprovider . Provider , config * config . Config , name , insType string , controlPlaneCount , workerCount int ,
2022-10-11 06:24:33 -04:00
) ( clusterid . File , error ) {
2022-04-13 07:01:38 -04:00
switch provider {
2022-10-21 06:24:18 -04:00
case cloudprovider . AWS :
// TODO: Remove this once AWS is supported.
if os . Getenv ( "CONSTELLATION_AWS_DEV" ) != "1" {
return clusterid . File { } , fmt . Errorf ( "AWS isn't supported yet" )
}
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 ( )
return c . createAWS ( ctx , cl , config , name , insType , controlPlaneCount , workerCount )
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 ( )
return c . createGCP ( ctx , cl , config , name , insType , controlPlaneCount , workerCount )
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 ( )
return c . createAzure ( ctx , cl , config , name , insType , controlPlaneCount , workerCount )
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 ( )
return c . createQEMU ( ctx , cl , lv , name , config , controlPlaneCount , workerCount )
2022-04-13 07:01:38 -04:00
default :
2022-10-11 06:24:33 -04:00
return clusterid . File { } , fmt . Errorf ( "unsupported cloud provider: %s" , provider )
2022-04-13 07:01:38 -04:00
}
}
2022-10-21 06:24:18 -04:00
func ( c * Creator ) createAWS ( ctx context . Context , cl terraformClient , config * config . Config ,
name , insType string , controlPlaneCount , workerCount int ,
) ( idFile clusterid . File , retErr error ) {
defer rollbackOnError ( context . Background ( ) , c . out , & retErr , & rollbackerTerraform { client : cl } )
vars := & terraform . AWSVariables {
CommonVariables : terraform . CommonVariables {
Name : name ,
CountControlPlanes : controlPlaneCount ,
CountWorkers : workerCount ,
StateDiskSizeGB : config . StateDiskSizeGB ,
} ,
2022-10-24 18:50:30 -04:00
StateDiskType : config . Provider . AWS . StateDiskType ,
2022-10-21 06:24:18 -04:00
Region : config . Provider . AWS . Region ,
Zone : config . Provider . AWS . Zone ,
InstanceType : insType ,
AMIImageID : config . Provider . AWS . Image ,
IAMProfileControlPlane : config . Provider . AWS . IAMProfileControlPlane ,
IAMProfileWorkerNodes : config . Provider . AWS . IAMProfileWorkerNodes ,
Debug : config . IsDebugCluster ( ) ,
}
2022-10-26 09:57:00 -04:00
ip , err := cl . CreateCluster ( ctx , cloudprovider . AWS , name , vars )
2022-10-21 06:24:18 -04:00
if err != nil {
return clusterid . File { } , err
}
return clusterid . File {
CloudProvider : cloudprovider . AWS ,
IP : ip ,
} , nil
}
2022-09-27 03:22:29 -04:00
func ( c * Creator ) createGCP ( ctx context . Context , cl terraformClient , config * config . Config ,
name , insType string , controlPlaneCount , workerCount int ,
2022-10-11 06:24:33 -04:00
) ( idFile clusterid . File , retErr error ) {
2022-09-27 03:22:29 -04:00
defer rollbackOnError ( context . Background ( ) , c . out , & retErr , & rollbackerTerraform { client : cl } )
2022-10-12 11:00:59 -04:00
vars := terraform . GCPVariables {
2022-09-27 03:22:29 -04:00
CommonVariables : terraform . CommonVariables {
Name : name ,
CountControlPlanes : controlPlaneCount ,
CountWorkers : workerCount ,
StateDiskSizeGB : config . StateDiskSizeGB ,
2022-05-24 04:04:42 -04:00
} ,
2022-09-27 03:22:29 -04:00
Project : config . Provider . GCP . Project ,
Region : config . Provider . GCP . Region ,
Zone : config . Provider . GCP . Zone ,
CredentialsFile : config . Provider . GCP . ServiceAccountKeyPath ,
InstanceType : insType ,
StateDiskType : config . Provider . GCP . StateDiskType ,
ImageID : config . Provider . GCP . Image ,
Debug : config . IsDebugCluster ( ) ,
2022-04-13 07:01:38 -04:00
}
2022-10-26 09:57:00 -04:00
ip , err := cl . CreateCluster ( ctx , cloudprovider . GCP , name , & vars )
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 ,
IP : ip ,
} , nil
2022-04-13 07:01:38 -04:00
}
2022-10-06 05:52:19 -04:00
func ( c * Creator ) createAzure ( ctx context . Context , cl terraformClient , config * config . Config ,
name , insType string , controlPlaneCount , workerCount int ,
2022-10-11 06:24:33 -04:00
) ( idFile clusterid . File , retErr error ) {
2022-10-06 05:52:19 -04:00
defer rollbackOnError ( context . Background ( ) , c . out , & retErr , & rollbackerTerraform { client : cl } )
2022-05-16 12:54:25 -04:00
2022-10-12 11:00:59 -04:00
vars := terraform . AzureVariables {
2022-10-06 05:52:19 -04:00
CommonVariables : terraform . CommonVariables {
Name : name ,
CountControlPlanes : controlPlaneCount ,
CountWorkers : workerCount ,
StateDiskSizeGB : config . StateDiskSizeGB ,
} ,
Location : config . Provider . Azure . Location ,
ResourceGroup : config . Provider . Azure . ResourceGroup ,
UserAssignedIdentity : config . Provider . Azure . UserAssignedIdentity ,
2022-04-13 07:01:38 -04:00
InstanceType : insType ,
2022-08-02 06:24:55 -04:00
StateDiskType : config . Provider . Azure . StateDiskType ,
2022-10-06 05:52:19 -04:00
ImageID : config . Provider . Azure . Image ,
2022-08-25 09:24:31 -04:00
ConfidentialVM : * config . Provider . Azure . ConfidentialVM ,
2022-10-19 07:10:15 -04:00
SecureBoot : * config . Provider . Azure . SecureBoot ,
2022-10-06 05:52:19 -04:00
Debug : config . IsDebugCluster ( ) ,
2022-04-13 07:01:38 -04:00
}
2022-10-06 05:52:19 -04:00
2022-10-12 11:00:59 -04:00
vars = normalizeAzureURIs ( vars )
2022-10-26 09:57:00 -04:00
ip , err := cl . CreateCluster ( ctx , cloudprovider . Azure , name , & vars )
2022-10-11 06:24:33 -04:00
if err != nil {
return clusterid . File { } , err
2022-04-13 07:01:38 -04:00
}
2022-10-11 06:24:33 -04:00
return clusterid . File {
CloudProvider : cloudprovider . Azure ,
IP : ip ,
} , nil
2022-04-13 07:01:38 -04:00
}
2022-09-26 09:52:31 -04:00
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-10-13 11:38:38 -04:00
func normalizeAzureURIs ( vars terraform . AzureVariables ) terraform . AzureVariables {
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
}
2022-10-05 03:11:30 -04:00
func ( c * Creator ) createQEMU ( ctx context . Context , cl terraformClient , lv libvirtRunner , name string , config * config . Config ,
2022-09-27 03:22:29 -04:00
controlPlaneCount , workerCount int ,
2022-10-11 06:24:33 -04:00
) ( idFile clusterid . File , retErr error ) {
2022-10-05 03:11:30 -04:00
defer rollbackOnError ( context . Background ( ) , c . out , & retErr , & rollbackerQEMU { client : cl , libvirt : lv } )
libvirtURI := config . Provider . QEMU . LibvirtURI
libvirtSocketPath := "."
switch {
// if no libvirt URI is specified, start a libvirt container
case libvirtURI == "" :
if err := lv . Start ( ctx , name , config . Provider . QEMU . LibvirtContainerImage ) ; err != nil {
2022-10-11 06:24:33 -04:00
return clusterid . File { } , 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 {
Name : name ,
CountControlPlanes : controlPlaneCount ,
CountWorkers : workerCount ,
StateDiskSizeGB : config . StateDiskSizeGB ,
2022-09-26 09:52:31 -04:00
} ,
2022-10-05 03:11:30 -04:00
LibvirtURI : libvirtURI ,
LibvirtSocketPath : libvirtSocketPath ,
ImagePath : config . Provider . QEMU . Image ,
ImageFormat : config . Provider . QEMU . ImageFormat ,
CPUCount : config . Provider . QEMU . VCPUs ,
MemorySizeMiB : config . Provider . QEMU . Memory ,
MetadataAPIImage : config . Provider . QEMU . MetadataAPIImage ,
MetadataLibvirtURI : metadataLibvirtURI ,
2022-10-19 07:10:15 -04:00
NVRAM : config . Provider . QEMU . NVRAM ,
Firmware : config . Provider . QEMU . Firmware ,
2022-09-26 09:52:31 -04:00
}
2022-10-26 09:57:00 -04:00
ip , err := cl . CreateCluster ( ctx , cloudprovider . QEMU , name , & vars )
2022-10-11 06:24:33 -04:00
if err != nil {
return clusterid . File { } , err
2022-09-26 09:52:31 -04:00
}
2022-10-11 06:24:33 -04:00
return clusterid . File {
CloudProvider : cloudprovider . QEMU ,
IP : ip ,
} , nil
2022-09-26 09:52:31 -04:00
}