Repository: skupperproject/skupper
Pull request #1510 (2024-06-17 19:48:09)

site commands: status, delete and update
Implementation of the remaining site commands:
- skupper site create: allow to export the resource in yaml/json format
- skupper site update
- skupper site delete
- skupper site status

Because the commands are implementations suitable to kubernetes, they were moved to the kube package.


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0c27e108..4964f845 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -458,7 +458,7 @@ jobs:
                 exit 1
               fi
       - go/mod-download-cached
-      - run: make generate-client
+      - run: make
       - run:
           name: Creating artifacts directory
           command: mkdir /tmp/artifacts
@@ -506,7 +506,7 @@ jobs:
     steps:
       - checkout
       - go/mod-download-cached
-      - run: make generate-client
+      - run: make
 
       - compile_go_program:
           goos: linux
diff --git a/Makefile b/Makefile
index 90de626b..361026f2 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ PLATFORMS ?= linux/amd64,linux/arm64
 GOOS ?= linux
 GOARCH ?= amd64
 
-all: generate-client build-cmd build-get build-config-sync build-controllers build-tests build-manifest
+all: build-cmd build-get build-config-sync build-controllers build-tests build-manifest
 
 build-tests:
 	mkdir -p ${TEST_BINARIES_FOLDER}
@@ -24,7 +24,7 @@ build-tests:
 	GOOS=${GOOS} GOARCH=${GOARCH} go test -c -tags=job -v ./test/integration/examples/custom/hipstershop/job -o ${TEST_BINARIES_FOLDER}/grpcclient_test
 	GOOS=${GOOS} GOARCH=${GOARCH} go test -c -tags=job -v ./test/integration/examples/tls_t/job -o ${TEST_BINARIES_FOLDER}/tls_test
 
-build-cmd: generate-client
+build-cmd:
 	GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags="${LDFLAGS}"  -o skupper ./cmd/skupper
 
 build-get:
@@ -61,7 +61,7 @@ docker-build-test-image:
 	${DOCKER} buildx build --platform ${PLATFORMS} -t ${TEST_IMAGE} -f Dockerfile.ci-test .
 	${DOCKER} buildx build --load -t ${TEST_IMAGE} -f Dockerfile.ci-test .
 
-docker-build: generate-client docker-build-test-image
+docker-build: docker-build-test-image
 	${DOCKER} buildx build --platform ${PLATFORMS} -t ${SERVICE_CONTROLLER_IMAGE} -f Dockerfile.service-controller .
 	${DOCKER} buildx build --load  -t ${SERVICE_CONTROLLER_IMAGE} -f Dockerfile.service-controller .
 	${DOCKER} buildx build --platform ${PLATFORMS} -t ${CONTROLLER_PODMAN_IMAGE} -f Dockerfile.controller-podman .
@@ -88,11 +88,9 @@ format:
 
 generate-client:
 	./scripts/update-codegen.sh
-	./scripts/libpod-generate.sh
 
 force-generate-client:
 	FORCE=true ./scripts/update-codegen.sh
-	FORCE=true ./scripts/libpod-generate.sh
 
 client-mock-test:
 	go test -v -count=1 ./client
diff --git a/client/compat/container.go b/client/compat/container.go
index 549c7b0c..b92b7e89 100644
--- a/client/compat/container.go
+++ b/client/compat/container.go
@@ -13,9 +13,9 @@ import (
 	"github.com/go-openapi/runtime"
 	runtimeclient "github.com/go-openapi/runtime/client"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/containers_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/exec_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/containers_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/exec_compat"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/compat/container_mock.go b/client/compat/container_mock.go
index c5e370dc..f357e0a7 100644
--- a/client/compat/container_mock.go
+++ b/client/compat/container_mock.go
@@ -13,11 +13,11 @@ import (
 	"github.com/go-openapi/strfmt"
 	"github.com/google/uuid"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/containers_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/networks_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/system_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/containers_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/networks_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/system_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes_compat"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/config"
 	"github.com/skupperproject/skupper/pkg/container"
 )
diff --git a/client/compat/image.go b/client/compat/image.go
index b9e55f77..bcd5f35f 100644
--- a/client/compat/image.go
+++ b/client/compat/image.go
@@ -11,7 +11,7 @@ import (
 	"time"
 
 	"github.com/go-openapi/runtime"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/images_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/images_compat"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/compat/network.go b/client/compat/network.go
index 928d6dea..409aae00 100644
--- a/client/compat/network.go
+++ b/client/compat/network.go
@@ -7,8 +7,8 @@ import (
 	"github.com/go-openapi/runtime"
 	"github.com/go-openapi/strfmt"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/networks_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/networks_compat"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 	"github.com/skupperproject/skupper/pkg/utils"
 )
diff --git a/client/compat/parser.go b/client/compat/parser.go
index f41fde96..e0a399c9 100644
--- a/client/compat/parser.go
+++ b/client/compat/parser.go
@@ -7,7 +7,7 @@ import (
 	"regexp"
 
 	"github.com/go-openapi/runtime"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 )
 
 // boolTrue returns a true bool pointer (for false, just use new(bool))
diff --git a/client/compat/version.go b/client/compat/version.go
index b13fd4e6..d6839c53 100644
--- a/client/compat/version.go
+++ b/client/compat/version.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	system "github.com/skupperproject/skupper/client/generated/libpod/client/system_compat"
+	system "github.com/skupperproject/skupper-libpod/v4/client/system_compat"
 	"github.com/skupperproject/skupper/pkg/container"
 	"github.com/skupperproject/skupper/pkg/utils"
 )
diff --git a/client/compat/volume.go b/client/compat/volume.go
index 044e2f0c..8549d2cf 100644
--- a/client/compat/volume.go
+++ b/client/compat/volume.go
@@ -7,8 +7,8 @@ import (
 	"github.com/go-openapi/runtime"
 	"github.com/go-openapi/strfmt"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes_compat"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes_compat"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/podman/container.go b/client/podman/container.go
index 5929a375..441b711b 100644
--- a/client/podman/container.go
+++ b/client/podman/container.go
@@ -11,9 +11,9 @@ import (
 	"github.com/go-openapi/runtime"
 	runtimeclient "github.com/go-openapi/runtime/client"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/containers"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/exec"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/containers"
+	"github.com/skupperproject/skupper-libpod/v4/client/exec"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/podman/container_mock.go b/client/podman/container_mock.go
index 93bc2074..f62ac742 100644
--- a/client/podman/container_mock.go
+++ b/client/podman/container_mock.go
@@ -11,11 +11,11 @@ import (
 	"github.com/go-openapi/runtime"
 	"github.com/go-openapi/strfmt"
 	"github.com/google/uuid"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/containers"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/networks"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/system"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/containers"
+	"github.com/skupperproject/skupper-libpod/v4/client/networks"
+	"github.com/skupperproject/skupper-libpod/v4/client/system"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/podman/image.go b/client/podman/image.go
index 23ce2f43..9238147d 100644
--- a/client/podman/image.go
+++ b/client/podman/image.go
@@ -9,7 +9,7 @@ import (
 	"strings"
 
 	"github.com/go-openapi/runtime"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/images"
+	"github.com/skupperproject/skupper-libpod/v4/client/images"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/podman/network.go b/client/podman/network.go
index 025d2146..1b5eada0 100644
--- a/client/podman/network.go
+++ b/client/podman/network.go
@@ -6,8 +6,8 @@ import (
 
 	"github.com/go-openapi/strfmt"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/networks"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/networks"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 	"github.com/skupperproject/skupper/pkg/utils"
 )
diff --git a/client/podman/rest.go b/client/podman/rest.go
index c373b2ed..e25634c7 100644
--- a/client/podman/rest.go
+++ b/client/podman/rest.go
@@ -17,7 +17,7 @@ import (
 	"github.com/go-openapi/runtime"
 	runtimeclient "github.com/go-openapi/runtime/client"
 	"github.com/go-openapi/strfmt"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/config"
 	"github.com/skupperproject/skupper/pkg/utils"
 )
diff --git a/client/podman/version.go b/client/podman/version.go
index a9446618..92944550 100644
--- a/client/podman/version.go
+++ b/client/podman/version.go
@@ -1,7 +1,7 @@
 package podman
 
 import (
-	"github.com/skupperproject/skupper/client/generated/libpod/client/system"
+	"github.com/skupperproject/skupper-libpod/v4/client/system"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/client/podman/volume.go b/client/podman/volume.go
index 154a8c32..f64ef3bb 100644
--- a/client/podman/volume.go
+++ b/client/podman/volume.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes"
-	"github.com/skupperproject/skupper/client/generated/libpod/models"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes"
+	"github.com/skupperproject/skupper-libpod/v4/models"
 	"github.com/skupperproject/skupper/pkg/container"
 )
 
diff --git a/go.mod b/go.mod
index 37833c7b..a7eb4381 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/skupperproject/skupper
 
-go 1.21
+go 1.21.0
 
 require (
 	github.com/briandowns/spinner v1.23.0
@@ -22,6 +22,7 @@ require (
 	github.com/prometheus/client_golang v1.14.0
 	github.com/rogpeppe/go-internal v1.3.0
 	github.com/skupperproject/skupper-example-tcp-echo v0.0.0-20210727195922-db4a7dc7b35d
+	github.com/skupperproject/skupper-libpod/v4 v4.0.3-0
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/pflag v1.0.5
 	github.com/tsenart/vegeta/v12 v12.8.3
diff --git a/go.sum b/go.sum
index 094b4080..d43e179a 100644
--- a/go.sum
+++ b/go.sum
@@ -443,6 +443,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/skupperproject/skupper-example-tcp-echo v0.0.0-20210727195922-db4a7dc7b35d h1:XmA9DC9Ffu1gnNAJZyjjoNImRs+iI5XBf/30YbqXVcs=
 github.com/skupperproject/skupper-example-tcp-echo v0.0.0-20210727195922-db4a7dc7b35d/go.mod h1:+Hm/U62PKiUAFpD3RTrjogZnPA0W2ZIDRG9+Cf7qjSU=
+github.com/skupperproject/skupper-libpod/v4 v4.0.3-0 h1:hhnfTCM0Re3bnQbdeC+ff3rJI4KTbqfr0H16HrGTcac=
+github.com/skupperproject/skupper-libpod/v4 v4.0.3-0/go.mod h1:ToBI6fKBtixpugNsnHZ3GqVh6mXiETXXwSJVOWqUvNk=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
diff --git a/internal/cmd/skupper/root/root.go b/internal/cmd/skupper/root/root.go
index 191a9d24..87dc8132 100644
--- a/internal/cmd/skupper/root/root.go
+++ b/internal/cmd/skupper/root/root.go
@@ -19,13 +19,17 @@ type SkupperCommand interface {
 	WaitUntilReady() error
 }
 
+var SelectedNamespace string
+var SelectedContext string
+var KubeConfigPath string
+
 func NewSkupperRootCommand() *cobra.Command {
 
 	rootCmd := &cobra.Command{
 		Use:   "skupper",
 		Short: "Skupper is a tool for secure, cross-cluster Kubernetes communication",
 		Long: `Skupper is an open-source tool that enables secure communication across clusters with no VPNs or special firewall rules.
-For more information visit https://skupper.io`,
+For more information visit https://skupperproject.github.io/refdog/index.html`,
 	}
 
 	rootCmd.AddCommand(site.NewCmdSite())
@@ -34,8 +38,10 @@ For more information visit https://skupper.io`,
 	rootCmd.AddCommand(link.NewCmdLink())
 	rootCmd.AddCommand(connector.NewCmdConnector())
 
-	//TODO: Add persistent flags for context and namespace
-	rootCmd.PersistentFlags().StringVarP(&config.Platform, "platform", "p", "", "The platform type to use [kubernetes, podman]")
+	rootCmd.PersistentFlags().StringVarP(&SelectedNamespace, "namespace", "n", "", "Set the namespace")
+	rootCmd.PersistentFlags().StringVarP(&SelectedContext, "context", "c", "", "Set the kubeconfig context")
+	rootCmd.PersistentFlags().StringVarP(&KubeConfigPath, "kubeconfig", "", "", "Path to the kubeconfig file to use")
+	rootCmd.PersistentFlags().StringVarP(&config.Platform, "platform", "p", "", "Set the platform type to use [kubernetes, podman, docker, systemd]")
 
 	return rootCmd
 }
diff --git a/internal/cmd/skupper/site/site_create.go b/internal/cmd/skupper/site/kube/site_create.go
similarity index 79%
rename from internal/cmd/skupper/site/site_create.go
rename to internal/cmd/skupper/site/kube/site_create.go
index eb254eff..b89d6933 100644
--- a/internal/cmd/skupper/site/site_create.go
+++ b/internal/cmd/skupper/site/kube/site_create.go
@@ -1,7 +1,7 @@
 /*
 Copyright © 2024 Skupper Team <skupper@googlegroups.com>
 */
-package site
+package kube
 
 import (
 	"context"
@@ -20,15 +20,13 @@ var (
 	siteCreateLong = `A site is a place where components of your application are running. 
 Sites are linked to form application networks.
 There can be only one site definition per namespace.`
-
-	linkAccessTypes = []string{"route", "loadbalancer", "default"}
 )
 
 type CreateFlags struct {
 	enableLinkAccess bool
 	linkAccessType   string
-	linkAccessHost   string
 	serviceAccount   string
+	output           string
 }
 
 type CmdSiteCreate struct {
@@ -40,6 +38,7 @@ type CmdSiteCreate struct {
 	serviceAccountName string
 	Namespace          string
 	linkAccessType     string
+	output             string
 }
 
 func NewCmdSiteCreate() *CmdSiteCreate {
@@ -69,7 +68,7 @@ func NewCmdSiteCreate() *CmdSiteCreate {
 }
 
 func (cmd *CmdSiteCreate) NewClient(cobraCommand *cobra.Command, args []string) {
-	cli, err := client.NewClient("", "", "")
+	cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String())
 	utils.HandleError(err)
 
 	cmd.Client = cli.GetSkupperClient().SkupperV1alpha1()
@@ -81,15 +80,16 @@ func (cmd *CmdSiteCreate) AddFlags() {
 	cmd.CobraCmd.Flags().StringVar(&cmd.flags.linkAccessType, "link-access-type", "", `configure external access for links from remote sites.
 Choices: [route|loadbalancer]. Default: On OpenShift, route is the default; 
 for other Kubernetes flavors, loadbalancer is the default.`)
-	cmd.CobraCmd.Flags().StringVar(&cmd.flags.linkAccessHost, "link-access-host", "", "the host or IP address at which to expose link access")
 	cmd.CobraCmd.Flags().StringVar(&cmd.flags.serviceAccount, "service-account", "skupper-controller", "the Kubernetes service account under which to run the Skupper controller")
+	cmd.CobraCmd.Flags().StringVarP(&cmd.flags.output, "output", "o", "", "print resources to the console instead of submitting them to the Skupper controller. Choices: json, yaml")
 }
 
 func (cmd *CmdSiteCreate) ValidateInput(args []string) []error {
 
 	var validationErrors []error
 	resourceStringValidator := validator.NewResourceStringValidator()
-	linkAccessTypeValidator := validator.NewOptionValidator(linkAccessTypes)
+	linkAccessTypeValidator := validator.NewOptionValidator(utils.LinkAccessTypes)
+	outputTypeValidator := validator.NewOptionValidator(utils.OutputTypes)
 
 	//Validate if there is already a site defined in the namespace
 	siteList, _ := cmd.Client.Sites(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
@@ -128,6 +128,13 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error {
 		}
 	}
 
+	if cmd.flags.output != "" {
+		ok, err := outputTypeValidator.Evaluate(cmd.flags.output)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("output type is not valid: %s", err))
+		}
+	}
+
 	return validationErrors
 }
 
@@ -148,18 +155,23 @@ func (cmd *CmdSiteCreate) InputToOptions() {
 	options := make(map[string]string)
 	options[site.SiteConfigNameKey] = cmd.siteName
 
-	if cmd.flags.linkAccessHost != "" {
-		options[site.SiteConfigIngressHostKey] = cmd.flags.linkAccessHost
-	}
-
 	cmd.options = options
 
+	cmd.output = cmd.flags.output
+
 }
 
 func (cmd *CmdSiteCreate) Run() error {
 
 	resource := v1alpha1.Site{
-		ObjectMeta: metav1.ObjectMeta{Name: cmd.siteName},
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v1alpha1",
+			Kind:       "Site",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      cmd.siteName,
+			Namespace: cmd.Namespace,
+		},
 		Spec: v1alpha1.SiteSpec{
 			Settings:       cmd.options,
 			ServiceAccount: cmd.serviceAccountName,
@@ -167,13 +179,27 @@ func (cmd *CmdSiteCreate) Run() error {
 		},
 	}
 
-	_, err := cmd.Client.Sites(cmd.Namespace).Create(context.TODO(), &resource, metav1.CreateOptions{})
-	return err
+	if cmd.output != "" {
+		encodedOutput, err := utils.Encode(cmd.output, resource)
+		fmt.Println(encodedOutput)
+
+		return err
+
+	} else {
+		_, err := cmd.Client.Sites(cmd.Namespace).Create(context.TODO(), &resource, metav1.CreateOptions{})
+		return err
+	}
+
 }
 
 func (cmd *CmdSiteCreate) WaitUntilReady() error {
 
-	err := utils.NewSpinner("Waiting for site...", 5, func() error {
+	// the site resource was not created
+	if cmd.output != "" {
+		return nil
+	}
+
+	err := utils.NewSpinner("Waiting for status...", 5, func() error {
 
 		resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
 		if err != nil {
diff --git a/internal/cmd/skupper/site/kube/site_delete.go b/internal/cmd/skupper/site/kube/site_delete.go
new file mode 100644
index 00000000..b3a5da0f
--- /dev/null
+++ b/internal/cmd/skupper/site/kube/site_delete.go
@@ -0,0 +1,121 @@
+package kube
+
+import (
+	"context"
+	"fmt"
+	"github.com/skupperproject/skupper/client"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
+	skupperv1alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1"
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var (
+	siteDeleteLong = `Delete a site by name`
+)
+
+type CmdSiteDelete struct {
+	Client    skupperv1alpha1.SkupperV1alpha1Interface
+	CobraCmd  cobra.Command
+	Namespace string
+	siteName  string
+}
+
+func NewCmdSiteDelete() *CmdSiteDelete {
+
+	skupperCmd := CmdSiteDelete{}
+
+	cmd := cobra.Command{
+		Use:     "delete",
+		Short:   "Delete a site",
+		Long:    siteDeleteLong,
+		Example: "skupper site delete my-site",
+		PreRun:  skupperCmd.NewClient,
+		Run: func(cmd *cobra.Command, args []string) {
+			utils.HandleErrorList(skupperCmd.ValidateInput(args))
+			utils.HandleError(skupperCmd.Run())
+		},
+		PostRunE: func(cmd *cobra.Command, args []string) error {
+			return skupperCmd.WaitUntilReady()
+		},
+	}
+
+	skupperCmd.CobraCmd = cmd
+
+	return &skupperCmd
+}
+
+func (cmd *CmdSiteDelete) NewClient(cobraCommand *cobra.Command, args []string) {
+	cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String())
+	utils.HandleError(err)
+
+	cmd.Client = cli.GetSkupperClient().SkupperV1alpha1()
+	cmd.Namespace = cli.Namespace
+}
+
+func (cmd *CmdSiteDelete) AddFlags() {}
+
+func (cmd *CmdSiteDelete) ValidateInput(args []string) []error {
+	var validationErrors []error
+
+	//Validate if there is already a site defined in the namespace
+	siteList, err := cmd.Client.Sites(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+
+	if err != nil {
+		validationErrors = append(validationErrors, err)
+	} else if siteList == nil || (siteList != nil && len(siteList.Items) == 0) {
+		validationErrors = append(validationErrors, fmt.Errorf("there is no existing Skupper site resource to update"))
+	} else {
+
+		if len(args) > 1 {
+			validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command"))
+		} else if len(args) == 1 {
+
+			selectedSite := args[0]
+			for _, s := range siteList.Items {
+				if s.Name == selectedSite {
+					cmd.siteName = s.Name
+				}
+			}
+
+			if cmd.siteName == "" {
+				validationErrors = append(validationErrors, fmt.Errorf("site with name %q is not available", selectedSite))
+			}
+		} else if len(args) == 0 {
+			if len(siteList.Items) > 1 {
+				validationErrors = append(validationErrors, fmt.Errorf("site name is required because there are several sites in this namespace"))
+			} else if len(siteList.Items) == 1 {
+				cmd.siteName = siteList.Items[0].Name
+			}
+		}
+	}
+
+	return validationErrors
+}
+func (cmd *CmdSiteDelete) InputToOptions() {}
+
+func (cmd *CmdSiteDelete) Run() error {
+	err := cmd.Client.Sites(cmd.Namespace).Delete(context.TODO(), cmd.siteName, metav1.DeleteOptions{})
+	return err
+}
+func (cmd *CmdSiteDelete) WaitUntilReady() error {
+	err := utils.NewSpinner("Waiting for deletion to complete...", 5, func() error {
+
+		resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
+
+		if err == nil && resource != nil {
+			return fmt.Errorf("error deleting the resource")
+		} else {
+			return nil
+		}
+
+	})
+
+	if err != nil {
+		return fmt.Errorf("Site %q not deleted yet, check the logs for more information\n", cmd.siteName)
+	}
+
+	fmt.Printf("Site %q is deleted\n", cmd.siteName)
+
+	return nil
+}
diff --git a/internal/cmd/skupper/site/kube/site_status.go b/internal/cmd/skupper/site/kube/site_status.go
new file mode 100644
index 00000000..116dfa17
--- /dev/null
+++ b/internal/cmd/skupper/site/kube/site_status.go
@@ -0,0 +1,89 @@
+package kube
+
+import (
+	"context"
+	"fmt"
+	"github.com/skupperproject/skupper/client"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
+	skupperv1alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1"
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"os"
+	"text/tabwriter"
+)
+
+var (
+	siteStatusLong = `Display the current status of a site.`
+)
+
+type CmdSiteStatus struct {
+	Client    skupperv1alpha1.SkupperV1alpha1Interface
+	CobraCmd  cobra.Command
+	Namespace string
+}
+
+func NewCmdSiteStatus() *CmdSiteStatus {
+
+	skupperCmd := CmdSiteStatus{}
+
+	cmd := cobra.Command{
+		Use:     "status",
+		Short:   "Get the site status",
+		Long:    siteStatusLong,
+		Example: "skupper site status",
+		PreRun:  skupperCmd.NewClient,
+		Run: func(cmd *cobra.Command, args []string) {
+			utils.HandleErrorList(skupperCmd.ValidateInput(args))
+			utils.HandleError(skupperCmd.Run())
+		},
+	}
+
+	skupperCmd.CobraCmd = cmd
+
+	return &skupperCmd
+}
+
+func (cmd *CmdSiteStatus) NewClient(cobraCommand *cobra.Command, args []string) {
+	cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String())
+	utils.HandleError(err)
+
+	cmd.Client = cli.GetSkupperClient().SkupperV1alpha1()
+	cmd.Namespace = cli.Namespace
+}
+func (cmd *CmdSiteStatus) AddFlags() {}
+func (cmd *CmdSiteStatus) ValidateInput(args []string) []error {
+	var validationErrors []error
+
+	if len(args) > 0 {
+		validationErrors = append(validationErrors, fmt.Errorf("this command does not need any arguments"))
+	}
+
+	return validationErrors
+}
+
+func (cmd *CmdSiteStatus) InputToOptions() {}
+func (cmd *CmdSiteStatus) Run() error {
+
+	siteList, err := cmd.Client.Sites(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+
+	if err != nil {
+		return err
+	}
+
+	if siteList != nil && len(siteList.Items) == 0 {
+		fmt.Println("There is no existing Skupper site resource")
+		return nil
+	}
+
+	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
+	fmt.Fprintln(writer, "NAME\tSTATUS")
+
+	for _, site := range siteList.Items {
+		fmt.Fprintf(writer, "%s\t%s", site.Name, site.Status.StatusMessage)
+		fmt.Fprintln(writer)
+	}
+
+	writer.Flush()
+	return nil
+}
+func (cmd *CmdSiteStatus) WaitUntilReady() error { return nil }
diff --git a/internal/cmd/skupper/site/kube/site_update.go b/internal/cmd/skupper/site/kube/site_update.go
new file mode 100644
index 00000000..3e3819e0
--- /dev/null
+++ b/internal/cmd/skupper/site/kube/site_update.go
@@ -0,0 +1,250 @@
+package kube
+
+import (
+	"context"
+	"fmt"
+	"github.com/skupperproject/skupper/client"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v1alpha1"
+	skupperv1alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1"
+	"github.com/skupperproject/skupper/pkg/site"
+	"github.com/skupperproject/skupper/pkg/utils/validator"
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var (
+	siteUpdateLong = `Change site settings of a given site.`
+)
+
+type UpdateFlags struct {
+	enableLinkAccess bool
+	linkAccessType   string
+	serviceAccount   string
+	output           string
+}
+
+type CmdSiteUpdate struct {
+	Client             skupperv1alpha1.SkupperV1alpha1Interface
+	CobraCmd           cobra.Command
+	flags              UpdateFlags
+	options            map[string]string
+	siteName           string
+	serviceAccountName string
+	Namespace          string
+	linkAccessType     string
+	output             string
+}
+
+func NewCmdSiteUpdate() *CmdSiteUpdate {
+
+	skupperCmd := CmdSiteUpdate{}
+
+	cmd := cobra.Command{
+		Use:     "update <name>",
+		Short:   "Change site settings",
+		Long:    siteUpdateLong,
+		Example: "skupper site update my-site --enable-link-access",
+		PreRun:  skupperCmd.NewClient,
+		Run: func(cmd *cobra.Command, args []string) {
+			utils.HandleErrorList(skupperCmd.ValidateInput(args))
+			skupperCmd.InputToOptions()
+			utils.HandleError(skupperCmd.Run())
+		},
+		PostRunE: func(cmd *cobra.Command, args []string) error {
+			return skupperCmd.WaitUntilReady()
+		},
+	}
+
+	skupperCmd.CobraCmd = cmd
+	skupperCmd.AddFlags()
+
+	return &skupperCmd
+}
+
+func (cmd *CmdSiteUpdate) NewClient(cobraCommand *cobra.Command, args []string) {
+	cli, err := client.NewClient(cobraCommand.Flag("namespace").Value.String(), cobraCommand.Flag("context").Value.String(), cobraCommand.Flag("kubeconfig").Value.String())
+	utils.HandleError(err)
+
+	cmd.Client = cli.GetSkupperClient().SkupperV1alpha1()
+	cmd.Namespace = cli.Namespace
+}
+
+func (cmd *CmdSiteUpdate) AddFlags() {
+	cmd.CobraCmd.Flags().BoolVar(&cmd.flags.enableLinkAccess, "enable-link-access", false, "allow access for incoming links from remote sites (default: false)")
+	cmd.CobraCmd.Flags().StringVar(&cmd.flags.linkAccessType, "link-access-type", "", `configure external access for links from remote sites.
+Choices: [route|loadbalancer]. Default: On OpenShift, route is the default; 
+for other Kubernetes flavors, loadbalancer is the default.`)
+	cmd.CobraCmd.Flags().StringVar(&cmd.flags.serviceAccount, "service-account", "skupper-controller", "the Kubernetes service account under which to run the Skupper controller")
+	cmd.CobraCmd.Flags().StringVarP(&cmd.flags.output, "output", "o", "", "print resources to the console instead of submitting them to the Skupper controller. Choices: json, yaml")
+}
+
+func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error {
+
+	var validationErrors []error
+	resourceStringValidator := validator.NewResourceStringValidator()
+	linkAccessTypeValidator := validator.NewOptionValidator(utils.LinkAccessTypes)
+	outputTypeValidator := validator.NewOptionValidator(utils.OutputTypes)
+
+	//Validate if there is already a site defined in the namespace
+	siteList, _ := cmd.Client.Sites(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
+	if siteList != nil && len(siteList.Items) == 0 {
+		validationErrors = append(validationErrors, fmt.Errorf("there is no existing Skupper site resource to update"))
+	} else {
+
+		if len(args) > 1 {
+			validationErrors = append(validationErrors, fmt.Errorf("only one argument is allowed for this command"))
+		} else if len(args) == 1 {
+
+			selectedSite := args[0]
+			for _, s := range siteList.Items {
+				if s.Name == selectedSite {
+					cmd.siteName = s.Name
+				}
+			}
+
+			if cmd.siteName == "" {
+				validationErrors = append(validationErrors, fmt.Errorf("site with name %q is not available", selectedSite))
+			}
+		} else if len(args) == 0 {
+			if len(siteList.Items) > 1 {
+				validationErrors = append(validationErrors, fmt.Errorf("site name is required because there are several sites in this namespace"))
+			} else if len(siteList.Items) == 1 {
+				cmd.siteName = siteList.Items[0].Name
+			}
+		}
+	}
+
+	if cmd.flags.linkAccessType != "" {
+		ok, err := linkAccessTypeValidator.Evaluate(cmd.flags.linkAccessType)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("link access type is not valid: %s", err))
+		}
+	}
+
+	if !cmd.flags.enableLinkAccess && len(cmd.flags.linkAccessType) > 0 {
+		validationErrors = append(validationErrors, fmt.Errorf("for the site to work with this type of linkAccess, the --enable-link-access option must be set to true"))
+	}
+
+	if cmd.flags.serviceAccount != "" {
+		ok, err := resourceStringValidator.Evaluate(cmd.flags.serviceAccount)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("service account name 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 *CmdSiteUpdate) InputToOptions() {
+	cmd.serviceAccountName = cmd.flags.serviceAccount
+
+	if cmd.flags.enableLinkAccess {
+		if cmd.flags.linkAccessType == "" {
+			cmd.linkAccessType = "default"
+		} else {
+			cmd.linkAccessType = cmd.flags.linkAccessType
+		}
+	} else {
+		cmd.linkAccessType = "none"
+	}
+
+	options := make(map[string]string)
+	if cmd.siteName != "" {
+		options[site.SiteConfigNameKey] = cmd.siteName
+	}
+
+	cmd.options = options
+
+	cmd.output = cmd.flags.output
+
+}
+func (cmd *CmdSiteUpdate) Run() error {
+
+	currentSite, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
+
+	if err != nil {
+		return err
+	}
+
+	updatedSettings := currentSite.Spec.Settings
+	if len(cmd.options) != 0 {
+		updatedSettings = cmd.options
+	}
+
+	updatedServiceAccount := currentSite.Spec.ServiceAccount
+	if cmd.serviceAccountName != "" {
+		updatedServiceAccount = cmd.serviceAccountName
+	}
+
+	updatedLinkAccessType := currentSite.Spec.LinkAccess
+	if cmd.linkAccessType != "" {
+		updatedLinkAccessType = cmd.linkAccessType
+	}
+
+	resource := v1alpha1.Site{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "skupper.io/v1alpha1",
+			Kind:       "Site",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:              currentSite.Name,
+			Namespace:         currentSite.Namespace,
+			CreationTimestamp: currentSite.CreationTimestamp,
+			ResourceVersion:   currentSite.ResourceVersion,
+		},
+		Spec: v1alpha1.SiteSpec{
+			Settings:       updatedSettings,
+			ServiceAccount: updatedServiceAccount,
+			LinkAccess:     updatedLinkAccessType,
+		},
+		Status: currentSite.Status,
+	}
+
+	if cmd.output != "" {
+		encodedOutput, err := utils.Encode(cmd.output, resource)
+		fmt.Println(encodedOutput)
+
+		return err
+
+	} else {
+		_, err := cmd.Client.Sites(cmd.Namespace).Update(context.TODO(), &resource, metav1.UpdateOptions{})
+		return err
+	}
+}
+
+func (cmd *CmdSiteUpdate) WaitUntilReady() error {
+
+	// the site resource was not updated
+	if cmd.output != "" {
+		return nil
+	}
+
+	err := utils.NewSpinner("Waiting for update to complete...", 5, func() error {
+
+		resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
+		if err != nil {
+			return err
+		}
+
+		if resource != nil && resource.Status.StatusMessage == "OK" {
+			return nil
+		}
+
+		return fmt.Errorf("error getting the resource")
+	})
+
+	if err != nil {
+		return fmt.Errorf("Site %q not ready yet, check the logs for more information\n", cmd.siteName)
+	}
+
+	fmt.Printf("Site %q is updated\n", cmd.siteName)
+
+	return nil
+}
diff --git a/internal/cmd/skupper/site/site.go b/internal/cmd/skupper/site/site.go
index 21b69f04..b2f093b0 100644
--- a/internal/cmd/skupper/site/site.go
+++ b/internal/cmd/skupper/site/site.go
@@ -4,6 +4,7 @@ Copyright © 2024 Skupper Team <skupper@googlegroups.com>
 package site
 
 import (
+	"github.com/skupperproject/skupper/internal/cmd/skupper/site/kube"
 	"github.com/spf13/cobra"
 )
 
@@ -17,13 +18,13 @@ func NewCmdSite() *cobra.Command {
 skupper site get my-site`,
 	}
 
-	siteCreateCommand := NewCmdSiteCreate()
-	siteGetCommand := NewCmdSiteGet()
-	siteUpdateCommand := NewCmdSiteUpdate()
-	siteDeleteCommand := NewCmdSiteDelete()
+	siteCreateCommand := kube.NewCmdSiteCreate()
+	siteStatusCommand := kube.NewCmdSiteStatus()
+	siteUpdateCommand := kube.NewCmdSiteUpdate()
+	siteDeleteCommand := kube.NewCmdSiteDelete()
 
 	cmd.AddCommand(&siteCreateCommand.CobraCmd)
-	cmd.AddCommand(&siteGetCommand.CobraCmd)
+	cmd.AddCommand(&siteStatusCommand.CobraCmd)
 	cmd.AddCommand(&siteUpdateCommand.CobraCmd)
 	cmd.AddCommand(&siteDeleteCommand.CobraCmd)
 
diff --git a/internal/cmd/skupper/site/site_delete.go b/internal/cmd/skupper/site/site_delete.go
deleted file mode 100644
index 9cf811f1..00000000
--- a/internal/cmd/skupper/site/site_delete.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package site
-
-import (
-	"fmt"
-	"github.com/skupperproject/skupper/client"
-	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
-	"github.com/spf13/cobra"
-)
-
-type CmdSiteDelete struct {
-	client   *client.VanClient
-	CobraCmd cobra.Command
-}
-
-func NewCmdSiteDelete() *CmdSiteDelete {
-
-	skupperCmd := CmdSiteDelete{}
-
-	cmd := cobra.Command{
-		Use:     "delete",
-		Short:   "delete the site",
-		Long:    "",
-		Example: "",
-		PreRun:  skupperCmd.NewClient,
-		Run: func(cmd *cobra.Command, args []string) {
-			utils.HandleErrorList(skupperCmd.ValidateInput(args))
-			skupperCmd.InputToOptions()
-			utils.HandleError(skupperCmd.Run())
-		},
-	}
-
-	skupperCmd.CobraCmd = cmd
-
-	return &skupperCmd
-}
-
-func (cmd *CmdSiteDelete) NewClient(cobraCommand *cobra.Command, args []string) {}
-func (cmd *CmdSiteDelete) AddFlags()                                            {}
-func (cmd *CmdSiteDelete) ValidateInput(args []string) []error                  { return nil }
-func (cmd *CmdSiteDelete) InputToOptions()                                      {}
-func (cmd *CmdSiteDelete) Run() error                                           { fmt.Println("Not implemented yet."); return nil }
-func (cmd *CmdSiteDelete) WaitUntilReady() error                                { return nil }
diff --git a/internal/cmd/skupper/site/site_get.go b/internal/cmd/skupper/site/site_get.go
deleted file mode 100644
index 00add50b..00000000
--- a/internal/cmd/skupper/site/site_get.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package site
-
-import (
-	"fmt"
-	"github.com/skupperproject/skupper/client"
-	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
-	"github.com/spf13/cobra"
-)
-
-type CmdSiteGet struct {
-	client   *client.VanClient
-	CobraCmd cobra.Command
-}
-
-func NewCmdSiteGet() *CmdSiteGet {
-
-	skupperCmd := CmdSiteGet{}
-
-	cmd := cobra.Command{
-		Use:     "get",
-		Short:   "get the site",
-		Long:    "",
-		Example: "",
-		PreRun:  skupperCmd.NewClient,
-		Run: func(cmd *cobra.Command, args []string) {
-			utils.HandleErrorList(skupperCmd.ValidateInput(args))
-			skupperCmd.InputToOptions()
-			utils.HandleError(skupperCmd.Run())
-		},
-	}
-
-	skupperCmd.CobraCmd = cmd
-
-	return &skupperCmd
-}
-
-func (cmd *CmdSiteGet) NewClient(cobraCommand *cobra.Command, args []string) {}
-func (cmd *CmdSiteGet) AddFlags()                                            {}
-func (cmd *CmdSiteGet) ValidateInput(args []string) []error                  { return nil }
-func (cmd *CmdSiteGet) InputToOptions()                                      {}
-func (cmd *CmdSiteGet) Run() error                                           { fmt.Println("Not implemented yet."); return nil }
-func (cmd *CmdSiteGet) WaitUntilReady() error                                { return nil }
diff --git a/internal/cmd/skupper/site/site_update.go b/internal/cmd/skupper/site/site_update.go
deleted file mode 100644
index cd4696d5..00000000
--- a/internal/cmd/skupper/site/site_update.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package site
-
-import (
-	"fmt"
-	"github.com/skupperproject/skupper/client"
-	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
-	"github.com/spf13/cobra"
-)
-
-type CmdSiteUpdate struct {
-	client   *client.VanClient
-	CobraCmd cobra.Command
-}
-
-func NewCmdSiteUpdate() *CmdSiteUpdate {
-
-	skupperCmd := CmdSiteUpdate{}
-
-	cmd := cobra.Command{
-		Use:     "update",
-		Short:   "update the site",
-		Long:    "",
-		Example: "",
-		PreRun:  skupperCmd.NewClient,
-		Run: func(cmd *cobra.Command, args []string) {
-			utils.HandleErrorList(skupperCmd.ValidateInput(args))
-			skupperCmd.InputToOptions()
-			utils.HandleError(skupperCmd.Run())
-		},
-	}
-
-	skupperCmd.CobraCmd = cmd
-
-	return &skupperCmd
-}
-
-func (cmd *CmdSiteUpdate) NewClient(cobraCommand *cobra.Command, args []string) {}
-func (cmd *CmdSiteUpdate) AddFlags()                                            {}
-func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error                  { return nil }
-func (cmd *CmdSiteUpdate) InputToOptions()                                      {}
-func (cmd *CmdSiteUpdate) Run() error                                           { fmt.Println("Not implemented yet."); return nil }
-func (cmd *CmdSiteUpdate) WaitUntilReady() error                                { return nil }
diff --git a/internal/cmd/skupper/utils/constants.go b/internal/cmd/skupper/utils/constants.go
new file mode 100644
index 00000000..579b289c
--- /dev/null
+++ b/internal/cmd/skupper/utils/constants.go
@@ -0,0 +1,6 @@
+package utils
+
+var (
+	LinkAccessTypes = []string{"route", "loadbalancer", "default"}
+	OutputTypes     = []string{"json", "yaml"}
+)
diff --git a/internal/cmd/skupper/utils/encode.go b/internal/cmd/skupper/utils/encode.go
new file mode 100644
index 00000000..fd0554cd
--- /dev/null
+++ b/internal/cmd/skupper/utils/encode.go
@@ -0,0 +1,24 @@
+package utils
+
+import (
+	"encoding/json"
+	"fmt"
+	"sigs.k8s.io/yaml"
+)
+
+func Encode(outputType string, resource interface{}) (string, error) {
+
+	var result []byte
+	var err error
+
+	switch outputType {
+	case "json":
+		result, err = json.MarshalIndent(resource, "", "  ")
+	case "yaml":
+		result, err = yaml.Marshal(resource)
+	default:
+		return "", fmt.Errorf("format %s not supported", outputType)
+	}
+
+	return string(result), err
+}
diff --git a/internal/cmd/skupper/utils/handle_error.go b/internal/cmd/skupper/utils/handle_error.go
index 490275b8..821c6e43 100644
--- a/internal/cmd/skupper/utils/handle_error.go
+++ b/internal/cmd/skupper/utils/handle_error.go
@@ -21,3 +21,11 @@ func HandleErrorList(errList []error) {
 		syscall.Exit(0)
 	}
 }
+
+func ErrorsToMessages(errs []error) []string {
+	messages := make([]string, len(errs))
+	for i, err := range errs {
+		messages[i] = err.Error()
+	}
+	return messages
+}
diff --git a/pkg/apis/skupper/v1alpha1/types.go b/pkg/apis/skupper/v1alpha1/types.go
index a42a62ec..99a474ea 100644
--- a/pkg/apis/skupper/v1alpha1/types.go
+++ b/pkg/apis/skupper/v1alpha1/types.go
@@ -95,6 +95,8 @@ type Status struct {
 	StatusMessage string `json:"status,omitempty"`
 }
 
+const STATUS_OK = "OK"
+
 type SiteStatus struct {
 	Status            `json:",inline"`
 	Endpoints         []Endpoint   `json:"endpoints,omitempty"`
diff --git a/pkg/domain/podman/credential_handler.go b/pkg/domain/podman/credential_handler.go
index 5cc46e17..b3d5e6b8 100644
--- a/pkg/domain/podman/credential_handler.go
+++ b/pkg/domain/podman/credential_handler.go
@@ -7,7 +7,7 @@ import (
 	"path"
 
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes"
 	"github.com/skupperproject/skupper/client/podman"
 	"github.com/skupperproject/skupper/pkg/certs"
 	"github.com/skupperproject/skupper/pkg/container"
diff --git a/pkg/domain/podman/router_config_handler.go b/pkg/domain/podman/router_config_handler.go
index 960dcadb..0b827b64 100644
--- a/pkg/domain/podman/router_config_handler.go
+++ b/pkg/domain/podman/router_config_handler.go
@@ -6,7 +6,7 @@ import (
 	"path"
 
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes"
 	"github.com/skupperproject/skupper/client/podman"
 	"github.com/skupperproject/skupper/pkg/container"
 	"github.com/skupperproject/skupper/pkg/qdr"
diff --git a/pkg/kube/certificates/mgr.go b/pkg/kube/certificates/mgr.go
index 4c0e2ddb..b3c32cf6 100644
--- a/pkg/kube/certificates/mgr.go
+++ b/pkg/kube/certificates/mgr.go
@@ -183,7 +183,7 @@ func (m *CertificateManagerImpl) secretDeleted(key string) error {
 
 func (m *CertificateManagerImpl) updateStatus(certificate *skupperv1alpha1.Certificate, err error) error {
 	if err == nil {
-		certificate.Status.Status = "Ok"
+		certificate.Status.Status = skupperv1alpha1.STATUS_OK
 	} else {
 		certificate.Status.Status = err.Error()
 	}
diff --git a/pkg/kube/claims/grants.go b/pkg/kube/claims/grants.go
index 3735b67a..acb9ea46 100644
--- a/pkg/kube/claims/grants.go
+++ b/pkg/kube/claims/grants.go
@@ -126,8 +126,8 @@ func (g *Grants) recheckUrl() {
 		if changed, message := g.checkUrl(key, grant); changed {
 			if message != "" {
 				grant.Status.Status = message
-			} else if grant.Status.Status != "Ok" {
-				grant.Status.Status = "Ok"
+			} else if grant.Status.Status != skupperv1alpha1.STATUS_OK {
+				grant.Status.Status = skupperv1alpha1.STATUS_OK
 			}
 			if err := g.updateGrantStatus(grant); err != nil {
 				log.Printf("Error updating grant %s after setting url: %s", key, err)
@@ -224,8 +224,8 @@ func (g *Grants) checkGrant(key string, grant *skupperv1alpha1.AccessGrant) erro
 	if len(status) != 0 {
 		grant.Status.Status = strings.Join(status, ", ")
 		changed = true
-	} else if grant.Status.Status != "Ok" {
-		grant.Status.Status = "Ok"
+	} else if grant.Status.Status != skupperv1alpha1.STATUS_OK {
+		grant.Status.Status = skupperv1alpha1.STATUS_OK
 		changed = true
 	}
 
diff --git a/pkg/kube/claims/redeem.go b/pkg/kube/claims/redeem.go
index bd10e93c..2968104f 100644
--- a/pkg/kube/claims/redeem.go
+++ b/pkg/kube/claims/redeem.go
@@ -78,7 +78,7 @@ func RedeemAccessToken(claim *skupperv1alpha1.AccessToken, site *skupperv1alpha1
 func updateAccessTokenStatus(claim *skupperv1alpha1.AccessToken, err error, clients kube.Clients) error {
 	if err == nil {
 		log.Printf("Redeemed claim %s/%s successfully", claim.Namespace, claim.Name)
-		claim.Status.Status = "Ok"
+		claim.Status.Status = skupperv1alpha1.STATUS_OK
 		claim.Status.Redeemed = true
 	} else {
 		log.Printf("Error processing claim %s/%s: %s", claim.Namespace, claim.Name, err)
diff --git a/pkg/kube/securedaccess/loadbalancer.go b/pkg/kube/securedaccess/loadbalancer.go
index 349456f6..bbce8696 100644
--- a/pkg/kube/securedaccess/loadbalancer.go
+++ b/pkg/kube/securedaccess/loadbalancer.go
@@ -19,10 +19,10 @@ func newLoadbalancerAccess(m *SecuredAccessManager) AccessType {
 }
 
 func (o *LoadbalancerAccessType) Realise(access *skupperv1alpha1.SecuredAccess) bool {
-	if access.Status.Status == "OK" {
+	if access.Status.Status == skupperv1alpha1.STATUS_OK {
 		return false
 	}
-	access.Status.Status = "OK"
+	access.Status.Status = skupperv1alpha1.STATUS_OK
 	return true
 }
 
diff --git a/pkg/kube/securedaccess/local.go b/pkg/kube/securedaccess/local.go
index b332e597..fbf4b7dc 100644
--- a/pkg/kube/securedaccess/local.go
+++ b/pkg/kube/securedaccess/local.go
@@ -15,10 +15,10 @@ func newLocalAccess(m *SecuredAccessManager) AccessType {
 }
 
 func (o *LocalAccessType) Realise(access *skupperv1alpha1.SecuredAccess) bool {
-	if access.Status.Status == "OK" {
+	if access.Status.Status == skupperv1alpha1.STATUS_OK {
 		return false
 	}
-	access.Status.Status = "OK"
+	access.Status.Status = skupperv1alpha1.STATUS_OK
 	return true
 }
 
diff --git a/pkg/kube/site/site.go b/pkg/kube/site/site.go
index 22f1837a..ef1644d5 100644
--- a/pkg/kube/site/site.go
+++ b/pkg/kube/site/site.go
@@ -580,7 +580,7 @@ func (s *Site) updateConnectorStatus(connector *skupperv1alpha1.Connector, err e
 	}
 	if err == nil {
 		connector.Status.Active = true
-		connector.Status.StatusMessage = "Ok"
+		connector.Status.StatusMessage = skupperv1alpha1.STATUS_OK
 	} else {
 		connector.Status.Active = false
 		connector.Status.StatusMessage = err.Error()
@@ -603,6 +603,9 @@ func (s *Site) CheckConnector(name string, connector *skupperv1alpha1.Connector)
 	if update == nil {
 		return nil
 	}
+	if s.site == nil {
+		return nil
+	}
 	err = s.updateRouterConfigForGroups(update)
 	return s.updateConnectorStatus(connector, err)
 }
@@ -613,7 +616,7 @@ func (s *Site) updateListenerStatus(listener *skupperv1alpha1.Listener, err erro
 	}
 	if err == nil {
 		listener.Status.Active = true
-		listener.Status.StatusMessage = "Ok"
+		listener.Status.StatusMessage = skupperv1alpha1.STATUS_OK
 	} else {
 		listener.Status.Active = false
 		listener.Status.StatusMessage = err.Error()
@@ -696,7 +699,7 @@ func (s *Site) updateLinkStatus(link *skupperv1alpha1.Link, err error) error {
 	}
 	if err == nil {
 		link.Status.Configured = true
-		link.Status.StatusMessage = "Ok"
+		link.Status.StatusMessage = skupperv1alpha1.STATUS_OK
 	} else {
 		link.Status.Configured = false
 		link.Status.StatusMessage = err.Error()
@@ -719,7 +722,7 @@ func (s *Site) updateStatus() error {
 	log.Printf("Updating site status for %s", s.namespace)
 	s.site.Status.Active = true
 	s.site.Status.Endpoints = s.endpoints()
-	s.site.Status.StatusMessage = "OK"
+	s.site.Status.StatusMessage = skupperv1alpha1.STATUS_OK
 	s.site.Status.DefaultIssuer = s.defaultIssuer()
 	updated, err := s.controller.GetSkupperClient().SkupperV1alpha1().Sites(s.site.ObjectMeta.Namespace).UpdateStatus(context.TODO(), s.site, metav1.UpdateOptions{})
 	if err != nil {
@@ -787,7 +790,7 @@ func (s *Site) CheckSecuredAccess(sa *skupperv1alpha1.SecuredAccess) {
 	}
 	la.Status.Active = len(la.Status.Endpoints) > 0
 	if la.Status.Active {
-		la.Status.StatusMessage = "OK"
+		la.Status.StatusMessage = skupperv1alpha1.STATUS_OK
 	}
 	s.updateRouterAccessStatus(la)
 }


Test patch:
diff --git a/client/compat/client_test.go b/client/compat/client_test.go
index ef32b1f4..5e4a06c9 100644
--- a/client/compat/client_test.go
+++ b/client/compat/client_test.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/skupperproject/skupper/client/generated/libpod/client/volumes_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/volumes_compat"
 	"gotest.tools/assert"
 )
 
diff --git a/client/compat/container_test.go b/client/compat/container_test.go
index 4c36824b..6631ab13 100644
--- a/client/compat/container_test.go
+++ b/client/compat/container_test.go
@@ -14,7 +14,7 @@ import (
 
 	"github.com/go-openapi/runtime"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/containers_compat"
+	"github.com/skupperproject/skupper-libpod/v4/client/containers_compat"
 	"github.com/skupperproject/skupper/pkg/container"
 	"github.com/skupperproject/skupper/pkg/images"
 	"github.com/skupperproject/skupper/pkg/utils"
diff --git a/client/podman/container_test.go b/client/podman/container_test.go
index 10131481..1b89d1ef 100644
--- a/client/podman/container_test.go
+++ b/client/podman/container_test.go
@@ -14,7 +14,7 @@ import (
 
 	"github.com/go-openapi/runtime"
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client/generated/libpod/client/containers"
+	"github.com/skupperproject/skupper-libpod/v4/client/containers"
 	"github.com/skupperproject/skupper/pkg/container"
 	"github.com/skupperproject/skupper/pkg/images"
 	"github.com/skupperproject/skupper/pkg/utils"
diff --git a/internal/cmd/skupper/site/site_create_test.go b/internal/cmd/skupper/site/kube/site_create_test.go
similarity index 82%
rename from internal/cmd/skupper/site/site_create_test.go
rename to internal/cmd/skupper/site/kube/site_create_test.go
index 6fed2af9..d746d927 100644
--- a/internal/cmd/skupper/site/site_create_test.go
+++ b/internal/cmd/skupper/site/kube/site_create_test.go
@@ -1,7 +1,8 @@
-package site
+package kube
 
 import (
 	"fmt"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
 	"github.com/skupperproject/skupper/pkg/apis/skupper/v1alpha1"
 	"github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1/fake"
 	"github.com/spf13/pflag"
@@ -35,8 +36,8 @@ func TestCmdSiteCreate_AddFlags(t *testing.T) {
 	expectedFlagsWithDefaultValue := map[string]interface{}{
 		"enable-link-access": "false",
 		"link-access-type":   "",
-		"link-access-host":   "",
 		"service-account":    "skupper-controller",
+		"output":             "",
 	}
 	var flagList []string
 
@@ -54,7 +55,7 @@ func TestCmdSiteCreate_AddFlags(t *testing.T) {
 
 		cmd.CobraCmd.Flags().VisitAll(func(flag *pflag.Flag) {
 			flagList = append(flagList, flag.Name)
-			assert.Check(t, expectedFlagsWithDefaultValue[flag.Name] != nil)
+			assert.Check(t, expectedFlagsWithDefaultValue[flag.Name] != nil, fmt.Sprintf("flag %q not expected", flag.Name))
 			assert.Check(t, expectedFlagsWithDefaultValue[flag.Name] == flag.DefValue)
 		})
 
@@ -154,6 +155,19 @@ func TestCmdSiteCreate_ValidateInput(t *testing.T) {
 				"for the site to work with this type of linkAccess, the --enable-link-access option must be set to true",
 			},
 		},
+		{
+			name: "output format is not valid",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteCreate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.flags = CreateFlags{output: "not-valid"}
+			},
+			expectedErrors: []string{
+				"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]",
+			},
+		},
 	}
 
 	for _, test := range testTable {
@@ -165,7 +179,7 @@ func TestCmdSiteCreate_ValidateInput(t *testing.T) {
 
 			actualErrors := command.ValidateInput(test.args)
 
-			actualErrorsMessages := errorsToMessages(actualErrors)
+			actualErrorsMessages := utils.ErrorsToMessages(actualErrors)
 
 			assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors)
 
@@ -181,6 +195,7 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 		flags              CreateFlags
 		expectedSettings   map[string]string
 		expectedLinkAccess string
+		expectedOutput     string
 	}
 
 	testTable := []test{
@@ -192,16 +207,17 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 				"name": "my-site",
 			},
 			expectedLinkAccess: "none",
+			expectedOutput:     "",
 		},
 		{
 			name:  "options with link access enabled but using a type by default and link access host specified",
 			args:  []string{"my-site"},
-			flags: CreateFlags{enableLinkAccess: true, linkAccessHost: "host"},
+			flags: CreateFlags{enableLinkAccess: true},
 			expectedSettings: map[string]string{
-				"name":         "my-site",
-				"ingress-host": "host",
+				"name": "my-site",
 			},
 			expectedLinkAccess: "loadbalancer",
+			expectedOutput:     "",
 		},
 		{
 			name:  "options with link access enabled using the nodeport type",
@@ -211,6 +227,7 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 				"name": "my-site",
 			},
 			expectedLinkAccess: "nodeport",
+			expectedOutput:     "",
 		},
 		{
 			name:  "options with link access options not well specified",
@@ -220,6 +237,17 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 				"name": "my-site",
 			},
 			expectedLinkAccess: "none",
+			expectedOutput:     "",
+		},
+		{
+			name:  "options output type",
+			args:  []string{"my-site"},
+			flags: CreateFlags{enableLinkAccess: false, linkAccessType: "nodeport", output: "yaml"},
+			expectedSettings: map[string]string{
+				"name": "my-site",
+			},
+			expectedLinkAccess: "none",
+			expectedOutput:     "yaml",
 		},
 	}
 
@@ -233,6 +261,8 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 			cmd.InputToOptions()
 
 			assert.DeepEqual(t, cmd.options, test.expectedSettings)
+
+			assert.Check(t, cmd.output == test.expectedOutput)
 		})
 	}
 }
@@ -266,7 +296,7 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 						return true, nil, fmt.Errorf("unexpected value")
 					}
 
-					if site.Spec.Settings["ingress"] != "none" {
+					if site.Spec.Settings["name"] != "my-site" {
 						return true, nil, fmt.Errorf("unexpected value")
 					}
 
@@ -279,7 +309,7 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 				command.Client = fakeSkupperClient
 				command.siteName = "my-site"
 				command.serviceAccountName = "my-service-account"
-				command.options = map[string]string{"ingress": "none"}
+				command.options = map[string]string{"name": "my-site"}
 			},
 		},
 		{
@@ -292,10 +322,36 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 				})
 				command.Client = fakeSkupperClient
 				command.siteName = "my-site"
-				command.options = map[string]string{"ingress": "none"}
 			},
 			errorMessage: "error",
 		},
+		{
+			name: "runs ok without creating site",
+			setUpMock: func(command *CmdSiteCreate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+				command.serviceAccountName = "my-service-account"
+				command.options = map[string]string{"name": "my-site"}
+				command.siteName = "test"
+				command.output = "yaml"
+			},
+		},
+		{
+			name: "runs fails because the output format is not supported",
+			setUpMock: func(command *CmdSiteCreate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+				command.serviceAccountName = "my-service-account"
+				command.options = map[string]string{"name": "my-site"}
+				command.siteName = "test"
+				command.output = "unsupported"
+			},
+			errorMessage: "format unsupported not supported",
+		},
 	}
 
 	for _, test := range testTable {
@@ -358,54 +414,17 @@ func TestCmdSiteCreate_WaitUntilReady(t *testing.T) {
 			expectError: true,
 		},
 		{
-			name: "site is ready but network status configmap is empty",
+			name: "there is no need to wait for a site, the user just wanted the output",
 			setUpMock: func(command *CmdSiteCreate) {
 				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
 				fakeSkupperClient.Fake.ClearActions()
-				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
-
-					return true, &v1alpha1.Site{
-						ObjectMeta: v1.ObjectMeta{
-							Name:      "my-site",
-							Namespace: "test",
-						},
-						Status: v1alpha1.SiteStatus{
-							Status: v1alpha1.Status{
-								StatusMessage: "OK",
-							},
-						},
-					}, nil
-				})
-				command.Client = fakeSkupperClient
-
-			},
-			expectError: false,
-		},
-		{
-			name: "site is ready but network status configmap is not returned",
-			setUpMock: func(command *CmdSiteCreate) {
-				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
-				fakeSkupperClient.Fake.ClearActions()
-				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
-
-					return true, &v1alpha1.Site{
-						ObjectMeta: v1.ObjectMeta{
-							Name:      "my-site",
-							Namespace: "test",
-						},
-						Status: v1alpha1.SiteStatus{
-							Status: v1alpha1.Status{
-								StatusMessage: "OK",
-							},
-						},
-					}, nil
-				})
 				command.Client = fakeSkupperClient
+				command.output = "json"
 			},
 			expectError: false,
 		},
 		{
-			name: "site and network status configmap are ready",
+			name: "site is ready",
 			setUpMock: func(command *CmdSiteCreate) {
 				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
 				fakeSkupperClient.Fake.ClearActions()
@@ -436,8 +455,11 @@ func TestCmdSiteCreate_WaitUntilReady(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 
 			err := cmd.WaitUntilReady()
-			if err != nil {
-				assert.Check(t, test.expectError)
+
+			if test.expectError {
+				assert.Check(t, err != nil)
+			} else {
+				assert.Check(t, err == nil)
 			}
 
 		})
@@ -455,11 +477,3 @@ func newCmdSiteCreateWithMocks() *CmdSiteCreate {
 
 	return cmdSiteCreate
 }
-
-func errorsToMessages(errs []error) []string {
-	msgs := make([]string, len(errs))
-	for i, err := range errs {
-		msgs[i] = err.Error()
-	}
-	return msgs
-}
diff --git a/internal/cmd/skupper/site/kube/site_delete_test.go b/internal/cmd/skupper/site/kube/site_delete_test.go
new file mode 100644
index 00000000..957d95b9
--- /dev/null
+++ b/internal/cmd/skupper/site/kube/site_delete_test.go
@@ -0,0 +1,372 @@
+package kube
+
+import (
+	"fmt"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v1alpha1"
+	"github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1/fake"
+	"gotest.tools/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	testing2 "k8s.io/client-go/testing"
+	"testing"
+)
+
+func TestCmdSiteDelete_NewCmdSiteDelete(t *testing.T) {
+
+	t.Run("delete command", func(t *testing.T) {
+
+		result := NewCmdSiteDelete()
+
+		assert.Check(t, result.CobraCmd.Use != "")
+		assert.Check(t, result.CobraCmd.Short != "")
+		assert.Check(t, result.CobraCmd.Long != "")
+		assert.Check(t, result.CobraCmd.PreRun != nil)
+		assert.Check(t, result.CobraCmd.Run != nil)
+		assert.Check(t, result.CobraCmd.PostRunE != nil)
+		assert.Check(t, result.CobraCmd.Flags() != nil)
+
+	})
+
+}
+
+func TestCmdSiteDelete_ValidateInput(t *testing.T) {
+	type test struct {
+		name           string
+		args           []string
+		setUpMock      func(command *CmdSiteDelete)
+		expectedErrors []string
+	}
+
+	testTable := []test{
+		{
+			name: "site is not deleted because it does not exist",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"there is no existing Skupper site resource to update"},
+		},
+		{
+			name: "site is not deleted because there is an error trying to retrieve it",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("error getting the site")
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"error getting the site"},
+		},
+		{
+			name: "more than one argument was specified",
+			args: []string{"my", "site"},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name: "there are several skupper sites and no site name was specified",
+			args: []string{},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "another-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"site name is required because there are several sites in this namespace"},
+		},
+		{
+			name: "there are several skupper sites but the one specified by the user",
+			args: []string{"special-site"},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "another-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"site with name \"special-site\" is not available"},
+		},
+		{
+			name: "there are several skupper sites and the user specifies one of them",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "another-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{},
+		},
+		{
+			name: "trying to delete a site that does not exist",
+			args: []string{"siteb"},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"site with name \"siteb\" is not available"},
+		},
+		{
+			name: "deleting the site successfully",
+			args: []string{},
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{},
+		},
+	}
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+
+			command := &CmdSiteDelete{
+				Namespace: "test",
+			}
+
+			if test.setUpMock != nil {
+				test.setUpMock(command)
+			}
+
+			actualErrors := command.ValidateInput(test.args)
+
+			actualErrorsMessages := utils.ErrorsToMessages(actualErrors)
+
+			assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors)
+
+		})
+	}
+}
+
+func TestCmdSiteDelete_Run(t *testing.T) {
+	type test struct {
+		name         string
+		setUpMock    func(command *CmdSiteDelete)
+		errorMessage string
+	}
+
+	testTable := []test{
+		{
+			name: "runs ok",
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("delete", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					deleteAction, ok := action.(testing2.DeleteAction)
+					if !ok {
+						return
+					}
+					siteName := deleteAction.GetName()
+
+					if siteName != "my-site" {
+						return true, nil, fmt.Errorf("unexpected value")
+					}
+
+					return true, nil, nil
+				})
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+			},
+		},
+		{
+			name: "run fails",
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("delete", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("error")
+				})
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+			},
+			errorMessage: "error",
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := newCmdSiteDeleteWithMocks()
+		test.setUpMock(cmd)
+
+		t.Run(test.name, func(t *testing.T) {
+
+			err := cmd.Run()
+			if err != nil {
+				assert.Check(t, test.errorMessage == err.Error())
+			} else {
+				assert.Check(t, err == nil)
+			}
+		})
+	}
+}
+
+func TestCmdSiteDelete_WaitUntilReady(t *testing.T) {
+	type test struct {
+		name        string
+		setUpMock   func(command *CmdSiteDelete)
+		expectError bool
+	}
+
+	testTable := []test{
+		{
+			name: "site is not deleted",
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+
+					return true, &v1alpha1.Site{
+						ObjectMeta: v1.ObjectMeta{
+							Name:      "my-site",
+							Namespace: "test",
+						},
+						Status: v1alpha1.SiteStatus{
+							Status: v1alpha1.Status{
+								StatusMessage: "",
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectError: true,
+		},
+		{
+			name: "site is deleted",
+			setUpMock: func(command *CmdSiteDelete) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("no site")
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectError: false,
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := newCmdSiteDeleteWithMocks()
+		cmd.siteName = "my-site"
+		test.setUpMock(cmd)
+		t.Run(test.name, func(t *testing.T) {
+
+			err := cmd.WaitUntilReady()
+			if err != nil {
+				assert.Check(t, test.expectError)
+			}
+
+		})
+	}
+}
+
+// --- helper methods
+
+func newCmdSiteDeleteWithMocks() *CmdSiteDelete {
+
+	cmdSiteDelete := &CmdSiteDelete{
+		Client:    &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}},
+		Namespace: "test",
+	}
+
+	return cmdSiteDelete
+}
diff --git a/internal/cmd/skupper/site/kube/site_status_test.go b/internal/cmd/skupper/site/kube/site_status_test.go
new file mode 100644
index 00000000..eb075871
--- /dev/null
+++ b/internal/cmd/skupper/site/kube/site_status_test.go
@@ -0,0 +1,167 @@
+package kube
+
+import (
+	"fmt"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v1alpha1"
+	"github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1/fake"
+	"gotest.tools/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	testing2 "k8s.io/client-go/testing"
+	"testing"
+)
+
+func TestCmdSiteStatus_NewCmdSiteStatus(t *testing.T) {
+
+	t.Run("status command", func(t *testing.T) {
+
+		result := NewCmdSiteStatus()
+
+		assert.Check(t, result.CobraCmd.Use != "")
+		assert.Check(t, result.CobraCmd.Short != "")
+		assert.Check(t, result.CobraCmd.Long != "")
+		assert.Check(t, result.CobraCmd.PreRun != nil)
+		assert.Check(t, result.CobraCmd.Run != nil)
+		assert.Check(t, result.CobraCmd.Flags() != nil)
+
+	})
+
+}
+
+func TestCmdSiteStatus_ValidateInput(t *testing.T) {
+	type test struct {
+		name           string
+		args           []string
+		setUpMock      func(command *CmdSiteStatus)
+		expectedErrors []string
+	}
+
+	command := &CmdSiteStatus{
+		Namespace: "test",
+	}
+
+	testTable := []test{
+		{
+			name: "more than one argument was specified",
+			args: []string{"my-site", ""},
+			setUpMock: func(command *CmdSiteStatus) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"this command does not need any arguments"},
+		},
+	}
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+
+			if test.setUpMock != nil {
+				test.setUpMock(command)
+			}
+
+			actualErrors := command.ValidateInput(test.args)
+
+			actualErrorsMessages := utils.ErrorsToMessages(actualErrors)
+
+			assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors)
+
+		})
+	}
+}
+
+func TestCmdSiteStatus_Run(t *testing.T) {
+	type test struct {
+		name         string
+		setUpMock    func(command *CmdSiteStatus)
+		errorMessage string
+	}
+
+	testTable := []test{
+		{
+			name: "runs ok",
+			setUpMock: func(command *CmdSiteStatus) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "old-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+				command.Client = fakeSkupperClient
+			},
+		},
+		{
+			name: "run fails",
+			setUpMock: func(command *CmdSiteStatus) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("error")
+				})
+				command.Client = fakeSkupperClient
+			},
+			errorMessage: "error",
+		},
+		{
+			name: "there is no existing skupper site",
+			setUpMock: func(command *CmdSiteStatus) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := newCmdSiteStatusWithMocks()
+		test.setUpMock(cmd)
+
+		t.Run(test.name, func(t *testing.T) {
+
+			err := cmd.Run()
+			if err != nil {
+				assert.Check(t, test.errorMessage == err.Error())
+			} else {
+				assert.Check(t, err == nil)
+			}
+		})
+	}
+}
+
+func TestCmdSiteStatus_WaitUntilReady(t *testing.T) {
+
+	t.Run("", func(t *testing.T) {
+
+		cmd := newCmdSiteStatusWithMocks()
+
+		result := cmd.WaitUntilReady()
+		assert.Check(t, result == nil)
+
+	})
+
+}
+
+// --- helper methods
+
+func newCmdSiteStatusWithMocks() *CmdSiteStatus {
+
+	CmdSiteStatus := &CmdSiteStatus{
+		Client:    &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}},
+		Namespace: "test",
+	}
+
+	return CmdSiteStatus
+}
diff --git a/internal/cmd/skupper/site/kube/site_update_test.go b/internal/cmd/skupper/site/kube/site_update_test.go
new file mode 100644
index 00000000..dbd226a7
--- /dev/null
+++ b/internal/cmd/skupper/site/kube/site_update_test.go
@@ -0,0 +1,666 @@
+package kube
+
+import (
+	"fmt"
+	"github.com/skupperproject/skupper/internal/cmd/skupper/utils"
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v1alpha1"
+	"github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v1alpha1/fake"
+	"github.com/spf13/pflag"
+	"gotest.tools/assert"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	testing2 "k8s.io/client-go/testing"
+	"testing"
+)
+
+func TestCmdSiteUpdate_NewCmdSiteUpdate(t *testing.T) {
+
+	t.Run("update command", func(t *testing.T) {
+
+		result := NewCmdSiteUpdate()
+
+		assert.Check(t, result.CobraCmd.Use != "")
+		assert.Check(t, result.CobraCmd.Short != "")
+		assert.Check(t, result.CobraCmd.Long != "")
+		assert.Check(t, result.CobraCmd.PreRun != nil)
+		assert.Check(t, result.CobraCmd.Run != nil)
+		assert.Check(t, result.CobraCmd.PostRunE != nil)
+		assert.Check(t, result.CobraCmd.Flags() != nil)
+
+	})
+
+}
+
+func TestCmdSiteUpdate_AddFlags(t *testing.T) {
+
+	expectedFlagsWithDefaultValue := map[string]interface{}{
+		"enable-link-access": "false",
+		"link-access-type":   "",
+		"service-account":    "skupper-controller",
+		"output":             "",
+	}
+	var flagList []string
+
+	cmd := newCmdSiteUpdateWithMocks()
+
+	t.Run("add flags", func(t *testing.T) {
+
+		cmd.CobraCmd.Flags().VisitAll(func(flag *pflag.Flag) {
+			flagList = append(flagList, flag.Name)
+		})
+
+		assert.Check(t, len(flagList) == 0)
+
+		cmd.AddFlags()
+
+		cmd.CobraCmd.Flags().VisitAll(func(flag *pflag.Flag) {
+			flagList = append(flagList, flag.Name)
+			assert.Check(t, expectedFlagsWithDefaultValue[flag.Name] != nil, fmt.Sprintf("flag %q not expected", flag.Name))
+			assert.Check(t, expectedFlagsWithDefaultValue[flag.Name] == flag.DefValue)
+		})
+
+		assert.Check(t, len(flagList) == len(expectedFlagsWithDefaultValue))
+
+	})
+
+}
+
+func TestCmdSiteUpdate_ValidateInput(t *testing.T) {
+	type test struct {
+		name           string
+		args           []string
+		setUpMock      func(command *CmdSiteUpdate)
+		expectedErrors []string
+	}
+
+	testTable := []test{
+		{
+			name: "site is updated because there is already a site in the namespace.",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{},
+		},
+		{
+			name: "site name is not specified.",
+			args: []string{},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{},
+		},
+		{
+			name: "more than one argument was specified",
+			args: []string{"my", "site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"only one argument is allowed for this command"},
+		},
+		{
+			name: "service account name is not valid.",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.flags = UpdateFlags{serviceAccount: "not valid service account name"}
+			},
+			expectedErrors: []string{"service account 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: "link access type is not valid",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.flags = UpdateFlags{linkAccessType: "not-valid"}
+			},
+			expectedErrors: []string{
+				"link access type is not valid: value not-valid not allowed. It should be one of this options: [route loadbalancer default]",
+				"for the site to work with this type of linkAccess, the --enable-link-access option must be set to true",
+			},
+		},
+		{
+			name: "output format is not valid",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+				command.flags = UpdateFlags{output: "not-valid"}
+			},
+			expectedErrors: []string{
+				"output type is not valid: value not-valid not allowed. It should be one of this options: [json yaml]",
+			},
+		},
+		{
+			name: "there is no skupper site",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{
+				"there is no existing Skupper site resource to update",
+			},
+		},
+		{
+			name: "there are several skupper sites and no site name was specified",
+			args: []string{},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "another-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"site name is required because there are several sites in this namespace"},
+		},
+		{
+			name: "there are several skupper sites but the one specified by the user",
+			args: []string{"special-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "another-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{"site with name \"special-site\" is not available"},
+		},
+		{
+			name: "there are several skupper sites and the user specifies one of them",
+			args: []string{"my-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "another-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{},
+		},
+		{
+			name: "the name specified in the arguments does not match with the current site",
+			args: []string{"a-site"},
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("list", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, &v1alpha1.SiteList{
+						Items: []v1alpha1.Site{
+							{
+								ObjectMeta: v1.ObjectMeta{
+									Name:      "my-site",
+									Namespace: "test",
+								},
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectedErrors: []string{
+				"site with name \"a-site\" is not available",
+			},
+		},
+	}
+
+	for _, test := range testTable {
+		command := &CmdSiteUpdate{
+			Namespace: "test",
+		}
+
+		t.Run(test.name, func(t *testing.T) {
+
+			if test.setUpMock != nil {
+				test.setUpMock(command)
+			}
+
+			actualErrors := command.ValidateInput(test.args)
+
+			actualErrorsMessages := utils.ErrorsToMessages(actualErrors)
+
+			assert.DeepEqual(t, actualErrorsMessages, test.expectedErrors)
+
+		})
+	}
+}
+
+func TestCmdSiteUpdate_InputToOptions(t *testing.T) {
+
+	type test struct {
+		name               string
+		args               []string
+		flags              UpdateFlags
+		expectedSettings   map[string]string
+		expectedLinkAccess string
+		expectedOutput     string
+	}
+
+	testTable := []test{
+		{
+			name:  "options without link access enabled",
+			args:  []string{"my-site"},
+			flags: UpdateFlags{},
+			expectedSettings: map[string]string{
+				"name": "my-site",
+			},
+			expectedLinkAccess: "none",
+			expectedOutput:     "",
+		},
+		{
+			name:  "options with link access enabled but using a type by default and link access host specified",
+			args:  []string{"my-site"},
+			flags: UpdateFlags{enableLinkAccess: true},
+			expectedSettings: map[string]string{
+				"name": "my-site",
+			},
+			expectedLinkAccess: "loadbalancer",
+			expectedOutput:     "",
+		},
+		{
+			name:  "options with link access enabled using the nodeport type",
+			args:  []string{"my-site"},
+			flags: UpdateFlags{enableLinkAccess: true, linkAccessType: "nodeport"},
+			expectedSettings: map[string]string{
+				"name": "my-site",
+			},
+			expectedLinkAccess: "nodeport",
+			expectedOutput:     "",
+		},
+		{
+			name:  "options with link access options not well specified",
+			args:  []string{"my-site"},
+			flags: UpdateFlags{enableLinkAccess: false, linkAccessType: "nodeport"},
+			expectedSettings: map[string]string{
+				"name": "my-site",
+			},
+			expectedLinkAccess: "none",
+			expectedOutput:     "",
+		},
+		{
+			name:  "options output type",
+			args:  []string{"my-site"},
+			flags: UpdateFlags{enableLinkAccess: false, linkAccessType: "nodeport", output: "yaml"},
+			expectedSettings: map[string]string{
+				"name": "my-site",
+			},
+			expectedLinkAccess: "none",
+			expectedOutput:     "yaml",
+		},
+	}
+
+	for _, test := range testTable {
+		t.Run(test.name, func(t *testing.T) {
+
+			cmd := newCmdSiteUpdateWithMocks()
+			cmd.flags = test.flags
+			cmd.siteName = "my-site"
+
+			cmd.InputToOptions()
+
+			assert.DeepEqual(t, cmd.options, test.expectedSettings)
+
+			assert.Check(t, cmd.output == test.expectedOutput)
+		})
+	}
+}
+
+func TestCmdSiteUpdate_Run(t *testing.T) {
+	type test struct {
+		name         string
+		setUpMock    func(command *CmdSiteUpdate)
+		errorMessage string
+	}
+
+	testTable := []test{
+		{
+			name: "runs ok",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("update", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					createAction, ok := action.(testing2.CreateActionImpl)
+					if !ok {
+						return
+					}
+					createdObject := createAction.GetObject()
+
+					site, ok := createdObject.(*v1alpha1.Site)
+					if !ok {
+						return
+					}
+
+					if site.Name != "my-site" {
+						return true, nil, fmt.Errorf("unexpected value")
+					}
+
+					if site.Spec.Settings["name"] != "my-site" {
+						return true, nil, fmt.Errorf("unexpected value")
+					}
+
+					if site.Spec.ServiceAccount != "my-service-account" {
+						return true, nil, fmt.Errorf("unexpected value")
+					}
+					if site.Spec.LinkAccess != "default" {
+						return true, nil, fmt.Errorf("unexpected value")
+					}
+
+					return true, nil, nil
+				})
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+				command.serviceAccountName = "my-service-account"
+				command.linkAccessType = "default"
+				command.options = map[string]string{"name": "my-site"}
+			},
+		},
+		{
+			name: "run fails",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("update", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("error")
+				})
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+			},
+			errorMessage: "error",
+		},
+		{
+			name: "runs ok without creating site",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+				command.serviceAccountName = "my-service-account"
+				command.options = map[string]string{"name": "my-site"}
+				command.siteName = "test"
+				command.output = "yaml"
+			},
+		},
+		{
+			name: "runs fails because the output format is not supported",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+				command.serviceAccountName = "my-service-account"
+				command.options = map[string]string{"name": "my-site"}
+				command.siteName = "test"
+				command.output = "unsupported"
+			},
+			errorMessage: "format unsupported not supported",
+		},
+		{
+			name: "run fails because the site cannot be retrieved",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("error")
+				})
+				command.Client = fakeSkupperClient
+				command.siteName = "my-site"
+			},
+			errorMessage: "error",
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := newCmdSiteUpdateWithMocks()
+		test.setUpMock(cmd)
+
+		t.Run(test.name, func(t *testing.T) {
+
+			err := cmd.Run()
+			if err != nil {
+				assert.Check(t, test.errorMessage == err.Error())
+			} else {
+				assert.Check(t, err == nil)
+			}
+		})
+	}
+}
+
+func TestCmdSiteUpdate_WaitUntilReady(t *testing.T) {
+	type test struct {
+		name        string
+		setUpMock   func(command *CmdSiteUpdate)
+		expectError bool
+	}
+
+	testTable := []test{
+		{
+			name: "site is not ready",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+
+					return true, &v1alpha1.Site{
+						ObjectMeta: v1.ObjectMeta{
+							Name:      "my-site",
+							Namespace: "test",
+						},
+						Status: v1alpha1.SiteStatus{
+							Status: v1alpha1.Status{
+								StatusMessage: "",
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectError: true,
+		},
+		{
+			name: "site is not returned",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+					return true, nil, fmt.Errorf("it failed")
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectError: true,
+		},
+		{
+			name: "there is no need to wait for a site, the user just wanted the output",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				command.Client = fakeSkupperClient
+				command.output = "json"
+			},
+			expectError: false,
+		},
+		{
+			name: "site is updated",
+			setUpMock: func(command *CmdSiteUpdate) {
+				fakeSkupperClient := &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}}
+				fakeSkupperClient.Fake.ClearActions()
+				fakeSkupperClient.Fake.PrependReactor("get", "sites", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
+
+					return true, &v1alpha1.Site{
+						ObjectMeta: v1.ObjectMeta{
+							Name:      "my-site",
+							Namespace: "test",
+						},
+						Status: v1alpha1.SiteStatus{
+							Status: v1alpha1.Status{
+								StatusMessage: "OK",
+							},
+						},
+					}, nil
+				})
+				command.Client = fakeSkupperClient
+			},
+			expectError: false,
+		},
+	}
+
+	for _, test := range testTable {
+		cmd := newCmdSiteUpdateWithMocks()
+		cmd.siteName = "my-site"
+		test.setUpMock(cmd)
+		t.Run(test.name, func(t *testing.T) {
+
+			err := cmd.WaitUntilReady()
+			if err != nil {
+				assert.Check(t, test.expectError)
+			}
+
+		})
+	}
+}
+
+// --- helper methods
+
+func newCmdSiteUpdateWithMocks() *CmdSiteUpdate {
+
+	CmdSiteUpdate := &CmdSiteUpdate{
+		Client:    &fake.FakeSkupperV1alpha1{Fake: &testing2.Fake{}},
+		Namespace: "test",
+	}
+
+	return CmdSiteUpdate
+}
diff --git a/internal/cmd/skupper/site/site_delete_test.go b/internal/cmd/skupper/site/site_delete_test.go
deleted file mode 100644
index 041e786e..00000000
--- a/internal/cmd/skupper/site/site_delete_test.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package site
diff --git a/internal/cmd/skupper/site/site_get_test.go b/internal/cmd/skupper/site/site_get_test.go
deleted file mode 100644
index 041e786e..00000000
--- a/internal/cmd/skupper/site/site_get_test.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package site
diff --git a/internal/cmd/skupper/site/site_update_test.go b/internal/cmd/skupper/site/site_update_test.go
deleted file mode 100644
index 041e786e..00000000
--- a/internal/cmd/skupper/site/site_update_test.go
+++ /dev/null
@@ -1,1 +0,0 @@
-package site
diff --git a/internal/cmd/skupper/utils/encode_test.go b/internal/cmd/skupper/utils/encode_test.go
new file mode 100644
index 00000000..49a6415d
--- /dev/null
+++ b/internal/cmd/skupper/utils/encode_test.go
@@ -0,0 +1,90 @@
+package utils
+
+import (
+	"github.com/skupperproject/skupper/pkg/apis/skupper/v1alpha1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"testing"
+)
+
+func Test_Marshal(t *testing.T) {
+	tests := []struct {
+		outputType     string
+		resource       any
+		expectedOutput string
+		hasError       bool
+	}{
+		{"json", v1alpha1.Site{
+			TypeMeta: v1.TypeMeta{
+				APIVersion: "skupper.io/v1alpha1",
+				Kind:       "Site",
+			},
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "my-site",
+				Namespace: "test",
+			},
+			Spec: v1alpha1.SiteSpec{
+				LinkAccess: "default",
+			},
+		},
+			`{
+  "kind": "Site",
+  "apiVersion": "skupper.io/v1alpha1",
+  "metadata": {
+    "name": "my-site",
+    "namespace": "test",
+    "creationTimestamp": null
+  },
+  "spec": {
+    "linkAccess": "default"
+  },
+  "status": {
+    "active": false
+  }
+}`, false},
+		{"yaml", v1alpha1.Site{
+			TypeMeta: v1.TypeMeta{
+				APIVersion: "skupper.io/v1alpha1",
+				Kind:       "Site",
+			},
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "my-site",
+				Namespace: "test",
+			},
+			Spec: v1alpha1.SiteSpec{
+				LinkAccess: "default",
+			},
+		},
+			`apiVersion: skupper.io/v1alpha1
+kind: Site
+metadata:
+  creationTimestamp: null
+  name: my-site
+  namespace: test
+spec:
+  linkAccess: default
+status:
+  active: false
+`,
+			false},
+		{"unsupported", v1alpha1.Site{
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "my-site",
+				Namespace: "test",
+			},
+		}, ``, true},
+	}
+
+	for _, tt := range tests {
+		output, err := Encode(tt.outputType, tt.resource)
+		if err != nil && !tt.hasError {
+			t.Fatalf("Expected no error, but got: %v", err)
+		}
+		if err == nil && tt.hasError {
+			t.Fatal("Expected an error, but got none")
+		}
+
+		if output != tt.expectedOutput {
+			t.Errorf("Expected output %v but got %v", tt.expectedOutput, output)
+		}
+	}
+}