Repository: skupperproject/skupper
Pull request #1713 (2024-10-11 12:43:40)

V2 nonkube connector CLI
Add Connector CLI commands for nonkube implementation
includes create, update status and delete.

lynnemorrison: @nluaces For unit tests I created a dummy test file and then deleted it after test.   Want to verify this is acceptable.
Also need to evaluate names for yaml encoding functions in custome_resource_handler.go
nluaces: 
> timeout flag is specified in refdog for nonkube but not sure how that is used.
The timeout flag should be not used for now, giving that there is no controller for non kube environments. I suggest to hide it for the non_kube implementations.

Regarding the custom resource handler interface, remember to write implementations (empty if it is necessary) to all the resources that implement the interface (i.e. if you implement the List method for connectors, you also need to create the List method in sites)


Patch:
diff --git a/internal/cmd/skupper/common/constants.go b/internal/cmd/skupper/common/constants.go
index 4e9e31e7..06f77244 100644
--- a/internal/cmd/skupper/common/constants.go
+++ b/internal/cmd/skupper/common/constants.go
@@ -7,3 +7,10 @@ var (
 	ConnectorTypes  = []string{"tcp"}
 	WorkloadTypes   = []string{"deployment", "service", "daemonset", "statefulset"}
 )
+
+const (
+	Connectors     string = "connectors"
+	Listeners      string = "listeners"
+	Sites          string = "sites"
+	RouterAccesses string = "routerAccesses"
+)
diff --git a/internal/cmd/skupper/connector/kube/connector_update.go b/internal/cmd/skupper/connector/kube/connector_update.go
index 7eff05f4..62b6147b 100644
--- a/internal/cmd/skupper/connector/kube/connector_update.go
+++ b/internal/cmd/skupper/connector/kube/connector_update.go
@@ -99,7 +99,6 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error {
 			cmd.newSettings.connectorType = connector.Spec.Type
 			cmd.newSettings.includeNotReady = connector.Spec.IncludeNotReady
 			cmd.newSettings.routingKey = connector.Spec.RoutingKey
-
 			cmd.existingHost = connector.Spec.Host
 			cmd.existingSelector = connector.Spec.Selector
 		}
diff --git a/internal/cmd/skupper/connector/nonkube/connector_create.go b/internal/cmd/skupper/connector/nonkube/connector_create.go
index 774d3c33..f3dcd4bc 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_create.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_create.go
@@ -2,15 +2,30 @@ 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 CmdConnectorCreate struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandConnectorCreateFlags
-	Namespace string
-	siteName  string
+	connectorHandler *fs.ConnectorHandler
+	CobraCmd         *cobra.Command
+	Flags            *common.CommandConnectorCreateFlags
+	namespace        string
+	connectorName    string
+	port             int
+	output           string
+	host             string
+	routingKey       string
+	connectorType    string
+	tlsSecret        string
 }
 
 func NewCmdConnectorCreate() *CmdConnectorCreate {
@@ -18,12 +33,139 @@ func NewCmdConnectorCreate() *CmdConnectorCreate {
 }
 
 func (cmd *CmdConnectorCreate) NewClient(cobraCommand *cobra.Command, args []string) {
-	//TODO
+	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 *CmdConnectorCreate) 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.TlsSecret != "" {
+		// TBD what is valid TlsSecret
+	}
+
+	return validationErrors
+}
+
+func (cmd *CmdConnectorCreate) 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.output = cmd.Flags.Output
+	cmd.tlsSecret = cmd.Flags.TlsSecret
 }
 
-func (cmd *CmdConnectorCreate) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdConnectorCreate) InputToOptions()                     {}
 func (cmd *CmdConnectorCreate) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	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.tlsSecret,
+			Type:           cmd.connectorType,
+		},
+	}
+
+	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
 }
+
 func (cmd *CmdConnectorCreate) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/connector/nonkube/connector_delete.go b/internal/cmd/skupper/connector/nonkube/connector_delete.go
index 339b3d9f..eea74970 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_delete.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_delete.go
@@ -2,15 +2,19 @@ package nonkube
 
 import (
 	"fmt"
+
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
+	"github.com/skupperproject/skupper/internal/nonkube/client/fs"
+	"github.com/skupperproject/skupper/pkg/utils/validator"
 	"github.com/spf13/cobra"
 )
 
 type CmdConnectorDelete struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandConnectorDeleteFlags
-	Namespace string
-	siteName  string
+	connectorHandler *fs.ConnectorHandler
+	CobraCmd         *cobra.Command
+	Flags            *common.CommandConnectorDeleteFlags
+	namespace        string
+	connectorName    string
 }
 
 func NewCmdConnectorDelete() *CmdConnectorDelete {
@@ -18,12 +22,56 @@ func NewCmdConnectorDelete() *CmdConnectorDelete {
 }
 
 func (cmd *CmdConnectorDelete) NewClient(cobraCommand *cobra.Command, args []string) {
-	//TODO
+	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 *CmdConnectorDelete) ValidateInput(args []string) []error {
+	var validationErrors []error
+	resourceStringValidator := validator.NewResourceStringValidator()
+
+	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")
+	}
+
+	// Validate arguments name
+	if len(args) < 1 {
+		validationErrors = append(validationErrors, fmt.Errorf("connector name must be configured"))
+	} else if len(args) > 1 {
+		validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command"))
+	} else if args[0] == "" {
+		validationErrors = append(validationErrors, fmt.Errorf("connector name 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]
+		}
+	}
+
+	return validationErrors
+}
+
+func (cmd *CmdConnectorDelete) InputToOptions() {
+	if cmd.namespace == "" {
+		cmd.namespace = "default"
+	}
 }
 
-func (cmd *CmdConnectorDelete) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdConnectorDelete) InputToOptions()                     {}
 func (cmd *CmdConnectorDelete) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	err := cmd.connectorHandler.Delete(cmd.connectorName)
+	if err != nil {
+		return err
+	}
+	return nil
 }
+
 func (cmd *CmdConnectorDelete) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/connector/nonkube/connector_status.go b/internal/cmd/skupper/connector/nonkube/connector_status.go
index cc391e8b..7ea8f487 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_status.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_status.go
@@ -2,15 +2,23 @@ package nonkube
 
 import (
 	"fmt"
+	"os"
+	"text/tabwriter"
+
 	"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/utils/validator"
 	"github.com/spf13/cobra"
 )
 
 type CmdConnectorStatus struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandConnectorStatusFlags
-	Namespace string
-	siteName  string
+	connectorHandler *fs.ConnectorHandler
+	CobraCmd         *cobra.Command
+	Flags            *common.CommandConnectorStatusFlags
+	namespace        string
+	connectorName    string
+	output           string
 }
 
 func NewCmdConnectorStatus() *CmdConnectorStatus {
@@ -18,12 +26,107 @@ func NewCmdConnectorStatus() *CmdConnectorStatus {
 }
 
 func (cmd *CmdConnectorStatus) NewClient(cobraCommand *cobra.Command, args []string) {
-	//TODO
+	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 *CmdConnectorStatus) ValidateInput(args []string) []error {
+	var validationErrors []error
+	resourceStringValidator := validator.NewResourceStringValidator()
+	outputTypeValidator := validator.NewOptionValidator(common.OutputTypes)
+
+	// Validate arguments name if specified
+	if len(args) > 1 {
+		validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command"))
+	} else if len(args) == 1 {
+		if args[0] == "" {
+			validationErrors = append(validationErrors, fmt.Errorf("connector name 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]
+			}
+		}
+	}
+	// Validate that there is a connector with this name in the namespace
+	if cmd.connectorName != "" {
+		connector, err := cmd.connectorHandler.Get(cmd.connectorName)
+		if connector == nil || err != nil {
+			validationErrors = append(validationErrors, fmt.Errorf("connector %s does not exist in namespace %s", cmd.connectorName, cmd.namespace))
+		}
+	}
+
+	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
+		}
+	}
+
+	return validationErrors
 }
 
-func (cmd *CmdConnectorStatus) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdConnectorStatus) InputToOptions()                     {}
 func (cmd *CmdConnectorStatus) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	if cmd.connectorName == "" {
+		connectors, err := cmd.connectorHandler.List()
+		if connectors == nil || err != nil {
+			fmt.Println("No connectors found:")
+			return err
+		}
+		if cmd.output != "" {
+			for _, connector := range connectors {
+				encodedOutput, err := utils.Encode(cmd.output, connector)
+				if err != nil {
+					return err
+				}
+				fmt.Println(encodedOutput)
+			}
+		} else {
+			tw := tabwriter.NewWriter(os.Stdout, 8, 8, 1, '\t', tabwriter.TabIndent)
+			_, _ = fmt.Fprintln(tw, fmt.Sprintf("%s\t%s\t%s\t%s\t%s",
+				"NAME", "STATUS", "ROUTING-KEY", "HOST", "PORT"))
+			for _, connector := range connectors {
+				status := "Not Ready"
+				if connector.IsConfigured() {
+					status = "Ok"
+				}
+				fmt.Fprintln(tw, fmt.Sprintf("%s\t%s\t%s\t%s\t%d",
+					connector.Name, status, connector.Spec.RoutingKey, connector.Spec.Host, connector.Spec.Port))
+			}
+			_ = tw.Flush()
+		}
+	} else {
+		connector, err := cmd.connectorHandler.Get(cmd.connectorName)
+		if connector == nil || err != nil {
+			fmt.Println("No connectors found:")
+			return err
+		}
+		if cmd.output != "" {
+			encodedOutput, err := utils.Encode(cmd.output, connector)
+			if err != nil {
+				return err
+			}
+			fmt.Println(encodedOutput)
+		} else {
+			status := "Not Ready"
+			if connector.IsConfigured() {
+				status = "Ok"
+			}
+			tw := tabwriter.NewWriter(os.Stdout, 8, 8, 1, '\t', tabwriter.TabIndent)
+			fmt.Fprintln(tw, fmt.Sprintf("Name:\t%s\nStatus:\t%s\nRouting key:\t%s\nHost:\t%s\nPort:\t%d\n",
+				connector.Name, status, connector.Spec.RoutingKey, connector.Spec.Host, connector.Spec.Port))
+			_ = tw.Flush()
+		}
+	}
+	return nil
 }
+
+func (cmd *CmdConnectorStatus) InputToOptions()  {}
 func (cmd *CmdConnectorStatus) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/connector/nonkube/connector_update.go b/internal/cmd/skupper/connector/nonkube/connector_update.go
index 6e695067..e6044b63 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_update.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_update.go
@@ -2,15 +2,32 @@ 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 ConnectorUpdates struct {
+	routingKey    string
+	host          string
+	connectorType string
+	port          int
+	tlsSecret     string
+}
 type CmdConnectorUpdate struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandConnectorUpdateFlags
-	Namespace string
-	siteName  string
+	connectorHandler *fs.ConnectorHandler
+	CobraCmd         *cobra.Command
+	Flags            *common.CommandConnectorUpdateFlags
+	namespace        string
+	connectorName    string
+	newSettings      ConnectorUpdates
+	output           string
 }
 
 func NewCmdConnectorUpdate() *CmdConnectorUpdate {
@@ -18,12 +35,142 @@ func NewCmdConnectorUpdate() *CmdConnectorUpdate {
 }
 
 func (cmd *CmdConnectorUpdate) NewClient(cobraCommand *cobra.Command, args []string) {
-	//TODO
+	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 *CmdConnectorUpdate) ValidateInput(args []string) []error {
+	var validationErrors []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() != "" {
+		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")
+	}
+
+	// Validate arguments name
+	if len(args) < 1 {
+		validationErrors = append(validationErrors, fmt.Errorf("connector name must be configured"))
+	} else if len(args) > 1 {
+		validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command"))
+	} else if args[0] == "" {
+		validationErrors = append(validationErrors, fmt.Errorf("connector name 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]
+		}
+	}
+
+	// Validate that there is already a connector with this name in the namespace
+	if cmd.connectorName != "" {
+		connector, err := cmd.connectorHandler.Update(cmd.connectorName)
+		if connector == nil || err != nil {
+			validationErrors = append(validationErrors, fmt.Errorf("connector %s must exist in namespace %s to be updated", cmd.connectorName, cmd.namespace))
+		} else {
+			// save existing values
+			cmd.newSettings.host = connector.Spec.Host
+			cmd.newSettings.port = connector.Spec.Port
+			cmd.newSettings.connectorType = connector.Spec.Type
+			cmd.newSettings.tlsSecret = connector.Spec.TlsCredentials
+			cmd.newSettings.routingKey = connector.Spec.RoutingKey
+		}
+	}
+
+	// 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))
+		} else {
+			cmd.newSettings.routingKey = cmd.Flags.RoutingKey
+		}
+	}
+	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))
+		} else {
+			cmd.newSettings.connectorType = cmd.Flags.ConnectorType
+		}
+	}
+	if cmd.Flags.Port != 0 {
+		ok, err := numberValidator.Evaluate(cmd.Flags.Port)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("connector port is not valid: %s", err))
+		} else {
+			cmd.newSettings.port = cmd.Flags.Port
+		}
+	}
+	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 {
+			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
+		}
+	}
+
+	return validationErrors
+}
+
+func (cmd *CmdConnectorUpdate) InputToOptions() {
+	if cmd.namespace == "" {
+		cmd.namespace = "default"
+	}
 }
 
-func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdConnectorUpdate) InputToOptions()                     {}
 func (cmd *CmdConnectorUpdate) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	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.newSettings.host,
+			Port:           cmd.newSettings.port,
+			RoutingKey:     cmd.newSettings.routingKey,
+			TlsCredentials: cmd.newSettings.tlsSecret,
+			Type:           cmd.newSettings.connectorType,
+		},
+	}
+
+	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
 }
+
 func (cmd *CmdConnectorUpdate) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/site/site.go b/internal/cmd/skupper/site/site.go
index 56dcd3d5..28810cb6 100644
--- a/internal/cmd/skupper/site/site.go
+++ b/internal/cmd/skupper/site/site.go
@@ -4,13 +4,14 @@ Copyright © 2024 Skupper Team <skupper@googlegroups.com>
 package site
 
 import (
+	"time"
+
 	"github.com/skupperproject/skupper/api/types"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/site/kube"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/site/nonkube"
 	"github.com/skupperproject/skupper/pkg/config"
 	"github.com/spf13/cobra"
-	"time"
 )
 
 func NewCmdSite() *cobra.Command {
diff --git a/internal/nonkube/client/fs/connector_handler.go b/internal/nonkube/client/fs/connector_handler.go
new file mode 100644
index 00000000..bceaec6c
--- /dev/null
+++ b/internal/nonkube/client/fs/connector_handler.go
@@ -0,0 +1,120 @@
+package fs
+
+import (
+	"errors"
+	"fmt"
+	"io/fs"
+
+	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+)
+
+type ConnectorHandler struct {
+	BaseCustomResourceHandler
+	pathProvider PathProvider
+}
+
+func NewConnectorHandler(namespace string) *ConnectorHandler {
+	return &ConnectorHandler{
+		pathProvider: PathProvider{
+			Namespace: namespace,
+		},
+	}
+}
+
+func (s *ConnectorHandler) Add(resource v2alpha1.Connector) error {
+
+	fileName := resource.Name + ".yaml"
+	content, err := s.EncodeToYaml(resource)
+	if err != nil {
+		return err
+	}
+
+	err = s.WriteFile(s.pathProvider.GetNamespace(), fileName, content, common.Connectors)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *ConnectorHandler) Get(name string) (*v2alpha1.Connector, error) {
+	var context v2alpha1.Connector
+	fileName := name + ".yaml"
+
+	// First read from runtime directory, where output is found after bootstrap
+	// has run.  If no runtime connectors try and display configured connectors
+	err, file := s.ReadFile(s.pathProvider.GetRuntimeNamespace(), fileName, common.Connectors)
+	if err != nil {
+		fmt.Println("Site not initialized yet")
+		err, file = s.ReadFile(s.pathProvider.GetNamespace(), fileName, common.Connectors)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if err = s.DecodeYaml(file, &context); err != nil {
+		return nil, err
+	}
+
+	return &context, nil
+}
+
+func (s *ConnectorHandler) Delete(name string) error {
+	fileName := name + ".yaml"
+
+	if err := s.DeleteFile(s.pathProvider.GetNamespace(), fileName, common.Connectors); err != nil {
+		if !errors.Is(err, fs.ErrNotExist) {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (s *ConnectorHandler) List() ([]*v2alpha1.Connector, error) {
+	var connectors []*v2alpha1.Connector
+
+	// First read from runtime directory, where output is found after bootstrap
+	// has run.  If no runtime connectors try and display configured connectors
+	path := s.pathProvider.GetRuntimeNamespace()
+	err, files := s.ReadDir(path, common.Connectors)
+	if err != nil {
+		fmt.Println("Site not initialized yet")
+		path = s.pathProvider.GetNamespace()
+		err, files = s.ReadDir(path, common.Connectors)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	for _, file := range files {
+		err, connector := s.ReadFile(path, file.Name(), common.Connectors)
+		if err != nil {
+			return nil, err
+		}
+		var context v2alpha1.Connector
+		if err = s.DecodeYaml(connector, &context); err != nil {
+			return nil, err
+		}
+		connectors = append(connectors, &context)
+	}
+	return connectors, nil
+}
+
+func (s *ConnectorHandler) Update(name string) (*v2alpha1.Connector, error) {
+	var context v2alpha1.Connector
+	fileName := name + ".yaml"
+
+	// read from input directory to get lastest config
+	err, file := s.ReadFile(s.pathProvider.GetNamespace(), fileName, common.Connectors)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = s.DecodeYaml(file, &context); err != nil {
+		return nil, err
+	}
+
+	return &context, nil
+}
diff --git a/internal/nonkube/client/fs/custom_resource_handler.go b/internal/nonkube/client/fs/custom_resource_handler.go
index 630cc768..22d76d1b 100644
--- a/internal/nonkube/client/fs/custom_resource_handler.go
+++ b/internal/nonkube/client/fs/custom_resource_handler.go
@@ -2,20 +2,29 @@ package fs
 
 import (
 	"fmt"
-	"github.com/skupperproject/skupper/internal/cmd/skupper/common/utils"
+	"io/fs"
 	"os"
 	"path/filepath"
+
+	"github.com/skupperproject/skupper/internal/cmd/skupper/common/utils"
+
+	"sigs.k8s.io/yaml"
 )
 
 type CustomResourceHandler[T any] interface {
 	Add(T) error
 	Update(T) error
-	Get(name string) T
+	Get(name string) (T, error)
+	List() ([]T, error)
 	Delete(name string) error
 
 	//Common methods
 	EncodeToYaml(resource interface{}) (string, error)
 	WriteFile(path string, name string, content string, kind string) error
+	DecodeYaml(content []byte, resource interface{}) (interface{}, error)
+	ReadFile(path string, name string, kind string) (error, []byte)
+	DeleteFile(path string, name string, kind string) error
+	ReadDir(path string, kind string) (error, []fs.DirEntry)
 }
 
 type BaseCustomResourceHandler struct{}
@@ -25,19 +34,21 @@ func (b *BaseCustomResourceHandler) EncodeToYaml(resource interface{}) (string,
 	return utils.Encode("yaml", resource)
 }
 
-func (b *BaseCustomResourceHandler) WriteFile(path string, name string, content string, kind string) error {
+func (b *BaseCustomResourceHandler) DecodeYaml(content []byte, resource interface{}) error {
 
-	// Resolve the home directory
-	homeDir, err := os.UserHomeDir()
-	if err != nil {
+	if err := yaml.Unmarshal(content, &resource); err != nil {
 		return err
 	}
+	return nil
+}
 
-	fullPath := filepath.Join(homeDir, path, kind)
+func (b *BaseCustomResourceHandler) WriteFile(path string, name string, content string, kind string) error {
+
+	fullPath := filepath.Join(path, kind)
 	completeFilePath := filepath.Join(fullPath, name)
 
 	// Create the directories recursively
-	err = os.MkdirAll(fullPath, 0775)
+	err := os.MkdirAll(fullPath, 0775)
 	if err != nil {
 		return fmt.Errorf("failed to create directories: %s", err)
 	}
@@ -59,3 +70,41 @@ func (b *BaseCustomResourceHandler) WriteFile(path string, name string, content
 
 	return nil
 }
+
+func (b *BaseCustomResourceHandler) ReadFile(path string, name string, kind string) (error, []byte) {
+
+	fullPath := filepath.Join(path, kind)
+	completeFilePath := filepath.Join(fullPath, name)
+
+	file, err := os.ReadFile(completeFilePath)
+	if err != nil {
+		return fmt.Errorf("failed to read file: %s", err), nil
+	}
+
+	return nil, file
+}
+
+func (b *BaseCustomResourceHandler) DeleteFile(path string, name string, kind string) error {
+	var completeFilePath string
+
+	fullPath := filepath.Join(path, kind)
+	completeFilePath = filepath.Join(fullPath, name)
+
+	if err := os.RemoveAll(completeFilePath); err != nil {
+		return fmt.Errorf("failed to delete file: %s", err)
+	}
+
+	return nil
+}
+
+func (b *BaseCustomResourceHandler) ReadDir(path string, kind string) (error, []fs.DirEntry) {
+
+	fullPath := filepath.Join(path, kind)
+
+	files, err := os.ReadDir(fullPath)
+	if err != nil {
+		return fmt.Errorf("failed to read directory: %s", err), nil
+	}
+
+	return nil, files
+}
diff --git a/internal/nonkube/client/fs/path_provider.go b/internal/nonkube/client/fs/path_provider.go
index 5851fdf0..a2fdff72 100644
--- a/internal/nonkube/client/fs/path_provider.go
+++ b/internal/nonkube/client/fs/path_provider.go
@@ -1,19 +1,17 @@
 package fs
 
-import "fmt"
+import (
+	"github.com/skupperproject/skupper/pkg/nonkube/api"
+)
 
 type PathProvider struct {
 	Namespace string
 }
 
-func (p *PathProvider) getDefaultNamespace() string {
-	return ".local/share/skupper/namespaces/default/input/sources"
-}
-
 func (p *PathProvider) GetNamespace() string {
+	return api.GetHostNamespaceHome(p.Namespace) + "/" + string(api.InputSiteStatePath)
+}
 
-	if p.Namespace == "" {
-		return p.getDefaultNamespace()
-	}
-	return fmt.Sprintf(".local/share/skupper/namespaces/%s/input/sources", p.Namespace)
+func (p *PathProvider) GetRuntimeNamespace() string {
+	return api.GetHostNamespaceHome(p.Namespace) + "/" + string(api.RuntimeSiteStatePath)
 }
diff --git a/internal/nonkube/client/fs/router_access_handler.go b/internal/nonkube/client/fs/router_access_handler.go
index fcbdd329..8877da45 100644
--- a/internal/nonkube/client/fs/router_access_handler.go
+++ b/internal/nonkube/client/fs/router_access_handler.go
@@ -1,6 +1,9 @@
 package fs
 
-import "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+import (
+	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+)
 
 type RouterAccessHandler struct {
 	BaseCustomResourceHandler
@@ -23,13 +26,14 @@ func (s *RouterAccessHandler) Add(resource v2alpha1.RouterAccess) error {
 		return err
 	}
 
-	err = s.WriteFile(s.pathProvider.GetNamespace(), fileName, content, "routerAccesses")
+	err = s.WriteFile(s.pathProvider.GetNamespace(), fileName, content, common.RouterAccesses)
 	if err != nil {
 		return err
 	}
 
 	return nil
 }
-func (s *RouterAccessHandler) Update(resource v2alpha1.RouterAccess) error { return nil }
-func (s *RouterAccessHandler) Get(name string) *v2alpha1.Site              { return nil }
-func (s *RouterAccessHandler) Delete(name string) error                    { return nil }
+func (s *RouterAccessHandler) Update(name string) (*v2alpha1.RouterAccess, error) { return nil, nil }
+func (s *RouterAccessHandler) Get(name string) (*v2alpha1.RouterAccess, error)    { return nil, nil }
+func (s *RouterAccessHandler) Delete(name string) error                           { return nil }
+func (s *RouterAccessHandler) List() ([]*v2alpha1.RouterAccess, error)            { return nil, nil }
diff --git a/internal/nonkube/client/fs/site_handler.go b/internal/nonkube/client/fs/site_handler.go
index b396fc7a..b465081a 100644
--- a/internal/nonkube/client/fs/site_handler.go
+++ b/internal/nonkube/client/fs/site_handler.go
@@ -1,6 +1,9 @@
 package fs
 
-import "github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+import (
+	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+)
 
 type SiteHandler struct {
 	BaseCustomResourceHandler
@@ -23,13 +26,15 @@ func (s *SiteHandler) Add(resource v2alpha1.Site) error {
 		return err
 	}
 
-	err = s.WriteFile(s.pathProvider.GetNamespace(), fileName, content, "sites")
+	err = s.WriteFile(s.pathProvider.GetNamespace(), fileName, content, common.Sites)
 	if err != nil {
 		return err
 	}
 
 	return nil
 }
-func (s *SiteHandler) Update(resource v2alpha1.Site) error { return nil }
-func (s *SiteHandler) Get(name string) *v2alpha1.Site      { return nil }
-func (s *SiteHandler) Delete(name string) error            { return nil }
+
+func (s *SiteHandler) Update(name string) (*v2alpha1.Site, error) { return nil, nil }
+func (s *SiteHandler) Get(name string) (*v2alpha1.Site, error)    { return nil, nil }
+func (s *SiteHandler) Delete(name string) error                   { return nil }
+func (s *SiteHandler) List() ([]*v2alpha1.Site, error)            { return nil, nil }
diff --git a/pkg/utils/validator/simple_validator.go b/pkg/utils/validator/simple_validator.go
index 427eaddd..1230e57f 100644
--- a/pkg/utils/validator/simple_validator.go
+++ b/pkg/utils/validator/simple_validator.go
@@ -28,6 +28,17 @@ func NewStringValidator() *StringValidator {
 	}
 }
 
+func NewHostStringValidator() *StringValidator {
+	re, err := regexp.Compile(`^[a-z0-9]+([-.]{1}[a-z0-9]+)*$`)
+	if err != nil {
+		fmt.Printf("Error compiling regex: %v", err)
+		return nil
+	}
+	return &StringValidator{
+		Expression: re,
+	}
+}
+
 func NewResourceStringValidator() *StringValidator {
 	re, err := regexp.Compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$")
 	if err != nil {


Test patch:
diff --git a/internal/cmd/skupper/connector/nonkube/connector_create_test.go b/internal/cmd/skupper/connector/nonkube/connector_create_test.go
index 6072c5b0..7b407551 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_create_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_create_test.go
@@ -1,1 +1,293 @@
 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/assert"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestNonKubeCmdConnectorCreate_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		k8sObjects        []runtime.Object
+		skupperObjects    []runtime.Object
+		flags             *common.CommandConnectorCreateFlags
+		cobraGenericFlags map[string]string
+		expectedErrors    []string
+	}
+
+	testTable := []test{
+		{
+			name:           "Connector name and port are not specified",
+			args:           []string{},
+			flags:          &common.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{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.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"},
+			flags:          &common.CommandConnectorCreateFlags{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.CommandConnectorCreateFlags{
+				RoutingKey:    "routingkeyname",
+				TlsSecret:     "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 := &CmdConnectorCreate{Flags: &common.CommandConnectorCreateFlags{}}
+			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 TestNonKubeCmdConnectorCreate_InputToOptions(t *testing.T) {
+
+	type test struct {
+		name                  string
+		args                  []string
+		namespace             string
+		flags                 common.CommandConnectorCreateFlags
+		expectedNamespace     string
+		Connectorname         string
+		expectedTlsSecret     string
+		expectedHost          string
+		expectedRoutingKey    string
+		expectedConnectorType string
+		expectedOutput        string
+	}
+
+	testTable := []test{
+		{
+			name:                  "test1",
+			flags:                 common.CommandConnectorCreateFlags{"backend", "", "", "secret", "tcp", false, "", 0, "json"},
+			expectedTlsSecret:     "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"},
+			expectedTlsSecret:     "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"},
+			expectedTlsSecret:     "secret",
+			expectedHost:          "",
+			expectedRoutingKey:    "my-Connector",
+			expectedConnectorType: "tcp",
+			expectedOutput:        "yaml",
+			expectedNamespace:     "test",
+		},
+	}
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			cmd := CmdConnectorCreate{}
+			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.tlsSecret == test.expectedTlsSecret)
+			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 TestNonKubeCmdConnectorCreate_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
+		tlsSecret      string
+		connectorType  string
+		connectorPort  int
+	}
+
+	testTable := []test{
+		{
+			name:           "runs ok",
+			namespace:      "test",
+			k8sObjects:     nil,
+			skupperObjects: nil,
+			connectorName:  "test1",
+			connectorPort:  8080,
+			connectorType:  "tcp",
+			routingKey:     "keyname",
+			tlsSecret:      "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
+		command.namespace = test.namespace
+		command.connectorHandler = fs.NewConnectorHandler(command.namespace)
+		defer command.connectorHandler.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/connector/nonkube/connector_delete_test.go b/internal/cmd/skupper/connector/nonkube/connector_delete_test.go
index 6072c5b0..521ef67e 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_delete_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_delete_test.go
@@ -1,1 +1,133 @@
 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/assert"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestCmdConnectorDelete_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		flags             *common.CommandConnectorDeleteFlags
+		cobraGenericFlags map[string]string
+		k8sObjects        []runtime.Object
+		skupperObjects    []runtime.Object
+		expectedErrors    []string
+	}
+
+	testTable := []test{
+		{
+			name:           "connector name is not specified",
+			args:           []string{},
+			flags:          &common.CommandConnectorDeleteFlags{},
+			expectedErrors: []string{"connector name must be configured"},
+		},
+		{
+			name:           "connector name is nil",
+			args:           []string{""},
+			flags:          &common.CommandConnectorDeleteFlags{},
+			expectedErrors: []string{"connector name must not be empty"},
+		},
+		{
+			name:           "connector name is not valid",
+			args:           []string{"my name"},
+			flags:          &common.CommandConnectorDeleteFlags{},
+			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:           "more than one argument is specified",
+			args:           []string{"my", "connector"},
+			flags:          &common.CommandConnectorDeleteFlags{},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name:           "kubernetes flags are not valid on this platform",
+			args:           []string{"my-connector"},
+			flags:          &common.CommandConnectorDeleteFlags{},
+			expectedErrors: []string{},
+			cobraGenericFlags: map[string]string{
+				common.FlagNameContext:    "test",
+				common.FlagNameKubeconfig: "test",
+			},
+		},
+	}
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			command := &CmdConnectorDelete{Flags: &common.CommandConnectorDeleteFlags{}}
+			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 TestCmdConnectorDelete_Run(t *testing.T) {
+	type test struct {
+		name                string
+		namespace           string
+		deleteName          string
+		k8sObjects          []runtime.Object
+		skupperObjects      []runtime.Object
+		skupperErrorMessage string
+		errorMessage        string
+	}
+
+	testTable := []test{
+		{
+			name:                "run fails default",
+			deleteName:          "my-connector",
+			skupperErrorMessage: "error",
+			errorMessage:        "error",
+		},
+		{
+			name:                "run fails",
+			namespace:           "test",
+			deleteName:          "my-connector",
+			skupperErrorMessage: "error",
+			errorMessage:        "error",
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := &CmdConnectorDelete{}
+
+		t.Run(test.name, func(t *testing.T) {
+
+			cmd.connectorName = test.deleteName
+			cmd.namespace = test.namespace
+			cmd.connectorHandler = fs.NewConnectorHandler(cmd.namespace)
+			cmd.InputToOptions()
+
+			err := cmd.Run()
+			if err != nil {
+				assert.Check(t, test.errorMessage == err.Error())
+			} else {
+				assert.Check(t, err == nil)
+			}
+		})
+	}
+}
diff --git a/internal/cmd/skupper/connector/nonkube/connector_status_test.go b/internal/cmd/skupper/connector/nonkube/connector_status_test.go
index 6072c5b0..8608284b 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_status_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_status_test.go
@@ -1,1 +1,298 @@
 package nonkube
+
+import (
+	"os"
+	"path/filepath"
+	"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/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+	"github.com/skupperproject/skupper/pkg/nonkube/api"
+	"gotest.tools/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestCmdConnectorStatus_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		flags             *common.CommandConnectorStatusFlags
+		cobraGenericFlags map[string]string
+		k8sObjects        []runtime.Object
+		skupperObjects    []runtime.Object
+		expectedErrors    []string
+	}
+
+	homeDir, err := os.UserHomeDir()
+	assert.Check(t, err == nil)
+	path := filepath.Join(homeDir, "/.local/share/skupper/namespaces/test/", string(api.RuntimeSiteStatePath))
+
+	testTable := []test{
+		{
+			name:           "connector is not shown because connector does not exist in the namespace",
+			args:           []string{"no-connector"},
+			flags:          &common.CommandConnectorStatusFlags{},
+			expectedErrors: []string{"connector no-connector does not exist in namespace test"},
+		},
+		{
+			name:           "connector name is nil",
+			args:           []string{""},
+			flags:          &common.CommandConnectorStatusFlags{},
+			expectedErrors: []string{"connector name must not be empty"},
+		},
+		{
+			name:           "more than one argument is specified",
+			args:           []string{"my", "connector"},
+			flags:          &common.CommandConnectorStatusFlags{},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name:           "connector name is not valid.",
+			args:           []string{"my new connector"},
+			flags:          &common.CommandConnectorStatusFlags{},
+			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:           "no args",
+			flags:          &common.CommandConnectorStatusFlags{},
+			expectedErrors: []string{},
+		},
+		{
+			name:           "bad output status",
+			args:           []string{"my-connector"},
+			flags:          &common.CommandConnectorStatusFlags{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:           "good output status",
+			args:           []string{"my-connector"},
+			flags:          &common.CommandConnectorStatusFlags{Output: "json"},
+			expectedErrors: []string{},
+		},
+	}
+
+	//Add a temp file so connector exists for status tests
+	connectorResource := v2alpha1.Connector{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Connector",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-connector",
+			Namespace: "test",
+		},
+	}
+
+	command := &CmdConnectorStatus{}
+	command.namespace = "test"
+	command.connectorHandler = fs.NewConnectorHandler(command.namespace)
+
+	defer command.connectorHandler.Delete("my-connector")
+	content, err := command.connectorHandler.EncodeToYaml(connectorResource)
+	assert.Check(t, err == nil)
+	err = command.connectorHandler.WriteFile(path, "my-connector.yaml", content, common.Connectors)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			command.connectorName = ""
+
+			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 TestCmdConnectorStatus_Run(t *testing.T) {
+	type test struct {
+		name                string
+		connectorName       string
+		flags               common.CommandConnectorStatusFlags
+		k8sObjects          []runtime.Object
+		skupperObjects      []runtime.Object
+		skupperErrorMessage string
+		errorMessage        string
+	}
+
+	homeDir, err := os.UserHomeDir()
+	assert.Check(t, err == nil)
+	path := filepath.Join(homeDir, "/.local/share/skupper/namespaces/test/", string(api.InputSiteStatePath))
+
+	testTable := []test{
+		{
+			name:          "run fails connector doesn't exist",
+			connectorName: "no-connector",
+			errorMessage:  "failed to read file: open " + path + "/connectors/no-connector.yaml: no such file or directory",
+		},
+		{
+			name:          "runs ok, returns 1 connectors",
+			connectorName: "my-connector",
+		},
+		{
+			name:          "runs ok, returns 1 connectors yaml",
+			connectorName: "my-connector",
+			flags:         common.CommandConnectorStatusFlags{Output: "yaml"},
+		},
+		{
+			name: "runs ok, returns all connectors",
+		},
+		{
+			name:  "runs ok, returns all connectors json",
+			flags: common.CommandConnectorStatusFlags{Output: "json"},
+		},
+		{
+			name:         "runs ok, returns all connectors output bad",
+			flags:        common.CommandConnectorStatusFlags{Output: "bad-value"},
+			errorMessage: "format bad-value not supported",
+		},
+		{
+			name:          "runs ok, returns 1 connectors bad output",
+			connectorName: "my-connector",
+			flags:         common.CommandConnectorStatusFlags{Output: "bad-value"},
+			errorMessage:  "format bad-value not supported",
+		},
+	}
+
+	//Add a temp file so connector exists for status tests
+	connectorResource1 := v2alpha1.Connector{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Connector",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-connector",
+			Namespace: "test",
+		},
+		Spec: v2alpha1.ConnectorSpec{
+			Host:       "1.2.3.4",
+			Port:       8080,
+			RoutingKey: "backend-8080",
+		},
+		Status: v2alpha1.ConnectorStatus{
+			Status: v2alpha1.Status{
+				Conditions: []metav1.Condition{
+					{
+						Type:   "Configured",
+						Status: "True",
+					},
+				},
+			},
+		},
+	}
+	connectorResource2 := v2alpha1.Connector{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Connector",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-connector2",
+			Namespace: "test",
+		},
+		Spec: v2alpha1.ConnectorSpec{
+			Host:       "1.1.1.1",
+			Port:       9999,
+			RoutingKey: "test-9999",
+		},
+		Status: v2alpha1.ConnectorStatus{
+			Status: v2alpha1.Status{
+				Conditions: []metav1.Condition{
+					{
+						Type:   "Configured",
+						Status: "True",
+					},
+				},
+			},
+		},
+	}
+
+	// add two connectors in runtime directory
+	command := &CmdConnectorStatus{}
+	command.namespace = "test"
+	command.connectorHandler = fs.NewConnectorHandler(command.namespace)
+
+	defer command.connectorHandler.Delete("my-connector")
+	defer command.connectorHandler.Delete("my-connector2")
+
+	path = filepath.Join(homeDir, "/.local/share/skupper/namespaces/test/", string(api.RuntimeSiteStatePath))
+	content, err := command.connectorHandler.EncodeToYaml(connectorResource1)
+	assert.Check(t, err == nil)
+	err = command.connectorHandler.WriteFile(path, "my-connector.yaml", content, common.Connectors)
+	assert.Check(t, err == nil)
+
+	content, err = command.connectorHandler.EncodeToYaml(connectorResource2)
+	assert.Check(t, err == nil)
+	err = command.connectorHandler.WriteFile(path, "my-connector2.yaml", content, common.Connectors)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		command.connectorName = test.connectorName
+		command.Flags = &test.flags
+		command.output = command.Flags.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)
+			}
+		})
+	}
+}
+
+func TestCmdConnectorStatus_RunNoDirectory(t *testing.T) {
+	type test struct {
+		name                string
+		flags               common.CommandConnectorStatusFlags
+		k8sObjects          []runtime.Object
+		skupperObjects      []runtime.Object
+		skupperErrorMessage string
+		errorMessage        string
+	}
+
+	homeDir, err := os.UserHomeDir()
+	assert.Check(t, err == nil)
+	path := filepath.Join(homeDir, "/.local/share/skupper/namespaces/test1/", string(api.InputSiteStatePath))
+
+	testTable := []test{
+		{
+			name:         "runs fails no directory",
+			errorMessage: "failed to read file: open " + path + "/connectors/my-connector.yaml: no such file or directory",
+		},
+	}
+
+	for _, test := range testTable {
+		command := &CmdConnectorStatus{}
+		command.namespace = "test1"
+		command.connectorHandler = fs.NewConnectorHandler(command.namespace)
+		command.connectorName = "my-connector"
+		command.Flags = &test.flags
+		command.output = command.Flags.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)
+			}
+		})
+	}
+}
diff --git a/internal/cmd/skupper/connector/nonkube/connector_update_test.go b/internal/cmd/skupper/connector/nonkube/connector_update_test.go
index 6072c5b0..8f0be13a 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_update_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_update_test.go
@@ -1,1 +1,227 @@
 package nonkube
+
+import (
+	"os"
+	"path/filepath"
+	"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/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+	"github.com/skupperproject/skupper/pkg/nonkube/api"
+	"github.com/spf13/cobra"
+	"gotest.tools/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestCmdConnectorUpdate_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		flags             *common.CommandConnectorUpdateFlags
+		k8sObjects        []runtime.Object
+		skupperObjects    []runtime.Object
+		cobraGenericFlags map[string]string
+		expectedErrors    []string
+	}
+
+	homeDir, err := os.UserHomeDir()
+	assert.Check(t, err == nil)
+	path := filepath.Join(homeDir, "/.local/share/skupper/namespaces/test/", string(api.InputSiteStatePath))
+	testTable := []test{
+		{
+			name:           "connector is not updated because get connector returned error",
+			args:           []string{"no-connector"},
+			flags:          &common.CommandConnectorUpdateFlags{},
+			expectedErrors: []string{"connector no-connector must exist in namespace test to be updated"},
+		},
+		{
+			name:           "connector name is not specified",
+			args:           []string{},
+			flags:          &common.CommandConnectorUpdateFlags{},
+			expectedErrors: []string{"connector name must be configured"},
+		},
+		{
+			name:           "connector name is nil",
+			args:           []string{""},
+			flags:          &common.CommandConnectorUpdateFlags{},
+			expectedErrors: []string{"connector name must not be empty"},
+		},
+		{
+			name:           "more than one argument is specified",
+			args:           []string{"my", "connector"},
+			flags:          &common.CommandConnectorUpdateFlags{},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name:           "connector name is not valid.",
+			args:           []string{"my new connector"},
+			flags:          &common.CommandConnectorUpdateFlags{},
+			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 type is not valid",
+			args:           []string{"my-connector"},
+			flags:          &common.CommandConnectorUpdateFlags{ConnectorType: "not-valid"},
+			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"},
+			flags:          &common.CommandConnectorUpdateFlags{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:           "host is not valid",
+			args:           []string{"my-connector"},
+			flags:          &common.CommandConnectorUpdateFlags{Host: "not-valid$"},
+			expectedErrors: []string{"host is not valid: a valid IP address or hostname is expected"},
+		},
+		{
+			name:           "port is not valid",
+			args:           []string{"my-connector"},
+			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"},
+			flags: &common.CommandConnectorUpdateFlags{},
+			cobraGenericFlags: map[string]string{
+				common.FlagNameContext:    "test",
+				common.FlagNameKubeconfig: "test",
+			},
+			expectedErrors: []string{},
+		},
+		{
+			name: "flags all valid",
+			args: []string{"my-connector"},
+			flags: &common.CommandConnectorUpdateFlags{
+				RoutingKey:    "routingkeyname",
+				TlsSecret:     "secretname",
+				Port:          1234,
+				ConnectorType: "tcp",
+				Output:        "json",
+				Host:          "1.2.3.4",
+			},
+			expectedErrors: []string{},
+		},
+	}
+
+	//Add a temp file so connector exists for update tests will pass
+	connectorResource := v2alpha1.Connector{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Connector",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-connector",
+			Namespace: "test",
+		},
+	}
+
+	command := &CmdConnectorUpdate{Flags: &common.CommandConnectorUpdateFlags{}}
+	command.CobraCmd = &cobra.Command{Use: "test"}
+	command.namespace = "test"
+	command.connectorHandler = fs.NewConnectorHandler(command.namespace)
+
+	defer command.connectorHandler.Delete("my-connector")
+	content, err := command.connectorHandler.EncodeToYaml(connectorResource)
+	assert.Check(t, err == nil)
+	err = command.connectorHandler.WriteFile(path, "my-connector.yaml", content, common.Connectors)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			command.connectorName = ""
+			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 TestCmdConnectorUpdate_Run(t *testing.T) {
+	type test struct {
+		name                string
+		namespace           string
+		k8sObjects          []runtime.Object
+		skupperObjects      []runtime.Object
+		skupperErrorMessage string
+		errorMessage        string
+		connectorName       string
+		host                string
+		output              string
+		routingKey          string
+		tlsSecret           string
+		connectorType       string
+		port                int
+	}
+
+	testTable := []test{
+		{
+			name:          "runs ok",
+			namespace:     "test",
+			connectorName: "my-connector",
+			port:          8080,
+			connectorType: "tcp",
+			host:          "hostname",
+			routingKey:    "keyname",
+			tlsSecret:     "secretname",
+		},
+		{
+			name:          "run output json",
+			connectorName: "my-connector",
+			port:          8181,
+			connectorType: "tcp",
+			host:          "hostname",
+			routingKey:    "keyname",
+			tlsSecret:     "secretname",
+			output:        "json",
+		},
+	}
+
+	for _, test := range testTable {
+		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
+		command.newSettings.tlsSecret = test.tlsSecret
+		command.namespace = test.namespace
+		command.connectorHandler = fs.NewConnectorHandler(command.namespace)
+		defer command.connectorHandler.Delete("my-connector")
+		t.Run(test.name, func(t *testing.T) {
+			command.InputToOptions()
+			err := command.Run()
+			if err != nil {
+				assert.Check(t, test.errorMessage == err.Error(), err.Error())
+			} else {
+				assert.Check(t, err == nil)
+			}
+		})
+	}
+}