2022-03-22 11:03:15 -04:00
package cmd
import (
"errors"
"fmt"
"io/fs"
2022-06-07 10:30:41 -04:00
"github.com/edgelesssys/constellation/cli/internal/azure"
2022-06-08 02:26:08 -04:00
"github.com/edgelesssys/constellation/cli/internal/cloudcmd"
2022-06-07 08:52:47 -04:00
"github.com/edgelesssys/constellation/cli/internal/gcp"
2022-06-07 05:08:44 -04:00
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
2022-04-06 04:36:58 -04:00
"github.com/edgelesssys/constellation/internal/constants"
2022-05-16 11:32:00 -04:00
"github.com/edgelesssys/constellation/internal/file"
2022-04-13 07:01:38 -04:00
"github.com/spf13/afero"
2022-03-22 11:03:15 -04:00
"github.com/spf13/cobra"
)
2022-06-08 02:14:28 -04:00
// NewCreateCmd returns a new cobra.Command for the create command.
func NewCreateCmd ( ) * cobra . Command {
2022-03-22 11:03:15 -04:00
cmd := & cobra . Command {
2022-05-10 06:38:43 -04:00
Use : "create {aws|azure|gcp}" ,
2022-05-06 11:51:41 -04:00
Short : "Create instances on a cloud platform for your Constellation cluster" ,
2022-05-04 03:13:46 -04:00
Long : "Create instances on a cloud platform for your Constellation cluster." ,
2022-04-13 07:01:38 -04:00
Args : cobra . MatchAll (
2022-05-04 02:50:50 -04:00
cobra . ExactArgs ( 1 ) ,
isCloudProvider ( 0 ) ,
2022-04-13 07:01:38 -04:00
warnAWS ( 0 ) ,
) ,
ValidArgsFunction : createCompletion ,
RunE : runCreate ,
2022-03-22 11:03:15 -04:00
}
2022-05-09 11:02:47 -04:00
cmd . Flags ( ) . String ( "name" , "constell" , "create the cluster with the specified name" )
cmd . Flags ( ) . BoolP ( "yes" , "y" , false , "create the cluster without further confirmation" )
2022-05-04 12:41:24 -04:00
cmd . Flags ( ) . IntP ( "control-plane-nodes" , "c" , 0 , "number of control-plane nodes (required)" )
2022-05-04 02:50:50 -04:00
must ( cobra . MarkFlagRequired ( cmd . Flags ( ) , "control-plane-nodes" ) )
2022-05-04 12:41:24 -04:00
cmd . Flags ( ) . IntP ( "worker-nodes" , "w" , 0 , "number of worker nodes (required)" )
2022-05-04 02:50:50 -04:00
must ( cobra . MarkFlagRequired ( cmd . Flags ( ) , "worker-nodes" ) )
2022-05-06 11:51:41 -04:00
cmd . Flags ( ) . StringP ( "instance-type" , "t" , "" , "instance type of cluster nodes" )
2022-05-04 02:50:50 -04:00
must ( cmd . RegisterFlagCompletionFunc ( "instance-type" , instanceTypeCompletion ) )
2022-05-10 06:34:47 -04:00
cmd . SetHelpTemplate ( cmd . HelpTemplate ( ) + fmt . Sprintf ( `
Azure instance types :
% v
GCP instance types :
% v
` , formatInstanceTypes ( azure . InstanceTypes ) , formatInstanceTypes ( gcp . InstanceTypes ) ) )
2022-03-22 11:03:15 -04:00
return cmd
}
2022-04-13 07:01:38 -04:00
func runCreate ( cmd * cobra . Command , args [ ] string ) error {
provider := cloudprovider . FromString ( args [ 0 ] )
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
creator := cloudcmd . NewCreator ( cmd . OutOrStdout ( ) )
2022-05-04 02:50:50 -04:00
return create ( cmd , creator , fileHandler , provider )
2022-04-13 07:01:38 -04:00
}
2022-05-04 02:50:50 -04:00
func create ( cmd * cobra . Command , creator cloudCreator , fileHandler file . Handler , provider cloudprovider . Provider ,
2022-04-13 07:01:38 -04:00
) ( retErr error ) {
2022-05-04 02:50:50 -04:00
flags , err := parseCreateFlags ( cmd , provider )
2022-04-13 07:01:38 -04:00
if err != nil {
return err
}
if err := checkDirClean ( fileHandler ) ; err != nil {
return err
}
2022-05-23 09:01:39 -04:00
config , err := readConfig ( cmd . OutOrStdout ( ) , fileHandler , flags . configPath , provider )
2022-04-13 07:01:38 -04:00
if err != nil {
2022-06-09 10:10:42 -04:00
return fmt . Errorf ( "reading and validating config: %w" , err )
2022-04-13 07:01:38 -04:00
}
if ! flags . yes {
// Ask user to confirm action.
2022-05-04 03:13:46 -04:00
cmd . Printf ( "The following Constellation cluster will be created:\n" )
2022-05-04 02:50:50 -04:00
cmd . Printf ( "%d control-planes nodes of type %s will be created.\n" , flags . controllerCount , flags . insType )
cmd . Printf ( "%d worker nodes of type %s will be created.\n" , flags . workerCount , flags . insType )
2022-05-04 03:13:46 -04:00
ok , err := askToConfirm ( cmd , "Do you want to create this cluster?" )
2022-04-13 07:01:38 -04:00
if err != nil {
return err
}
if ! ok {
2022-05-04 03:13:46 -04:00
cmd . Println ( "The creation of the cluster was aborted." )
2022-04-13 07:01:38 -04:00
return nil
}
}
2022-05-04 02:50:50 -04:00
state , err := creator . Create ( cmd . Context ( ) , provider , config , flags . name , flags . insType , flags . controllerCount , flags . workerCount )
2022-04-13 07:01:38 -04:00
if err != nil {
return err
}
if err := fileHandler . WriteJSON ( constants . StateFilename , state , file . OptNone ) ; err != nil {
return err
}
2022-05-04 03:13:46 -04:00
cmd . Println ( "Your Constellation cluster was created successfully." )
2022-04-13 07:01:38 -04:00
return nil
}
// parseCreateFlags parses the flags of the create command.
2022-05-04 02:50:50 -04:00
func parseCreateFlags ( cmd * cobra . Command , provider cloudprovider . Provider ) ( createFlags , error ) {
controllerCount , err := cmd . Flags ( ) . GetInt ( "control-plane-nodes" )
if err != nil {
2022-06-09 10:10:42 -04:00
return createFlags { } , fmt . Errorf ( "parsing number of control-plane nodes: %w" , err )
2022-05-04 02:50:50 -04:00
}
if controllerCount < constants . MinControllerCount {
return createFlags { } , fmt . Errorf ( "number of control-plane nodes must be at least %d" , constants . MinControllerCount )
}
workerCount , err := cmd . Flags ( ) . GetInt ( "worker-nodes" )
if err != nil {
2022-06-09 10:10:42 -04:00
return createFlags { } , fmt . Errorf ( "parsing number of worker nodes: %w" , err )
2022-05-04 02:50:50 -04:00
}
if workerCount < constants . MinWorkerCount {
return createFlags { } , fmt . Errorf ( "number of worker nodes must be at least %d" , constants . MinWorkerCount )
}
insType , err := cmd . Flags ( ) . GetString ( "instance-type" )
if err != nil {
2022-06-09 10:10:42 -04:00
return createFlags { } , fmt . Errorf ( "parsing instance type argument: %w" , err )
2022-05-04 02:50:50 -04:00
}
if insType == "" {
insType = defaultInstanceType ( provider )
}
2022-05-10 06:34:47 -04:00
if err := validInstanceTypeForProvider ( cmd , insType , provider ) ; err != nil {
2022-05-04 02:50:50 -04:00
return createFlags { } , err
}
2022-04-13 07:01:38 -04:00
name , err := cmd . Flags ( ) . GetString ( "name" )
if err != nil {
2022-06-09 10:10:42 -04:00
return createFlags { } , fmt . Errorf ( "parsing name argument: %w" , err )
2022-04-13 07:01:38 -04:00
}
2022-04-12 10:07:17 -04:00
if len ( name ) > constants . ConstellationNameLength {
2022-04-13 07:01:38 -04:00
return createFlags { } , fmt . Errorf (
2022-05-04 03:13:46 -04:00
"name for Constellation cluster too long, maximum length is %d, got %d: %s" ,
2022-04-12 10:07:17 -04:00
constants . ConstellationNameLength , len ( name ) , name ,
2022-04-13 07:01:38 -04:00
)
}
2022-05-04 02:50:50 -04:00
2022-04-13 07:01:38 -04:00
yes , err := cmd . Flags ( ) . GetBool ( "yes" )
if err != nil {
2022-06-09 10:10:42 -04:00
return createFlags { } , fmt . Errorf ( "%w; Set '-yes' without a value to automatically confirm" , err )
2022-04-13 07:01:38 -04:00
}
2022-05-04 02:50:50 -04:00
2022-05-13 05:56:43 -04:00
configPath , err := cmd . Flags ( ) . GetString ( "config" )
2022-04-13 07:01:38 -04:00
if err != nil {
2022-06-09 10:10:42 -04:00
return createFlags { } , fmt . Errorf ( "parsing config path argument: %w" , err )
2022-04-13 07:01:38 -04:00
}
return createFlags {
2022-05-04 02:50:50 -04:00
controllerCount : controllerCount ,
workerCount : workerCount ,
insType : insType ,
name : name ,
2022-05-13 05:56:43 -04:00
configPath : configPath ,
2022-05-04 02:50:50 -04:00
yes : yes ,
2022-04-13 07:01:38 -04:00
} , nil
}
// createFlags contains the parsed flags of the create command.
type createFlags struct {
2022-05-04 02:50:50 -04:00
controllerCount int
workerCount int
insType string
name string
2022-05-13 05:56:43 -04:00
configPath string
2022-05-04 02:50:50 -04:00
yes bool
}
// defaultInstanceType returns the default instance type for the given provider.
func defaultInstanceType ( provider cloudprovider . Provider ) string {
switch provider {
case cloudprovider . GCP :
return gcp . InstanceTypes [ 0 ]
case cloudprovider . Azure :
return azure . InstanceTypes [ 0 ]
default :
return ""
}
2022-04-13 07:01:38 -04:00
}
2022-03-22 11:03:15 -04:00
// checkDirClean checks if files of a previous Constellation are left in the current working dir.
2022-04-06 04:36:58 -04:00
func checkDirClean ( fileHandler file . Handler ) error {
if _ , err := fileHandler . Stat ( constants . StateFilename ) ; ! errors . Is ( err , fs . ErrNotExist ) {
return fmt . Errorf ( "file '%s' already exists in working directory, run 'constellation terminate' before creating a new one" , constants . StateFilename )
2022-03-22 11:03:15 -04:00
}
2022-04-06 04:36:58 -04:00
if _ , err := fileHandler . Stat ( constants . AdminConfFilename ) ; ! errors . Is ( err , fs . ErrNotExist ) {
return fmt . Errorf ( "file '%s' already exists in working directory, run 'constellation terminate' before creating a new one" , constants . AdminConfFilename )
2022-03-22 11:03:15 -04:00
}
2022-04-06 04:36:58 -04:00
if _ , err := fileHandler . Stat ( constants . MasterSecretFilename ) ; ! errors . Is ( err , fs . ErrNotExist ) {
2022-06-09 10:10:42 -04: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" , constants . MasterSecretFilename )
2022-03-22 11:03:15 -04:00
}
return nil
}
2022-04-13 07:01:38 -04:00
// createCompletion handles the completion of the create command. It is frequently called
// while the user types arguments of the command to suggest completion.
func createCompletion ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
switch len ( args ) {
case 0 :
return [ ] string { "aws" , "gcp" , "azure" } , cobra . ShellCompDirectiveNoFileComp
2022-05-04 02:50:50 -04:00
default :
return [ ] string { } , cobra . ShellCompDirectiveError
}
}
2022-06-08 02:14:28 -04:00
func must ( err error ) {
if err != nil {
panic ( err )
}
}
2022-05-04 02:50:50 -04:00
func instanceTypeCompletion ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if len ( args ) != 1 {
return [ ] string { } , cobra . ShellCompDirectiveError
}
switch args [ 0 ] {
case "gcp" :
return gcp . InstanceTypes , cobra . ShellCompDirectiveNoFileComp
case "azure" :
return azure . InstanceTypes , cobra . ShellCompDirectiveNoFileComp
2022-04-13 07:01:38 -04:00
default :
return [ ] string { } , cobra . ShellCompDirectiveError
}
}