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{}),
}