2022-10-07 03:38:43 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
package cmd
import (
"context"
"errors"
"fmt"
"net"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
2023-08-08 09:42:06 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix"
2023-06-28 04:28:48 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/featureset"
2023-08-04 04:42:09 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
2022-10-07 03:38:43 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
2023-04-14 08:15:07 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-05-03 05:11:53 -04:00
"github.com/edgelesssys/constellation/v2/internal/atls"
2022-10-07 03:38:43 -04:00
"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/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
func newMiniUpCmd ( ) * cobra . Command {
cmd := & cobra . Command {
Use : "up" ,
2022-10-14 04:48:20 -04:00
Short : "Create and initialize a new MiniConstellation cluster" ,
2023-01-17 08:01:56 -05:00
Long : "Create and initialize a new MiniConstellation cluster.\n\n" +
"A mini cluster consists of a single control-plane and worker node, hosted using QEMU/KVM." ,
2022-10-07 03:38:43 -04:00
Args : cobra . ExactArgs ( 0 ) ,
RunE : runUp ,
}
2023-03-08 09:48:36 -05:00
cmd . Flags ( ) . Bool ( "merge-kubeconfig" , true , "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config" )
2022-10-07 03:38:43 -04:00
return cmd
}
2023-01-04 04:46:29 -05:00
type miniUpCmd struct {
2023-06-01 07:55:46 -04:00
log debugLog
2023-06-07 10:16:32 -04:00
configFetcher attestationconfigapi . Fetcher
2023-01-04 04:46:29 -05:00
}
2023-03-20 06:03:36 -04:00
func runUp ( cmd * cobra . Command , _ [ ] string ) error {
2023-01-04 04:46:29 -05:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
defer log . Sync ( )
2023-01-18 07:10:24 -05:00
spinner , err := newSpinnerOrStderr ( cmd )
if err != nil {
return err
}
2022-10-07 13:35:07 -04:00
defer spinner . Stop ( )
2022-10-21 08:26:42 -04:00
creator := cloudcmd . NewCreator ( spinner )
2022-10-07 13:35:07 -04:00
2023-06-07 10:16:32 -04:00
m := & miniUpCmd { log : log , configFetcher : attestationconfigapi . NewFetcher ( ) }
2023-01-04 04:46:29 -05:00
return m . up ( cmd , creator , spinner )
2022-10-07 13:35:07 -04:00
}
2023-01-04 04:46:29 -05:00
func ( m * miniUpCmd ) up ( cmd * cobra . Command , creator cloudCreator , spinner spinnerInterf ) error {
if err := m . checkSystemRequirements ( cmd . ErrOrStderr ( ) ) ; err != nil {
2022-10-07 03:38:43 -04:00
return fmt . Errorf ( "system requirements not met: %w" , err )
}
2023-04-14 08:15:07 -04:00
flags , err := m . parseUpFlags ( cmd )
if err != nil {
return fmt . Errorf ( "parsing flags: %w" , err )
}
2022-10-07 03:38:43 -04:00
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
// create config if not passed as flag and set default values
2023-04-14 08:15:07 -04:00
config , err := m . prepareConfig ( cmd , fileHandler , flags )
2022-10-07 03:38:43 -04:00
if err != nil {
return fmt . Errorf ( "preparing config: %w" , err )
}
// create cluster
2022-10-07 13:35:07 -04:00
spinner . Start ( "Creating cluster in QEMU " , false )
2023-08-04 07:53:51 -04:00
err = m . createMiniCluster ( cmd . Context ( ) , fileHandler , creator , config , flags )
2022-10-07 03:38:43 -04:00
spinner . Stop ( )
if err != nil {
return fmt . Errorf ( "creating cluster: %w" , err )
}
cmd . Println ( "Cluster successfully created." )
connectURI := config . Provider . QEMU . LibvirtURI
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Using connect URI %s" , connectURI )
2022-10-07 03:38:43 -04:00
if connectURI == "" {
connectURI = libvirt . LibvirtTCPConnectURI
}
cmd . Println ( "Connect to the VMs by executing:" )
cmd . Printf ( "\tvirsh -c %s\n\n" , connectURI )
// initialize cluster
2023-01-04 04:46:29 -05:00
if err := m . initializeMiniCluster ( cmd , fileHandler , spinner ) ; err != nil {
2022-10-07 03:38:43 -04:00
return fmt . Errorf ( "initializing cluster: %w" , err )
}
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Initialized cluster" )
2022-10-07 03:38:43 -04:00
return nil
}
// prepareConfig reads a given config, or creates a new minimal QEMU config.
2023-04-14 08:15:07 -04:00
func ( m * miniUpCmd ) prepareConfig ( cmd * cobra . Command , fileHandler file . Handler , flags upFlags ) ( * config . Config , error ) {
2023-08-04 07:53:51 -04:00
_ , err := fileHandler . Stat ( constants . ConfigFilename )
if err == nil {
// config already exists, prompt user if they want to use this file
cmd . PrintErrln ( "A config file already exists in the configured workspace." )
ok , err := askToConfirm ( cmd , "Do you want to create the Constellation using that config?" )
2022-10-07 03:38:43 -04:00
if err != nil {
2023-02-07 06:56:25 -05:00
return nil , err
2022-10-07 03:38:43 -04:00
}
2023-08-04 07:53:51 -04:00
if ok {
return m . prepareExistingConfig ( cmd , fileHandler , flags )
2022-10-07 03:38:43 -04:00
}
2023-08-04 07:53:51 -04:00
// user declined to reuse config file, prompt if they want to overwrite it
ok , err = askToConfirm ( cmd , "Do you want to overwrite it and create a new config?" )
2022-10-20 07:36:20 -04:00
if err != nil {
return nil , err
}
if ! ok {
return nil , errors . New ( "not overwriting existing config" )
}
}
2023-08-04 07:53:51 -04:00
2023-06-28 04:28:48 -04:00
if ! featureset . CanUseEmbeddedMeasurmentsAndImage {
cmd . PrintErrln ( "Generating a valid default config is not supported in the OSS build of the Constellation CLI. Consult the documentation for instructions on where to download the enterprise version." )
return nil , errors . New ( "cannot create a mini cluster without a config file in the OSS build" )
}
config , err := config . MiniDefault ( )
if err != nil {
return nil , fmt . Errorf ( "mini default config is invalid: %v" , err )
2023-05-12 11:14:32 -04:00
}
2023-01-18 07:10:24 -05:00
m . log . Debugf ( "Prepared configuration" )
2022-10-07 03:38:43 -04:00
return config , fileHandler . WriteYAML ( constants . ConfigFilename , config , file . OptOverwrite )
}
2023-08-04 07:53:51 -04:00
func ( m * miniUpCmd ) prepareExistingConfig ( cmd * cobra . Command , fileHandler file . Handler , flags upFlags ) ( * config . Config , error ) {
conf , err := config . New ( fileHandler , constants . ConfigFilename , m . configFetcher , flags . force )
var configValidationErr * config . ValidationError
if errors . As ( err , & configValidationErr ) {
cmd . PrintErrln ( configValidationErr . LongMessage ( ) )
}
if err != nil {
return nil , err
}
if conf . GetProvider ( ) != cloudprovider . QEMU {
return nil , errors . New ( "invalid provider for MiniConstellation cluster" )
}
return conf , nil
}
2022-10-07 03:38:43 -04:00
// createMiniCluster creates a new cluster using the given config.
2023-08-04 07:53:51 -04:00
func ( m * miniUpCmd ) createMiniCluster ( ctx context . Context , fileHandler file . Handler , creator cloudCreator , config * config . Config , flags upFlags ) error {
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Creating mini cluster" )
2023-04-14 08:15:07 -04:00
opts := cloudcmd . CreateOptions {
2023-08-04 07:53:51 -04:00
Provider : cloudprovider . QEMU ,
Config : config ,
TFWorkspace : constants . TerraformWorkingDir ,
TFLogLevel : flags . tfLogLevel ,
2023-04-14 08:15:07 -04:00
}
idFile , err := creator . Create ( ctx , opts )
2022-10-07 03:38:43 -04:00
if err != nil {
return err
}
2023-01-25 08:42:52 -05:00
idFile . UID = constants . MiniConstellationUID // use UID "mini" to identify MiniConstellation clusters.
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Cluster id file contains %v" , idFile )
2023-08-04 07:53:51 -04:00
return fileHandler . WriteJSON ( constants . ClusterIDsFilename , idFile , file . OptNone )
2022-10-07 03:38:43 -04:00
}
// initializeMiniCluster initializes a QEMU cluster.
2023-01-04 04:46:29 -05:00
func ( m * miniUpCmd ) initializeMiniCluster ( cmd * cobra . Command , fileHandler file . Handler , spinner spinnerInterf ) ( retErr error ) {
m . log . Debugf ( "Initializing mini cluster" )
2022-10-07 03:38:43 -04:00
// clean up cluster resources if initialization fails
defer func ( ) {
if retErr != nil {
2022-11-10 04:27:24 -05:00
cmd . PrintErrf ( "An error occurred: %s\n" , retErr )
cmd . PrintErrln ( "Attempting to roll back." )
2022-10-07 03:38:43 -04:00
_ = runDown ( cmd , [ ] string { } )
2022-11-10 04:27:24 -05:00
cmd . PrintErrf ( "Rollback succeeded.\n\n" )
2022-10-07 03:38:43 -04:00
}
} ( )
2023-05-03 05:11:53 -04:00
newDialer := func ( validator atls . Validator ) * dialer . Dialer {
return dialer . New ( nil , validator , & net . Dialer { } )
2022-10-07 03:38:43 -04:00
}
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Created new dialer" )
2022-10-07 03:38:43 -04:00
cmd . Flags ( ) . String ( "endpoint" , "" , "" )
cmd . Flags ( ) . Bool ( "conformance" , false , "" )
2023-07-10 09:16:45 -04:00
cmd . Flags ( ) . Bool ( "skip-helm-wait" , false , "install helm charts without waiting for deployments to be ready" )
2023-01-04 04:46:29 -05:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
m . log . Debugf ( "Created new logger" )
defer log . Sync ( )
2023-08-04 04:42:09 -04:00
2023-08-04 07:53:51 -04:00
helmInstaller , err := helm . NewInitializer ( log , constants . AdminConfFilename )
2023-08-04 04:42:09 -04:00
if err != nil {
return fmt . Errorf ( "creating Helm installer: %w" , err )
}
tfClient , err := terraform . New ( cmd . Context ( ) , constants . TerraformWorkingDir )
if err != nil {
return fmt . Errorf ( "creating Terraform client: %w" , err )
}
i := newInitCmd ( tfClient , helmInstaller , fileHandler , spinner , & kubeconfigMerger { log : log } , log )
if err := i . initialize ( cmd , newDialer , license . NewClient ( ) , m . configFetcher ) ; err != nil {
2022-10-07 03:38:43 -04:00
return err
}
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Initialized mini cluster" )
2022-10-07 03:38:43 -04:00
return nil
}
2023-04-14 08:15:07 -04:00
type upFlags struct {
force bool
tfLogLevel terraform . LogLevel
}
func ( m * miniUpCmd ) parseUpFlags ( cmd * cobra . Command ) ( upFlags , error ) {
m . log . Debugf ( "Preparing configuration" )
2023-08-08 09:42:06 -04:00
workDir , err := cmd . Flags ( ) . GetString ( "workspace" )
2023-04-14 08:15:07 -04:00
if err != nil {
return upFlags { } , fmt . Errorf ( "parsing config string: %w" , err )
}
2023-08-08 09:42:06 -04:00
m . log . Debugf ( "Workspace set to %q" , workDir )
2023-04-14 08:15:07 -04:00
force , err := cmd . Flags ( ) . GetBool ( "force" )
if err != nil {
return upFlags { } , fmt . Errorf ( "parsing force bool: %w" , err )
}
2023-08-08 09:42:06 -04:00
m . log . Debugf ( "force flag is %q" , force )
2023-04-14 08:15:07 -04:00
logLevelString , err := cmd . Flags ( ) . GetString ( "tf-log" )
if err != nil {
return upFlags { } , fmt . Errorf ( "parsing tf-log string: %w" , err )
}
logLevel , err := terraform . ParseLogLevel ( logLevelString )
if err != nil {
return upFlags { } , fmt . Errorf ( "parsing Terraform log level %s: %w" , logLevelString , err )
}
2023-08-08 09:42:06 -04:00
m . log . Debugf ( "Terraform logs will be written into %s at level %s" , pathprefix . New ( workDir ) . PrefixPath ( constants . TerraformLogFile ) , logLevel . String ( ) )
2023-04-14 08:15:07 -04:00
return upFlags {
force : force ,
tfLogLevel : logLevel ,
} , nil
}