diff --git a/go.sum b/go.sum index d9b9953bff..65c6ad4be6 100644 --- a/go.sum +++ b/go.sum @@ -965,6 +965,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 17ccad2d7e..6bd3f7a6d2 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -98,6 +98,9 @@ func newCmdInstall(rootCmdOptions *RootCmdOptions) (*cobra.Command, *installCmdO cmd.Flags().String("maven-settings", "", "Configure the source of the maven settings (configmap|secret:name[/key])") cmd.Flags().StringArray("maven-repository", nil, "Add a maven repository") + // save + cmd.Flags().Bool("save", false, "Save the install parameters into the default kamel configuration file (kamel-config.yaml)") + // completion support configureBashAnnotationForFlag( &cmd, @@ -119,6 +122,7 @@ type installCmdOptions struct { ExampleSetup bool `mapstructure:"example"` Global bool `mapstructure:"global"` KanikoBuildCache bool `mapstructure:"kaniko-build-cache"` + Save bool `mapstructure:"save"` ClusterType string `mapstructure:"cluster-type"` OutputFormat string `mapstructure:"output"` CamelVersion string `mapstructure:"camel-version"` @@ -323,6 +327,12 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error { return o.printOutput(collection) } + if o.Save { + if err := saveDefaultConfig(cobraCmd, "kamel.install", "kamel.install"); err != nil { + return err + } + } + return nil } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 699d3cdff0..a232039e54 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -78,11 +78,12 @@ func kamelPostAddCommandInit(cmd *cobra.Command) error { } configName := os.Getenv("KAMEL_CONFIG_NAME") - if configName != "" { - configName = "config" + if configName == "" { + configName = "kamel-config" } viper.SetConfigName(configName) + viper.AddConfigPath(".") viper.AddConfigPath(".kamel") viper.AddConfigPath("$HOME/.kamel") viper.AutomaticEnv() diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 3df33faf49..d8ab15caff 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -40,10 +40,8 @@ import ( k8slog "github.com/apache/camel-k/pkg/util/kubernetes/log" "github.com/apache/camel-k/pkg/util/sync" "github.com/apache/camel-k/pkg/util/watch" - "github.com/pkg/errors" "github.com/spf13/cobra" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -63,7 +61,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions) Short: "Run a integration on Kubernetes", Long: `Deploys and execute a integration pod on Kubernetes.`, Args: options.validateArgs, - PreRunE: decode(&options), + PreRunE: options.decode, RunE: options.run, } @@ -88,6 +86,8 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions) cmd.Flags().StringArrayP("volume", "v", nil, "Mount a volume into the integration container. E.g \"-v pvcname:/container/path\"") cmd.Flags().StringArrayP("env", "e", nil, "Set an environment variable in the integration container. E.g \"-e MY_VAR=my-value\"") + cmd.Flags().Bool("save", false, "Save the run parameters into the default kamel configuration file (kamel-config.yaml)") + // completion support configureKnownCompletions(&cmd) @@ -101,6 +101,7 @@ type runCmdOptions struct { Logs bool `mapstructure:"logs"` Sync bool `mapstructure:"sync"` Dev bool `mapstructure:"dev"` + Save bool `mapstructure:"save"` IntegrationKit string `mapstructure:"kit"` IntegrationName string `mapstructure:"name"` Profile string `mapstructure:"profile"` @@ -118,6 +119,23 @@ type runCmdOptions struct { EnvVars []string `mapstructure:"envs"` } +func (o *runCmdOptions) decode(cmd *cobra.Command, args []string) error { + pathToRoot := pathToRoot(cmd) + if err := decodeKey(o, pathToRoot); err != nil { + return err + } + + name := o.GetIntegrationName(args) + if name != "" { + secondaryPath := pathToRoot + ".integration." + name + if err := decodeKey(o, secondaryPath); err != nil { + return err + } + } + + return nil +} + func (o *runCmdOptions) validateArgs(_ *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("run expects at least 1 argument, received 0") @@ -241,6 +259,17 @@ func (o *runCmdOptions) run(cmd *cobra.Command, args []string) error { // Let's add a Wait point, otherwise the script terminates <-o.Context.Done() } + + if o.Save { + name := o.GetIntegrationName(args) + if name != "" { + key := fmt.Sprintf("kamel.run.integration.%s", name) + if err := saveDefaultConfig(cmd, "kamel.run", key); err != nil { + return err + } + } + } + return nil } @@ -304,13 +333,7 @@ func (o *runCmdOptions) createIntegration(c client.Client, sources []string) (*v func (o *runCmdOptions) updateIntegrationCode(c client.Client, sources []string) (*v1.Integration, error) { namespace := o.Namespace - name := "" - if o.IntegrationName != "" { - name = o.IntegrationName - name = kubernetes.SanitizeName(name) - } else if len(sources) == 1 { - name = kubernetes.SanitizeName(sources[0]) - } + name := o.GetIntegrationName(sources) if name == "" { return nil, errors.New("unable to determine integration name") @@ -462,6 +485,17 @@ func (o *runCmdOptions) updateIntegrationCode(c client.Client, sources []string) return &integration, nil } +func (o *runCmdOptions) GetIntegrationName(sources []string) string { + name := "" + if o.IntegrationName != "" { + name = o.IntegrationName + name = kubernetes.SanitizeName(name) + } else if len(sources) == 1 { + name = kubernetes.SanitizeName(sources[0]) + } + return name +} + func (*runCmdOptions) loadData(fileName string, compress bool) (string, error) { var content []byte var err error diff --git a/pkg/cmd/util.go b/pkg/cmd/util.go index 7bb4bd0859..bb38cdfc8b 100644 --- a/pkg/cmd/util.go +++ b/pkg/cmd/util.go @@ -25,6 +25,7 @@ import ( "reflect" "strings" + "github.com/apache/camel-k/pkg/util/config" "github.com/mitchellh/mapstructure" v1 "github.com/apache/camel-k/pkg/apis/camel/v1" @@ -158,6 +159,36 @@ func decode(target interface{}) func(*cobra.Command, []string) error { } } +func saveDefaultConfig(cmd *cobra.Command, from string, to string) error { + settings := viper.AllSettings() + cfg, err := config.LoadDefault() + if err != nil { + return err + } + cfg.Delete(to) + cfg.Set(settings, from, to, func(s string) bool { + if s == "save" { + return false + } + pl := p.NewClient() + f := cmd.Flag(s) + if f == nil { + // may be a plural flag, let's lookup the singular version to check if changed + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if pl.Plural(flag.Name) == s { + f = flag + } + }) + } + + return f != nil && f.Changed + }) + if err := cfg.WriteDefault(); err != nil { + return err + } + return nil +} + func stringToSliceHookFunc(comma rune) mapstructure.DecodeHookFunc { return func( f reflect.Kind, diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go new file mode 100644 index 0000000000..b6eb5af753 --- /dev/null +++ b/pkg/util/config/config.go @@ -0,0 +1,141 @@ +/* +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 config + +import ( + "io/ioutil" + "os" + "strings" + + p "github.com/gertd/go-pluralize" + yaml "gopkg.in/yaml.v2" +) + +const ( + // DefaultConfigLocation is the main place where the kamel config is stored + DefaultConfigLocation = "./kamel-config.yaml" +) + +// KamelConfig is a helper class to manipulate kamel configuration files +type KamelConfig struct { + config map[string]interface{} +} + +// LoadDefault loads the kamel configuration from the default location +func LoadDefault() (*KamelConfig, error) { + return LoadConfig(DefaultConfigLocation) +} + +// LoadConfig loads a kamel configuration file +func LoadConfig(file string) (*KamelConfig, error) { + config := make(map[string]interface{}) + data, err := ioutil.ReadFile(file) + if err != nil && os.IsNotExist(err) { + return &KamelConfig{config: config}, nil + } else if err != nil { + return nil, err + } + if err = yaml.Unmarshal(data, &config); err != nil { + return nil, err + } + + return &KamelConfig{config: config}, nil +} + +// Set allows to replace a subtree with a given config +func (c *KamelConfig) Set(values map[string]interface{}, from, to string, filter func(string) bool) { + source := navigate(values, from, false) + destination := navigate(c.config, to, true) + pl := p.NewClient() + for k, v := range source { + if filter(k) { + plural := pl.Plural(k) + key := k + if source[plural] != nil { + // prefer plural names if available + key = plural + } + destination[key] = v + } + } +} + +// Delete allows to remove a substree from the kamel config +func (c *KamelConfig) Delete(path string) { + leaf := navigate(c.config, path, false) + for k := range leaf { + delete(leaf, k) + } +} + +// WriteDefault writes the configuration in the default location +func (c *KamelConfig) WriteDefault() error { + return c.Write(DefaultConfigLocation) +} + +// Write writes a kamel configuration to a file +func (c *KamelConfig) Write(file string) error { + data, err := yaml.Marshal(c.config) + if err != nil { + return err + } + return ioutil.WriteFile(file, data, 0777) +} + +func navigate(values map[string]interface{}, prefix string, create bool) map[string]interface{} { + nodes := strings.Split(prefix, ".") + + for _, node := range nodes { + v := values[node] + + if v == nil { + if create { + v = make(map[string]interface{}) + values[node] = v + } else { + return nil + } + } + + if m, ok := v.(map[string]interface{}); ok { + values = m + } else if mg, ok := v.(map[interface{}]interface{}); ok { + converted := convert(mg) + values[node] = converted + values = converted + } else { + if create { + child := make(map[string]interface{}) + values[node] = child + return child + } + return nil + } + } + return values +} + +func convert(m map[interface{}]interface{}) map[string]interface{} { + res := make(map[string]interface{}) + for k, v := range m { + if ks, ok := k.(string); ok { + res[ks] = v + } + } + return res +}