Repository: skupperproject/skupper
Pull request #1778 (2024-11-08 19:05:21)

Add support for nonkube listener commands
Add nonkube listener CLI and unit tests.

nluaces: The issue #1777 needs to be addressed before merging this PR
nluaces: this pr needs to rebase from v2, that way the build won't fail.

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index b442e31f..280143e2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,6 +3,7 @@ orbs:
     docker: circleci/docker@1.0.1
     kube-orb: circleci/kubernetes@0.11.0
     go: circleci/go@1.7.1
+    helm: circleci/helm@3.1.0
 
 executors:
   go_cimg:
@@ -203,6 +204,22 @@ jobs:
           paths:
             - skupper-manifest
 
+  generate-skupper-setup:
+    executor:
+      name: go_cimg
+    steps:
+      - checkout
+      - go/mod-download-cached
+      - helm/install
+      - run: make generate-skupper-deployment-cluster-scoped
+      - run: make generate-skupper-deployment-namespace-scoped
+      - run: mkdir skupper-setup
+      - run: cp ./skupper-setup-*-scope.yaml skupper-setup
+      - persist_to_workspace:
+          root: .
+          paths:
+            - skupper-setup
+
   build-oci-images:
     executor: local_cluster_test_executor
     parameters:
@@ -341,6 +358,7 @@ jobs:
             done
             cd ${BASEDIR}
             cp "${BASEDIR}/skupper-manifest/manifest.json" "${BASEDIR}/archives"
+            cp "${BASEDIR}/skupper-setup/*.yaml" "${BASEDIR}/archives"
             ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace -prerelease -draft ${VERSION} "${BASEDIR}/archives"
 
   publish-github-prerelease-artifacts:
diff --git a/.gitignore b/.gitignore
index 88d33a1c..31adc73b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,5 @@ vendor/
 # Generated files
 manifest.json
 oci-archives/
+skupper-setup-cluster-scope.yaml
+skupper-setup-namespace-scope.yaml
diff --git a/Makefile b/Makefile
index 181d68d8..d2ba627e 100644
--- a/Makefile
+++ b/Makefile
@@ -119,6 +119,12 @@ generate-doc: build-doc-generator
 update-helm-crd:
 	./scripts/update-helm-crds.sh
 
+generate-skupper-deployment-cluster-scoped:
+	helm template ./charts/skupper-setup  --include-crds --set scope=cluster > skupper-setup-cluster-scope.yaml
+
+generate-skupper-deployment-namespace-scoped:
+	helm template ./charts/skupper-setup  --include-crds --set scope=namespace > skupper-setup-namespace-scope.yaml
+
 clean:
 	rm -rf skupper controller kube-adaptor \
 		network-observer generate-doc \
diff --git a/deployments/helm/Chart.yaml b/charts/skupper-setup/Chart.yaml
similarity index 66%
rename from deployments/helm/Chart.yaml
rename to charts/skupper-setup/Chart.yaml
index 31e77b1b..767500d3 100644
--- a/deployments/helm/Chart.yaml
+++ b/charts/skupper-setup/Chart.yaml
@@ -1,4 +1,5 @@
 apiVersion: v2
 name: skupper-setup
 description:  Helm chart for setting up Skupper.
-version: 0.1.0
\ No newline at end of file
+version: 0.1.0-devel
+appVersion: v2-latest
\ No newline at end of file
diff --git a/deployments/helm/README.md b/charts/skupper-setup/README.md
similarity index 100%
rename from deployments/helm/README.md
rename to charts/skupper-setup/README.md
diff --git a/deployments/helm/crds/skupper_access_grant_crd.yaml b/charts/skupper-setup/crds/skupper_access_grant_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_access_grant_crd.yaml
rename to charts/skupper-setup/crds/skupper_access_grant_crd.yaml
diff --git a/deployments/helm/crds/skupper_access_token_crd.yaml b/charts/skupper-setup/crds/skupper_access_token_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_access_token_crd.yaml
rename to charts/skupper-setup/crds/skupper_access_token_crd.yaml
diff --git a/deployments/helm/crds/skupper_attached_connector_binding_crd.yaml b/charts/skupper-setup/crds/skupper_attached_connector_binding_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_attached_connector_binding_crd.yaml
rename to charts/skupper-setup/crds/skupper_attached_connector_binding_crd.yaml
diff --git a/deployments/helm/crds/skupper_attached_connector_crd.yaml b/charts/skupper-setup/crds/skupper_attached_connector_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_attached_connector_crd.yaml
rename to charts/skupper-setup/crds/skupper_attached_connector_crd.yaml
diff --git a/deployments/helm/crds/skupper_certificate_crd.yaml b/charts/skupper-setup/crds/skupper_certificate_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_certificate_crd.yaml
rename to charts/skupper-setup/crds/skupper_certificate_crd.yaml
diff --git a/deployments/helm/crds/skupper_connector_crd.yaml b/charts/skupper-setup/crds/skupper_connector_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_connector_crd.yaml
rename to charts/skupper-setup/crds/skupper_connector_crd.yaml
diff --git a/deployments/helm/crds/skupper_link_crd.yaml b/charts/skupper-setup/crds/skupper_link_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_link_crd.yaml
rename to charts/skupper-setup/crds/skupper_link_crd.yaml
diff --git a/deployments/helm/crds/skupper_listener_crd.yaml b/charts/skupper-setup/crds/skupper_listener_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_listener_crd.yaml
rename to charts/skupper-setup/crds/skupper_listener_crd.yaml
diff --git a/deployments/helm/crds/skupper_router_access_crd.yaml b/charts/skupper-setup/crds/skupper_router_access_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_router_access_crd.yaml
rename to charts/skupper-setup/crds/skupper_router_access_crd.yaml
diff --git a/deployments/helm/crds/skupper_secured_access_crd.yaml b/charts/skupper-setup/crds/skupper_secured_access_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_secured_access_crd.yaml
rename to charts/skupper-setup/crds/skupper_secured_access_crd.yaml
diff --git a/deployments/helm/crds/skupper_site_crd.yaml b/charts/skupper-setup/crds/skupper_site_crd.yaml
similarity index 100%
rename from deployments/helm/crds/skupper_site_crd.yaml
rename to charts/skupper-setup/crds/skupper_site_crd.yaml
diff --git a/deployments/helm/templates/deploy_controller.yaml b/charts/skupper-setup/templates/deploy_controller.yaml
similarity index 100%
rename from deployments/helm/templates/deploy_controller.yaml
rename to charts/skupper-setup/templates/deploy_controller.yaml
diff --git a/deployments/helm/values.yaml b/charts/skupper-setup/values.yaml
similarity index 100%
rename from deployments/helm/values.yaml
rename to charts/skupper-setup/values.yaml
diff --git a/cmd/controller/deploy_cluster_scope.yaml b/cmd/controller/deploy_cluster_scope.yaml
index aef4ab13..e69de29b 100644
--- a/cmd/controller/deploy_cluster_scope.yaml
+++ b/cmd/controller/deploy_cluster_scope.yaml
@@ -1,234 +0,0 @@
----
-apiVersion: v1
-kind: Namespace
-metadata:
-  name: skupper
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: skupper-controller
-  namespace: skupper
-  labels:
-    application: skupper-controller
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  labels:
-    application: skupper-controller
-  name: skupper-controller
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - nodes
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - ""
-  resources:
-  - configmaps
-  - pods
-  - pods/exec
-  - services
-  - secrets
-  - serviceaccounts
-  - events
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - delete
-  - patch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - delete
-  - patch
-- apiGroups:
-  - route.openshift.io
-  resources:
-  - routes
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-    - apps.openshift.io
-  resources:
-    - deploymentconfigs
-  verbs:
-    - get
-    - list
-    - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
-  - ingresses
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - projectcontour.io
-  resources:
-  - httpproxies
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - gateways
-  - tlsroutes
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-  - patch
-- apiGroups:
-  - coordination.k8s.io
-  resources:
-  - leases
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - rbac.authorization.k8s.io
-  resources:
-  - rolebindings
-  - roles
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - skupper.io
-  resources:
-  - sites
-  - sites/status
-  - links
-  - links/status
-  - accesstokens
-  - accesstokens/status
-  - accessgrants
-  - accessgrants/status
-  - listeners
-  - listeners/status
-  - connectors
-  - connectors/status
-  - attachedconnectors
-  - attachedconnectors/status
-  - attachedconnectorbindings
-  - attachedconnectorbindings/status
-  - routeraccesses
-  - routeraccesses/status
-  - securedaccesses
-  - securedaccesses/status
-  - certificates
-  - certificates/status
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - delete
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  labels:
-    application: skupper-controller
-  name: skupper-controller
-subjects:
-- kind: ServiceAccount
-  name: skupper-controller
-  namespace: skupper
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: skupper-controller
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: skupper-controller
-  namespace: skupper
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      application: skupper-controller
-  template:
-    metadata:
-      labels:
-        app.kubernetes.io/part-of: skupper
-        application: skupper-controller
-    spec:
-      serviceAccountName: skupper-controller
-      # Prevent kubernetes from injecting env vars for grant service
-      # as these then collide with those that actually configure the
-      # controller:
-      enableServiceLinks: false
-      # Please ensure that you can use SeccompProfile and do not use
-      # if your project must work on old Kubernetes
-      # versions < 1.19 or on vendors versions which
-      # do NOT support this field by default
-      securityContext:
-        runAsNonRoot: true
-        seccompProfile:
-          type: RuntimeDefault
-      containers:
-      - name: controller
-        image: quay.io/skupper/controller:v2-latest
-        imagePullPolicy: Always
-        command: ["/app/controller"]
-        args: ["-enable-grants", "-grant-server-autoconfigure"]
-        env:
-        - name: SKUPPER_ADAPTOR_IMAGE
-          value: quay.io/skupper/kube-adaptor:v2-latest
-        securityContext:
-          capabilities:
-            drop:
-            - ALL
-          runAsNonRoot: true
-          allowPrivilegeEscalation: false
-        volumeMounts:
-        - name: tls-credentials
-          mountPath: /etc/controller
-      volumes:
-      - name: tls-credentials
-        emptyDir: {}
diff --git a/cmd/controller/deploy_namespace_scope.yaml b/cmd/controller/deploy_namespace_scope.yaml
index f8d238cc..e69de29b 100644
--- a/cmd/controller/deploy_namespace_scope.yaml
+++ b/cmd/controller/deploy_namespace_scope.yaml
@@ -1,226 +0,0 @@
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: skupper-controller
-  labels:
-    application: skupper-controller
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  labels:
-    application: skupper-controller
-  name: skupper-controller
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - nodes
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - ""
-  resources:
-  - configmaps
-  - pods
-  - pods/exec
-  - services
-  - secrets
-  - serviceaccounts
-  - events
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - delete
-  - patch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - delete
-  - patch
-- apiGroups:
-  - route.openshift.io
-  resources:
-  - routes
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-    - apps.openshift.io
-  resources:
-    - deploymentconfigs
-  verbs:
-    - get
-    - list
-    - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
-  - ingresses
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - projectcontour.io
-  resources:
-  - httpproxies
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - gateways
-  - tlsroutes
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-  - patch
-- apiGroups:
-  - coordination.k8s.io
-  resources:
-  - leases
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - rbac.authorization.k8s.io
-  resources:
-  - rolebindings
-  - roles
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - delete
-  - update
-- apiGroups:
-  - skupper.io
-  resources:
-  - sites
-  - sites/status
-  - links
-  - links/status
-  - accesstokens
-  - accesstokens/status
-  - accessgrants
-  - accessgrants/status
-  - listeners
-  - listeners/status
-  - connectors
-  - connectors/status
-  - attachedconnectors
-  - attachedconnectors/status
-  - attachedconnectorbindings
-  - attachedconnectorbindings/status
-  - routeraccesses
-  - routeraccesses/status
-  - securedaccesses
-  - securedaccesses/status
-  - certificates
-  - certificates/status
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - delete
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  labels:
-    application: skupper-controller
-  name: skupper-controller
-subjects:
-- kind: ServiceAccount
-  name: skupper-controller
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: Role
-  name: skupper-controller
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: skupper-controller
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      application: skupper-controller
-  template:
-    metadata:
-      labels:
-        app.kubernetes.io/part-of: skupper
-        application: skupper-controller
-    spec:
-      serviceAccountName: skupper-controller
-      # Prevent kubernetes from injecting env vars for grant service
-      # as these then collide with those that actually configure the
-      # controller:
-      enableServiceLinks: false
-      securityContext:
-        runAsNonRoot: true
-        seccompProfile:
-          type: RuntimeDefault
-      containers:
-      - name: controller
-        image: quay.io/skupper/controller:v2-latest
-        imagePullPolicy: Always
-        command: ["/app/controller"]
-        args: ["-enable-grants", "-grant-server-autoconfigure"]
-        env:
-        - name: SKUPPER_ADAPTOR_IMAGE
-          value: quay.io/skupper/kube-adaptor:v2-latest
-        - name: WATCH_NAMESPACE
-          valueFrom:
-             fieldRef:
-               fieldPath: metadata.namespace
-        securityContext:
-          capabilities:
-            drop:
-            - ALL
-          runAsNonRoot: true
-          allowPrivilegeEscalation: false
-        volumeMounts:
-        - name: tls-credentials
-          mountPath: /etc/controller
-      volumes:
-      - name: tls-credentials
-        emptyDir: {}
diff --git a/cmd/network-observer/internal/api/types_gen.go b/cmd/network-observer/internal/api/types_gen.go
index 0a890a81..2c0197bb 100644
--- a/cmd/network-observer/internal/api/types_gen.go
+++ b/cmd/network-observer/internal/api/types_gen.go
@@ -114,6 +114,8 @@ type ApplicationFlowRecord struct {
 	// Identity The unique identifier for the record.
 	Identity          string `json:"identity"`
 	Method            string `json:"method"`
+	Octets            uint64 `json:"octets"`
+	OctetsReverse     uint64 `json:"octetsReverse"`
 	Protocol          string `json:"protocol"`
 	RoutingKey        string `json:"routingKey"`
 	SourceProcessId   string `json:"sourceProcessId"`
diff --git a/cmd/network-observer/internal/server/views/flows.go b/cmd/network-observer/internal/server/views/flows.go
index f8ce4fef..1098b60f 100644
--- a/cmd/network-observer/internal/server/views/flows.go
+++ b/cmd/network-observer/internal/server/views/flows.go
@@ -26,6 +26,7 @@ func NewConnectionsSliceProvider(stor store.Interface) func([]store.Entry) []api
 		return results
 	}
 }
+
 func NewConnectionsProvider(stor store.Interface) func(collector.ConnectionRecord) (api.ConnectionRecord, bool) {
 	traceProvider := newTraceProvider(stor)
 	return func(conn collector.ConnectionRecord) (api.ConnectionRecord, bool) {
@@ -199,6 +200,8 @@ func NewRequestProvider(stor store.Interface) func(collector.RequestRecord) (api
 		out.StartTime, out.EndTime = vanflowTimes(record.BaseRecord)
 		setOpt(&out.Method, record.Method)
 		setOpt(&out.Status, record.Result)
+		setOpt(&out.Octets, record.Octets)
+		setOpt(&out.OctetsReverse, record.OctetsReverse)
 
 		if record.EndTime != nil && record.StartTime != nil && record.EndTime.After(record.StartTime.Time) {
 			if record.EndTime != nil && record.StartTime != nil {
diff --git a/cmd/network-observer/spec/openapi.yaml b/cmd/network-observer/spec/openapi.yaml
index 4f657497..dfb35d1b 100644
--- a/cmd/network-observer/spec/openapi.yaml
+++ b/cmd/network-observer/spec/openapi.yaml
@@ -1378,6 +1378,8 @@ components:
             - status
             - traceRouters
             - traceSites
+            - octets
+            - octetsReverse
           properties:
             connectionId:
               type: string
@@ -1412,6 +1414,12 @@ components:
             status:
               type: string
               example: 200
+            octets:
+              type: integer
+              format: uint64
+            octetsReverse:
+              type: integer
+              format: uint64
             traceRouters:
               type: array
               description: Ordered array of the names of routers involved in proxying the connection
diff --git a/cmd/skupper/README.md b/cmd/skupper/README.md
index 9fdcf93c..1392cdd2 100644
--- a/cmd/skupper/README.md
+++ b/cmd/skupper/README.md
@@ -5,26 +5,8 @@ kubectl create namespace skupper
 kubectl config set-context --current --namespace skupper
 ```
 
-Apply the CRDs in the cluster:
+Apply the CRDs in the cluster and deploy the skupper controller ([instructions](../../charts/skupper-setup/README.md)):
 
-```
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_access_grant_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_access_token_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_attached_connector_anchor_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_attached_connector_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_certificate_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_connector_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_link_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_listener_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_router_access_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_secured_access_crd.yaml
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/api/types/crds/skupper_site_crd.yaml
-```
-
-Deploy the Skupper controller in the cluster:
-```
-kubectl apply -f https://raw.githubusercontent.com/skupperproject/skupper/v2/cmd/controller/deploy_cluster_scope.yaml
-```
 
 # Deploy application in two namespaces (or contexts)
 
diff --git a/internal/cmd/skupper/common/flags.go b/internal/cmd/skupper/common/flags.go
index a36e89e1..383ed2b4 100644
--- a/internal/cmd/skupper/common/flags.go
+++ b/internal/cmd/skupper/common/flags.go
@@ -82,6 +82,10 @@ for other Kubernetes flavors, loadbalancer is the default.`
 	FlagNameWait       = "wait"
 	FlagDescWait       = "Wait for the given status before exiting. Choices: configured, ready, none"
 	FlagDescDeleteWait = "Wait for deletion to complete before exiting"
+
+	FlagNameAll       = "all"
+	FlagDescAll       = "delete all skupper resources in current namespace"
+	FlagDescDeleteAll = "delete all skupper resources associated with site in current namespace"
 )
 
 type CommandSiteCreateFlags struct {
@@ -109,6 +113,7 @@ type CommandSiteUpdateFlags struct {
 }
 
 type CommandSiteDeleteFlags struct {
+	All     bool
 	Timeout time.Duration
 	Wait    bool
 }
diff --git a/internal/cmd/skupper/connector/kube/connector_create.go b/internal/cmd/skupper/connector/kube/connector_create.go
index c2d3c5e7..72827154 100644
--- a/internal/cmd/skupper/connector/kube/connector_create.go
+++ b/internal/cmd/skupper/connector/kube/connector_create.go
@@ -326,9 +326,9 @@ func (cmd *CmdConnectorCreate) WaitUntil() error {
 	})
 
 	if err != nil && connectorCondition == nil {
-		return fmt.Errorf("Connector %q is not %s yet, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Connector %q is not yet %s, check the status for more information\n", cmd.name, cmd.status)
 	} else if err != nil && connectorCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Connector %q is %s with errors, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Connector %q is not yet %s: %s\n", cmd.name, cmd.status, connectorCondition.Message)
 	}
 
 	fmt.Printf("Connector %q is %s.\n", cmd.name, cmd.status)
diff --git a/internal/cmd/skupper/connector/kube/connector_update.go b/internal/cmd/skupper/connector/kube/connector_update.go
index b15a2360..14d14f60 100644
--- a/internal/cmd/skupper/connector/kube/connector_update.go
+++ b/internal/cmd/skupper/connector/kube/connector_update.go
@@ -330,9 +330,9 @@ func (cmd *CmdConnectorUpdate) WaitUntil() error {
 	})
 
 	if err != nil && connectorCondition == nil {
-		return fmt.Errorf("Connector %q is not %s yet, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Connector %q is not yet %s, check the status for more information\n", cmd.name, cmd.status)
 	} else if err != nil && connectorCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Connector %q is %s with errors, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Connector %q is not yet %s: %s\n", cmd.name, cmd.status, connectorCondition.Message)
 	}
 
 	fmt.Printf("Connector %q is %s.\n", cmd.name, cmd.status)
diff --git a/internal/cmd/skupper/connector/nonkube/connector_create.go b/internal/cmd/skupper/connector/nonkube/connector_create.go
index e4075963..2e3d2cab 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_create.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_create.go
@@ -112,7 +112,10 @@ func (cmd *CmdConnectorCreate) ValidateInput(args []string) []error {
 		validationErrors = append(validationErrors, fmt.Errorf("host name must be configured: an IP address or hostname is expected"))
 	}
 	if cmd.Flags.TlsCredentials != "" {
-		// TBD what is valid TlsCredentials
+		ok, err := resourceStringValidator.Evaluate(cmd.Flags.TlsCredentials)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("tlsCredentials value is not valid: %s", err))
+		}
 	}
 
 	return validationErrors
diff --git a/internal/cmd/skupper/connector/nonkube/connector_delete.go b/internal/cmd/skupper/connector/nonkube/connector_delete.go
index eea74970..43f9d808 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_delete.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_delete.go
@@ -31,6 +31,7 @@ func (cmd *CmdConnectorDelete) NewClient(cobraCommand *cobra.Command, args []str
 
 func (cmd *CmdConnectorDelete) ValidateInput(args []string) []error {
 	var validationErrors []error
+	opts := fs.GetOptions{RuntimeFirst: false, LogWarning: false}
 	resourceStringValidator := validator.NewResourceStringValidator()
 
 	if cmd.CobraCmd != nil && cmd.CobraCmd.Flag(common.FlagNameContext) != nil && cmd.CobraCmd.Flag(common.FlagNameContext).Value.String() != "" {
@@ -57,6 +58,14 @@ func (cmd *CmdConnectorDelete) ValidateInput(args []string) []error {
 		}
 	}
 
+	if cmd.connectorName != "" {
+		// Validate that there is already a connector with this name
+		connector, err := cmd.connectorHandler.Get(cmd.connectorName, opts)
+		if connector == nil || err != nil {
+			validationErrors = append(validationErrors, fmt.Errorf("connector %s does not exist", cmd.connectorName))
+		}
+	}
+
 	return validationErrors
 }
 
diff --git a/internal/cmd/skupper/connector/nonkube/connector_status.go b/internal/cmd/skupper/connector/nonkube/connector_status.go
index 2ad5f1d8..014c1141 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_status.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_status.go
@@ -35,7 +35,7 @@ func (cmd *CmdConnectorStatus) NewClient(cobraCommand *cobra.Command, args []str
 
 func (cmd *CmdConnectorStatus) ValidateInput(args []string) []error {
 	var validationErrors []error
-	opts := fs.GetOptions{RuntimeFirst: true}
+	opts := fs.GetOptions{RuntimeFirst: true, LogWarning: false}
 	resourceStringValidator := validator.NewResourceStringValidator()
 	outputTypeValidator := validator.NewOptionValidator(common.OutputTypes)
 
@@ -75,7 +75,7 @@ func (cmd *CmdConnectorStatus) ValidateInput(args []string) []error {
 }
 
 func (cmd *CmdConnectorStatus) Run() error {
-	opts := fs.GetOptions{RuntimeFirst: true}
+	opts := fs.GetOptions{RuntimeFirst: true, LogWarning: true}
 	if cmd.connectorName == "" {
 		connectors, err := cmd.connectorHandler.List()
 		if connectors == nil || err != nil {
@@ -122,8 +122,8 @@ func (cmd *CmdConnectorStatus) Run() error {
 				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))
+			fmt.Fprintln(tw, fmt.Sprintf("Name:\t%s\nStatus:\t%s\nRouting key:\t%s\nHost:\t%s\nPort:\t%d\nTlsCredentials:\t%s",
+				connector.Name, status, connector.Spec.RoutingKey, connector.Spec.Host, connector.Spec.Port, connector.Spec.TlsCredentials))
 			_ = tw.Flush()
 		}
 	}
diff --git a/internal/cmd/skupper/connector/nonkube/connector_update.go b/internal/cmd/skupper/connector/nonkube/connector_update.go
index c495dcdd..c291dc04 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_update.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_update.go
@@ -44,7 +44,7 @@ func (cmd *CmdConnectorUpdate) NewClient(cobraCommand *cobra.Command, args []str
 
 func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error {
 	var validationErrors []error
-	opts := fs.GetOptions{RuntimeFirst: false}
+	opts := fs.GetOptions{RuntimeFirst: false, LogWarning: false}
 	resourceStringValidator := validator.NewResourceStringValidator()
 	numberValidator := validator.NewNumberValidator()
 	connectorTypeValidator := validator.NewOptionValidator(common.ConnectorTypes)
@@ -132,7 +132,14 @@ func (cmd *CmdConnectorUpdate) ValidateInput(args []string) []error {
 			cmd.output = cmd.Flags.Output
 		}
 	}
-
+	if cmd.Flags.TlsCredentials != "" {
+		ok, err := resourceStringValidator.Evaluate(cmd.Flags.TlsCredentials)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("tlsCredentials value is not valid: %s", err))
+		} else {
+			cmd.newSettings.tlsCredentials = cmd.Flags.TlsCredentials
+		}
+	}
 	return validationErrors
 }
 
diff --git a/internal/cmd/skupper/link/kube/link_update.go b/internal/cmd/skupper/link/kube/link_update.go
index 2ba3d753..5da082f6 100644
--- a/internal/cmd/skupper/link/kube/link_update.go
+++ b/internal/cmd/skupper/link/kube/link_update.go
@@ -216,9 +216,9 @@ func (cmd *CmdLinkUpdate) WaitUntil() error {
 	})
 
 	if err != nil && linkCondition == nil {
-		return fmt.Errorf("Link %q is not %s yet, check the status for more information\n", cmd.linkName, cmd.status)
+		return fmt.Errorf("Link %q is not yet %s, check the status for more information\n", cmd.linkName, cmd.status)
 	} else if err != nil && linkCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Link %q is %s with errors, check the status for more information\n", cmd.linkName, cmd.status)
+		return fmt.Errorf("Link %q is not yet %s: %s\n", cmd.linkName, cmd.status, linkCondition.Message)
 	}
 
 	fmt.Printf("Link %q is updated\n", cmd.linkName)
diff --git a/internal/cmd/skupper/listener/kube/listener_create.go b/internal/cmd/skupper/listener/kube/listener_create.go
index 6574f923..c847be93 100644
--- a/internal/cmd/skupper/listener/kube/listener_create.go
+++ b/internal/cmd/skupper/listener/kube/listener_create.go
@@ -239,9 +239,9 @@ func (cmd *CmdListenerCreate) WaitUntil() error {
 	})
 
 	if err != nil && listenerCondition == nil {
-		return fmt.Errorf("Listener %q is not %s yet, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Listener %q is not yet %s, check the status for more information\n", cmd.name, cmd.status)
 	} else if err != nil && listenerCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Listener %q is %s with errors, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Listener %q is not yet %s: %s\n", cmd.name, cmd.status, listenerCondition.Message)
 	}
 
 	fmt.Printf("Listener %q is %s.\n", cmd.name, cmd.status)
diff --git a/internal/cmd/skupper/listener/kube/listener_update.go b/internal/cmd/skupper/listener/kube/listener_update.go
index ca829296..d6672644 100644
--- a/internal/cmd/skupper/listener/kube/listener_update.go
+++ b/internal/cmd/skupper/listener/kube/listener_update.go
@@ -232,9 +232,9 @@ func (cmd *CmdListenerUpdate) WaitUntil() error {
 	})
 
 	if err != nil && listenerCondition == nil {
-		return fmt.Errorf("Listener %q is not %s yet, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Listener %q is not yet %s, check the status for more information\n", cmd.name, cmd.status)
 	} else if err != nil && listenerCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Listener %q is %s with errors, check the status for more information\n", cmd.name, cmd.status)
+		return fmt.Errorf("Listener %q is not yet %s: %s\n", cmd.name, cmd.status, listenerCondition.Message)
 	}
 
 	fmt.Printf("Listener %q is updated\n", cmd.name)
diff --git a/internal/cmd/skupper/listener/listener.go b/internal/cmd/skupper/listener/listener.go
index 3215f561..a2b150d4 100644
--- a/internal/cmd/skupper/listener/listener.go
+++ b/internal/cmd/skupper/listener/listener.go
@@ -46,7 +46,7 @@ func CmdListenerCreateFactory(configuredPlatform types.Platform) *cobra.Command
 
 	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.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)
@@ -73,7 +73,7 @@ func CmdListenerUpdateFactory(configuredPlatform types.Platform) *cobra.Command
 		Use:   "update <name>",
 		Short: "update a listener",
 		Long: `Clients at this site use the listener host and port to establish connections to the remote service.
-	The user can change port, host name, TLS secret, listener type and routing key`,
+	The user can change port, host name, TLS credentials, listener type and routing key`,
 		Example: "skupper listener update database --host mysql --port 3306",
 	}
 
diff --git a/internal/cmd/skupper/listener/nonkube/listener_create.go b/internal/cmd/skupper/listener/nonkube/listener_create.go
index 2b53b788..70eec0fc 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_create.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_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 CmdListenerCreate struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandListenerCreateFlags
-	Namespace string
-	siteName  string
+	listenerHandler *fs.ListenerHandler
+	CobraCmd        *cobra.Command
+	Flags           *common.CommandListenerCreateFlags
+	namespace       string
+	listenerName    string
+	port            int
+	host            string
+	tlsCredentials  string
+	listenerType    string
+	routingKey      string
+	output          string
 }
 
 func NewCmdListenerCreate() *CmdListenerCreate {
@@ -18,12 +33,149 @@ func NewCmdListenerCreate() *CmdListenerCreate {
 }
 
 func (cmd *CmdListenerCreate) 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.listenerHandler = fs.NewListenerHandler(cmd.namespace)
+}
+
+func (cmd *CmdListenerCreate) 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 value 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 *CmdListenerCreate) 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 *CmdListenerCreate) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdListenerCreate) InputToOptions()                     {}
 func (cmd *CmdListenerCreate) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	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,
+		},
+	}
+
+	if cmd.output != "" {
+		encodedOutput, err := utils.Encode(cmd.output, listenerResource)
+		fmt.Println(encodedOutput)
+		return err
+	} else {
+		err := cmd.listenerHandler.Add(listenerResource)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
+
 func (cmd *CmdListenerCreate) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/listener/nonkube/listener_delete.go b/internal/cmd/skupper/listener/nonkube/listener_delete.go
index f7631da3..52838264 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_delete.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_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 CmdListenerDelete struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandListenerDeleteFlags
-	Namespace string
-	siteName  string
+	listenerHandler *fs.ListenerHandler
+	CobraCmd        *cobra.Command
+	Flags           *common.CommandListenerDeleteFlags
+	namespace       string
+	listenerName    string
 }
 
 func NewCmdListenerDelete() *CmdListenerDelete {
@@ -18,12 +22,67 @@ func NewCmdListenerDelete() *CmdListenerDelete {
 }
 
 func (cmd *CmdListenerDelete) 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.listenerHandler = fs.NewListenerHandler(cmd.namespace)
+
+}
+
+func (cmd *CmdListenerDelete) ValidateInput(args []string) []error {
+	var validationErrors []error
+	opts := fs.GetOptions{RuntimeFirst: false, LogWarning: false}
+
+	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()
+
+	// Validate arguments name
+	if len(args) < 1 {
+		validationErrors = append(validationErrors, fmt.Errorf("listener name must be specified"))
+	} 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("listener name 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]
+		}
+	}
+
+	if cmd.listenerName != "" {
+		// Validate that there is already a listener with this name
+		listener, err := cmd.listenerHandler.Get(cmd.listenerName, opts)
+		if listener == nil || err != nil {
+			validationErrors = append(validationErrors, fmt.Errorf("listener %s does not exist", cmd.listenerName))
+		}
+	}
+
+	return validationErrors
+}
+
+func (cmd *CmdListenerDelete) InputToOptions() {
+	if cmd.namespace == "" {
+		cmd.namespace = "default"
+	}
 }
 
-func (cmd *CmdListenerDelete) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdListenerDelete) InputToOptions()                     {}
 func (cmd *CmdListenerDelete) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	err := cmd.listenerHandler.Delete(cmd.listenerName)
+	if err != nil {
+		return err
+	}
+	return nil
 }
+
 func (cmd *CmdListenerDelete) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/listener/nonkube/listener_status.go b/internal/cmd/skupper/listener/nonkube/listener_status.go
index df2b3ec8..4a9eb6cf 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_status.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_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 CmdListenerStatus struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandListenerStatusFlags
-	Namespace string
-	siteName  string
+	listenerHandler *fs.ListenerHandler
+	CobraCmd        *cobra.Command
+	Flags           *common.CommandListenerStatusFlags
+	namespace       string
+	listenerName    string
+	output          string
 }
 
 func NewCmdListenerStatus() *CmdListenerStatus {
@@ -18,12 +26,109 @@ func NewCmdListenerStatus() *CmdListenerStatus {
 }
 
 func (cmd *CmdListenerStatus) 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.listenerHandler = fs.NewListenerHandler(cmd.namespace)
+}
+
+func (cmd *CmdListenerStatus) ValidateInput(args []string) []error {
+	var validationErrors []error
+	opts := fs.GetOptions{RuntimeFirst: true, LogWarning: false}
+	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("listener name 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]
+			}
+		}
+	}
+	// Validate that there is a listener with this name in the namespace
+	if cmd.listenerName != "" {
+		listener, err := cmd.listenerHandler.Get(cmd.listenerName, opts)
+		if listener == nil || err != nil {
+			validationErrors = append(validationErrors, fmt.Errorf("listener %s does not exist in namespace %s", cmd.listenerName, 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 *CmdListenerStatus) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdListenerStatus) InputToOptions()                     {}
 func (cmd *CmdListenerStatus) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	opts := fs.GetOptions{RuntimeFirst: true, LogWarning: true}
+	if cmd.listenerName == "" {
+		listeners, err := cmd.listenerHandler.List()
+		if listeners == nil || err != nil {
+			fmt.Println("no listeners found:")
+			return err
+		}
+		if cmd.output != "" {
+			for _, listener := range listeners {
+				encodedOutput, err := utils.Encode(cmd.output, listener)
+				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 _, listener := range listeners {
+				status := "Not Ready"
+				if listener.IsConfigured() {
+					status = "Ok"
+				}
+				fmt.Fprintln(tw, fmt.Sprintf("%s\t%s\t%s\t%s\t%d",
+					listener.Name, status, listener.Spec.RoutingKey, listener.Spec.Host, listener.Spec.Port))
+			}
+			_ = tw.Flush()
+		}
+	} else {
+		listener, err := cmd.listenerHandler.Get(cmd.listenerName, opts)
+		if listener == nil || err != nil {
+			fmt.Println("No listeners found:", err)
+			return err
+		}
+		if cmd.output != "" {
+			encodedOutput, err := utils.Encode(cmd.output, listener)
+			if err != nil {
+				return err
+			}
+			fmt.Println(encodedOutput)
+		} else {
+			status := "Not Ready"
+			if listener.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\nTlsCredentials:\t%s\n",
+				listener.Name, status, listener.Spec.RoutingKey, listener.Spec.Host, listener.Spec.Port, listener.Spec.TlsCredentials))
+			_ = tw.Flush()
+		}
+	}
+	return nil
 }
+
+func (cmd *CmdListenerStatus) InputToOptions()  {}
 func (cmd *CmdListenerStatus) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/listener/nonkube/listener_update.go b/internal/cmd/skupper/listener/nonkube/listener_update.go
index 21cc9986..dc75696b 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_update.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_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 ListenerUpdates struct {
+	routingKey     string
+	host           string
+	tlsCredentials string
+	listenerType   string
+	port           int
+	output         string
+}
 type CmdListenerUpdate struct {
-	CobraCmd  *cobra.Command
-	Flags     *common.CommandListenerUpdateFlags
-	Namespace string
-	siteName  string
+	listenerHandler *fs.ListenerHandler
+	CobraCmd        *cobra.Command
+	Flags           *common.CommandListenerUpdateFlags
+	namespace       string
+	listenerName    string
+	newSettings     ListenerUpdates
 }
 
 func NewCmdListenerUpdate() *CmdListenerUpdate {
@@ -18,12 +35,154 @@ func NewCmdListenerUpdate() *CmdListenerUpdate {
 }
 
 func (cmd *CmdListenerUpdate) 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.listenerHandler = fs.NewListenerHandler(cmd.namespace)
+}
+
+func (cmd *CmdListenerUpdate) ValidateInput(args []string) []error {
+	var validationErrors []error
+	opts := fs.GetOptions{RuntimeFirst: false, LogWarning: false}
+	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() != "" {
+		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("listener 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("listener name 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]
+		}
+	}
+
+	// Validate that there is already a listener with this name in the namespace
+	if cmd.listenerName != "" {
+		listener, err := cmd.listenerHandler.Get(cmd.listenerName, opts)
+		if listener == nil || err != nil {
+			validationErrors = append(validationErrors, fmt.Errorf("listener %s must exist in namespace %s to be updated", cmd.listenerName, cmd.namespace))
+		} else {
+			// save existing values
+			cmd.newSettings.host = listener.Spec.Host
+			cmd.newSettings.port = listener.Spec.Port
+			cmd.newSettings.tlsCredentials = listener.Spec.TlsCredentials
+			cmd.newSettings.listenerType = listener.Spec.Type
+			cmd.newSettings.routingKey = listener.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.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.TlsCredentials != "" {
+		ok, err := resourceStringValidator.Evaluate(cmd.Flags.TlsCredentials)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("tlsCredentials value is not valid: %s", err))
+		} else {
+			cmd.newSettings.tlsCredentials = cmd.Flags.TlsCredentials
+		}
+	}
+	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))
+		} else {
+			cmd.newSettings.listenerType = cmd.Flags.ListenerType
+		}
+	}
+	if cmd.Flags.Port != 0 {
+		ok, err := numberValidator.Evaluate(cmd.Flags.Port)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("listener port is not valid: %s", err))
+		} else {
+			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
+}
+
+func (cmd *CmdListenerUpdate) InputToOptions() {
+	if cmd.namespace == "" {
+		cmd.namespace = "default"
+	}
+	// user wants to clear TlsCredentials
+	if cmd.CobraCmd.Flags().Changed(common.FlagNameTlsCredentials) && cmd.Flags.TlsCredentials == "" {
+		cmd.newSettings.tlsCredentials = ""
+	}
 }
 
-func (cmd *CmdListenerUpdate) ValidateInput(args []string) []error { return nil }
-func (cmd *CmdListenerUpdate) InputToOptions()                     {}
 func (cmd *CmdListenerUpdate) Run() error {
-	return fmt.Errorf("command not supported by the selected platform")
+	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.newSettings.host,
+			Port:           cmd.newSettings.port,
+			RoutingKey:     cmd.newSettings.routingKey,
+			TlsCredentials: cmd.newSettings.tlsCredentials,
+			Type:           cmd.newSettings.listenerType,
+		},
+	}
+	if cmd.newSettings.output != "" {
+		encodedOutput, err := utils.Encode(cmd.newSettings.output, listenerResource)
+		fmt.Println(encodedOutput)
+		return err
+	} else {
+		err := cmd.listenerHandler.Add(listenerResource)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
+
 func (cmd *CmdListenerUpdate) WaitUntil() error { return nil }
diff --git a/internal/cmd/skupper/site/kube/site_create.go b/internal/cmd/skupper/site/kube/site_create.go
index 6e8f4571..8d8cc047 100644
--- a/internal/cmd/skupper/site/kube/site_create.go
+++ b/internal/cmd/skupper/site/kube/site_create.go
@@ -225,9 +225,9 @@ func (cmd *CmdSiteCreate) WaitUntil() error {
 	})
 
 	if err != nil && siteCondition == nil {
-		return fmt.Errorf("Site %q is not %s yet, check the status for more information\n", cmd.siteName, cmd.status)
+		return fmt.Errorf("Site %q is not yet %s, check the status for more information\n", cmd.siteName, cmd.status)
 	} else if err != nil && siteCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Site %q is %s with errors, check the status for more information\n", cmd.siteName, cmd.status)
+		return fmt.Errorf("Site %q is not yet %s: %s\n", cmd.siteName, cmd.status, siteCondition.Message)
 	}
 
 	fmt.Printf("Site %q is %s.\n", cmd.siteName, cmd.status)
diff --git a/internal/cmd/skupper/site/kube/site_delete.go b/internal/cmd/skupper/site/kube/site_delete.go
index 1779161d..6d731770 100644
--- a/internal/cmd/skupper/site/kube/site_delete.go
+++ b/internal/cmd/skupper/site/kube/site_delete.go
@@ -3,10 +3,11 @@ package kube
 import (
 	"context"
 	"fmt"
+	"time"
+
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common/utils"
 	"github.com/skupperproject/skupper/pkg/utils/validator"
-	"time"
 
 	"github.com/skupperproject/skupper/internal/kube/client"
 	skupperv2alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v2alpha1"
@@ -22,6 +23,7 @@ type CmdSiteDelete struct {
 	siteName  string
 	timeout   time.Duration
 	wait      bool
+	all       bool
 }
 
 func NewCmdSiteDelete() *CmdSiteDelete {
@@ -87,11 +89,120 @@ func (cmd *CmdSiteDelete) ValidateInput(args []string) []error {
 func (cmd *CmdSiteDelete) InputToOptions() {
 	cmd.timeout = cmd.Flags.Timeout
 	cmd.wait = cmd.Flags.Wait
+	cmd.all = cmd.Flags.All
 }
 
 func (cmd *CmdSiteDelete) Run() error {
+
 	err := cmd.Client.Sites(cmd.Namespace).Delete(context.TODO(), cmd.siteName, metav1.DeleteOptions{})
-	return err
+	if err != nil {
+		return err
+	}
+
+	// if delete all, also remove all the other resources
+	if cmd.all {
+		connectors, err := cmd.Client.Connectors(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && connectors != nil && len(connectors.Items) != 0 {
+			for _, connector := range connectors.Items {
+				err = cmd.Client.Connectors(cmd.Namespace).Delete(context.TODO(), connector.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		listeners, err := cmd.Client.Listeners(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && listeners != nil && len(listeners.Items) != 0 {
+			for _, listener := range listeners.Items {
+				err = cmd.Client.Listeners(cmd.Namespace).Delete(context.TODO(), listener.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		links, err := cmd.Client.Links(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && links != nil && len(links.Items) != 0 {
+			for _, link := range links.Items {
+				err = cmd.Client.Links(cmd.Namespace).Delete(context.TODO(), link.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		accessTokens, err := cmd.Client.AccessTokens(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && accessTokens != nil && len(accessTokens.Items) != 0 {
+			for _, accessToken := range accessTokens.Items {
+				err = cmd.Client.AccessTokens(cmd.Namespace).Delete(context.TODO(), accessToken.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		accessGrants, err := cmd.Client.AccessGrants(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && accessGrants != nil && len(accessGrants.Items) != 0 {
+			for _, accessGrant := range accessGrants.Items {
+				err = cmd.Client.AccessGrants(cmd.Namespace).Delete(context.TODO(), accessGrant.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		routerAccesses, err := cmd.Client.RouterAccesses(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && routerAccesses != nil && len(routerAccesses.Items) != 0 {
+			for _, routerAccess := range routerAccesses.Items {
+				err = cmd.Client.RouterAccesses(cmd.Namespace).Delete(context.TODO(), routerAccess.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		securedAccesses, err := cmd.Client.SecuredAccesses(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && securedAccesses != nil && len(securedAccesses.Items) != 0 {
+			for _, securedAccess := range securedAccesses.Items {
+				err = cmd.Client.SecuredAccesses(cmd.Namespace).Delete(context.TODO(), securedAccess.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		certificates, err := cmd.Client.Certificates(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && certificates != nil && len(certificates.Items) != 0 {
+			for _, certificate := range certificates.Items {
+				err = cmd.Client.Certificates(cmd.Namespace).Delete(context.TODO(), certificate.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		attachedConnectors, err := cmd.Client.AttachedConnectors(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && attachedConnectors != nil && len(attachedConnectors.Items) != 0 {
+			for _, attachedConnector := range attachedConnectors.Items {
+				err = cmd.Client.AttachedConnectors(cmd.Namespace).Delete(context.TODO(), attachedConnector.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+
+		attachedConnectorBindings, err := cmd.Client.AttachedConnectorBindings(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+		if err == nil && attachedConnectorBindings != nil && len(attachedConnectorBindings.Items) != 0 {
+			for _, attachedConnectorBinding := range attachedConnectorBindings.Items {
+				err = cmd.Client.AttachedConnectorBindings(cmd.Namespace).Delete(context.TODO(), attachedConnectorBinding.Name, metav1.DeleteOptions{})
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	return nil
 }
 func (cmd *CmdSiteDelete) WaitUntil() error {
 
diff --git a/internal/cmd/skupper/site/kube/site_update.go b/internal/cmd/skupper/site/kube/site_update.go
index c5a22a2e..ab0a4bd3 100644
--- a/internal/cmd/skupper/site/kube/site_update.go
+++ b/internal/cmd/skupper/site/kube/site_update.go
@@ -244,9 +244,9 @@ func (cmd *CmdSiteUpdate) WaitUntil() error {
 	})
 
 	if err != nil && siteCondition == nil {
-		return fmt.Errorf("Site %q is not %s yet, check the status for more information\n", cmd.siteName, cmd.status)
+		return fmt.Errorf("Site %q is not yet %s, check the status for more information\n", cmd.siteName, cmd.status)
 	} else if err != nil && siteCondition.Status == metav1.ConditionFalse {
-		return fmt.Errorf("Site %q is %s with errors, check the status for more information\n", cmd.siteName, cmd.status)
+		return fmt.Errorf("Site %q is not yet %s: %s\n", cmd.siteName, cmd.status, siteCondition.Message)
 	}
 
 	fmt.Printf("Site %q is updated\n", cmd.siteName)
diff --git a/internal/cmd/skupper/site/site.go b/internal/cmd/skupper/site/site.go
index 8b5c2549..5c565219 100644
--- a/internal/cmd/skupper/site/site.go
+++ b/internal/cmd/skupper/site/site.go
@@ -148,6 +148,7 @@ skupper site delete --wait=false`,
 	cmd := common.ConfigureCobraCommand(configuredPlatform, cmdSiteDeleteDesc, kubeCommand, nonKubeCommand)
 	cmdFlags := common.CommandSiteDeleteFlags{}
 
+	cmd.Flags().BoolVar(&cmdFlags.All, common.FlagNameAll, false, common.FlagDescDeleteAll)
 	cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout)
 	cmd.Flags().BoolVar(&cmdFlags.Wait, common.FlagNameWait, true, common.FlagDescDeleteWait)
 
diff --git a/internal/nonkube/client/fs/connector_handler.go b/internal/nonkube/client/fs/connector_handler.go
index a1e0c50e..7ecd7014 100644
--- a/internal/nonkube/client/fs/connector_handler.go
+++ b/internal/nonkube/client/fs/connector_handler.go
@@ -2,8 +2,8 @@ package fs
 
 import (
 	"errors"
-	"fmt"
 	"io/fs"
+	"os"
 
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
 	"github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
@@ -47,7 +47,9 @@ func (s *ConnectorHandler) Get(name string, opt GetOptions) (*v2alpha1.Connector
 		// 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")
+			if opt.LogWarning {
+				os.Stderr.WriteString("Site not initialized yet\n")
+			}
 			err, file = s.ReadFile(s.pathProvider.GetNamespace(), fileName, common.Connectors)
 			if err != nil {
 				return nil, err
@@ -90,7 +92,7 @@ func (s *ConnectorHandler) List() ([]*v2alpha1.Connector, error) {
 	path := s.pathProvider.GetRuntimeNamespace()
 	err, files := s.ReadDir(path, common.Connectors)
 	if err != nil {
-		fmt.Println("Site not initialized yet")
+		os.Stderr.WriteString("Site not initialized yet\n")
 		path = s.pathProvider.GetNamespace()
 		err, files = s.ReadDir(path, common.Connectors)
 		if err != nil {
diff --git a/internal/nonkube/client/fs/custom_resource_handler.go b/internal/nonkube/client/fs/custom_resource_handler.go
index 9575d7fa..a156a59b 100644
--- a/internal/nonkube/client/fs/custom_resource_handler.go
+++ b/internal/nonkube/client/fs/custom_resource_handler.go
@@ -13,6 +13,7 @@ import (
 
 type GetOptions struct {
 	RuntimeFirst bool
+	LogWarning   bool
 }
 
 type CustomResourceHandler[T any] interface {
diff --git a/internal/nonkube/client/fs/listener_handler.go b/internal/nonkube/client/fs/listener_handler.go
new file mode 100644
index 00000000..7aa83649
--- /dev/null
+++ b/internal/nonkube/client/fs/listener_handler.go
@@ -0,0 +1,116 @@
+package fs
+
+import (
+	"errors"
+	"io/fs"
+	"os"
+
+	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
+)
+
+type ListenerHandler struct {
+	BaseCustomResourceHandler
+	pathProvider PathProvider
+}
+
+func NewListenerHandler(namespace string) *ListenerHandler {
+	return &ListenerHandler{
+		pathProvider: PathProvider{
+			Namespace: namespace,
+		},
+	}
+}
+
+func (s *ListenerHandler) Add(resource v2alpha1.Listener) error {
+
+	fileName := resource.Name + ".yaml"
+	content, err := s.EncodeToYaml(resource)
+	if err != nil {
+		return err
+	}
+
+	err = s.WriteFile(s.pathProvider.GetNamespace(), fileName, content, "listeners")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *ListenerHandler) Get(name string, opts GetOptions) (*v2alpha1.Listener, error) {
+	var context v2alpha1.Listener
+	fileName := name + ".yaml"
+
+	if opts.RuntimeFirst == true {
+		// First read from runtime directory, where output is found after bootstrap
+		// has run.  If no runtime listeners try and display configured listeners
+		err, file := s.ReadFile(s.pathProvider.GetRuntimeNamespace(), fileName, common.Listeners)
+		if err != nil {
+			if opts.LogWarning {
+				os.Stderr.WriteString("Site not initialized yet\n")
+			}
+			err, file = s.ReadFile(s.pathProvider.GetNamespace(), fileName, common.Listeners)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		if err = s.DecodeYaml(file, &context); err != nil {
+			return nil, err
+		}
+	} else {
+		// read from input directory to get lastest config
+		err, file := s.ReadFile(s.pathProvider.GetNamespace(), fileName, common.Listeners)
+		if err != nil {
+			return nil, err
+		}
+		if err := s.DecodeYaml(file, &context); err != nil {
+			return nil, err
+		}
+	}
+
+	return &context, nil
+}
+
+func (s *ListenerHandler) Delete(name string) error {
+	fileName := name + ".yaml"
+
+	if err := s.DeleteFile(s.pathProvider.GetNamespace(), fileName, "listeners"); err != nil {
+		if !errors.Is(err, fs.ErrNotExist) {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (s *ListenerHandler) List() ([]*v2alpha1.Listener, error) {
+	var listeners []*v2alpha1.Listener
+
+	// First read from runtime directory, where output is found after bootstrap
+	// has run.  If no runtime listeners try and display configured listeners
+	path := s.pathProvider.GetRuntimeNamespace()
+	err, files := s.ReadDir(path, common.Listeners)
+	if err != nil {
+		os.Stderr.WriteString("Site not initialized yet\n")
+		path = s.pathProvider.GetNamespace()
+		err, files = s.ReadDir(path, common.Listeners)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	for _, file := range files {
+		err, listener := s.ReadFile(path, file.Name(), common.Listeners)
+		if err != nil {
+			return nil, err
+		}
+		var context v2alpha1.Listener
+		if err = s.DecodeYaml(listener, &context); err != nil {
+			return nil, err
+		}
+		listeners = append(listeners, &context)
+	}
+	return listeners, nil
+}
diff --git a/pkg/vanflow/records.go b/pkg/vanflow/records.go
index 3fc56696..e0f33188 100644
--- a/pkg/vanflow/records.go
+++ b/pkg/vanflow/records.go
@@ -292,11 +292,13 @@ func (r TransportBiflowRecord) GetTypeMeta() TypeMeta {
 
 type AppBiflowRecord struct {
 	BaseRecord
-	Parent   *string `vflow:"2"`
-	Protocol *string `vflow:"16"`
-	Latency  *uint64 `vflow:"24"`
-	Method   *string `vflow:"27"`
-	Result   *string `vflow:"28"`
+	Parent        *string `vflow:"2"`
+	Protocol      *string `vflow:"16"`
+	Latency       *uint64 `vflow:"24"`
+	Method        *string `vflow:"27"`
+	Result        *string `vflow:"28"`
+	Octets        *uint64 `vflow:"23"`
+	OctetsReverse *uint64 `vflow:"58"`
 }
 
 func (r AppBiflowRecord) GetTypeMeta() TypeMeta {


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 bb095a87..ed977eb0 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_create_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_create_test.go
@@ -78,6 +78,12 @@ func TestNonKubeCmdConnectorCreate_ValidateInput(t *testing.T) {
 			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:           "TlsCredentials is not valid",
+			args:           []string{"my-connector-tls", "8080"},
+			flags:          &common.CommandConnectorCreateFlags{TlsCredentials: "not-valid$", Host: "1.2.3.4"},
+			expectedErrors: []string{"tlsCredentials value 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"},
diff --git a/internal/cmd/skupper/connector/nonkube/connector_delete_test.go b/internal/cmd/skupper/connector/nonkube/connector_delete_test.go
index d83ebc8b..45dffefe 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_delete_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_delete_test.go
@@ -1,14 +1,18 @@
 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/v3/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -23,6 +27,10 @@ func TestCmdConnectorDelete_ValidateInput(t *testing.T) {
 		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 name is not specified",
@@ -48,6 +56,12 @@ func TestCmdConnectorDelete_ValidateInput(t *testing.T) {
 			flags:          &common.CommandConnectorDeleteFlags{},
 			expectedErrors: []string{"only one argument is allowed for this command"},
 		},
+		{
+			name:           "connector doesn't exist ",
+			args:           []string{"no-connector"},
+			flags:          &common.CommandConnectorDeleteFlags{},
+			expectedErrors: []string{"connector no-connector does not exist"},
+		},
 		{
 			name:           "kubernetes flags are not valid on this platform",
 			args:           []string{"my-connector"},
@@ -60,11 +74,32 @@ func TestCmdConnectorDelete_ValidateInput(t *testing.T) {
 		},
 	}
 
+	//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 := &CmdConnectorDelete{Flags: &common.CommandConnectorDeleteFlags{}}
+	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 := &CmdConnectorDelete{Flags: &common.CommandConnectorDeleteFlags{}}
-			command.CobraCmd = &cobra.Command{Use: "test"}
-
+			command.connectorName = ""
 			if test.flags != nil {
 				command.Flags = test.flags
 			}
diff --git a/internal/cmd/skupper/connector/nonkube/connector_update_test.go b/internal/cmd/skupper/connector/nonkube/connector_update_test.go
index 93f5619e..6d38bc57 100644
--- a/internal/cmd/skupper/connector/nonkube/connector_update_test.go
+++ b/internal/cmd/skupper/connector/nonkube/connector_update_test.go
@@ -73,6 +73,12 @@ func TestCmdConnectorUpdate_ValidateInput(t *testing.T) {
 			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:           "tlsCredentials is not valid",
+			args:           []string{"my-connector"},
+			flags:          &common.CommandConnectorUpdateFlags{TlsCredentials: "not-valid$", Host: "1.2.3.4"},
+			expectedErrors: []string{"tlsCredentials value 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"},
diff --git a/internal/cmd/skupper/listener/kube/listener_update_test.go b/internal/cmd/skupper/listener/kube/listener_update_test.go
index 2fe680be..998a31a3 100644
--- a/internal/cmd/skupper/listener/kube/listener_update_test.go
+++ b/internal/cmd/skupper/listener/kube/listener_update_test.go
@@ -460,13 +460,13 @@ func TestCmdListenerUpdate_WaitUntil(t *testing.T) {
 				},
 			},
 			expectError:  true,
-			errorMessage: "Listener \"my-listener\" is not ready yet, check the status for more information\n",
+			errorMessage: "Listener \"my-listener\" is not yet ready, check the status for more information\n",
 		},
 		{
 			name:         "listener is not returned",
 			status:       "ready",
 			expectError:  true,
-			errorMessage: "Listener \"my-listener\" is not ready yet, check the status for more information\n",
+			errorMessage: "Listener \"my-listener\" is not yet ready, check the status for more information\n",
 		},
 		{
 			name:   "listener is ready",
@@ -588,7 +588,7 @@ func TestCmdListenerUpdate_WaitUntil(t *testing.T) {
 				},
 			},
 			expectError:  true,
-			errorMessage: "Listener \"my-listener\" is configured with errors, check the status for more information\n",
+			errorMessage: "Listener \"my-listener\" is not yet configured: Error\n",
 		},
 	}
 
diff --git a/internal/cmd/skupper/listener/nonkube/listener_create_test.go b/internal/cmd/skupper/listener/nonkube/listener_create_test.go
index 6072c5b0..3027cdf3 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_create_test.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_create_test.go
@@ -1,1 +1,290 @@
 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 TestNonKubeCmdListenerCreate_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		k8sObjects        []runtime.Object
+		skupperObjects    []runtime.Object
+		flags             *common.CommandListenerCreateFlags
+		cobraGenericFlags map[string]string
+		expectedErrors    []string
+	}
+
+	testTable := []test{
+		{
+			name:           "listener name and port are not specified",
+			args:           []string{},
+			flags:          &common.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{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.CommandListenerCreateFlags{TlsCredentials: "not-valid$", Host: "1.2.3.4"},
+			expectedErrors: []string{"tlsCredentials value 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.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"},
+			flags:          &common.CommandListenerCreateFlags{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.CommandListenerCreateFlags{
+				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 := &CmdListenerCreate{Flags: &common.CommandListenerCreateFlags{}}
+			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 TestNonKubeCmdListenerCreate_InputToOptions(t *testing.T) {
+
+	type test struct {
+		name                   string
+		args                   []string
+		namespace              string
+		flags                  common.CommandListenerCreateFlags
+		expectedNamespace      string
+		listenerName           string
+		expectedTlsCredentials string
+		expectedHost           string
+		expectedRoutingKey     string
+		expectedListenerType   string
+		expectedOutput         string
+	}
+
+	testTable := []test{
+		{
+			name:                   "test1",
+			flags:                  common.CommandListenerCreateFlags{"backend", "", "secret", "tcp", 0, "json", "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"},
+			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"},
+			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 := CmdListenerCreate{}
+			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 TestNonKubeCmdListenerCreate_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",
+		},
+		{
+			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 := &CmdListenerCreate{}
+
+		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_delete_test.go b/internal/cmd/skupper/listener/nonkube/listener_delete_test.go
index 6072c5b0..6405fc0f 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_delete_test.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_delete_test.go
@@ -1,1 +1,168 @@
 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/v3/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestCmdListenerDelete_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		flags             *common.CommandListenerDeleteFlags
+		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.InputSiteStatePath))
+
+	testTable := []test{
+		{
+			name:           "listener name is not specified",
+			args:           []string{},
+			flags:          &common.CommandListenerDeleteFlags{},
+			expectedErrors: []string{"listener name must be specified"},
+		},
+		{
+			name:           "listener name is nil",
+			args:           []string{""},
+			flags:          &common.CommandListenerDeleteFlags{},
+			expectedErrors: []string{"listener name must not be empty"},
+		},
+		{
+			name:           "listener name is not valid",
+			args:           []string{"my name"},
+			flags:          &common.CommandListenerDeleteFlags{},
+			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:           "more than one argument is specified",
+			args:           []string{"my", "listener"},
+			flags:          &common.CommandListenerDeleteFlags{},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name:           "listener doesn't exist",
+			args:           []string{"no-listener"},
+			flags:          &common.CommandListenerDeleteFlags{},
+			expectedErrors: []string{"listener no-listener does not exist"},
+		},
+		{
+			name:           "kubernetes flags are not valid on this platform",
+			args:           []string{"my-listener"},
+			flags:          &common.CommandListenerDeleteFlags{},
+			expectedErrors: []string{},
+			cobraGenericFlags: map[string]string{
+				common.FlagNameContext:    "test",
+				common.FlagNameKubeconfig: "test",
+			},
+		},
+	}
+
+	// Add a temp file so listener exists for delete tests to pass
+	listenerResource := v2alpha1.Listener{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Listener",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-listener",
+			Namespace: "test",
+		},
+	}
+
+	command := &CmdListenerDelete{Flags: &common.CommandListenerDeleteFlags{}}
+	command.CobraCmd = &cobra.Command{Use: "test"}
+	command.namespace = "test"
+	command.listenerHandler = fs.NewListenerHandler(command.namespace)
+
+	defer command.listenerHandler.Delete("my-listener")
+	content, err := command.listenerHandler.EncodeToYaml(listenerResource)
+	assert.Check(t, err == nil)
+	err = command.listenerHandler.WriteFile(path, "my-listener.yaml", content, common.Listeners)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			command.listenerName = ""
+			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 TestCmdListenerDelete_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-listener",
+			skupperErrorMessage: "error",
+			errorMessage:        "error",
+		},
+		{
+			name:                "run fails",
+			namespace:           "test",
+			deleteName:          "my-listener",
+			skupperErrorMessage: "error",
+			errorMessage:        "error",
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := &CmdListenerDelete{}
+
+		t.Run(test.name, func(t *testing.T) {
+
+			cmd.listenerName = test.deleteName
+			cmd.namespace = test.namespace
+			cmd.listenerHandler = fs.NewListenerHandler(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/listener/nonkube/listener_status_test.go b/internal/cmd/skupper/listener/nonkube/listener_status_test.go
index 6072c5b0..a9d92ede 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_status_test.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_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/v3/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestCmdListenerStatus_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		flags             *common.CommandListenerStatusFlags
+		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:           "listener is not shown because listener does not exist in the namespace",
+			args:           []string{"no-listener"},
+			flags:          &common.CommandListenerStatusFlags{},
+			expectedErrors: []string{"listener no-listener does not exist in namespace test"},
+		},
+		{
+			name:           "listener name is nil",
+			args:           []string{""},
+			flags:          &common.CommandListenerStatusFlags{},
+			expectedErrors: []string{"listener name must not be empty"},
+		},
+		{
+			name:           "more than one argument is specified",
+			args:           []string{"my", "listener"},
+			flags:          &common.CommandListenerStatusFlags{},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name:           "listener name is not valid.",
+			args:           []string{"my new listener"},
+			flags:          &common.CommandListenerStatusFlags{},
+			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:           "no args",
+			flags:          &common.CommandListenerStatusFlags{},
+			expectedErrors: []string{},
+		},
+		{
+			name:           "bad output status",
+			args:           []string{"my-listener"},
+			flags:          &common.CommandListenerStatusFlags{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-listener"},
+			flags:          &common.CommandListenerStatusFlags{Output: "json"},
+			expectedErrors: []string{},
+		},
+	}
+
+	//Add a temp file so listener exists for status tests
+	listenerResource := v2alpha1.Listener{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Listener",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-listener",
+			Namespace: "test",
+		},
+	}
+
+	command := &CmdListenerStatus{}
+	command.namespace = "test"
+	command.listenerHandler = fs.NewListenerHandler(command.namespace)
+
+	defer command.listenerHandler.Delete("my-listener")
+	content, err := command.listenerHandler.EncodeToYaml(listenerResource)
+	assert.Check(t, err == nil)
+	err = command.listenerHandler.WriteFile(path, "my-listener.yaml", content, common.Listeners)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			command.listenerName = ""
+
+			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 TestCmdListenerStatus_Run(t *testing.T) {
+	type test struct {
+		name                string
+		listenerName        string
+		flags               common.CommandListenerStatusFlags
+		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 listener doesn't exist",
+			listenerName: "no-listener",
+			errorMessage: "failed to read file: open " + path + "/listeners/no-listener.yaml: no such file or directory",
+		},
+		{
+			name:         "runs ok, returns 1 listeners",
+			listenerName: "my-listener",
+		},
+		{
+			name:         "runs ok, returns 1 listeners yaml",
+			listenerName: "my-listener",
+			flags:        common.CommandListenerStatusFlags{Output: "yaml"},
+		},
+		{
+			name: "runs ok, returns all listeners",
+		},
+		{
+			name:  "runs ok, returns all listeners json",
+			flags: common.CommandListenerStatusFlags{Output: "json"},
+		},
+		{
+			name:         "runs ok, returns all listeners output bad",
+			flags:        common.CommandListenerStatusFlags{Output: "bad-value"},
+			errorMessage: "format bad-value not supported",
+		},
+		{
+			name:         "runs ok, returns 1 listeners bad output",
+			listenerName: "my-listener",
+			flags:        common.CommandListenerStatusFlags{Output: "bad-value"},
+			errorMessage: "format bad-value not supported",
+		},
+	}
+
+	//Add a temp file so listener exists for status tests
+	listenerResource1 := v2alpha1.Listener{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Listener",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-listener",
+			Namespace: "test",
+		},
+		Spec: v2alpha1.ListenerSpec{
+			Host:       "1.2.3.4",
+			Port:       8080,
+			RoutingKey: "backend-8080",
+		},
+		Status: v2alpha1.ListenerStatus{
+			Status: v2alpha1.Status{
+				Conditions: []metav1.Condition{
+					{
+						Type:   "Configured",
+						Status: "True",
+					},
+				},
+			},
+		},
+	}
+	listenerResource2 := v2alpha1.Listener{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Listener",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-listener2",
+			Namespace: "test",
+		},
+		Spec: v2alpha1.ListenerSpec{
+			Host:       "1.1.1.1",
+			Port:       9999,
+			RoutingKey: "test-9999",
+		},
+		Status: v2alpha1.ListenerStatus{
+			Status: v2alpha1.Status{
+				Conditions: []metav1.Condition{
+					{
+						Type:   "Configured",
+						Status: "True",
+					},
+				},
+			},
+		},
+	}
+
+	// add two listeners in runtime directory
+	command := &CmdListenerStatus{}
+	command.namespace = "test"
+	command.listenerHandler = fs.NewListenerHandler(command.namespace)
+
+	defer command.listenerHandler.Delete("my-listener")
+	defer command.listenerHandler.Delete("my-listener2")
+
+	path = filepath.Join(homeDir, "/.local/share/skupper/namespaces/test/", string(api.RuntimeSiteStatePath))
+	content, err := command.listenerHandler.EncodeToYaml(listenerResource1)
+	assert.Check(t, err == nil)
+	err = command.listenerHandler.WriteFile(path, "my-listener.yaml", content, common.Listeners)
+	assert.Check(t, err == nil)
+
+	content, err = command.listenerHandler.EncodeToYaml(listenerResource2)
+	assert.Check(t, err == nil)
+	err = command.listenerHandler.WriteFile(path, "my-listener2.yaml", content, common.Listeners)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		command.listenerName = test.listenerName
+		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 TestCmdListenerStatus_RunNoDirectory(t *testing.T) {
+	type test struct {
+		name                string
+		flags               common.CommandListenerStatusFlags
+		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 + "/listeners/my-listener.yaml: no such file or directory",
+		},
+	}
+
+	for _, test := range testTable {
+		command := &CmdListenerStatus{}
+		command.namespace = "test1"
+		command.listenerHandler = fs.NewListenerHandler(command.namespace)
+		command.listenerName = "my-listener"
+		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/listener/nonkube/listener_update_test.go b/internal/cmd/skupper/listener/nonkube/listener_update_test.go
index 6072c5b0..91d08ca2 100644
--- a/internal/cmd/skupper/listener/nonkube/listener_update_test.go
+++ b/internal/cmd/skupper/listener/nonkube/listener_update_test.go
@@ -1,1 +1,235 @@
 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/v3/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+func TestCmdListenerUpdate_ValidateInput(t *testing.T) {
+	type test struct {
+		name              string
+		args              []string
+		flags             *common.CommandListenerUpdateFlags
+		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:           "Listener is not updated because get listener returned error",
+			args:           []string{"no-listener"},
+			flags:          &common.CommandListenerUpdateFlags{},
+			expectedErrors: []string{"listener no-listener must exist in namespace test to be updated"},
+		},
+		{
+			name:           "listener name is not specified",
+			args:           []string{},
+			flags:          &common.CommandListenerUpdateFlags{},
+			expectedErrors: []string{"listener name must be configured"},
+		},
+		{
+			name:           "listener name is nil",
+			args:           []string{""},
+			flags:          &common.CommandListenerUpdateFlags{},
+			expectedErrors: []string{"listener name must not be empty"},
+		},
+		{
+			name:           "more than one argument is specified",
+			args:           []string{"my", "listener"},
+			flags:          &common.CommandListenerUpdateFlags{},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name:           "listener name is not valid.",
+			args:           []string{"my new listener"},
+			flags:          &common.CommandListenerUpdateFlags{},
+			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 type is not valid",
+			args:           []string{"my-listener"},
+			flags:          &common.CommandListenerUpdateFlags{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"},
+			flags:          &common.CommandListenerUpdateFlags{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:           "TlsCredentials key is not valid",
+			args:           []string{"my-listener"},
+			flags:          &common.CommandListenerUpdateFlags{TlsCredentials: "not-valid$", Host: "1.2.3.4"},
+			expectedErrors: []string{"tlsCredentials value 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"},
+			flags:          &common.CommandListenerUpdateFlags{Port: -1},
+			expectedErrors: []string{"listener port is not valid: value is not positive"},
+		},
+		{
+			name:           "host is not valid",
+			args:           []string{"my-listener"},
+			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"},
+			flags: &common.CommandListenerUpdateFlags{},
+			cobraGenericFlags: map[string]string{
+				common.FlagNameContext:    "test",
+				common.FlagNameKubeconfig: "test",
+			},
+			expectedErrors: []string{},
+		},
+		{
+			name: "flags all valid",
+			args: []string{"my-listener"},
+			flags: &common.CommandListenerUpdateFlags{
+				RoutingKey:     "routingkeyname",
+				TlsCredentials: "secretname",
+				Port:           1234,
+				ListenerType:   "tcp",
+				Output:         "json",
+				Host:           "1.2.3.4",
+			},
+			expectedErrors: []string{},
+		},
+	}
+
+	// Add a temp file so listener exists for update tests to pass
+	listenerResource := v2alpha1.Listener{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v2alpha1",
+			Kind:       "Listener",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "my-listener",
+			Namespace: "test",
+		},
+	}
+
+	command := &CmdListenerUpdate{Flags: &common.CommandListenerUpdateFlags{}}
+	command.CobraCmd = &cobra.Command{Use: "test"}
+	command.namespace = "test"
+	command.listenerHandler = fs.NewListenerHandler(command.namespace)
+
+	defer command.listenerHandler.Delete("my-listener")
+	content, err := command.listenerHandler.EncodeToYaml(listenerResource)
+	assert.Check(t, err == nil)
+	err = command.listenerHandler.WriteFile(path, "my-listener.yaml", content, common.Listeners)
+	assert.Check(t, err == nil)
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+			command.listenerName = ""
+
+			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 TestCmdListenerUpdate_Run(t *testing.T) {
+	type test struct {
+		name                string
+		namespace           string
+		k8sObjects          []runtime.Object
+		skupperObjects      []runtime.Object
+		skupperErrorMessage string
+		errorMessage        string
+		listenerName        string
+		host                string
+		output              string
+		routingKey          string
+		tlsCredentials      string
+		listenerType        string
+		port                int
+	}
+
+	testTable := []test{
+		{
+			name:           "runs ok",
+			namespace:      "test",
+			listenerName:   "my-listener",
+			port:           8080,
+			listenerType:   "tcp",
+			host:           "hostname",
+			routingKey:     "keyname",
+			tlsCredentials: "secretname",
+		},
+		{
+			name:           "run output json",
+			listenerName:   "my-listener",
+			port:           8181,
+			listenerType:   "tcp",
+			host:           "hostname",
+			routingKey:     "keyname",
+			tlsCredentials: "secretname",
+			output:         "json",
+		},
+	}
+
+	for _, test := range testTable {
+		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
+		command.newSettings.tlsCredentials = test.tlsCredentials
+		command.namespace = test.namespace
+		command.listenerHandler = fs.NewListenerHandler(command.namespace)
+		defer command.listenerHandler.Delete("my-listener")
+		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)
+			}
+		})
+	}
+}
diff --git a/internal/cmd/skupper/site/kube/site_delete_test.go b/internal/cmd/skupper/site/kube/site_delete_test.go
index 6006e550..6b87e055 100644
--- a/internal/cmd/skupper/site/kube/site_delete_test.go
+++ b/internal/cmd/skupper/site/kube/site_delete_test.go
@@ -1,11 +1,12 @@
 package kube
 
 import (
-	"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"
@@ -251,6 +252,7 @@ func TestCmdSiteDelete_InputToOptions(t *testing.T) {
 		flags           *common.CommandSiteDeleteFlags
 		expectedTimeout time.Duration
 		expectedWait    bool
+		expectedAll     bool
 	}
 
 	testTable := []test{
@@ -264,6 +266,11 @@ func TestCmdSiteDelete_InputToOptions(t *testing.T) {
 			flags:        &common.CommandSiteDeleteFlags{Wait: true},
 			expectedWait: true,
 		},
+		{
+			name:        "all site resources",
+			flags:       &common.CommandSiteDeleteFlags{All: true},
+			expectedAll: true,
+		},
 	}
 
 	for _, test := range testTable {
@@ -276,6 +283,7 @@ func TestCmdSiteDelete_InputToOptions(t *testing.T) {
 
 			assert.Check(t, command.timeout == test.expectedTimeout)
 			assert.Check(t, command.wait == test.expectedWait)
+			assert.Check(t, command.all == test.expectedAll)
 		})
 	}
 }
@@ -288,6 +296,7 @@ func TestCmdSiteDelete_Run(t *testing.T) {
 		skupperError   string
 		siteName       string
 		errorMessage   string
+		all            bool
 	}
 
 	testTable := []test{
@@ -327,6 +336,101 @@ func TestCmdSiteDelete_Run(t *testing.T) {
 			siteName:       "my-site",
 			errorMessage:   "sites.skupper.io \"my-site\" not found",
 		},
+		{
+			name:       "deletes all",
+			all:        true,
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Message: "OK",
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
+				},
+				&v2alpha1.AccessGrant{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-token",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.AccessToken{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-token",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.Connector{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-connector",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.Listener{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-listener",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "the-site",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.Certificate{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "link-test",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.Link{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-link",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.AttachedConnectorBinding{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-attachedConnectorBinding",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.AttachedConnector{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-attachedConnector",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.RouterAccess{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-routerAccess",
+						Namespace: "test",
+					},
+				},
+				&v2alpha1.SecuredAccess{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-securedAccess",
+						Namespace: "test",
+					},
+				},
+			},
+			skupperError: "",
+			siteName:     "my-site",
+		},
 	}
 
 	for _, test := range testTable {
@@ -337,7 +441,7 @@ func TestCmdSiteDelete_Run(t *testing.T) {
 		fakeSkupperClient, err := fakeclient.NewFakeClient(command.Namespace, test.k8sObjects, test.skupperObjects, test.skupperError)
 		assert.Assert(t, err)
 		command.Client = fakeSkupperClient.GetSkupperClient().SkupperV2alpha1()
-
+		command.all = test.all
 		command.siteName = test.siteName
 
 		t.Run(test.name, func(t *testing.T) {
diff --git a/internal/cmd/skupper/site/kube/site_update_test.go b/internal/cmd/skupper/site/kube/site_update_test.go
index a85c6dba..a96da0ad 100644
--- a/internal/cmd/skupper/site/kube/site_update_test.go
+++ b/internal/cmd/skupper/site/kube/site_update_test.go
@@ -574,7 +574,7 @@ func TestCmdSiteUpdate_WaitUntil(t *testing.T) {
 			},
 			siteName:     "my-site",
 			skupperError: "",
-			errorMessage: "Site \"my-site\" is not ready yet, check the status for more information\n",
+			errorMessage: "Site \"my-site\" is not yet ready, check the status for more information\n",
 			expectError:  true,
 		},
 		{
@@ -662,7 +662,7 @@ func TestCmdSiteUpdate_WaitUntil(t *testing.T) {
 			siteName:     "my-site",
 			skupperError: "",
 			expectError:  true,
-			errorMessage: "Site \"my-site\" is configured with errors, check the status for more information\n",
+			errorMessage: "Site \"my-site\" is not yet configured: Error\n",
 		},
 	}
 
diff --git a/internal/cmd/skupper/site/site_test.go b/internal/cmd/skupper/site/site_test.go
index 53339e1a..9cbe32d2 100644
--- a/internal/cmd/skupper/site/site_test.go
+++ b/internal/cmd/skupper/site/site_test.go
@@ -1,12 +1,13 @@
 package site
 
 import (
+	"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 TestCmdSiteFactory(t *testing.T) {
@@ -51,6 +52,7 @@ func TestCmdSiteFactory(t *testing.T) {
 			expectedFlagsWithDefaultValue: map[string]interface{}{
 				common.FlagNameTimeout: "1m0s",
 				common.FlagNameWait:    "true",
+				common.FlagNameAll:     "false",
 			},
 			command: CmdSiteDeleteFactory(types.PlatformKubernetes),
 		},