From c5edb27760446bfd235c4c6baf26b8201ff032bf Mon Sep 17 00:00:00 2001 From: lynnemorrison Date: Thu, 21 Nov 2024 15:34:30 -0500 Subject: [PATCH] Add generate option to all commands and remove output flag from commands. --- internal/cmd/skupper/common/flags.go | 37 +- internal/cmd/skupper/connector/connector.go | 42 +- .../cmd/skupper/connector/connector_test.go | 16 +- .../connector/kube/connector_create.go | 27 +- .../connector/kube/connector_create_test.go | 76 +-- .../connector/kube/connector_generate.go | 244 +++++++ .../connector/kube/connector_generate_test.go | 640 ++++++++++++++++++ .../connector/kube/connector_update.go | 28 +- .../connector/kube/connector_update_test.go | 103 --- .../connector/nonkube/connector_create.go | 20 +- .../nonkube/connector_create_test.go | 41 +- .../connector/nonkube/connector_generate.go | 167 +++++ .../nonkube/connector_generate_test.go | 297 ++++++++ .../connector/nonkube/connector_update.go | 26 +- .../nonkube/connector_update_test.go | 14 +- internal/cmd/skupper/link/kube/link_update.go | 32 +- .../cmd/skupper/link/kube/link_update_test.go | 62 +- internal/cmd/skupper/link/link.go | 1 - internal/cmd/skupper/link/link_test.go | 4 +- .../skupper/listener/kube/listener_create.go | 27 +- .../listener/kube/listener_create_test.go | 60 +- .../listener/kube/listener_generate.go | 159 +++++ .../listener/kube/listener_generate_test.go | 300 ++++++++ .../skupper/listener/kube/listener_update.go | 29 +- .../listener/kube/listener_update_test.go | 83 --- internal/cmd/skupper/listener/listener.go | 33 +- .../cmd/skupper/listener/listener_test.go | 16 +- .../listener/nonkube/listener_create.go | 20 +- .../listener/nonkube/listener_create_test.go | 30 +- .../listener/nonkube/listener_generate.go | 174 +++++ .../nonkube/listener_generate_test.go | 291 ++++++++ .../listener/nonkube/listener_update.go | 23 +- .../listener/nonkube/listener_update_test.go | 23 +- internal/cmd/skupper/site/kube/site_create.go | 30 +- .../cmd/skupper/site/kube/site_create_test.go | 73 +- .../cmd/skupper/site/kube/site_generate.go | 145 ++++ .../skupper/site/kube/site_generate_test.go | 257 +++++++ internal/cmd/skupper/site/kube/site_update.go | 32 +- .../cmd/skupper/site/kube/site_update_test.go | 68 +- .../cmd/skupper/site/nonkube/site_create.go | 42 +- .../skupper/site/nonkube/site_create_test.go | 51 +- .../cmd/skupper/site/nonkube/site_generate.go | 186 +++++ .../site/nonkube/site_generate_test.go | 302 +++++++++ .../cmd/skupper/site/nonkube/site_update.go | 45 +- .../skupper/site/nonkube/site_update_test.go | 9 - internal/cmd/skupper/site/site.go | 43 +- internal/cmd/skupper/site/site_test.go | 14 +- 47 files changed, 3451 insertions(+), 991 deletions(-) create mode 100644 internal/cmd/skupper/connector/kube/connector_generate.go create mode 100644 internal/cmd/skupper/connector/kube/connector_generate_test.go create mode 100644 internal/cmd/skupper/connector/nonkube/connector_generate.go create mode 100644 internal/cmd/skupper/connector/nonkube/connector_generate_test.go create mode 100644 internal/cmd/skupper/listener/kube/listener_generate.go create mode 100644 internal/cmd/skupper/listener/kube/listener_generate_test.go create mode 100644 internal/cmd/skupper/listener/nonkube/listener_generate.go create mode 100644 internal/cmd/skupper/listener/nonkube/listener_generate_test.go create mode 100644 internal/cmd/skupper/site/kube/site_generate.go create mode 100644 internal/cmd/skupper/site/kube/site_generate_test.go create mode 100644 internal/cmd/skupper/site/nonkube/site_generate.go create mode 100644 internal/cmd/skupper/site/nonkube/site_generate_test.go diff --git a/internal/cmd/skupper/common/flags.go b/internal/cmd/skupper/common/flags.go index f7520297f..bf7630480 100644 --- a/internal/cmd/skupper/common/flags.go +++ b/internal/cmd/skupper/common/flags.go @@ -92,8 +92,6 @@ type CommandSiteCreateFlags struct { EnableLinkAccess bool LinkAccessType string ServiceAccount string - Output string - Host string Timeout time.Duration BindHost string SubjectAlternativeNames []string @@ -104,8 +102,6 @@ type CommandSiteUpdateFlags struct { EnableLinkAccess bool LinkAccessType string ServiceAccount string - Output string - Host string Timeout time.Duration BindHost string SubjectAlternativeNames []string @@ -122,6 +118,15 @@ type CommandSiteStatusFlags struct { Output string } +type CommandSiteGenerateFlags struct { + EnableLinkAccess bool + LinkAccessType string + ServiceAccount string + BindHost string + SubjectAlternativeNames []string + Output string +} + type CommandLinkGenerateFlags struct { TlsCredentials string Cost string @@ -132,7 +137,6 @@ type CommandLinkGenerateFlags struct { type CommandLinkUpdateFlags struct { TlsCredentials string Cost string - Output string Timeout time.Duration Wait string } @@ -167,7 +171,6 @@ type CommandConnectorCreateFlags struct { IncludeNotReadyPods bool Workload string Timeout time.Duration - Output string Wait string } @@ -181,7 +184,6 @@ type CommandConnectorUpdateFlags struct { Selector string IncludeNotReadyPods bool Timeout time.Duration - Output string Wait string } @@ -194,13 +196,23 @@ type CommandConnectorStatusFlags struct { Output string } +type CommandConnectorGenerateFlags struct { + RoutingKey string + Host string + Selector string + TlsCredentials string + ConnectorType string + IncludeNotReadyPods bool + Workload string + Output string +} + type CommandListenerCreateFlags struct { RoutingKey string Host string TlsCredentials string ListenerType string Timeout time.Duration - Output string Wait string } @@ -211,7 +223,6 @@ type CommandListenerUpdateFlags struct { ListenerType string Timeout time.Duration Port int - Output string Wait string } @@ -230,6 +241,14 @@ type CommandSystemSetupFlags struct { Force bool } +type CommandListenerGenerateFlags struct { + RoutingKey string + Host string + TlsCredentials string + ListenerType string + Output string +} + type CommandVersionFlags struct { Output string } diff --git a/internal/cmd/skupper/connector/connector.go b/internal/cmd/skupper/connector/connector.go index a526b110c..086e40992 100644 --- a/internal/cmd/skupper/connector/connector.go +++ b/internal/cmd/skupper/connector/connector.go @@ -25,6 +25,7 @@ skupper connector status my-connector`, cmd.AddCommand(CmdConnectorStatusFactory(config.GetPlatform())) cmd.AddCommand(CmdConnectorUpdateFactory(config.GetPlatform())) cmd.AddCommand(CmdConnectorDeleteFactory(config.GetPlatform())) + cmd.AddCommand(CmdConnectorGenerateFactory(config.GetPlatform())) return cmd } @@ -53,7 +54,6 @@ skupper connector create backend 8080 --workload deployment/backend`, cmd.Flags().StringVarP(&cmdFlags.Selector, common.FlagNameSelector, "s", "", common.FlagDescSelector) cmd.Flags().StringVarP(&cmdFlags.Workload, common.FlagNameWorkload, "w", "", common.FlagDescWorkload) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "configured", common.FlagDescWait) kubeCommand.CobraCmd = cmd @@ -120,7 +120,6 @@ func CmdConnectorUpdateFactory(configuredPlatform types.Platform) *cobra.Command cmd.Flags().StringVarP(&cmdFlags.Selector, common.FlagNameSelector, "s", "", common.FlagDescSelector) cmd.Flags().StringVarP(&cmdFlags.Workload, common.FlagNameWorkload, "w", "", common.FlagDescWorkload) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().IntVar(&cmdFlags.Port, common.FlagNameConnectorPort, 0, common.FlagDescConnectorPort) cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "configured", common.FlagDescWait) @@ -164,3 +163,42 @@ func CmdConnectorDeleteFactory(configuredPlatform types.Platform) *cobra.Command return cmd } + +func CmdConnectorGenerateFactory(configuredPlatform types.Platform) *cobra.Command { + kubeCommand := kube.NewCmdConnectorGenerate() + nonKubeCommand := nonkube.NewCmdConnectorGenerate() + + cmdConnectorGenerateDesc := common.SkupperCmdDescription{ + Use: "generate ", + Short: "Generate a connector", + Long: "Clients at this site use the connector host and port to establish connections to the remote service.", + Example: `skupper connector Generate database 5432 +skupper connector Generate backend 8080 --workload deployment/backend`, + } + + cmd := common.ConfigureCobraCommand(configuredPlatform, cmdConnectorGenerateDesc, kubeCommand, nonKubeCommand) + + cmdFlags := common.CommandConnectorGenerateFlags{} + + cmd.Flags().StringVarP(&cmdFlags.RoutingKey, common.FlagNameRoutingKey, "r", "", common.FlagDescRoutingKey) + cmd.Flags().StringVar(&cmdFlags.Host, common.FlagNameHost, "", common.FlagDescHost) + cmd.Flags().StringVar(&cmdFlags.TlsCredentials, common.FlagNameTlsCredentials, "", common.FlagDescTlsCredentials) + cmd.Flags().StringVar(&cmdFlags.ConnectorType, common.FlagNameConnectorType, "tcp", common.FlagDescConnectorType) + cmd.Flags().BoolVarP(&cmdFlags.IncludeNotReadyPods, common.FlagNameIncludeNotReadyPods, "i", false, common.FlagDescIncludeNotRead) + cmd.Flags().StringVarP(&cmdFlags.Selector, common.FlagNameSelector, "s", "", common.FlagDescSelector) + cmd.Flags().StringVarP(&cmdFlags.Workload, common.FlagNameWorkload, "w", "", common.FlagDescWorkload) + cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "yaml", common.FlagDescOutput) + + kubeCommand.CobraCmd = cmd + kubeCommand.Flags = &cmdFlags + nonKubeCommand.CobraCmd = cmd + nonKubeCommand.Flags = &cmdFlags + + if configuredPlatform != types.PlatformKubernetes { + cmd.Flags().MarkHidden(common.FlagNameIncludeNotReadyPods) + cmd.Flags().MarkHidden(common.FlagNameSelector) + cmd.Flags().MarkHidden(common.FlagNameWorkload) + } + + return cmd +} diff --git a/internal/cmd/skupper/connector/connector_test.go b/internal/cmd/skupper/connector/connector_test.go index bd79a3429..b2c52f512 100644 --- a/internal/cmd/skupper/connector/connector_test.go +++ b/internal/cmd/skupper/connector/connector_test.go @@ -30,7 +30,6 @@ func TestCmdConnectorFactory(t *testing.T) { common.FlagNameIncludeNotReadyPods: "false", common.FlagNameSelector: "", common.FlagNameWorkload: "", - common.FlagNameOutput: "", common.FlagNameTimeout: "1m0s", common.FlagNameWait: "configured", }, @@ -46,7 +45,6 @@ func TestCmdConnectorFactory(t *testing.T) { common.FlagNameIncludeNotReadyPods: "false", common.FlagNameSelector: "", common.FlagNameWorkload: "", - common.FlagNameOutput: "", common.FlagNameTimeout: "1m0s", common.FlagNameConnectorPort: "0", common.FlagNameWait: "configured", @@ -68,6 +66,20 @@ func TestCmdConnectorFactory(t *testing.T) { }, command: CmdConnectorDeleteFactory(types.PlatformKubernetes), }, + { + name: "CmdConnectorGenerateFactory", + expectedFlagsWithDefaultValue: map[string]interface{}{ + common.FlagNameRoutingKey: "", + common.FlagNameHost: "", + common.FlagNameTlsCredentials: "", + common.FlagNameConnectorType: "tcp", + common.FlagNameIncludeNotReadyPods: "false", + common.FlagNameSelector: "", + common.FlagNameWorkload: "", + common.FlagNameOutput: "yaml", + }, + command: CmdConnectorGenerateFactory(types.PlatformKubernetes), + }, } for _, test := range testTable { diff --git a/internal/cmd/skupper/connector/kube/connector_create.go b/internal/cmd/skupper/connector/kube/connector_create.go index 728271543..09c2274ef 100644 --- a/internal/cmd/skupper/connector/kube/connector_create.go +++ b/internal/cmd/skupper/connector/kube/connector_create.go @@ -3,10 +3,11 @@ package kube import ( "context" "fmt" - "k8s.io/apimachinery/pkg/api/meta" "strconv" "time" + "k8s.io/apimachinery/pkg/api/meta" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" @@ -28,7 +29,6 @@ type CmdConnectorCreate struct { namespace string name string port int - output string host string selector string tlsCredentials string @@ -59,7 +59,6 @@ func (cmd *CmdConnectorCreate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) timeoutValidator := validator.NewTimeoutInSecondsValidator() workloadStringValidator := validator.NewWorkloadStringValidator(common.WorkloadTypes) selectorStringValidator := validator.NewSelectorStringValidator() @@ -193,19 +192,12 @@ func (cmd *CmdConnectorCreate) ValidateInput(args []string) []error { } } } - //TBD what is valid timeout if cmd.Flags != nil && cmd.Flags.Timeout.String() != "" { ok, err := timeoutValidator.Evaluate(cmd.Flags.Timeout) if !ok { validationErrors = append(validationErrors, fmt.Errorf("timeout is not valid: %s", err)) } } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } if cmd.Flags != nil && cmd.Flags.Wait != "" { ok, err := statusValidator.Evaluate(cmd.Flags.Wait) @@ -240,7 +232,6 @@ func (cmd *CmdConnectorCreate) InputToOptions() { cmd.timeout = cmd.Flags.Timeout cmd.tlsCredentials = cmd.Flags.TlsCredentials cmd.connectorType = cmd.Flags.ConnectorType - cmd.output = cmd.Flags.Output cmd.includeNotReadyPods = cmd.Flags.IncludeNotReadyPods cmd.status = cmd.Flags.Wait } @@ -267,21 +258,11 @@ func (cmd *CmdConnectorCreate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, resource) - fmt.Println(encodedOutput) - return err - } else { - _, err := cmd.client.Connectors(cmd.namespace).Create(context.TODO(), &resource, metav1.CreateOptions{}) - return err - } + _, err := cmd.client.Connectors(cmd.namespace).Create(context.TODO(), &resource, metav1.CreateOptions{}) + return err } func (cmd *CmdConnectorCreate) WaitUntil() error { - // the site resource was not created - if cmd.output != "" { - return nil - } if cmd.status == "none" { return nil diff --git a/internal/cmd/skupper/connector/kube/connector_create_test.go b/internal/cmd/skupper/connector/kube/connector_create_test.go index 0d60839c8..808ae500b 100644 --- a/internal/cmd/skupper/connector/kube/connector_create_test.go +++ b/internal/cmd/skupper/connector/kube/connector_create_test.go @@ -201,23 +201,11 @@ func TestCmdConnectorCreate_ValidateInput(t *testing.T) { }, expectedErrors: []string{"timeout is not valid: duration must not be less than 10s; got 0s"}, }, - { - name: "output is not valid", - args: []string{"bad-output", "1234"}, - flags: common.CommandConnectorCreateFlags{ - Host: "host", - Timeout: 10 * time.Second, - Output: "not-supported", - }, - expectedErrors: []string{ - "output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "selector/host", args: []string{"selector", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Selector: "app=test", Host: "test", }, @@ -230,7 +218,6 @@ func TestCmdConnectorCreate_ValidateInput(t *testing.T) { args: []string{"workload", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/test", Host: "test", }, @@ -293,7 +280,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-no-deployment", args: []string{"workload-deployment", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/backend", }, expectedErrors: []string{ @@ -303,7 +289,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-deployment-no-labels", args: []string{"workload-deployment-no-labels", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/backend", }, k8sObjects: []runtime.Object{ @@ -327,7 +312,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-deployment", args: []string{"workload-deployment", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/backend", }, k8sObjects: []runtime.Object{ @@ -356,7 +340,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-no-service", args: []string{"workload-no-service", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "service/backend", }, expectedErrors: []string{ @@ -367,7 +350,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-service-no-labels", args: []string{"workload-service-no-labels", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "service/backend", }, k8sObjects: []runtime.Object{ @@ -389,7 +371,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-service", args: []string{"workload-service", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "service/backend", }, k8sObjects: []runtime.Object{ @@ -416,7 +397,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-no-daemonset", args: []string{"workload-no-daemonset", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "daemonset/backend", }, expectedErrors: []string{ @@ -427,7 +407,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-daemonset-no-labels", args: []string{"workload-daemonset-no-labels", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "daemonset/backend", }, k8sObjects: []runtime.Object{ @@ -451,7 +430,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-daemonset", args: []string{"workload-daemonset", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "DaemonSet/backend", }, k8sObjects: []runtime.Object{ @@ -480,7 +458,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-no-statefulset", args: []string{"workload-no-statefulset", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "StatefulSet/backend", }, expectedErrors: []string{ @@ -491,7 +468,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-statefulset-no-labels", args: []string{"workload-statefulset-no-labels", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "statefulset/backend", }, k8sObjects: []runtime.Object{ @@ -515,7 +491,6 @@ func TestCmdConnectorCreate_ValidateWorkload(t *testing.T) { name: "workload-statefulset", args: []string{"workload-statefulset", "1234"}, flags: common.CommandConnectorCreateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "statefulset/backend", }, k8sObjects: []runtime.Object{ @@ -581,7 +556,6 @@ func TestCmdConnectorCreate_InputToOptions(t *testing.T) { expectedSelector string expectedRoutingKey string expectedConnectorType string - expectedOutput string expectedTimeout time.Duration expectedStatus string } @@ -589,37 +563,34 @@ func TestCmdConnectorCreate_InputToOptions(t *testing.T) { testTable := []test{ { name: "test1", - flags: common.CommandConnectorCreateFlags{"backend", "", "app=backend", "secret", "tcp", true, "", 20 * time.Second, "json", "ready"}, + flags: common.CommandConnectorCreateFlags{"backend", "", "app=backend", "secret", "tcp", true, "", 20 * time.Second, "ready"}, expectedTlsCredentials: "secret", expectedHost: "", expectedRoutingKey: "backend", expectedTimeout: 20 * time.Second, expectedConnectorType: "tcp", - expectedOutput: "json", expectedSelector: "app=backend", expectedStatus: "ready", }, { name: "test2", - flags: common.CommandConnectorCreateFlags{"backend", "backend", "", "secret", "tcp", true, "", 20 * time.Second, "json", "configured"}, + flags: common.CommandConnectorCreateFlags{"backend", "backend", "", "secret", "tcp", true, "", 20 * time.Second, "configured"}, expectedTlsCredentials: "secret", expectedHost: "backend", expectedRoutingKey: "backend", expectedTimeout: 20 * time.Second, expectedConnectorType: "tcp", - expectedOutput: "json", expectedSelector: "", expectedStatus: "configured", }, { name: "test3", - flags: common.CommandConnectorCreateFlags{"", "", "", "secret", "tcp", false, "", 30 * time.Second, "yaml", "none"}, + flags: common.CommandConnectorCreateFlags{"", "", "", "secret", "tcp", false, "", 30 * time.Second, "none"}, expectedTlsCredentials: "secret", expectedHost: "", expectedRoutingKey: "test3", expectedTimeout: 30 * time.Second, expectedConnectorType: "tcp", - expectedOutput: "yaml", expectedSelector: "app=test3", expectedStatus: "none", }, @@ -637,7 +608,6 @@ func TestCmdConnectorCreate_InputToOptions(t *testing.T) { cmd.InputToOptions() assert.Check(t, cmd.routingKey == test.expectedRoutingKey) - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) assert.Check(t, cmd.host == test.expectedHost) assert.Check(t, cmd.timeout == test.expectedTimeout) @@ -674,20 +644,6 @@ func TestCmdConnectorCreate_Run(t *testing.T) { Timeout: 10 * time.Second, }, }, - { - name: "run output json", - connectorName: "my-connector-json", - connectorPort: 8080, - flags: common.CommandConnectorCreateFlags{ - ConnectorType: "tcp", - Host: "hostname", - RoutingKey: "keyname", - TlsCredentials: "secretname", - IncludeNotReadyPods: true, - Timeout: 10 * time.Second, - Output: "json", - }, - }, } for _, test := range testTable { @@ -699,7 +655,6 @@ func TestCmdConnectorCreate_Run(t *testing.T) { cmd.Flags = &common.CommandConnectorCreateFlags{} cmd.name = test.connectorName cmd.port = test.connectorPort - cmd.output = test.flags.Output cmd.namespace = "test" err := cmd.Run() @@ -715,7 +670,6 @@ func TestCmdConnectorCreate_Run(t *testing.T) { func TestCmdConnectorCreate_WaitUntil(t *testing.T) { type test struct { name string - output string status string k8sObjects []runtime.Object skupperObjects []runtime.Object @@ -764,29 +718,6 @@ func TestCmdConnectorCreate_WaitUntil(t *testing.T) { }, expectError: false, }, - { - name: "connector is ready yaml output", - output: "yaml", - skupperObjects: []runtime.Object{ - &v2alpha1.Connector{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-connector", - Namespace: "test", - }, - Status: v2alpha1.ConnectorStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - expectError: false, - }, { name: "connector is not ready yet, but user waits for configured", status: "configured", @@ -875,7 +806,6 @@ func TestCmdConnectorCreate_WaitUntil(t *testing.T) { assert.Assert(t, err) cmd.name = "my-connector" - cmd.output = test.output cmd.timeout = 1 * time.Second cmd.namespace = "test" cmd.status = test.status diff --git a/internal/cmd/skupper/connector/kube/connector_generate.go b/internal/cmd/skupper/connector/kube/connector_generate.go new file mode 100644 index 000000000..d48195bc5 --- /dev/null +++ b/internal/cmd/skupper/connector/kube/connector_generate.go @@ -0,0 +1,244 @@ +package kube + +import ( + "context" + "fmt" + "strconv" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + + "github.com/skupperproject/skupper/internal/kube/client" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + skupperv2alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v2alpha1" + pkgUtils "github.com/skupperproject/skupper/pkg/utils" + "github.com/skupperproject/skupper/pkg/utils/validator" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +type CmdConnectorGenerate struct { + client skupperv2alpha1.SkupperV2alpha1Interface + CobraCmd *cobra.Command + Flags *common.CommandConnectorGenerateFlags + namespace string + name string + port int + output string + host string + selector string + tlsCredentials string + routingKey string + connectorType string + includeNotReadyPods bool + KubeClient kubernetes.Interface +} + +func NewCmdConnectorGenerate() *CmdConnectorGenerate { + + return &CmdConnectorGenerate{} +} + +func (cmd *CmdConnectorGenerate) NewClient(cobraCommand *cobra.Command, args []string) { + cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String()) + utils.HandleError(err) + + cmd.client = cli.GetSkupperClient().SkupperV2alpha1() + cmd.namespace = cli.Namespace + cmd.KubeClient = cli.Kube +} + +func (cmd *CmdConnectorGenerate) ValidateInput(args []string) []error { + var validationErrors []error + resourceStringValidator := validator.NewResourceStringValidator() + numberValidator := validator.NewNumberValidator() + connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes) + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) + workloadStringValidator := validator.NewWorkloadStringValidator(common.WorkloadTypes) + selectorStringValidator := validator.NewSelectorStringValidator() + + // Validate arguments name and port + if len(args) < 2 { + validationErrors = append(validationErrors, fmt.Errorf("connector name and port must be configured")) + } else if len(args) > 2 { + validationErrors = append(validationErrors, fmt.Errorf("only two arguments are allowed for this command")) + } else if args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("connector name must not be empty")) + } else if args[1] == "" { + validationErrors = append(validationErrors, fmt.Errorf("connector port must not be empty")) + } else { + ok, err := resourceStringValidator.Evaluate(args[0]) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("connector name is not valid: %s", err)) + } else { + cmd.name = args[0] + } + + cmd.port, err = strconv.Atoi(args[1]) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("connector port is not valid: %s", err)) + } + ok, err = numberValidator.Evaluate(cmd.port) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("connector port is not valid: %s", err)) + } + } + + // Validate flags + if cmd.Flags != nil && cmd.Flags.RoutingKey != "" { + ok, err := resourceStringValidator.Evaluate(cmd.Flags.RoutingKey) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("routing key is not valid: %s", err)) + } + } + if cmd.Flags != nil && cmd.Flags.TlsCredentials != "" { + // check that the secret exists + _, err := cmd.KubeClient.CoreV1().Secrets(cmd.namespace).Get(context.TODO(), cmd.Flags.TlsCredentials, metav1.GetOptions{}) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("tlsCredentials is not valid: does not exist")) + } + } + if cmd.Flags != nil && cmd.Flags.ConnectorType != "" { + ok, err := connectorTypeValidator.Evaluate(cmd.Flags.ConnectorType) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("connector type is not valid: %s", err)) + } + } + // only one of workload, selector or host can be specified + if cmd.Flags != nil && cmd.Flags.Host != "" { + if cmd.Flags.Workload != "" || cmd.Flags.Selector != "" { + validationErrors = append(validationErrors, fmt.Errorf("If host is configured, cannot configure workload or selector")) + } + //TBD what characters are not allowed for host flag + } + if cmd.Flags != nil && cmd.Flags.Selector != "" { + if cmd.Flags.Workload != "" || cmd.Flags.Host != "" { + validationErrors = append(validationErrors, fmt.Errorf("If selector is configured, cannot configure workload or host")) + } + ok, err := selectorStringValidator.Evaluate(cmd.Flags.Selector) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("selector is not valid: %s", err)) + } + cmd.selector = cmd.Flags.Selector + } + if cmd.Flags != nil && cmd.Flags.Workload != "" { + if cmd.Flags.Selector != "" || cmd.Flags.Host != "" { + validationErrors = append(validationErrors, fmt.Errorf("If workload is configured, cannot configure selector or host")) + } + //workload get resource-type/resource-name and find selector labels + resourceType, resourceName, ok, err := workloadStringValidator.Evaluate(cmd.Flags.Workload) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("workload is not valid: %s", err)) + } else { + switch resourceType { + case "deployment": + deployment, err := cmd.KubeClient.AppsV1().Deployments(cmd.namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("failed trying to get Deployment specified by workload: %s", err)) + } else { + if deployment.Spec.Selector.MatchLabels != nil { + cmd.selector = pkgUtils.StringifySelector(deployment.Spec.Selector.MatchLabels) + } else { + validationErrors = append(validationErrors, fmt.Errorf("workload, no selector Matchlabels found")) + } + } + case "service": + service, err := cmd.KubeClient.CoreV1().Services(cmd.namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("failed trying to get Service specified by workload: %s", err)) + } else { + if service.Spec.Selector != nil { + cmd.selector = pkgUtils.StringifySelector(service.Spec.Selector) + } else { + validationErrors = append(validationErrors, fmt.Errorf("workload, no selector labels found")) + } + } + case "daemonset": + daemonSet, err := cmd.KubeClient.AppsV1().DaemonSets(cmd.namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("failed trying to get DaemonSet specified by workload: %s", err)) + } else { + if daemonSet.Spec.Selector.MatchLabels != nil { + cmd.selector = pkgUtils.StringifySelector(daemonSet.Spec.Selector.MatchLabels) + } else { + validationErrors = append(validationErrors, fmt.Errorf("workload, no selector Matchlabels found")) + } + } + case "statefulset": + statefulSet, err := cmd.KubeClient.AppsV1().StatefulSets(cmd.namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("failed trying to get StatefulSet specified by workload: %s", err)) + } else { + if statefulSet.Spec.Selector.MatchLabels != nil { + cmd.selector = pkgUtils.StringifySelector(statefulSet.Spec.Selector.MatchLabels) + } else { + validationErrors = append(validationErrors, fmt.Errorf("workload, no selector Matchlabels found")) + } + } + } + } + } + if cmd.Flags != nil && cmd.Flags.Output != "" { + ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) + } + } + return validationErrors +} + +func (cmd *CmdConnectorGenerate) InputToOptions() { + + // workload, selector or host must be specified + if cmd.Flags.Workload == "" && cmd.Flags.Selector == "" && cmd.Flags.Host == "" { + // default selector to name of connector + cmd.selector = "app=" + cmd.name + } + if cmd.Flags.Host != "" { + cmd.host = cmd.Flags.Host + } + if cmd.Flags.Selector != "" { + cmd.selector = cmd.Flags.Selector + } + + // default routingkey to name of connector + if cmd.Flags.RoutingKey == "" { + cmd.routingKey = cmd.name + } else { + cmd.routingKey = cmd.Flags.RoutingKey + } + cmd.tlsCredentials = cmd.Flags.TlsCredentials + cmd.connectorType = cmd.Flags.ConnectorType + cmd.output = cmd.Flags.Output + cmd.includeNotReadyPods = cmd.Flags.IncludeNotReadyPods +} + +func (cmd *CmdConnectorGenerate) Run() error { + + resource := v2alpha1.Connector{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Connector", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.name, + Namespace: cmd.namespace, + }, + Spec: v2alpha1.ConnectorSpec{ + Host: cmd.host, + Port: cmd.port, + RoutingKey: cmd.routingKey, + TlsCredentials: cmd.tlsCredentials, + Type: cmd.connectorType, + IncludeNotReadyPods: cmd.includeNotReadyPods, + Selector: cmd.selector, + }, + } + + encodedOutput, err := utils.Encode(cmd.output, resource) + fmt.Println(encodedOutput) + return err +} + +func (cmd *CmdConnectorGenerate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/connector/kube/connector_generate_test.go b/internal/cmd/skupper/connector/kube/connector_generate_test.go new file mode 100644 index 000000000..dfa3b3c39 --- /dev/null +++ b/internal/cmd/skupper/connector/kube/connector_generate_test.go @@ -0,0 +1,640 @@ +package kube + +import ( + "testing" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + + fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake" + "gotest.tools/v3/assert" + appsv1 "k8s.io/api/apps/v1" + v12 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestCmdConnectorGenerate_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + flags common.CommandConnectorGenerateFlags + k8sObjects []runtime.Object + skupperObjects []runtime.Object + expectedErrors []string + } + + testTable := []test{ + { + name: "connector name and port are not specified", + args: []string{}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector name and port must be configured"}, + }, + { + name: "connector name empty", + args: []string{"", "8090"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector name must not be empty"}, + }, + { + name: "connector port empty", + args: []string{"my-name-port-empty", ""}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector port must not be empty"}, + }, + { + name: "connector port not positive", + args: []string{"my-port-positive", "-45"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector port is not valid: value is not positive"}, + }, + { + name: "connector name and port are not specified", + args: []string{}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector name and port must be configured"}, + }, + { + name: "connector port is not specified", + args: []string{"my-name"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector name and port must be configured"}, + }, + { + name: "more than two arguments are specified", + args: []string{"my", "connector", "8080"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"only two arguments are allowed for this command"}, + }, + { + name: "connector name is not valid.", + args: []string{"my new connector", "8080"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector name is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "port is not valid.", + args: []string{"my-connector-port", "abcd"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "backend", + }, + expectedErrors: []string{"connector port is not valid: strconv.Atoi: parsing \"abcd\": invalid syntax"}, + }, + { + name: "connector type is not valid", + args: []string{"my-connector-type", "8080"}, + flags: common.CommandConnectorGenerateFlags{ + ConnectorType: "not-valid", + Selector: "backend", + }, + expectedErrors: []string{ + "connector type is not valid: value not-valid not allowed. It should be one of this options: [tcp]"}, + }, + { + name: "routing key is not valid", + args: []string{"my-connector-rk", "8080"}, + flags: common.CommandConnectorGenerateFlags{ + RoutingKey: "not-valid$", + Selector: "backend", + }, + expectedErrors: []string{ + "routing key is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "tls-credentials does not exist", + args: []string{"my-connector-tls", "8080"}, + flags: common.CommandConnectorGenerateFlags{ + TlsCredentials: "not-valid", + Selector: "backend", + }, + expectedErrors: []string{"tlsCredentials is not valid: does not exist"}, + }, + { + name: "workload is not valid", + args: []string{"bad-workload", "1234"}, + flags: common.CommandConnectorGenerateFlags{ + Workload: "@345", + }, + expectedErrors: []string{ + "workload is not valid: workload must include /"}, + }, + { + name: "selector is not valid", + args: []string{"bad-selector", "1234"}, + flags: common.CommandConnectorGenerateFlags{ + Selector: "@#$%", + }, + expectedErrors: []string{ + "selector is not valid: value does not match this regular expression: ^[A-Za-z0-9=:./-]+$"}, + }, + { + name: "output is not valid", + args: []string{"bad-output", "1234"}, + flags: common.CommandConnectorGenerateFlags{ + Host: "host", + Output: "not-supported", + }, + expectedErrors: []string{ + "output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, + }, + { + name: "selector/host", + args: []string{"selector", "1234"}, + flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Selector: "app=test", + Host: "test", + }, + expectedErrors: []string{ + "If host is configured, cannot configure workload or selector", + "If selector is configured, cannot configure workload or host"}, + }, + { + name: "workload/host", + args: []string{"workload", "1234"}, + flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "deployment/test", + Host: "test", + }, + k8sObjects: []runtime.Object{ + &appsv1.Deployment{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: appsv1.DeploymentSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "backend", + }, + }, + }, + }, + }, + expectedErrors: []string{ + "If host is configured, cannot configure workload or selector", + "If workload is configured, cannot configure selector or host"}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + command, err := newCmdConnectorGenerateWithMocks("test", test.k8sObjects, test.skupperObjects, "") + assert.Assert(t, err) + + command.Flags = &test.flags + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + }) + } +} + +func TestCmdConnectorGenerate_ValidateWorkload(t *testing.T) { + type test struct { + name string + args []string + flags common.CommandConnectorGenerateFlags + k8sObjects []runtime.Object + skupperObjects []runtime.Object + expectedErrors []string + expectedSelector string + } + + testTable := []test{ + { + name: "workload-no-deployment", + args: []string{"workload-deployment", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "deployment/backend", + }, + expectedErrors: []string{ + "failed trying to get Deployment specified by workload: deployments.apps \"backend\" not found"}, + }, + { + name: "workload-deployment-no-labels", + args: []string{"workload-deployment-no-labels", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "deployment/backend", + }, + k8sObjects: []runtime.Object{ + &appsv1.Deployment{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: appsv1.DeploymentSpec{ + Selector: &v1.LabelSelector{}, + }, + }, + }, + expectedErrors: []string{"workload, no selector Matchlabels found"}, + }, + { + name: "workload-deployment", + args: []string{"workload-deployment", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "deployment/backend", + }, + k8sObjects: []runtime.Object{ + &appsv1.Deployment{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: appsv1.DeploymentSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "backend", + }, + }, + }, + }, + }, + expectedErrors: []string{}, + expectedSelector: "app=backend", + }, + { + name: "workload-no-service", + args: []string{"workload-no-service", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "service/backend", + }, + expectedErrors: []string{ + "failed trying to get Service specified by workload: services \"backend\" not found", + }, + }, + { + name: "workload-service-no-labels", + args: []string{"workload-service-no-labels", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "service/backend", + }, + k8sObjects: []runtime.Object{ + &v12.Service{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Service", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: v12.ServiceSpec{}, + }, + }, + expectedErrors: []string{"workload, no selector labels found"}, + }, + { + name: "workload-service", + args: []string{"workload-service", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "service/backend", + }, + k8sObjects: []runtime.Object{ + &v12.Service{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Service", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: v12.ServiceSpec{ + Selector: map[string]string{ + "app": "backend", + }, + }, + }, + }, + expectedErrors: []string{}, + expectedSelector: "app=backend", + }, + { + name: "workload-no-daemonset", + args: []string{"workload-no-daemonset", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "daemonset/backend", + }, + expectedErrors: []string{ + "failed trying to get DaemonSet specified by workload: daemonsets.apps \"backend\" not found", + }, + }, + { + name: "workload-daemonset-no-labels", + args: []string{"workload-daemonset-no-labels", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "daemonset/backend", + }, + k8sObjects: []runtime.Object{ + &appsv1.DaemonSet{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "daemonset", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: appsv1.DaemonSetSpec{ + Selector: &v1.LabelSelector{}, + }, + }, + }, + expectedErrors: []string{"workload, no selector Matchlabels found"}, + }, + { + name: "workload-daemonset", + args: []string{"workload-daemonset", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "DaemonSet/backend", + }, + k8sObjects: []runtime.Object{ + &appsv1.DaemonSet{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "DaemonSet", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: appsv1.DaemonSetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "backend", + }, + }, + }, + }, + }, + expectedErrors: []string{}, + expectedSelector: "app=backend", + }, + { + name: "workload-no-statefulset", + args: []string{"workload-no-statefulset", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "StatefulSet/backend", + }, + expectedErrors: []string{ + "failed trying to get StatefulSet specified by workload: statefulsets.apps \"backend\" not found", + }, + }, + { + name: "workload-statefulset-no-labels", + args: []string{"workload-statefulset-no-labels", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "statefulset/backend", + }, + k8sObjects: []runtime.Object{ + &appsv1.StatefulSet{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "statefulset", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: appsv1.StatefulSetSpec{ + Selector: &v1.LabelSelector{}, + }, + }, + }, + expectedErrors: []string{"workload, no selector Matchlabels found"}, + }, + { + name: "workload-statefulset", + args: []string{"workload-statefulset", "1234"}, flags: common.CommandConnectorGenerateFlags{ + Output: "json", + Workload: "statefulset/backend", + }, + k8sObjects: []runtime.Object{ + &appsv1.StatefulSet{ + TypeMeta: v1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "StatefulSet", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "backend", + Namespace: "test", + }, + Spec: appsv1.StatefulSetSpec{ + Selector: &v1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "backend", + }, + }, + }, + }, + }, + expectedErrors: []string{}, + expectedSelector: "app=backend", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + command, err := newCmdConnectorGenerateWithMocks("test", test.k8sObjects, test.skupperObjects, "") + assert.Assert(t, err) + + command.Flags = &test.flags + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + //validate selector is correct + assert.Check(t, command.selector == test.expectedSelector) + }) + } +} + +func TestCmdConnectorGenerate_InputToOptions(t *testing.T) { + + type test struct { + name string + flags common.CommandConnectorGenerateFlags + Connectorname string + expectedTlsCredentials string + expectedHost string + expectedSelector string + expectedRoutingKey string + expectedConnectorType string + expectedOutput string + } + + testTable := []test{ + { + name: "test1", + flags: common.CommandConnectorGenerateFlags{"backend", "", "app=backend", "secret", "tcp", true, "", "json"}, + expectedTlsCredentials: "secret", + expectedHost: "", + expectedRoutingKey: "backend", + expectedConnectorType: "tcp", + expectedOutput: "json", + expectedSelector: "app=backend", + }, + { + name: "test2", + flags: common.CommandConnectorGenerateFlags{"backend", "backend", "", "secret", "tcp", true, "", "json"}, + expectedTlsCredentials: "secret", + expectedHost: "backend", + expectedRoutingKey: "backend", + expectedConnectorType: "tcp", + expectedOutput: "json", + expectedSelector: "", + }, + { + name: "test3", + flags: common.CommandConnectorGenerateFlags{"", "", "", "secret", "tcp", false, "", "yaml"}, + expectedTlsCredentials: "secret", + expectedHost: "", + expectedRoutingKey: "test3", + expectedConnectorType: "tcp", + expectedOutput: "yaml", + expectedSelector: "app=test3", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + cmd, err := newCmdConnectorGenerateWithMocks("test", nil, nil, "") + assert.Assert(t, err) + + cmd.Flags = &test.flags + cmd.name = test.name + + cmd.InputToOptions() + + assert.Check(t, cmd.routingKey == test.expectedRoutingKey) + assert.Check(t, cmd.output == test.expectedOutput) + assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) + assert.Check(t, cmd.host == test.expectedHost) + assert.Check(t, cmd.selector == test.expectedSelector) + assert.Check(t, cmd.connectorType == test.expectedConnectorType) + }) + } +} + +func TestCmdConnectorGenerate_Run(t *testing.T) { + type test struct { + name string + connectorName string + connectorPort int + flags common.CommandConnectorGenerateFlags + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperErrorMessage string + errorMessage string + } + + testTable := []test{ + { + name: "runs ok yaml", + connectorName: "my-connector-ok", + connectorPort: 8080, + flags: common.CommandConnectorGenerateFlags{ + ConnectorType: "tcp", + RoutingKey: "keyname", + TlsCredentials: "secretname", + IncludeNotReadyPods: true, + Selector: "app=backend", + Output: "yaml", + }, + }, + { + name: "run ok json", + connectorName: "my-connector-json", + connectorPort: 8080, + flags: common.CommandConnectorGenerateFlags{ + ConnectorType: "tcp", + Host: "hostname", + RoutingKey: "keyname", + TlsCredentials: "secretname", + IncludeNotReadyPods: true, + Output: "json", + }, + }, + } + + for _, test := range testTable { + cmd, err := newCmdConnectorGenerateWithMocks("test", test.k8sObjects, test.skupperObjects, test.skupperErrorMessage) + assert.Assert(t, err) + + t.Run(test.name, func(t *testing.T) { + + cmd.Flags = &common.CommandConnectorGenerateFlags{} + cmd.name = test.connectorName + cmd.port = test.connectorPort + cmd.output = test.flags.Output + cmd.namespace = "test" + + err := cmd.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} + +// --- helper methods + +func newCmdConnectorGenerateWithMocks(namespace string, k8sObjects []runtime.Object, skupperObjects []runtime.Object, fakeSkupperError string) (*CmdConnectorGenerate, error) { + + // We make sure the interval is appropriate + utils.SetRetryProfile(utils.TestRetryProfile) + client, err := fakeclient.NewFakeClient(namespace, k8sObjects, skupperObjects, fakeSkupperError) + if err != nil { + return nil, err + } + cmdConnectorGenerate := &CmdConnectorGenerate{ + client: client.GetSkupperClient().SkupperV2alpha1(), + KubeClient: client.GetKubeClient(), + namespace: namespace, + } + return cmdConnectorGenerate, nil +} diff --git a/internal/cmd/skupper/connector/kube/connector_update.go b/internal/cmd/skupper/connector/kube/connector_update.go index 14d14f60a..3bdbf7f0b 100644 --- a/internal/cmd/skupper/connector/kube/connector_update.go +++ b/internal/cmd/skupper/connector/kube/connector_update.go @@ -3,9 +3,10 @@ package kube import ( "context" "fmt" - "k8s.io/apimachinery/pkg/api/meta" "time" + "k8s.io/apimachinery/pkg/api/meta" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" @@ -30,7 +31,6 @@ type ConnectorUpdates struct { selector string includeNotReadyPods bool timeout time.Duration - output string } type CmdConnectorUpdate struct { @@ -67,7 +67,6 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) timeoutValidator := validator.NewTimeoutInSecondsValidator() workloadStringValidator := validator.NewWorkloadStringValidator(common.WorkloadTypes) selectorStringValidator := validator.NewSelectorStringValidator() @@ -230,14 +229,6 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error { cmd.newSettings.selector = cmd.newSettings.workload } } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } else { - cmd.newSettings.output = cmd.Flags.Output - } - } if cmd.Flags != nil && cmd.Flags.Wait != "" { ok, err := statusValidator.Evaluate(cmd.Flags.Wait) @@ -272,23 +263,12 @@ func (cmd *CmdConnectorUpdate) Run() error { }, } - if cmd.newSettings.output != "" { - encodedOutput, err := utils.Encode(cmd.newSettings.output, resource) - fmt.Println(encodedOutput) - return err - } else { - _, err := cmd.client.Connectors(cmd.namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) - return err - } + _, err := cmd.client.Connectors(cmd.namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) + return err } func (cmd *CmdConnectorUpdate) WaitUntil() error { - // the site resource was not created - if cmd.newSettings.output != "" { - return nil - } - if cmd.status == "none" { return nil } diff --git a/internal/cmd/skupper/connector/kube/connector_update_test.go b/internal/cmd/skupper/connector/kube/connector_update_test.go index 130df8722..737b11360 100644 --- a/internal/cmd/skupper/connector/kube/connector_update_test.go +++ b/internal/cmd/skupper/connector/kube/connector_update_test.go @@ -229,7 +229,6 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) { args: []string{"selector"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Selector: "app=test", Host: "test", }, @@ -260,7 +259,6 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) { args: []string{"workload"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/test", Host: "test", }, @@ -332,34 +330,6 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) { }, expectedErrors: []string{"timeout is not valid: duration must not be less than 10s; got 1s"}, }, - { - name: "output is not valid", - args: []string{"bad-output"}, - flags: common.CommandConnectorUpdateFlags{ - Output: "not-supported", - Timeout: 10 * time.Minute, - }, - skupperObjects: []runtime.Object{ - &v2alpha1.Connector{ - ObjectMeta: v1.ObjectMeta{ - Name: "bad-output", - Namespace: "test", - }, - Status: v2alpha1.ConnectorStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - expectedErrors: []string{ - "output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "flags all valid", args: []string{"my-connector-flags"}, @@ -370,7 +340,6 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) { ConnectorType: "tcp", IncludeNotReadyPods: false, Timeout: 50 * time.Second, - Output: "json", }, skupperObjects: []runtime.Object{ &v2alpha1.Connector{ @@ -463,7 +432,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-no-deployment", args: []string{"workload-deployment"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/backend", }, skupperObjects: []runtime.Object{ @@ -492,7 +460,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-deployment-no-labels", args: []string{"workload-deployment-no-labels"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/backend", }, skupperObjects: []runtime.Object{ @@ -534,7 +501,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-deployment", args: []string{"workload-deployment"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "deployment/backend", }, skupperObjects: []runtime.Object{ @@ -581,7 +547,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-no-service", args: []string{"workload-no-service"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "service/backend", }, skupperObjects: []runtime.Object{ @@ -610,7 +575,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-service-no-labels", args: []string{"workload-service-no-labels"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "service/backend", }, skupperObjects: []runtime.Object{ @@ -650,7 +614,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-service", args: []string{"workload-service"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "service/backend", }, skupperObjects: []runtime.Object{ @@ -695,7 +658,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-no-daemonset", args: []string{"workload-no-daemonset"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "daemonset/backend", }, skupperObjects: []runtime.Object{ @@ -724,7 +686,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-daemonset-no-labels", args: []string{"workload-daemonset-no-labels"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "daemonset/backend", }, skupperObjects: []runtime.Object{ @@ -766,7 +727,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-daemonset", args: []string{"workload-daemonset"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "DaemonSet/backend", }, skupperObjects: []runtime.Object{ @@ -813,7 +773,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-no-statefulset", args: []string{"workload-no-statefulset"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "StatefulSet/backend", }, skupperObjects: []runtime.Object{ @@ -842,7 +801,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-statefulset-no-labels", args: []string{"workload-statefulset-no-labels"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "statefulset/backend", }, skupperObjects: []runtime.Object{ @@ -884,7 +842,6 @@ func TestCmdConnectorUpdate_ValidateWorkload(t *testing.T) { name: "workload-statefulset", args: []string{"workload-statefulset"}, flags: common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: "json", Workload: "statefulset/backend", }, skupperObjects: []runtime.Object{ @@ -994,40 +951,6 @@ func TestCmdConnectorUpdate_Run(t *testing.T) { }, }, }, - { - name: "run output json", - connectorName: "my-connector-json", - flags: ConnectorUpdates{ - port: 8181, - connectorType: "tcp", - host: "hostname", - routingKey: "keyname", - tlsCredentials: "secretname", - includeNotReadyPods: true, - workload: "deployment/backend", - selector: "backend", - timeout: 10 * time.Second, - output: "json", - }, - skupperObjects: []runtime.Object{ - &v2alpha1.Connector{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-connector-json", - Namespace: "test", - }, - Status: v2alpha1.ConnectorStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - }, } for _, test := range testTable { @@ -1053,7 +976,6 @@ func TestCmdConnectorUpdate_Run(t *testing.T) { func TestCmdConnectorUpdate_WaitUntil(t *testing.T) { type test struct { name string - output string status string k8sObjects []runtime.Object skupperObjects []runtime.Object @@ -1102,29 +1024,6 @@ func TestCmdConnectorUpdate_WaitUntil(t *testing.T) { }, expectError: false, }, - { - name: "connector is ready json output", - output: "json", - skupperObjects: []runtime.Object{ - &v2alpha1.Connector{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-connector", - Namespace: "test", - }, - Status: v2alpha1.ConnectorStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - expectError: false, - }, { name: "connector is not ready yet, but user waits for configured", status: "configured", @@ -1215,10 +1114,8 @@ func TestCmdConnectorUpdate_WaitUntil(t *testing.T) { cmd.name = "my-connector" cmd.Flags = &common.CommandConnectorUpdateFlags{ Timeout: 10 * time.Second, - Output: test.output, } cmd.namespace = "test" - cmd.newSettings.output = cmd.Flags.Output cmd.status = test.status t.Run(test.name, func(t *testing.T) { diff --git a/internal/cmd/skupper/connector/nonkube/connector_create.go b/internal/cmd/skupper/connector/nonkube/connector_create.go index 2e3d2cabe..f05e18d4b 100644 --- a/internal/cmd/skupper/connector/nonkube/connector_create.go +++ b/internal/cmd/skupper/connector/nonkube/connector_create.go @@ -6,7 +6,6 @@ import ( "strconv" "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "github.com/skupperproject/skupper/pkg/utils/validator" @@ -21,7 +20,6 @@ type CmdConnectorCreate struct { namespace string connectorName string port int - output string host string routingKey string connectorType string @@ -54,7 +52,6 @@ func (cmd *CmdConnectorCreate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) hostStringValidator := validator.NewHostStringValidator() // Validate arguments name and port @@ -96,12 +93,6 @@ func (cmd *CmdConnectorCreate) ValidateInput(args []string) []error { validationErrors = append(validationErrors, fmt.Errorf("connector type is not valid: %s", err)) } } - if cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } if cmd.Flags.Host != "" { ip := net.ParseIP(cmd.Flags.Host) ok, _ := hostStringValidator.Evaluate(cmd.Flags.Host) @@ -135,7 +126,6 @@ func (cmd *CmdConnectorCreate) InputToOptions() { cmd.host = cmd.Flags.Host cmd.connectorType = cmd.Flags.ConnectorType - cmd.output = cmd.Flags.Output cmd.tlsCredentials = cmd.Flags.TlsCredentials } @@ -158,15 +148,9 @@ func (cmd *CmdConnectorCreate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, connectorResource) - fmt.Println(encodedOutput) + err := cmd.connectorHandler.Add(connectorResource) + if err != nil { return err - } else { - err := cmd.connectorHandler.Add(connectorResource) - if err != nil { - return err - } } return nil } diff --git a/internal/cmd/skupper/connector/nonkube/connector_create_test.go b/internal/cmd/skupper/connector/nonkube/connector_create_test.go index ed977eb08..e944767da 100644 --- a/internal/cmd/skupper/connector/nonkube/connector_create_test.go +++ b/internal/cmd/skupper/connector/nonkube/connector_create_test.go @@ -96,12 +96,6 @@ func TestNonKubeCmdConnectorCreate_ValidateInput(t *testing.T) { flags: &common.CommandConnectorCreateFlags{}, expectedErrors: []string{"host name must be configured: an IP address or hostname is expected"}, }, - { - name: "output format is not valid", - args: []string{"my-connector", "8080"}, - flags: &common.CommandConnectorCreateFlags{Output: "not-valid", Host: "1.2.3.4"}, - expectedErrors: []string{"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]"}, - }, { name: "kubernetes flags are not valid on this platform", args: []string{"my-connector", "8080"}, @@ -119,7 +113,6 @@ func TestNonKubeCmdConnectorCreate_ValidateInput(t *testing.T) { RoutingKey: "routingkeyname", TlsCredentials: "secretname", ConnectorType: "tcp", - Output: "json", Host: "1.2.3.4", }, expectedErrors: []string{}, @@ -164,40 +157,36 @@ func TestNonKubeCmdConnectorCreate_InputToOptions(t *testing.T) { expectedHost string expectedRoutingKey string expectedConnectorType string - expectedOutput string } testTable := []test{ { name: "test1", - flags: common.CommandConnectorCreateFlags{"backend", "", "", "secret", "tcp", false, "", 0, "json", "none"}, + flags: common.CommandConnectorCreateFlags{"backend", "", "", "secret", "tcp", false, "", 0, "none"}, expectedTlsCredentials: "secret", expectedHost: "", expectedRoutingKey: "backend", expectedConnectorType: "tcp", - expectedOutput: "json", expectedNamespace: "default", }, { name: "test2", namespace: "test", - flags: common.CommandConnectorCreateFlags{"backend", "1.2.3.4", "", "secret", "tcp", false, "", 0, "json", "configured"}, + flags: common.CommandConnectorCreateFlags{"backend", "1.2.3.4", "", "secret", "tcp", false, "", 0, "configured"}, expectedTlsCredentials: "secret", expectedHost: "1.2.3.4", expectedRoutingKey: "backend", expectedConnectorType: "tcp", - expectedOutput: "json", expectedNamespace: "test", }, { name: "test3", namespace: "test", - flags: common.CommandConnectorCreateFlags{"", "", "", "secret", "tcp", false, "", 0, "yaml", "ready"}, + flags: common.CommandConnectorCreateFlags{"", "", "", "secret", "tcp", false, "", 0, "ready"}, expectedTlsCredentials: "secret", expectedHost: "", expectedRoutingKey: "my-Connector", expectedConnectorType: "tcp", - expectedOutput: "yaml", expectedNamespace: "test", }, } @@ -216,7 +205,6 @@ func TestNonKubeCmdConnectorCreate_InputToOptions(t *testing.T) { assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) assert.Check(t, cmd.host == test.expectedHost) assert.Check(t, cmd.connectorType == test.expectedConnectorType) - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.namespace == test.expectedNamespace) }) } @@ -231,7 +219,6 @@ func TestNonKubeCmdConnectorCreate_Run(t *testing.T) { skupperError string connectorName string host string - output string errorMessage string routingKey string tlsCredentials string @@ -252,34 +239,12 @@ func TestNonKubeCmdConnectorCreate_Run(t *testing.T) { tlsCredentials: "secretname", host: "1.2.3.4", }, - { - name: "runs ok yaml", - k8sObjects: nil, - skupperObjects: nil, - connectorName: "test2", - connectorPort: 8080, - connectorType: "tcp", - host: "2.2.2.2", - output: "yaml", - }, - { - name: "runs fails because the output format is not supported", - namespace: "default", - k8sObjects: nil, - skupperObjects: nil, - connectorName: "test3", - connectorPort: 8080, - host: "3.3.3.3", - output: "unsupported", - errorMessage: "format unsupported not supported", - }, } for _, test := range testTable { command := &CmdConnectorCreate{} command.connectorName = test.connectorName - command.output = test.output command.port = test.connectorPort command.host = test.host command.connectorType = test.connectorType diff --git a/internal/cmd/skupper/connector/nonkube/connector_generate.go b/internal/cmd/skupper/connector/nonkube/connector_generate.go new file mode 100644 index 000000000..7e86b0457 --- /dev/null +++ b/internal/cmd/skupper/connector/nonkube/connector_generate.go @@ -0,0 +1,167 @@ +package nonkube + +import ( + "fmt" + "net" + "strconv" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + "github.com/skupperproject/skupper/pkg/utils/validator" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type CmdConnectorGenerate struct { + connectorHandler *fs.ConnectorHandler + CobraCmd *cobra.Command + Flags *common.CommandConnectorGenerateFlags + namespace string + connectorName string + port int + output string + host string + routingKey string + connectorType string + tlsCredentials string +} + +func NewCmdConnectorGenerate() *CmdConnectorGenerate { + return &CmdConnectorGenerate{} +} + +func (cmd *CmdConnectorGenerate) NewClient(cobraCommand *cobra.Command, args []string) { + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace) != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() != "" { + cmd.namespace = cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() + } + + cmd.connectorHandler = fs.NewConnectorHandler(cmd.namespace) +} + +func (cmd *CmdConnectorGenerate) ValidateInput(args []string) []error { + var validationErrors []error + + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameContext) != nil && cmd.CobraCmd.Flag(common.FlagNameContext).Value.String() != "" { + fmt.Println("Warning: --context flag is not supported on this platform") + } + + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameKubeconfig) != nil && cmd.CobraCmd.Flag(common.FlagNameKubeconfig).Value.String() != "" { + fmt.Println("Warning: --kubeconfig flag is not supported on this platform") + } + + resourceStringValidator := validator.NewResourceStringValidator() + numberValidator := validator.NewNumberValidator() + connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes) + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) + hostStringValidator := validator.NewHostStringValidator() + + // Validate arguments name and port + if len(args) < 2 { + validationErrors = append(validationErrors, fmt.Errorf("connector name and port must be configured")) + } else if len(args) > 2 { + validationErrors = append(validationErrors, fmt.Errorf("only two arguments are allowed for this command")) + } else if args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("connector name must not be empty")) + } else if args[1] == "" { + validationErrors = append(validationErrors, fmt.Errorf("connector port must not be empty")) + } else { + ok, err := resourceStringValidator.Evaluate(args[0]) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("connector name is not valid: %s", err)) + } else { + cmd.connectorName = args[0] + } + cmd.port, err = strconv.Atoi(args[1]) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("connector port is not valid: %s", err)) + } + ok, err = numberValidator.Evaluate(cmd.port) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("connector port is not valid: %s", err)) + } + } + + // Validate flags + if cmd.Flags.RoutingKey != "" { + ok, err := resourceStringValidator.Evaluate(cmd.Flags.RoutingKey) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("routing key is not valid: %s", err)) + } + } + if cmd.Flags.ConnectorType != "" { + ok, err := connectorTypeValidator.Evaluate(cmd.Flags.ConnectorType) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("connector type is not valid: %s", err)) + } + } + if cmd.Flags.Output != "" { + ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) + } + } + if cmd.Flags.Host != "" { + ip := net.ParseIP(cmd.Flags.Host) + ok, _ := hostStringValidator.Evaluate(cmd.Flags.Host) + if !ok && ip == nil { + validationErrors = append(validationErrors, fmt.Errorf("host is not valid: a valid IP address or hostname is expected")) + } + } else { + validationErrors = append(validationErrors, fmt.Errorf("host name must be configured: an IP address or hostname is expected")) + } + if cmd.Flags.TlsCredentials != "" { + ok, err := resourceStringValidator.Evaluate(cmd.Flags.TlsCredentials) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("tlsCredentials is not valid: %s", err)) + } + } + + return validationErrors +} + +func (cmd *CmdConnectorGenerate) InputToOptions() { + // default routingkey to name of connector + if cmd.Flags.RoutingKey == "" { + cmd.routingKey = cmd.connectorName + } else { + cmd.routingKey = cmd.Flags.RoutingKey + } + + if cmd.namespace == "" { + cmd.namespace = "default" + } + + cmd.host = cmd.Flags.Host + cmd.connectorType = cmd.Flags.ConnectorType + cmd.tlsCredentials = cmd.Flags.TlsCredentials + cmd.output = cmd.Flags.Output +} + +func (cmd *CmdConnectorGenerate) Run() error { + connectorResource := v2alpha1.Connector{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Connector", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.connectorName, + Namespace: cmd.namespace, + }, + Spec: v2alpha1.ConnectorSpec{ + Host: cmd.host, + Port: cmd.port, + RoutingKey: cmd.routingKey, + TlsCredentials: cmd.tlsCredentials, + Type: cmd.connectorType, + }, + } + + encodedOutput, err := utils.Encode(cmd.output, connectorResource) + fmt.Println(encodedOutput) + return err + +} + +func (cmd *CmdConnectorGenerate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/connector/nonkube/connector_generate_test.go b/internal/cmd/skupper/connector/nonkube/connector_generate_test.go new file mode 100644 index 000000000..ae853bb92 --- /dev/null +++ b/internal/cmd/skupper/connector/nonkube/connector_generate_test.go @@ -0,0 +1,297 @@ +package nonkube + +import ( + "testing" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/spf13/cobra" + + "gotest.tools/v3/assert" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestNonKubeCmdConnectorGenerate_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + flags *common.CommandConnectorGenerateFlags + cobraGenericFlags map[string]string + expectedErrors []string + } + + testTable := []test{ + { + name: "Connector name and port are not specified", + args: []string{}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"connector name and port must be configured"}, + }, + { + name: "Connector name is not valid", + args: []string{"my new Connector", "8080"}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"connector name is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "Connector name is empty", + args: []string{"", "1234"}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"connector name must not be empty"}, + }, + { + name: "connector port empty", + args: []string{"my-name-port-empty", ""}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"connector port must not be empty"}, + }, + { + name: "port is not valid", + args: []string{"my-connector-port", "abcd"}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"connector port is not valid: strconv.Atoi: parsing \"abcd\": invalid syntax"}, + }, + { + name: "port not positive", + args: []string{"my-port-positive", "-45"}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"connector port is not valid: value is not positive"}, + }, + { + name: "more than two arguments was specified", + args: []string{"my", "Connector", "test"}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"only two arguments are allowed for this command"}, + }, + { + name: "type is not valid", + args: []string{"my-connector", "8080"}, + flags: &common.CommandConnectorGenerateFlags{ConnectorType: "not-valid", Host: "1.2.3.4"}, + expectedErrors: []string{"connector type is not valid: value not-valid not allowed. It should be one of this options: [tcp]"}, + }, + { + name: "routing key is not valid", + args: []string{"my-connector-rk", "8080"}, + flags: &common.CommandConnectorGenerateFlags{RoutingKey: "not-valid$", Host: "1.2.3.4"}, + expectedErrors: []string{"routing key is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "host is not valid", + args: []string{"my-connector-host", "8080"}, + flags: &common.CommandConnectorGenerateFlags{Host: "not-valid$"}, + expectedErrors: []string{"host is not valid: a valid IP address or hostname is expected"}, + }, + { + name: "host is not configued", + args: []string{"my-connector-host", "8080"}, + flags: &common.CommandConnectorGenerateFlags{}, + expectedErrors: []string{"host name must be configured: an IP address or hostname is expected"}, + }, + { + name: "tlsCredentials is not valid", + args: []string{"my-connector-tls", "8080"}, + flags: &common.CommandConnectorGenerateFlags{TlsCredentials: "not-valid$", Host: "1.2.3.4"}, + expectedErrors: []string{"tlsCredentials is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "output format is not valid", + args: []string{"my-connector", "8080"}, + flags: &common.CommandConnectorGenerateFlags{Output: "not-valid", Host: "1.2.3.4"}, + expectedErrors: []string{"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]"}, + }, + { + name: "kubernetes flags are not valid on this platform", + args: []string{"my-connector", "8080"}, + flags: &common.CommandConnectorGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{}, + cobraGenericFlags: map[string]string{ + common.FlagNameContext: "test", + common.FlagNameKubeconfig: "test", + }, + }, + { + name: "flags all valid", + args: []string{"my-connector-flags", "8080"}, + flags: &common.CommandConnectorGenerateFlags{ + RoutingKey: "routingkeyname", + TlsCredentials: "secretname", + ConnectorType: "tcp", + Output: "json", + Host: "1.2.3.4", + }, + expectedErrors: []string{}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + command := &CmdConnectorGenerate{Flags: &common.CommandConnectorGenerateFlags{}} + command.CobraCmd = &cobra.Command{Use: "test"} + + if test.flags != nil { + command.Flags = test.flags + } + + if test.cobraGenericFlags != nil && len(test.cobraGenericFlags) > 0 { + for name, value := range test.cobraGenericFlags { + command.CobraCmd.Flags().String(name, value, "") + } + } + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + }) + } +} + +func TestNonKubeCmdConnectorGenerate_InputToOptions(t *testing.T) { + + type test struct { + name string + args []string + namespace string + flags common.CommandConnectorGenerateFlags + expectedNamespace string + Connectorname string + expectedTlsCredentials string + expectedHost string + expectedRoutingKey string + expectedConnectorType string + expectedOutput string + } + + testTable := []test{ + { + name: "test1", + flags: common.CommandConnectorGenerateFlags{"backend", "", "", "secret", "tcp", false, "", "json"}, + expectedTlsCredentials: "secret", + expectedHost: "", + expectedRoutingKey: "backend", + expectedConnectorType: "tcp", + expectedOutput: "json", + expectedNamespace: "default", + }, + { + name: "test2", + namespace: "test", + flags: common.CommandConnectorGenerateFlags{"backend", "1.2.3.4", "", "", "tcp", false, "", "yaml"}, + expectedHost: "1.2.3.4", + expectedRoutingKey: "backend", + expectedConnectorType: "tcp", + expectedOutput: "yaml", + expectedNamespace: "test", + }, + { + name: "test3", + namespace: "test", + flags: common.CommandConnectorGenerateFlags{"", "", "", "secret", "tcp", false, "", "yaml"}, + expectedTlsCredentials: "secret", + expectedHost: "", + expectedRoutingKey: "my-Connector", + expectedConnectorType: "tcp", + expectedOutput: "yaml", + expectedNamespace: "test", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + cmd := CmdConnectorGenerate{} + cmd.Flags = &test.flags + cmd.connectorName = "my-Connector" + cmd.namespace = test.namespace + cmd.connectorHandler = fs.NewConnectorHandler(cmd.namespace) + + cmd.InputToOptions() + + assert.Check(t, cmd.routingKey == test.expectedRoutingKey) + assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) + assert.Check(t, cmd.host == test.expectedHost) + assert.Check(t, cmd.connectorType == test.expectedConnectorType) + assert.Check(t, cmd.output == test.expectedOutput) + assert.Check(t, cmd.namespace == test.expectedNamespace) + }) + } +} + +func TestNonKubeCmdConnectorGenerate_Run(t *testing.T) { + type test struct { + name string + namespace string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperError string + connectorName string + host string + output string + errorMessage string + routingKey string + tlsCredentials string + connectorType string + connectorPort int + } + + testTable := []test{ + { + name: "runs ok", + namespace: "test", + k8sObjects: nil, + skupperObjects: nil, + connectorName: "test1", + connectorPort: 8080, + connectorType: "tcp", + routingKey: "keyname", + tlsCredentials: "secretname", + host: "1.2.3.4", + output: "json", + }, + { + name: "runs ok yaml", + k8sObjects: nil, + skupperObjects: nil, + connectorName: "test2", + connectorPort: 8080, + connectorType: "tcp", + host: "2.2.2.2", + output: "yaml", + }, + { + name: "runs fails because the output format is not supported", + namespace: "default", + k8sObjects: nil, + skupperObjects: nil, + connectorName: "test3", + connectorPort: 8080, + host: "3.3.3.3", + output: "unsupported", + errorMessage: "format unsupported not supported", + }, + } + + for _, test := range testTable { + command := &CmdConnectorGenerate{} + + command.connectorName = test.connectorName + command.output = test.output + command.port = test.connectorPort + command.host = test.host + command.connectorType = test.connectorType + command.namespace = test.namespace + t.Run(test.name, func(t *testing.T) { + + err := command.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error(), err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} diff --git a/internal/cmd/skupper/connector/nonkube/connector_update.go b/internal/cmd/skupper/connector/nonkube/connector_update.go index c291dc040..b0ea705b3 100644 --- a/internal/cmd/skupper/connector/nonkube/connector_update.go +++ b/internal/cmd/skupper/connector/nonkube/connector_update.go @@ -5,7 +5,6 @@ import ( "net" "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "github.com/skupperproject/skupper/pkg/utils/validator" @@ -27,7 +26,6 @@ type CmdConnectorUpdate struct { namespace string connectorName string newSettings ConnectorUpdates - output string } func NewCmdConnectorUpdate() *CmdConnectorUpdate { @@ -48,7 +46,6 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) hostStringValidator := validator.NewHostStringValidator() if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameContext) != nil && cmd.CobraCmd.Flag(common.FlagNameContext).Value.String() != "" { @@ -124,14 +121,6 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error { cmd.newSettings.host = cmd.Flags.Host } } - if cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } else { - cmd.output = cmd.Flags.Output - } - } if cmd.Flags.TlsCredentials != "" { ok, err := resourceStringValidator.Evaluate(cmd.Flags.TlsCredentials) if !ok { @@ -140,6 +129,7 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error { cmd.newSettings.tlsCredentials = cmd.Flags.TlsCredentials } } + return validationErrors } @@ -168,17 +158,9 @@ func (cmd *CmdConnectorUpdate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, connectorResource) - fmt.Println(encodedOutput) - return err - } else { - err := cmd.connectorHandler.Add(connectorResource) - if err != nil { - return err - } - } - return nil + err := cmd.connectorHandler.Add(connectorResource) + return err + } func (cmd *CmdConnectorUpdate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/connector/nonkube/connector_update_test.go b/internal/cmd/skupper/connector/nonkube/connector_update_test.go index 6d38bc578..93acb54df 100644 --- a/internal/cmd/skupper/connector/nonkube/connector_update_test.go +++ b/internal/cmd/skupper/connector/nonkube/connector_update_test.go @@ -91,12 +91,6 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) { flags: &common.CommandConnectorUpdateFlags{Port: -1}, expectedErrors: []string{"connector port is not valid: value is not positive"}, }, - { - name: "output is not valid", - args: []string{"my-connector"}, - flags: &common.CommandConnectorUpdateFlags{Output: "not-supported"}, - expectedErrors: []string{"output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "kubernetes flags are not valid on this platform", args: []string{"my-connector"}, @@ -115,7 +109,6 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) { TlsCredentials: "secretname", Port: 1234, ConnectorType: "tcp", - Output: "json", Host: "1.2.3.4", }, expectedErrors: []string{}, @@ -178,7 +171,6 @@ func TestCmdConnectorUpdate_Run(t *testing.T) { errorMessage string connectorName string host string - output string routingKey string tlsCredentials string connectorType string @@ -197,14 +189,13 @@ func TestCmdConnectorUpdate_Run(t *testing.T) { tlsCredentials: "secretname", }, { - name: "run output json", + name: "runs default namespace", connectorName: "my-connector", - port: 8181, + port: 8080, connectorType: "tcp", host: "hostname", routingKey: "keyname", tlsCredentials: "secretname", - output: "json", }, } @@ -212,7 +203,6 @@ func TestCmdConnectorUpdate_Run(t *testing.T) { command := &CmdConnectorUpdate{} command.connectorName = test.connectorName - command.output = test.output command.newSettings.port = test.port command.newSettings.host = test.host command.newSettings.routingKey = test.routingKey diff --git a/internal/cmd/skupper/link/kube/link_update.go b/internal/cmd/skupper/link/kube/link_update.go index 5da082f69..b35758f4f 100644 --- a/internal/cmd/skupper/link/kube/link_update.go +++ b/internal/cmd/skupper/link/kube/link_update.go @@ -6,6 +6,9 @@ package kube import ( "context" "fmt" + "strconv" + "time" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/kube/client" @@ -16,8 +19,6 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "strconv" - "time" ) type CmdLinkUpdate struct { @@ -29,7 +30,6 @@ type CmdLinkUpdate struct { Namespace string tlsCredentials string cost int - output string timeout time.Duration status string } @@ -52,7 +52,6 @@ func (cmd *CmdLinkUpdate) ValidateInput(args []string) []error { var validationErrors []error numberValidator := validator.NewNumberValidator() timeoutValidator := validator.NewTimeoutInSecondsValidator() - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) statusValidator := validator.NewOptionValidator(common.WaitStatusTypes) //Validate if there is already a site defined in the namespace @@ -89,13 +88,6 @@ func (cmd *CmdLinkUpdate) ValidateInput(args []string) []error { validationErrors = append(validationErrors, fmt.Errorf("link cost is not valid: %s", err)) } - if cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } - ok, err = timeoutValidator.Evaluate(cmd.Flags.Timeout) if !ok { validationErrors = append(validationErrors, fmt.Errorf("timeout is not valid: %s", err)) @@ -115,7 +107,6 @@ func (cmd *CmdLinkUpdate) InputToOptions() { cmd.cost, _ = strconv.Atoi(cmd.Flags.Cost) cmd.tlsCredentials = cmd.Flags.TlsCredentials - cmd.output = cmd.Flags.Output cmd.timeout = cmd.Flags.Timeout cmd.status = cmd.Flags.Wait @@ -156,26 +147,13 @@ func (cmd *CmdLinkUpdate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, resource) - fmt.Println(encodedOutput) - - return err - - } else { - _, err := cmd.Client.Links(cmd.Namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) - return err - } + _, err = cmd.Client.Links(cmd.Namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) + return err } func (cmd *CmdLinkUpdate) WaitUntil() error { - // the site resource was not created - if cmd.output != "" { - return nil - } - if cmd.status == "none" { return nil } diff --git a/internal/cmd/skupper/link/kube/link_update_test.go b/internal/cmd/skupper/link/kube/link_update_test.go index 9ff6fcb98..925384340 100644 --- a/internal/cmd/skupper/link/kube/link_update_test.go +++ b/internal/cmd/skupper/link/kube/link_update_test.go @@ -1,6 +1,9 @@ package kube import ( + "testing" + "time" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake" @@ -9,8 +12,6 @@ import ( v12 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "testing" - "time" ) func TestCmdLinkUpdate_ValidateInput(t *testing.T) { @@ -178,32 +179,6 @@ func TestCmdLinkUpdate_ValidateInput(t *testing.T) { "link cost is not valid: value is not positive", }, }, - { - name: "output format is not valid", - args: []string{"my-link"}, - flags: common.CommandLinkUpdateFlags{Cost: "1", Output: "not-valid", Timeout: time.Minute}, - skupperObjects: []runtime.Object{ - &v2alpha1.SiteList{ - Items: []v2alpha1.Site{ - { - ObjectMeta: v1.ObjectMeta{ - Name: "the-site", - Namespace: "test", - }, - }, - }, - }, - &v2alpha1.Link{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-link", - Namespace: "test", - }, - }, - }, - expectedErrors: []string{ - "output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]", - }, - }, { name: "tls secret not available", args: []string{"my-link"}, @@ -319,7 +294,6 @@ func TestCmdLinkUpdate_InputToOptions(t *testing.T) { flags common.CommandLinkUpdateFlags expectedTlsCredentials string expectedCost int - expectedOutput string expectedTimeout time.Duration expectedStatus string } @@ -328,10 +302,9 @@ func TestCmdLinkUpdate_InputToOptions(t *testing.T) { { name: "check options", args: []string{"my-link"}, - flags: common.CommandLinkUpdateFlags{TlsCredentials: "secret", Cost: "1", Output: "json", Timeout: time.Minute, Wait: "ready"}, + flags: common.CommandLinkUpdateFlags{TlsCredentials: "secret", Cost: "1", Timeout: time.Minute, Wait: "ready"}, expectedCost: 1, expectedTlsCredentials: "secret", - expectedOutput: "json", expectedTimeout: time.Minute, expectedStatus: "ready", }, @@ -346,7 +319,6 @@ func TestCmdLinkUpdate_InputToOptions(t *testing.T) { cmd.InputToOptions() - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) assert.Check(t, cmd.cost == test.expectedCost) assert.Check(t, cmd.timeout == test.expectedTimeout) @@ -362,7 +334,6 @@ func TestCmdLinkUpdate_Run(t *testing.T) { skupperObjects []runtime.Object linkName string Cost int - output string tlsCredentials string errorMessage string skupperErrorMessage string @@ -397,21 +368,6 @@ func TestCmdLinkUpdate_Run(t *testing.T) { { name: "runs ok without updating link", linkName: "my-link", - output: "yaml", - skupperObjects: []runtime.Object{ - &v2alpha1.Link{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-link", - Namespace: "test", - }, - }, - }, - }, - { - name: "runs fails because the output format is not supported", - linkName: "my-link", - output: "unsupported", - errorMessage: "format unsupported not supported", skupperObjects: []runtime.Object{ &v2alpha1.Link{ ObjectMeta: v1.ObjectMeta{ @@ -428,7 +384,6 @@ func TestCmdLinkUpdate_Run(t *testing.T) { assert.Assert(t, err) cmd.linkName = test.linkName - cmd.output = test.output cmd.tlsCredentials = test.tlsCredentials cmd.cost = test.Cost @@ -452,7 +407,6 @@ func TestCmdLinkUpdate_WaitUntil(t *testing.T) { skupperObjects []runtime.Object skupperErrorMessage string linkName string - output string timeout time.Duration expectError bool } @@ -480,13 +434,6 @@ func TestCmdLinkUpdate_WaitUntil(t *testing.T) { timeout: time.Second, expectError: true, }, - { - name: "there is no need to wait for a link, the user just wanted the output", - linkName: "my-link", - output: "json", - timeout: time.Second, - expectError: false, - }, { name: "link is ready", status: "ready", @@ -612,7 +559,6 @@ func TestCmdLinkUpdate_WaitUntil(t *testing.T) { cmd, err := newCmdLinkUpdateWithMocks("test", test.k8sObjects, test.skupperObjects, test.skupperErrorMessage) assert.Assert(t, err) cmd.linkName = test.linkName - cmd.output = test.output cmd.timeout = test.timeout cmd.status = test.status diff --git a/internal/cmd/skupper/link/link.go b/internal/cmd/skupper/link/link.go index 6661daf63..865163f50 100644 --- a/internal/cmd/skupper/link/link.go +++ b/internal/cmd/skupper/link/link.go @@ -71,7 +71,6 @@ func CmdLinkUpdateFactory(configuredPlatform types.Platform) *cobra.Command { cmd.Flags().StringVar(&cmdFlags.TlsCredentials, common.FlagNameTlsCredentials, "", common.FlagDescTlsCredentials) cmd.Flags().StringVar(&cmdFlags.Cost, common.FlagNameCost, "1", common.FlagDescCost) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "ready", common.FlagDescWait) kubeCommand.CobraCmd = cmd diff --git a/internal/cmd/skupper/link/link_test.go b/internal/cmd/skupper/link/link_test.go index b0889d6b1..03af366c1 100644 --- a/internal/cmd/skupper/link/link_test.go +++ b/internal/cmd/skupper/link/link_test.go @@ -2,12 +2,13 @@ package link import ( "fmt" + "testing" + "github.com/skupperproject/skupper/api/types" "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/spf13/cobra" "github.com/spf13/pflag" "gotest.tools/v3/assert" - "testing" ) func TestCmdLinkFactory(t *testing.T) { @@ -35,7 +36,6 @@ func TestCmdLinkFactory(t *testing.T) { expectedFlagsWithDefaultValue: map[string]interface{}{ common.FlagNameTlsCredentials: "", common.FlagNameCost: "1", - common.FlagNameOutput: "", common.FlagNameTimeout: "1m0s", common.FlagNameWait: "ready", }, diff --git a/internal/cmd/skupper/listener/kube/listener_create.go b/internal/cmd/skupper/listener/kube/listener_create.go index c847be934..8ee94d46f 100644 --- a/internal/cmd/skupper/listener/kube/listener_create.go +++ b/internal/cmd/skupper/listener/kube/listener_create.go @@ -3,10 +3,11 @@ package kube import ( "context" "fmt" - "k8s.io/apimachinery/pkg/api/meta" "strconv" "time" + "k8s.io/apimachinery/pkg/api/meta" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" @@ -32,7 +33,6 @@ type CmdListenerCreate struct { listenerType string routingKey string timeout time.Duration - output string KubeClient kubernetes.Interface status string } @@ -57,7 +57,6 @@ func (cmd *CmdListenerCreate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() listenerTypeValidator := validator.NewOptionValidator(common.ListenerTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) timeoutValidator := validator.NewTimeoutInSecondsValidator() statusValidator := validator.NewOptionValidator(common.WaitStatusTypes) @@ -126,13 +125,6 @@ func (cmd *CmdListenerCreate) ValidateInput(args []string) []error { } } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } - if cmd.Flags != nil && cmd.Flags.Wait != "" { ok, err := statusValidator.Evaluate(cmd.Flags.Wait) if !ok { @@ -158,7 +150,6 @@ func (cmd *CmdListenerCreate) InputToOptions() { cmd.timeout = cmd.Flags.Timeout cmd.tlsCredentials = cmd.Flags.TlsCredentials cmd.listenerType = cmd.Flags.ListenerType - cmd.output = cmd.Flags.Output cmd.status = cmd.Flags.Wait } @@ -182,21 +173,11 @@ func (cmd *CmdListenerCreate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, resource) - fmt.Println(encodedOutput) - return err - } else { - _, err := cmd.client.Listeners(cmd.namespace).Create(context.TODO(), &resource, metav1.CreateOptions{}) - return err - } + _, err := cmd.client.Listeners(cmd.namespace).Create(context.TODO(), &resource, metav1.CreateOptions{}) + return err } func (cmd *CmdListenerCreate) WaitUntil() error { - // the site resource was not created - if cmd.output != "" { - return nil - } if cmd.status == "none" { return nil diff --git a/internal/cmd/skupper/listener/kube/listener_create_test.go b/internal/cmd/skupper/listener/kube/listener_create_test.go index 38e2c4455..6dbc3bb30 100644 --- a/internal/cmd/skupper/listener/kube/listener_create_test.go +++ b/internal/cmd/skupper/listener/kube/listener_create_test.go @@ -144,16 +144,6 @@ func TestCmdListenerCreate_ValidateInput(t *testing.T) { flags: common.CommandListenerCreateFlags{Timeout: 0 * time.Second}, expectedErrors: []string{"timeout is not valid: duration must not be less than 10s; got 0s"}, }, - { - name: "output is not valid", - args: []string{"bad-output", "1234"}, - flags: common.CommandListenerCreateFlags{ - Timeout: 30 * time.Second, - Output: "not-supported", - }, - expectedErrors: []string{ - "output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "flags all valid", args: []string{"my-listener-flags", "8080"}, @@ -163,7 +153,6 @@ func TestCmdListenerCreate_ValidateInput(t *testing.T) { TlsCredentials: "secretname", ListenerType: "tcp", Timeout: 1 * time.Minute, - Output: "json", }, skupperObjects: []runtime.Object{ &v2alpha1.Listener{ @@ -231,7 +220,6 @@ func TestCmdListenerCreate_InputToOptions(t *testing.T) { expectedHost string expectedRoutingKey string expectedListenerType string - expectedOutput string expectedTimeout time.Duration expectedStatus string } @@ -239,25 +227,23 @@ func TestCmdListenerCreate_InputToOptions(t *testing.T) { testTable := []test{ { name: "test1", - flags: common.CommandListenerCreateFlags{"backend", "backend", "secret", "tcp", 20 * time.Second, "json", "configured"}, + flags: common.CommandListenerCreateFlags{"backend", "backend", "secret", "tcp", 20 * time.Second, "configured"}, expectedTlsCredentials: "secret", expectedHost: "backend", expectedRoutingKey: "backend", expectedTimeout: 20 * time.Second, expectedListenerType: "tcp", - expectedOutput: "json", expectedStatus: "configured", }, { name: "test2", - flags: common.CommandListenerCreateFlags{"", "", "secret", "tcp", 30 * time.Second, "yaml", "configured"}, + flags: common.CommandListenerCreateFlags{"", "", "secret", "tcp", 30 * time.Second, "ready"}, expectedTlsCredentials: "secret", expectedHost: "test2", expectedRoutingKey: "test2", expectedTimeout: 30 * time.Second, expectedListenerType: "tcp", - expectedOutput: "yaml", - expectedStatus: "configured", + expectedStatus: "ready", }, } @@ -273,11 +259,9 @@ func TestCmdListenerCreate_InputToOptions(t *testing.T) { cmd.InputToOptions() assert.Check(t, cmd.routingKey == test.expectedRoutingKey) - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) assert.Check(t, cmd.host == test.expectedHost) assert.Check(t, cmd.timeout == test.expectedTimeout) - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.listenerType == test.expectedListenerType) assert.Check(t, cmd.status == test.expectedStatus) }) @@ -308,18 +292,6 @@ func TestCmdListenerCreate_Run(t *testing.T) { TlsCredentials: "secretname", }, }, - { - name: "output yaml", - listenerName: "run-listener", - listenerPort: 8080, - flags: common.CommandListenerCreateFlags{ - ListenerType: "tcp", - Host: "hostname", - RoutingKey: "keyname", - TlsCredentials: "secretname", - Output: "yaml", - }, - }, } for _, test := range testTable { @@ -328,7 +300,6 @@ func TestCmdListenerCreate_Run(t *testing.T) { cmd.name = test.listenerName cmd.port = test.listenerPort cmd.Flags = &test.flags - cmd.output = cmd.Flags.Output cmd.namespace = "test" t.Run(test.name, func(t *testing.T) { @@ -345,7 +316,6 @@ func TestCmdListenerCreate_Run(t *testing.T) { func TestCmdListenerCreate_WaitUntil(t *testing.T) { type test struct { name string - output string status string k8sObjects []runtime.Object skupperObjects []runtime.Object @@ -397,29 +367,6 @@ func TestCmdListenerCreate_WaitUntil(t *testing.T) { }, expectError: false, }, - { - name: "listener is configured yaml output", - output: "yaml", - skupperObjects: []runtime.Object{ - &v2alpha1.Listener{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-listener", - Namespace: "test", - }, - Status: v2alpha1.ListenerStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - expectError: false, - }, { name: "user does not wait", status: "none", @@ -479,7 +426,6 @@ func TestCmdListenerCreate_WaitUntil(t *testing.T) { cmd.name = "my-listener" cmd.port = 8080 - cmd.output = test.output cmd.timeout = 1 * time.Second cmd.namespace = "test" cmd.status = test.status diff --git a/internal/cmd/skupper/listener/kube/listener_generate.go b/internal/cmd/skupper/listener/kube/listener_generate.go new file mode 100644 index 000000000..e286f7fad --- /dev/null +++ b/internal/cmd/skupper/listener/kube/listener_generate.go @@ -0,0 +1,159 @@ +package kube + +import ( + "context" + "fmt" + "strconv" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + + "github.com/skupperproject/skupper/internal/kube/client" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + skupperv2alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v2alpha1" + "github.com/skupperproject/skupper/pkg/utils/validator" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +type CmdListenerGenerate struct { + client skupperv2alpha1.SkupperV2alpha1Interface + CobraCmd *cobra.Command + Flags *common.CommandListenerGenerateFlags + namespace string + name string + port int + host string + tlsCredentials string + listenerType string + routingKey string + output string + KubeClient kubernetes.Interface +} + +func NewCmdListenerGenerate() *CmdListenerGenerate { + + return &CmdListenerGenerate{} + +} + +func (cmd *CmdListenerGenerate) NewClient(cobraCommand *cobra.Command, args []string) { + cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String()) + utils.HandleError(err) + + cmd.client = cli.GetSkupperClient().SkupperV2alpha1() + cmd.namespace = cli.Namespace + cmd.KubeClient = cli.Kube +} + +func (cmd *CmdListenerGenerate) ValidateInput(args []string) []error { + var validationErrors []error + resourceStringValidator := validator.NewResourceStringValidator() + numberValidator := validator.NewNumberValidator() + listenerTypeValidator := validator.NewOptionValidator(common.ListenerTypes) + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) + + // Validate arguments name and port + if len(args) < 2 { + validationErrors = append(validationErrors, fmt.Errorf("listener name and port must be configured")) + } else if len(args) > 2 { + validationErrors = append(validationErrors, fmt.Errorf("only two arguments are allowed for this command")) + } else if args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("listener name must not be empty")) + } else if args[1] == "" { + validationErrors = append(validationErrors, fmt.Errorf("listener port must not be empty")) + } else { + ok, err := resourceStringValidator.Evaluate(args[0]) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("listener name is not valid: %s", err)) + } else { + cmd.name = args[0] + } + + cmd.port, err = strconv.Atoi(args[1]) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("listener port is not valid: %s", err)) + } + ok, err = numberValidator.Evaluate(cmd.port) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("listener port is not valid: %s", err)) + } + } + + // Validate flags + if cmd.Flags != nil && cmd.Flags.RoutingKey != "" { + ok, err := resourceStringValidator.Evaluate(cmd.Flags.RoutingKey) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("routing key is not valid: %s", err)) + } + } + + if cmd.Flags != nil && cmd.Flags.TlsCredentials != "" { + // check that the secret exists + _, err := cmd.KubeClient.CoreV1().Secrets(cmd.namespace).Get(context.TODO(), cmd.Flags.TlsCredentials, metav1.GetOptions{}) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("tls-secret is not valid: does not exist")) + } + } + + if cmd.Flags != nil && cmd.Flags.ListenerType != "" { + ok, err := listenerTypeValidator.Evaluate(cmd.Flags.ListenerType) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("listener type is not valid: %s", err)) + } + } + + if cmd.Flags != nil && cmd.Flags.Output != "" { + ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) + } + } + return validationErrors +} + +func (cmd *CmdListenerGenerate) InputToOptions() { + // default host and routingkey to name of listener + if cmd.Flags.Host == "" { + cmd.host = cmd.name + } else { + cmd.host = cmd.Flags.Host + } + if cmd.Flags.RoutingKey == "" { + cmd.routingKey = cmd.name + } else { + cmd.routingKey = cmd.Flags.RoutingKey + } + + cmd.tlsCredentials = cmd.Flags.TlsCredentials + cmd.listenerType = cmd.Flags.ListenerType + cmd.output = cmd.Flags.Output +} + +func (cmd *CmdListenerGenerate) Run() error { + + resource := v2alpha1.Listener{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Listener", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.name, + Namespace: cmd.namespace, + }, + Spec: v2alpha1.ListenerSpec{ + Host: cmd.host, + Port: cmd.port, + RoutingKey: cmd.routingKey, + TlsCredentials: cmd.tlsCredentials, + Type: cmd.listenerType, + }, + } + + encodedOutput, err := utils.Encode(cmd.output, resource) + fmt.Println(encodedOutput) + return err +} + +func (cmd *CmdListenerGenerate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/listener/kube/listener_generate_test.go b/internal/cmd/skupper/listener/kube/listener_generate_test.go new file mode 100644 index 000000000..65e292670 --- /dev/null +++ b/internal/cmd/skupper/listener/kube/listener_generate_test.go @@ -0,0 +1,300 @@ +package kube + +import ( + "testing" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + + fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + "gotest.tools/v3/assert" + v12 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestCmdListenerGenerate_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + flags common.CommandListenerGenerateFlags + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperErrorMessage string + expectedErrors []string + } + + testTable := []test{ + { + name: "listener name and port are not specified", + args: []string{}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"listener name and port must be configured"}, + }, + { + name: "listener name empty", + args: []string{"", "8090"}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"listener name must not be empty"}, + }, + { + name: "listener port empty", + args: []string{"my-name-port-empty", ""}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"listener port must not be empty"}, + }, + { + name: "listener port not positive", + args: []string{"my-port-positive", "-45"}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"listener port is not valid: value is not positive"}, + }, + { + name: "listener name and port are not specified", + args: []string{}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"listener name and port must be configured"}, + }, + { + name: "listener port is not specified", + args: []string{"my-name"}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"listener name and port must be configured"}, + }, + { + name: "more than two arguments are specified", + args: []string{"my", "listener", "8080"}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{"only two arguments are allowed for this command"}, + }, + { + name: "listener name is not valid.", + args: []string{"my new listener", "8080"}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{ + "listener name is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "port is not valid.", + args: []string{"my-listener-port", "abcd"}, + flags: common.CommandListenerGenerateFlags{}, + expectedErrors: []string{ + "listener port is not valid: strconv.Atoi: parsing \"abcd\": invalid syntax"}, + }, + { + name: "listener type is not valid", + args: []string{"my-listener-type", "8080"}, + flags: common.CommandListenerGenerateFlags{ListenerType: "not-valid"}, + expectedErrors: []string{ + "listener type is not valid: value not-valid not allowed. It should be one of this options: [tcp]"}, + }, + { + name: "routing key is not valid", + args: []string{"my-listener-rk", "8080"}, + flags: common.CommandListenerGenerateFlags{RoutingKey: "not-valid$"}, + expectedErrors: []string{ + "routing key is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "tls-credentials does not exist", + args: []string{"my-listener-tls", "8080"}, + flags: common.CommandListenerGenerateFlags{TlsCredentials: "not-valid"}, + expectedErrors: []string{"tls-secret is not valid: does not exist"}, + }, + { + name: "output is not valid", + args: []string{"bad-output", "1234"}, + flags: common.CommandListenerGenerateFlags{Output: "not-supported"}, + expectedErrors: []string{ + "output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, + }, + { + name: "flags all valid", + args: []string{"my-listener-flags", "8080"}, + flags: common.CommandListenerGenerateFlags{ + Host: "hostname", + RoutingKey: "routingkeyname", + TlsCredentials: "secretname", + ListenerType: "tcp", + Output: "json", + }, + skupperObjects: []runtime.Object{ + &v2alpha1.Listener{ + ObjectMeta: v1.ObjectMeta{ + Name: "my-listener", + Namespace: "test", + }, + Status: v2alpha1.ListenerStatus{ + Status: v2alpha1.Status{ + Conditions: []v1.Condition{ + { + Type: "Configured", + Status: "True", + }, + }, + }, + }, + }, + }, + k8sObjects: []runtime.Object{ + &v12.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: "secretname", + Namespace: "test", + }, + }, + }, + expectedErrors: []string{}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + command, err := newCmdListenerGenerateWithMocks("test", test.k8sObjects, test.skupperObjects, "") + assert.Assert(t, err) + + command.Flags = &test.flags + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + }) + } +} + +func TestCmdListenerGenerate_InputToOptions(t *testing.T) { + + type test struct { + name string + flags common.CommandListenerGenerateFlags + Listenername string + expectedTlsCredentials string + expectedHost string + expectedRoutingKey string + expectedListenerType string + expectedOutput string + } + + testTable := []test{ + { + name: "test1", + flags: common.CommandListenerGenerateFlags{"backend", "backend", "secret", "tcp", "json"}, + expectedTlsCredentials: "secret", + expectedHost: "backend", + expectedRoutingKey: "backend", + expectedListenerType: "tcp", + expectedOutput: "json", + }, + { + name: "test2", + flags: common.CommandListenerGenerateFlags{"", "", "secret", "tcp", "yaml"}, + expectedTlsCredentials: "secret", + expectedHost: "test2", + expectedRoutingKey: "test2", + expectedListenerType: "tcp", + expectedOutput: "yaml", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + + cmd, err := newCmdListenerGenerateWithMocks("test", nil, nil, "") + assert.Assert(t, err) + + cmd.Flags = &test.flags + cmd.name = test.name + + cmd.InputToOptions() + + assert.Check(t, cmd.routingKey == test.expectedRoutingKey) + assert.Check(t, cmd.output == test.expectedOutput) + assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) + assert.Check(t, cmd.host == test.expectedHost) + assert.Check(t, cmd.output == test.expectedOutput) + assert.Check(t, cmd.listenerType == test.expectedListenerType) + }) + } +} + +func TestCmdListenerGenerate_Run(t *testing.T) { + type test struct { + name string + listenerName string + listenerPort int + flags common.CommandListenerGenerateFlags + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperErrorMessage string + errorMessage string + } + + testTable := []test{ + { + name: "runs ok", + listenerName: "run-listener", + listenerPort: 8080, + flags: common.CommandListenerGenerateFlags{ + ListenerType: "tcp", + Host: "hostname", + RoutingKey: "keyname", + TlsCredentials: "secretname", + Output: "json", + }, + }, + { + name: "output yaml", + listenerName: "run-listener", + listenerPort: 8080, + flags: common.CommandListenerGenerateFlags{ + ListenerType: "tcp", + Host: "hostname", + RoutingKey: "keyname", + TlsCredentials: "secretname", + Output: "yaml", + }, + }, + } + + for _, test := range testTable { + cmd, err := newCmdListenerGenerateWithMocks("test", test.k8sObjects, test.skupperObjects, test.skupperErrorMessage) + assert.Assert(t, err) + cmd.name = test.listenerName + cmd.port = test.listenerPort + cmd.Flags = &test.flags + cmd.output = cmd.Flags.Output + cmd.namespace = "test" + + t.Run(test.name, func(t *testing.T) { + err := cmd.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} + +// --- helper methods + +func newCmdListenerGenerateWithMocks(namespace string, k8sObjects []runtime.Object, skupperObjects []runtime.Object, fakeSkupperError string) (*CmdListenerGenerate, error) { + + // We make sure the interval is appropriate + utils.SetRetryProfile(utils.TestRetryProfile) + client, err := fakeclient.NewFakeClient(namespace, k8sObjects, skupperObjects, fakeSkupperError) + if err != nil { + return nil, err + } + cmdListenerGenerate := &CmdListenerGenerate{ + client: client.GetSkupperClient().SkupperV2alpha1(), + KubeClient: client.GetKubeClient(), + namespace: namespace, + } + return cmdListenerGenerate, nil +} diff --git a/internal/cmd/skupper/listener/kube/listener_update.go b/internal/cmd/skupper/listener/kube/listener_update.go index d66726448..927dd5217 100644 --- a/internal/cmd/skupper/listener/kube/listener_update.go +++ b/internal/cmd/skupper/listener/kube/listener_update.go @@ -3,9 +3,10 @@ package kube import ( "context" "fmt" - "k8s.io/apimachinery/pkg/api/meta" "time" + "k8s.io/apimachinery/pkg/api/meta" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" @@ -26,8 +27,8 @@ type ListenerUpdates struct { listenerType string port int timeout time.Duration - output string } + type CmdListenerUpdate struct { client skupperv2alpha1.SkupperV2alpha1Interface CobraCmd *cobra.Command @@ -59,7 +60,6 @@ func (cmd *CmdListenerUpdate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() listenerTypeValidator := validator.NewOptionValidator(common.ListenerTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) timeoutValidator := validator.NewTimeoutInSecondsValidator() statusValidator := validator.NewOptionValidator(common.WaitStatusTypes) @@ -137,14 +137,6 @@ func (cmd *CmdListenerUpdate) ValidateInput(args []string) []error { validationErrors = append(validationErrors, fmt.Errorf("timeout is not valid: %s", err)) } } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } else { - cmd.newSettings.output = cmd.Flags.Output - } - } if cmd.Flags != nil && cmd.Flags.Wait != "" { ok, err := statusValidator.Evaluate(cmd.Flags.Wait) @@ -176,23 +168,12 @@ func (cmd *CmdListenerUpdate) Run() error { }, } - if cmd.newSettings.output != "" { - encodedOutput, err := utils.Encode(cmd.newSettings.output, resource) - fmt.Println(encodedOutput) - return err - } else { - _, err := cmd.client.Listeners(cmd.namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) - return err - } + _, err := cmd.client.Listeners(cmd.namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) + return err } func (cmd *CmdListenerUpdate) WaitUntil() error { - // the site resource was not created - if cmd.newSettings.output != "" { - return nil - } - if cmd.status == "none" { return nil } diff --git a/internal/cmd/skupper/listener/kube/listener_update_test.go b/internal/cmd/skupper/listener/kube/listener_update_test.go index 998a31a3a..66faf44a3 100644 --- a/internal/cmd/skupper/listener/kube/listener_update_test.go +++ b/internal/cmd/skupper/listener/kube/listener_update_test.go @@ -189,34 +189,6 @@ func TestCmdListenerUpdate_ValidateInput(t *testing.T) { }, expectedErrors: []string{"timeout is not valid: duration must not be less than 10s; got 5s"}, }, - { - name: "output is not valid", - args: []string{"bad-output"}, - flags: common.CommandListenerUpdateFlags{ - Output: "not-supported", - Timeout: 10 * time.Second, - }, - skupperObjects: []runtime.Object{ - &v2alpha1.Listener{ - ObjectMeta: v1.ObjectMeta{ - Name: "bad-output", - Namespace: "test", - }, - Status: v2alpha1.ListenerStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - expectedErrors: []string{ - "output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "flags all valid", args: []string{"my-listener-flags"}, @@ -227,7 +199,6 @@ func TestCmdListenerUpdate_ValidateInput(t *testing.T) { Port: 1234, ListenerType: "tcp", Timeout: 10 * time.Second, - Output: "json", }, skupperObjects: []runtime.Object{ &v2alpha1.Listener{ @@ -355,7 +326,6 @@ func TestCmdListenerUpdate_Run(t *testing.T) { Host: "hostname", RoutingKey: "keyname", TlsCredentials: "secretname", - Output: "yaml", Timeout: 1 * time.Minute, }, skupperObjects: []runtime.Object{ @@ -377,32 +347,6 @@ func TestCmdListenerUpdate_Run(t *testing.T) { }, }, }, - { - name: "new output json", - listenerName: "run-listener", - flags: common.CommandListenerUpdateFlags{ - Timeout: 1 * time.Minute, - }, - newOutput: "json", - skupperObjects: []runtime.Object{ - &v2alpha1.Listener{ - ObjectMeta: v1.ObjectMeta{ - Name: "run-listener", - Namespace: "test", - }, - Status: v2alpha1.ListenerStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - }, { name: "run fails", listenerName: "run-listener", @@ -419,7 +363,6 @@ func TestCmdListenerUpdate_Run(t *testing.T) { cmd.name = test.listenerName cmd.Flags = &test.flags cmd.namespace = "test" - cmd.newSettings.output = test.newOutput t.Run(test.name, func(t *testing.T) { err := cmd.Run() @@ -435,7 +378,6 @@ func TestCmdListenerUpdate_Run(t *testing.T) { func TestCmdListenerUpdate_WaitUntil(t *testing.T) { type test struct { name string - output string status string k8sObjects []runtime.Object skupperObjects []runtime.Object @@ -491,29 +433,6 @@ func TestCmdListenerUpdate_WaitUntil(t *testing.T) { }, expectError: false, }, - { - name: "listener is ready json output", - output: "json", - skupperObjects: []runtime.Object{ - &v2alpha1.Listener{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-listener", - Namespace: "test", - }, - Status: v2alpha1.ListenerStatus{ - Status: v2alpha1.Status{ - Conditions: []v1.Condition{ - { - Type: "Configured", - Status: "True", - }, - }, - }, - }, - }, - }, - expectError: false, - }, { name: "listener is not ready yet, but user waits for configured", status: "configured", @@ -599,10 +518,8 @@ func TestCmdListenerUpdate_WaitUntil(t *testing.T) { cmd.name = "my-listener" cmd.Flags = &common.CommandListenerUpdateFlags{ Timeout: 1 * time.Second, - Output: test.output, } cmd.namespace = "test" - cmd.newSettings.output = cmd.Flags.Output cmd.status = test.status t.Run(test.name, func(t *testing.T) { diff --git a/internal/cmd/skupper/listener/listener.go b/internal/cmd/skupper/listener/listener.go index a2b150d4a..e97946b5c 100644 --- a/internal/cmd/skupper/listener/listener.go +++ b/internal/cmd/skupper/listener/listener.go @@ -25,6 +25,7 @@ skupper listener status my-listener`, cmd.AddCommand(CmdListenerStatusFactory(config.GetPlatform())) cmd.AddCommand(CmdListenerUpdateFactory(config.GetPlatform())) cmd.AddCommand(CmdListenerDeleteFactory(config.GetPlatform())) + cmd.AddCommand(CmdListenerGenerateFactory(config.GetPlatform())) return cmd } @@ -49,7 +50,6 @@ func CmdListenerCreateFactory(configuredPlatform types.Platform) *cobra.Command cmd.Flags().StringVar(&cmdFlags.TlsCredentials, common.FlagNameTlsCredentials, "", common.FlagDescTlsCredentials) cmd.Flags().StringVar(&cmdFlags.ListenerType, common.FlagNameListenerType, "tcp", common.FlagDescListenerType) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "configured", common.FlagDescWait) kubeCommand.CobraCmd = cmd @@ -87,7 +87,6 @@ func CmdListenerUpdateFactory(configuredPlatform types.Platform) *cobra.Command cmd.Flags().StringVar(&cmdFlags.ListenerType, common.FlagNameListenerType, "tcp", common.FlagDescListenerType) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout) cmd.Flags().IntVar(&cmdFlags.Port, common.FlagNameListenerPort, 0, common.FlagDescListenerPort) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "configured", common.FlagDescWait) kubeCommand.CobraCmd = cmd @@ -158,3 +157,33 @@ func CmdListenerDeleteFactory(configuredPlatform types.Platform) *cobra.Command return cmd } + +func CmdListenerGenerateFactory(configuredPlatform types.Platform) *cobra.Command { + kubeCommand := kube.NewCmdListenerGenerate() + nonKubeCommand := nonkube.NewCmdListenerGenerate() + + cmdListenerGenerateDesc := common.SkupperCmdDescription{ + Use: "generate ", + Short: "generate a listener resource and output it to a file", + Long: `Clients at this site use the listener host and port to establish connections to the remote service. + generate a listener to evaluate what will be created with listener create command`, + Example: "skupper listener generate database 5432", + } + + cmd := common.ConfigureCobraCommand(configuredPlatform, cmdListenerGenerateDesc, kubeCommand, nonKubeCommand) + + cmdFlags := common.CommandListenerGenerateFlags{} + + cmd.Flags().StringVarP(&cmdFlags.RoutingKey, common.FlagNameRoutingKey, "r", "", common.FlagDescRoutingKey) + cmd.Flags().StringVar(&cmdFlags.Host, common.FlagNameListenerHost, "", common.FlagDescListenerHost) + cmd.Flags().StringVarP(&cmdFlags.TlsCredentials, common.FlagNameTlsCredentials, "t", "", common.FlagDescTlsCredentials) + cmd.Flags().StringVar(&cmdFlags.ListenerType, common.FlagNameListenerType, "tcp", common.FlagDescListenerType) + cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "yaml", common.FlagDescOutput) + + kubeCommand.CobraCmd = cmd + kubeCommand.Flags = &cmdFlags + nonKubeCommand.CobraCmd = cmd + nonKubeCommand.Flags = &cmdFlags + + return cmd +} diff --git a/internal/cmd/skupper/listener/listener_test.go b/internal/cmd/skupper/listener/listener_test.go index 79a276c77..abe6c88c8 100644 --- a/internal/cmd/skupper/listener/listener_test.go +++ b/internal/cmd/skupper/listener/listener_test.go @@ -2,12 +2,13 @@ package listener import ( "fmt" + "testing" + "github.com/skupperproject/skupper/api/types" "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/spf13/cobra" "github.com/spf13/pflag" "gotest.tools/v3/assert" - "testing" ) func TestCmdListenerFactory(t *testing.T) { @@ -26,7 +27,6 @@ func TestCmdListenerFactory(t *testing.T) { common.FlagNameListenerHost: "", common.FlagNameTlsCredentials: "", common.FlagNameListenerType: "tcp", - common.FlagNameOutput: "", common.FlagNameTimeout: "1m0s", common.FlagNameWait: "configured", }, @@ -39,7 +39,6 @@ func TestCmdListenerFactory(t *testing.T) { common.FlagNameListenerHost: "", common.FlagNameTlsCredentials: "", common.FlagNameListenerType: "tcp", - common.FlagNameOutput: "", common.FlagNameTimeout: "1m0s", common.FlagNameListenerPort: "0", common.FlagNameWait: "configured", @@ -61,6 +60,17 @@ func TestCmdListenerFactory(t *testing.T) { }, command: CmdListenerDeleteFactory(types.PlatformKubernetes), }, + { + name: "CmdListenerGenerateFactory", + expectedFlagsWithDefaultValue: map[string]interface{}{ + common.FlagNameRoutingKey: "", + common.FlagNameListenerHost: "", + common.FlagNameTlsCredentials: "", + common.FlagNameListenerType: "tcp", + common.FlagNameOutput: "yaml", + }, + command: CmdListenerGenerateFactory(types.PlatformKubernetes), + }, } for _, test := range testTable { diff --git a/internal/cmd/skupper/listener/nonkube/listener_create.go b/internal/cmd/skupper/listener/nonkube/listener_create.go index 70eec0fc5..ab184905d 100644 --- a/internal/cmd/skupper/listener/nonkube/listener_create.go +++ b/internal/cmd/skupper/listener/nonkube/listener_create.go @@ -6,7 +6,6 @@ import ( "strconv" "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "github.com/skupperproject/skupper/pkg/utils/validator" @@ -25,7 +24,6 @@ type CmdListenerCreate struct { tlsCredentials string listenerType string routingKey string - output string } func NewCmdListenerCreate() *CmdListenerCreate { @@ -54,7 +52,6 @@ func (cmd *CmdListenerCreate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() listenerTypeValidator := validator.NewOptionValidator(common.ListenerTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) hostStringValidator := validator.NewHostStringValidator() // Validate arguments name and port @@ -114,12 +111,6 @@ func (cmd *CmdListenerCreate) ValidateInput(args []string) []error { } } - if cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } return validationErrors } @@ -143,7 +134,6 @@ func (cmd *CmdListenerCreate) InputToOptions() { cmd.tlsCredentials = cmd.Flags.TlsCredentials cmd.listenerType = cmd.Flags.ListenerType - cmd.output = cmd.Flags.Output } func (cmd *CmdListenerCreate) Run() error { @@ -165,15 +155,9 @@ func (cmd *CmdListenerCreate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, listenerResource) - fmt.Println(encodedOutput) + err := cmd.listenerHandler.Add(listenerResource) + if err != nil { return err - } else { - err := cmd.listenerHandler.Add(listenerResource) - if err != nil { - return err - } } return nil } diff --git a/internal/cmd/skupper/listener/nonkube/listener_create_test.go b/internal/cmd/skupper/listener/nonkube/listener_create_test.go index 3027cdf34..e5f9e9bf9 100644 --- a/internal/cmd/skupper/listener/nonkube/listener_create_test.go +++ b/internal/cmd/skupper/listener/nonkube/listener_create_test.go @@ -90,12 +90,6 @@ func TestNonKubeCmdListenerCreate_ValidateInput(t *testing.T) { flags: &common.CommandListenerCreateFlags{Host: "not-valid$"}, expectedErrors: []string{"host is not valid: a valid IP address or hostname is expected"}, }, - { - name: "output format is not valid", - args: []string{"my-listener", "8080"}, - flags: &common.CommandListenerCreateFlags{Output: "not-valid", Host: "1.2.3.4"}, - expectedErrors: []string{"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]"}, - }, { name: "kubernetes flags are not valid on this platform", args: []string{"my-listener", "8080"}, @@ -113,7 +107,6 @@ func TestNonKubeCmdListenerCreate_ValidateInput(t *testing.T) { RoutingKey: "routingkeyname", TlsCredentials: "secretname", ListenerType: "tcp", - Output: "json", Host: "1.2.3.4", }, expectedErrors: []string{}, @@ -158,40 +151,36 @@ func TestNonKubeCmdListenerCreate_InputToOptions(t *testing.T) { expectedHost string expectedRoutingKey string expectedListenerType string - expectedOutput string } testTable := []test{ { name: "test1", - flags: common.CommandListenerCreateFlags{"backend", "", "secret", "tcp", 0, "json", "none"}, + flags: common.CommandListenerCreateFlags{"backend", "", "secret", "tcp", 0, "none"}, expectedTlsCredentials: "secret", expectedHost: "0.0.0.0", expectedRoutingKey: "backend", expectedListenerType: "tcp", - expectedOutput: "json", expectedNamespace: "default", }, { name: "test2", namespace: "test", - flags: common.CommandListenerCreateFlags{"backend", "1.2.3.4", "secret", "tcp", 0, "json", "configured"}, + flags: common.CommandListenerCreateFlags{"backend", "1.2.3.4", "secret", "tcp", 0, "configured"}, expectedTlsCredentials: "secret", expectedHost: "1.2.3.4", expectedRoutingKey: "backend", expectedListenerType: "tcp", - expectedOutput: "json", expectedNamespace: "test", }, { name: "test3", namespace: "default", - flags: common.CommandListenerCreateFlags{"", "", "secret", "tcp", 0, "yaml", "ready"}, + flags: common.CommandListenerCreateFlags{"", "", "secret", "tcp", 0, "ready"}, expectedTlsCredentials: "secret", expectedHost: "0.0.0.0", expectedRoutingKey: "my-listener", expectedListenerType: "tcp", - expectedOutput: "yaml", expectedNamespace: "default", }, } @@ -210,7 +199,6 @@ func TestNonKubeCmdListenerCreate_InputToOptions(t *testing.T) { assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) assert.Check(t, cmd.host == test.expectedHost) assert.Check(t, cmd.listenerType == test.expectedListenerType) - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.namespace == test.expectedNamespace) }) } @@ -224,7 +212,6 @@ func TestNonKubeCmdListenerCreate_Run(t *testing.T) { skupperError string listenerName string host string - output string errorMessage string routingKey string tlsCredentials string @@ -234,7 +221,7 @@ func TestNonKubeCmdListenerCreate_Run(t *testing.T) { testTable := []test{ { - name: "runs ok", + name: "test1", k8sObjects: nil, skupperObjects: nil, listenerName: "test1", @@ -245,24 +232,20 @@ func TestNonKubeCmdListenerCreate_Run(t *testing.T) { host: "1.2.3.4", }, { - name: "runs ok yaml", + name: "test2", k8sObjects: nil, skupperObjects: nil, listenerName: "test2", listenerPort: 8080, listenerType: "tcp", host: "2.2.2.2", - output: "yaml", }, { - name: "runs fails because the output format is not supported", + name: "test3", k8sObjects: nil, skupperObjects: nil, listenerName: "test3", listenerPort: 8080, - host: "3.3.3.3", - output: "unsupported", - errorMessage: "format unsupported not supported", }, } @@ -270,7 +253,6 @@ func TestNonKubeCmdListenerCreate_Run(t *testing.T) { command := &CmdListenerCreate{} command.listenerName = test.listenerName - command.output = test.output command.port = test.listenerPort command.host = test.host command.listenerType = test.listenerType diff --git a/internal/cmd/skupper/listener/nonkube/listener_generate.go b/internal/cmd/skupper/listener/nonkube/listener_generate.go new file mode 100644 index 000000000..408e25d78 --- /dev/null +++ b/internal/cmd/skupper/listener/nonkube/listener_generate.go @@ -0,0 +1,174 @@ +package nonkube + +import ( + "fmt" + "net" + "strconv" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + "github.com/skupperproject/skupper/pkg/utils/validator" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type CmdListenerGenerate struct { + listenerHandler *fs.ListenerHandler + CobraCmd *cobra.Command + Flags *common.CommandListenerGenerateFlags + namespace string + listenerName string + port int + host string + tlsCredentials string + listenerType string + routingKey string + output string +} + +func NewCmdListenerGenerate() *CmdListenerGenerate { + return &CmdListenerGenerate{} +} + +func (cmd *CmdListenerGenerate) NewClient(cobraCommand *cobra.Command, args []string) { + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace) != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() != "" { + cmd.namespace = cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() + } + + cmd.listenerHandler = fs.NewListenerHandler(cmd.namespace) +} + +func (cmd *CmdListenerGenerate) ValidateInput(args []string) []error { + var validationErrors []error + + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameContext) != nil && cmd.CobraCmd.Flag(common.FlagNameContext).Value.String() != "" { + fmt.Println("Warning: --context flag is not supported on this platform") + } + + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameKubeconfig) != nil && cmd.CobraCmd.Flag(common.FlagNameKubeconfig).Value.String() != "" { + fmt.Println("Warning: --kubeconfig flag is not supported on this platform") + } + + resourceStringValidator := validator.NewResourceStringValidator() + numberValidator := validator.NewNumberValidator() + listenerTypeValidator := validator.NewOptionValidator(common.ListenerTypes) + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) + hostStringValidator := validator.NewHostStringValidator() + + // Validate arguments name and port + if len(args) < 2 { + validationErrors = append(validationErrors, fmt.Errorf("listener name and port must be configured")) + } else if len(args) > 2 { + validationErrors = append(validationErrors, fmt.Errorf("only two arguments are allowed for this command")) + } else if args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("listener name must not be empty")) + } else if args[1] == "" { + validationErrors = append(validationErrors, fmt.Errorf("listener port must not be empty")) + } else { + ok, err := resourceStringValidator.Evaluate(args[0]) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("listener name is not valid: %s", err)) + } else { + cmd.listenerName = args[0] + } + + cmd.port, err = strconv.Atoi(args[1]) + if err != nil { + validationErrors = append(validationErrors, fmt.Errorf("listener port is not valid: %s", err)) + } + ok, err = numberValidator.Evaluate(cmd.port) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("listener port is not valid: %s", err)) + } + } + + // Validate flags + if cmd.Flags.RoutingKey != "" { + ok, err := resourceStringValidator.Evaluate(cmd.Flags.RoutingKey) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("routing key is not valid: %s", err)) + } + } + + if cmd.Flags.ListenerType != "" { + ok, err := listenerTypeValidator.Evaluate(cmd.Flags.ListenerType) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("listener type is not valid: %s", err)) + } + } + + if cmd.Flags.Host != "" { + ip := net.ParseIP(cmd.Flags.Host) + ok, _ := hostStringValidator.Evaluate(cmd.Flags.Host) + if !ok || ip == nil { + validationErrors = append(validationErrors, fmt.Errorf("host is not valid: a valid IP address or hostname is expected")) + } + } + + if cmd.Flags.TlsCredentials != "" { + ok, err := resourceStringValidator.Evaluate(cmd.Flags.TlsCredentials) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("tlsCredentials is not valid: %s", err)) + } + } + + if cmd.Flags.Output != "" { + ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) + } + } + return validationErrors +} + +func (cmd *CmdListenerGenerate) InputToOptions() { + // default routingkey to name of listener + if cmd.Flags.RoutingKey == "" { + cmd.routingKey = cmd.listenerName + } else { + cmd.routingKey = cmd.Flags.RoutingKey + } + + if cmd.namespace == "" { + cmd.namespace = "default" + } + + if cmd.Flags.Host == "" { + cmd.host = "0.0.0.0" + } else { + cmd.host = cmd.Flags.Host + } + + cmd.tlsCredentials = cmd.Flags.TlsCredentials + cmd.listenerType = cmd.Flags.ListenerType + cmd.output = cmd.Flags.Output +} + +func (cmd *CmdListenerGenerate) Run() error { + listenerResource := v2alpha1.Listener{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Listener", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.listenerName, + Namespace: cmd.namespace, + }, + Spec: v2alpha1.ListenerSpec{ + Host: cmd.host, + Port: cmd.port, + RoutingKey: cmd.routingKey, + TlsCredentials: cmd.tlsCredentials, + Type: cmd.listenerType, + }, + } + + encodedOutput, err := utils.Encode(cmd.output, listenerResource) + fmt.Println(encodedOutput) + return err + +} + +func (cmd *CmdListenerGenerate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/listener/nonkube/listener_generate_test.go b/internal/cmd/skupper/listener/nonkube/listener_generate_test.go new file mode 100644 index 000000000..04edcb8f7 --- /dev/null +++ b/internal/cmd/skupper/listener/nonkube/listener_generate_test.go @@ -0,0 +1,291 @@ +package nonkube + +import ( + "testing" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/spf13/cobra" + + "gotest.tools/v3/assert" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestNonKubeCmdListenerGenerate_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + flags *common.CommandListenerGenerateFlags + cobraGenericFlags map[string]string + expectedErrors []string + } + + testTable := []test{ + { + name: "listener name and port are not specified", + args: []string{}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"listener name and port must be configured"}, + }, + { + name: "listener name is not valid", + args: []string{"my new Listener", "8080"}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"listener name is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "listener name is empty", + args: []string{"", "1234"}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"listener name must not be empty"}, + }, + { + name: "listener port empty", + args: []string{"my-name-port-empty", ""}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"listener port must not be empty"}, + }, + { + name: "port is not valid", + args: []string{"my-listener-port", "abcd"}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"listener port is not valid: strconv.Atoi: parsing \"abcd\": invalid syntax"}, + }, + { + name: "listener port not positive", + args: []string{"my-port-positive", "-45"}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"listener port is not valid: value is not positive"}, + }, + { + name: "more than two arguments was specified", + args: []string{"my", "listener", "test"}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{"only two arguments are allowed for this command"}, + }, + { + name: "type is not valid", + args: []string{"my-listener", "8080"}, + flags: &common.CommandListenerGenerateFlags{ListenerType: "not-valid", Host: "1.2.3.4"}, + expectedErrors: []string{"listener type is not valid: value not-valid not allowed. It should be one of this options: [tcp]"}, + }, + { + name: "routing key is not valid", + args: []string{"my-listener-rk", "8080"}, + flags: &common.CommandListenerGenerateFlags{RoutingKey: "not-valid$", Host: "1.2.3.4"}, + expectedErrors: []string{"routing key is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "TlsCredentials key is not valid", + args: []string{"my-listener-tls", "8080"}, + flags: &common.CommandListenerGenerateFlags{TlsCredentials: "not-valid$", Host: "1.2.3.4"}, + expectedErrors: []string{"tlsCredentials is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "host is not valid", + args: []string{"my-listener-host", "8080"}, + flags: &common.CommandListenerGenerateFlags{Host: "not-valid$"}, + expectedErrors: []string{"host is not valid: a valid IP address or hostname is expected"}, + }, + { + name: "output format is not valid", + args: []string{"my-listener", "8080"}, + flags: &common.CommandListenerGenerateFlags{Output: "not-valid", Host: "1.2.3.4"}, + expectedErrors: []string{"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]"}, + }, + { + name: "kubernetes flags are not valid on this platform", + args: []string{"my-listener", "8080"}, + flags: &common.CommandListenerGenerateFlags{Host: "1.2.3.4"}, + expectedErrors: []string{}, + cobraGenericFlags: map[string]string{ + common.FlagNameContext: "test", + common.FlagNameKubeconfig: "test", + }, + }, + { + name: "flags all valid", + args: []string{"my-listener-flags", "8080"}, + flags: &common.CommandListenerGenerateFlags{ + RoutingKey: "routingkeyname", + TlsCredentials: "secretname", + ListenerType: "tcp", + Output: "json", + Host: "1.2.3.4", + }, + expectedErrors: []string{}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + command := &CmdListenerGenerate{Flags: &common.CommandListenerGenerateFlags{}} + command.CobraCmd = &cobra.Command{Use: "test"} + + if test.flags != nil { + command.Flags = test.flags + } + + if test.cobraGenericFlags != nil && len(test.cobraGenericFlags) > 0 { + for name, value := range test.cobraGenericFlags { + command.CobraCmd.Flags().String(name, value, "") + } + } + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + }) + } +} + +func TestNonKubeCmdListenerGenerate_InputToOptions(t *testing.T) { + + type test struct { + name string + args []string + namespace string + flags common.CommandListenerGenerateFlags + expectedNamespace string + listenerName string + expectedTlsCredentials string + expectedHost string + expectedRoutingKey string + expectedListenerType string + expectedOutput string + } + + testTable := []test{ + { + name: "test1", + flags: common.CommandListenerGenerateFlags{"backend", "", "secret", "tcp", "json"}, + expectedTlsCredentials: "secret", + expectedHost: "0.0.0.0", + expectedRoutingKey: "backend", + expectedListenerType: "tcp", + expectedOutput: "json", + expectedNamespace: "default", + }, + { + name: "test2", + namespace: "test", + flags: common.CommandListenerGenerateFlags{"backend", "1.2.3.4", "secret", "tcp", "json"}, + expectedTlsCredentials: "secret", + expectedHost: "1.2.3.4", + expectedRoutingKey: "backend", + expectedListenerType: "tcp", + expectedOutput: "json", + expectedNamespace: "test", + }, + { + name: "test3", + namespace: "default", + flags: common.CommandListenerGenerateFlags{"", "", "secret", "tcp", "yaml"}, + expectedTlsCredentials: "secret", + expectedHost: "0.0.0.0", + expectedRoutingKey: "my-listener", + expectedListenerType: "tcp", + expectedOutput: "yaml", + expectedNamespace: "default", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + cmd := CmdListenerGenerate{} + cmd.Flags = &test.flags + cmd.listenerName = "my-listener" + cmd.namespace = test.namespace + cmd.listenerHandler = fs.NewListenerHandler(cmd.namespace) + + cmd.InputToOptions() + + assert.Check(t, cmd.routingKey == test.expectedRoutingKey) + assert.Check(t, cmd.tlsCredentials == test.expectedTlsCredentials) + assert.Check(t, cmd.host == test.expectedHost) + assert.Check(t, cmd.listenerType == test.expectedListenerType) + assert.Check(t, cmd.output == test.expectedOutput) + assert.Check(t, cmd.namespace == test.expectedNamespace) + }) + } +} + +func TestNonKubeCmdListenerGenerate_Run(t *testing.T) { + type test struct { + name string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperError string + listenerName string + host string + output string + errorMessage string + routingKey string + tlsCredentials string + listenerType string + listenerPort int + } + + testTable := []test{ + { + name: "runs ok", + k8sObjects: nil, + skupperObjects: nil, + listenerName: "test1", + listenerPort: 8080, + listenerType: "tcp", + routingKey: "keyname", + tlsCredentials: "secretname", + host: "1.2.3.4", + output: "json", + }, + { + name: "runs ok yaml", + k8sObjects: nil, + skupperObjects: nil, + listenerName: "test2", + listenerPort: 8080, + listenerType: "tcp", + host: "2.2.2.2", + output: "yaml", + }, + { + name: "runs fails because the output format is not supported", + k8sObjects: nil, + skupperObjects: nil, + listenerName: "test3", + listenerPort: 8080, + host: "3.3.3.3", + output: "unsupported", + errorMessage: "format unsupported not supported", + }, + } + + for _, test := range testTable { + command := &CmdListenerGenerate{} + + command.listenerName = test.listenerName + command.output = test.output + command.port = test.listenerPort + command.host = test.host + command.listenerType = test.listenerType + command.namespace = "test" + command.listenerHandler = fs.NewListenerHandler(command.namespace) + defer command.listenerHandler.Delete("test1") + t.Run(test.name, func(t *testing.T) { + + err := command.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error(), err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} diff --git a/internal/cmd/skupper/listener/nonkube/listener_update.go b/internal/cmd/skupper/listener/nonkube/listener_update.go index dc75696b6..b37e7000d 100644 --- a/internal/cmd/skupper/listener/nonkube/listener_update.go +++ b/internal/cmd/skupper/listener/nonkube/listener_update.go @@ -5,7 +5,6 @@ import ( "net" "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "github.com/skupperproject/skupper/pkg/utils/validator" @@ -19,7 +18,6 @@ type ListenerUpdates struct { tlsCredentials string listenerType string port int - output string } type CmdListenerUpdate struct { listenerHandler *fs.ListenerHandler @@ -48,7 +46,6 @@ func (cmd *CmdListenerUpdate) ValidateInput(args []string) []error { resourceStringValidator := validator.NewResourceStringValidator() numberValidator := validator.NewNumberValidator() listenerTypeValidator := validator.NewOptionValidator(common.ListenerTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) hostStringValidator := validator.NewHostStringValidator() if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameContext) != nil && cmd.CobraCmd.Flag(common.FlagNameContext).Value.String() != "" { @@ -132,14 +129,6 @@ func (cmd *CmdListenerUpdate) ValidateInput(args []string) []error { cmd.newSettings.port = cmd.Flags.Port } } - if cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } else { - cmd.newSettings.output = cmd.Flags.Output - } - } return validationErrors } @@ -172,16 +161,12 @@ func (cmd *CmdListenerUpdate) Run() error { Type: cmd.newSettings.listenerType, }, } - if cmd.newSettings.output != "" { - encodedOutput, err := utils.Encode(cmd.newSettings.output, listenerResource) - fmt.Println(encodedOutput) + + err := cmd.listenerHandler.Add(listenerResource) + if err != nil { return err - } else { - err := cmd.listenerHandler.Add(listenerResource) - if err != nil { - return err - } } + return nil } diff --git a/internal/cmd/skupper/listener/nonkube/listener_update_test.go b/internal/cmd/skupper/listener/nonkube/listener_update_test.go index 91d08ca2d..30c327fd7 100644 --- a/internal/cmd/skupper/listener/nonkube/listener_update_test.go +++ b/internal/cmd/skupper/listener/nonkube/listener_update_test.go @@ -92,12 +92,6 @@ func TestCmdListenerUpdate_ValidateInput(t *testing.T) { flags: &common.CommandListenerUpdateFlags{Host: "not-valid$"}, expectedErrors: []string{"host is not valid: a valid IP address or hostname is expected"}, }, - { - name: "output is not valid", - args: []string{"my-listener"}, - flags: &common.CommandListenerUpdateFlags{Output: "not-supported"}, - expectedErrors: []string{"output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "kubernetes flags are not valid on this platform", args: []string{"my-listener"}, @@ -116,7 +110,6 @@ func TestCmdListenerUpdate_ValidateInput(t *testing.T) { TlsCredentials: "secretname", Port: 1234, ListenerType: "tcp", - Output: "json", Host: "1.2.3.4", }, expectedErrors: []string{}, @@ -180,7 +173,6 @@ func TestCmdListenerUpdate_Run(t *testing.T) { errorMessage string listenerName string host string - output string routingKey string tlsCredentials string listenerType string @@ -199,14 +191,12 @@ func TestCmdListenerUpdate_Run(t *testing.T) { tlsCredentials: "secretname", }, { - name: "run output json", - listenerName: "my-listener", - port: 8181, - listenerType: "tcp", - host: "hostname", - routingKey: "keyname", - tlsCredentials: "secretname", - output: "json", + name: "run ok no secret", + listenerName: "my-listener", + port: 8181, + listenerType: "tcp", + host: "hostname", + routingKey: "keyname", }, } @@ -214,7 +204,6 @@ func TestCmdListenerUpdate_Run(t *testing.T) { command := &CmdListenerUpdate{} command.CobraCmd = &cobra.Command{Use: "test"} command.listenerName = test.listenerName - command.newSettings.output = test.output command.newSettings.port = test.port command.newSettings.host = test.host command.newSettings.routingKey = test.routingKey diff --git a/internal/cmd/skupper/site/kube/site_create.go b/internal/cmd/skupper/site/kube/site_create.go index 8d8cc0470..a10f02e55 100644 --- a/internal/cmd/skupper/site/kube/site_create.go +++ b/internal/cmd/skupper/site/kube/site_create.go @@ -6,9 +6,10 @@ package kube import ( "context" "fmt" - "k8s.io/apimachinery/pkg/api/meta" "time" + "k8s.io/apimachinery/pkg/api/meta" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/kube/client" @@ -29,7 +30,6 @@ type CmdSiteCreate struct { serviceAccountName string Namespace string linkAccessType string - output string timeout time.Duration status string } @@ -55,7 +55,6 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error { var validationErrors []error resourceStringValidator := validator.NewResourceStringValidator() linkAccessTypeValidator := validator.NewOptionValidator(common.LinkAccessTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) timeoutValidator := validator.NewTimeoutInSecondsValidator() statusValidator := validator.NewOptionValidator(common.WaitStatusTypes) @@ -104,13 +103,6 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error { } } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } - if cmd.Flags != nil && cmd.Flags.Timeout.String() != "" { ok, err := timeoutValidator.Evaluate(cmd.Flags.Timeout) if !ok { @@ -140,7 +132,6 @@ func (cmd *CmdSiteCreate) InputToOptions() { } } - cmd.output = cmd.Flags.Output cmd.timeout = cmd.Flags.Timeout cmd.status = cmd.Flags.Wait @@ -163,26 +154,13 @@ func (cmd *CmdSiteCreate) Run() error { }, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, resource) - fmt.Println(encodedOutput) - - return err - - } else { - _, err := cmd.Client.Sites(cmd.Namespace).Create(context.TODO(), &resource, metav1.CreateOptions{}) - return err - } + _, err := cmd.Client.Sites(cmd.Namespace).Create(context.TODO(), &resource, metav1.CreateOptions{}) + return err } func (cmd *CmdSiteCreate) WaitUntil() error { - // the site resource was not created - if cmd.output != "" { - return nil - } - if cmd.status == "none" { return nil } diff --git a/internal/cmd/skupper/site/kube/site_create_test.go b/internal/cmd/skupper/site/kube/site_create_test.go index 2531ed8dd..7e3a9d01f 100644 --- a/internal/cmd/skupper/site/kube/site_create_test.go +++ b/internal/cmd/skupper/site/kube/site_create_test.go @@ -1,12 +1,13 @@ package kube import ( + "testing" + "time" + "github.com/skupperproject/skupper/api/types" "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake" - "testing" - "time" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "gotest.tools/v3/assert" @@ -64,12 +65,6 @@ func TestCmdSiteCreate_ValidateInput(t *testing.T) { flags: &common.CommandSiteCreateFlags{ServiceAccount: "not valid service account name", Timeout: time.Minute}, expectedErrors: []string{"service account name is not valid: serviceaccounts \"not valid service account name\" not found"}, }, - { - name: "host name was specified, but this flag does not work on kube platforms", - args: []string{"my-site"}, - flags: &common.CommandSiteCreateFlags{Host: "host", Timeout: time.Minute}, - expectedErrors: []string{}, - }, { name: "link access type is not valid", args: []string{"my-site"}, @@ -80,17 +75,9 @@ func TestCmdSiteCreate_ValidateInput(t *testing.T) { }, }, { - name: "output format is not valid", - args: []string{"my-site"}, - flags: &common.CommandSiteCreateFlags{Output: "not-valid", Timeout: time.Minute}, - expectedErrors: []string{ - "output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]", - }, - }, - { - name: "host flag is not valid for this platform", + name: "bind-host flag is not valid for this platform", args: []string{"my-site"}, - flags: &common.CommandSiteCreateFlags{Host: "host", Timeout: time.Minute}, + flags: &common.CommandSiteCreateFlags{BindHost: "host", Timeout: time.Minute}, expectedErrors: []string{}, }, { @@ -153,7 +140,6 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) { args []string flags common.CommandSiteCreateFlags expectedLinkAccess string - expectedOutput string expectedTimeout time.Duration expectedStatus string } @@ -164,42 +150,30 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) { args: []string{"my-site"}, flags: common.CommandSiteCreateFlags{}, expectedLinkAccess: "", - expectedOutput: "", }, { name: "options with link access enabled but using a type by default", args: []string{"my-site"}, flags: common.CommandSiteCreateFlags{EnableLinkAccess: true}, expectedLinkAccess: "default", - expectedOutput: "", }, { name: "options with link access enabled using the nodeport type", args: []string{"my-site"}, flags: common.CommandSiteCreateFlags{EnableLinkAccess: true, LinkAccessType: "nodeport"}, expectedLinkAccess: "nodeport", - expectedOutput: "", }, { name: "options with link access options not well specified", args: []string{"my-site"}, flags: common.CommandSiteCreateFlags{EnableLinkAccess: false, LinkAccessType: "nodeport"}, expectedLinkAccess: "", - expectedOutput: "", - }, - { - name: "options output type", - args: []string{"my-site"}, - flags: common.CommandSiteCreateFlags{EnableLinkAccess: false, LinkAccessType: "nodeport", Output: "yaml"}, - expectedLinkAccess: "", - expectedOutput: "yaml", }, { name: "options with timeout", args: []string{"my-site"}, flags: common.CommandSiteCreateFlags{Timeout: time.Second * 60}, expectedLinkAccess: "", - expectedOutput: "", expectedTimeout: time.Minute, }, { @@ -219,7 +193,6 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) { cmd.InputToOptions() - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.linkAccessType == test.expectedLinkAccess) assert.Check(t, cmd.timeout == test.expectedTimeout) assert.Check(t, cmd.status == test.expectedStatus) @@ -236,7 +209,6 @@ func TestCmdSiteCreate_Run(t *testing.T) { siteName string serviceAccountName string options map[string]string - output string errorMessage string } @@ -277,20 +249,8 @@ func TestCmdSiteCreate_Run(t *testing.T) { siteName: "test", serviceAccountName: "my-service-account", options: map[string]string{"name": "my-site"}, - output: "yaml", skupperError: "", }, - { - name: "run fails because the output format is not supported", - k8sObjects: nil, - skupperObjects: nil, - siteName: "test", - serviceAccountName: "my-service-account", - options: map[string]string{"name": "my-site"}, - output: "unsupported", - skupperError: "", - errorMessage: "format unsupported not supported", - }, } for _, test := range testTable { @@ -304,7 +264,6 @@ func TestCmdSiteCreate_Run(t *testing.T) { command.siteName = test.siteName command.serviceAccountName = test.serviceAccountName - command.output = test.output t.Run(test.name, func(t *testing.T) { @@ -325,7 +284,6 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) { skupperObjects []runtime.Object skupperError string expectError bool - output string } testTable := []test{ @@ -362,26 +320,6 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) { skupperError: "it failed", expectError: true, }, - { - name: "there is no need to wait for a site, the user just wanted the output", - k8sObjects: nil, - skupperObjects: []runtime.Object{ - &v2alpha1.Site{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-site", - Namespace: "test", - }, - Status: v2alpha1.SiteStatus{ - Status: v2alpha1.Status{ - Message: "OK", - }, - }, - }, - }, - output: "yaml", - skupperError: "", - expectError: false, - }, { name: "site is ready", status: "ready", @@ -506,7 +444,6 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) { assert.Assert(t, err) command.Client = fakeSkupperClient.GetSkupperClient().SkupperV2alpha1() command.siteName = "my-site" - command.output = test.output command.timeout = time.Second command.status = test.status diff --git a/internal/cmd/skupper/site/kube/site_generate.go b/internal/cmd/skupper/site/kube/site_generate.go new file mode 100644 index 000000000..082074a1a --- /dev/null +++ b/internal/cmd/skupper/site/kube/site_generate.go @@ -0,0 +1,145 @@ +/* +Copyright © 2024 Skupper Team +*/ +package kube + +import ( + "context" + "fmt" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + "github.com/skupperproject/skupper/internal/kube/client" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + skupperv2alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v2alpha1" + "github.com/skupperproject/skupper/pkg/utils/validator" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +type CmdSiteGenerate struct { + Client skupperv2alpha1.SkupperV2alpha1Interface + KubeClient kubernetes.Interface + CobraCmd *cobra.Command + Flags *common.CommandSiteGenerateFlags + siteName string + serviceAccountName string + Namespace string + linkAccessType string + output string +} + +func NewCmdSiteGenerate() *CmdSiteGenerate { + + skupperCmd := CmdSiteGenerate{} + + return &skupperCmd +} + +func (cmd *CmdSiteGenerate) NewClient(cobraCommand *cobra.Command, args []string) { + cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String()) + utils.HandleError(err) + + cmd.Client = cli.GetSkupperClient().SkupperV2alpha1() + cmd.KubeClient = cli.GetKubeClient() + cmd.Namespace = cli.Namespace +} + +func (cmd *CmdSiteGenerate) ValidateInput(args []string) []error { + + var validationErrors []error + resourceStringValidator := validator.NewResourceStringValidator() + linkAccessTypeValidator := validator.NewOptionValidator(common.LinkAccessTypes) + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) + + if cmd.Flags != nil && cmd.Flags.BindHost != "" { + fmt.Println("Warning: --bind-host flag is not supported on this platform") + } + + if cmd.Flags != nil && cmd.Flags.SubjectAlternativeNames != nil && len(cmd.Flags.SubjectAlternativeNames) > 0 { + fmt.Println("Warning: --subject-alternative-names flag is not supported on this platform") + } + + if len(args) == 0 || args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("site name must not be empty")) + } else if len(args) > 1 { + validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command.")) + } else { + cmd.siteName = args[0] + + ok, err := resourceStringValidator.Evaluate(cmd.siteName) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("site name is not valid: %s", err)) + } + } + + if cmd.Flags != nil && cmd.Flags.LinkAccessType != "" { + ok, err := linkAccessTypeValidator.Evaluate(cmd.Flags.LinkAccessType) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("link access type is not valid: %s", err)) + } + } + + if cmd.Flags != nil && !cmd.Flags.EnableLinkAccess && len(cmd.Flags.LinkAccessType) > 0 { + validationErrors = append(validationErrors, fmt.Errorf("for the site to work with this type of linkAccess, the --enable-link-access option must be set to true")) + } + + if cmd.Flags != nil && cmd.Flags.ServiceAccount != "" { + svcAccount, err := cmd.KubeClient.CoreV1().ServiceAccounts(cmd.Namespace).Get(context.TODO(), cmd.Flags.ServiceAccount, metav1.GetOptions{}) + if err != nil || svcAccount == nil { + validationErrors = append(validationErrors, fmt.Errorf("service account name is not valid: %s", err)) + } + } + if cmd.Flags != nil && cmd.Flags.Output != "" { + ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("format %s", err)) + } + } + + return validationErrors +} + +func (cmd *CmdSiteGenerate) InputToOptions() { + + cmd.serviceAccountName = cmd.Flags.ServiceAccount + + if cmd.Flags.EnableLinkAccess { + if cmd.Flags.LinkAccessType == "" { + cmd.linkAccessType = "default" + } else { + cmd.linkAccessType = cmd.Flags.LinkAccessType + } + } + + if cmd.Flags.Output != "" { + cmd.output = cmd.Flags.Output + } else { + cmd.output = "yaml" + } +} + +func (cmd *CmdSiteGenerate) Run() error { + + resource := v2alpha1.Site{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Site", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.siteName, + Namespace: cmd.Namespace, + }, + Spec: v2alpha1.SiteSpec{ + ServiceAccount: cmd.serviceAccountName, + LinkAccess: cmd.linkAccessType, + }, + } + + encodedOutput, err := utils.Encode(cmd.output, resource) + fmt.Println(encodedOutput) + return err +} + +func (cmd *CmdSiteGenerate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/site/kube/site_generate_test.go b/internal/cmd/skupper/site/kube/site_generate_test.go new file mode 100644 index 000000000..ee8d710a0 --- /dev/null +++ b/internal/cmd/skupper/site/kube/site_generate_test.go @@ -0,0 +1,257 @@ +package kube + +import ( + "testing" + + "github.com/skupperproject/skupper/api/types" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake" + + "gotest.tools/v3/assert" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestCmdSiteGenerate_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + flags *common.CommandSiteGenerateFlags + expectedErrors []string + } + + testTable := []test{ + { + name: "site name is not valid.", + args: []string{"my new site"}, + expectedErrors: []string{"site name is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "site name is not specified.", + args: []string{}, + expectedErrors: []string{"site name must not be empty"}, + }, + { + name: "more than one argument was specified", + args: []string{"my", "site"}, + expectedErrors: []string{"only one argument is allowed for this command."}, + }, + { + name: "service account name is not valid.", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{ServiceAccount: "not valid service account name"}, + expectedErrors: []string{"service account name is not valid: serviceaccounts \"not valid service account name\" not found"}, + }, + { + name: "link access type is not valid", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{LinkAccessType: "not-valid"}, + expectedErrors: []string{ + "link access type is not valid: value not-valid not allowed. It should be one of this options: [route loadbalancer default]", + "for the site to work with this type of linkAccess, the --enable-link-access option must be set to true", + }, + }, + { + name: "output format is not valid", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{Output: "not-valid"}, + expectedErrors: []string{ + "format value not-valid not allowed. It should be one of this options: [json yaml]", + }, + }, + { + name: "bind-host flag is not valid for this platform", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{BindHost: "host"}, + expectedErrors: []string{}, + }, + { + name: "subject alternative names flag is not valid for this platform", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{SubjectAlternativeNames: []string{"test"}}, + expectedErrors: []string{}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + command := &CmdSiteGenerate{ + Namespace: "test", + } + + cmd := common.ConfigureCobraCommand(types.PlatformKubernetes, common.SkupperCmdDescription{}, command, nil) + + command.CobraCmd = cmd + + fakeSkupperClient, err := fakeclient.NewFakeClient(command.Namespace, test.k8sObjects, test.skupperObjects, "") + assert.Assert(t, err) + command.Client = fakeSkupperClient.GetSkupperClient().SkupperV2alpha1() + command.KubeClient = fakeSkupperClient.GetKubeClient() + + if test.flags != nil { + command.Flags = test.flags + } + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + }) + } +} + +func TestCmdSiteGenerate_InputToOptions(t *testing.T) { + + type test struct { + name string + args []string + flags common.CommandSiteGenerateFlags + expectedLinkAccess string + expectedOutput string + } + + testTable := []test{ + { + name: "options without link access enabled", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{}, + expectedLinkAccess: "", + expectedOutput: "yaml", + }, + { + name: "options with link access enabled but using a type by default", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: true}, + expectedLinkAccess: "default", + expectedOutput: "yaml", + }, + { + name: "options with link access enabled using the nodeport type", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: true, LinkAccessType: "nodeport"}, + expectedLinkAccess: "nodeport", + expectedOutput: "yaml", + }, + { + name: "options with link access options not well specified", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: false, LinkAccessType: "nodeport"}, + expectedLinkAccess: "", + expectedOutput: "yaml", + }, + { + name: "options output type", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: false, LinkAccessType: "nodeport", Output: "json"}, + expectedLinkAccess: "", + expectedOutput: "json", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + cmd, err := newCmdSiteGenerateWithMocks("test") + assert.Assert(t, err) + cmd.Flags = &test.flags + cmd.siteName = "my-site" + + cmd.InputToOptions() + + assert.Check(t, cmd.output == test.expectedOutput) + assert.Check(t, cmd.linkAccessType == test.expectedLinkAccess) + }) + } +} + +func TestCmdSiteGenerate_Run(t *testing.T) { + type test struct { + name string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperError string + siteName string + serviceAccountName string + options map[string]string + output string + errorMessage string + } + + testTable := []test{ + { + name: "runs ok", + k8sObjects: nil, + skupperObjects: nil, + siteName: "my-site", + serviceAccountName: "my-service-account", + options: map[string]string{"name": "my-site"}, + skupperError: "", + output: "yaml", + }, + { + name: "runs ok with yaml output", + k8sObjects: nil, + skupperObjects: nil, + siteName: "test", + serviceAccountName: "my-service-account", + options: map[string]string{"name": "my-site"}, + output: "json", + skupperError: "", + }, + { + name: "runs fails because the output format is not supported", + k8sObjects: nil, + skupperObjects: nil, + siteName: "test", + serviceAccountName: "my-service-account", + options: map[string]string{"name": "my-site"}, + output: "unsupported", + skupperError: "", + errorMessage: "format unsupported not supported", + }, + } + + for _, test := range testTable { + command := &CmdSiteGenerate{ + Namespace: "test", + } + + fakeSkupperClient, err := fakeclient.NewFakeClient(command.Namespace, test.k8sObjects, test.skupperObjects, test.skupperError) + assert.Assert(t, err) + command.Client = fakeSkupperClient.GetSkupperClient().SkupperV2alpha1() + + command.siteName = test.siteName + command.serviceAccountName = test.serviceAccountName + command.output = test.output + + t.Run(test.name, func(t *testing.T) { + + err := command.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} + +// --- helper methods + +func newCmdSiteGenerateWithMocks(namespace string) (*CmdSiteGenerate, error) { + + client, err := fakeclient.NewFakeClient(namespace, nil, nil, "") + if err != nil { + return nil, err + } + cmdSiteGenerate := &CmdSiteGenerate{ + Client: client.GetSkupperClient().SkupperV2alpha1(), + KubeClient: client.GetKubeClient(), + Namespace: namespace, + } + + return cmdSiteGenerate, nil +} diff --git a/internal/cmd/skupper/site/kube/site_update.go b/internal/cmd/skupper/site/kube/site_update.go index ab0a4bd36..e350dbf7a 100644 --- a/internal/cmd/skupper/site/kube/site_update.go +++ b/internal/cmd/skupper/site/kube/site_update.go @@ -3,11 +3,12 @@ package kube import ( "context" "fmt" + "time" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/kubernetes" - "time" "github.com/skupperproject/skupper/internal/kube/client" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" @@ -26,7 +27,6 @@ type CmdSiteUpdate struct { serviceAccountName string Namespace string linkAccessType string - output string timeout time.Duration status string } @@ -51,7 +51,6 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error { var validationErrors []error linkAccessTypeValidator := validator.NewOptionValidator(common.LinkAccessTypes) - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) timeoutValidator := validator.NewTimeoutInSecondsValidator() statusValidator := validator.NewOptionValidator(common.WaitStatusTypes) @@ -85,7 +84,7 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error { } if cmd.Flags != nil && cmd.Flags.BindHost != "" { - validationErrors = append(validationErrors, fmt.Errorf("--host flag is not supported on this platform")) + validationErrors = append(validationErrors, fmt.Errorf("--bind-host flag is not supported on this platform")) } if cmd.Flags.LinkAccessType != "" { @@ -106,13 +105,6 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error { } } - if cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } - if cmd.Flags != nil && cmd.Flags.Timeout.String() != "" { ok, err := timeoutValidator.Evaluate(cmd.Flags.Timeout) if !ok { @@ -140,7 +132,6 @@ func (cmd *CmdSiteUpdate) InputToOptions() { } } - cmd.output = cmd.Flags.Output cmd.timeout = cmd.Flags.Timeout cmd.status = cmd.Flags.Wait @@ -184,25 +175,12 @@ func (cmd *CmdSiteUpdate) Run() error { Status: currentSite.Status, } - if cmd.output != "" { - encodedOutput, err := utils.Encode(cmd.output, resource) - fmt.Println(encodedOutput) - - return err - - } else { - _, err := cmd.Client.Sites(cmd.Namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) - return err - } + _, err = cmd.Client.Sites(cmd.Namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{}) + return err } func (cmd *CmdSiteUpdate) WaitUntil() error { - // the site resource was not updated - if cmd.output != "" { - return nil - } - if cmd.status == "none" { return nil } diff --git a/internal/cmd/skupper/site/kube/site_update_test.go b/internal/cmd/skupper/site/kube/site_update_test.go index a96da0ad9..03a1d50cd 100644 --- a/internal/cmd/skupper/site/kube/site_update_test.go +++ b/internal/cmd/skupper/site/kube/site_update_test.go @@ -2,11 +2,12 @@ package kube import ( "fmt" - "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "testing" "time" + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "gotest.tools/v3/assert" @@ -111,7 +112,7 @@ func TestCmdSiteUpdate_ValidateInput(t *testing.T) { expectedErrors: []string{"service account name is not valid: serviceaccounts \"not valid service account name\" not found"}, }, { - name: "host name was specified, but this flag does not work on kube platforms", + name: "bind-host name was specified, but this flag does not work on kube platforms", args: []string{"my-site"}, flags: &common.CommandSiteUpdateFlags{BindHost: "host", Timeout: time.Minute}, skupperObjects: []runtime.Object{ @@ -127,7 +128,7 @@ func TestCmdSiteUpdate_ValidateInput(t *testing.T) { }, }, }, - expectedErrors: []string{"--host flag is not supported on this platform"}, + expectedErrors: []string{"--bind-host flag is not supported on this platform"}, }, { name: "link access type is not valid", @@ -153,28 +154,6 @@ func TestCmdSiteUpdate_ValidateInput(t *testing.T) { "for the site to work with this type of linkAccess, the --enable-link-access option must be set to true", }, }, - { - name: "output format is not valid", - args: []string{"my-site"}, - flags: &common.CommandSiteUpdateFlags{Output: "not-valid", Timeout: time.Minute}, - k8sObjects: nil, - skupperObjects: []runtime.Object{ - &v2alpha1.Site{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-site", - Namespace: "test", - }, - Status: v2alpha1.SiteStatus{ - Status: v2alpha1.Status{ - Message: "OK", - }, - }, - }, - }, - expectedErrors: []string{ - "output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]", - }, - }, { name: "there is no skupper site", args: []string{"my-site"}, @@ -384,7 +363,6 @@ func TestCmdSiteUpdate_InputToOptions(t *testing.T) { args []string flags common.CommandSiteUpdateFlags expectedLinkAccess string - expectedOutput string expectedTimeout time.Duration expectedStatus string } @@ -394,36 +372,31 @@ func TestCmdSiteUpdate_InputToOptions(t *testing.T) { name: "options without link access enabled", args: []string{"my-site"}, flags: common.CommandSiteUpdateFlags{}, - expectedLinkAccess: "none", - expectedOutput: "", + expectedLinkAccess: "", }, { name: "options with link access enabled but using a type by default and link access host specified", args: []string{"my-site"}, flags: common.CommandSiteUpdateFlags{EnableLinkAccess: true}, - expectedLinkAccess: "loadbalancer", - expectedOutput: "", + expectedLinkAccess: "default", }, { name: "options with link access enabled using the nodeport type", args: []string{"my-site"}, flags: common.CommandSiteUpdateFlags{EnableLinkAccess: true, LinkAccessType: "nodeport"}, expectedLinkAccess: "nodeport", - expectedOutput: "", }, { name: "options with link access options not well specified", args: []string{"my-site"}, flags: common.CommandSiteUpdateFlags{EnableLinkAccess: false, LinkAccessType: "nodeport"}, - expectedLinkAccess: "none", - expectedOutput: "", + expectedLinkAccess: "", }, { - name: "options with output type and timeout", + name: "options with loadbalancer and timeout", args: []string{"my-site"}, - flags: common.CommandSiteUpdateFlags{EnableLinkAccess: false, LinkAccessType: "nodeport", Output: "yaml", Timeout: time.Minute}, - expectedLinkAccess: "none", - expectedOutput: "yaml", + flags: common.CommandSiteUpdateFlags{EnableLinkAccess: true, LinkAccessType: "loadbalancer", Timeout: time.Minute}, + expectedLinkAccess: "loadbalancer", expectedTimeout: time.Minute, }, { @@ -448,8 +421,9 @@ func TestCmdSiteUpdate_InputToOptions(t *testing.T) { command.InputToOptions() - assert.Check(t, command.output == test.expectedOutput) assert.Check(t, command.status == test.expectedStatus) + assert.Check(t, command.linkAccessType == test.expectedLinkAccess) + assert.Check(t, command.timeout == test.expectedTimeout) }) } } @@ -463,7 +437,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { siteName string serviceAccountName string linkAccessType string - output string errorMessage string } @@ -482,7 +455,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { siteName: "my-site", serviceAccountName: "my-service-account", linkAccessType: "default", - output: "", skupperError: "", errorMessage: "", }, @@ -493,7 +465,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { siteName: "my-site", serviceAccountName: "my-service-account", linkAccessType: "default", - output: "", skupperError: "error", errorMessage: "error", }, @@ -504,18 +475,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { siteName: "my-site", serviceAccountName: "my-service-account", linkAccessType: "default", - output: "yaml", - skupperError: "", - errorMessage: "sites.skupper.io \"my-site\" not found", - }, - { - name: "runs fails because the output format is not supported", - k8sObjects: nil, - skupperObjects: nil, - siteName: "my-site", - serviceAccountName: "my-service-account", - linkAccessType: "default", - output: "unsupported", skupperError: "", errorMessage: "sites.skupper.io \"my-site\" not found", }, @@ -532,7 +491,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { command.siteName = test.siteName command.serviceAccountName = test.serviceAccountName command.linkAccessType = test.linkAccessType - command.output = test.output t.Run(test.name, func(t *testing.T) { diff --git a/internal/cmd/skupper/site/nonkube/site_create.go b/internal/cmd/skupper/site/nonkube/site_create.go index 701e8454d..b082574f1 100644 --- a/internal/cmd/skupper/site/nonkube/site_create.go +++ b/internal/cmd/skupper/site/nonkube/site_create.go @@ -8,7 +8,6 @@ import ( "net" "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "github.com/skupperproject/skupper/pkg/utils/validator" @@ -24,7 +23,6 @@ type CmdSiteCreate struct { options map[string]string siteName string linkAccessEnabled bool - output string namespace string bindHost string routerAccessName string @@ -49,7 +47,6 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error { var validationErrors []error hostStringValidator := validator.NewHostStringValidator() resourceStringValidator := validator.NewResourceStringValidator() - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) if cmd.Flags.ServiceAccount != "" { fmt.Println("Warning: --service-account flag is not supported on this platform") @@ -75,13 +72,6 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error { cmd.siteName = args[0] } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } - if cmd.Flags != nil && cmd.Flags.BindHost != "" { ip := net.ParseIP(cmd.Flags.BindHost) ok, _ := hostStringValidator.Evaluate(cmd.Flags.BindHost) @@ -114,7 +104,6 @@ func (cmd *CmdSiteCreate) InputToOptions() { options[common.SiteConfigNameKey] = cmd.siteName cmd.options = options - cmd.output = cmd.Flags.Output if cmd.namespace == "" { cmd.namespace = "default" @@ -162,33 +151,16 @@ func (cmd *CmdSiteCreate) Run() error { }, } - if cmd.output != "" { - encodedSiteOutput, err := utils.Encode(cmd.output, siteResource) - if err != nil { - return err - } - fmt.Println(encodedSiteOutput) - if cmd.linkAccessEnabled == true { - fmt.Println("---") - encodedRouterAccessOutput, err := utils.Encode(cmd.output, routerAccessResource) - if err != nil { - return err - } - fmt.Println(encodedRouterAccessOutput) - } - } else { - err := cmd.siteHandler.Add(siteResource) + err := cmd.siteHandler.Add(siteResource) + if err != nil { + return err + } + + if cmd.linkAccessEnabled == true { + err = cmd.routerAccessHandler.Add(routerAccessResource) if err != nil { return err } - - if cmd.linkAccessEnabled == true { - err = cmd.routerAccessHandler.Add(routerAccessResource) - if err != nil { - return err - } - } - } return nil diff --git a/internal/cmd/skupper/site/nonkube/site_create_test.go b/internal/cmd/skupper/site/nonkube/site_create_test.go index bfda53471..6d5108262 100644 --- a/internal/cmd/skupper/site/nonkube/site_create_test.go +++ b/internal/cmd/skupper/site/nonkube/site_create_test.go @@ -41,14 +41,6 @@ func TestNonKubeCmdSiteCreate_ValidateInput(t *testing.T) { flags: &common.CommandSiteCreateFlags{BindHost: "bindhost"}, expectedErrors: []string{"only one argument is allowed for this command"}, }, - { - name: "output format is not valid", - args: []string{"my-site"}, - flags: &common.CommandSiteCreateFlags{BindHost: "127.0.0.1", Output: "not-valid"}, - expectedErrors: []string{ - "output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]", - }, - }, { name: "bindHost was not specified ok", args: []string{"my-site"}, @@ -64,7 +56,7 @@ func TestNonKubeCmdSiteCreate_ValidateInput(t *testing.T) { { name: "subjectAlternativeNames was not valid", args: []string{"my-site"}, - flags: &common.CommandSiteCreateFlags{EnableLinkAccess: true, BindHost: "not-valid", SubjectAlternativeNames: []string{"not-valid$"}}, + flags: &common.CommandSiteCreateFlags{EnableLinkAccess: true, BindHost: "bindhost", SubjectAlternativeNames: []string{"not-valid$"}}, expectedErrors: []string{"SubjectAlternativeNames is not valid: a valid IP address or hostname is expected"}, }, { @@ -81,7 +73,6 @@ func TestNonKubeCmdSiteCreate_ValidateInput(t *testing.T) { name: "flags all valid", args: []string{"my-site"}, flags: &common.CommandSiteCreateFlags{ - Output: "json", BindHost: "1.2.3.4", EnableLinkAccess: true, SubjectAlternativeNames: []string{"3.3.3.3"}, @@ -125,7 +116,6 @@ func TestNonKubeCmdSiteCreate_InputToOptions(t *testing.T) { flags common.CommandSiteCreateFlags expectedSettings map[string]string expectedLinkAccess bool - expectedOutput string expectedNamespace string expectedSubjectAlternativeNames []string expectedBindHost string @@ -141,7 +131,6 @@ func TestNonKubeCmdSiteCreate_InputToOptions(t *testing.T) { "name": "my-site", }, expectedLinkAccess: false, - expectedOutput: "", expectedNamespace: "default", expectedBindHost: "", expectedRouterAccessName: "", @@ -155,26 +144,11 @@ func TestNonKubeCmdSiteCreate_InputToOptions(t *testing.T) { "name": "my-site", }, expectedLinkAccess: true, - expectedOutput: "", expectedNamespace: "default", expectedBindHost: "test", expectedRouterAccessName: "router-access-my-site", expectedSubjectAlternativeNames: nil, }, - { - name: "options output type", - args: []string{"my-site"}, - flags: common.CommandSiteCreateFlags{EnableLinkAccess: false, BindHost: "test", Output: "yaml"}, - expectedSettings: map[string]string{ - "name": "my-site", - }, - expectedLinkAccess: false, - expectedOutput: "yaml", - expectedNamespace: "default", - expectedBindHost: "", - expectedSubjectAlternativeNames: nil, - expectedRouterAccessName: "", - }, { name: "options with subject alternative names", args: []string{"my-site"}, @@ -217,7 +191,6 @@ func TestNonKubeCmdSiteCreate_InputToOptions(t *testing.T) { cmd.InputToOptions() assert.DeepEqual(t, cmd.options, test.expectedSettings) - assert.Check(t, cmd.output == test.expectedOutput) assert.Check(t, cmd.namespace == test.expectedNamespace) assert.Check(t, cmd.bindHost == test.expectedBindHost) assert.Check(t, cmd.linkAccessEnabled == test.expectedLinkAccess) @@ -236,7 +209,6 @@ func TestNonKubeCmdSiteCreate_Run(t *testing.T) { siteName string host string options map[string]string - output string errorMessage string routerAccessName string linkAccessEnabled bool @@ -254,27 +226,13 @@ func TestNonKubeCmdSiteCreate_Run(t *testing.T) { options: map[string]string{"name": "my-site"}, }, { - name: "runs ok without create site", - k8sObjects: nil, - skupperObjects: nil, - siteName: "my-site", - host: "bindhost", - routerAccessName: "ra-test", - options: map[string]string{"name": "my-site"}, - output: "yaml", - linkAccessEnabled: true, - skupperError: "", - }, - { - name: "runs fails because the output format is not supported", + name: "runs ok without create site", k8sObjects: nil, skupperObjects: nil, - siteName: "my-site", - host: "bindHost", + siteName: "test", + host: "bindhost", options: map[string]string{"name": "my-site"}, - output: "unsupported", skupperError: "", - errorMessage: "format unsupported not supported", }, } @@ -283,7 +241,6 @@ func TestNonKubeCmdSiteCreate_Run(t *testing.T) { command.siteName = test.siteName command.options = test.options - command.output = test.output command.routerAccessName = test.routerAccessName command.linkAccessEnabled = test.linkAccessEnabled command.siteHandler = fs.NewSiteHandler(command.namespace) diff --git a/internal/cmd/skupper/site/nonkube/site_generate.go b/internal/cmd/skupper/site/nonkube/site_generate.go new file mode 100644 index 000000000..f7a79efac --- /dev/null +++ b/internal/cmd/skupper/site/nonkube/site_generate.go @@ -0,0 +1,186 @@ +/* +Copyright © 2024 Skupper Team +*/ +package nonkube + +import ( + "fmt" + "net" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" + "github.com/skupperproject/skupper/pkg/utils/validator" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type CmdSiteGenerate struct { + siteHandler *fs.SiteHandler + routerAccessHandler *fs.RouterAccessHandler + CobraCmd *cobra.Command + Flags *common.CommandSiteGenerateFlags + options map[string]string + siteName string + linkAccessEnabled bool + output string + namespace string + bindHost string + routerAccessName string + subjectAlternativeNames []string +} + +func NewCmdSiteGenerate() *CmdSiteGenerate { + return &CmdSiteGenerate{} +} + +func (cmd *CmdSiteGenerate) NewClient(cobraCommand *cobra.Command, args []string) { + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace) != nil && cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() != "" { + cmd.namespace = cmd.CobraCmd.Flag(common.FlagNameNamespace).Value.String() + } + + cmd.siteHandler = fs.NewSiteHandler(cmd.namespace) + cmd.routerAccessHandler = fs.NewRouterAccessHandler(cmd.namespace) + +} + +func (cmd *CmdSiteGenerate) ValidateInput(args []string) []error { + + var validationErrors []error + resourceStringValidator := validator.NewResourceStringValidator() + hostStringValidator := validator.NewHostStringValidator() + outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) + + if cmd.Flags.ServiceAccount != "" { + fmt.Println("Warning: --service-account flag is not supported on this platform") + } + + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameContext) != nil && cmd.CobraCmd.Flag(common.FlagNameContext).Value.String() != "" { + fmt.Println("Warning: --context flag is not supported on this platform") + } + + if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameKubeconfig) != nil && cmd.CobraCmd.Flag(common.FlagNameKubeconfig).Value.String() != "" { + fmt.Println("Warning: --kubeconfig flag is not supported on this platform") + } + + if len(args) == 0 || args[0] == "" { + validationErrors = append(validationErrors, fmt.Errorf("site name must not be empty")) + } else if len(args) > 1 { + validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command")) + } else { + ok, err := resourceStringValidator.Evaluate(args[0]) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("site name is not valid: %s", err)) + } + cmd.siteName = args[0] + + } + + if cmd.Flags != nil && cmd.Flags.BindHost != "" { + ip := net.ParseIP(cmd.Flags.BindHost) + ok, _ := hostStringValidator.Evaluate(cmd.Flags.BindHost) + if !ok && ip == nil { + validationErrors = append(validationErrors, fmt.Errorf("bindhost is not valid: a valid IP address or hostname is expected")) + } + } + if cmd.Flags != nil && len(cmd.Flags.SubjectAlternativeNames) != 0 { + for _, name := range cmd.Flags.SubjectAlternativeNames { + ip := net.ParseIP(name) + ok, _ := hostStringValidator.Evaluate(name) + if !ok && ip == nil { + validationErrors = append(validationErrors, fmt.Errorf("SubjectAlternativeNames is not valid: a valid IP address or hostname is expected")) + } + } + } + + if cmd.Flags.Output != "" { + ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) + if !ok { + validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) + } + } + + return validationErrors +} + +func (cmd *CmdSiteGenerate) InputToOptions() { + if cmd.Flags.EnableLinkAccess { + cmd.linkAccessEnabled = true + cmd.bindHost = cmd.Flags.BindHost + cmd.routerAccessName = "router-access-" + cmd.siteName + cmd.subjectAlternativeNames = cmd.Flags.SubjectAlternativeNames + } + options := make(map[string]string) + options[common.SiteConfigNameKey] = cmd.siteName + + cmd.options = options + + if cmd.namespace == "" { + cmd.namespace = "default" + } + + cmd.output = cmd.Flags.Output + +} + +func (cmd *CmdSiteGenerate) Run() error { + + siteResource := v2alpha1.Site{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "Site", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.siteName, + Namespace: cmd.namespace, + }, + Spec: v2alpha1.SiteSpec{ + Settings: cmd.options, + }, + } + + routerAccessResource := v2alpha1.RouterAccess{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "skupper.io/v2alpha1", + Kind: "RouterAccess", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmd.routerAccessName, + Namespace: cmd.namespace, + }, + Spec: v2alpha1.RouterAccessSpec{ + Roles: []v2alpha1.RouterAccessRole{ + { + Name: "inter-router", + Port: 55671, + }, + { + Name: "edge", + Port: 45671, + }, + }, + BindHost: cmd.bindHost, + SubjectAlternativeNames: cmd.subjectAlternativeNames, + }, + } + + encodedSiteOutput, err := utils.Encode(cmd.output, siteResource) + if err != nil { + return err + } + fmt.Println(encodedSiteOutput) + + if cmd.linkAccessEnabled == true { + fmt.Println("---") + encodedRouterAccessOutput, err := utils.Encode(cmd.output, routerAccessResource) + if err != nil { + return err + } + fmt.Println(encodedRouterAccessOutput) + } + + return err +} + +func (cmd *CmdSiteGenerate) WaitUntil() error { return nil } diff --git a/internal/cmd/skupper/site/nonkube/site_generate_test.go b/internal/cmd/skupper/site/nonkube/site_generate_test.go new file mode 100644 index 000000000..b6ff46df0 --- /dev/null +++ b/internal/cmd/skupper/site/nonkube/site_generate_test.go @@ -0,0 +1,302 @@ +package nonkube + +import ( + "testing" + + "github.com/skupperproject/skupper/internal/cmd/skupper/common" + "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" + fs2 "github.com/skupperproject/skupper/internal/nonkube/client/fs" + "github.com/spf13/cobra" + + "gotest.tools/v3/assert" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestNonKubeCmdSiteGenerate_ValidateInput(t *testing.T) { + type test struct { + name string + args []string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + flags *common.CommandSiteGenerateFlags + cobraGenericFlags map[string]string + expectedErrors []string + } + + testTable := []test{ + { + name: "site name is not valid.", + args: []string{"my new site"}, + flags: &common.CommandSiteGenerateFlags{BindHost: "bindhost"}, + expectedErrors: []string{"site name is not valid: value does not match this regular expression: ^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$"}, + }, + { + name: "site name is not specified.", + args: []string{}, + flags: &common.CommandSiteGenerateFlags{}, + expectedErrors: []string{"site name must not be empty"}, + }, + { + name: "more than one argument was specified", + args: []string{"my", "site"}, + flags: &common.CommandSiteGenerateFlags{BindHost: "1.2.3.4"}, + expectedErrors: []string{"only one argument is allowed for this command"}, + }, + { + name: "bindHost was not specified ok", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{EnableLinkAccess: true}, + expectedErrors: []string{}, + }, + { + name: "bindHost was not valid", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{EnableLinkAccess: true, BindHost: "not-valid$"}, + expectedErrors: []string{"bindhost is not valid: a valid IP address or hostname is expected"}, + }, + { + name: "subjectAlternativeNames was not valid", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{EnableLinkAccess: true, BindHost: "bindhost", SubjectAlternativeNames: []string{"not-valid$"}}, + expectedErrors: []string{"SubjectAlternativeNames is not valid: a valid IP address or hostname is expected"}, + }, + { + name: "output format is not valid", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{BindHost: "bindhost", Output: "not-valid"}, + expectedErrors: []string{"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]"}, + }, + { + name: "service-account is not valid on this platform", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{ServiceAccount: "service-account"}, + expectedErrors: []string{}, + }, + { + name: "kubernetes flags are not valid on this platform", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{}, + expectedErrors: []string{}, + cobraGenericFlags: map[string]string{ + common.FlagNameContext: "test", + common.FlagNameKubeconfig: "test", + }, + }, + { + name: "flags all valid", + args: []string{"my-site"}, + flags: &common.CommandSiteGenerateFlags{ + BindHost: "1.2.3.4", + EnableLinkAccess: true, + SubjectAlternativeNames: []string{"3.3.3.3"}, + }, + expectedErrors: []string{}, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + command := &CmdSiteGenerate{Flags: &common.CommandSiteGenerateFlags{}} + command.CobraCmd = &cobra.Command{Use: "test"} + + if test.flags != nil { + command.Flags = test.flags + } + + if test.cobraGenericFlags != nil && len(test.cobraGenericFlags) > 0 { + for name, value := range test.cobraGenericFlags { + command.CobraCmd.Flags().String(name, value, "") + } + } + + actualErrors := command.ValidateInput(test.args) + + actualErrorsMessages := utils.ErrorsToMessages(actualErrors) + + assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors) + + }) + } +} + +func TestNonKubeCmdSiteGenerate_InputToOptions(t *testing.T) { + + type test struct { + name string + args []string + namespace string + flags common.CommandSiteGenerateFlags + expectedSettings map[string]string + expectedLinkAccess bool + expectedOutput string + expectedNamespace string + expectedBindHost string + expectedSubjectAlternativeNames []string + expectedRouterAccessName string + } + + testTable := []test{ + { + name: "options without link access enabled", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{}, + expectedSettings: map[string]string{ + "name": "my-site", + }, + expectedLinkAccess: false, + expectedOutput: "", + expectedBindHost: "", + expectedRouterAccessName: "", + expectedNamespace: "default", + }, + { + name: "options with link access enabled", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: true, BindHost: "test"}, + expectedSettings: map[string]string{ + "name": "my-site", + }, + expectedLinkAccess: true, + expectedNamespace: "default", + expectedBindHost: "test", + expectedRouterAccessName: "router-access-my-site", + expectedSubjectAlternativeNames: nil, + }, + { + name: "options with subject alternative names", + args: []string{"my-site"}, + namespace: "test", + flags: common.CommandSiteGenerateFlags{BindHost: "1.2.3.4", SubjectAlternativeNames: []string{"test"}}, + expectedSettings: map[string]string{ + "name": "my-site", + }, + expectedLinkAccess: false, + expectedNamespace: "test", + expectedBindHost: "", + expectedSubjectAlternativeNames: nil, + expectedRouterAccessName: "", + }, + { + name: "options with enable link access and subject alternative names", + args: []string{"my-site"}, + namespace: "test", + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: true, BindHost: "1.2.3.4", SubjectAlternativeNames: []string{"test"}}, + expectedSettings: map[string]string{ + "name": "my-site", + }, + expectedLinkAccess: true, + expectedNamespace: "test", + expectedSubjectAlternativeNames: []string{"test"}, + expectedBindHost: "1.2.3.4", + expectedRouterAccessName: "router-access-my-site", + }, + { + name: "options output type", + args: []string{"my-site"}, + flags: common.CommandSiteGenerateFlags{EnableLinkAccess: false, Output: "yaml"}, + expectedSettings: map[string]string{ + "name": "my-site", + }, + expectedLinkAccess: false, + expectedOutput: "yaml", + expectedNamespace: "default", + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + cmd := CmdSiteGenerate{} + cmd.Flags = &test.flags + cmd.siteName = "my-site" + cmd.namespace = test.namespace + cmd.siteHandler = fs2.NewSiteHandler(cmd.namespace) + cmd.routerAccessHandler = fs2.NewRouterAccessHandler(cmd.namespace) + + cmd.InputToOptions() + + assert.DeepEqual(t, cmd.options, test.expectedSettings) + + assert.Check(t, cmd.output == test.expectedOutput) + assert.DeepEqual(t, cmd.options, test.expectedSettings) + assert.Check(t, cmd.namespace == test.expectedNamespace) + assert.Check(t, cmd.bindHost == test.expectedBindHost) + assert.Check(t, cmd.linkAccessEnabled == test.expectedLinkAccess) + assert.Check(t, cmd.routerAccessName == test.expectedRouterAccessName) + assert.DeepEqual(t, cmd.subjectAlternativeNames, test.expectedSubjectAlternativeNames) + }) + } +} + +func TestNonKubeCmdSiteGenerate_Run(t *testing.T) { + type test struct { + name string + k8sObjects []runtime.Object + skupperObjects []runtime.Object + skupperError string + siteName string + host string + options map[string]string + output string + errorMessage string + routerAccessName string + linkAccessEnabled bool + } + + testTable := []test{ + { + name: "runs ok", + k8sObjects: nil, + skupperObjects: nil, + siteName: "my-site", + host: "bindHost", + routerAccessName: "ra-test", + linkAccessEnabled: true, + options: map[string]string{"name": "my-site"}, + output: "json", + }, + { + name: "runs ok with yaml output", + k8sObjects: nil, + skupperObjects: nil, + siteName: "test", + host: "bindhost", + options: map[string]string{"name": "my-site"}, + output: "yaml", + skupperError: "", + }, + { + name: "runs fails because the output format is not supported", + k8sObjects: nil, + skupperObjects: nil, + siteName: "test", + host: "bindhost", + options: map[string]string{"name": "my-site"}, + output: "unsupported", + skupperError: "", + errorMessage: "format unsupported not supported", + }, + } + + for _, test := range testTable { + command := &CmdSiteGenerate{} + + command.siteName = test.siteName + command.options = test.options + command.output = test.output + command.routerAccessName = test.routerAccessName + command.linkAccessEnabled = test.linkAccessEnabled + command.siteHandler = fs2.NewSiteHandler(command.namespace) + command.routerAccessHandler = fs2.NewRouterAccessHandler(command.namespace) + defer command.siteHandler.Delete("my-site") + defer command.routerAccessHandler.Delete("my-site") + t.Run(test.name, func(t *testing.T) { + + err := command.Run() + if err != nil { + assert.Check(t, test.errorMessage == err.Error(), err.Error()) + } else { + assert.Check(t, err == nil) + } + }) + } +} diff --git a/internal/cmd/skupper/site/nonkube/site_update.go b/internal/cmd/skupper/site/nonkube/site_update.go index 088007830..21479599a 100644 --- a/internal/cmd/skupper/site/nonkube/site_update.go +++ b/internal/cmd/skupper/site/nonkube/site_update.go @@ -5,7 +5,6 @@ import ( "net" "github.com/skupperproject/skupper/internal/cmd/skupper/common" - "github.com/skupperproject/skupper/internal/cmd/skupper/common/utils" "github.com/skupperproject/skupper/internal/nonkube/client/fs" "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1" "github.com/skupperproject/skupper/pkg/utils/validator" @@ -29,7 +28,6 @@ type CmdSiteUpdate struct { namespace string linkAccessEnabled bool bindHost string - output string routerAccessName string subjectAlternativeNames []string newSettings SiteUpdates @@ -52,7 +50,6 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error { var validationErrors []error opts := fs.GetOptions{RuntimeFirst: false, LogWarning: false} resourceStringValidator := validator.NewResourceStringValidator() - outputTypeValidator := validator.NewOptionValidator(common.OutputTypes) hostStringValidator := validator.NewHostStringValidator() if cmd.Flags.ServiceAccount != "" { @@ -124,12 +121,6 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error { } } } - if cmd.Flags != nil && cmd.Flags.Output != "" { - ok, err := outputTypeValidator.Evaluate(cmd.Flags.Output) - if !ok { - validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err)) - } - } return validationErrors } @@ -157,7 +148,6 @@ func (cmd *CmdSiteUpdate) InputToOptions() { options[common.SiteConfigNameKey] = cmd.siteName cmd.options = options - cmd.output = cmd.Flags.Output cmd.routerAccessName = "router-access-" + cmd.siteName if cmd.namespace == "" { @@ -206,39 +196,22 @@ func (cmd *CmdSiteUpdate) Run() error { }, } - if cmd.output != "" { - encodedSiteOutput, err := utils.Encode(cmd.output, siteResource) + err := cmd.siteHandler.Add(siteResource) + if err != nil { + return err + } + + if cmd.linkAccessEnabled == true { + err = cmd.routerAccessHandler.Add(routerAccessResource) if err != nil { return err } - fmt.Println(encodedSiteOutput) - if cmd.linkAccessEnabled == true { - fmt.Println("---") - encodedRouterAccessOutput, err := utils.Encode(cmd.output, routerAccessResource) - if err != nil { - return err - } - fmt.Println(encodedRouterAccessOutput) - } - return err } else { - err := cmd.siteHandler.Add(siteResource) + fmt.Println("link access not enabled, router access removed", cmd.routerAccessName) + err = cmd.routerAccessHandler.Delete(cmd.routerAccessName) if err != nil { return err } - - if cmd.linkAccessEnabled == true { - err = cmd.routerAccessHandler.Add(routerAccessResource) - if err != nil { - return err - } - } else { - fmt.Println("link access not enabled, router access removed", cmd.routerAccessName) - err = cmd.routerAccessHandler.Delete(cmd.routerAccessName) - if err != nil { - return err - } - } } return nil diff --git a/internal/cmd/skupper/site/nonkube/site_update_test.go b/internal/cmd/skupper/site/nonkube/site_update_test.go index 05ef5d5ca..811c92c72 100644 --- a/internal/cmd/skupper/site/nonkube/site_update_test.go +++ b/internal/cmd/skupper/site/nonkube/site_update_test.go @@ -74,12 +74,6 @@ func TestCmdSiteUpdate_ValidateInput(t *testing.T) { flags: &common.CommandSiteUpdateFlags{SubjectAlternativeNames: []string{"not-valid$"}}, expectedErrors: []string{"SubjectAlternativeNames are not valid: a valid IP address or hostname is expected"}, }, - { - name: "output is not valid", - args: []string{"my-site"}, - flags: &common.CommandSiteUpdateFlags{Output: "not-supported"}, - expectedErrors: []string{"output type is not valid: value not-supported not allowed. It should be one of this options: [json yaml]"}, - }, { name: "kubernetes flags are not valid on this platform", args: []string{"my-site"}, @@ -94,7 +88,6 @@ func TestCmdSiteUpdate_ValidateInput(t *testing.T) { name: "flags all valid", args: []string{"my-site"}, flags: &common.CommandSiteUpdateFlags{ - Output: "json", BindHost: "1.2.3.4", EnableLinkAccess: true, SubjectAlternativeNames: []string{"3.3.3.3"}, @@ -313,7 +306,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { BindHost: "1.2.3.4", EnableLinkAccess: true, SubjectAlternativeNames: []string{"2.2.2.2", "test", "5.6.7.8"}, - Output: "json", }, linkAccessEnabled: true, }, @@ -322,7 +314,6 @@ func TestCmdSiteUpdate_Run(t *testing.T) { siteName: "my-site", flags: common.CommandSiteUpdateFlags{ EnableLinkAccess: false, - Output: "yaml", }, }, } diff --git a/internal/cmd/skupper/site/site.go b/internal/cmd/skupper/site/site.go index 67984af84..753181ffb 100644 --- a/internal/cmd/skupper/site/site.go +++ b/internal/cmd/skupper/site/site.go @@ -28,6 +28,7 @@ skupper site status`, cmd.AddCommand(CmdSiteStatusFactory(config.GetPlatform())) cmd.AddCommand(CmdSiteDeleteFactory(config.GetPlatform())) cmd.AddCommand(CmdSiteUpdateFactory(config.GetPlatform())) + cmd.AddCommand(CmdSiteGenerateFactory(config.GetPlatform())) return cmd } @@ -51,7 +52,6 @@ There can be only one site definition per namespace.`, cmd.Flags().BoolVar(&cmdFlags.EnableLinkAccess, common.FlagNameEnableLinkAccess, false, common.FlagDescEnableLinkAccess) cmd.Flags().StringVar(&cmdFlags.LinkAccessType, common.FlagNameLinkAccessType, "", common.FlagDescLinkAccessType) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().StringVar(&cmdFlags.ServiceAccount, common.FlagNameServiceAccount, "", common.FlagDescServiceAccount) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 30*time.Second, common.FlagDescTimeout) cmd.Flags().StringVar(&cmdFlags.BindHost, common.FlagNameBindHost, "0.0.0.0", common.FlagDescBindHost) @@ -91,7 +91,6 @@ func CmdSiteUpdateFactory(configuredPlatform types.Platform) *cobra.Command { cmd.Flags().BoolVar(&cmdFlags.EnableLinkAccess, common.FlagNameEnableLinkAccess, false, common.FlagDescEnableLinkAccess) cmd.Flags().StringVar(&cmdFlags.LinkAccessType, common.FlagNameLinkAccessType, "", common.FlagDescLinkAccessType) - cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput) cmd.Flags().StringVar(&cmdFlags.ServiceAccount, common.FlagNameServiceAccount, "", common.FlagDescServiceAccount) cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 30*time.Second, common.FlagDescTimeout) cmd.Flags().StringVar(&cmdFlags.BindHost, common.FlagNameBindHost, "", common.FlagDescBindHost) @@ -168,3 +167,43 @@ skupper site delete --wait=false`, return cmd } + +func CmdSiteGenerateFactory(configuredPlatform types.Platform) *cobra.Command { + kubeCommand := kube.NewCmdSiteGenerate() + nonKubeCommand := nonkube.NewCmdSiteGenerate() + + cmdSiteGenerateDesc := common.SkupperCmdDescription{ + Use: "generate ", + Short: "Generate a site resource and output it to a file or screen", + Long: `A site is a place where components of your application are running. +Sites are linked to form application networks. +There can be only one site definition per namespace. +Generate a site resource to evaluate what will be created with the site create command`, + } + + cmd := common.ConfigureCobraCommand(configuredPlatform, cmdSiteGenerateDesc, kubeCommand, nonKubeCommand) + + cmdFlags := common.CommandSiteGenerateFlags{} + + cmd.Flags().BoolVar(&cmdFlags.EnableLinkAccess, common.FlagNameEnableLinkAccess, false, common.FlagDescEnableLinkAccess) + cmd.Flags().StringVar(&cmdFlags.LinkAccessType, common.FlagNameLinkAccessType, "", common.FlagDescLinkAccessType) + cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "yaml", common.FlagDescOutput) + cmd.Flags().StringVar(&cmdFlags.ServiceAccount, common.FlagNameServiceAccount, "", common.FlagDescServiceAccount) + cmd.Flags().StringVar(&cmdFlags.BindHost, common.FlagNameBindHost, "0.0.0.0", common.FlagDescBindHost) + cmd.Flags().StringSliceVar(&cmdFlags.SubjectAlternativeNames, common.FlagNameSubjectAlternativeNames, []string{}, common.FlagDescSubjectAlternativeNames) + + kubeCommand.CobraCmd = cmd + kubeCommand.Flags = &cmdFlags + nonKubeCommand.CobraCmd = cmd + nonKubeCommand.Flags = &cmdFlags + + if configuredPlatform == types.PlatformKubernetes { + cmd.Flags().MarkHidden(common.FlagNameBindHost) + cmd.Flags().MarkHidden(common.FlagNameSubjectAlternativeNames) + } else { + cmd.Flags().MarkHidden(common.FlagNameServiceAccount) + } + + return cmd + +} diff --git a/internal/cmd/skupper/site/site_test.go b/internal/cmd/skupper/site/site_test.go index f3b9f93ad..b2ccb00e7 100644 --- a/internal/cmd/skupper/site/site_test.go +++ b/internal/cmd/skupper/site/site_test.go @@ -25,7 +25,6 @@ func TestCmdSiteFactory(t *testing.T) { common.FlagNameEnableLinkAccess: "false", common.FlagNameLinkAccessType: "", common.FlagNameServiceAccount: "", - common.FlagNameOutput: "", common.FlagNameTimeout: "30s", common.FlagNameBindHost: "0.0.0.0", common.FlagNameSubjectAlternativeNames: "[]", @@ -39,7 +38,6 @@ func TestCmdSiteFactory(t *testing.T) { common.FlagNameEnableLinkAccess: "false", common.FlagNameLinkAccessType: "", common.FlagNameServiceAccount: "", - common.FlagNameOutput: "", common.FlagNameBindHost: "", common.FlagNameTimeout: "30s", common.FlagNameSubjectAlternativeNames: "[]", @@ -63,6 +61,18 @@ func TestCmdSiteFactory(t *testing.T) { }, command: CmdSiteStatusFactory(types.PlatformKubernetes), }, + { + name: "CmdSiteGenerateFactory", + expectedFlagsWithDefaultValue: map[string]interface{}{ + common.FlagNameEnableLinkAccess: "false", + common.FlagNameLinkAccessType: "", + common.FlagNameServiceAccount: "", + common.FlagNameOutput: "yaml", + common.FlagNameBindHost: "0.0.0.0", + common.FlagNameSubjectAlternativeNames: "[]", + }, + command: CmdSiteGenerateFactory(types.PlatformKubernetes), + }, } for _, test := range testTable {