This is an automated email from the ASF dual-hosted git repository. nfilotto pushed a commit to branch 1184/configure-default-namespace in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit b1464c0c4a60996d230e8759c2091d4f39ba54d4 Author: Nicolas Filotto <[email protected]> AuthorDate: Mon Sep 5 10:17:40 2022 +0200 feat(cli): Add a config command to manage the default settings --- pkg/cmd/config.go | 163 +++++++++++++++++++++++++++++++++++++++++++++++++ pkg/cmd/config_test.go | 137 +++++++++++++++++++++++++++++++++++++++++ pkg/cmd/root.go | 11 +++- pkg/cmd/util_config.go | 11 +++- 4 files changed, 318 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go new file mode 100644 index 000000000..7250900aa --- /dev/null +++ b/pkg/cmd/config.go @@ -0,0 +1,163 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/yaml.v2" +) + +// ConfigFolder defines the different types of folder containing the configuration file +type ConfigFolder string + +const ( + // The path of the folder containing the configuration file is retrieved from the environment + // variable KAMEL_CONFIG_PATH + ConfigFolderEnvVar ConfigFolder = "env" + // The path of the folder containing the configuration file is $HOME/.kamel + ConfigFolderHome ConfigFolder = "home" + // The folder containing the configuration file is .kamel located in the working directory + ConfigFolderSubDirectory ConfigFolder = "sub" + // The folder containing the configuration file is the working directory + ConfigFolderWorking ConfigFolder = "working" + // The folder containing the configuration file is the directory currently used by the kamel script + ConfigFolderUsed ConfigFolder = "used" +) + +func newCmdConfig(rootCmdOptions *RootCmdOptions) (*cobra.Command, *configCmdOptions) { + options := configCmdOptions{} + cmd := cobra.Command{ + Use: "config", + Short: "Configure the default settings", + PreRunE: decode(&options), + Args: options.validateArgs, + RunE: options.run, + } + + cmd.Flags().String("folder", "used", "The type of folder containing the configuration file to read/write. The supported values are env, home, sub, working and used for respectively $KAMEL_CONFIG_PATH, $HOME/.kamel, .kamel, . and the folder used by kamel") + cmd.Flags().String("default-namespace", "", "The name of the namespace to use by default") + cmd.Flags().BoolP("list", "l", false, "List all existing settings") + return &cmd, &options +} + +type configCmdOptions struct { + DefaultNamespace string `mapstructure:"default-namespace"` +} + +func (o *configCmdOptions) validateArgs(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.New("No arguments are expected") + } + return nil +} + +func (o *configCmdOptions) run(cmd *cobra.Command, args []string) error { + path, err := getConfigLocation(cmd) + if err != nil { + return err + } + if cmd.Flags().Lookup("default-namespace").Changed { + err = o.saveConfiguration(cmd, path) + if err != nil { + return err + } + } + if cmd.Flags().Lookup("list").Changed { + err = printConfiguration(cmd, path) + if err != nil { + return err + } + } + return nil +} + +// Save the configuration at the given location +func (o *configCmdOptions) saveConfiguration(cmd *cobra.Command, path string) error { + cfg, err := LoadConfigurationFrom(path) + if err != nil { + return err + } + + cfg.Update(cmd, pathToRoot(cmd), o, true) + + err = cfg.Save() + if err != nil { + return err + } + return nil +} + +// Gives the location of the configuration file +func getConfigLocation(cmd *cobra.Command) (string, error) { + var folder ConfigFolder + if s, err := cmd.Flags().GetString("folder"); err == nil { + folder = ConfigFolder(s) + } else { + return "", err + } + var path string + switch folder { + case ConfigFolderUsed: + path = viper.ConfigFileUsed() + if path != "" { + return path, nil + } + case ConfigFolderEnvVar: + path = os.Getenv("KAMEL_CONFIG_PATH") + case ConfigFolderHome: + home, err := os.UserHomeDir() + cobra.CheckErr(err) + path = filepath.Join(home, ".kamel") + case ConfigFolderSubDirectory: + path = ".kamel" + case ConfigFolderWorking: + path = "." + default: + return "", errors.New(fmt.Sprintf("unsupported type of folder: %s", folder)) + } + configName := os.Getenv("KAMEL_CONFIG_NAME") + if configName == "" { + configName = DefaultConfigName + } + return filepath.Join(path, fmt.Sprintf("%s.yaml", configName)), nil +} + +// Print the content of the configuration file located at the given path +func printConfiguration(cmd *cobra.Command, path string) error { + cfg, err := LoadConfigurationFrom(path) + if err != nil { + return err + } + if len(cfg.content) == 0 { + fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("No settings could be found at %s", cfg.location)) + } else { + bs, err := yaml.Marshal(cfg.content) + if err != nil { + return errors.New(fmt.Sprintf("unable to marshal config to YAML: %v", err)) + } + fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("Configuration file read from %s", cfg.location)) + fmt.Fprintln(cmd.OutOrStdout(), string(bs)) + } + return nil +} diff --git a/pkg/cmd/config_test.go b/pkg/cmd/config_test.go new file mode 100644 index 000000000..57d5a981e --- /dev/null +++ b/pkg/cmd/config_test.go @@ -0,0 +1,137 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/apache/camel-k/pkg/util/test" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +const cmdConfig = "config" + +// nolint: unparam +func initializeConfigCmdOptions(t *testing.T, mock bool) (*configCmdOptions, *cobra.Command, RootCmdOptions) { + t.Helper() + + options, rootCmd := kamelTestPreAddCommandInit() + configCmdOptions := addTestConfigCmd(*options, rootCmd, mock) + kamelTestPostAddCommandInit(t, rootCmd) + + return configCmdOptions, rootCmd, *options +} + +func addTestConfigCmd(options RootCmdOptions, rootCmd *cobra.Command, mock bool) *configCmdOptions { + // add a testing version of config Command + configCmd, configOptions := newCmdConfig(&options) + if mock { + configCmd.RunE = func(c *cobra.Command, args []string) error { + return nil + } + } + configCmd.Args = test.ArbitraryArgs + rootCmd.AddCommand(configCmd) + return configOptions +} + +func TestConfigNonExistingFlag(t *testing.T) { + _, rootCmd, _ := initializeConfigCmdOptions(t, true) + _, err := test.ExecuteCommand(rootCmd, cmdConfig, "--nonExistingFlag") + assert.NotNil(t, err) +} + +func TestConfigDefaultNamespaceFlag(t *testing.T) { + configCmdOptions, rootCmd, _ := initializeConfigCmdOptions(t, true) + _, err := test.ExecuteCommand(rootCmd, cmdConfig, "--default-namespace", "foo") + assert.Nil(t, err) + assert.Equal(t, "foo", configCmdOptions.DefaultNamespace) +} + +func TestConfigListFlag(t *testing.T) { + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, "No settings"), "The output is unexpected: "+output) +} + +func TestConfigFolderFlagToUsed(t *testing.T) { + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list", "--folder", "used") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, fmt.Sprintf(" %s", DefaultConfigLocation)), "The output is unexpected: "+output) +} + +func TestConfigFolderFlagToSub(t *testing.T) { + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list", "--folder", "sub") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, fmt.Sprintf(" .kamel/%s", DefaultConfigLocation)), "The output is unexpected: "+output) +} + +func TestConfigFolderFlagToHome(t *testing.T) { + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list", "--folder", "home") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, fmt.Sprintf("/.kamel/%s", DefaultConfigLocation)), "The output is unexpected: "+output) +} + +func TestConfigFolderFlagToEnv(t *testing.T) { + os.Setenv("KAMEL_CONFIG_PATH", "/foo/bar") + t.Cleanup(func() { os.Unsetenv("KAMEL_CONFIG_PATH") }) + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list", "--folder", "env") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, fmt.Sprintf("/foo/bar/%s", DefaultConfigLocation)), "The output is unexpected: "+output) +} + +func TestConfigFolderFlagToEnvWithConfigName(t *testing.T) { + os.Setenv("KAMEL_CONFIG_NAME", "config") + os.Setenv("KAMEL_CONFIG_PATH", "/foo/bar") + t.Cleanup(func() { + os.Unsetenv("KAMEL_CONFIG_NAME") + os.Unsetenv("KAMEL_CONFIG_PATH") + }) + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list", "--folder", "env") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, "/foo/bar/config.yaml"), "The output is unexpected: "+output) +} + +func TestConfigDefaultNamespace(t *testing.T) { + _, err := os.Stat(DefaultConfigLocation) + assert.True(t, os.IsNotExist(err), "No file at "+DefaultConfigLocation+" was expected") + _, rootCmd, _ := initializeConfigCmdOptions(t, false) + t.Cleanup(func() { os.Remove(DefaultConfigLocation) }) + _, err = test.ExecuteCommand(rootCmd, cmdConfig, "--default-namespace", "foo") + assert.Nil(t, err) + _, err = os.Stat(DefaultConfigLocation) + assert.Nil(t, err, "A file at "+DefaultConfigLocation+" was expected") + output, err := test.ExecuteCommand(rootCmd, cmdConfig, "--list") + assert.Nil(t, err) + assert.True(t, strings.Contains(output, "foo"), "The output is unexpected: "+output) + _, rootCmd, _ = initializeInstallCmdOptions(t) + _, err = test.ExecuteCommand(rootCmd, cmdInstall) + assert.Nil(t, err) + // Check default namespace is set + assert.Equal(t, "foo", rootCmd.Flag("namespace").Value.String()) +} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index b75afd321..f72128903 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -152,6 +152,7 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) { cmd.AddCommand(cmdOnly(newCmdBind(options))) cmd.AddCommand(cmdOnly(newCmdPromote(options))) cmd.AddCommand(newCmdKamelet(options)) + cmd.AddCommand(cmdOnly(newCmdConfig(options))) } func addHelpSubCommands(cmd *cobra.Command, options *RootCmdOptions) error { @@ -182,9 +183,13 @@ func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error { return errors.Wrap(err, "cannot get command client") } if command.Namespace == "" { - current, err := c.GetCurrentNamespace(command.KubeConfig) - if err != nil { - return errors.Wrap(err, "cannot get current namespace") + current := viper.GetString("kamel.config.default-namespace") + if current == "" { + defaultNS, err := c.GetCurrentNamespace(command.KubeConfig) + if err != nil { + return errors.Wrap(err, "cannot get current namespace") + } + current = defaultNS } err = cmd.Flag("namespace").Value.Set(current) if err != nil { diff --git a/pkg/cmd/util_config.go b/pkg/cmd/util_config.go index 68d47eb7a..3ebb313e2 100644 --- a/pkg/cmd/util_config.go +++ b/pkg/cmd/util_config.go @@ -55,8 +55,17 @@ type Config struct { // LoadConfiguration loads a kamel configuration file. func LoadConfiguration() (*Config, error) { + return loadConfiguration(viper.ConfigFileUsed()) +} + +// LoadConfiguration loads a kamel configuration file from a specific location. +func LoadConfigurationFrom(location string) (*Config, error) { + return loadConfiguration(location) +} + +func loadConfiguration(location string) (*Config, error) { config := Config{ - location: viper.ConfigFileUsed(), + location: location, content: make(map[string]interface{}), }
