| /* |
| 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/fatih/structs" |
| "github.com/spf13/cobra" |
| "gopkg.in/yaml.v2" |
| |
| "github.com/apache/camel-k/deploy" |
| v1 "github.com/apache/camel-k/pkg/apis/camel/v1" |
| "github.com/apache/camel-k/pkg/trait" |
| "github.com/apache/camel-k/pkg/util/indentedwriter" |
| ) |
| |
| 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(cmd *cobra.Command, args []string) error { |
| if err := options.validate(args); err != nil { |
| return err |
| } |
| return options.run(cmd, args) |
| }, |
| Annotations: map[string]string{ |
| offlineCommandLabel: "true", |
| }, |
| } |
| |
| 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"` |
| Description string `json:"description" yaml:"description"` |
| } |
| |
| type traitPropertyDescription struct { |
| Name string `json:"name" yaml:"name"` |
| TypeName string `json:"type" yaml:"type"` |
| DefaultValue interface{} `json:"defaultValue,omitempty" yaml:"defaultValue,omitempty"` |
| Description string `json:"description" yaml:"description"` |
| } |
| |
| type traitMetaData struct { |
| Traits []traitDescription `yaml:"traits"` |
| } |
| |
| 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(cmd *cobra.Command, args []string) error { |
| var traitDescriptions []*traitDescription |
| var catalog = trait.NewCatalog(command.Context, nil) |
| |
| var traitMetaData = &traitMetaData{} |
| err := yaml.Unmarshal(deploy.Resource("/traits.yaml"), traitMetaData) |
| if err != nil { |
| return err |
| } |
| |
| 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), |
| } |
| |
| var targetTrait *traitDescription |
| for _, item := range traitMetaData.Traits { |
| item := item |
| if item.Name == t.ID() { |
| targetTrait = &item |
| td.Description = item.Description |
| break |
| } |
| } |
| computeTraitProperties(structs.Fields(t), &td.Properties, targetTrait) |
| 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.Fprintln(cmd.OutOrStdout(), string(res)) |
| case "YAML": |
| res, err := yaml.Marshal(traitDescriptions) |
| if err != nil { |
| return err |
| } |
| fmt.Fprintln(cmd.OutOrStdout(), string(res)) |
| default: |
| res, err := outputTraits(traitDescriptions) |
| if err != nil { |
| return err |
| } |
| fmt.Fprintln(cmd.OutOrStdout(), res) |
| } |
| |
| 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, targetTrait *traitDescription) { |
| for _, f := range fields { |
| if f.IsEmbedded() && f.IsExported() && f.Kind() == reflect.Struct { |
| computeTraitProperties(f.Fields(), properties, targetTrait) |
| } |
| |
| 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() |
| } |
| |
| // apply the description from metadata |
| if targetTrait != nil { |
| for _, item := range targetTrait.Properties { |
| if item.Name == tp.Name { |
| tp.Description = item.Description |
| } |
| } |
| } |
| |
| *properties = append(*properties, tp) |
| } |
| } |
| |
| func outputTraits(descriptions []*traitDescription) (string, error) { |
| return indentedwriter.IndentedString(func(out io.Writer) error { |
| 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, "") |
| } |
| |
| return nil |
| }) |
| } |