This is an automated email from the ASF dual-hosted git repository. astefanutti pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/master by this push: new 89f3a29 feat: Offer easier access to trait list 89f3a29 is described below commit 89f3a29a860fa6e6fbd0de617a679f37ac810917 Author: James Netherton <jamesnether...@gmail.com> AuthorDate: Wed Jan 22 10:27:14 2020 +0000 feat: Offer easier access to trait list fixes #1203 --- pkg/cmd/root.go | 25 ++++++ pkg/cmd/trait_help.go | 219 +++++++++++++++++++++++++++++++++++++++++++++ pkg/cmd/trait_help_test.go | 37 ++++++++ 3 files changed, 281 insertions(+) diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index a232039..6413cd4 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -50,6 +50,11 @@ func NewKamelCommand(ctx context.Context) (*cobra.Command, error) { var err error cmd := kamelPreAddCommandInit(&options) addKamelSubcommands(cmd, &options) + + if err := addHelpSubCommands(cmd, &options); err != nil { + return cmd, err + } + err = kamelPostAddCommandInit(cmd) return cmd, err @@ -117,6 +122,26 @@ func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) { cmd.AddCommand(cmdOnly(newCmdBuilder(options))) } +func addHelpSubCommands(cmd *cobra.Command, options *RootCmdOptions) error { + cmd.InitDefaultHelpCmd() + + var helpCmd *cobra.Command + for _, c := range cmd.Commands() { + if c.Name() == "help" { + helpCmd = c + break + } + } + + if helpCmd == nil { + return errors.New("could not find any configured help command") + } + + helpCmd.AddCommand(cmdOnly(newTraitHelpCmd(options))) + + return nil +} + func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error { if command.Namespace == "" { var current string diff --git a/pkg/cmd/trait_help.go b/pkg/cmd/trait_help.go new file mode 100644 index 0000000..170abef --- /dev/null +++ b/pkg/cmd/trait_help.go @@ -0,0 +1,219 @@ +/* +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 ( + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strings" + + "github.com/apache/camel-k/pkg/util/indentedwriter" + + "github.com/fatih/structs" + "gopkg.in/yaml.v2" + + "github.com/apache/camel-k/pkg/trait" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/spf13/cobra" +) + +func newTraitHelpCmd(rootCmdOptions *RootCmdOptions) (*cobra.Command, *traitHelpCommandOptions) { + options := traitHelpCommandOptions{ + RootCmdOptions: rootCmdOptions, + } + + cmd := cobra.Command{ + Use: "trait", + Short: "Trait help information", + Long: `Displays help information for traits in a specified output format.`, + PreRunE: decode(&options), + RunE: func(_ *cobra.Command, args []string) error { + if err := options.validate(args); err != nil { + return err + } + return options.run(args) + }, + } + + cmd.Flags().Bool("all", false, "Include all traits") + cmd.Flags().StringP("output", "o", "", "Output format. One of json, yaml") + + return &cmd, &options +} + +type traitHelpCommandOptions struct { + *RootCmdOptions + IncludeAll bool `mapstructure:"all"` + OutputFormat string `mapstructure:"output"` +} + +type traitDescription struct { + Name trait.ID `json:"name" yaml:"name"` + Platform bool `json:"platform" yaml:"platform"` + Profiles []string `json:"profiles" yaml:"profiles"` + Properties []traitPropertyDescription `json:"properties" yaml:"properties"` +} + +type traitPropertyDescription struct { + Name string `json:"name" yaml:"name"` + TypeName string `json:"type" yaml:"type"` + DefaultValue interface{} `json:"defaultValue,omitempty" yaml:"defaultValue,omitempty"` +} + +func (command *traitHelpCommandOptions) validate(args []string) error { + if command.IncludeAll && len(args) > 0 { + return errors.New("invalid combination: both all flag and a named trait is set") + } + if !command.IncludeAll && len(args) == 0 { + return errors.New("invalid combination: neither all flag nor a named trait is set") + } + return nil +} + +func (command *traitHelpCommandOptions) run(args []string) error { + var traitDescriptions []*traitDescription + var catalog = trait.NewCatalog(command.Context, nil) + + for _, tp := range v1.AllTraitProfiles { + traits := catalog.TraitsForProfile(tp) + for _, t := range traits { + if len(args) == 1 && trait.ID(args[0]) != t.ID() { + continue + } + + td := findTraitDescription(t.ID(), traitDescriptions) + if td == nil { + td = &traitDescription{ + Name: t.ID(), + Platform: t.IsPlatformTrait(), + Profiles: make([]string, 0), + } + computeTraitProperties(structs.Fields(t), &td.Properties) + traitDescriptions = append(traitDescriptions, td) + } + td.addProfile(string(tp)) + } + } + + if len(args) == 1 && len(traitDescriptions) == 0 { + return fmt.Errorf("no trait named '%s' exists", args[0]) + } + + switch strings.ToUpper(command.OutputFormat) { + case "JSON": + res, err := json.Marshal(traitDescriptions) + if err != nil { + return err + } + fmt.Println(string(res)) + case "YAML": + res, err := yaml.Marshal(traitDescriptions) + if err != nil { + return err + } + fmt.Println(string(res)) + default: + fmt.Println(outputTraits(traitDescriptions)) + } + + return nil +} + +func (td *traitDescription) addProfile(tp string) { + for _, p := range td.Profiles { + if p == tp { + return + } + } + td.Profiles = append(td.Profiles, tp) +} + +func findTraitDescription(id trait.ID, traitDescriptions []*traitDescription) *traitDescription { + for _, td := range traitDescriptions { + if td.Name == id { + return td + } + } + return nil +} + +func computeTraitProperties(fields []*structs.Field, properties *[]traitPropertyDescription) { + for _, f := range fields { + if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct { + computeTraitProperties(f.Fields(), properties) + } + + if !f.IsExported() || f.IsEmbedded() { + continue + } + + property := f.Tag("property") + if property == "" { + continue + } + + tp := traitPropertyDescription{} + tp.Name = property + + switch f.Kind() { + case reflect.Ptr: + tp.TypeName = reflect.TypeOf(f.Value()).Elem().String() + case reflect.Slice: + tp.TypeName = fmt.Sprintf("slice:%s", reflect.TypeOf(f.Value()).Elem().String()) + default: + tp.TypeName = f.Kind().String() + } + + if f.IsZero() { + if tp.TypeName == "bool" { + tp.DefaultValue = false + } else { + tp.DefaultValue = nil + } + } else { + tp.DefaultValue = f.Value() + } + + *properties = append(*properties, tp) + } +} + +func outputTraits(descriptions []*traitDescription) string { + return indentedwriter.IndentedString(func(out io.Writer) { + w := indentedwriter.NewWriter(out) + + for _, td := range descriptions { + w.Write(0, "Name:\t%s\n", td.Name) + w.Write(0, "Profiles:\t%s\n", strings.Join(td.Profiles, ",")) + w.Write(0, "Platform:\t%t\n", td.Platform) + w.Write(0, "Properties:\n") + for _, p := range td.Properties { + w.Write(1, "%s:\n", p.Name) + w.Write(2, "Type:\t%s\n", p.TypeName) + if p.DefaultValue != nil { + w.Write(2, "Default Value:\t%v\n", p.DefaultValue) + } + } + w.Writeln(0, "") + } + }) +} diff --git a/pkg/cmd/trait_help_test.go b/pkg/cmd/trait_help_test.go new file mode 100644 index 0000000..e438ce0 --- /dev/null +++ b/pkg/cmd/trait_help_test.go @@ -0,0 +1,37 @@ +/* +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 ( + "testing" + + "github.com/apache/camel-k/pkg/util/test" +) + +func TestHelpForNonExistentTrait(t *testing.T) { + options, rootCommand := kamelTestPreAddCommandInit() + traitHelpCommand, _ := newTraitHelpCmd(options) + rootCommand.AddCommand(traitHelpCommand) + + kamelTestPostAddCommandInit(t, rootCommand) + + _, err := test.ExecuteCommand(rootCommand, "trait", "foobar") + if err == nil { + t.Fatalf("Expected error result for invalid trait 'foobar'") + } +}