2022-12-07 05:48:54 -05:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
package cmd
import (
2023-02-01 05:32:01 -05:00
"context"
"encoding/base64"
"encoding/json"
"fmt"
"regexp"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"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"
2022-12-07 05:48:54 -05:00
"github.com/spf13/cobra"
2023-10-16 09:05:29 -04:00
"github.com/spf13/pflag"
2022-12-07 05:48:54 -05:00
)
2023-02-01 05:32:01 -05:00
var (
// GCP-specific validation regexes
2023-07-04 07:55:52 -04:00
// Source: https://cloud.google.com/compute/docs/regions-zones
zoneRegex = regexp . MustCompile ( ` ^\w+-\w+-[abc]$ ` )
regionRegex = regexp . MustCompile ( ` ^\w+-\w+[0-9]$ ` )
2023-02-01 05:32:01 -05:00
// Source: https://cloud.google.com/resource-manager/reference/rest/v1/projects.
2023-07-05 10:44:57 -04:00
gcpIDRegex = regexp . MustCompile ( ` ^[a-z][-a-z0-9] { 4,28}[a-z0-9]$ ` )
2023-02-01 05:32:01 -05:00
)
2023-10-16 09:05:29 -04:00
// newIAMCreateCmd returns a new cobra.Command for the iam create parent command. It needs another verb, and does nothing on its own.
2022-12-07 05:48:54 -05:00
func newIAMCreateCmd ( ) * cobra . Command {
cmd := & cobra . Command {
Use : "create" ,
Short : "Create IAM configuration on a cloud platform for your Constellation cluster" ,
Long : "Create IAM configuration on a cloud platform for your Constellation cluster." ,
Args : cobra . ExactArgs ( 0 ) ,
}
2023-03-31 11:26:14 -04:00
cmd . PersistentFlags ( ) . BoolP ( "yes" , "y" , false , "create the IAM configuration without further confirmation" )
2023-07-05 10:44:57 -04:00
cmd . PersistentFlags ( ) . Bool ( "update-config" , false , "update the config file with the specific IAM information" )
2023-01-12 05:35:26 -05:00
2022-12-07 05:48:54 -05:00
cmd . AddCommand ( newIAMCreateAWSCmd ( ) )
cmd . AddCommand ( newIAMCreateAzureCmd ( ) )
cmd . AddCommand ( newIAMCreateGCPCmd ( ) )
return cmd
}
2023-02-01 05:32:01 -05:00
2023-10-16 09:05:29 -04:00
type iamCreateFlags struct {
rootFlags
yes bool
updateConfig bool
2023-02-01 05:32:01 -05:00
}
2023-10-16 09:05:29 -04:00
func ( f * iamCreateFlags ) parse ( flags * pflag . FlagSet ) error {
var err error
if err = f . rootFlags . parse ( flags ) ; err != nil {
return err
2023-02-01 05:32:01 -05:00
}
2023-10-16 09:05:29 -04:00
f . yes , err = flags . GetBool ( "yes" )
if err != nil {
return fmt . Errorf ( "getting 'yes' flag: %w" , err )
2023-02-01 05:32:01 -05:00
}
2023-10-16 09:05:29 -04:00
f . updateConfig , err = flags . GetBool ( "update-config" )
if err != nil {
return fmt . Errorf ( "getting 'update-config' flag: %w" , err )
2023-02-01 05:32:01 -05:00
}
2023-10-16 09:05:29 -04:00
return nil
2023-02-01 05:32:01 -05:00
}
2023-10-16 09:05:29 -04:00
func runIAMCreate ( cmd * cobra . Command , providerCreator providerIAMCreator , provider cloudprovider . Provider ) error {
2023-02-09 04:37:22 -05:00
spinner , err := newSpinnerOrStderr ( cmd )
if err != nil {
2023-10-16 09:05:29 -04:00
return fmt . Errorf ( "creating spinner: %w" , err )
2023-02-09 04:37:22 -05:00
}
2023-10-16 09:05:29 -04:00
defer spinner . Stop ( )
2023-02-09 04:37:22 -05:00
log , err := newCLILogger ( cmd )
if err != nil {
2023-10-16 09:05:29 -04:00
return fmt . Errorf ( "creating logger: %w" , err )
}
iamCreator := & iamCreator {
cmd : cmd ,
spinner : spinner ,
log : log ,
creator : cloudcmd . NewIAMCreator ( spinner ) ,
fileHandler : file . NewHandler ( afero . NewOsFs ( ) ) ,
providerCreator : providerCreator ,
provider : provider ,
}
if err := iamCreator . flags . parse ( cmd . Flags ( ) ) ; err != nil {
return err
2023-02-09 04:37:22 -05:00
}
2023-04-14 08:15:07 -04:00
2023-10-16 09:05:29 -04:00
return iamCreator . create ( cmd . Context ( ) )
2023-02-01 05:32:01 -05:00
}
// iamCreator is the iamCreator for the iam create command.
type iamCreator struct {
cmd * cobra . Command
spinner spinnerInterf
creator cloudIAMCreator
fileHandler file . Handler
provider cloudprovider . Provider
providerCreator providerIAMCreator
2023-04-14 08:15:07 -04:00
iamConfig * cloudcmd . IAMConfigOptions
2023-02-09 04:37:22 -05:00
log debugLog
2023-10-16 09:05:29 -04:00
flags iamCreateFlags
2023-02-01 05:32:01 -05:00
}
// create IAM configuration on the iamCreator's cloud provider.
func ( c * iamCreator ) create ( ctx context . Context ) error {
2023-08-08 09:42:06 -04:00
if err := c . checkWorkingDir ( ) ; err != nil {
2023-02-13 02:42:54 -05:00
return err
}
2023-10-16 09:05:29 -04:00
if ! c . flags . yes {
2023-02-01 05:32:01 -05:00
c . cmd . Printf ( "The following IAM configuration will be created:\n\n" )
2023-10-16 09:05:29 -04:00
c . providerCreator . printConfirmValues ( c . cmd )
2023-02-01 05:32:01 -05:00
ok , err := askToConfirm ( c . cmd , "Do you want to create the configuration?" )
if err != nil {
return err
}
if ! ok {
c . cmd . Println ( "The creation of the configuration was aborted." )
return nil
}
}
2023-06-28 08:47:44 -04:00
var conf config . Config
2023-10-16 09:05:29 -04:00
if c . flags . updateConfig {
2024-04-03 09:49:03 -04:00
c . log . Debug ( fmt . Sprintf ( "Parsing config %q" , c . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) ) )
2023-10-16 09:05:29 -04:00
if err := c . fileHandler . ReadYAML ( constants . ConfigFilename , & conf ) ; err != nil {
2023-06-28 08:47:44 -04:00
return fmt . Errorf ( "error reading the configuration file: %w" , err )
}
2023-10-16 09:05:29 -04:00
if err := c . providerCreator . validateConfigWithFlagCompatibility ( conf ) ; err != nil {
2023-07-04 07:55:52 -04:00
return err
}
2023-10-16 09:05:29 -04:00
c . cmd . Printf ( "The configuration file %q will be automatically updated with the IAM values and zone/region information.\n" , c . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) )
2023-06-28 08:47:44 -04:00
}
2023-02-01 05:32:01 -05:00
2023-10-16 09:05:29 -04:00
iamConfig := c . providerCreator . getIAMConfigOptions ( )
iamConfig . TFWorkspace = constants . TerraformIAMWorkingDir
iamConfig . TFLogLevel = c . flags . tfLogLevel
2023-06-28 08:47:44 -04:00
c . spinner . Start ( "Creating" , false )
2023-10-16 09:05:29 -04:00
iamFile , err := c . creator . Create ( ctx , c . provider , iamConfig )
2023-02-01 05:32:01 -05:00
c . spinner . Stop ( )
if err != nil {
return err
}
c . cmd . Println ( ) // Print empty line to separate after spinner ended.
2024-02-08 09:20:01 -05:00
c . log . Debug ( "Successfully created the IAM cloud resources" )
2023-02-01 05:32:01 -05:00
err = c . providerCreator . parseAndWriteIDFile ( iamFile , c . fileHandler )
if err != nil {
return err
}
2023-10-16 09:05:29 -04:00
if c . flags . updateConfig {
2024-04-03 09:49:03 -04:00
c . log . Debug ( fmt . Sprintf ( "Writing IAM configuration to %q" , c . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) ) )
2023-10-16 09:05:29 -04:00
c . providerCreator . writeOutputValuesToConfig ( & conf , iamFile )
2023-08-04 07:53:51 -04:00
if err := c . fileHandler . WriteYAML ( constants . ConfigFilename , conf , file . OptOverwrite ) ; err != nil {
2023-02-01 05:32:01 -05:00
return err
}
2023-10-16 09:05:29 -04:00
c . cmd . Printf ( "Your IAM configuration was created and filled into %s successfully.\n" , c . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) )
2023-02-01 05:32:01 -05:00
return nil
}
2023-10-16 09:05:29 -04:00
c . providerCreator . printOutputValues ( c . cmd , iamFile )
2023-02-01 05:32:01 -05:00
c . cmd . Println ( "Your IAM configuration was created successfully. Please fill the above values into your configuration file." )
return nil
}
2023-06-28 08:47:44 -04:00
// checkWorkingDir checks if the current working directory already contains a Terraform dir.
2023-08-08 09:42:06 -04:00
func ( c * iamCreator ) checkWorkingDir ( ) error {
2023-02-13 02:42:54 -05:00
if _ , err := c . fileHandler . Stat ( constants . TerraformIAMWorkingDir ) ; err == nil {
2023-10-16 09:05:29 -04:00
return fmt . Errorf (
"the current working directory already contains the Terraform workspace directory %q. Please run the command in a different directory or destroy the existing workspace" ,
c . flags . pathPrefixer . PrefixPrintablePath ( constants . TerraformIAMWorkingDir ) ,
)
2023-02-13 02:42:54 -05:00
}
return nil
}
2023-02-01 05:32:01 -05:00
// providerIAMCreator is an interface for the IAM actions of different cloud providers.
type providerIAMCreator interface {
// printConfirmValues prints the values that will be created on the cloud provider and need to be confirmed by the user.
2023-10-16 09:05:29 -04:00
printConfirmValues ( cmd * cobra . Command )
2023-02-01 05:32:01 -05:00
// printOutputValues prints the values that were created on the cloud provider.
2023-10-16 09:05:29 -04:00
printOutputValues ( cmd * cobra . Command , iamFile cloudcmd . IAMOutput )
2023-02-01 05:32:01 -05:00
// writeOutputValuesToConfig writes the output values of the IAM creation to the constellation config file.
2023-10-16 09:05:29 -04:00
writeOutputValuesToConfig ( conf * config . Config , iamFile cloudcmd . IAMOutput )
// getIAMConfigOptions sets up the IAM values required to create the IAM configuration.
getIAMConfigOptions ( ) * cloudcmd . IAMConfigOptions
2023-02-01 05:32:01 -05:00
// parseAndWriteIDFile parses the GCP service account key and writes it to a keyfile. It is only implemented for GCP.
2023-08-08 06:06:22 -04:00
parseAndWriteIDFile ( iamFile cloudcmd . IAMOutput , fileHandler file . Handler ) error
2023-02-01 05:32:01 -05:00
2023-10-16 09:05:29 -04:00
validateConfigWithFlagCompatibility ( config . Config ) error
2023-02-01 05:32:01 -05:00
}
// parseIDFile parses the given base64 encoded JSON string of the GCP service account key and returns a map.
func parseIDFile ( serviceAccountKeyBase64 string ) ( map [ string ] string , error ) {
dec , err := base64 . StdEncoding . DecodeString ( serviceAccountKeyBase64 )
if err != nil {
return nil , err
}
out := make ( map [ string ] string )
if err = json . Unmarshal ( dec , & out ) ; err != nil {
return nil , err
}
return out , nil
}
2023-05-03 05:07:47 -04:00
2023-07-04 07:55:52 -04:00
// validateConfigWithFlagCompatibility checks if the config is compatible with the flags.
2023-10-16 09:05:29 -04:00
func validateConfigWithFlagCompatibility ( iamProvider cloudprovider . Provider , cfg config . Config , zone string ) error {
2023-07-04 07:55:52 -04:00
if ! cfg . HasProvider ( iamProvider ) {
return fmt . Errorf ( "cloud provider from the the configuration file differs from the one provided via the command %q" , iamProvider )
}
2023-10-16 09:05:29 -04:00
return checkIfCfgZoneAndFlagZoneDiffer ( zone , cfg )
2023-07-04 07:55:52 -04:00
}
2023-10-16 09:05:29 -04:00
func checkIfCfgZoneAndFlagZoneDiffer ( zone string , cfg config . Config ) error {
2023-07-04 07:55:52 -04:00
configZone := cfg . GetZone ( )
2023-10-16 09:05:29 -04:00
if configZone != "" && zone != configZone {
return fmt . Errorf ( "zone/region from the configuration file %q differs from the one provided via flags %q" , configZone , zone )
2023-07-04 07:55:52 -04:00
}
return nil
}