Skip to content

Commit

Permalink
Merge pull request #1217 from nicolaferraro/save-config
Browse files Browse the repository at this point in the history
Fix #1181: add --save option to install and run
  • Loading branch information
oscerd authored Jan 23, 2020
2 parents 7ffcd69 + 3c82acc commit acab986
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 12 deletions.
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
10 changes: 10 additions & 0 deletions pkg/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"`
Expand Down Expand Up @@ -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
}

Expand Down
5 changes: 3 additions & 2 deletions pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
54 changes: 44 additions & 10 deletions pkg/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
}

Expand All @@ -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)

Expand All @@ -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"`
Expand All @@ -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")
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions pkg/cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
141 changes: 141 additions & 0 deletions pkg/util/config/config.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit acab986

Please sign in to comment.