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"
2023-10-31 07:46:40 -04:00
"os"
"path/filepath"
2023-10-24 09:39:18 -04:00
"time"
2022-10-07 03:38:43 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
2023-06-28 04:28:48 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/featureset"
2022-10-07 03:38:43 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
2023-10-09 07:04:29 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/state"
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
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/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-10-16 09:05:29 -04:00
fileHandler file . Handler
flags rootFlags
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 ( )
2023-10-16 09:05:29 -04:00
m := & miniUpCmd {
log : log ,
configFetcher : attestationconfigapi . NewFetcher ( ) ,
fileHandler : file . NewHandler ( afero . NewOsFs ( ) ) ,
}
if err := m . flags . parse ( cmd . Flags ( ) ) ; err != nil {
return err
}
2023-10-31 07:46:40 -04:00
creator , cleanUp , err := cloudcmd . NewApplier (
cmd . Context ( ) ,
spinner ,
constants . TerraformWorkingDir ,
filepath . Join ( constants . UpgradeDir , "create" ) , // Not used by create
m . flags . tfLogLevel ,
m . fileHandler ,
)
if err != nil {
return err
}
defer cleanUp ( )
2023-01-04 04:46:29 -05:00
return m . up ( cmd , creator , spinner )
2022-10-07 13:35:07 -04:00
}
2023-10-31 07:46:40 -04:00
func ( m * miniUpCmd ) up ( cmd * cobra . Command , creator cloudApplier , spinner spinnerInterf ) error {
2023-01-04 04:46:29 -05:00
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-10-31 07:46:40 -04:00
if clean , err := m . fileHandler . IsEmpty ( constants . TerraformWorkingDir ) ; err != nil && ! errors . Is ( err , os . ErrNotExist ) {
return fmt . Errorf ( "checking if terraform working directory is empty: %w" , err )
} else if err == nil && ! clean {
return fmt . Errorf (
"directory %q already exists and is not empty, run 'constellation mini down' before creating a new cluster" ,
m . flags . pathPrefixer . PrefixPrintablePath ( constants . TerraformWorkingDir ) ,
)
}
// create config if not present in directory and set default values
2023-10-16 09:05:29 -04:00
config , err := m . prepareConfig ( cmd )
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-10-16 09:05:29 -04:00
err = m . createMiniCluster ( cmd . Context ( ) , creator , config )
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-10-24 09:39:18 -04:00
if err := m . initializeMiniCluster ( cmd ) ; 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-10-16 09:05:29 -04:00
func ( m * miniUpCmd ) prepareConfig ( cmd * cobra . Command ) ( * config . Config , error ) {
_ , err := m . fileHandler . Stat ( constants . ConfigFilename )
2023-08-04 07:53:51 -04:00
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 {
2023-10-16 09:05:29 -04:00
return m . prepareExistingConfig ( cmd )
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
2023-10-16 09:05:29 -04:00
return config , m . fileHandler . WriteYAML ( constants . ConfigFilename , config , file . OptOverwrite )
2022-10-07 03:38:43 -04:00
}
2023-10-16 09:05:29 -04:00
func ( m * miniUpCmd ) prepareExistingConfig ( cmd * cobra . Command ) ( * config . Config , error ) {
conf , err := config . New ( m . fileHandler , constants . ConfigFilename , m . configFetcher , m . flags . force )
2023-08-04 07:53:51 -04:00
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-10-31 07:46:40 -04:00
func ( m * miniUpCmd ) createMiniCluster ( ctx context . Context , creator cloudApplier , config * config . Config ) error {
2023-01-04 04:46:29 -05:00
m . log . Debugf ( "Creating mini cluster" )
2023-10-31 07:46:40 -04:00
if _ , err := creator . Plan ( ctx , config ) ; err != nil {
return err
2023-04-14 08:15:07 -04:00
}
2023-10-31 07:46:40 -04:00
infraState , err := creator . Apply ( ctx , config . GetProvider ( ) , cloudcmd . WithoutRollbackOnError )
2022-10-07 03:38:43 -04:00
if err != nil {
return err
}
2023-10-09 07:04:29 -04:00
infraState . UID = constants . MiniConstellationUID // use UID "mini" to identify MiniConstellation clusters.
stateFile := state . New ( ) .
SetInfrastructure ( infraState )
m . log . Debugf ( "Cluster state file contains %v" , stateFile )
2023-10-16 09:05:29 -04:00
return stateFile . WriteToFile ( m . fileHandler , constants . StateFilename )
2022-10-07 03:38:43 -04:00
}
// initializeMiniCluster initializes a QEMU cluster.
2023-10-24 09:39:18 -04:00
func ( m * miniUpCmd ) initializeMiniCluster ( cmd * cobra . Command ) ( retErr error ) {
2023-01-04 04:46:29 -05:00
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-10-16 09:05:29 -04:00
2023-10-24 09:39:18 -04:00
// Define flags for apply backend that are not set by mini up
cmd . Flags ( ) . StringSlice (
"skip-phases" ,
[ ] string { string ( skipInfrastructurePhase ) , string ( skipK8sPhase ) , string ( skipImagePhase ) } ,
"" ,
)
cmd . Flags ( ) . Bool ( "yes" , false , "" )
cmd . Flags ( ) . Bool ( "skip-helm-wait" , false , "" )
cmd . Flags ( ) . Bool ( "conformance" , false , "" )
cmd . Flags ( ) . Duration ( "timeout" , time . Hour , "" )
2023-10-16 09:05:29 -04:00
2023-10-24 09:39:18 -04:00
if err := runApply ( cmd , nil ) ; 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
}