diff --git a/cli/internal/cmd/configgenerate.go b/cli/internal/cmd/configgenerate.go index 16fe57bb3..fc0f34e16 100644 --- a/cli/internal/cmd/configgenerate.go +++ b/cli/internal/cmd/configgenerate.go @@ -70,11 +70,7 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file cg.log.Debugf("Parsed flags as %v", flags) cg.log.Debugf("Using cloud provider %s", provider.String()) conf := createConfig(provider) - extendedVersion := config.K8sVersionFromMajorMinor(flags.k8sVersion) - if extendedVersion == "" { - return fmt.Errorf("kubernetes (%s) does not specify a valid Kubernetes version. Supported versions: %s", strings.TrimPrefix(flags.k8sVersion, "v"), supportedVersions()) - } - conf.KubernetesVersion = extendedVersion + conf.KubernetesVersion = flags.k8sVersion if flags.file == "-" { content, err := encoder.NewEncoder(conf).Encode() if err != nil { @@ -134,14 +130,14 @@ func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) { if err != nil { return generateFlags{}, fmt.Errorf("parsing kuberentes flag: %w", err) } - prefixedVersion := compatibility.EnsurePrefixV(k8sVersion) - if !semver.IsValid(prefixedVersion) { - return generateFlags{}, fmt.Errorf("kubernetes flag does not specify a valid semantic version: %s", k8sVersion) + resolvedVersion, err := resolveK8sVersion(k8sVersion) + if err != nil { + return generateFlags{}, fmt.Errorf("resolving kuberentes version from flag: %w", err) } return generateFlags{ file: file, - k8sVersion: prefixedVersion, + k8sVersion: resolvedVersion, }, nil } @@ -155,3 +151,19 @@ func generateCompletion(cmd *cobra.Command, args []string, toComplete string) ([ return []string{}, cobra.ShellCompDirectiveError } } + +// resolveK8sVersion takes the user input from --kubernetes and transforms a MAJOR.MINOR definition into a supported +// MAJOR.MINOR.PATCH release. +func resolveK8sVersion(k8sVersion string) (string, error) { + prefixedVersion := compatibility.EnsurePrefixV(k8sVersion) + if !semver.IsValid(prefixedVersion) { + return "", fmt.Errorf("kubernetes flag does not specify a valid semantic version: %s", k8sVersion) + } + + extendedVersion := config.K8sVersionFromMajorMinor(prefixedVersion) + if extendedVersion == "" { + return "", fmt.Errorf("--kubernetes (%s) does not specify a valid Kubernetes version. Supported versions: %s", strings.TrimPrefix(k8sVersion, "v"), supportedVersions()) + } + + return extendedVersion, nil +} diff --git a/cli/internal/cmd/iamcreate.go b/cli/internal/cmd/iamcreate.go index 028f04915..688e57e7a 100644 --- a/cli/internal/cmd/iamcreate.go +++ b/cli/internal/cmd/iamcreate.go @@ -21,6 +21,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" "github.com/spf13/cobra" + "golang.org/x/mod/semver" ) var ( @@ -56,8 +57,9 @@ func newIAMCreateCmd() *cobra.Command { Args: cobra.ExactArgs(0), } - cmd.PersistentFlags().Bool("generate-config", false, "automatically generate a configuration file and fill in the required fields") cmd.PersistentFlags().Bool("yes", false, "create the IAM configuration without further confirmation") + cmd.PersistentFlags().Bool("generate-config", false, "automatically generate a configuration file and fill in the required fields") + cmd.PersistentFlags().StringP("kubernetes", "k", semver.MajorMinor(config.Default().KubernetesVersion), "Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config") cmd.AddCommand(newIAMCreateAWSCmd()) cmd.AddCommand(newIAMCreateAzureCmd()) @@ -238,6 +240,10 @@ func (c *iamCreator) create(ctx context.Context) error { if flags.generateConfig { c.log.Debugf("Writing IAM configuration to %s", flags.configPath) c.providerCreator.writeOutputValuesToConfig(conf, flags, iamFile) + // Only overwrite when --generate-config && --kubernetes. Otherwise this string is empty from parseFlagsAndSetupConfig. + if flags.k8sVersion != "" { + conf.KubernetesVersion = flags.k8sVersion + } if err := c.fileHandler.WriteYAML(flags.configPath, conf, file.OptMkdirAll); err != nil { return err } @@ -257,19 +263,39 @@ func (c *iamCreator) parseFlagsAndSetupConfig() (iamFlags, error) { if err != nil { return iamFlags{}, fmt.Errorf("parsing config string: %w", err) } - generateConfig, err := c.cmd.Flags().GetBool("generate-config") - if err != nil { - return iamFlags{}, fmt.Errorf("parsing generate-config bool: %w", err) - } yesFlag, err := c.cmd.Flags().GetBool("yes") if err != nil { return iamFlags{}, fmt.Errorf("parsing yes bool: %w", err) } + generateConfig, err := c.cmd.Flags().GetBool("generate-config") + if err != nil { + return iamFlags{}, fmt.Errorf("parsing generate-config bool: %w", err) + } + k8sVersion, err := c.cmd.Flags().GetString("kubernetes") + if err != nil { + return iamFlags{}, fmt.Errorf("parsing kubernetes string: %w", err) + } + + // This is implemented slightly differently compared to "config generate", since this flag is only respected in combination with --generate-config. + // Even if an invalid version is set, in case --generate-config is false, we don't overwrite the default value of the config. + // So we only need to validate the input to the flag when --generate-config is set. + // Otherwise, we return an empty string. Later, we only overwrite the value in the config when we haven't passed an empty string. + // Instead, we should have our validated K8s version parameter then. + var resolvedVersion string + if generateConfig { + resolvedVersion, err = resolveK8sVersion(k8sVersion) + if err != nil { + return iamFlags{}, fmt.Errorf("resolving kubernetes version: %w", err) + } + } else if c.cmd.Flag("kubernetes").Changed { + c.cmd.Println("Warning: --generate-config is not set, ignoring --kubernetes flag.") + } flags := iamFlags{ - generateConfig: generateConfig, configPath: configPath, yesFlag: yesFlag, + generateConfig: generateConfig, + k8sVersion: resolvedVersion, } flags, err = c.providerCreator.parseFlagsAndSetupConfig(c.cmd, flags, c.iamConfig) @@ -298,9 +324,10 @@ type iamFlags struct { aws awsFlags azure azureFlags gcp gcpFlags - generateConfig bool configPath string yesFlag bool + generateConfig bool + k8sVersion string } // awsFlags contains the parsed flags of the iam create aws command. diff --git a/cli/internal/cmd/iamcreate_test.go b/cli/internal/cmd/iamcreate_test.go index df5e9886a..9aaf151d0 100644 --- a/cli/internal/cmd/iamcreate_test.go +++ b/cli/internal/cmd/iamcreate_test.go @@ -18,9 +18,11 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/versions" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/mod/semver" ) func TestParseIDFile(t *testing.T) { @@ -98,6 +100,7 @@ func TestIAMCreateAWS(t *testing.T) { prefixFlag string yesFlag bool generateConfigFlag bool + k8sVersionFlag string configFlag string existingFiles []string existingDirs []string @@ -225,6 +228,38 @@ func TestIAMCreateAWS(t *testing.T) { wantErr: true, configFlag: constants.ConfigFilename, }, + "iam create azure without generate config and invalid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.AWS, + zoneFlag: "us-east-2a", + prefixFlag: "test", + k8sVersionFlag: "1.11.1", // supposed to be ignored without generateConfigFlag + yesFlag: true, + }, + "iam create azure generate config with valid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.AWS, + zoneFlag: "us-east-2a", + prefixFlag: "test", + generateConfigFlag: true, + k8sVersionFlag: semver.MajorMinor(string(versions.Default)), + configFlag: constants.ConfigFilename, + yesFlag: true, + }, + "iam create azure generate config with invalid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.AWS, + zoneFlag: "us-east-2a", + prefixFlag: "test", + generateConfigFlag: true, + k8sVersionFlag: "1.22.1", + configFlag: constants.ConfigFilename, + yesFlag: true, + wantErr: true, + }, } for name, tc := range testCases { @@ -237,10 +272,12 @@ func TestIAMCreateAWS(t *testing.T) { cmd.SetErr(&bytes.Buffer{}) cmd.SetIn(bytes.NewBufferString(tc.stdin)) - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("generate-config", false, "") // register persistent flag manually - cmd.Flags().Bool("yes", false, "") // register persistent flag manually - cmd.Flags().String("name", "constell", "") // register persistent flag manually + // register persistent flags manually + cmd.Flags().String("config", constants.ConfigFilename, "") + cmd.Flags().Bool("generate-config", false, "") + cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "") + cmd.Flags().Bool("yes", false, "") + cmd.Flags().String("name", "constell", "") if tc.zoneFlag != "" { require.NoError(cmd.Flags().Set("zone", tc.zoneFlag)) @@ -257,6 +294,9 @@ func TestIAMCreateAWS(t *testing.T) { if tc.configFlag != "" { require.NoError(cmd.Flags().Set("config", tc.configFlag)) } + if tc.k8sVersionFlag != "" { + require.NoError(cmd.Flags().Set("kubernetes", tc.k8sVersionFlag)) + } fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles, tc.existingDirs)) @@ -334,6 +374,7 @@ func TestIAMCreateAzure(t *testing.T) { resourceGroupFlag string yesFlag bool generateConfigFlag bool + k8sVersionFlag string configFlag string existingFiles []string existingDirs []string @@ -462,6 +503,41 @@ func TestIAMCreateAzure(t *testing.T) { configFlag: constants.ConfigFilename, wantErr: true, }, + "iam create azure without generate config and invalid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.Azure, + regionFlag: "westus", + servicePrincipalFlag: "constell-test-sp", + resourceGroupFlag: "constell-test-rg", + k8sVersionFlag: "1.11.1", // supposed to be ignored without generateConfigFlag + yesFlag: true, + }, + "iam create azure generate config with valid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.Azure, + regionFlag: "westus", + servicePrincipalFlag: "constell-test-sp", + resourceGroupFlag: "constell-test-rg", + generateConfigFlag: true, + k8sVersionFlag: semver.MajorMinor(string(versions.Default)), + configFlag: constants.ConfigFilename, + yesFlag: true, + }, + "iam create azure generate config with invalid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.Azure, + regionFlag: "westus", + servicePrincipalFlag: "constell-test-sp", + resourceGroupFlag: "constell-test-rg", + generateConfigFlag: true, + k8sVersionFlag: "1.22.1", + configFlag: constants.ConfigFilename, + yesFlag: true, + wantErr: true, + }, } for name, tc := range testCases { @@ -474,10 +550,12 @@ func TestIAMCreateAzure(t *testing.T) { cmd.SetErr(&bytes.Buffer{}) cmd.SetIn(bytes.NewBufferString(tc.stdin)) - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("generate-config", false, "") // register persistent flag manually - cmd.Flags().Bool("yes", false, "") // register persistent flag manually - cmd.Flags().String("name", "constell", "") // register persistent flag manually + // register persistent flag manually + cmd.Flags().String("config", constants.ConfigFilename, "") + cmd.Flags().Bool("generate-config", false, "") + cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "") + cmd.Flags().Bool("yes", false, "") + cmd.Flags().String("name", "constell", "") if tc.regionFlag != "" { require.NoError(cmd.Flags().Set("region", tc.regionFlag)) @@ -497,6 +575,9 @@ func TestIAMCreateAzure(t *testing.T) { if tc.configFlag != "" { require.NoError(cmd.Flags().Set("config", tc.configFlag)) } + if tc.k8sVersionFlag != "" { + require.NoError(cmd.Flags().Set("kubernetes", tc.k8sVersionFlag)) + } fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles, tc.existingDirs)) @@ -579,6 +660,7 @@ func TestIAMCreateGCP(t *testing.T) { projectIDFlag string yesFlag bool generateConfigFlag bool + k8sVersionFlag string configFlag string existingFiles []string existingDirs []string @@ -727,6 +809,41 @@ func TestIAMCreateGCP(t *testing.T) { configFlag: constants.ConfigFilename, wantErr: true, }, + "iam create gcp without generate config and invalid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.GCP, + zoneFlag: "europe-west1-a", + serviceAccountIDFlag: "constell-test", + projectIDFlag: "constell-1234", + k8sVersionFlag: "1.11.1", // supposed to be ignored without generateConfigFlag + yesFlag: true, + }, + "iam create gcp generate config with valid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.GCP, + zoneFlag: "europe-west1-a", + serviceAccountIDFlag: "constell-test", + projectIDFlag: "constell-1234", + generateConfigFlag: true, + k8sVersionFlag: semver.MajorMinor(string(versions.Default)), + configFlag: constants.ConfigFilename, + yesFlag: true, + }, + "iam create gcp generate config with invalid kubernetes version": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.GCP, + zoneFlag: "europe-west1-a", + serviceAccountIDFlag: "constell-test", + projectIDFlag: "constell-1234", + generateConfigFlag: true, + k8sVersionFlag: "1.22.1", + configFlag: constants.ConfigFilename, + yesFlag: true, + wantErr: true, + }, } for name, tc := range testCases { @@ -739,10 +856,12 @@ func TestIAMCreateGCP(t *testing.T) { cmd.SetErr(&bytes.Buffer{}) cmd.SetIn(bytes.NewBufferString(tc.stdin)) - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("generate-config", false, "") // register persistent flag manually - cmd.Flags().Bool("yes", false, "") // register persistent flag manually - cmd.Flags().String("name", "constell", "") // register persistent flag manually + // register persistent flags manually + cmd.Flags().String("config", constants.ConfigFilename, "") + cmd.Flags().Bool("generate-config", false, "") + cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "") + cmd.Flags().Bool("yes", false, "") + cmd.Flags().String("name", "constell", "") if tc.zoneFlag != "" { require.NoError(cmd.Flags().Set("zone", tc.zoneFlag)) @@ -762,6 +881,9 @@ func TestIAMCreateGCP(t *testing.T) { if tc.configFlag != "" { require.NoError(cmd.Flags().Set("config", tc.configFlag)) } + if tc.k8sVersionFlag != "" { + require.NoError(cmd.Flags().Set("kubernetes", tc.k8sVersionFlag)) + } fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles, tc.existingDirs))