From e253ea93776c35e02b9e0f2320e833a008c7e73a Mon Sep 17 00:00:00 2001 From: Daisy Guo Date: Tue, 21 Jul 2020 18:31:35 +0800 Subject: [PATCH] seperate PodSpec from Service flags --- .../service/configuration_edit_flags.go | 139 +++++++---------- pkg/kn/commands/service/create.go | 2 +- pkg/kn/flags/podspec.go | 143 ++++++++++++++++++ pkg/kn/flags/podspec_test.go | 51 +++++++ 4 files changed, 246 insertions(+), 89 deletions(-) create mode 100644 pkg/kn/flags/podspec.go create mode 100644 pkg/kn/flags/podspec_test.go diff --git a/pkg/kn/commands/service/configuration_edit_flags.go b/pkg/kn/commands/service/configuration_edit_flags.go index 6321e9a8ad..101dd36d9e 100644 --- a/pkg/kn/commands/service/configuration_edit_flags.go +++ b/pkg/kn/commands/service/configuration_edit_flags.go @@ -15,7 +15,6 @@ package service import ( - "errors" "fmt" "strings" @@ -33,36 +32,24 @@ import ( ) type ConfigurationEditFlags struct { + //Fields for PodSpecFlags + PodSpecFlags knflags.PodSpecFlags + // Direct field manipulation - Image uniqueStringArg - Env []string - EnvFrom []string - Mount []string - Volume []string - - Command string - Arg []string - - RequestsFlags, LimitsFlags ResourceFlags // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0 - Resources knflags.ResourceOptions - Scale int - MinScale int - MaxScale int - ConcurrencyTarget int - ConcurrencyLimit int - ConcurrencyUtilization int - AutoscaleWindow string - Port string - Labels []string - LabelsService []string - LabelsRevision []string - NamePrefix string - RevisionName string - ServiceAccountName string - ImagePullSecrets string - Annotations []string - ClusterLocal bool - User int64 + Scale int + MinScale int + MaxScale int + ConcurrencyTarget int + ConcurrencyLimit int + ConcurrencyUtilization int + AutoscaleWindow string + Labels []string + LabelsService []string + LabelsRevision []string + NamePrefix string + RevisionName string + Annotations []string + ClusterLocal bool // Preferences about how to do the action. LockToDigest bool @@ -75,30 +62,6 @@ type ConfigurationEditFlags struct { flags []string } -type ResourceFlags struct { - CPU string - Memory string -} - -// -- uniqueStringArg Value -// Custom implementation of flag.Value interface to prevent multiple value assignment. -// Useful to enforce unique use of flags, e.g. --image. -type uniqueStringArg string - -func (s *uniqueStringArg) Set(val string) error { - if len(*s) > 0 { - return errors.New("can be provided only once") - } - *s = uniqueStringArg(val) - return nil -} - -func (s *uniqueStringArg) Type() string { - return "string" -} - -func (s *uniqueStringArg) String() string { return string(*s) } - // markFlagMakesRevision indicates that a flag will create a new revision if you // set it. func (p *ConfigurationEditFlags) markFlagMakesRevision(f string) { @@ -107,22 +70,22 @@ func (p *ConfigurationEditFlags) markFlagMakesRevision(f string) { // addSharedFlags adds the flags common between create & update. func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { - command.Flags().VarP(&p.Image, "image", "", "Image to run.") + command.Flags().VarP(&p.PodSpecFlags.Image, "image", "", "Image to run.") p.markFlagMakesRevision("image") - command.Flags().StringArrayVarP(&p.Env, "env", "e", []string{}, + command.Flags().StringArrayVarP(&p.PodSpecFlags.Env, "env", "e", []string{}, "Environment variable to set. NAME=value; you may provide this flag "+ "any number of times to set multiple environment variables. "+ "To unset, specify the environment variable name followed by a \"-\" (e.g., NAME-).") p.markFlagMakesRevision("env") - command.Flags().StringArrayVarP(&p.EnvFrom, "env-from", "", []string{}, + command.Flags().StringArrayVarP(&p.PodSpecFlags.EnvFrom, "env-from", "", []string{}, "Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). "+ "Example: --env-from cm:myconfigmap or --env-from secret:mysecret. "+ "You can use this flag multiple times. "+ "To unset a ConfigMap/Secret reference, append \"-\" to the name, e.g. --env-from cm:myconfigmap-.") p.markFlagMakesRevision("env-from") - command.Flags().StringArrayVarP(&p.Mount, "mount", "", []string{}, + command.Flags().StringArrayVarP(&p.PodSpecFlags.Mount, "mount", "", []string{}, "Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. "+ "Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. "+ "When a configmap or a secret is specified, a corresponding volume is automatically generated. "+ @@ -130,25 +93,25 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "For unmounting a directory, append \"-\", e.g. --mount /mydir-, which also removes any auto-generated volume.") p.markFlagMakesRevision("mount") - command.Flags().StringArrayVarP(&p.Volume, "volume", "", []string{}, + command.Flags().StringArrayVarP(&p.PodSpecFlags.Volume, "volume", "", []string{}, "Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). "+ "Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. "+ "You can use this flag multiple times. "+ "To unset a ConfigMap/Secret reference, append \"-\" to the name, e.g. --volume myvolume-.") p.markFlagMakesRevision("volume") - command.Flags().StringVarP(&p.Command, "cmd", "", "", + command.Flags().StringVarP(&p.PodSpecFlags.Command, "cmd", "", "", "Specify command to be used as entrypoint instead of default one. "+ "Example: --cmd /app/start or --cmd /app/start --arg myArg to pass aditional arguments.") p.markFlagMakesRevision("cmd") - command.Flags().StringArrayVarP(&p.Arg, "arg", "", []string{}, + command.Flags().StringArrayVarP(&p.PodSpecFlags.Arg, "arg", "", []string{}, "Add argument to the container command. "+ "Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. "+ "You can use this flag multiple times.") p.markFlagMakesRevision("arg") - command.Flags().StringSliceVar(&p.Resources.Limits, + command.Flags().StringSliceVar(&p.PodSpecFlags.Resources.Limits, "limit", nil, "The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. "+ @@ -156,7 +119,7 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "To unset a resource limit, append \"-\" to the resource name, e.g. '--limit memory-'.") p.markFlagMakesRevision("limit") - command.Flags().StringSliceVar(&p.Resources.Requests, + command.Flags().StringSliceVar(&p.PodSpecFlags.Resources.Requests, "request", nil, "The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. "+ @@ -164,21 +127,21 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "To unset a resource request, append \"-\" to the resource name, e.g. '--request cpu-'.") p.markFlagMakesRevision("request") - command.Flags().StringVar(&p.RequestsFlags.CPU, "requests-cpu", "", + command.Flags().StringVar(&p.PodSpecFlags.RequestsFlags.CPU, "requests-cpu", "", "DEPRECATED: please use --request instead. The requested CPU (e.g., 250m).") p.markFlagMakesRevision("requests-cpu") - command.Flags().StringVar(&p.RequestsFlags.Memory, "requests-memory", "", + command.Flags().StringVar(&p.PodSpecFlags.RequestsFlags.Memory, "requests-memory", "", "DEPRECATED: please use --request instead. The requested memory (e.g., 64Mi).") p.markFlagMakesRevision("requests-memory") // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0 - command.Flags().StringVar(&p.LimitsFlags.CPU, "limits-cpu", "", + command.Flags().StringVar(&p.PodSpecFlags.LimitsFlags.CPU, "limits-cpu", "", "DEPRECATED: please use --limit instead. The limits on the requested CPU (e.g., 1000m).") p.markFlagMakesRevision("limits-cpu") // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0 - command.Flags().StringVar(&p.LimitsFlags.Memory, "limits-memory", "", + command.Flags().StringVar(&p.PodSpecFlags.LimitsFlags.Memory, "limits-memory", "", "DEPRECATED: please use --limit instead. The limits on the requested memory (e.g., 1024Mi).") p.markFlagMakesRevision("limits-memory") @@ -213,7 +176,7 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "Percentage of concurrent requests utilization before scaling up.") p.markFlagMakesRevision("concurrency-utilization") - command.Flags().StringVarP(&p.Port, "port", "p", "", "The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.") + command.Flags().StringVarP(&p.PodSpecFlags.Port, "port", "p", "", "The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.") p.markFlagMakesRevision("port") command.Flags().StringArrayVarP(&p.Labels, "label", "l", []string{}, @@ -247,7 +210,7 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "the image. (--no-lock-to-digest pulls the image tag afresh with each new revision)") // Don't mark as changing the revision. - command.Flags().StringVar(&p.ServiceAccountName, + command.Flags().StringVar(&p.PodSpecFlags.ServiceAccountName, "service-account", "", "Service account name to set. An empty argument (\"\") clears the service account. The referenced service account must exist in the service's namespace.") @@ -259,12 +222,12 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "To unset, specify the annotation name followed by a \"-\" (e.g., name-).") p.markFlagMakesRevision("annotation") - command.Flags().StringVar(&p.ImagePullSecrets, + command.Flags().StringVar(&p.PodSpecFlags.ImagePullSecrets, "pull-secret", "", "Image pull secret to set. An empty argument (\"\") clears the pull secret. The referenced secret must exist in the service's namespace.") p.markFlagMakesRevision("pull-secret") - command.Flags().Int64VarP(&p.User, "user", "", 0, "The user ID to run the container (e.g., 1001).") + command.Flags().Int64VarP(&p.PodSpecFlags.User, "user", "", 0, "The user ID to run the container (e.g., 1001).") p.markFlagMakesRevision("user") } @@ -290,7 +253,7 @@ func (p *ConfigurationEditFlags) Apply( template := &service.Spec.Template if cmd.Flags().Changed("env") { - envMap, err := util.MapFromArrayAllowingSingles(p.Env, "=") + envMap, err := util.MapFromArrayAllowingSingles(p.PodSpecFlags.Env, "=") if err != nil { return fmt.Errorf("Invalid --env: %w", err) } @@ -305,7 +268,7 @@ func (p *ConfigurationEditFlags) Apply( if cmd.Flags().Changed("env-from") { envFromSourceToUpdate := []string{} envFromSourceToRemove := []string{} - for _, name := range p.EnvFrom { + for _, name := range p.PodSpecFlags.EnvFrom { if name == "-" { return fmt.Errorf("\"-\" is not a valid value for \"--env-from\"") } else if strings.HasSuffix(name, "-") { @@ -322,12 +285,12 @@ func (p *ConfigurationEditFlags) Apply( } if cmd.Flags().Changed("mount") || cmd.Flags().Changed("volume") { - mountsToUpdate, mountsToRemove, err := util.OrderedMapAndRemovalListFromArray(p.Mount, "=") + mountsToUpdate, mountsToRemove, err := util.OrderedMapAndRemovalListFromArray(p.PodSpecFlags.Mount, "=") if err != nil { return fmt.Errorf("Invalid --mount: %w", err) } - volumesToUpdate, volumesToRemove, err := util.OrderedMapAndRemovalListFromArray(p.Volume, "=") + volumesToUpdate, volumesToRemove, err := util.OrderedMapAndRemovalListFromArray(p.PodSpecFlags.Volume, "=") if err != nil { return fmt.Errorf("Invalid --volume: %w", err) } @@ -349,7 +312,7 @@ func (p *ConfigurationEditFlags) Apply( imageSet := false if cmd.Flags().Changed("image") { - err = servinglib.UpdateImage(template, p.Image.String()) + err = servinglib.UpdateImage(template, p.PodSpecFlags.Image.String()) if err != nil { return err } @@ -383,11 +346,11 @@ func (p *ConfigurationEditFlags) Apply( fmt.Fprintf(cmd.OutOrStdout(), "\nWARNING: flags --requests-cpu / --requests-memory are deprecated and going to be removed in future release, please use --request instead.\n\n") } - limitsResources, err := p.computeResources(p.LimitsFlags) + limitsResources, err := p.computeResources(p.PodSpecFlags.LimitsFlags) if err != nil { return err } - requestsResources, err := p.computeResources(p.RequestsFlags) + requestsResources, err := p.computeResources(p.PodSpecFlags.RequestsFlags) if err != nil { return err } @@ -396,32 +359,32 @@ func (p *ConfigurationEditFlags) Apply( return err } - requestsToRemove, limitsToRemove, err := p.Resources.Validate() + requestsToRemove, limitsToRemove, err := p.PodSpecFlags.Resources.Validate() if err != nil { return err } - err = servinglib.UpdateResources(template, p.Resources.ResourceRequirements, requestsToRemove, limitsToRemove) + err = servinglib.UpdateResources(template, p.PodSpecFlags.Resources.ResourceRequirements, requestsToRemove, limitsToRemove) if err != nil { return err } if cmd.Flags().Changed("cmd") { - err = servinglib.UpdateContainerCommand(template, p.Command) + err = servinglib.UpdateContainerCommand(template, p.PodSpecFlags.Command) if err != nil { return err } } if cmd.Flags().Changed("arg") { - err = servinglib.UpdateContainerArg(template, p.Arg) + err = servinglib.UpdateContainerArg(template, p.PodSpecFlags.Arg) if err != nil { return err } } if cmd.Flags().Changed("port") { - err = servinglib.UpdateContainerPort(template, p.Port) + err = servinglib.UpdateContainerPort(template, p.PodSpecFlags.Port) if err != nil { return err } @@ -527,18 +490,18 @@ func (p *ConfigurationEditFlags) Apply( } if cmd.Flags().Changed("service-account") { - err = servinglib.UpdateServiceAccountName(template, p.ServiceAccountName) + err = servinglib.UpdateServiceAccountName(template, p.PodSpecFlags.ServiceAccountName) if err != nil { return err } } if cmd.Flags().Changed("pull-secret") { - servinglib.UpdateImagePullSecrets(template, p.ImagePullSecrets) + servinglib.UpdateImagePullSecrets(template, p.PodSpecFlags.ImagePullSecrets) } if cmd.Flags().Changed("user") { - servinglib.UpdateUser(template, p.User) + servinglib.UpdateUser(template, p.PodSpecFlags.User) } return nil @@ -558,7 +521,7 @@ func (p *ConfigurationEditFlags) updateLabels(obj *metav1.ObjectMeta, flagLabels return nil } -func (p *ConfigurationEditFlags) computeResources(resourceFlags ResourceFlags) (corev1.ResourceList, error) { +func (p *ConfigurationEditFlags) computeResources(resourceFlags knflags.ResourceFlags) (corev1.ResourceList, error) { resourceList := corev1.ResourceList{} if resourceFlags.CPU != "" { @@ -584,7 +547,7 @@ func (p *ConfigurationEditFlags) computeResources(resourceFlags ResourceFlags) ( return resourceList, nil } -// AnyMutation returns true if there are any revision template mutations in the +// AnyMutation returns true if there are any revision template corev1 "k8s.io/api/core/v1" in the // command. func (p *ConfigurationEditFlags) AnyMutation(cmd *cobra.Command) bool { for _, flag := range p.flags { diff --git a/pkg/kn/commands/service/create.go b/pkg/kn/commands/service/create.go index a2095d8c13..5ba92e8994 100644 --- a/pkg/kn/commands/service/create.go +++ b/pkg/kn/commands/service/create.go @@ -88,7 +88,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command { if len(args) == 1 { name = args[0] } - if editFlags.Image == "" && editFlags.Filename == "" { + if editFlags.PodSpecFlags.Image == "" && editFlags.Filename == "" { return errors.New("'service create' requires the image name to run provided with the --image option") } diff --git a/pkg/kn/flags/podspec.go b/pkg/kn/flags/podspec.go new file mode 100644 index 0000000000..ce8fd65302 --- /dev/null +++ b/pkg/kn/flags/podspec.go @@ -0,0 +1,143 @@ +// 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 ( + "errors" + + "github.com/spf13/pflag" +) + +// PodSpecFlags to hold the container resource requirements values +type PodSpecFlags struct { + // Direct field manipulation + Image uniqueStringArg + Env []string + EnvFrom []string + Mount []string + Volume []string + + Command string + Arg []string + + RequestsFlags, LimitsFlags ResourceFlags // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0 + Resources ResourceOptions + Port string + ServiceAccountName string + ImagePullSecrets string + User int64 +} + +type ResourceFlags struct { + CPU string + Memory string +} + +// -- uniqueStringArg Value +// Custom implementation of flag.Value interface to prevent multiple value assignment. +// Useful to enforce unique use of flags, e.g. --image. +type uniqueStringArg string + +func (s *uniqueStringArg) Set(val string) error { + if len(*s) > 0 { + return errors.New("can be provided only once") + } + *s = uniqueStringArg(val) + return nil +} + +func (s *uniqueStringArg) Type() string { + return "string" +} + +func (s *uniqueStringArg) String() string { return string(*s) } + +//AddFlags will add Pod related flags to FlagSet +func (p *PodSpecFlags) AddFlags(flagset *pflag.FlagSet) { + flagset.VarP(&p.Image, "image", "", "Image to run.") + flagset.StringArrayVarP(&p.Env, "env", "e", []string{}, + "Environment variable to set. NAME=value; you may provide this flag "+ + "any number of times to set multiple environment variables. "+ + "To unset, specify the environment variable name followed by a \"-\" (e.g., NAME-).") + + flagset.StringArrayVarP(&p.EnvFrom, "env-from", "", []string{}, + "Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). "+ + "Example: --env-from cm:myconfigmap or --env-from secret:mysecret. "+ + "You can use this flag multiple times. "+ + "To unset a ConfigMap/Secret reference, append \"-\" to the name, e.g. --env-from cm:myconfigmap-.") + + flagset.StringArrayVarP(&p.Mount, "mount", "", []string{}, + "Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. "+ + "Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. "+ + "When a configmap or a secret is specified, a corresponding volume is automatically generated. "+ + "You can use this flag multiple times. "+ + "For unmounting a directory, append \"-\", e.g. --mount /mydir-, which also removes any auto-generated volume.") + + flagset.StringArrayVarP(&p.Volume, "volume", "", []string{}, + "Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). "+ + "Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. "+ + "You can use this flag multiple times. "+ + "To unset a ConfigMap/Secret reference, append \"-\" to the name, e.g. --volume myvolume-.") + + flagset.StringVarP(&p.Command, "cmd", "", "", + "Specify command to be used as entrypoint instead of default one. "+ + "Example: --cmd /app/start or --cmd /app/start --arg myArg to pass aditional arguments.") + + flagset.StringArrayVarP(&p.Arg, "arg", "", []string{}, + "Add argument to the container command. "+ + "Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. "+ + "You can use this flag multiple times.") + + flagset.StringSliceVar(&p.Resources.Limits, + "limit", + nil, + "The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. "+ + "You can use this flag multiple times. "+ + "To unset a resource limit, append \"-\" to the resource name, e.g. '--limit memory-'.") + + flagset.StringSliceVar(&p.Resources.Requests, + "request", + nil, + "The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. "+ + "You can use this flag multiple times. "+ + "To unset a resource request, append \"-\" to the resource name, e.g. '--request cpu-'.") + + flagset.StringVar(&p.RequestsFlags.CPU, "requests-cpu", "", + "DEPRECATED: please use --request instead. The requested CPU (e.g., 250m).") + + flagset.StringVar(&p.RequestsFlags.Memory, "requests-memory", "", + "DEPRECATED: please use --request instead. The requested memory (e.g., 64Mi).") + + // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0 + flagset.StringVar(&p.LimitsFlags.CPU, "limits-cpu", "", + "DEPRECATED: please use --limit instead. The limits on the requested CPU (e.g., 1000m).") + + // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0 + flagset.StringVar(&p.LimitsFlags.Memory, "limits-memory", "", + "DEPRECATED: please use --limit instead. The limits on the requested memory (e.g., 1024Mi).") + + flagset.StringVarP(&p.Port, "port", "p", "", "The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.") + + flagset.StringVar(&p.ServiceAccountName, + "service-account", + "", + "Service account name to set. An empty argument (\"\") clears the service account. The referenced service account must exist in the service's namespace.") + + flagset.StringVar(&p.ImagePullSecrets, + "pull-secret", + "", + "Image pull secret to set. An empty argument (\"\") clears the pull secret. The referenced secret must exist in the service's namespace.") + flagset.Int64VarP(&p.User, "user", "", 0, "The user ID to run the container (e.g., 1001).") +} diff --git a/pkg/kn/flags/podspec_test.go b/pkg/kn/flags/podspec_test.go new file mode 100644 index 0000000000..2248c3613d --- /dev/null +++ b/pkg/kn/flags/podspec_test.go @@ -0,0 +1,51 @@ +// 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 ( + "testing" + + "github.com/spf13/cobra" + "gotest.tools/assert" +) + +func TestPodSpecFlags(t *testing.T) { + args := []string{"--image", "repo/user/imageID:tag", "--env", "b=c"} + wantedPod := &PodSpecFlags{ + Image: "repo/user/imageID:tag", + Env: []string{"b=c"}, + EnvFrom: []string{}, + Mount: []string{}, + Volume: []string{}, + Arg: []string{}, + } + flags := &PodSpecFlags{} + testCmd := &cobra.Command{ + Use: "test", + Run: func(cmd *cobra.Command, args []string) { + assert.DeepEqual(t, wantedPod, flags) + }, + } + testCmd.SetArgs(args) + flags.AddFlags(testCmd.Flags()) + testCmd.Execute() +} + +func TestUniqueStringArg(t *testing.T) { + var a uniqueStringArg + a.Set("test") + assert.Equal(t, "test", a.String()) + assert.Equal(t, "string", a.Type()) +}