This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git


The following commit(s) were added to refs/heads/main by this push:
     new 90888bc7a feat(cli): Add a config command to manage the default 
settings
90888bc7a is described below

commit 90888bc7a94948989cfcd860bb6802fac0d83576
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
---
 docs/modules/ROOT/pages/cli/file-based-config.adoc |  88 +++++++++--
 e2e/namespace/install/cli/config_test.go           |  71 +++++++++
 pkg/cmd/config.go                                  | 164 +++++++++++++++++++++
 pkg/cmd/config_test.go                             | 137 +++++++++++++++++
 pkg/cmd/root.go                                    |  11 +-
 pkg/cmd/util_config.go                             |  11 +-
 6 files changed, 469 insertions(+), 13 deletions(-)

diff --git a/docs/modules/ROOT/pages/cli/file-based-config.adoc 
b/docs/modules/ROOT/pages/cli/file-based-config.adoc
index 40b4575ca..a824cc1eb 100644
--- a/docs/modules/ROOT/pages/cli/file-based-config.adoc
+++ b/docs/modules/ROOT/pages/cli/file-based-config.adoc
@@ -2,23 +2,93 @@
 
 File-based configuration is used to set command flags. Flag values do not need 
to be entered on a regular basis. The file is read on Kamel startup and the 
flags are set accordingly.
 
-The file's default name is `kamel-config.yaml` . Which should be placed in 
either of this directory structure:
+The file's default name is `kamel-config.yaml`, it can be changed by setting 
the environment variable `KAMEL_CONFIG_NAME`. Kamel tries to read the file from 
the following directories in the given order:
 
- - `~/.kamel/`
- - `./.kamel/`
  - `.`
+ - `./.kamel/`
+ - `~/.kamel/`
 
-it can be overridden by setting an env value `KAMEL_CONFIG_NAME` to file path.
+It can be overridden by setting the environment variable `KAMEL_CONFIG_PATH` 
to file path.
 
 
-To configure this flag, create a file named kamel-config.yaml on the same 
directory as your integration. The file must contain a yaml structure as shown 
below:
+To configure this flag, create a file named `kamel-config.yaml` on the same 
directory as your integration. The file must contain a yaml structure as shown 
below:
 
-.Kamel-config.yaml
+.kamel-config.yaml
 
 ```yaml
 kamel:
     install:
-        namespace: kamel
-        healthPort: 8081
-        monitoringPort: 8082
+        health-port: 8081
+        monitoring-port: 8082
 ```
+
+As there are several supported locations, it can be handy to list a 
configuration file in one specific location, in this particular case the 
`config` command can be used.
+
+To list the configuration file used in practice by Kamel:
+
+[source,console]
+----
+$ kamel config --list
+The configuration file is read from /some/path/kamel-config.yaml
+kamel:
+  config:
+    default-namespace: some-name
+----
+
+Alternatively, the same result can be retrieved using the `--folder` flag with 
`used` as value.
+
+[source,console]
+----
+$ kamel config --list --folder used
+----
+
+The flag `--folder` accepts 4 other possible values, one per possible location.
+
+To list the configuration file in the working directory (`.`):
+
+[source,console]
+----
+$ kamel config --list --folder working
+----
+
+To list the configuration file in the folder `.kamel` located in the working 
directory (`./.kamel/`):
+
+[source,console]
+----
+$ kamel config --list --folder sub
+----
+
+To list the configuration file in the home directory (`~/.kamel/`):
+
+[source,console]
+----
+$ kamel config --list --folder home
+----
+
+To list the configuration file located in the folder whose path is set in the 
environment variable `KAMEL_CONFIG_PATH`:
+
+[source,console]
+----
+$ kamel config --list --folder env
+----
+
+The `config` command can also set the default namespace for all Kamel commands 
thanks to the flag `--default-namespace` as next:
+
+[source,console]
+----
+$ kamel config --default-namespace some-name
+----
+
+Note that the flag `--default-namespace` can be associated with `--list` to 
see directly the resulting content:
+
+[source,console]
+----
+$ kamel config --list --default-namespace some-name
+The configuration file is read from /some/path/kamel-config.yaml
+kamel:
+  config:
+    default-namespace: some-name
+  install:
+    health-port: 8081
+    monitoring-port: 8082
+----
diff --git a/e2e/namespace/install/cli/config_test.go 
b/e2e/namespace/install/cli/config_test.go
new file mode 100644
index 000000000..fada15bc0
--- /dev/null
+++ b/e2e/namespace/install/cli/config_test.go
@@ -0,0 +1,71 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> 
Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 common
+
+import (
+       "os"
+       "strings"
+       "testing"
+
+       corev1 "k8s.io/api/core/v1"
+
+       . "github.com/onsi/gomega"
+       "github.com/stretchr/testify/assert"
+
+       . "github.com/apache/camel-k/e2e/support"
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/pkg/cmd"
+)
+
+func TestKamelCLIConfig(t *testing.T) {
+       WithNewTestNamespace(t, func(ns string) {
+               operatorID := "camel-k-cli-config"
+               Expect(KamelInstallWithID(operatorID, 
ns).Execute()).To(Succeed())
+
+               t.Run("check default namespace", func(t *testing.T) {
+                       _, err := os.Stat(cmd.DefaultConfigLocation)
+                       assert.True(t, os.IsNotExist(err), "No file at 
"+cmd.DefaultConfigLocation+" was expected")
+                       t.Cleanup(func() { os.Remove(cmd.DefaultConfigLocation) 
})
+                       Expect(Kamel("config", "--default-namespace", 
ns).Execute()).To(Succeed())
+                       _, err = os.Stat(cmd.DefaultConfigLocation)
+                       assert.Nil(t, err, "A file at 
"+cmd.DefaultConfigLocation+" was expected")
+                       Expect(Kamel("run", "--operator-id", operatorID, 
"files/yaml.yaml").Execute()).To(Succeed())
+
+                       Eventually(IntegrationPodPhase(ns, "yaml"), 
TestTimeoutLong).Should(Equal(corev1.PodRunning))
+                       Eventually(IntegrationConditionStatus(ns, "yaml", 
v1.IntegrationConditionReady), TestTimeoutShort).
+                               Should(Equal(corev1.ConditionTrue))
+                       Eventually(IntegrationLogs(ns, "yaml"), 
TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+
+                       // first line of the integration logs
+                       logs := strings.Split(IntegrationLogs(ns, "yaml")(), 
"\n")[0]
+                       podName := IntegrationPod(ns, "yaml")().Name
+
+                       logsCLI := GetOutputStringAsync(Kamel("log", "yaml"))
+                       Eventually(logsCLI).Should(ContainSubstring("Monitoring 
pod " + podName))
+                       Eventually(logsCLI).Should(ContainSubstring(logs))
+
+                       // Clean up
+                       Expect(Kamel("delete", "--all").Execute()).To(Succeed())
+               })
+       })
+}
diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go
new file mode 100644
index 000000000..0b361d163
--- /dev/null
+++ b/pkg/cmd/config.go
@@ -0,0 +1,164 @@
+/*
+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 Kamel.
+       ConfigFolderUsed ConfigFolder = "used"
+)
+
+// nolint: unparam
+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 "", fmt.Errorf("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.Fprintf(cmd.OutOrStdout(), "No settings could be found in 
%s\n", cfg.location)
+       } else {
+               bs, err := yaml.Marshal(cfg.content)
+               if err != nil {
+                       return fmt.Errorf("unable to marshal config to YAML: 
%w", err)
+               }
+               fmt.Fprintf(cmd.OutOrStdout(), "The configuration file is read 
from %s\n", 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{}),
        }
 

Reply via email to