2022-09-05 07:06:08 +00:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-03-22 15:03:15 +00:00
package cmd
import (
"errors"
"fmt"
"io/fs"
2022-09-21 11:47:57 +00:00
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
2023-09-25 14:19:43 +00:00
"github.com/edgelesssys/constellation/v2/cli/internal/state"
2023-06-07 14:16:32 +00:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-06-27 16:24:35 +00:00
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
2023-06-09 13:41:02 +00:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
2022-11-15 14:40:49 +00:00
"github.com/edgelesssys/constellation/v2/internal/config"
2022-09-21 11:47:57 +00:00
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
2023-06-27 16:24:35 +00:00
"github.com/edgelesssys/constellation/v2/internal/semver"
2022-04-13 11:01:38 +00:00
"github.com/spf13/afero"
2022-03-22 15:03:15 +00:00
"github.com/spf13/cobra"
2023-10-16 13:05:29 +00:00
"github.com/spf13/pflag"
2022-03-22 15:03:15 +00:00
)
2022-06-08 06:14:28 +00:00
// NewCreateCmd returns a new cobra.Command for the create command.
func NewCreateCmd ( ) * cobra . Command {
2022-03-22 15:03:15 +00:00
cmd := & cobra . Command {
2022-09-07 13:38:29 +00:00
Use : "create" ,
2022-05-06 15:51:41 +00:00
Short : "Create instances on a cloud platform for your Constellation cluster" ,
2022-05-04 07:13:46 +00:00
Long : "Create instances on a cloud platform for your Constellation cluster." ,
2022-12-07 10:48:54 +00:00
Args : cobra . ExactArgs ( 0 ) ,
RunE : runCreate ,
2022-03-22 15:03:15 +00:00
}
2022-05-09 15:02:47 +00:00
cmd . Flags ( ) . BoolP ( "yes" , "y" , false , "create the cluster without further confirmation" )
2022-03-22 15:03:15 +00:00
return cmd
}
2023-10-16 13:05:29 +00:00
// createFlags contains the parsed flags of the create command.
type createFlags struct {
rootFlags
yes bool
}
// parse parses the flags of the create command.
func ( f * createFlags ) parse ( flags * pflag . FlagSet ) error {
if err := f . rootFlags . parse ( flags ) ; err != nil {
return err
}
yes , err := flags . GetBool ( "yes" )
if err != nil {
return fmt . Errorf ( "getting 'yes' flag: %w" , err )
}
f . yes = yes
return nil
}
2023-01-04 09:46:29 +00:00
type createCmd struct {
2023-10-16 13:05:29 +00:00
log debugLog
flags createFlags
2023-01-04 09:46:29 +00:00
}
2023-03-20 10:03:36 +00:00
func runCreate ( cmd * cobra . Command , _ [ ] string ) error {
2023-01-04 09:46:29 +00:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
defer log . Sync ( )
2023-01-18 12:10:24 +00:00
spinner , err := newSpinnerOrStderr ( cmd )
2023-01-04 10:00:07 +00:00
if err != nil {
return fmt . Errorf ( "creating spinner: %w" , err )
}
2022-10-07 17:35:07 +00:00
defer spinner . Stop ( )
2023-01-04 10:00:07 +00:00
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
2022-10-21 12:26:42 +00:00
creator := cloudcmd . NewCreator ( spinner )
2023-01-04 09:46:29 +00:00
c := & createCmd { log : log }
2023-10-16 13:05:29 +00:00
if err := c . flags . parse ( cmd . Flags ( ) ) ; err != nil {
return err
}
c . log . Debugf ( "Using flags: %+v" , c . flags )
2023-06-07 14:16:32 +00:00
fetcher := attestationconfigapi . NewFetcher ( )
2023-06-01 11:55:46 +00:00
return c . create ( cmd , creator , fileHandler , spinner , fetcher )
2022-04-13 11:01:38 +00:00
}
2023-06-07 14:16:32 +00:00
func ( c * createCmd ) create ( cmd * cobra . Command , creator cloudCreator , fileHandler file . Handler , spinner spinnerInterf , fetcher attestationconfigapi . Fetcher ) ( retErr error ) {
2023-08-08 13:42:06 +00:00
if err := c . checkDirClean ( fileHandler ) ; err != nil {
2022-04-13 11:01:38 +00:00
return err
}
2023-10-16 13:05:29 +00:00
c . log . Debugf ( "Loading configuration file from %q" , c . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) )
conf , err := config . New ( fileHandler , constants . ConfigFilename , fetcher , c . flags . force )
2023-03-31 17:19:10 +00:00
c . log . Debugf ( "Configuration file loaded: %+v" , conf )
2023-02-07 11:56:25 +00:00
var configValidationErr * config . ValidationError
if errors . As ( err , & configValidationErr ) {
cmd . PrintErrln ( configValidationErr . LongMessage ( ) )
}
2022-04-13 11:01:38 +00:00
if err != nil {
2023-02-07 11:56:25 +00:00
return err
2022-04-13 11:01:38 +00:00
}
2023-10-16 13:05:29 +00:00
if ! c . flags . force {
2023-07-25 12:20:25 +00:00
if err := validateCLIandConstellationVersionAreEqual ( constants . BinaryVersion ( ) , conf . Image , conf . MicroserviceVersion ) ; err != nil {
2023-06-27 16:24:35 +00:00
return err
}
}
2022-04-13 11:01:38 +00:00
2023-01-04 09:46:29 +00:00
c . log . Debugf ( "Checking configuration for warnings" )
2022-09-06 11:05:49 +00:00
var printedAWarning bool
2022-11-22 17:47:08 +00:00
if ! conf . IsReleaseImage ( ) {
2022-11-10 09:27:24 +00:00
cmd . PrintErrln ( "Configured image doesn't look like a released production image. Double check image before deploying to production." )
2022-09-06 11:05:49 +00:00
printedAWarning = true
}
2023-06-19 14:51:39 +00:00
if conf . IsNamedLikeDebugImage ( ) && ! conf . IsDebugCluster ( ) {
cmd . PrintErrln ( "WARNING: A debug image is used but debugCluster is false." )
printedAWarning = true
}
2022-11-15 14:40:49 +00:00
if conf . IsDebugCluster ( ) {
2022-11-10 09:27:24 +00:00
cmd . PrintErrln ( "WARNING: Creating a debug cluster. This cluster is not secure and should only be used for debugging purposes." )
cmd . PrintErrln ( "DO NOT USE THIS CLUSTER IN PRODUCTION." )
2022-09-06 11:05:49 +00:00
printedAWarning = true
2022-08-16 13:53:54 +00:00
}
2023-05-03 09:11:53 +00:00
if conf . GetAttestationConfig ( ) . GetVariant ( ) . Equal ( variant . AzureTrustedLaunch { } ) {
2022-11-10 09:27:24 +00:00
cmd . PrintErrln ( "Disabling Confidential VMs is insecure. Use only for evaluation purposes." )
2022-09-06 11:05:49 +00:00
printedAWarning = true
2022-08-25 13:24:31 +00:00
}
2022-09-06 11:05:49 +00:00
// Print an extra new line later to separate warnings from the prompt message of the create command
if printedAWarning {
2022-11-10 09:27:24 +00:00
cmd . PrintErrln ( "" )
2022-09-06 11:05:49 +00:00
}
2022-11-15 14:40:49 +00:00
provider := conf . GetProvider ( )
2023-08-02 08:36:55 +00:00
controlPlaneGroup , ok := conf . NodeGroups [ constants . DefaultControlPlaneGroupName ]
if ! ok {
return fmt . Errorf ( "default control-plane node group %q not found in configuration" , constants . DefaultControlPlaneGroupName )
}
workerGroup , ok := conf . NodeGroups [ constants . DefaultWorkerGroupName ]
if ! ok {
return fmt . Errorf ( "default worker node group %q not found in configuration" , constants . DefaultWorkerGroupName )
}
otherGroupNames := make ( [ ] string , 0 , len ( conf . NodeGroups ) - 2 )
for groupName := range conf . NodeGroups {
if groupName != constants . DefaultControlPlaneGroupName && groupName != constants . DefaultWorkerGroupName {
otherGroupNames = append ( otherGroupNames , groupName )
}
}
if len ( otherGroupNames ) > 0 {
c . log . Debugf ( "Creating %d additional node groups: %v" , len ( otherGroupNames ) , otherGroupNames )
2022-08-31 15:35:33 +00:00
}
2023-10-16 13:05:29 +00:00
if ! c . flags . yes {
2022-04-13 11:01:38 +00:00
// Ask user to confirm action.
2022-05-04 07:13:46 +00:00
cmd . Printf ( "The following Constellation cluster will be created:\n" )
2023-08-02 08:36:55 +00:00
cmd . Printf ( " %d control-plane node%s of type %s will be created.\n" , controlPlaneGroup . InitialCount , isPlural ( controlPlaneGroup . InitialCount ) , controlPlaneGroup . InstanceType )
cmd . Printf ( " %d worker node%s of type %s will be created.\n" , workerGroup . InitialCount , isPlural ( workerGroup . InitialCount ) , workerGroup . InstanceType )
for _ , groupName := range otherGroupNames {
group := conf . NodeGroups [ groupName ]
cmd . Printf ( " group %s with %d node%s of type %s will be created.\n" , groupName , group . InitialCount , isPlural ( group . InitialCount ) , group . InstanceType )
}
2022-05-04 07:13:46 +00:00
ok , err := askToConfirm ( cmd , "Do you want to create this cluster?" )
2022-04-13 11:01:38 +00:00
if err != nil {
return err
}
if ! ok {
2022-05-04 07:13:46 +00:00
cmd . Println ( "The creation of the cluster was aborted." )
2022-04-13 11:01:38 +00:00
return nil
}
}
2022-10-07 17:35:07 +00:00
spinner . Start ( "Creating" , false )
2023-04-14 12:15:07 +00:00
opts := cloudcmd . CreateOptions {
2023-08-04 11:53:51 +00:00
Provider : provider ,
Config : conf ,
2023-10-16 13:05:29 +00:00
TFLogLevel : c . flags . tfLogLevel ,
2023-08-04 11:53:51 +00:00
TFWorkspace : constants . TerraformWorkingDir ,
2023-04-14 12:15:07 +00:00
}
2023-09-25 14:19:43 +00:00
infraState , err := creator . Create ( cmd . Context ( ) , opts )
2022-10-04 16:17:05 +00:00
spinner . Stop ( )
2022-04-13 11:01:38 +00:00
if err != nil {
2023-10-26 08:55:50 +00:00
return err
2022-04-13 11:01:38 +00:00
}
2023-02-03 10:05:42 +00:00
c . log . Debugf ( "Successfully created the cloud resources for the cluster" )
2022-04-13 11:01:38 +00:00
2023-10-17 12:37:09 +00:00
stateFile , err := state . CreateOrRead ( fileHandler , constants . StateFilename )
2023-10-16 18:18:59 +00:00
if err != nil {
return fmt . Errorf ( "reading state file: %w" , err )
}
stateFile = stateFile . SetInfrastructure ( infraState )
if err := stateFile . WriteToFile ( fileHandler , constants . StateFilename ) ; err != nil {
2023-10-09 11:04:29 +00:00
return fmt . Errorf ( "writing state file: %w" , err )
2023-09-25 14:19:43 +00:00
}
2022-07-29 08:01:10 +00:00
2022-05-04 07:13:46 +00:00
cmd . Println ( "Your Constellation cluster was created successfully." )
2022-04-13 11:01:38 +00:00
return nil
}
2022-03-22 15:03:15 +00:00
// checkDirClean checks if files of a previous Constellation are left in the current working dir.
2023-08-08 13:42:06 +00:00
func ( c * createCmd ) checkDirClean ( fileHandler file . Handler ) error {
2023-01-04 09:46:29 +00:00
c . log . Debugf ( "Checking admin configuration file" )
2022-04-06 08:36:58 +00:00
if _ , err := fileHandler . Stat ( constants . AdminConfFilename ) ; ! errors . Is ( err , fs . ErrNotExist ) {
2023-10-16 13:05:29 +00:00
return fmt . Errorf (
"file '%s' already exists in working directory, run 'constellation terminate' before creating a new one" ,
c . flags . pathPrefixer . PrefixPrintablePath ( constants . AdminConfFilename ) ,
)
2022-03-22 15:03:15 +00:00
}
2023-01-04 09:46:29 +00:00
c . log . Debugf ( "Checking master secrets file" )
2022-04-06 08:36:58 +00:00
if _ , err := fileHandler . Stat ( constants . MasterSecretFilename ) ; ! errors . Is ( err , fs . ErrNotExist ) {
2023-10-16 13:05:29 +00:00
return fmt . Errorf (
"file '%s' already exists in working directory. Constellation won't overwrite previous master secrets. Move it somewhere or delete it before creating a new cluster" ,
c . flags . pathPrefixer . PrefixPrintablePath ( constants . MasterSecretFilename ) ,
)
2022-03-22 15:03:15 +00:00
}
2023-10-26 08:55:50 +00:00
c . log . Debugf ( "Checking Terraform working directory" )
if _ , err := fileHandler . Stat ( constants . TerraformWorkingDir ) ; ! errors . Is ( err , fs . ErrNotExist ) {
return fmt . Errorf (
"directory '%s' already exists in working directory, run 'constellation terminate' before creating a new one" ,
c . flags . pathPrefixer . PrefixPrintablePath ( constants . TerraformWorkingDir ) ,
)
}
2022-03-22 15:03:15 +00:00
return nil
}
2022-04-13 11:01:38 +00:00
2023-02-17 07:15:17 +00:00
func isPlural ( count int ) string {
if count == 1 {
return ""
}
return "s"
}
2023-06-27 16:24:35 +00:00
// validateCLIandConstellationVersionAreEqual checks if the image and microservice version are equal (down to patch level) to the CLI version.
2023-07-25 12:20:25 +00:00
func validateCLIandConstellationVersionAreEqual ( cliVersion semver . Semver , imageVersion string , microserviceVersion semver . Semver ) error {
2023-06-27 16:24:35 +00:00
parsedImageVersion , err := versionsapi . NewVersionFromShortPath ( imageVersion , versionsapi . VersionKindImage )
if err != nil {
return fmt . Errorf ( "parsing image version: %w" , err )
}
2023-08-01 14:48:13 +00:00
semImage , err := semver . New ( parsedImageVersion . Version ( ) )
2023-06-27 16:24:35 +00:00
if err != nil {
return fmt . Errorf ( "parsing image semantical version: %w" , err )
}
2023-07-25 12:20:25 +00:00
if ! cliVersion . MajorMinorEqual ( semImage ) {
return fmt . Errorf ( "image version %q does not match the major and minor version of the cli version %q" , semImage . String ( ) , cliVersion . String ( ) )
2023-06-27 16:24:35 +00:00
}
2023-07-25 12:20:25 +00:00
if cliVersion . Compare ( microserviceVersion ) != 0 {
return fmt . Errorf ( "cli version %q does not match microservice version %q" , cliVersion . String ( ) , microserviceVersion . String ( ) )
2023-06-27 16:24:35 +00:00
}
return nil
}