Repository: skupperproject/skupper
Pull request #1280 (2023-11-15 12:59:14)

update podman expose help


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2e175afd..c6a73b04 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,6 +5,12 @@ orbs:
     go: circleci/go@1.7.1
 
 executors:
+  go_cimg:
+    docker:
+      - image: cimg/go:1.20
+    environment:
+      CGO_ENABLED: 0
+
   local_cluster_test_executor:
     machine:
       image: ubuntu-2204:2022.10.2
@@ -292,6 +298,12 @@ commands:
             go build -ldflags "-X github.com/skupperproject/skupper/pkg/version.Version=${VERSION}"
             -o dist/<< parameters.platform >>/skupper<< parameters.exesuffix >>
             ./cmd/skupper
+      - run:
+          name: Audit << parameters.platform >>
+          command: >-
+            sha512sum dist/<< parameters.platform >>/skupper<< parameters.exesuffix >>;
+            file dist/<< parameters.platform >>/skupper<< parameters.exesuffix >>;
+            go version -m dist/<< parameters.platform >>/skupper<< parameters.exesuffix >>;
 
   podman-latest:
     description: "Install latest podman v4 or higher"
@@ -348,6 +360,7 @@ yaml-templates:
   release_requires: &release_requires
     requires:
       - build-all
+      - test-skupper-binary
       - test
       - main_tests_minikube_local_cluster
       - policy_tests_minikube_local_cluster
@@ -358,6 +371,17 @@ workflows:
     jobs:
       - build-all:
           <<: *run_for_all_branches_and_numeric_tags
+      - test-skupper-binary:
+          <<: *run_for_all_branches_and_numeric_tags
+          matrix:
+            parameters:
+              image:
+                - quay.io/centos/centos:stream8
+                - quay.io/centos/centos:stream9
+                - quay.io/fedora/fedora:38
+                - quay.io/fedora/fedora:39
+          requires:
+            - build-all
       - test:
           <<: *run_for_all_branches_and_numeric_tags
       - build_and_save_test_images:
@@ -402,6 +426,13 @@ workflows:
           context:
             - skupper-org
 
+      - publish-github-main-artifacts:
+          <<: *run_for_main_branch
+          requires:
+            - publish-github-main-images
+          context:
+            - skupper-org
+
       - publish-github-main-images:
           <<: *run_for_main_branch
           <<: *release_requires
@@ -411,8 +442,7 @@ workflows:
 jobs:
   test:
     executor:
-      name: go/default
-      tag: "1.20"
+      name: go_cimg
     steps:
       - checkout
       - run:
@@ -467,8 +497,7 @@ jobs:
 
   build-all:
     executor:
-      name: go/default
-      tag: "1.20"
+      name: go_cimg
     steps:
       - checkout
       - go/mod-download-cached
@@ -520,6 +549,22 @@ jobs:
           paths:
             - dist
 
+  test-skupper-binary:
+    description: Run skupper command in a specific image
+    parameters:
+      image:
+        type: string
+    docker:
+      - image: << parameters.image >>
+    steps:
+      - attach_workspace:
+          at: .
+      - run:
+          name: "Run the skupper binary"
+          command: |
+            cd dist/linux-amd64
+            ./skupper version
+
   main_tests_minikube_local_cluster:
     executor: local_cluster_test_executor
     steps:
@@ -551,8 +596,7 @@ jobs:
 
   generate-manifest:
     executor:
-      name: go/default
-      tag: "1.20"
+      name: go_cimg
     steps:
       - checkout
       - go/mod-download-cached
@@ -591,8 +635,7 @@ jobs:
 
   publish-github-release-images:
     executor:
-      name: go/default
-      tag: "1.20"
+      name: go_cimg
     steps:
       - checkout
       - setup_remote_docker
@@ -612,8 +655,7 @@ jobs:
 
   publish-github-main-images:
     executor:
-      name: go/default
-      tag: "1.20"
+      name: go_cimg
     steps:
       - checkout
       - setup_remote_docker
@@ -630,3 +672,26 @@ jobs:
             source $BASH_ENV
             make -e docker-build
             make -e docker-push
+
+  publish-github-main-artifacts:
+    docker:
+      - image: cibuilds/github:0.10
+    steps:
+      - attach_workspace:
+          at: .
+      - run:
+          name: "Create a Pre-Release on GitHub"
+          command: |
+            VERSION="main-release"
+            BASEDIR=`pwd`
+            mkdir "${BASEDIR}/archives"
+            for p in `ls dist` ; do
+              cd "$BASEDIR/dist/$p"
+              if [[ $p == windows* ]] ; then
+                zip -q "${BASEDIR}/archives/skupper-cli-${VERSION}-$p.zip" *
+              else
+                tar -zcf "${BASEDIR}/archives/skupper-cli-${VERSION}-$p.tgz" *
+              fi
+            done
+            cd ${BASEDIR}
+            ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace -prerelease ${VERSION} "${BASEDIR}/archives"
diff --git a/api/types/client.go b/api/types/client.go
index 5f75cf97..30cace52 100644
--- a/api/types/client.go
+++ b/api/types/client.go
@@ -4,9 +4,10 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"github.com/skupperproject/skupper/pkg/network"
 	"time"
 
+	"github.com/skupperproject/skupper/pkg/network"
+
 	corev1 "k8s.io/api/core/v1"
 )
 
@@ -343,10 +344,12 @@ type VanClientInterface interface {
 	SkupperDump(ctx context.Context, tarName string, version string, kubeConfigPath string, kubeConfigContext string) (string, error)
 	SkupperEvents(verbose bool) (*bytes.Buffer, error)
 	SkupperCheckService(service string, verbose bool) (*bytes.Buffer, error)
+	SkupperPolicies(verbose bool) (*bytes.Buffer, error)
 	GetNamespace() string
 	GetVersion(component string, name string) string
 	GetIngressDefault() string
 	RevokeAccess(ctx context.Context) error
 	NetworkStatus(ctx context.Context) (*network.NetworkStatusInfo, error)
 	GetRemoteLinks(ctx context.Context, siteConfig *SiteConfig) ([]*network.RemoteLinkInfo, error)
+	GetConsoleUrl(namespace string) (string, error)
 }
diff --git a/client/client_debug.go b/client/client_debug.go
index 77afb916..1ffa40da 100644
--- a/client/client_debug.go
+++ b/client/client_debug.go
@@ -226,3 +226,7 @@ func (cli *VanClient) SkupperEvents(verbose bool) (*bytes.Buffer, error) {
 func (cli *VanClient) SkupperCheckService(service string, verbose bool) (*bytes.Buffer, error) {
 	return cli.execInServiceControllerPod(addOutputFlag([]string{"get", "servicecheck", service}, verbose))
 }
+
+func (cli *VanClient) SkupperPolicies(verbose bool) (*bytes.Buffer, error) {
+	return cli.execInServiceControllerPod(addOutputFlag([]string{"get", "policies", "list"}, verbose))
+}
diff --git a/client/connector_create.go b/client/connector_create.go
index fde22f53..6599cce6 100644
--- a/client/connector_create.go
+++ b/client/connector_create.go
@@ -3,8 +3,8 @@ package client
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net/url"
+	"os"
 	"reflect"
 	"strconv"
 
@@ -24,7 +24,7 @@ import (
 )
 
 func (cli *VanClient) ConnectorCreateFromFile(ctx context.Context, secretFile string, options types.ConnectorCreateOptions) (*corev1.Secret, error) {
-	yaml, err := ioutil.ReadFile(secretFile)
+	yaml, err := os.ReadFile(secretFile)
 	if err != nil {
 		fmt.Println("Could not read connection token", err.Error())
 		return nil, err
diff --git a/client/container/client.go b/client/container/client.go
index 334f8aae..3dccc3bb 100644
--- a/client/container/client.go
+++ b/client/container/client.go
@@ -3,7 +3,6 @@ package container
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"strings"
@@ -170,7 +169,7 @@ func (v *Volume) ReadFile(name string) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	data, err := ioutil.ReadFile(path.Join(vDir.Name(), name))
+	data, err := os.ReadFile(path.Join(vDir.Name(), name))
 	if err != nil {
 		return "", err
 	}
diff --git a/client/gateway.go b/client/gateway.go
index cfc0442f..1a7445a6 100644
--- a/client/gateway.go
+++ b/client/gateway.go
@@ -10,7 +10,6 @@ import (
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"os"
 	"os/exec"
@@ -286,7 +285,7 @@ func getConfigHome() string {
 }
 
 func getGatewaySiteId(gatewayDir string) (string, error) {
-	siteId, err := ioutil.ReadFile(gatewayDir + "/config/siteid.txt")
+	siteId, err := os.ReadFile(gatewayDir + "/config/siteid.txt")
 	if err != nil {
 		return "", fmt.Errorf("Failed to read site id: %w", err)
 	}
@@ -294,7 +293,7 @@ func getGatewaySiteId(gatewayDir string) (string, error) {
 }
 
 func getRouterId(gatewayDir string) (string, error) {
-	routerId, err := ioutil.ReadFile(gatewayDir + "/config/routerid.txt")
+	routerId, err := os.ReadFile(gatewayDir + "/config/routerid.txt")
 	if err != nil {
 		return "", fmt.Errorf("Failed to read router id: %w", err)
 	}
@@ -302,7 +301,7 @@ func getRouterId(gatewayDir string) (string, error) {
 }
 
 func getRouterUrl(gatewayDir string) (string, error) {
-	url, err := ioutil.ReadFile(gatewayDir + "/config/url.txt")
+	url, err := os.ReadFile(gatewayDir + "/config/url.txt")
 	if err != nil {
 		return "", fmt.Errorf("Failed to read router url: %w", err)
 	}
@@ -333,9 +332,9 @@ func getRouterVersion(gatewayName string, gatewayType string) (string, error) {
 }
 
 func getMachineID() (string, error) {
-	id, err := ioutil.ReadFile("/var/lib/dbus/machine-id")
+	id, err := os.ReadFile("/var/lib/dbus/machine-id")
 	if err != nil {
-		id, err = ioutil.ReadFile("/etc/machine-id")
+		id, err = os.ReadFile("/etc/machine-id")
 	}
 	if err != nil {
 		return "", err
@@ -471,12 +470,12 @@ func setupLocalDir(localDir string) error {
 
 func startGatewayUserService(gatewayName, unitDir, localDir string) error {
 
-	unitFile, err := ioutil.ReadFile(localDir + "/user/" + gatewayName + ".service")
+	unitFile, err := os.ReadFile(localDir + "/user/" + gatewayName + ".service")
 	if err != nil {
 		return fmt.Errorf("Unable to read service file: %w", err)
 	}
 
-	err = ioutil.WriteFile(unitDir+"/"+gatewayName+".service", unitFile, 0644)
+	err = os.WriteFile(unitDir+"/"+gatewayName+".service", unitFile, 0644)
 	if err != nil {
 		return fmt.Errorf("Unable to write user unit file: %w", err)
 	}
@@ -548,7 +547,7 @@ func updateLocalGatewayConfig(gatewayDir string, gatewayType string, gatewayConf
 	qdrConfig := template.Must(template.New("qdrConfig").Parse(mc))
 	qdrConfig.Execute(&buf, instance)
 
-	err = ioutil.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
+	err = os.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write config file: %w", err)
 	}
@@ -575,7 +574,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 	}
 
 	for _, cert := range certs {
-		err = ioutil.WriteFile(gatewayDir+"/skupper-router-certs/conn1-profile/"+cert, secret.Data[cert], 0644)
+		err = os.WriteFile(gatewayDir+"/skupper-router-certs/conn1-profile/"+cert, secret.Data[cert], 0644)
 		if err != nil {
 			return fmt.Errorf("Failed to write cert file: %w", err)
 		}
@@ -602,14 +601,14 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 
 	// store the url for instance queries
 	url := fmt.Sprintf("amqp://127.0.0.1:%s", strconv.Itoa(amqpPort))
-	err = ioutil.WriteFile(gatewayDir+"/config/url.txt", []byte(url), 0644)
+	err = os.WriteFile(gatewayDir+"/config/url.txt", []byte(url), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write instance url file: %w", err)
 	}
 
 	// generate a router id and store it for subsequent template updates
 	routerId := newUUID()
-	err = ioutil.WriteFile(gatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
+	err = os.WriteFile(gatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write instance id file: %w", err)
 	}
@@ -620,7 +619,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 		return fmt.Errorf("Failed to retrieve site id: %w", err)
 	}
 	if siteConfig != nil {
-		err = ioutil.WriteFile(gatewayDir+"/config/siteid.txt", []byte(siteConfig.Reference.UID), 0644)
+		err = os.WriteFile(gatewayDir+"/config/siteid.txt", []byte(siteConfig.Reference.UID), 0644)
 		if err != nil {
 			return fmt.Errorf("Failed to write site id file: %w", err)
 		}
@@ -664,7 +663,7 @@ func (cli *VanClient) setupGatewayConfig(ctx context.Context, gatewayName string
 	qdrConfig := template.Must(template.New("qdrConfig").Parse(mc))
 	qdrConfig.Execute(&buf, instance)
 
-	err = ioutil.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
+	err = os.WriteFile(gatewayDir+"/config/skrouterd.json", buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write config file: %w", err)
 	}
@@ -693,7 +692,7 @@ func (cli *VanClient) gatewayStartService(ctx context.Context, gatewayName strin
 		ConfigPath:      gatewayDir,
 		GatewayName:     gatewayName,
 	})
-	err = ioutil.WriteFile(gatewayDir+"/user/"+gatewayName+".service", []byte(qdrUserUnit), 0644)
+	err = os.WriteFile(gatewayDir+"/user/"+gatewayName+".service", []byte(qdrUserUnit), 0644)
 	if err != nil {
 		return fmt.Errorf("Failed to write unit file: %w", err)
 	}
@@ -926,7 +925,7 @@ func (cli *VanClient) newGateway(ctx context.Context, gatewayName string, gatewa
 
 	if configFile != "" {
 		// grab the bindings and forwards from the config file
-		yamlFile, err := ioutil.ReadFile(configFile)
+		yamlFile, err := os.ReadFile(configFile)
 		if err != nil {
 			return "", fmt.Errorf("Failed to read gateway config file: %w", err)
 		}
@@ -1138,7 +1137,7 @@ func (cli *VanClient) GatewayInit(ctx context.Context, gatewayName string, gatew
 	}
 
 	if configFile != "" && gatewayType != GatewayMockType {
-		yamlFile, err := ioutil.ReadFile(configFile)
+		yamlFile, err := os.ReadFile(configFile)
 		if err != nil {
 			return "", fmt.Errorf("Failed to read gateway config file: %w", err)
 		}
@@ -2055,7 +2054,7 @@ func (cli *VanClient) GatewayExportConfig(ctx context.Context, targetGatewayName
 		return exportFile, fmt.Errorf("Failed to marshal export config: %w", err)
 	}
 
-	err = ioutil.WriteFile(exportFile, mcData, 0644)
+	err = os.WriteFile(exportFile, mcData, 0644)
 	if err != nil {
 		return exportFile, fmt.Errorf("Failed to write export config file: %w", err)
 	}
@@ -2071,7 +2070,7 @@ func (cli *VanClient) GatewayGenerateBundle(ctx context.Context, configFile stri
 		return "", fmt.Errorf("Skupper is not enabled in namespace '%s'", cli.Namespace)
 	}
 
-	yamlFile, err := ioutil.ReadFile(configFile)
+	yamlFile, err := os.ReadFile(configFile)
 	if err != nil {
 		return "", fmt.Errorf("Failed to read gateway config file: %w", err)
 	}
diff --git a/client/network_status.go b/client/network_status.go
index e7131088..cea132ea 100644
--- a/client/network_status.go
+++ b/client/network_status.go
@@ -7,13 +7,12 @@ import (
 	"github.com/skupperproject/skupper/pkg/domain/kube"
 	k8s "github.com/skupperproject/skupper/pkg/kube"
 	"github.com/skupperproject/skupper/pkg/network"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func (cli *VanClient) NetworkStatus(ctx context.Context) (*network.NetworkStatusInfo, error) {
 
 	//Checking if the router has been deployed
-	_, err := cli.KubeClient.AppsV1().Deployments(cli.Namespace).Get(ctx, types.TransportDeploymentName, metav1.GetOptions{})
+	_, err := k8s.GetDeployment(types.TransportDeploymentName, cli.Namespace, cli.KubeClient)
 	if err != nil {
 		return nil, fmt.Errorf("Skupper is not installed: %s", err)
 	}
diff --git a/client/podman/rest.go b/client/podman/rest.go
index 821450b9..e73b595a 100644
--- a/client/podman/rest.go
+++ b/client/podman/rest.go
@@ -5,7 +5,6 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -201,7 +200,7 @@ func (r *responseReaderBody) Consume(reader io.Reader, i interface{}) error {
 	if !ok {
 		return fmt.Errorf("error parsing body")
 	}
-	data, err := ioutil.ReadAll(reader)
+	data, err := io.ReadAll(reader)
 	if err != nil {
 		return fmt.Errorf("error reading response body: %v", err)
 	}
diff --git a/client/router_create.go b/client/router_create.go
index a988f89f..6d60bf0b 100644
--- a/client/router_create.go
+++ b/client/router_create.go
@@ -3,6 +3,7 @@ package client
 import (
 	"context"
 	"fmt"
+	"github.com/briandowns/spinner"
 	"log"
 	"net"
 	"strconv"
@@ -1305,7 +1306,6 @@ sasldb_path: /tmp/skrouterd.sasldb
 	if cn != nil {
 		defer cn()
 	}
-	deadline, _ := currentContext.Deadline()
 
 	if options.Spec.RouterMode == string(types.TransportModeInterior) {
 		if options.Spec.IsIngressNginxIngress() || options.Spec.IsIngressKubernetes() {
@@ -1331,15 +1331,21 @@ sasldb_path: /tmp/skrouterd.sasldb
 					return err
 				}
 
+				var spin *spinner.Spinner
 				if len(hosts) == 0 {
+					waitingFor := "Waiting to try and resolve SANs for router..."
+
 					if options.Spec.IsIngressLoadBalancer() {
-						fmt.Printf("Waiting %d seconds for LoadBalancer IP or hostname...\n", time.Until(deadline).Milliseconds()/1000)
-					} else {
-						fmt.Printf("Waiting %d seconds to try and resolve SANs for router...\n", time.Until(deadline).Milliseconds()/1000)
+						waitingFor = "Waiting for LoadBalancer IP or hostname..."
 					}
+
+					spin = spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+					spin.Prefix = waitingFor
+					spin.FinalMSG = waitingFor + "\n"
 				}
 
 				for len(hosts) == 0 && !deadlineExceeded {
+					spin.Start()
 					select {
 					case <-ctx.Done():
 						fmt.Println("context deadline exceeded")
@@ -1354,6 +1360,10 @@ sasldb_path: /tmp/skrouterd.sasldb
 					}
 				}
 
+				if spin != nil {
+					spin.Stop()
+				}
+
 				if len(hosts) == 0 {
 					if options.Spec.IsIngressLoadBalancer() {
 						return fmt.Errorf("Failed to get LoadBalancer IP or Hostname for service %s", types.TransportServiceName)
@@ -1517,11 +1527,15 @@ func (cli *VanClient) appendLoadBalancerHostOrIp(ctx context.Context, serviceNam
 	host := kube.GetLoadBalancerHostOrIP(service)
 
 	ctx, _ = getCurrentContextOrDefault(ctx)
-	deadline, _ := ctx.Deadline()
-	fmt.Printf("Waiting %d seconds for LoadBalancer IP or hostname...\n", time.Until(deadline).Milliseconds()/1000)
 	deadlineExceeded := false
+	spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+
+	message := "Waiting for LoadBalancer IP or hostname..."
+	spin.Prefix = message
+	spin.FinalMSG = message + "\n"
 
 	for host == "" && !deadlineExceeded {
+		spin.Start()
 		select {
 		case <-ctx.Done():
 			fmt.Println("context deadline exceeded")
@@ -1534,6 +1548,8 @@ func (cli *VanClient) appendLoadBalancerHostOrIp(ctx context.Context, serviceNam
 		}
 	}
 
+	spin.Stop()
+
 	if host == "" {
 		return fmt.Errorf("Failed to get LoadBalancer IP or Hostname for service %s", serviceName)
 	} else {
diff --git a/client/router_inspect.go b/client/router_inspect.go
index 64697f11..35193d96 100644
--- a/client/router_inspect.go
+++ b/client/router_inspect.go
@@ -18,7 +18,7 @@ import (
 	"github.com/skupperproject/skupper/pkg/qdr"
 )
 
-func (cli *VanClient) getConsoleUrl(namespace string) (string, error) {
+func (cli *VanClient) GetConsoleUrl(namespace string) (string, error) {
 	config, err := cli.SiteConfigInspectInNamespace(context.Background(), nil, namespace)
 	if err != nil {
 		return "", err
@@ -116,7 +116,7 @@ func (cli *VanClient) RouterInspectNamespace(ctx context.Context, namespace stri
 		} else {
 			vir.ExposedServices = len(vsis)
 		}
-		url, err := cli.getConsoleUrl(namespace)
+		url, err := cli.GetConsoleUrl(namespace)
 		if url != "" {
 			vir.ConsoleUrl = url
 		}
diff --git a/client/router_update.go b/client/router_update.go
index 9a717d13..68240f85 100644
--- a/client/router_update.go
+++ b/client/router_update.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net"
 	"os"
@@ -910,17 +909,17 @@ func updateGatewayMultiport(ctx context.Context, cli *VanClient) error {
 			}
 			// generate a router id and store it for subsequent template updates
 			routerId := newUUID()
-			err = ioutil.WriteFile(newGatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
+			err = os.WriteFile(newGatewayDir+"/config/routerid.txt", []byte(routerId), 0644)
 			if err != nil {
 				return err
 			}
 			updateFileContent := func(fileName, oldPath, newPath string) error {
-				content, err := ioutil.ReadFile(fileName)
+				content, err := os.ReadFile(fileName)
 				if err != nil {
 					return err
 				}
 				updatedContent := strings.ReplaceAll(string(content), oldPath, newPath)
-				err = ioutil.WriteFile(fileName, []byte(updatedContent), 0)
+				err = os.WriteFile(fileName, []byte(updatedContent), 0)
 				if err != nil {
 					return err
 				}
diff --git a/cmd/config-sync/config_sync.go b/cmd/config-sync/config_sync.go
index 705d28df..c6563ed1 100644
--- a/cmd/config-sync/config_sync.go
+++ b/cmd/config-sync/config_sync.go
@@ -3,7 +3,6 @@ package main
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"math"
 	"net/http"
@@ -403,7 +402,7 @@ func (c *ConfigSync) copyCertsFilesToPath(path string, profilename string, secre
 
 	_, err = os.Stat(certFile)
 	if secret.Data["tls.crt"] != nil && os.IsNotExist(err) {
-		err = ioutil.WriteFile(certFile, secret.Data["tls.crt"], 0777)
+		err = os.WriteFile(certFile, secret.Data["tls.crt"], 0777)
 		if err != nil {
 			return err
 		}
@@ -411,7 +410,7 @@ func (c *ConfigSync) copyCertsFilesToPath(path string, profilename string, secre
 
 	_, err = os.Stat(keyFile)
 	if secret.Data["tls.key"] != nil && os.IsNotExist(err) {
-		err = ioutil.WriteFile(keyFile, secret.Data["tls.key"], 0777)
+		err = os.WriteFile(keyFile, secret.Data["tls.key"], 0777)
 		if err != nil {
 			return err
 		}
@@ -419,7 +418,7 @@ func (c *ConfigSync) copyCertsFilesToPath(path string, profilename string, secre
 
 	_, err = os.Stat(caCertFile)
 	if secret.Data["ca.crt"] != nil && os.IsNotExist(err) {
-		err = ioutil.WriteFile(caCertFile, secret.Data["ca.crt"], 0777)
+		err = os.WriteFile(caCertFile, secret.Data["ca.crt"], 0777)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/flow-collector/handlers.go b/cmd/flow-collector/handlers.go
index 0ebad711..0591d1d1 100644
--- a/cmd/flow-collector/handlers.go
+++ b/cmd/flow-collector/handlers.go
@@ -2,7 +2,7 @@ package main
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 
@@ -184,7 +184,7 @@ func (c *Controller) promqueryHandler(w http.ResponseWriter, r *http.Request) {
 		log.Printf("COLLECTOR: Prometheus query error: %s\n", err.Error())
 	} else {
 		w.WriteHeader(proxyResp.StatusCode)
-		data, _ := ioutil.ReadAll(proxyResp.Body)
+		data, _ := io.ReadAll(proxyResp.Body)
 		proxyResp.Body.Close()
 		fmt.Fprintf(w, "%s\n", data)
 	}
@@ -205,7 +205,7 @@ func (c *Controller) promqueryrangeHandler(w http.ResponseWriter, r *http.Reques
 	if err != nil {
 		log.Printf("COLLECTOR: Prometheus query_range error: %s\n", err.Error())
 	} else {
-		data, _ := ioutil.ReadAll(proxyResp.Body)
+		data, _ := io.ReadAll(proxyResp.Body)
 		proxyResp.Body.Close()
 		fmt.Fprintf(w, "%s\n", data)
 	}
diff --git a/cmd/flow-collector/main.go b/cmd/flow-collector/main.go
old mode 100644
new mode 100755
index 1c081a67..4a83a0a9
--- a/cmd/flow-collector/main.go
+++ b/cmd/flow-collector/main.go
@@ -2,11 +2,12 @@ package main
 
 import (
 	"context"
+	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -47,13 +48,18 @@ type connectJson struct {
 	Tls    tlsConfig `json:"tls,omitempty"`
 }
 
+type UserResponse struct {
+	Username string `json:"username"`
+	AuthMode string `json:"authType"`
+}
+
 var onlyOneSignalHandler = make(chan struct{})
 var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
 
 func getConnectInfo(file string) (connectJson, error) {
 	cj := connectJson{}
 
-	jsonFile, err := ioutil.ReadFile(file)
+	jsonFile, err := os.ReadFile(file)
 	if err != nil {
 		return cj, err
 	}
@@ -107,7 +113,7 @@ func authenticate(dir string, user string, password string) bool {
 	}
 	defer file.Close()
 
-	bytes, err := ioutil.ReadAll(file)
+	bytes, err := io.ReadAll(file)
 	if err != nil {
 		log.Printf("COLLECTOR: Failed to authenticate %s: %s", user, err)
 		return false
@@ -134,6 +140,72 @@ func authenticated(h http.HandlerFunc) http.HandlerFunc {
 	}
 }
 
+func getOpenshiftUser(r *http.Request) UserResponse {
+	userResponse := UserResponse{
+		Username: "",
+		AuthMode: string(types.ConsoleAuthModeOpenshift),
+	}
+
+	if cookie, err := r.Cookie("_oauth_proxy"); err == nil && cookie != nil {
+		if cookieDecoded, _ := base64.StdEncoding.DecodeString(cookie.Value); cookieDecoded != nil {
+			userResponse.Username = string(cookieDecoded)
+		}
+	}
+
+	return userResponse
+}
+
+func getInternalUser(r *http.Request) UserResponse {
+	userResponse := UserResponse{
+		Username: "",
+		AuthMode: string(types.ConsoleAuthModeInternal),
+	}
+
+	user, _, ok := r.BasicAuth()
+
+	if ok {
+		userResponse.Username = user
+	}
+
+	return userResponse
+}
+
+func getUnsecuredUser(r *http.Request) UserResponse {
+	return UserResponse{
+		Username: "",
+		AuthMode: string(types.ConsoleAuthModeUnsecured)}
+}
+
+func openshiftLogout(w http.ResponseWriter, r *http.Request) {
+	// Create a new cookie with MaxAge set to -1 to delete the existing cookie.
+	cookie := http.Cookie{
+		Name:   "_oauth_proxy", // openshift cookie name
+		Path:   "/",
+		MaxAge: -1,
+		Domain: r.Host,
+	}
+
+	http.SetCookie(w, &cookie)
+}
+
+func internalLogout(w http.ResponseWriter, r *http.Request, validNonces map[string]bool) {
+	queryParams := r.URL.Query()
+	nonce := queryParams.Get("nonce")
+
+	// When I logout, the browser open the prompt again and , if the credentials are correct, it calls the logout again.
+	// We track the second call using the nonce set from the client app to avoid loop of unauthenticated calls.
+	if _, exists := validNonces[nonce]; exists {
+		delete(validNonces, nonce)
+		fmt.Fprintf(w, "%s", "Logged out")
+
+		return
+	}
+
+	validNonces[nonce] = true
+	w.Header().Set("WWW-Authenticate", "Basic realm=skupper")
+	http.Error(w, "Unauthorized", http.StatusUnauthorized)
+}
+
 func main() {
 	// if -version used, report and exit
 	isVersion := flag.Bool("version", false, "Report the version of the Skupper Flow Collector")
@@ -156,6 +228,9 @@ func main() {
 	var flowRecordTtl time.Duration
 	var enableConsole bool
 	var prometheusUrl string
+	var authMode string
+	//collecting valid nonces for internal auth mode
+	var validNonces = make(map[string]bool)
 
 	// waiting on skupper-router to be available
 	if platform == "" || platform == types.PlatformKubernetes {
@@ -177,6 +252,7 @@ func main() {
 
 		flowRecordTtl = siteConfig.Spec.FlowCollector.FlowRecordTtl
 		enableConsole = siteConfig.Spec.EnableConsole
+		authMode = siteConfig.Spec.AuthMode
 
 		svc, err := kube.GetService(types.PrometheusServiceName, cli.Namespace, cli.KubeClient)
 		if err == nil {
@@ -207,6 +283,13 @@ func main() {
 		flowRecordTtl, _ = time.ParseDuration(os.Getenv("FLOW_RECORD_TTL"))
 		enableConsole, _ = strconv.ParseBool(os.Getenv("ENABLE_CONSOLE"))
 		prometheusUrl = "http://skupper-prometheus:9090/api/v1/"
+
+		flowUsers := os.Getenv("FLOW_USERS")
+		// Podman support only unsecured auth mode and internal auth mode
+		authMode = types.ConsoleAuthModeUnsecured
+		if flowUsers != "" {
+			authMode = types.ConsoleAuthModeInternal
+		}
 	}
 
 	tlsConfig := certs.GetTlsConfigRetriever(true, types.ControllerConfigPath+"tls.crt", types.ControllerConfigPath+"tls.key", types.ControllerConfigPath+"ca.crt")
@@ -223,6 +306,18 @@ func main() {
 	}
 	c.FlowCollector.Collector.PrometheusUrl = prometheusUrl
 
+	// map the authentication mode with the function to get the user
+	userMap := make(map[string]func(*http.Request) UserResponse)
+	userMap[string(types.ConsoleAuthModeOpenshift)] = getOpenshiftUser
+	userMap[string(types.ConsoleAuthModeInternal)] = getInternalUser
+	userMap[string(types.ConsoleAuthModeUnsecured)] = getUnsecuredUser
+
+	logoutMap := make(map[string]func(http.ResponseWriter, *http.Request))
+	logoutMap[string(types.ConsoleAuthModeOpenshift)] = openshiftLogout
+	logoutMap[string(types.ConsoleAuthModeInternal)] = func(w http.ResponseWriter, r *http.Request) {
+		internalLogout(w, r, validNonces)
+	}
+
 	var mux = mux.NewRouter().StrictSlash(true)
 
 	var api = mux.PathPrefix("/api").Subrouter()
@@ -284,6 +379,37 @@ func main() {
 		w.WriteHeader(http.StatusNotFound)
 	}))
 
+	var userApi = api1.PathPrefix("/user").Subrouter()
+	userApi.StrictSlash(true)
+	userApi.HandleFunc("/", authenticated(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		handler, exists := userMap[authMode]
+
+		if !exists {
+			w.WriteHeader(http.StatusNoContent)
+			return
+		}
+
+		response, err := json.Marshal(handler(r))
+
+		if err != nil {
+			log.Printf("Error /user response: %s", err.Error())
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(response)
+	})))
+
+	var userLogout = api1.PathPrefix("/logout").Subrouter()
+	userLogout.StrictSlash(true)
+	userLogout.HandleFunc("/", authenticated(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		handler, exists := logoutMap[authMode]
+		if exists {
+			handler(w, r)
+		}
+	})))
+
 	var siteApi = api1.PathPrefix("/sites").Subrouter()
 	siteApi.StrictSlash(true)
 	siteApi.HandleFunc("/", authenticated(http.HandlerFunc(c.siteHandler))).Name("list")
diff --git a/cmd/service-controller/console_server.go b/cmd/service-controller/console_server.go
index b3bd2670..4befe7cc 100644
--- a/cmd/service-controller/console_server.go
+++ b/cmd/service-controller/console_server.go
@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -65,7 +65,7 @@ func authenticate(dir string, user string, password string) bool {
 	}
 	defer file.Close()
 
-	bytes, err := ioutil.ReadAll(file)
+	bytes, err := io.ReadAll(file)
 	if err != nil {
 		event.Recordf(HttpAuthFailure, "Failed to authenticate %s: %s", user, err)
 		return false
diff --git a/cmd/service-controller/controller.go b/cmd/service-controller/controller.go
index c5bd9215..9b5d90b2 100644
--- a/cmd/service-controller/controller.go
+++ b/cmd/service-controller/controller.go
@@ -217,7 +217,9 @@ func NewController(cli *client.VanClient, origin string, tlsConfig *certs.TlsCon
 
 	controller.serviceSync = service_sync.NewServiceSync(origin, ttl, version.Version, qdr.NewConnectionFactory("amqps://"+types.QualifiedServiceName(types.LocalTransportServiceName, cli.Namespace)+":5671", tlsConfig), handler, controller.eventHandler)
 
-	controller.flowController = flow.NewFlowController(origin, version.Version, siteCreationTime, qdr.NewConnectionFactory("amqps://"+types.QualifiedServiceName(types.LocalTransportServiceName, cli.Namespace)+":5671", tlsConfig))
+	controller.flowController = flow.NewFlowController(origin, version.Version, siteCreationTime,
+		qdr.NewConnectionFactory("amqps://"+types.QualifiedServiceName(types.LocalTransportServiceName, cli.Namespace)+":5671", tlsConfig),
+		client.NewClusterPolicyValidator(cli))
 	ipHandler := func(deleted bool, name string, process *flow.ProcessRecord) error {
 		return flow.UpdateProcess(controller.flowController, deleted, name, process)
 	}
diff --git a/cmd/service-controller/links.go b/cmd/service-controller/links.go
index 47ef6ae4..625a9a0f 100644
--- a/cmd/service-controller/links.go
+++ b/cmd/service-controller/links.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strconv"
 	"time"
@@ -236,7 +236,7 @@ func serveLinks(m Links) http.Handler {
 				}
 			}
 		} else if r.Method == http.MethodPost {
-			body, err := ioutil.ReadAll(r.Body)
+			body, err := io.ReadAll(r.Body)
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
 			}
diff --git a/cmd/service-controller/services.go b/cmd/service-controller/services.go
index f6901960..63665d6f 100644
--- a/cmd/service-controller/services.go
+++ b/cmd/service-controller/services.go
@@ -4,10 +4,11 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/skupperproject/skupper/pkg/event"
-	"io/ioutil"
+	"io"
 	"net/http"
 
+	"github.com/skupperproject/skupper/pkg/event"
+
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
@@ -252,7 +253,7 @@ func (o *ServiceOptions) DeducePort() bool {
 
 func getServiceOptions(r *http.Request) (*ServiceOptions, error) {
 	options := &ServiceOptions{}
-	body, err := ioutil.ReadAll(r.Body)
+	body, err := io.ReadAll(r.Body)
 	if err != nil {
 		return options, err
 	}
diff --git a/cmd/skupper/skupper.go b/cmd/skupper/skupper.go
index b4872caa..a12ff51c 100644
--- a/cmd/skupper/skupper.go
+++ b/cmd/skupper/skupper.go
@@ -68,6 +68,7 @@ type SkupperDebugClient interface {
 	Dump(cmd *cobra.Command, args []string) error
 	Events(cmd *cobra.Command, args []string) error
 	Service(cmd *cobra.Command, args []string) error
+	Policies(cmd *cobra.Command, args []string) error
 	SkupperClientCommon
 }
 
@@ -910,6 +911,18 @@ func NewCmdDebugService(skupperClient SkupperDebugClient) *cobra.Command {
 	return cmd
 }
 
+func NewCmdDebugPolicies(skupperClient SkupperDebugClient) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:    "policies",
+		Short:  "List active SkupperClusterPolicies",
+		Args:   cobra.NoArgs,
+		PreRun: skupperClient.NewClient,
+		RunE:   skupperClient.Policies,
+	}
+	cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "More detailed output (in json)")
+	return cmd
+}
+
 func NewCmdRevokeaccess(skupperClient SkupperSiteClient) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "revoke-access",
@@ -1031,6 +1044,7 @@ func init() {
 	cmdDebugDump := NewCmdDebugDump(skupperCli.Debug())
 	cmdDebugEvents := NewCmdDebugEvents(skupperCli.Debug())
 	cmdDebugService := NewCmdDebugService(skupperCli.Debug())
+	cmdDebugPolicies := NewCmdDebugPolicies(skupperCli.Debug())
 
 	// Gateway command is only valid on Kubernetes sites
 	cmdGateway := NewCmdGateway()
@@ -1076,6 +1090,7 @@ func init() {
 	cmdDebug.AddCommand(cmdDebugDump)
 	cmdDebug.AddCommand(cmdDebugEvents)
 	cmdDebug.AddCommand(cmdDebugService)
+	cmdDebug.AddCommand(cmdDebugPolicies)
 
 	cmdLink := NewCmdLink()
 	cmdLink.AddCommand(NewCmdLinkCreate(skupperCli.Link(), ""))
diff --git a/cmd/skupper/skupper_kube_debug.go b/cmd/skupper/skupper_kube_debug.go
index 328ec232..ddd495be 100644
--- a/cmd/skupper/skupper_kube_debug.go
+++ b/cmd/skupper/skupper_kube_debug.go
@@ -6,6 +6,7 @@ import (
 	"os"
 
 	"github.com/skupperproject/skupper/api/types"
+	"github.com/skupperproject/skupper/client"
 	"github.com/skupperproject/skupper/pkg/version"
 	"github.com/spf13/cobra"
 )
@@ -52,3 +53,18 @@ func (s *SkupperKubeDebug) Service(cmd *cobra.Command, args []string) error {
 	os.Stdout.Write(output.Bytes())
 	return nil
 }
+
+func (s *SkupperKubeDebug) Policies(cmd *cobra.Command, args []string) error {
+	silenceCobra(cmd)
+	cpv := client.NewClusterPolicyValidator(s.kube.Cli.(*client.VanClient))
+	if !cpv.Enabled() {
+		fmt.Println("Skupper cluster policies are disabled")
+		return nil
+	}
+	output, err := s.kube.Cli.SkupperPolicies(verbose)
+	if err != nil {
+		return err
+	}
+	os.Stdout.Write(output.Bytes())
+	return nil
+}
diff --git a/cmd/skupper/skupper_kube_site.go b/cmd/skupper/skupper_kube_site.go
index 56aabc89..79a2e72b 100644
--- a/cmd/skupper/skupper_kube_site.go
+++ b/cmd/skupper/skupper_kube_site.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"github.com/skupperproject/skupper/pkg/network"
+	"github.com/skupperproject/skupper/pkg/utils"
 	"reflect"
 	"strings"
 	"time"
@@ -27,7 +28,6 @@ func (s *SkupperKubeSite) Platform() types.Platform {
 
 func (s *SkupperKubeSite) Create(cmd *cobra.Command, args []string) error {
 	cli := s.kube.Cli
-
 	silenceCobra(cmd)
 	ns := cli.GetNamespace()
 
@@ -102,6 +102,21 @@ func (s *SkupperKubeSite) Create(cmd *cobra.Command, args []string) error {
 		}
 		return err
 	}
+
+	err = utils.NewSpinner("Waiting for status...", 50, func() error {
+		statusInfo, statusError := cli.NetworkStatus(ctx)
+		if statusError != nil {
+			return statusError
+		} else if statusInfo == nil || len(statusInfo.SiteStatus) == 0 || len(statusInfo.SiteStatus[0].RouterStatus) == 0 {
+			return fmt.Errorf("network status not loaded yet")
+		}
+		return nil
+	})
+
+	if err != nil {
+		fmt.Println("Skupper status is not loaded yet.")
+	}
+
 	fmt.Println("Skupper is now installed in namespace '" + ns + "'.  Use 'skupper status' to get more information.")
 
 	return nil
@@ -219,14 +234,12 @@ func (s *SkupperKubeSite) ListFlags(cmd *cobra.Command) {}
 func (s *SkupperKubeSite) Status(cmd *cobra.Command, args []string) error {
 	silenceCobra(cmd)
 	cli := s.kube.Cli
-	vir, err := cli.RouterInspect(context.Background())
-	if err != nil {
-		fmt.Printf("Skupper is not enabled in namespace '%s'", cli.GetNamespace())
-		return nil
-	}
 
 	currentStatus, errStatus := cli.NetworkStatus(context.Background())
-	if errStatus != nil {
+	if errStatus != nil && strings.HasPrefix(errStatus.Error(), "Skupper is not installed") {
+		fmt.Printf("Skupper is not enabled in namespace '%s'", cli.GetNamespace())
+		return nil
+	} else if errStatus != nil {
 		return errStatus
 	}
 
@@ -255,15 +268,6 @@ func (s *SkupperKubeSite) Status(cmd *cobra.Command, args []string) error {
 				policies:  currentSite.Site.Policy,
 			}
 
-			if len(vir.Status.ConnectedSites.Warnings) > 0 {
-				var warnings []string
-				for _, w := range vir.Status.ConnectedSites.Warnings {
-					warnings = append(warnings, w)
-				}
-
-				statusDataOutput.warnings = warnings
-			}
-
 			mapSiteLink := statusManager.GetSiteLinkMapPerRouter(&currentSite.RouterStatus[0], &currentSite.Site)
 
 			totalSites := len(currentStatus.SiteStatus)
@@ -276,12 +280,14 @@ func (s *SkupperKubeSite) Status(cmd *cobra.Command, args []string) error {
 
 			statusDataOutput.exposedServices = len(currentStatus.Addresses)
 
+			consoleUrl, _ := cli.GetConsoleUrl(cli.GetNamespace())
+
 			siteConfig, err := cli.SiteConfigInspect(context.Background(), nil)
 			if err != nil {
 				return err
 			} else {
-				if siteConfig.Spec.EnableFlowCollector && vir.ConsoleUrl != "" {
-					statusDataOutput.consoleUrl = vir.ConsoleUrl
+				if siteConfig.Spec.EnableFlowCollector && consoleUrl != "" {
+					statusDataOutput.consoleUrl = consoleUrl
 					if siteConfig.Spec.AuthMode == "internal" {
 						statusDataOutput.credentials = PlatformSupport{"secret", "'skupper-console-users'"}
 					}
@@ -301,10 +307,12 @@ func (s *SkupperKubeSite) Status(cmd *cobra.Command, args []string) error {
 				}
 			}
 		} else {
-			return fmt.Errorf("unable to retrieve skupper status")
+			fmt.Println("Status pending...")
+			return nil
 		}
 	} else {
-		return fmt.Errorf("unable to retrieve skupper status")
+		fmt.Println("Status pending...")
+		return nil
 	}
 	return nil
 }
diff --git a/cmd/skupper/skupper_link.go b/cmd/skupper/skupper_link.go
index fe1c1459..6b0e967a 100644
--- a/cmd/skupper/skupper_link.go
+++ b/cmd/skupper/skupper_link.go
@@ -3,7 +3,7 @@ package main
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"strconv"
 	"time"
 
@@ -42,7 +42,7 @@ func NewCmdLinkCreate(skupperClient SkupperLinkClient, flag string) *cobra.Comma
 		PreRun: skupperClient.NewClient,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			// loading secret from file
-			yaml, err := ioutil.ReadFile(args[0])
+			yaml, err := os.ReadFile(args[0])
 			if err != nil {
 				return fmt.Errorf("Could not read connection token: %s", err.Error())
 			}
diff --git a/cmd/skupper/skupper_podman_debug.go b/cmd/skupper/skupper_podman_debug.go
index 0d8ee691..602dbf96 100644
--- a/cmd/skupper/skupper_podman_debug.go
+++ b/cmd/skupper/skupper_podman_debug.go
@@ -20,6 +20,10 @@ func (s *SkupperPodmanDebug) Service(cmd *cobra.Command, args []string) error {
 	return notImplementedErr
 }
 
+func (s *SkupperPodmanDebug) Policies(cmd *cobra.Command, args []string) error {
+	return notImplementedErr
+}
+
 func (s *SkupperPodmanDebug) NewClient(cmd *cobra.Command, args []string) {}
 
 func (s *SkupperPodmanDebug) Platform() types.Platform {
diff --git a/cmd/skupper/skupper_podman_service.go b/cmd/skupper/skupper_podman_service.go
index bcf18845..f1dc2f70 100644
--- a/cmd/skupper/skupper_podman_service.go
+++ b/cmd/skupper/skupper_podman_service.go
@@ -349,7 +349,11 @@ func (s *SkupperPodmanService) ExposeArgs(cmd *cobra.Command, args []string) err
 
 func (s *SkupperPodmanService) ExposeFlags(cmd *cobra.Command) {
 	cmd.Use = "expose [host <hostname|ip>]"
+	cmd.Short = "Expose one or more network services"
 	cmd.Example = `
+        # exposing a service running on the local machine
+        skupper expose host host.containers.internal --address my-service --port 8080
+
         # exposing a local network IP
         skupper expose host 10.0.0.1 --address my-service --port 8080
 
@@ -397,7 +401,11 @@ func (s *SkupperPodmanService) Unexpose(cmd *cobra.Command, args []string) error
 
 func (s *SkupperPodmanService) UnexposeFlags(cmd *cobra.Command) error {
 	cmd.Use = "unexpose [host <hostname|ip>]"
+	cmd.Short = "Unexpose one or more network services"
 	cmd.Example = `
+        # unexposing a service running on the local machine
+        skupper unexpose host host.containers.internal --address my-service
+
         # unexposing a local network IP
         skupper unexpose host 10.0.0.1 --address my-service
 
diff --git a/cmd/skupper/skupper_podman_site.go b/cmd/skupper/skupper_podman_site.go
index 138d871d..47b56756 100644
--- a/cmd/skupper/skupper_podman_site.go
+++ b/cmd/skupper/skupper_podman_site.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/google/uuid"
 	"github.com/skupperproject/skupper/api/types"
 	"github.com/skupperproject/skupper/client/container"
 	"github.com/skupperproject/skupper/pkg/domain"
@@ -76,6 +77,7 @@ func (s *SkupperPodmanSite) Create(cmd *cobra.Command, args []string) error {
 
 	siteHandler, err := podman.NewSitePodmanHandler(site.PodmanEndpoint)
 	if err != nil {
+		initErr := fmt.Errorf("Unable to initialize Skupper - %w", err)
 		recommendation := `
 Recommendation:
 
@@ -90,11 +92,13 @@ Recommendation:
 
 	You can get concrete examples through:
 
-		podman help system service
+		podman help system service`
 
-`
-		cmd.SetUsageTemplate(recommendation + cmd.UsageString())
-		return fmt.Errorf("Unable to initialize Skupper - %w", err)
+		cmd.SilenceUsage = true
+		cmd.SilenceErrors = true
+		fmt.Println("Error:", initErr)
+		fmt.Println(recommendation)
+		return initErr
 	}
 
 	// Validating ingress type
@@ -149,7 +153,8 @@ Recommendation:
 
 func getUserDefaultPodmanName() (string, error) {
 	hostname, _ := os.Hostname()
-	return hostname + "-" + strings.ToLower(podman.Username), nil
+	defaultName := fmt.Sprintf("%s-%s-%s", hostname, strings.ToLower(podman.Username), uuid.NewString()[:5])
+	return defaultName, nil
 }
 
 func (s *SkupperPodmanSite) CreateFlags(cmd *cobra.Command) {
diff --git a/cmd/skupper/skupper_status.go b/cmd/skupper/skupper_status.go
index 94c0b586..77d742a9 100644
--- a/cmd/skupper/skupper_status.go
+++ b/cmd/skupper/skupper_status.go
@@ -77,10 +77,10 @@ func PrintStatus(data StatusData) error {
 		fmt.Println("The site console url is: ", data.consoleUrl)
 		if len(data.credentials.supportName) > 0 {
 			fmt.Printf("The credentials for internal console-auth mode are held in %s: %s", data.credentials.supportType, data.credentials.supportName)
-			fmt.Println()
 		}
 	}
 
+	fmt.Println()
 	return nil
 }
 
diff --git a/go.mod b/go.mod
index bc9bc457..cd0b9900 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/skupperproject/skupper
 go 1.20
 
 require (
+	github.com/briandowns/spinner v1.23.0
 	github.com/davecgh/go-spew v1.1.1
 	github.com/fsnotify/fsnotify v1.6.0
 	github.com/go-openapi/errors v0.20.3
@@ -10,9 +11,10 @@ require (
 	github.com/go-openapi/strfmt v0.21.3
 	github.com/go-openapi/swag v0.21.1
 	github.com/go-openapi/validate v0.22.0
-	github.com/golang/glog v1.0.0
-	github.com/google/go-cmp v0.5.8
-	github.com/google/uuid v1.1.2
+	github.com/golang/glog v1.1.0
+	github.com/google/go-cmp v0.5.9
+	github.com/google/uuid v1.3.0
+	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5
 	github.com/openshift/api v0.0.0-20210428205234-a8389931bee7
@@ -23,11 +25,11 @@ require (
 	github.com/spf13/cobra v0.0.6
 	github.com/tsenart/vegeta/v12 v12.8.3
 	go.mongodb.org/mongo-driver v1.10.0
-	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
-	golang.org/x/net v0.8.0
-	golang.org/x/text v0.8.0
-	google.golang.org/grpc v1.35.0
-	google.golang.org/protobuf v1.28.1
+	golang.org/x/crypto v0.14.0
+	golang.org/x/net v0.17.0
+	golang.org/x/text v0.13.0
+	google.golang.org/grpc v1.56.3
+	google.golang.org/protobuf v1.30.0
 	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools v2.2.0+incompatible
 	k8s.io/api v0.20.0
@@ -38,7 +40,8 @@ require (
 replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
 
 require (
-	cloud.google.com/go v0.65.0 // indirect
+	cloud.google.com/go/compute v1.19.1 // indirect
+	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	github.com/Azure/azure-sdk-for-go v46.0.0+incompatible // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest v0.11.1 // indirect
@@ -52,10 +55,11 @@ require (
 	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 	github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 // indirect
 	github.com/evanphx/json-patch v4.9.0+incompatible // indirect
+	github.com/fatih/color v1.7.0 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
 	github.com/fortytw2/leaktest v1.3.0 // indirect
@@ -67,11 +71,10 @@ require (
 	github.com/go-openapi/spec v0.20.4 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golang/snappy v0.0.1 // indirect
 	github.com/google/gofuzz v1.1.0 // indirect
 	github.com/googleapis/gnostic v0.4.1 // indirect
-	github.com/gorilla/handlers v1.5.1 // indirect
 	github.com/hashicorp/golang-lru v0.5.1 // indirect
 	github.com/imdario/mergo v0.3.8 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
@@ -80,6 +83,8 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mattn/go-colorable v0.1.2 // indirect
+	github.com/mattn/go-isatty v0.0.8 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/mitchellh/mapstructure v1.4.3 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -98,13 +103,13 @@ require (
 	github.com/xdg-go/scram v1.1.1 // indirect
 	github.com/xdg-go/stringprep v1.0.3 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
-	golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
+	golang.org/x/oauth2 v0.7.0 // indirect
 	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
-	golang.org/x/sys v0.6.0 // indirect
-	golang.org/x/term v0.6.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/term v0.14.0 // indirect
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
-	google.golang.org/appengine v1.6.6 // indirect
-	google.golang.org/genproto v0.0.0-20210106152847-07624b53cd92 // indirect
+	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/klog/v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index ed320754..98b5b9d7 100644
--- a/go.sum
+++ b/go.sum
@@ -12,7 +12,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
 cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@@ -20,6 +19,10 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
+cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -79,18 +82,20 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDfD5VcY4CIfh1hRXBUavxrvELjTiOE=
 github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
+github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
+github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
 github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -118,10 +123,11 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -213,8 +219,8 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
-github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
+github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
+github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -242,8 +248,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50=
@@ -274,8 +281,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -290,8 +297,9 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
@@ -361,6 +369,10 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M=
@@ -481,7 +493,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -536,8 +547,9 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -607,16 +619,17 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
 golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -637,6 +650,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -682,13 +696,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
-golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -698,8 +712,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -780,8 +794,9 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -811,8 +826,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210106152847-07624b53cd92 h1:jOTk2Z6KYaWoptUFqZ167cS8peoUPjFEXrsqfVkkCGc=
-google.golang.org/genproto v0.0.0-20210106152847-07624b53cd92/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -826,8 +841,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
+google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -840,8 +855,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
-google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/certs/certs.go b/pkg/certs/certs.go
index e0d81cab..64274a1d 100644
--- a/pkg/certs/certs.go
+++ b/pkg/certs/certs.go
@@ -22,7 +22,6 @@ import (
 	"crypto/x509/pkix"
 	"encoding/pem"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"math/big"
 	"net"
@@ -309,7 +308,7 @@ func getTlsConfig(verify bool, cert, key, ca string) (*tls.Config, error) {
 	config.InsecureSkipVerify = true
 	if verify {
 		certPool := x509.NewCertPool()
-		file, err := ioutil.ReadFile(ca)
+		file, err := os.ReadFile(ca)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/config/local.go b/pkg/config/local.go
index 11476545..778a3820 100644
--- a/pkg/config/local.go
+++ b/pkg/config/local.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -56,7 +55,7 @@ func (l *ConfigFileHandlerCommon) Save() error {
 }
 
 func (l *ConfigFileHandlerCommon) Load() error {
-	data, err := ioutil.ReadFile(l.GetFilename())
+	data, err := os.ReadFile(l.GetFilename())
 	if err != nil && !os.IsNotExist(err) {
 		return fmt.Errorf("error loading %s: %v", l.GetFilename(), err)
 	}
@@ -116,7 +115,7 @@ func (p *PlatformInfo) Update(platform types.Platform) error {
 }
 
 func (p *PlatformInfo) Load() error {
-	data, err := ioutil.ReadFile(PlatformConfigFile)
+	data, err := os.ReadFile(PlatformConfigFile)
 	if err != nil && !os.IsNotExist(err) {
 		return fmt.Errorf("error loading %s: %v", PlatformConfigFile, err)
 	}
diff --git a/pkg/config/startup.go b/pkg/config/startup.go
index 6b2accfd..f752bc52 100644
--- a/pkg/config/startup.go
+++ b/pkg/config/startup.go
@@ -3,7 +3,6 @@ package config
 import (
 	_ "embed"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 
@@ -38,12 +37,12 @@ func GetStartupScripts(platform types.Platform) *StartupScripts {
 
 func (s *StartupScripts) Create() error {
 	startFileName := path.Join(GetDataHome(), s.GetStartFileName())
-	err := ioutil.WriteFile(startFileName, []byte(s.StartScript), 0755)
+	err := os.WriteFile(startFileName, []byte(s.StartScript), 0755)
 	if err != nil {
 		return err
 	}
 	stopFileName := path.Join(GetDataHome(), s.GetStopFileName())
-	err = ioutil.WriteFile(stopFileName, []byte(s.StopScript), 0755)
+	err = os.WriteFile(stopFileName, []byte(s.StopScript), 0755)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/config/systemd.go b/pkg/config/systemd.go
index 409318c3..02c60d31 100644
--- a/pkg/config/systemd.go
+++ b/pkg/config/systemd.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	_ "embed"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path"
@@ -55,7 +54,7 @@ func (s *systemdServiceInfo) Create() error {
 
 	// Saving systemd user service
 	serviceName := s.getServiceName()
-	err = ioutil.WriteFile(s.getServiceFile(), buf.Bytes(), 0644)
+	err = os.WriteFile(s.getServiceFile(), buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("Unable to write user unit file: %w", err)
 	}
diff --git a/pkg/domain/link.go b/pkg/domain/link.go
index a9fbccb6..5938692e 100644
--- a/pkg/domain/link.go
+++ b/pkg/domain/link.go
@@ -6,8 +6,7 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
-	"github.com/skupperproject/skupper/pkg/network"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"regexp"
@@ -15,6 +14,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/skupperproject/skupper/pkg/network"
+
 	"github.com/skupperproject/skupper/api/types"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/serializer/json"
@@ -115,7 +116,7 @@ func (c *ClaimRedeemer) RedeemClaim(claim *corev1.Secret) error {
 		fmt.Println()
 		return c.handleError(claim, err.Error(), false)
 	}
-	body, err := ioutil.ReadAll(response.Body)
+	body, err := io.ReadAll(response.Body)
 	if err != nil {
 		fmt.Printf("Claim request failed on reading body: %s", err)
 		fmt.Println()
diff --git a/pkg/domain/podman/controller/controller.go b/pkg/domain/podman/controller/controller.go
index 00c61dcb..1c73cb89 100644
--- a/pkg/domain/podman/controller/controller.go
+++ b/pkg/domain/podman/controller/controller.go
@@ -76,7 +76,7 @@ func (c *ControllerPodman) Run(stopCh <-chan struct{}) error {
 	c.site = sitePodman
 
 	siteCreationTime := uint64(time.Now().UnixNano()) / uint64(time.Microsecond)
-	flowController := flow.NewFlowController(c.origin, version.Version, siteCreationTime, qdr.NewConnectionFactory("amqps://"+types.LocalTransportServiceName+":5671", c.tlsConfig))
+	flowController := flow.NewFlowController(c.origin, version.Version, siteCreationTime, qdr.NewConnectionFactory("amqps://"+types.LocalTransportServiceName+":5671", c.tlsConfig), flow.WithPolicyDisabled)
 	flowController.Start(stopCh)
 	log.Println("Started flow-controller")
 
diff --git a/pkg/domain/podman/credential_handler.go b/pkg/domain/podman/credential_handler.go
index 4a59f7a1..d01b5b31 100644
--- a/pkg/domain/podman/credential_handler.go
+++ b/pkg/domain/podman/credential_handler.go
@@ -3,7 +3,6 @@ package podman
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 
@@ -171,7 +170,7 @@ func (p *CredentialHandler) LoadVolumeAsSecret(vol *container.Volume) (*corev1.S
 		if file.IsDir() {
 			continue
 		}
-		data, err := ioutil.ReadFile(path.Join(vol.Source, file.Name()))
+		data, err := os.ReadFile(path.Join(vol.Source, file.Name()))
 		if err != nil {
 			return nil, fmt.Errorf("error reading file %s for secret %s - %v", file.Name(), vol.Name, err)
 		}
diff --git a/pkg/domain/podman/site.go b/pkg/domain/podman/site.go
index 3af66872..4f025a3a 100644
--- a/pkg/domain/podman/site.go
+++ b/pkg/domain/podman/site.go
@@ -8,11 +8,13 @@ import (
 	"strings"
 	"time"
 
+	"github.com/google/uuid"
 	"github.com/skupperproject/skupper/api/types"
 	"github.com/skupperproject/skupper/client/container"
 	"github.com/skupperproject/skupper/client/podman"
 	"github.com/skupperproject/skupper/pkg/config"
 	"github.com/skupperproject/skupper/pkg/domain"
+	"github.com/skupperproject/skupper/pkg/images"
 	"github.com/skupperproject/skupper/pkg/qdr"
 	"github.com/skupperproject/skupper/pkg/utils"
 	"github.com/skupperproject/skupper/pkg/version"
@@ -428,6 +430,11 @@ func (s *SiteHandler) canCreate(site *Site) error {
 		return fmt.Errorf("You cannot use a remote podman endpoint - %w", err)
 	}
 
+	// Validating router container runs without errors
+	err = s.runTempContainer()
+	if err != nil {
+		return fmt.Errorf("site cannot be created: %s", err)
+	}
 	return nil
 }
 
@@ -902,3 +909,41 @@ func (s *SiteHandler) createPrometheusConfigFiles(site *Site) error {
 	}
 	return nil
 }
+
+func (s *SiteHandler) runTempContainer() error {
+	cli := s.cli
+	err := cli.ImagePull(images.GetRouterImageName())
+	if err != nil {
+		return err
+	}
+	tempName := fmt.Sprintf("skupper-temp-%s", uuid.NewString()[:5])
+	err = cli.ContainerCreate(
+		&container.Container{
+			Name:          tempName,
+			Image:         images.GetRouterImageName(),
+			RestartPolicy: "no",
+			Command:       []string{"skrouterd", "-version"},
+		},
+	)
+	if err != nil {
+		return fmt.Errorf("unable to validate container creation: %s", err)
+	}
+	defer func() {
+		_ = cli.ContainerRemove(tempName)
+	}()
+	if err = cli.ContainerStart(tempName); err != nil {
+		return fmt.Errorf("error starting validation container: %s", err)
+	}
+	ci, err := cli.ContainerInspect(tempName)
+	if err != nil {
+		return fmt.Errorf("error inspecting validation container: %s", err)
+	}
+	if ci.ExitCode != 0 {
+		logs, err := cli.ContainerLogs(tempName)
+		if err != nil {
+			return fmt.Errorf("error executing validation container (unable to read logs) - exit code: %d", ci.ExitCode)
+		}
+		return fmt.Errorf("unable to run %s - reason: %s", ci.Image, logs)
+	}
+	return nil
+}
diff --git a/pkg/flow/controller.go b/pkg/flow/controller.go
index 47978e0f..1d70ffe3 100644
--- a/pkg/flow/controller.go
+++ b/pkg/flow/controller.go
@@ -4,10 +4,10 @@ import (
 	"log"
 	"os"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/skupperproject/skupper/api/types"
-	"github.com/skupperproject/skupper/client"
 	"github.com/skupperproject/skupper/pkg/config"
 	"github.com/skupperproject/skupper/pkg/messaging"
 )
@@ -17,36 +17,40 @@ const (
 )
 
 type FlowController struct {
-	origin            string
-	creationTime      uint64
-	version           string
-	connectionFactory messaging.ConnectionFactory
-	beaconOutgoing    chan interface{}
-	heartbeatOutgoing chan interface{}
-	recordOutgoing    chan interface{}
-	flushIncoming     chan []interface{}
-	processOutgoing   chan *ProcessRecord
-	processRecords    map[string]*ProcessRecord
-	hostRecords       map[string]*HostRecord
-	hostOutgoing      chan *HostRecord
-	startTime         int64
+	origin               string
+	connectionFactory    messaging.ConnectionFactory
+	beaconOutgoing       chan interface{}
+	heartbeatOutgoing    chan interface{}
+	recordOutgoing       chan interface{}
+	flushIncoming        chan []interface{}
+	processOutgoing      chan *ProcessRecord
+	processRecords       map[string]*ProcessRecord
+	hostRecords          map[string]*HostRecord
+	hostOutgoing         chan *HostRecord
+	siteRecordController siteRecordController
+	startTime            int64
 }
 
-func NewFlowController(origin string, version string, creationTime uint64, connectionFactory messaging.ConnectionFactory) *FlowController {
+type PolicyEvaluator interface {
+	Enabled() bool
+}
+
+const WithPolicyDisabled = policyEnabledConst(false)
+
+func NewFlowController(origin string, version string, creationTime uint64, connectionFactory messaging.ConnectionFactory, policyEvaluator PolicyEvaluator) *FlowController {
 	fc := &FlowController{
-		origin:            origin,
-		creationTime:      creationTime,
-		version:           version,
-		connectionFactory: connectionFactory,
-		beaconOutgoing:    make(chan interface{}, 10),
-		heartbeatOutgoing: make(chan interface{}, 10),
-		recordOutgoing:    make(chan interface{}, 10),
-		flushIncoming:     make(chan []interface{}, 10),
-		processOutgoing:   make(chan *ProcessRecord, 10),
-		processRecords:    make(map[string]*ProcessRecord),
-		hostRecords:       make(map[string]*HostRecord),
-		hostOutgoing:      make(chan *HostRecord, 10),
-		startTime:         time.Now().Unix(),
+		origin:               origin,
+		connectionFactory:    connectionFactory,
+		beaconOutgoing:       make(chan interface{}, 10),
+		heartbeatOutgoing:    make(chan interface{}, 10),
+		recordOutgoing:       make(chan interface{}, 10),
+		flushIncoming:        make(chan []interface{}, 10),
+		processOutgoing:      make(chan *ProcessRecord, 10),
+		processRecords:       make(map[string]*ProcessRecord),
+		hostRecords:          make(map[string]*HostRecord),
+		hostOutgoing:         make(chan *HostRecord, 10),
+		siteRecordController: newSiteRecordController(creationTime, version, policyEvaluator),
+		startTime:            time.Now().Unix(),
 	}
 	return fc
 }
@@ -99,16 +103,13 @@ func UpdateHost(c *FlowController, deleted bool, name string, host *HostRecord)
 	return nil
 }
 
-func (c *FlowController) updateBeaconAndHeartbeats(stopCh <-chan struct{}) {
+func (c *FlowController) updateBeacon(stopCh <-chan struct{}) {
 	tickerAge := time.NewTicker(10 * time.Minute)
 	defer tickerAge.Stop()
 
 	beaconTimer := time.NewTicker(10 * time.Second)
 	defer beaconTimer.Stop()
 
-	heartbeatTimer := time.NewTicker(2 * time.Second)
-	defer heartbeatTimer.Stop()
-
 	identity := c.origin
 
 	beacon := &BeaconRecord{
@@ -119,20 +120,38 @@ func (c *FlowController) updateBeaconAndHeartbeats(stopCh <-chan struct{}) {
 		Identity:   identity,
 	}
 
+	c.beaconOutgoing <- beacon
+
+	for {
+		select {
+		case <-beaconTimer.C:
+			c.beaconOutgoing <- beacon
+		case <-tickerAge.C:
+		case <-stopCh:
+			return
+
+		}
+	}
+}
+
+func (c *FlowController) updateHeartbeats(stopCh <-chan struct{}) {
+	tickerAge := time.NewTicker(10 * time.Minute)
+	defer tickerAge.Stop()
+
+	heartbeatTimer := time.NewTicker(2 * time.Second)
+	defer heartbeatTimer.Stop()
+
 	heartbeat := &HeartbeatRecord{
 		Version:  1,
 		Identity: os.Getenv("SKUPPER_SITE_ID"),
 		Source:   "sfe." + c.origin,
 	}
 
-	c.beaconOutgoing <- beacon
 	heartbeat.Now = uint64(time.Now().UnixNano()) / uint64(time.Microsecond)
 	c.heartbeatOutgoing <- heartbeat
 
 	for {
 		select {
-		case <-beaconTimer.C:
-			c.beaconOutgoing <- beacon
 		case <-heartbeatTimer.C:
 			heartbeat.Now = uint64(time.Now().UnixNano()) / uint64(time.Microsecond)
 			c.heartbeatOutgoing <- heartbeat
@@ -144,46 +163,18 @@ func (c *FlowController) updateBeaconAndHeartbeats(stopCh <-chan struct{}) {
 	}
 }
 
-func (c *FlowController) updateRecords(stopCh <-chan struct{}) {
+func (c *FlowController) updateRecords(stopCh <-chan struct{}, siteRecordsIncoming <-chan *SiteRecord) {
 	tickerAge := time.NewTicker(10 * time.Minute)
 	defer tickerAge.Stop()
-
-	name := os.Getenv("SKUPPER_SITE_NAME")
-	nameSpace := os.Getenv("SKUPPER_NAMESPACE")
-	policy := Disabled
-	var platformStr string
-	platform := config.GetPlatform()
-	if platform == "" || platform == types.PlatformKubernetes {
-		platformStr = string(types.PlatformKubernetes)
-		cli, err := client.NewClient(nameSpace, "", "")
-		if err == nil {
-			cpv := client.NewClusterPolicyValidator(cli)
-			if cpv.Enabled() {
-				policy = Enabled
-			}
-		}
-	} else if platform == types.PlatformPodman {
-		platformStr = string(types.PlatformPodman)
-	}
-	site := &SiteRecord{
-		Base: Base{
-			RecType:   recordNames[Site],
-			Identity:  os.Getenv("SKUPPER_SITE_ID"),
-			StartTime: c.creationTime,
-		},
-		Name:      &name,
-		NameSpace: &nameSpace,
-		Platform:  &platformStr,
-		Version:   &c.version,
-		Policy:    &policy,
-	}
-
-	c.recordOutgoing <- site
-
 	for {
 		select {
 		case process := <-c.processOutgoing:
 			c.recordOutgoing <- process
+		case site, ok := <-siteRecordsIncoming:
+			if !ok {
+				continue
+			}
+			c.recordOutgoing <- site
 		case host := <-c.hostOutgoing:
 			c.recordOutgoing <- host
 		case flushUpdates := <-c.flushIncoming:
@@ -193,7 +184,7 @@ func (c *FlowController) updateRecords(stopCh <-chan struct{}) {
 					log.Println("Unable to convert interface to flush")
 				}
 			}
-			c.recordOutgoing <- site
+			c.recordOutgoing <- c.siteRecordController.Record()
 			for _, process := range c.processRecords {
 				c.recordOutgoing <- process
 			}
@@ -223,12 +214,127 @@ func (c *FlowController) run(stopCh <-chan struct{}) {
 	recordSender.start()
 	flushReceiver.start()
 
-	go c.updateBeaconAndHeartbeats(stopCh)
-	go c.updateRecords(stopCh)
+	go c.updateBeacon(stopCh)
+	go c.updateHeartbeats(stopCh)
+	go c.updateRecords(stopCh, c.siteRecordController.Start(stopCh))
 	<-stopCh
 
 	beaconSender.stop()
-	heartbeatSender.start()
+	heartbeatSender.stop()
 	recordSender.stop()
 	flushReceiver.stop()
 }
+
+type siteRecordController struct {
+	mu            sync.Mutex
+	Identity      string
+	CreatedAt     uint64
+	Version       string
+	Name          string
+	Namespace     string
+	Platform      string
+	PolicyEnabled bool
+
+	policyEvaluator PolicyEvaluator
+	pollInterval    time.Duration
+}
+
+func newSiteRecordController(createdAt uint64, version string, policyEvaluator PolicyEvaluator) siteRecordController {
+	var policy bool
+	var platformStr string
+	platform := config.GetPlatform()
+	if platform == "" || platform == types.PlatformKubernetes {
+		platformStr = string(types.PlatformKubernetes)
+		policy = policyEvaluator.Enabled()
+	} else if platform == types.PlatformPodman {
+		platformStr = string(types.PlatformPodman)
+	}
+	return siteRecordController{
+		Identity:        os.Getenv("SKUPPER_SITE_ID"),
+		CreatedAt:       createdAt,
+		Version:         version,
+		Name:            os.Getenv("SKUPPER_SITE_NAME"),
+		Namespace:       os.Getenv("SKUPPER_NAMESPACE"),
+		Platform:        platformStr,
+		PolicyEnabled:   policy,
+		policyEvaluator: policyEvaluator,
+	}
+}
+
+func (c *siteRecordController) Start(stopCh <-chan struct{}) <-chan *SiteRecord {
+	updates := make(chan *SiteRecord, 1)
+
+	go func() {
+		defer close(updates)
+		updates <- c.Record()
+
+		if c.Platform != string(types.PlatformKubernetes) {
+			return
+		}
+		// watch for changes to  policy enabled
+		pollInterval := c.pollInterval
+		if pollInterval <= 0 {
+			pollInterval = time.Second * 30
+		}
+		ticker := time.NewTicker(pollInterval)
+		defer ticker.Stop()
+		for {
+			select {
+			case <-stopCh:
+				return
+			case <-ticker.C:
+				if update := c.updatePolicy(); update != nil {
+					updates <- update
+				}
+			}
+		}
+	}()
+	return updates
+}
+
+func (c *siteRecordController) updatePolicy() *SiteRecord {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	enabled := c.policyEvaluator.Enabled()
+	if enabled == c.PolicyEnabled {
+		return nil
+	}
+	c.PolicyEnabled = enabled
+	policy := Disabled
+	if enabled {
+		policy = Enabled
+	}
+	return &SiteRecord{
+		Base: Base{
+			RecType:  recordNames[Site],
+			Identity: c.Identity,
+		},
+		Policy: &policy,
+	}
+
+}
+
+func (c *siteRecordController) Record() *SiteRecord {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	policy := Disabled
+	if c.PolicyEnabled {
+		policy = Enabled
+	}
+	return &SiteRecord{
+		Base: Base{
+			RecType:   recordNames[Site],
+			Identity:  c.Identity,
+			StartTime: c.CreatedAt,
+		},
+		Name:      &c.Name,
+		NameSpace: &c.Namespace,
+		Platform:  &c.Platform,
+		Version:   &c.Version,
+		Policy:    &policy,
+	}
+}
+
+type policyEnabledConst bool
+
+func (c policyEnabledConst) Enabled() bool { return bool(c) }
diff --git a/pkg/flow/flow_mem_driver.go b/pkg/flow/flow_mem_driver.go
index 76fed167..94cc67f0 100644
--- a/pkg/flow/flow_mem_driver.go
+++ b/pkg/flow/flow_mem_driver.go
@@ -690,6 +690,7 @@ func (fc *FlowCollector) updateLastHeard(source string) error {
 }
 
 func (fc *FlowCollector) updateRecord(record interface{}) error {
+	var updatesNetworkStatus bool
 	switch record.(type) {
 	case HeartbeatRecord:
 		if heartbeat, ok := record.(HeartbeatRecord); ok {
@@ -721,7 +722,10 @@ func (fc *FlowCollector) updateRecord(record interface{}) error {
 					}
 					fc.deleteRecord(current)
 				} else {
-					*current = site
+					updatesNetworkStatus = true
+					if site.Policy != nil {
+						current.Policy = site.Policy
+					}
 				}
 			}
 			fc.updateLastHeard(site.Source)
@@ -1182,6 +1186,10 @@ func (fc *FlowCollector) updateRecord(record interface{}) error {
 	default:
 		return fmt.Errorf("Unrecognized record type %T", record)
 	}
+
+	if updatesNetworkStatus && fc.mode == RecordStatus {
+		fc.updateNetworkStatus()
+	}
 	return nil
 }
 
@@ -2436,18 +2444,24 @@ func (fc *FlowCollector) reconcileConnectorRecords() error {
 				}
 				for _, process := range fc.Processes {
 					if siteId == process.Parent {
-						if process.SourceHost != nil {
+						if process.SourceHost != nil && matchHost != nil {
 							if *matchHost == *process.SourceHost {
-								connector.ProcessId = &process.Identity
-								connector.Target = process.Name
-								process.connector = &connector.Identity
-								process.ProcessBinding = &Bound
-								fc.updateNetworkStatus()
-								log.Printf("COLLECTOR: Connector %s/%s associated to process %s\n", connector.Identity, *connector.Address, *process.Name)
-								delete(fc.connectorsToReconcile, connId)
 								found = true
-								break
 							}
+						} else if process.HostName != nil {
+							if *process.HostName == *connector.DestHost {
+								found = true
+							}
+						}
+						if found {
+							connector.ProcessId = &process.Identity
+							connector.Target = process.Name
+							process.connector = &connector.Identity
+							process.ProcessBinding = &Bound
+							fc.updateNetworkStatus()
+							log.Printf("COLLECTOR: Connector %s/%s associated to process %s\n", connector.Identity, *connector.Address, *process.Name)
+							delete(fc.connectorsToReconcile, connId)
+							break
 						}
 					}
 				}
diff --git a/pkg/kube/claims/claim_verifier.go b/pkg/kube/claims/claim_verifier.go
index 54e379ff..8c8c621d 100644
--- a/pkg/kube/claims/claim_verifier.go
+++ b/pkg/kube/claims/claim_verifier.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"os"
@@ -143,7 +143,7 @@ func (server *ClaimVerifier) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	name := strings.Join(strings.Split(r.URL.Path, "/"), "")
-	body, err := ioutil.ReadAll(r.Body)
+	body, err := io.ReadAll(r.Body)
 	if err != nil {
 		log.Printf("Error reading body: %s", err.Error())
 		http.Error(w, "Request body not valid", http.StatusBadRequest)
diff --git a/pkg/kube/namespaces.go b/pkg/kube/namespaces.go
index 5dd62adb..57ba7ace 100644
--- a/pkg/kube/namespaces.go
+++ b/pkg/kube/namespaces.go
@@ -3,7 +3,6 @@ package kube
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 
 	corev1 "k8s.io/api/core/v1"
@@ -45,7 +44,7 @@ func CurrentNamespace() (string, error) {
 	}
 	_, err := os.Stat(NamespaceFile)
 	if err == nil {
-		raw, err := ioutil.ReadFile(NamespaceFile)
+		raw, err := os.ReadFile(NamespaceFile)
 		if err != nil {
 			return namespace, err
 		}
diff --git a/pkg/utils/spinner.go b/pkg/utils/spinner.go
new file mode 100644
index 00000000..fb46eb4e
--- /dev/null
+++ b/pkg/utils/spinner.go
@@ -0,0 +1,25 @@
+package utils
+
+import (
+	"github.com/briandowns/spinner"
+	"time"
+)
+
+func NewSpinner(message string, maxRetries int, function func() error) error {
+
+	spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	spin.Prefix = message
+	spin.FinalMSG = message + "\n"
+
+	spin.Start()
+
+	err := RetryError(time.Second, maxRetries, function)
+
+	spin.Stop()
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}


Test patch:
diff --git a/client/token_claim_create_test.go b/client/token_claim_create_test.go
index e38bfae4..767eaf4c 100644
--- a/client/token_claim_create_test.go
+++ b/client/token_claim_create_test.go
@@ -3,7 +3,6 @@ package client
 import (
 	"bytes"
 	"context"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"strings"
@@ -19,7 +18,7 @@ import (
 )
 
 func readSecretFromFile(filename string) (*corev1.Secret, error) {
-	yaml, err := ioutil.ReadFile(filename)
+	yaml, err := os.ReadFile(filename)
 	if err != nil {
 		return nil, err
 	}
diff --git a/cmd/service-controller/claim_handler_test.go b/cmd/service-controller/claim_handler_test.go
index de2e93af..26c3d2e2 100644
--- a/cmd/service-controller/claim_handler_test.go
+++ b/cmd/service-controller/claim_handler_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -53,7 +53,7 @@ func (server *MockVerifier) addFailedResult(code int, message string) {
 
 func (server *MockVerifier) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if len(server.Results) > server.Current {
-		body, err := ioutil.ReadAll(r.Body)
+		body, err := io.ReadAll(r.Body)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		} else {
diff --git a/cmd/service-controller/services_test.go b/cmd/service-controller/services_test.go
index 470e708e..01e95bad 100644
--- a/cmd/service-controller/services_test.go
+++ b/cmd/service-controller/services_test.go
@@ -6,7 +6,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"reflect"
@@ -447,7 +446,7 @@ func TestServeServices(t *testing.T) {
 		res := httptest.NewRecorder()
 
 		router.ServeHTTP(res, req)
-		responseBody, _ := ioutil.ReadAll(res.Body)
+		responseBody, _ := io.ReadAll(res.Body)
 		assert.Equal(t, res.Code, test.expectedCode, name+" "+string(responseBody))
 		if test.checker != nil {
 			assert.Check(t, test.checker.Check(responseBody), name)
@@ -638,7 +637,7 @@ func TestServeServiceTargets(t *testing.T) {
 		res := httptest.NewRecorder()
 
 		router.ServeHTTP(res, req)
-		responseBody, _ := ioutil.ReadAll(res.Body)
+		responseBody, _ := io.ReadAll(res.Body)
 		assert.Equal(t, res.Code, test.expectedCode, name+" "+string(responseBody))
 		if test.checker != nil {
 			assert.Check(t, test.checker.Check(responseBody), name)
diff --git a/cmd/service-controller/tokens_test.go b/cmd/service-controller/tokens_test.go
index 634217c8..032b9a5f 100644
--- a/cmd/service-controller/tokens_test.go
+++ b/cmd/service-controller/tokens_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"context"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
@@ -262,7 +261,7 @@ func TestDownloadClaim(t *testing.T) {
 		router.ServeHTTP(res, req)
 		assert.Equal(t, res.Code, test.expectedCode, name)
 		if res.Code == http.StatusOK {
-			body, err := ioutil.ReadAll(res.Body)
+			body, err := io.ReadAll(res.Body)
 			assert.Check(t, err, testname)
 			s := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme, json.SerializerOptions{})
 			var token corev1.Secret
diff --git a/cmd/skupper/skupper_cluster_test.go b/cmd/skupper/skupper_cluster_test.go
index 05c80297..6340ebdd 100644
--- a/cmd/skupper/skupper_cluster_test.go
+++ b/cmd/skupper/skupper_cluster_test.go
@@ -4,7 +4,10 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes"
+	k8stesting "k8s.io/client-go/testing"
 	"os"
 	"regexp"
 	"strings"
@@ -101,6 +104,9 @@ func (s *SkupperTestClient) NewClient(cmd *cobra.Command, args []string) {
 		s.Cli = NewClient(s.Namespace, s.KubeContext, s.KubeConfigPath)
 	} else {
 		s.Cli = newMockClient(s.Namespace)
+		if c, ok := s.Cli.(*client.VanClient); ok {
+			setUpMockups(c.KubeClient)
+		}
 	}
 	s.common = s
 }
@@ -144,7 +150,7 @@ func testCommand(t *testing.T, cmd *cobra.Command, testName string, expectedErro
 	cmdOutput, err := executeCommand(cmd, args...)
 
 	w.Close()
-	stdOutput, _ := ioutil.ReadAll(r)
+	stdOutput, _ := io.ReadAll(r)
 	os.Stdout = rescueStdout
 
 	lines := strings.Split(string(stdOutput), "\n")
@@ -1689,3 +1695,70 @@ func TestDebugDumpWithCluster(t *testing.T) {
 		testCommand(t, cmd, tc.doc, tc.expectedError, tc.expectedCapture, tc.expectedOutput, tc.outputRegExp, tc.args...)
 	}
 }
+
+func setUpMockups(cli kubernetes.Interface) {
+
+	cli.(*fake.Clientset).Fake.PrependReactor("get", "configmaps", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
+		configMapName := action.(k8stesting.GetAction).GetName()
+
+		if configMapName == "skupper-network-status" {
+			configMap := corev1.ConfigMap{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "v1",
+					Kind:       "ConfigMap",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: types.NetworkStatusConfigMapName,
+				},
+				Data: map[string]string{
+					"NetworkStatus": `
+{
+  "addresses": null,
+  "siteStatus": [
+    {
+      "site": {
+        "recType": "SITE",
+        "identity": "0c32be16-c586-47ee-bbc0-1c9ef8cda0d5",
+        "startTime": 1700583152000000,
+        "endTime": 0,
+        "source": "0c32be16-c586-47ee-bbc0-1c9ef8cda0d5",
+        "platform": "kubernetes",
+        "name": "public1",
+        "nameSpace": "public1",
+        "siteVersion": "285bd9f",
+        "policy": "disabled"
+      },
+      "routerStatus": [
+        {
+          "router": {
+            "recType": "ROUTER",
+            "identity": "tw6zq:0",
+            "parent": "0c32be16-c586-47ee-bbc0-1c9ef8cda0d5",
+            "startTime": 1700583162641974,
+            "endTime": 0,
+            "source": "tw6zq:0",
+            "name": "0/public1-skupper-router-77bcc658bc-tw6zq",
+            "namespace": "public1",
+            "imageName": "skupper-router",
+            "imageVersion": "latest",
+            "hostname": "skupper-router-77bcc658bc-tw6zq",
+            "buildVersion": "0.0.0+2e63964b8e793231ba23d668333c5bd6bcf6255f-main"
+          },
+          "links": null,
+          "listeners": null,
+          "connectors": null
+        }
+      ]
+    }
+  ]
+}
+`,
+				},
+			}
+			return true, &configMap, nil
+		} else {
+			return false, nil, nil
+		}
+	})
+
+}
diff --git a/cmd/skupper/skupper_mock_test.go b/cmd/skupper/skupper_mock_test.go
index 79d0e1f1..380ed7a1 100644
--- a/cmd/skupper/skupper_mock_test.go
+++ b/cmd/skupper/skupper_mock_test.go
@@ -4,12 +4,13 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-	"github.com/skupperproject/skupper/pkg/network"
 	"reflect"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/skupperproject/skupper/pkg/network"
+
 	"github.com/google/go-cmp/cmp"
 	"github.com/skupperproject/skupper/api/types"
 	"github.com/spf13/cobra"
@@ -241,6 +242,10 @@ func (v *vanClientMock) SkupperEvents(verbose bool) (*bytes.Buffer, error) {
 	return nil, nil
 }
 
+func (v *vanClientMock) SkupperPolicies(verbose bool) (*bytes.Buffer, error) {
+	return nil, nil
+}
+
 func (v *vanClientMock) SkupperCheckService(service string, verbose bool) (*bytes.Buffer, error) {
 	return nil, nil
 }
@@ -301,13 +306,27 @@ func (v *vanClientMock) RevokeAccess(ctx context.Context) error {
 }
 
 func (v *vanClientMock) NetworkStatus(ctx context.Context) (*network.NetworkStatusInfo, error) {
-	return nil, nil
+
+	routerStatus := network.RouterStatusInfo{
+		Router: network.RouterInfo{Name: "router", Namespace: "default"},
+	}
+
+	siteStatus := network.SiteStatusInfo{RouterStatus: []network.RouterStatusInfo{routerStatus}}
+
+	result := network.NetworkStatusInfo{
+		SiteStatus: []network.SiteStatusInfo{siteStatus},
+	}
+	return &result, nil
 }
 
 func (v *vanClientMock) GetRemoteLinks(ctx context.Context, siteConfig *types.SiteConfig) ([]*network.RemoteLinkInfo, error) {
 	return nil, nil
 }
 
+func (v *vanClientMock) GetConsoleUrl(namespace string) (string, error) {
+	return "", nil
+}
+
 func TestCmdUnexposeRun(t *testing.T) {
 	skupperClient := NewSkupperTestClient()
 	cmd := NewCmdUnexpose(skupperClient.Service())
diff --git a/pkg/config/local_test.go b/pkg/config/local_test.go
index 2b2379b6..9309b9e3 100644
--- a/pkg/config/local_test.go
+++ b/pkg/config/local_test.go
@@ -3,7 +3,6 @@ package config
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"reflect"
 	"testing"
@@ -365,7 +364,7 @@ func TestPlatformInfo_Update(t *testing.T) {
 
 			// loading file
 			if tt.want != nil {
-				data, _ := ioutil.ReadFile(tempFile)
+				data, _ := os.ReadFile(tempFile)
 				decoder := yaml.NewDecoder(bytes.NewReader(data))
 				current := &PlatformInfo{}
 				_ = decoder.Decode(current)
diff --git a/pkg/domain/podman/token_test.go b/pkg/domain/podman/token_test.go
index 7a5286e2..1a75cd3a 100644
--- a/pkg/domain/podman/token_test.go
+++ b/pkg/domain/podman/token_test.go
@@ -4,7 +4,6 @@
 package podman
 
 import (
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -48,7 +47,7 @@ func TestPodmanTokenCertHandler(t *testing.T) {
 	assert.Assert(t, err)
 
 	// loading secret from file
-	yaml, err := ioutil.ReadFile(tokenFile.Name())
+	yaml, err := os.ReadFile(tokenFile.Name())
 	assert.Assert(t, err)
 
 	// reading yaml as secret
diff --git a/pkg/flow/controller_test.go b/pkg/flow/controller_test.go
index f0badd1e..9b3856c8 100644
--- a/pkg/flow/controller_test.go
+++ b/pkg/flow/controller_test.go
@@ -5,11 +5,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/skupperproject/skupper/api/types"
 	"gotest.tools/assert"
 )
 
 func TestUpdateProcess(t *testing.T) {
-	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	procName := "tcp-go-echo"
 	sourceIP := "10.0.0.1"
@@ -37,7 +38,7 @@ func TestUpdateProcess(t *testing.T) {
 }
 
 func TestUpdateHost(t *testing.T) {
-	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mysite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	hostName := "bastion-server"
 	host := &HostRecord{
@@ -61,10 +62,11 @@ func TestUpdateHost(t *testing.T) {
 
 func TestUpdateBeaconAndHeartbeats(t *testing.T) {
 	_ = os.Setenv("SKUPPER_SITE_ID", "mySite")
-	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	stopCh := make(chan struct{})
-	go fc.updateBeaconAndHeartbeats(stopCh)
+	go fc.updateBeacon(stopCh)
+	go fc.updateHeartbeats(stopCh)
 	beaconUpdate := <-fc.beaconOutgoing
 	assert.Assert(t, beaconUpdate != nil)
 	beacon, ok := beaconUpdate.(*BeaconRecord)
@@ -89,10 +91,11 @@ func TestUpdateBeaconAndHeartbeats(t *testing.T) {
 func TestUpdateRecords(t *testing.T) {
 	_ = os.Setenv("SKUPPER_SITE_ID", "mySite")
 	_ = os.Setenv("SKUPPER_NAMESPACE", "myNamespace")
-	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil)
+	fc := NewFlowController("mySite", "X.Y.Z", uint64(time.Now().UnixNano())/uint64(time.Microsecond), nil, WithPolicyDisabled)
 	assert.Assert(t, fc != nil)
 	stopCh := make(chan struct{})
-	go fc.updateRecords(stopCh)
+
+	go fc.updateRecords(stopCh, fc.siteRecordController.Start(stopCh))
 
 	recordUpdate := <-fc.recordOutgoing
 	assert.Assert(t, recordUpdate != nil)
@@ -147,3 +150,146 @@ func TestUpdateRecords(t *testing.T) {
 	close(stopCh)
 
 }
+
+func TestSiteController(t *testing.T) {
+	alwaysOnEval := policyEnabledConst(true)
+	flipFlopEval := stubPolicyEvaluator{
+		Next: make(chan bool, 8),
+	}
+	testCases := []struct {
+		controller        *siteRecordController
+		expectedOut       []func(t *testing.T, record *SiteRecord, ok bool)
+		waitTimeout       time.Duration
+		expectWaitTimeout bool
+	}{
+		{
+			controller: &siteRecordController{
+				Identity:        "basic-podman",
+				Name:            "basic",
+				Platform:        string(types.PlatformPodman),
+				CreatedAt:       1_111_111,
+				policyEvaluator: WithPolicyDisabled,
+			},
+			expectedOut: []func(*testing.T, *SiteRecord, bool){
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, rec.Identity, "basic-podman")
+					assert.Equal(t, *rec.Name, "basic")
+					assert.Equal(t, *rec.Platform, string(types.PlatformPodman))
+					assert.Equal(t, rec.StartTime, uint64(1_111_111))
+					assert.Equal(t, *rec.Policy, Disabled)
+				},
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, ok, false) // expect closed
+				},
+			},
+			waitTimeout: time.Millisecond * 500,
+		},
+		{
+			controller: &siteRecordController{
+				Identity:        "basic-kube",
+				Name:            "basic",
+				Platform:        string(types.PlatformKubernetes),
+				CreatedAt:       1_111_111,
+				PolicyEnabled:   true,
+				policyEvaluator: alwaysOnEval,
+				pollInterval:    time.Millisecond * 5,
+			},
+			expectedOut: []func(*testing.T, *SiteRecord, bool){
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, rec.Identity, "basic-kube")
+					assert.Equal(t, *rec.Name, "basic")
+					assert.Equal(t, *rec.Platform, string(types.PlatformKubernetes))
+					assert.Equal(t, rec.StartTime, uint64(1_111_111))
+					assert.Equal(t, *rec.Policy, Enabled)
+				},
+			},
+			waitTimeout:       time.Millisecond * 250,
+			expectWaitTimeout: true,
+		},
+		{
+			controller: &siteRecordController{
+				Identity:        "policy-updates-kube",
+				Name:            "basic",
+				Platform:        string(types.PlatformKubernetes),
+				CreatedAt:       1_111_111,
+				policyEvaluator: flipFlopEval,
+				pollInterval:    time.Millisecond * 5,
+			},
+			expectedOut: []func(*testing.T, *SiteRecord, bool){
+				func(t *testing.T, rec *SiteRecord, ok bool) {
+					assert.Equal(t, rec.Identity, "policy-updates-kube")
+					assert.Equal(t, *rec.Name, "basic")
+					assert.Equal(t, *rec.Platform, string(types.PlatformKubernetes))
+					assert.Equal(t, rec.StartTime, uint64(1_111_111))
+					assert.Equal(t, *rec.Policy, Disabled)
+					// queue two updates to policy enabled
+					flipFlopEval.Next <- false
+					flipFlopEval.Next <- true
+					flipFlopEval.Next <- false
+				},
+				func(t *testing.T, rec *SiteRecord, ok bool) { // closed
+					assert.DeepEqual(t, *rec, SiteRecord{
+						Base: Base{
+							RecType:  recordNames[Site],
+							Identity: "policy-updates-kube",
+						},
+						Policy: &Enabled,
+					})
+				},
+				func(t *testing.T, rec *SiteRecord, ok bool) { // closed
+					assert.DeepEqual(t, rec, &SiteRecord{
+						Base: Base{
+							RecType:  recordNames[Site],
+							Identity: "policy-updates-kube",
+						},
+						Policy: &Disabled,
+					})
+				},
+			},
+			waitTimeout: time.Millisecond * 500,
+		},
+	}
+
+	for _, testCase := range testCases {
+		tc := testCase
+		t.Run(tc.controller.Identity, func(t *testing.T) {
+			t.Parallel()
+			end := make(chan struct{})
+			defer close(end)
+
+			out := tc.controller.Start(end)
+			for i, expect := range tc.expectedOut {
+				select {
+				case actual, ok := <-out:
+					expect(t, actual, ok)
+				case <-time.After(tc.waitTimeout):
+					t.Fatalf("test case timed out waiting for output %d", i)
+				}
+			}
+			if tc.expectWaitTimeout {
+				select {
+				case actual := <-out:
+					t.Errorf("unexpected update at end of test: %v", actual)
+				case <-time.After(tc.waitTimeout):
+					// okay
+				}
+			}
+		})
+	}
+}
+
+type stubPolicyEvaluator struct {
+	IsEnabled bool
+	Next      chan bool
+}
+
+func (s stubPolicyEvaluator) Enabled() bool {
+	select {
+	case next, ok := <-s.Next:
+		if ok {
+			s.IsEnabled = next
+		}
+	default:
+	}
+	return s.IsEnabled
+}
diff --git a/pkg/flow/flow_mem_driver_test.go b/pkg/flow/flow_mem_driver_test.go
index a6eb2f6c..e6a32819 100644
--- a/pkg/flow/flow_mem_driver_test.go
+++ b/pkg/flow/flow_mem_driver_test.go
@@ -12,9 +12,12 @@ import (
 )
 
 func TestRecordGraphWithMetrics(t *testing.T) {
-	name := "skupper-site"
-	namespace := "skupper-public"
+	name := "skupper-site1"
+	namespace := "skupper-public1"
 	provider := "aws"
+	name2 := "skupper-site2"
+	namespace2 := "skupper-public2"
+	provider2 := "gke"
 	address := "tcp-go-echo"
 	address2 := "redis-cart"
 	address3 := "mongo"
@@ -31,6 +34,8 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 	connectorProcess1 := "process:1"
 	connectorProcess2 := "process:2"
 	processConnector := "connector:0"
+	destHost1 := "10.20.30.40"
+	destHost2 := "host.cloud.com"
 
 	sites := []SiteRecord{
 		{
@@ -43,6 +48,16 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			NameSpace: &namespace,
 			Provider:  &provider,
 		},
+		{
+			Base: Base{
+				RecType:   recordNames[Site],
+				Identity:  "site:1",
+				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
+			},
+			Name:      &name2,
+			NameSpace: &namespace2,
+			Provider:  &provider2,
+		},
 	}
 	routers := []RouterRecord{
 		{
@@ -126,9 +141,10 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 				Parent:    "site:0",
 				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
 			},
-			Name:      &processName2,
-			GroupName: &groupName1,
-			connector: &processConnector,
+			Name:       &processName2,
+			GroupName:  &groupName1,
+			connector:  &processConnector,
+			SourceHost: &destHost1,
 		},
 		{
 			Base: Base{
@@ -139,6 +155,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			},
 			Name:      &processName1,
 			GroupName: &groupName1,
+			HostName:  &destHost2,
 		},
 	}
 	listeners := []ListenerRecord{
@@ -149,8 +166,9 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 				Parent:    "router:0",
 				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
 			},
-			Address:  &address,
-			Protocol: &protocol,
+			Address:   &address,
+			AddressId: &address,
+			Protocol:  &protocol,
 		},
 		{
 			Base: Base{
@@ -159,7 +177,9 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 				Parent:    "router:1",
 				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
 			},
-			Protocol: &protocol,
+			Protocol:  &protocol,
+			Address:   &address,
+			AddressId: &address,
 		},
 		{
 			Base: Base{
@@ -183,6 +203,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			Address:   &address3,
 			Protocol:  &protocol,
 			ProcessId: &connectorProcess1,
+			DestHost:  &destHost1,
 		},
 		{
 			Base: Base{
@@ -203,6 +224,19 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			Protocol:  &protocol,
 			Address:   &address4,
 			ProcessId: &connectorProcess2,
+			DestHost:  &destHost2,
+		},
+		{
+			Base: Base{
+				RecType:   recordNames[Connector],
+				Identity:  "connector:2",
+				Parent:    "router:0",
+				StartTime: uint64(time.Now().UnixNano()) / uint64(time.Microsecond),
+			},
+			Protocol:  &protocol,
+			Address:   &address,
+			ProcessId: &connectorProcess1,
+			DestHost:  &destHost1,
 		},
 	}
 	flows := []FlowRecord{
@@ -227,6 +261,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			Trace:       &flowTrace,
 			ProcessName: &processName2,
 			Process:     &connectorProcess1,
+			SourceHost:  &destHost1,
 		},
 		{
 			Base: Base{
@@ -377,14 +412,29 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 		}
 		if f.Trace != nil {
 			trace := fc.annotateFlowTrace(&f)
-			assert.Equal(t, *trace, "skupper-site@skupper-site")
+			assert.Equal(t, *trace, "skupper-site1@skupper-site1")
 		}
 	}
 
 	fc.reconcileConnectorRecords()
 	fc.reconcileFlowRecords()
 
+	var addressId string
+	for _, address := range fc.VanAddresses {
+		if address.Name == "tcp-go-echo" {
+			addressId = address.Identity
+			break
+		}
+	}
+
+	var processGroupId string
+	for x, _ := range fc.ProcessGroups {
+		processGroupId = x
+		break
+	}
+
 	type test struct {
+		desc         string
 		recordType   int
 		method       string
 		url          string
@@ -396,6 +446,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 
 	testTable := []test{
 		{
+			desc:         "Get Site list",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -405,6 +456,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(sites),
 		},
 		{
+			desc:         "Get Site item",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -414,6 +466,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Site routers",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -423,6 +476,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(routers),
 		},
 		{
+			desc:         "Get Site links",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -432,6 +486,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(links),
 		},
 		{
+			desc:         "Get Site hosts",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -441,6 +496,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(hosts),
 		},
 		{
+			desc:         "Get Site processes",
 			recordType:   Site,
 			method:       "Get",
 			url:          "/",
@@ -450,6 +506,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(processes),
 		},
 		{
+			desc:         "Get Host list",
 			recordType:   Host,
 			method:       "Get",
 			url:          "/",
@@ -459,6 +516,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(hosts),
 		},
 		{
+			desc:         "Get Host item",
 			recordType:   Host,
 			method:       "Get",
 			url:          "/",
@@ -468,6 +526,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router list",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -477,6 +536,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(routers),
 		},
 		{
+			desc:         "Get Router item",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -486,6 +546,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router flows",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -495,6 +556,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 6,
 		},
 		{
+			desc:         "Get Router links",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -504,6 +566,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router listeners",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
@@ -513,15 +576,17 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Router connectors",
 			recordType:   Router,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
 			vars:         map[string]string{"id": "router:0"},
 			name:         "connectors",
-			responseSize: 1,
+			responseSize: 2,
 		},
 		{
+			desc:         "Get Link list",
 			recordType:   Link,
 			method:       "Get",
 			url:          "/",
@@ -531,6 +596,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(links),
 		},
 		{
+			desc:         "Get Link item",
 			recordType:   Link,
 			method:       "Get",
 			url:          "/",
@@ -540,6 +606,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Listener list",
 			recordType:   Listener,
 			method:       "Get",
 			url:          "/",
@@ -549,6 +616,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(listeners),
 		},
 		{
+			desc:         "Get Listener item",
 			recordType:   Listener,
 			method:       "Get",
 			url:          "/",
@@ -558,6 +626,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Listener flows",
 			recordType:   Listener,
 			method:       "Get",
 			url:          "/",
@@ -567,15 +636,17 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 3,
 		},
 		{
+			desc:         "Get Connector list",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
 			vars:         map[string]string{},
 			name:         "list",
-			responseSize: 2,
+			responseSize: 3,
 		},
 		{
+			desc:         "Get Connector item",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
@@ -585,6 +656,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Connector flows",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
@@ -594,6 +666,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 3,
 		},
 		{
+			desc:         "Get Connector process",
 			recordType:   Connector,
 			method:       "Get",
 			url:          "/",
@@ -603,6 +676,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Address list",
 			recordType:   Address,
 			method:       "Get",
 			url:          "/",
@@ -612,15 +686,57 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(fc.VanAddresses),
 		},
 		{
-			recordType:   Connector,
+			desc:         "Get Address item",
+			recordType:   Address,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
-			vars:         map[string]string{"id": "connector:0"},
+			vars:         map[string]string{"id": addressId},
 			name:         "item",
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Address flows",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "flows",
+			responseSize: 3,
+		},
+		{
+			desc:         "Get Address flowpairs",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "flowpairs",
+			responseSize: 3,
+		},
+		{
+			desc:         "Get Address listeners",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "listeners",
+			responseSize: 2,
+		},
+		{
+			desc:         "Get Address connectors",
+			recordType:   Address,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": addressId},
+			name:         "connectors",
+			responseSize: 1,
+		},
+		{
+			desc:         "Get Flow list",
 			recordType:   Flow,
 			method:       "Get",
 			url:          "/",
@@ -630,6 +746,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(fc.Flows),
 		},
 		{
+			desc:         "Get Flow item",
 			recordType:   Flow,
 			method:       "Get",
 			url:          "/",
@@ -639,6 +756,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Flow process",
 			recordType:   Flow,
 			method:       "Get",
 			url:          "/",
@@ -648,6 +766,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Flowpair list",
 			recordType:   FlowPair,
 			method:       "Get",
 			url:          "/",
@@ -657,6 +776,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(fc.FlowPairs),
 		},
 		{
+			desc:         "Get Flowpair item",
 			recordType:   FlowPair,
 			method:       "Get",
 			url:          "/",
@@ -666,6 +786,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Process list",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -675,6 +796,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: len(processes),
 		},
 		{
+			desc:         "Get Process item",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -684,6 +806,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Process flows",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -693,15 +816,17 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 3,
 		},
 		{
+			desc:         "Get Process addresses",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
 			params:       map[string]string{"sortBy": "identity.asc"},
 			vars:         map[string]string{"id": "process:1"},
 			name:         "addresses",
-			responseSize: 1,
+			responseSize: 2,
 		},
 		{
+			desc:         "Get Process connector",
 			recordType:   Process,
 			method:       "Get",
 			url:          "/",
@@ -711,6 +836,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessGroup list",
 			recordType:   ProcessGroup,
 			method:       "Get",
 			url:          "/",
@@ -720,6 +846,27 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessGroup item",
+			recordType:   ProcessGroup,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": processGroupId},
+			name:         "item",
+			responseSize: 1,
+		},
+		{
+			desc:         "Get ProcessGroup processes",
+			recordType:   ProcessGroup,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{"id": processGroupId},
+			name:         "processes",
+			responseSize: 3,
+		},
+		{
+			desc:         "Get SitePair list",
 			recordType:   SitePair,
 			method:       "Get",
 			url:          "/",
@@ -729,6 +876,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get SitePair item",
 			recordType:   SitePair,
 			method:       "Get",
 			url:          "/",
@@ -738,6 +886,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessGroupPair list",
 			recordType:   ProcessGroupPair,
 			method:       "Get",
 			url:          "/",
@@ -747,6 +896,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessPair list",
 			recordType:   ProcessPair,
 			method:       "Get",
 			url:          "/",
@@ -756,6 +906,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get ProcessPair item",
 			recordType:   ProcessPair,
 			method:       "Get",
 			url:          "/",
@@ -765,6 +916,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			responseSize: 1,
 		},
 		{
+			desc:         "Get Collector list",
 			recordType:   Collector,
 			method:       "Get",
 			url:          "/",
@@ -773,8 +925,21 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 			name:         "list",
 			responseSize: 1,
 		},
+		{
+			desc:         "Get Collector item",
+			recordType:   Collector,
+			method:       "Get",
+			url:          "/",
+			params:       map[string]string{"sortBy": "identity.asc"},
+			vars:         map[string]string{},
+			name:         "item",
+			responseSize: 1,
+		},
 	}
 
+	fc.reconcileConnectorRecords()
+	fc.reconcileFlowRecords()
+
 	var payload Payload
 	for _, test := range testTable {
 		req, _ := http.NewRequest(test.method, test.url, nil)
@@ -788,7 +953,7 @@ func TestRecordGraphWithMetrics(t *testing.T) {
 		assert.Assert(t, err)
 		err = json.Unmarshal([]byte(*resp), &payload)
 		assert.Assert(t, err)
-		assert.Equal(t, test.responseSize, payload.Count)
+		assert.Equal(t, test.responseSize, payload.Count, test.desc)
 	}
 
 	endTime := uint64(time.Now().UnixNano()) / uint64(time.Microsecond)
diff --git a/test/integration/acceptance/gateway_test.go b/test/integration/acceptance/gateway_test.go
index b6483e14..02adf1a5 100644
--- a/test/integration/acceptance/gateway_test.go
+++ b/test/integration/acceptance/gateway_test.go
@@ -9,7 +9,6 @@ import (
 	"compress/gzip"
 	"context"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -43,14 +42,12 @@ func TestGateway(t *testing.T) {
 	t.Run("local-gateway-podman", testLocalGatewayPodman)
 }
 
-//
 // testLocalGateway uses localhost to run a TCP Echo server
 // bound to a dynamic port and expose it through a local
 // gateway into the skupper network, against two connected
 // clusters. It also forwards local requests to a cluster
 // port reaching out tcp-echo-cluster service using a
 // dynamic port
-//
 func testLocalGatewayService(t *testing.T) {
 	testLocalGateway(t, "")
 }
@@ -210,7 +207,7 @@ func installGateway(tarball string) error {
 	tarReader := tar.NewReader(tarStream)
 
 	// uncompress all files under temp directory
-	dir, err := ioutil.TempDir("", "gateway")
+	dir, err := os.MkdirTemp("", "gateway")
 	if err != nil {
 		return err
 	}
diff --git a/test/integration/examples/bookinfo/job/bookinfojob_test.go b/test/integration/examples/bookinfo/job/bookinfojob_test.go
index 13352ee6..033cc1f8 100644
--- a/test/integration/examples/bookinfo/job/bookinfojob_test.go
+++ b/test/integration/examples/bookinfo/job/bookinfojob_test.go
@@ -4,7 +4,7 @@ package job
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -38,10 +38,9 @@ func tryProductPage() ([]byte, error) {
 		return nil, fmt.Errorf("unexpedted http response status: %v", resp.Status)
 	}
 	defer resp.Body.Close()
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
 	return body, nil
 }
-
diff --git a/test/integration/examples/custom/hipstershop/hipstershop.go b/test/integration/examples/custom/hipstershop/hipstershop.go
index 4fdce60a..e63af859 100644
--- a/test/integration/examples/custom/hipstershop/hipstershop.go
+++ b/test/integration/examples/custom/hipstershop/hipstershop.go
@@ -3,8 +3,8 @@ package hipstershop
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"log"
+	"os"
 	"strings"
 	"testing"
 
@@ -101,7 +101,7 @@ func CreateVAN(t *testing.T, testRunner base.ClusterTestRunner) {
 	assert.Assert(t, err, "error creating router on private1 cluster")
 
 	// Creating a local directory for storing the token
-	tokenPath, err := ioutil.TempDir("", "token")
+	tokenPath, err := os.MkdirTemp("", "token")
 	assert.Assert(t, err, "error creating temporary directory for tokens")
 	cleanUpDirs = append(cleanUpDirs, tokenPath)
 
diff --git a/test/integration/examples/http/job/httpjob_test.go b/test/integration/examples/http/job/httpjob_test.go
index b6607074..59d7ea1a 100644
--- a/test/integration/examples/http/job/httpjob_test.go
+++ b/test/integration/examples/http/job/httpjob_test.go
@@ -6,15 +6,17 @@ package job
 import (
 	"crypto/tls"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 	"testing"
 	"time"
 
 	"crypto/x509"
+
 	"github.com/davecgh/go-spew/spew"
 	"github.com/tsenart/vegeta/v12/lib"
 	"golang.org/x/net/http2"
@@ -49,7 +51,7 @@ func TestHttp2Job(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -60,7 +62,7 @@ func TestHttp2Job(t *testing.T) {
 func TestHttp2TlsJob(t *testing.T) {
 
 	//Load CA cert
-	caCert, err := ioutil.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
+	caCert, err := os.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -90,7 +92,7 @@ func TestHttp2TlsJob(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -102,7 +104,7 @@ func TestHttp2TlsJob(t *testing.T) {
 func TestHttp2TcpTlsJob(t *testing.T) {
 
 	//Load CA cert
-	caCert, err := ioutil.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
+	caCert, err := os.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
 	assert.Assert(t, err)
 
 	caCertPool := x509.NewCertPool()
@@ -131,7 +133,7 @@ func TestHttp2TcpTlsJob(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
@@ -143,7 +145,7 @@ func TestHttp2TcpTlsJob(t *testing.T) {
 func TestHttp1TlsJob(t *testing.T) {
 
 	//Load CA cert
-	caCert, err := ioutil.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
+	caCert, err := os.ReadFile("/tmp/certs/skupper-service-client/ca.crt")
 	assert.Assert(t, err)
 
 	caCertPool := x509.NewCertPool()
@@ -171,7 +173,7 @@ func TestHttp1TlsJob(t *testing.T) {
 	fmt.Println("Client Header:", resp.Header)
 
 	defer resp.Body.Close()
-	_body, err := ioutil.ReadAll(resp.Body)
+	_body, err := io.ReadAll(resp.Body)
 	assert.Assert(t, err)
 
 	body := string(_body)
diff --git a/test/utils/k8s/yaml.go b/test/utils/k8s/yaml.go
index 8d3bb9da..c6fd41fd 100644
--- a/test/utils/k8s/yaml.go
+++ b/test/utils/k8s/yaml.go
@@ -6,8 +6,8 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
+	"os"
 	"regexp"
 
 	"github.com/skupperproject/skupper/client"
@@ -36,7 +36,7 @@ func CreateResourcesFromYAML(vanClient *client.VanClient, fileOrUrl string) erro
 		}
 	} else {
 		// Read YAML file
-		yamlData, err = ioutil.ReadFile(fileOrUrl)
+		yamlData, err = os.ReadFile(fileOrUrl)
 		if err != nil {
 			return fmt.Errorf("error reading yaml file: %s", err)
 		}
@@ -118,7 +118,7 @@ func readYAMLFromUrl(url string) ([]byte, error) {
 		return nil, fmt.Errorf("error loading yaml from url [%s]: %s", url, err)
 	}
 	defer resp.Body.Close()
-	yamlData, err = ioutil.ReadAll(resp.Body)
+	yamlData, err = io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, fmt.Errorf("error reading yaml from url [%s]: %s", url, err)
 	}
diff --git a/test/utils/skupper/cli/token/create.go b/test/utils/skupper/cli/token/create.go
index 67697351..1cf6423f 100644
--- a/test/utils/skupper/cli/token/create.go
+++ b/test/utils/skupper/cli/token/create.go
@@ -3,7 +3,6 @@ package token
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/url"
 	"os"
@@ -114,7 +113,7 @@ func (t *CreateTester) Run(platform types.Platform, cluster *base.ClusterContext
 	// Loading secret
 	var secret v1.Secret
 	var secretFile []byte
-	secretFile, err = ioutil.ReadFile(t.FileName)
+	secretFile, err = os.ReadFile(t.FileName)
 	if err != nil {
 		err = fmt.Errorf("unable to read token file - %v", err)
 		return