From c853c400aa8ed84e789f873ba581382bde634a71 Mon Sep 17 00:00:00 2001 From: Navid Shaikh Date: Thu, 13 Aug 2020 16:12:40 +0530 Subject: [PATCH] Channel type aliases via kn config and alias for InMemoryChannel - User can now configure channel type aliases in kn config and specify alias to GVK mappings for easy reference on CLI and refer with `--type` flag - User can also use inbuilt alias 'imc' for InMemoryChannel --- docs/cmd/kn_channel_create.md | 6 ++- pkg/kn/commands/channel/create.go | 10 ++-- pkg/kn/config/config.go | 25 ++++++++++ pkg/kn/config/config_test.go | 12 +++++ pkg/kn/config/testing.go | 10 ++-- pkg/kn/config/testing_test.go | 2 + pkg/kn/config/types.go | 20 ++++++++ pkg/kn/flags/channel_types.go | 76 +++++++++++++++++++++++++++++++ 8 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 pkg/kn/flags/channel_types.go diff --git a/docs/cmd/kn_channel_create.md b/docs/cmd/kn_channel_create.md index d258041d7f..c709efe7be 100644 --- a/docs/cmd/kn_channel_create.md +++ b/docs/cmd/kn_channel_create.md @@ -17,7 +17,9 @@ kn channel create NAME # Create a channel 'pipe' with default setting for channel configuration kn channel create pipe - # Create a channel 'imc1' of type InMemoryChannel + # Create a channel 'imc1' of type InMemoryChannel using inbuilt alias 'imc' + kn channel create imc1 --type imc + # same as above without using inbuilt alias but providing explicit GVK kn channel create imc1 --type messaging.knative.dev:v1beta1:InMemoryChannel # Create a channel 'k1' of type KafkaChannel @@ -29,7 +31,7 @@ kn channel create NAME ``` -h, --help help for create -n, --namespace string Specify the namespace to operate in. - --type string Type of the channel to create in the format 'Group:Version:Kind'. For example: '--type messaging.knative.dev:v1alpha1:KafkaChannel'. Default is to use messaging layer setting for default channel configuration. + --type string Override channel type to create, in the format '--type Group:Version:Kind'. If flag is not specified, it uses default messaging layer settings for channel type, cluster wide or specific namespace. You can configure aliases for channel types in kn config and refer the aliases with this flag. You can also refer inbuilt channel type InMemoryChannel using an alias 'imc' like '--type imc'. Examples: '--type messaging.knative.dev:v1alpha1:KafkaChannel' for specifying explicit Group:Version:Kind. ``` ### Options inherited from parent commands diff --git a/pkg/kn/commands/channel/create.go b/pkg/kn/commands/channel/create.go index 3fcb19cf56..455c2baece 100644 --- a/pkg/kn/commands/channel/create.go +++ b/pkg/kn/commands/channel/create.go @@ -24,11 +24,13 @@ import ( knerrors "knative.dev/client/pkg/errors" "knative.dev/client/pkg/kn/commands" + knflags "knative.dev/client/pkg/kn/flags" knmessagingv1beta1 "knative.dev/client/pkg/messaging/v1beta1" ) // NewChannelCreateCommand to create event channels func NewChannelCreateCommand(p *commands.KnParams) *cobra.Command { + var ctypeFlags knflags.ChannelTypeFlags cmd := &cobra.Command{ Use: "create NAME", Short: "Create an event channel", @@ -36,7 +38,9 @@ func NewChannelCreateCommand(p *commands.KnParams) *cobra.Command { # Create a channel 'pipe' with default setting for channel configuration kn channel create pipe - # Create a channel 'imc1' of type InMemoryChannel + # Create a channel 'imc1' of type InMemoryChannel using inbuilt alias 'imc' + kn channel create imc1 --type imc + # same as above without using inbuilt alias but providing explicit GVK kn channel create imc1 --type messaging.knative.dev:v1beta1:InMemoryChannel # Create a channel 'k1' of type KafkaChannel @@ -61,7 +65,7 @@ func NewChannelCreateCommand(p *commands.KnParams) *cobra.Command { cb := knmessagingv1beta1.NewChannelBuilder(name) if cmd.Flag("type").Changed { - gvk, err := processType(cmd.Flag("type").Value.String()) + gvk, err := ctypeFlags.Parse() if err != nil { return err } @@ -78,7 +82,7 @@ func NewChannelCreateCommand(p *commands.KnParams) *cobra.Command { }, } commands.AddNamespaceFlags(cmd.Flags(), false) - cmd.Flags().String("type", "", "Type of the channel to create in the format 'Group:Version:Kind'. For example: '--type messaging.knative.dev:v1alpha1:KafkaChannel'. Default is to use messaging layer setting for default channel configuration.") + ctypeFlags.Add(cmd) return cmd } diff --git a/pkg/kn/config/config.go b/pkg/kn/config/config.go index 48088e25cf..8225fdaa86 100644 --- a/pkg/kn/config/config.go +++ b/pkg/kn/config/config.go @@ -43,6 +43,9 @@ type config struct { // sinkMappings is a list of sink mapping sinkMappings []SinkMapping + + // channelTypeMappings is a list of channel type mapping + channelTypeMappings []ChannelTypeMapping } // ConfigFile returns the config file which is either the default XDG conform @@ -84,6 +87,10 @@ func (c *config) SinkMappings() []SinkMapping { return c.sinkMappings } +func (c *config) ChannelTypeMappings() []ChannelTypeMapping { + return c.channelTypeMappings +} + // Config used for flag binding var globalConfig = config{} @@ -143,6 +150,12 @@ func BootstrapConfig() error { // Deserialize sink mappings if configured err = parseSinkMappings() + if err != nil { + return err + } + + // Deserialize channel type mappings if configured + err = parseChannelTypeMappings() return err } @@ -243,6 +256,18 @@ func parseSinkMappings() error { return nil } +// parse channel type mappings and store them in the global configuration +func parseChannelTypeMappings() error { + if viper.IsSet(keyChannelTypeMappings) { + err := viper.UnmarshalKey(keyChannelTypeMappings, &globalConfig.channelTypeMappings) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("error while parsing channel type mappings in configuration file %s", + viper.ConfigFileUsed())) + } + } + return nil +} + // Prepare the default config file for the usage message func defaultConfigFileForUsageMessage() string { if runtime.GOOS == "windows" { diff --git a/pkg/kn/config/config_test.go b/pkg/kn/config/config_test.go index 53e5037c44..a0e7badc3f 100644 --- a/pkg/kn/config/config_test.go +++ b/pkg/kn/config/config_test.go @@ -37,6 +37,11 @@ eventing: resource: services group: core version: v1 + channel-type-mappings: + - alias: kafka + kind: KafkaChannel + group: messaging.knative.dev + version: v1alpha1 ` configFile, cleanup := setupConfig(t, configYaml) @@ -55,6 +60,13 @@ eventing: Group: "core", Version: "v1", }) + assert.Equal(t, len(GlobalConfig.ChannelTypeMappings()), 1) + assert.DeepEqual(t, (GlobalConfig.ChannelTypeMappings())[0], ChannelTypeMapping{ + Alias: "kafka", + Kind: "KafkaChannel", + Group: "messaging.knative.dev", + Version: "v1alpha1", + }) } func TestBootstrapConfigWithoutConfigFile(t *testing.T) { diff --git a/pkg/kn/config/testing.go b/pkg/kn/config/testing.go index 0ea521a77b..f7b2a05825 100644 --- a/pkg/kn/config/testing.go +++ b/pkg/kn/config/testing.go @@ -22,12 +22,14 @@ type TestConfig struct { TestConfigFile string TestLookupPluginsInPath bool TestSinkMappings []SinkMapping + TestChannelTypeMappings []ChannelTypeMapping } // Ensure that TestConfig implements the configuration interface var _ Config = &TestConfig{} -func (t TestConfig) PluginsDir() string { return t.TestPluginsDir } -func (t TestConfig) ConfigFile() string { return t.TestConfigFile } -func (t TestConfig) LookupPluginsInPath() bool { return t.TestLookupPluginsInPath } -func (t TestConfig) SinkMappings() []SinkMapping { return t.TestSinkMappings } +func (t TestConfig) PluginsDir() string { return t.TestPluginsDir } +func (t TestConfig) ConfigFile() string { return t.TestConfigFile } +func (t TestConfig) LookupPluginsInPath() bool { return t.TestLookupPluginsInPath } +func (t TestConfig) SinkMappings() []SinkMapping { return t.TestSinkMappings } +func (t TestConfig) ChannelTypeMappings() []ChannelTypeMapping { return t.TestChannelTypeMappings } diff --git a/pkg/kn/config/testing_test.go b/pkg/kn/config/testing_test.go index 5acf22b149..d18441aedf 100644 --- a/pkg/kn/config/testing_test.go +++ b/pkg/kn/config/testing_test.go @@ -28,10 +28,12 @@ func TestTestConfig(t *testing.T) { TestConfigFile: "configFile", TestLookupPluginsInPath: true, TestSinkMappings: nil, + TestChannelTypeMappings: nil, } assert.Equal(t, cfg.PluginsDir(), "pluginsDir") assert.Equal(t, cfg.ConfigFile(), "configFile") assert.Assert(t, cfg.LookupPluginsInPath()) assert.Assert(t, cfg.SinkMappings() == nil) + assert.Assert(t, cfg.ChannelTypeMappings() == nil) } diff --git a/pkg/kn/config/types.go b/pkg/kn/config/types.go index 281e1f8f85..0253ff7107 100644 --- a/pkg/kn/config/types.go +++ b/pkg/kn/config/types.go @@ -31,6 +31,9 @@ type Config interface { // SinkMappings returns additional mappings for sink prefixes to resources SinkMappings() []SinkMapping + + // ChannelTypeMappings returns additional mappings for channel type aliases + ChannelTypeMappings() []ChannelTypeMapping } // SinkMappings is the struct of sink prefix config in kn config @@ -49,11 +52,28 @@ type SinkMapping struct { Version string } +// ChannelTypeMapping is the struct of ChannelType alias config in kn config +type ChannelTypeMapping struct { + + // Alias is the mapping alias (like "kafka") + Alias string + + // Kind is the name for the mapped resource kind (like "KafkaChannel") + Kind string + + // Group is the API group for the mapped resource kind (like "messaging.knative.dev") + Group string + + // Version is the API version for the mapped resource kind (like "v1alpha1") + Version string +} + // config Keys for looking up in viper const ( keyPluginsDirectory = "plugins.directory" keyPluginsLookupInPath = "plugins.path-lookup" keySinkMappings = "eventing.sink-mappings" + keyChannelTypeMappings = "eventing.channel-type-mappings" ) // legacy config keys, deprecated diff --git a/pkg/kn/flags/channel_types.go b/pkg/kn/flags/channel_types.go new file mode 100644 index 0000000000..fcd70aab85 --- /dev/null +++ b/pkg/kn/flags/channel_types.go @@ -0,0 +1,76 @@ +// Copyright © 2020 The Knative Authors +// +// Licensed 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 flags + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime/schema" + + "knative.dev/client/pkg/kn/config" +) + +type ChannelTypeFlags struct { + ctype string +} + +func (i *ChannelTypeFlags) Add(cmd *cobra.Command) { + cmd.Flags().StringVar(&i.ctype, + "type", + "", + "Override channel type to create, in the format '--type Group:Version:Kind'. "+ + "If flag is not specified, it uses default messaging layer settings for channel type, cluster wide or specific namespace. "+ + "You can configure aliases for channel types in kn config and refer the aliases with this flag. "+ + "You can also refer inbuilt channel type InMemoryChannel using an alias 'imc' like '--type imc'. "+ + "Examples: '--type messaging.knative.dev:v1alpha1:KafkaChannel' for specifying explicit Group:Version:Kind.") + + for _, p := range config.GlobalConfig.ChannelTypeMappings() { + //user configration might override the default configuration + ctypeMappings[p.Alias] = schema.GroupVersionKind{ + Kind: p.Kind, + Group: p.Group, + Version: p.Version, + } + } +} + +// ctypeMappings maps aliases used for channel types to their GroupVersionKind +var ctypeMappings = map[string]schema.GroupVersionKind{ + "imc": { + Group: "messaging.knative.dev", + Version: "v1beta1", + Kind: "InMemoryChannel", + }, +} + +func (i *ChannelTypeFlags) Parse() (*schema.GroupVersionKind, error) { + parts := strings.Split(i.ctype, ":") + switch len(parts) { + case 1: + if typ, ok := ctypeMappings[i.ctype]; ok { + return &typ, nil + } + return nil, fmt.Errorf("Error: unsupported channel type alias: '%s'", parts) + case 3: + if parts[0] == "" || parts[1] == "" || parts[2] == "" { + return nil, fmt.Errorf("Error: incorrect value '%s' for '--type', must be in the format 'Group:Version:Kind'", i.ctype) + } + return &schema.GroupVersionKind{Group: parts[0], Version: parts[1], Kind: parts[2]}, nil + default: + return nil, fmt.Errorf("Error: incorrect value '%s' for '--type', must be in the format 'Group:Version:Kind' or configure an alias in kn config", i.ctype) + } +}