2022-09-05 09:06:08 +02:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-05-12 15:14:52 +02:00
package cmd
import (
2022-06-09 14:10:42 +00:00
"fmt"
2023-02-21 14:05:41 +01:00
"strings"
2022-06-09 14:10:42 +00:00
2023-06-09 15:41:02 +02:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
2022-09-21 13:47:57 +02:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
2023-12-08 16:27:04 +01:00
"github.com/edgelesssys/constellation/v2/internal/constellation/state"
2022-09-21 13:47:57 +02:00
"github.com/edgelesssys/constellation/v2/internal/file"
2023-02-21 14:05:41 +01:00
"github.com/edgelesssys/constellation/v2/internal/versions"
2022-05-12 15:14:52 +02:00
"github.com/spf13/afero"
"github.com/spf13/cobra"
2023-10-16 15:05:29 +02:00
"github.com/spf13/pflag"
2023-02-21 14:05:41 +01:00
"golang.org/x/mod/semver"
2022-05-12 15:14:52 +02:00
)
func newConfigGenerateCmd ( ) * cobra . Command {
cmd := & cobra . Command {
2023-05-30 09:02:50 +02:00
Use : "generate {aws|azure|gcp|openstack|qemu|stackit}" ,
2023-10-16 20:18:59 +02:00
Short : "Generate a default configuration and state file" ,
Long : "Generate a default configuration and state file for your selected cloud provider." ,
2022-05-18 11:39:14 +02:00
Args : cobra . MatchAll (
cobra . ExactArgs ( 1 ) ,
isCloudProvider ( 0 ) ,
) ,
ValidArgsFunction : generateCompletion ,
RunE : runConfigGenerate ,
2022-05-12 15:14:52 +02:00
}
2023-09-19 13:50:00 +02:00
cmd . Flags ( ) . StringP ( "kubernetes" , "k" , semver . MajorMinor ( string ( config . Default ( ) . KubernetesVersion ) ) , "Kubernetes version to use in format MAJOR.MINOR" )
2023-06-25 23:32:39 +02:00
cmd . Flags ( ) . StringP ( "attestation" , "a" , "" , fmt . Sprintf ( "attestation variant to use %s. If not specified, the default for the cloud provider is used" , printFormattedSlice ( variant . GetAvailableAttestationVariants ( ) ) ) )
2024-04-19 09:07:57 +00:00
cmd . Flags ( ) . StringSliceP ( "tags" , "t" , nil , "additional tags for created resources given a list of key=value" )
2022-05-12 15:14:52 +02:00
return cmd
}
type generateFlags struct {
2023-10-16 15:05:29 +02:00
rootFlags
2023-09-19 13:50:00 +02:00
k8sVersion versions . ValidK8sVersion
2023-05-17 16:53:56 +02:00
attestationVariant variant . Variant
2024-04-19 09:07:57 +00:00
tags cloudprovider . Tags
2022-05-12 15:14:52 +02:00
}
2023-10-16 15:05:29 +02:00
func ( f * generateFlags ) parse ( flags * pflag . FlagSet ) error {
if err := f . rootFlags . parse ( flags ) ; err != nil {
return err
}
k8sVersion , err := parseK8sFlag ( flags )
if err != nil {
return err
}
f . k8sVersion = k8sVersion
variant , err := parseAttestationFlag ( flags )
if err != nil {
return err
}
f . attestationVariant = variant
2024-04-19 09:07:57 +00:00
tags , err := parseTagsFlags ( flags )
if err != nil {
return err
}
f . tags = tags
2023-10-16 15:05:29 +02:00
return nil
}
2023-01-04 09:46:29 +00:00
type configGenerateCmd struct {
2023-10-16 15:05:29 +02:00
flags generateFlags
log debugLog
2023-01-04 09:46:29 +00:00
}
2022-05-12 15:14:52 +02:00
func runConfigGenerate ( cmd * cobra . Command , args [ ] string ) error {
2023-01-04 09:46:29 +00:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
2023-10-16 15:05:29 +02:00
2022-05-12 15:14:52 +02:00
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
2022-05-18 11:39:14 +02:00
provider := cloudprovider . FromString ( args [ 0 ] )
2023-10-16 15:05:29 +02:00
2023-01-04 09:46:29 +00:00
cg := & configGenerateCmd { log : log }
2023-10-16 15:05:29 +02:00
if err := cg . flags . parse ( cmd . Flags ( ) ) ; err != nil {
return fmt . Errorf ( "parsing flags: %w" , err )
}
2024-04-03 13:49:03 +00:00
log . Debug ( "Using flags" , "k8sVersion" , cg . flags . k8sVersion , "attestationVariant" , cg . flags . attestationVariant )
2023-10-16 15:05:29 +02:00
2023-05-30 09:02:50 +02:00
return cg . configGenerate ( cmd , fileHandler , provider , args [ 0 ] )
2022-05-12 15:14:52 +02:00
}
2023-05-30 09:02:50 +02:00
func ( cg * configGenerateCmd ) configGenerate ( cmd * cobra . Command , fileHandler file . Handler , provider cloudprovider . Provider , rawProvider string ) error {
2024-04-03 13:49:03 +00:00
cg . log . Debug ( fmt . Sprintf ( "Using cloud provider %q" , provider . String ( ) ) )
2023-10-16 20:18:59 +02:00
// Config creation
2023-10-16 15:05:29 +02:00
conf , err := createConfigWithAttestationVariant ( provider , rawProvider , cg . flags . attestationVariant )
2023-05-17 16:53:56 +02:00
if err != nil {
return fmt . Errorf ( "creating config: %w" , err )
}
2023-10-16 15:05:29 +02:00
conf . KubernetesVersion = cg . flags . k8sVersion
2024-04-19 09:07:57 +00:00
conf . Tags = cg . flags . tags
2024-02-08 14:20:01 +00:00
cg . log . Debug ( "Writing YAML data to configuration file" )
2023-08-04 13:53:51 +02:00
if err := fileHandler . WriteYAML ( constants . ConfigFilename , conf , file . OptMkdirAll ) ; err != nil {
2023-10-16 20:18:59 +02:00
return fmt . Errorf ( "writing config file: %w" , err )
2022-08-08 11:04:17 +02:00
}
2023-01-12 11:35:26 +01:00
2023-10-16 15:05:29 +02:00
cmd . Println ( "Config file written to" , cg . flags . pathPrefixer . PrefixPrintablePath ( constants . ConfigFilename ) )
2022-09-11 16:24:15 +02:00
cmd . Println ( "Please fill in your CSP-specific configuration before proceeding." )
2023-10-16 20:18:59 +02:00
// State-file creation
stateFile := state . New ( )
switch provider {
case cloudprovider . GCP :
stateFile . SetInfrastructure ( state . Infrastructure { GCP : & state . GCP { } } )
case cloudprovider . Azure :
stateFile . SetInfrastructure ( state . Infrastructure { Azure : & state . Azure { } } )
}
if err = stateFile . WriteToFile ( fileHandler , constants . StateFilename ) ; err != nil {
return fmt . Errorf ( "writing state file: %w" , err )
}
cmd . Println ( "State file written to" , cg . flags . pathPrefixer . PrefixPrintablePath ( constants . StateFilename ) )
2022-10-31 16:39:56 +01:00
cmd . Println ( "For more information refer to the documentation:" )
2022-09-10 13:44:17 +02:00
cmd . Println ( "\thttps://docs.edgeless.systems/constellation/getting-started/first-steps" )
2022-09-02 17:11:06 +02:00
2022-08-08 11:04:17 +02:00
return nil
2022-05-12 15:14:52 +02:00
}
2023-06-25 23:32:39 +02:00
// createConfigWithAttestationVariant creates a config file for the given provider.
func createConfigWithAttestationVariant ( provider cloudprovider . Provider , rawProvider string , attestationVariant variant . Variant ) ( * config . Config , error ) {
2024-03-01 17:45:12 +01:00
conf := config . Default ( ) . WithOpenStackProviderDefaults ( provider , rawProvider )
2023-01-12 11:35:26 +01:00
conf . RemoveProviderExcept ( provider )
// set a lower default for QEMU's state disk
if provider == cloudprovider . QEMU {
2023-08-02 10:42:18 +02:00
for groupName , group := range conf . NodeGroups {
group . StateDiskSizeGB = 10
conf . NodeGroups [ groupName ] = group
}
2023-01-12 11:35:26 +01:00
}
2023-05-17 16:53:56 +02:00
if provider == cloudprovider . Unknown {
return conf , nil
}
if attestationVariant . Equal ( variant . Dummy { } ) {
attestationVariant = variant . GetDefaultAttestation ( provider )
if attestationVariant . Equal ( variant . Dummy { } ) {
return nil , fmt . Errorf ( "provider %s does not have a default attestation variant" , provider )
}
} else if ! variant . ValidProvider ( provider , attestationVariant ) {
2023-06-25 23:32:39 +02:00
return nil , fmt . Errorf ( "provider %s does not support attestation variant %s" , provider , attestationVariant )
2023-05-17 16:53:56 +02:00
}
conf . SetAttestation ( attestationVariant )
2024-01-24 15:10:15 +01:00
conf . SetCSPNodeGroupDefaults ( provider )
2023-05-17 16:53:56 +02:00
return conf , nil
}
// createConfig creates a config file for the given provider.
func createConfig ( provider cloudprovider . Provider ) * config . Config {
2023-05-30 09:02:50 +02:00
// rawProvider can be hardcoded as it only matters for OpenStack
2023-06-25 23:32:39 +02:00
res , _ := createConfigWithAttestationVariant ( provider , "" , variant . Dummy { } )
2023-05-17 16:53:56 +02:00
return res
2023-01-12 11:35:26 +01:00
}
2023-05-17 16:53:56 +02:00
// generateCompletion handles the completion of the create command. It is frequently called
2022-05-18 11:39:14 +02:00
// while the user types arguments of the command to suggest completion.
2023-03-20 11:03:36 +01:00
func generateCompletion ( _ * cobra . Command , args [ ] string , _ string ) ( [ ] string , cobra . ShellCompDirective ) {
2022-05-18 11:39:14 +02:00
switch len ( args ) {
case 0 :
2023-05-30 09:02:50 +02:00
return [ ] string { "aws" , "gcp" , "azure" , "qemu" , "stackit" } , cobra . ShellCompDirectiveNoFileComp
2022-05-18 11:39:14 +02:00
default :
return [ ] string { } , cobra . ShellCompDirectiveError
}
}
2023-03-03 09:04:54 +01:00
2023-05-17 16:53:56 +02:00
func printFormattedSlice [ T any ] ( input [ ] T ) string {
return fmt . Sprintf ( "{%s}" , strings . Join ( toString ( input ) , "|" ) )
}
func toString [ T any ] ( t [ ] T ) [ ] string {
var res [ ] string
for _ , v := range t {
res = append ( res , fmt . Sprintf ( "%v" , v ) )
}
return res
}
2023-10-16 15:05:29 +02:00
func parseK8sFlag ( flags * pflag . FlagSet ) ( versions . ValidK8sVersion , error ) {
versionString , err := flags . GetString ( "kubernetes" )
if err != nil {
return "" , fmt . Errorf ( "getting kubernetes flag: %w" , err )
}
resolvedVersion , err := versions . ResolveK8sPatchVersion ( versionString )
if err != nil {
return "" , fmt . Errorf ( "resolving kubernetes patch version from flag: %w" , err )
}
k8sVersion , err := versions . NewValidK8sVersion ( resolvedVersion , true )
if err != nil {
return "" , fmt . Errorf ( "resolving Kubernetes version from flag: %w" , err )
}
return k8sVersion , nil
}
func parseAttestationFlag ( flags * pflag . FlagSet ) ( variant . Variant , error ) {
attestationString , err := flags . GetString ( "attestation" )
if err != nil {
return nil , fmt . Errorf ( "getting attestation flag: %w" , err )
}
var attestationVariant variant . Variant
// if no attestation variant is specified, use the default for the cloud provider
if attestationString == "" {
attestationVariant = variant . Dummy { }
} else {
attestationVariant , err = variant . FromString ( attestationString )
if err != nil {
return nil , fmt . Errorf ( "invalid attestation variant: %s" , attestationString )
}
}
return attestationVariant , nil
}
2024-04-19 09:07:57 +00:00
func parseTagsFlags ( flags * pflag . FlagSet ) ( cloudprovider . Tags , error ) {
tagsSlice , err := flags . GetStringSlice ( "tags" )
if err != nil {
return nil , fmt . Errorf ( "getting tags flag: %w" , err )
}
// no tags given
if tagsSlice == nil {
return nil , nil
}
tags := make ( cloudprovider . Tags )
for _ , tag := range tagsSlice {
tagSplit := strings . Split ( tag , "=" )
if len ( tagSplit ) != 2 {
return nil , fmt . Errorf ( "wrong format of tags: expected \"key=value\", got %q" , tag )
}
tags [ tagSplit [ 0 ] ] = tagSplit [ 1 ]
}
return tags , nil
}