2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-03-22 11:03:15 -04:00
package cmd
import (
"errors"
"fmt"
"io/fs"
2022-06-08 02:26:08 -04:00
"github.com/edgelesssys/constellation/cli/internal/cloudcmd"
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-07-29 04:01:10 -04:00
"github.com/edgelesssys/constellation/internal/state"
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-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-08-31 11:35:33 -04:00
flags , err := parseCreateFlags ( cmd )
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
}
2022-08-16 09:53:54 -04:00
if config . IsImageDebug ( ) {
cmd . Println ( "Configured image does not look like a released production image. Double check image before deploying to production." )
}
2022-08-25 09:24:31 -04:00
if config . IsAzureNonCVM ( ) {
cmd . Println ( "Disabling Confidential VMs is insecure. Use only for evaluation purposes." )
2022-08-31 14:10:49 -04:00
if config . EnforcesIdKeyDigest ( ) {
cmd . Println ( "Your config asks for enforcing the idkeydigest. This is only available on Confidential VMs. It will not be enforced." )
}
2022-08-25 09:24:31 -04:00
}
2022-08-31 11:35:33 -04:00
var instanceType string
switch provider {
case cloudprovider . Azure :
instanceType = config . Provider . Azure . InstanceType
case cloudprovider . GCP :
instanceType = config . Provider . GCP . InstanceType
}
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-08-31 11:35:33 -04:00
cmd . Printf ( "%d control-planes nodes of type %s will be created.\n" , flags . controllerCount , instanceType )
cmd . Printf ( "%d worker nodes of type %s will be created.\n" , flags . workerCount , instanceType )
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-08-31 11:35:33 -04:00
state , err := creator . Create ( cmd . Context ( ) , provider , config , flags . name , instanceType , 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-07-29 04:01:10 -04:00
if err := writeIPtoIDFile ( fileHandler , state ) ; 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-08-31 11:35:33 -04:00
func parseCreateFlags ( cmd * cobra . Command ) ( createFlags , error ) {
2022-05-04 02:50:50 -04:00
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 )
}
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 ,
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
name string
2022-05-13 05:56:43 -04:00
configPath string
2022-05-04 02:50:50 -04:00
yes bool
}
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
}
2022-07-29 04:01:10 -04:00
if _ , err := fileHandler . Stat ( constants . ClusterIDsFileName ) ; ! errors . Is ( err , fs . ErrNotExist ) {
return fmt . Errorf ( "file '%s' already exists in working directory. Constellation won't overwrite previous cluster IDs. Move it somewhere or delete it before creating a new cluster" , constants . ClusterIDsFileName )
}
2022-03-22 11:03:15 -04:00
return nil
}
2022-04-13 07:01:38 -04:00
2022-07-29 04:01:10 -04:00
func writeIPtoIDFile ( fileHandler file . Handler , state state . ConstellationState ) error {
2022-08-01 10:51:34 -04:00
ip := state . LoadBalancerIP
2022-07-29 04:01:10 -04:00
if ip == "" {
return fmt . Errorf ( "bootstrapper ip not found" )
}
idFile := clusterIDsFile { IP : ip }
return fileHandler . WriteJSON ( constants . ClusterIDsFileName , idFile , file . OptNone )
}
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 )
}
}