Repository: skupperproject/skupper
Pull request #1807 (2024-11-27 11:36:16)

Use regexp.MustCompile instead of handling errors
Invalid constant REs are a fatal error, there's no point in handling them explicitly at runtime; it's best to use MustCompile which panics on errors. Those errors will be caught during development.

StringValidator doesn't need to be public, make it private.

Use raw strings for REs to avoid having to escape twice.


Patch:
diff --git a/internal/cmd/skupper/common/constants.go b/internal/cmd/skupper/common/constants.go
index 06f77244..a7f33d25 100644
--- a/internal/cmd/skupper/common/constants.go
+++ b/internal/cmd/skupper/common/constants.go
@@ -6,6 +6,7 @@ var (
 	ListenerTypes   = []string{"tcp"}
 	ConnectorTypes  = []string{"tcp"}
 	WorkloadTypes   = []string{"deployment", "service", "daemonset", "statefulset"}
+	WaitStatusTypes = []string{"ready", "configured", "none"}
 )
 
 const (
diff --git a/internal/cmd/skupper/common/flags.go b/internal/cmd/skupper/common/flags.go
index 418b948e..791272e8 100644
--- a/internal/cmd/skupper/common/flags.go
+++ b/internal/cmd/skupper/common/flags.go
@@ -70,6 +70,10 @@ for other Kubernetes flavors, loadbalancer is the default.`
 	FlagDescListenerPort = "The port of the local listener"
 	FlagNameListenerHost = "host"
 	FlagDescListenerHost = "The hostname or IP address of the local listener. Clients at this site use the listener host and port to establish connections to the remote service."
+
+	FlagNameWait       = "wait"
+	FlagDescWait       = "Wait for the given status before exiting. Choices: configured, ready, none"
+	FlagDescDeleteWait = "Wait for deletion to complete before exiting"
 )
 
 type CommandSiteCreateFlags struct {
@@ -81,6 +85,7 @@ type CommandSiteCreateFlags struct {
 	Timeout                 time.Duration
 	BindHost                string
 	SubjectAlternativeNames []string
+	Wait                    string
 }
 
 type CommandSiteUpdateFlags struct {
@@ -92,10 +97,12 @@ type CommandSiteUpdateFlags struct {
 	Timeout                 time.Duration
 	BindHost                string
 	SubjectAlternativeNames []string
+	Wait                    string
 }
 
 type CommandSiteDeleteFlags struct {
 	Timeout time.Duration
+	Wait    bool
 }
 
 type CommandLinkGenerateFlags struct {
diff --git a/internal/cmd/skupper/site/kube/site_create.go b/internal/cmd/skupper/site/kube/site_create.go
index 65f895fb..a65d8c08 100644
--- a/internal/cmd/skupper/site/kube/site_create.go
+++ b/internal/cmd/skupper/site/kube/site_create.go
@@ -6,6 +6,7 @@ package kube
 import (
 	"context"
 	"fmt"
+	"k8s.io/apimachinery/pkg/api/meta"
 	"time"
 
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
@@ -30,6 +31,7 @@ type CmdSiteCreate struct {
 	linkAccessType     string
 	output             string
 	timeout            time.Duration
+	status             string
 }
 
 func NewCmdSiteCreate() *CmdSiteCreate {
@@ -55,6 +57,7 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error {
 	linkAccessTypeValidator := validator.NewOptionValidator(common.LinkAccessTypes)
 	outputTypeValidator := validator.NewOptionValidator(common.OutputTypes)
 	timeoutValidator := validator.NewTimeoutInSecondsValidator()
+	statusValidator := validator.NewOptionValidator(common.WaitStatusTypes)
 
 	//Validate if there is already a site defined in the namespace
 	siteList, _ := cmd.Client.Sites(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
@@ -115,6 +118,13 @@ func (cmd *CmdSiteCreate) ValidateInput(args []string) []error {
 		}
 	}
 
+	if cmd.Flags != nil && cmd.Flags.Wait != "" {
+		ok, err := statusValidator.Evaluate(cmd.Flags.Wait)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("status is not valid: %s", err))
+		}
+	}
+
 	return validationErrors
 }
 
@@ -132,6 +142,7 @@ func (cmd *CmdSiteCreate) InputToOptions() {
 
 	cmd.output = cmd.Flags.Output
 	cmd.timeout = cmd.Flags.Timeout
+	cmd.status = cmd.Flags.Wait
 
 }
 
@@ -172,7 +183,14 @@ func (cmd *CmdSiteCreate) WaitUntil() error {
 		return nil
 	}
 
+	if cmd.status == "none" {
+		return nil
+	}
+
 	waitTime := int(cmd.timeout.Seconds())
+
+	var siteCondition *metav1.Condition
+
 	err := utils.NewSpinnerWithTimeout("Waiting for status...", waitTime, func() error {
 
 		resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
@@ -180,18 +198,39 @@ func (cmd *CmdSiteCreate) WaitUntil() error {
 			return err
 		}
 
-		if resource != nil && resource.IsConfigured() {
+		isConditionFound := false
+		isConditionTrue := false
+
+		switch cmd.status {
+		case "configured":
+			siteCondition = meta.FindStatusCondition(resource.Status.Conditions, v2alpha1.CONDITION_TYPE_CONFIGURED)
+		default:
+			siteCondition = meta.FindStatusCondition(resource.Status.Conditions, v2alpha1.CONDITION_TYPE_READY)
+		}
+
+		if siteCondition != nil {
+			isConditionFound = true
+			isConditionTrue = siteCondition.Status == metav1.ConditionTrue
+		}
+
+		if resource != nil && isConditionFound && isConditionTrue {
 			return nil
 		}
 
+		if resource != nil && isConditionFound && !isConditionTrue {
+			return fmt.Errorf("error in the condition")
+		}
+
 		return fmt.Errorf("error getting the resource")
 	})
 
-	if err != nil {
-		return fmt.Errorf("Site %q not configured yet, check the status for more information\n", cmd.siteName)
+	if err != nil && siteCondition == nil {
+		return fmt.Errorf("Site %q is not %s yet, check the status for more information\n", cmd.siteName, cmd.status)
+	} else if err != nil {
+		return fmt.Errorf("Site %q is %s with errors, check the status for more information\n", cmd.siteName, cmd.status)
 	}
 
-	fmt.Printf("Site %q is configured. Check the status to see when it is ready\n", cmd.siteName)
+	fmt.Printf("Site %q is %s.\n", cmd.siteName, cmd.status)
 
 	return nil
 }
diff --git a/internal/cmd/skupper/site/kube/site_delete.go b/internal/cmd/skupper/site/kube/site_delete.go
index f9ced7bc..584b694c 100644
--- a/internal/cmd/skupper/site/kube/site_delete.go
+++ b/internal/cmd/skupper/site/kube/site_delete.go
@@ -21,6 +21,7 @@ type CmdSiteDelete struct {
 	Namespace string
 	siteName  string
 	timeout   time.Duration
+	wait      bool
 }
 
 func NewCmdSiteDelete() *CmdSiteDelete {
@@ -92,24 +93,27 @@ func (cmd *CmdSiteDelete) Run() error {
 	return err
 }
 func (cmd *CmdSiteDelete) WaitUntil() error {
-	waitTime := int(cmd.timeout.Seconds())
-	err := utils.NewSpinnerWithTimeout("Waiting for deletion to complete...", waitTime, func() error {
 
-		resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
+	if cmd.wait {
+		waitTime := int(cmd.timeout.Seconds())
+		err := utils.NewSpinnerWithTimeout("Waiting for deletion to complete...", waitTime, func() error {
 
-		if err == nil && resource != nil {
-			return fmt.Errorf("error deleting the resource")
-		} else {
-			return nil
-		}
+			resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
 
-	})
+			if err == nil && resource != nil {
+				return fmt.Errorf("error deleting the resource")
+			} else {
+				return nil
+			}
 
-	if err != nil {
-		return fmt.Errorf("Site %q not deleted yet, check the status for more information\n", cmd.siteName)
-	}
+		})
 
-	fmt.Printf("Site %q is deleted\n", cmd.siteName)
+		if err != nil {
+			return fmt.Errorf("Site %q not deleted yet, check the status for more information\n", cmd.siteName)
+		}
+
+		fmt.Printf("Site %q is deleted\n", cmd.siteName)
+	}
 
 	return nil
 }
diff --git a/internal/cmd/skupper/site/kube/site_update.go b/internal/cmd/skupper/site/kube/site_update.go
index a160a514..f8ef43f3 100644
--- a/internal/cmd/skupper/site/kube/site_update.go
+++ b/internal/cmd/skupper/site/kube/site_update.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common/utils"
+	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/client-go/kubernetes"
 	"time"
 
@@ -27,6 +28,7 @@ type CmdSiteUpdate struct {
 	linkAccessType     string
 	output             string
 	timeout            time.Duration
+	status             string
 }
 
 func NewCmdSiteUpdate() *CmdSiteUpdate {
@@ -51,6 +53,7 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error {
 	linkAccessTypeValidator := validator.NewOptionValidator(common.LinkAccessTypes)
 	outputTypeValidator := validator.NewOptionValidator(common.OutputTypes)
 	timeoutValidator := validator.NewTimeoutInSecondsValidator()
+	statusValidator := validator.NewOptionValidator(common.WaitStatusTypes)
 
 	//Validate if there is already a site defined in the namespace
 	siteList, _ := cmd.Client.Sites(cmd.Namespace).List(context.TODO(), metav1.ListOptions{})
@@ -117,6 +120,13 @@ func (cmd *CmdSiteUpdate) ValidateInput(args []string) []error {
 		}
 	}
 
+	if cmd.Flags != nil && cmd.Flags.Wait != "" {
+		ok, err := statusValidator.Evaluate(cmd.Flags.Wait)
+		if !ok {
+			validationErrors = append(validationErrors, fmt.Errorf("status is not valid: %s", err))
+		}
+	}
+
 	return validationErrors
 }
 func (cmd *CmdSiteUpdate) InputToOptions() {
@@ -132,6 +142,7 @@ func (cmd *CmdSiteUpdate) InputToOptions() {
 
 	cmd.output = cmd.Flags.Output
 	cmd.timeout = cmd.Flags.Timeout
+	cmd.status = cmd.Flags.Wait
 
 }
 func (cmd *CmdSiteUpdate) Run() error {
@@ -192,7 +203,13 @@ func (cmd *CmdSiteUpdate) WaitUntil() error {
 		return nil
 	}
 
+	if cmd.status == "none" {
+		return nil
+	}
+
 	waitTime := int(cmd.timeout.Seconds())
+	var siteCondition *metav1.Condition
+
 	err := utils.NewSpinnerWithTimeout("Waiting for update to complete...", waitTime, func() error {
 
 		resource, err := cmd.Client.Sites(cmd.Namespace).Get(context.TODO(), cmd.siteName, metav1.GetOptions{})
@@ -200,15 +217,36 @@ func (cmd *CmdSiteUpdate) WaitUntil() error {
 			return err
 		}
 
-		if resource != nil && resource.IsConfigured() {
+		isConditionFound := false
+		isConditionTrue := false
+
+		switch cmd.status {
+		case "configured":
+			siteCondition = meta.FindStatusCondition(resource.Status.Conditions, v2alpha1.CONDITION_TYPE_CONFIGURED)
+		default:
+			siteCondition = meta.FindStatusCondition(resource.Status.Conditions, v2alpha1.CONDITION_TYPE_READY)
+		}
+
+		if siteCondition != nil {
+			isConditionFound = true
+			isConditionTrue = siteCondition.Status == metav1.ConditionTrue
+		}
+
+		if resource != nil && isConditionFound && isConditionTrue {
 			return nil
 		}
 
+		if resource != nil && isConditionFound && !isConditionTrue {
+			return fmt.Errorf("error in the condition")
+		}
+
 		return fmt.Errorf("error getting the resource")
 	})
 
-	if err != nil {
-		return fmt.Errorf("Site %q not ready yet, check the status for more information\n", cmd.siteName)
+	if err != nil && siteCondition == nil {
+		return fmt.Errorf("Site %q is not %s yet, check the status for more information\n", cmd.siteName, cmd.status)
+	} else if err != nil {
+		return fmt.Errorf("Site %q is %s with errors, check the status for more information\n", cmd.siteName, cmd.status)
 	}
 
 	fmt.Printf("Site %q is updated\n", cmd.siteName)
diff --git a/internal/cmd/skupper/site/site.go b/internal/cmd/skupper/site/site.go
index 28810cb6..35df6077 100644
--- a/internal/cmd/skupper/site/site.go
+++ b/internal/cmd/skupper/site/site.go
@@ -42,6 +42,7 @@ func CmdSiteCreateFactory(configuredPlatform types.Platform) *cobra.Command {
 		Long: `A site is a place where components of your application are running.
 Sites are linked to form application networks.
 There can be only one site definition per namespace.`,
+		Example: "skupper site create my-site --wait configured",
 	}
 
 	cmd := common.ConfigureCobraCommand(configuredPlatform, cmdSiteCreateDesc, kubeCommand, nonKubeCommand)
@@ -52,9 +53,10 @@ There can be only one site definition per namespace.`,
 	cmd.Flags().StringVar(&cmdFlags.LinkAccessType, common.FlagNameLinkAccessType, "", common.FlagDescLinkAccessType)
 	cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput)
 	cmd.Flags().StringVar(&cmdFlags.ServiceAccount, common.FlagNameServiceAccount, "", common.FlagDescServiceAccount)
-	cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout)
+	cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 30*time.Second, common.FlagDescTimeout)
 	cmd.Flags().StringVar(&cmdFlags.BindHost, common.FlagNameBindHost, "", common.FlagDescBindHost)
 	cmd.Flags().StringSliceVar(&cmdFlags.SubjectAlternativeNames, common.FlagNameSubjectAlternativeNames, []string{}, common.FlagDescSubjectAlternativeNames)
+	cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "ready", common.FlagDescWait)
 
 	kubeCommand.CobraCmd = cmd
 	kubeCommand.Flags = &cmdFlags
@@ -66,6 +68,7 @@ There can be only one site definition per namespace.`,
 		cmd.Flags().MarkHidden(common.FlagNameSubjectAlternativeNames)
 	} else {
 		cmd.Flags().MarkHidden(common.FlagNameServiceAccount)
+		cmd.Flags().MarkHidden(common.FlagNameWait)
 	}
 
 	return cmd
@@ -90,9 +93,10 @@ func CmdSiteUpdateFactory(configuredPlatform types.Platform) *cobra.Command {
 	cmd.Flags().StringVar(&cmdFlags.LinkAccessType, common.FlagNameLinkAccessType, "", common.FlagDescLinkAccessType)
 	cmd.Flags().StringVarP(&cmdFlags.Output, common.FlagNameOutput, "o", "", common.FlagDescOutput)
 	cmd.Flags().StringVar(&cmdFlags.ServiceAccount, common.FlagNameServiceAccount, "", common.FlagDescServiceAccount)
-	cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout)
+	cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 30*time.Second, common.FlagDescTimeout)
 	cmd.Flags().StringVar(&cmdFlags.BindHost, common.FlagNameBindHost, "", common.FlagDescBindHost)
 	cmd.Flags().StringSliceVar(&cmdFlags.SubjectAlternativeNames, common.FlagNameSubjectAlternativeNames, []string{}, common.FlagDescSubjectAlternativeNames)
+	cmd.Flags().StringVar(&cmdFlags.Wait, common.FlagNameWait, "ready", common.FlagDescWait)
 
 	kubeCommand.CobraCmd = cmd
 	kubeCommand.Flags = &cmdFlags
@@ -104,6 +108,7 @@ func CmdSiteUpdateFactory(configuredPlatform types.Platform) *cobra.Command {
 		cmd.Flags().MarkHidden(common.FlagNameSubjectAlternativeNames)
 	} else {
 		cmd.Flags().MarkHidden(common.FlagNameServiceAccount)
+		cmd.Flags().MarkHidden(common.FlagNameWait)
 	}
 
 	return cmd
@@ -133,21 +138,27 @@ func CmdSiteDeleteFactory(configuredPlatform types.Platform) *cobra.Command {
 	nonKubeCommand := nonkube.NewCmdSiteDelete()
 
 	cmdSiteDeleteDesc := common.SkupperCmdDescription{
-		Use:     "delete",
-		Short:   "Delete a site",
-		Long:    `Delete a site by name`,
-		Example: "skupper site delete my-site",
+		Use:   "delete",
+		Short: "Delete a site",
+		Long:  `Delete a site by name`,
+		Example: `skupper site delete my-site
+skupper site delete --wait=false`,
 	}
 
 	cmd := common.ConfigureCobraCommand(configuredPlatform, cmdSiteDeleteDesc, kubeCommand, nonKubeCommand)
 	cmdFlags := common.CommandSiteDeleteFlags{}
 
 	cmd.Flags().DurationVar(&cmdFlags.Timeout, common.FlagNameTimeout, 60*time.Second, common.FlagDescTimeout)
+	cmd.Flags().BoolVar(&cmdFlags.Wait, common.FlagNameWait, true, common.FlagDescDeleteWait)
 
 	kubeCommand.CobraCmd = cmd
 	kubeCommand.Flags = &cmdFlags
 	nonKubeCommand.CobraCmd = cmd
 	nonKubeCommand.Flags = &cmdFlags
 
+	if configuredPlatform != types.PlatformKubernetes {
+		cmd.Flags().MarkHidden(common.FlagNameWait)
+	}
+
 	return cmd
 }
diff --git a/pkg/utils/validator/simple_validator.go b/pkg/utils/validator/simple_validator.go
index 1230e57f..a52b6652 100644
--- a/pkg/utils/validator/simple_validator.go
+++ b/pkg/utils/validator/simple_validator.go
@@ -13,67 +13,42 @@ type Validator interface {
 
 //
 
-type StringValidator struct {
+type stringValidator struct {
 	Expression *regexp.Regexp
 }
 
-func NewStringValidator() *StringValidator {
-	re, err := regexp.Compile("^\\S*$")
-	if err != nil {
-		fmt.Printf("Error compiling regex: %v", err)
-		return nil
-	}
-	return &StringValidator{
-		Expression: re,
+func NewStringValidator() *stringValidator {
+	return &stringValidator{
+		Expression: regexp.MustCompile(`^\S*$`),
 	}
 }
 
-func NewHostStringValidator() *StringValidator {
-	re, err := regexp.Compile(`^[a-z0-9]+([-.]{1}[a-z0-9]+)*$`)
-	if err != nil {
-		fmt.Printf("Error compiling regex: %v", err)
-		return nil
-	}
-	return &StringValidator{
-		Expression: re,
+func NewHostStringValidator() *stringValidator {
+	return &stringValidator{
+		Expression: regexp.MustCompile(`^[a-z0-9]+([-.]{1}[a-z0-9]+)*$`),
 	}
 }
 
-func NewResourceStringValidator() *StringValidator {
-	re, err := regexp.Compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$")
-	if err != nil {
-		fmt.Printf("Error compiling regex: %v", err)
-		return nil
-	}
-	return &StringValidator{
-		Expression: re,
+func NewResourceStringValidator() *stringValidator {
+	return &stringValidator{
+		Expression: regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$`),
 	}
 }
 
 // TBD what are valid characters for selector field
-func NewSelectorStringValidator() *StringValidator {
-	re, err := regexp.Compile("^[A-Za-z0-9=:./-]+$")
-	if err != nil {
-		fmt.Printf("Error compiling regex: %v", err)
-		return nil
-	}
-	return &StringValidator{
-		Expression: re,
+func NewSelectorStringValidator() *stringValidator {
+	return &stringValidator{
+		Expression: regexp.MustCompile(`^[A-Za-z0-9=:./-]+$`),
 	}
 }
 
-func NewFilePathStringValidator() *StringValidator {
-	re, err := regexp.Compile("^[A-Za-z0-9./~-]+$")
-	if err != nil {
-		fmt.Printf("Error compiling regex: %v", err)
-		return nil
-	}
-	return &StringValidator{
-		Expression: re,
+func NewFilePathStringValidator() *stringValidator {
+	return &stringValidator{
+		Expression: regexp.MustCompile(`^[A-Za-z0-9./~-]+$`),
 	}
 }
 
-func (s StringValidator) Evaluate(value interface{}) (bool, error) {
+func (s stringValidator) Evaluate(value interface{}) (bool, error) {
 	v, ok := value.(string)
 
 	if !ok {


Test patch:
diff --git a/internal/cmd/skupper/site/kube/site_create_test.go b/internal/cmd/skupper/site/kube/site_create_test.go
index bd08a494..761ef3be 100644
--- a/internal/cmd/skupper/site/kube/site_create_test.go
+++ b/internal/cmd/skupper/site/kube/site_create_test.go
@@ -107,6 +107,14 @@ func TestCmdSiteCreate_ValidateInput(t *testing.T) {
 				"timeout is not valid: duration must not be less than 10s; got 0s",
 			},
 		},
+		{
+			name:  "wait status is not valid",
+			args:  []string{"my-site"},
+			flags: &common.CommandSiteCreateFlags{Timeout: time.Minute, Wait: "created"},
+			expectedErrors: []string{
+				"status is not valid: value created not allowed. It should be one of this options: [ready configured none]",
+			},
+		},
 	}
 
 	for _, test := range testTable {
@@ -147,6 +155,7 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 		expectedLinkAccess string
 		expectedOutput     string
 		expectedTimeout    time.Duration
+		expectedStatus     string
 	}
 
 	testTable := []test{
@@ -193,6 +202,12 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 			expectedOutput:     "",
 			expectedTimeout:    time.Minute,
 		},
+		{
+			name:           "options with waiting status",
+			args:           []string{"my-site"},
+			flags:          common.CommandSiteCreateFlags{Wait: "configured"},
+			expectedStatus: "configured",
+		},
 	}
 
 	for _, test := range testTable {
@@ -207,6 +222,7 @@ func TestCmdSiteCreate_InputToOptions(t *testing.T) {
 			assert.Check(t, cmd.output == test.expectedOutput)
 			assert.Check(t, cmd.linkAccessType == test.expectedLinkAccess)
 			assert.Check(t, cmd.timeout == test.expectedTimeout)
+			assert.Check(t, cmd.status == test.expectedStatus)
 		})
 	}
 }
@@ -235,7 +251,7 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 			skupperError:       "",
 		},
 		{
-			name:       "runs fails",
+			name:       "run fails",
 			k8sObjects: nil,
 			skupperObjects: []runtime.Object{
 				&v2alpha1.Site{
@@ -255,7 +271,7 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 			errorMessage:       "sites.skupper.io \"my-site\" already exists",
 		},
 		{
-			name:               "runs ok without create site",
+			name:               "runs ok but it does not create the site",
 			k8sObjects:         nil,
 			skupperObjects:     nil,
 			siteName:           "test",
@@ -265,7 +281,7 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 			skupperError:       "",
 		},
 		{
-			name:               "runs fails because the output format is not supported",
+			name:               "run fails because the output format is not supported",
 			k8sObjects:         nil,
 			skupperObjects:     nil,
 			siteName:           "test",
@@ -304,6 +320,7 @@ func TestCmdSiteCreate_Run(t *testing.T) {
 func TestCmdSiteCreate_WaitUntil(t *testing.T) {
 	type test struct {
 		name           string
+		status         string
 		k8sObjects     []runtime.Object
 		skupperObjects []runtime.Object
 		skupperError   string
@@ -314,6 +331,7 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) {
 	testTable := []test{
 		{
 			name:       "site is not ready",
+			status:     "ready",
 			k8sObjects: nil,
 			skupperObjects: []runtime.Object{
 				&v2alpha1.Site{
@@ -322,7 +340,17 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) {
 						Namespace: "test",
 					},
 					Status: v2alpha1.SiteStatus{
-						Status: v2alpha1.Status{},
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Pending",
+								},
+							},
+						},
 					},
 				},
 			},
@@ -356,6 +384,35 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) {
 		},
 		{
 			name:       "site is ready",
+			status:     "ready",
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Ready",
+								},
+							},
+						},
+					},
+				},
+			},
+			skupperError: "",
+			expectError:  false,
+		},
+		{
+			name:       "site is not ready yet, but user waits for configured",
+			status:     "configured",
 			k8sObjects: nil,
 			skupperObjects: []runtime.Object{
 				&v2alpha1.Site{
@@ -381,6 +438,62 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) {
 			skupperError: "",
 			expectError:  false,
 		},
+		{
+			name:       "user does not wait",
+			status:     "none",
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
+				},
+			},
+			skupperError: "",
+			expectError:  false,
+		},
+		{
+			name:       "user waits for configured, but site had some errors while being configured",
+			status:     "configured",
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "Error",
+									ObservedGeneration: 1,
+									Reason:             "Error",
+									Status:             "False",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
+				},
+			},
+			skupperError: "",
+			expectError:  true,
+		},
 	}
 
 	for _, test := range testTable {
@@ -395,6 +508,7 @@ func TestCmdSiteCreate_WaitUntil(t *testing.T) {
 		command.siteName = "my-site"
 		command.output = test.output
 		command.timeout = time.Second
+		command.status = test.status
 
 		t.Run(test.name, func(t *testing.T) {
 
diff --git a/internal/cmd/skupper/site/kube/site_delete_test.go b/internal/cmd/skupper/site/kube/site_delete_test.go
index b56ee5ab..6e7b4c8f 100644
--- a/internal/cmd/skupper/site/kube/site_delete_test.go
+++ b/internal/cmd/skupper/site/kube/site_delete_test.go
@@ -250,6 +250,7 @@ func TestCmdSiteDelete_InputToOptions(t *testing.T) {
 		name            string
 		flags           *common.CommandSiteDeleteFlags
 		expectedTimeout time.Duration
+		expectedWait    bool
 	}
 
 	testTable := []test{
@@ -258,6 +259,11 @@ func TestCmdSiteDelete_InputToOptions(t *testing.T) {
 			flags:           &common.CommandSiteDeleteFlags{Timeout: time.Second * 30},
 			expectedTimeout: time.Second * 30,
 		},
+		{
+			name:         "wait for site enabled",
+			flags:        &common.CommandSiteDeleteFlags{Wait: true},
+			expectedWait: true,
+		},
 	}
 
 	for _, test := range testTable {
@@ -348,6 +354,7 @@ func TestCmdSiteDelete_Run(t *testing.T) {
 func TestCmdSiteDelete_WaitUntil(t *testing.T) {
 	type test struct {
 		name           string
+		wait           bool
 		k8sObjects     []runtime.Object
 		skupperObjects []runtime.Object
 		skupperError   string
@@ -358,6 +365,7 @@ func TestCmdSiteDelete_WaitUntil(t *testing.T) {
 		{
 			name:       "site is not deleted",
 			k8sObjects: nil,
+			wait:       true,
 			skupperObjects: []runtime.Object{
 				&v2alpha1.Site{
 					ObjectMeta: v1.ObjectMeta{
@@ -385,11 +393,41 @@ func TestCmdSiteDelete_WaitUntil(t *testing.T) {
 		},
 		{
 			name:           "site is deleted",
+			wait:           true,
 			k8sObjects:     nil,
 			skupperObjects: nil,
 			skupperError:   "no site",
 			expectError:    false,
 		},
+		{
+			name:       "site is not deleted but user does not want to wait",
+			k8sObjects: nil,
+			wait:       false,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Message: "OK",
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
+				},
+			},
+			skupperError: "",
+			expectError:  false,
+		},
 	}
 
 	for _, test := range testTable {
@@ -403,6 +441,7 @@ func TestCmdSiteDelete_WaitUntil(t *testing.T) {
 		command.Client = fakeSkupperClient.GetSkupperClient().SkupperV2alpha1()
 		command.siteName = "my-site"
 		command.timeout = time.Second
+		command.wait = test.wait
 
 		t.Run(test.name, func(t *testing.T) {
 
diff --git a/internal/cmd/skupper/site/kube/site_update_test.go b/internal/cmd/skupper/site/kube/site_update_test.go
index cf3b288e..525032c9 100644
--- a/internal/cmd/skupper/site/kube/site_update_test.go
+++ b/internal/cmd/skupper/site/kube/site_update_test.go
@@ -327,6 +327,28 @@ func TestCmdSiteUpdate_ValidateInput(t *testing.T) {
 				"timeout is not valid: duration must not be less than 10s; got 0s",
 			},
 		},
+		{
+			name:       "wait status is not valid",
+			args:       []string{"my-site"},
+			flags:      &common.CommandSiteUpdateFlags{Timeout: time.Second * 30, Wait: "created"},
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Message: "OK",
+						},
+					},
+				},
+			},
+			expectedErrors: []string{
+				"status is not valid: value created not allowed. It should be one of this options: [ready configured none]",
+			},
+		},
 	}
 
 	for _, test := range testTable {
@@ -364,6 +386,7 @@ func TestCmdSiteUpdate_InputToOptions(t *testing.T) {
 		expectedLinkAccess string
 		expectedOutput     string
 		expectedTimeout    time.Duration
+		expectedStatus     string
 	}
 
 	testTable := []test{
@@ -403,6 +426,12 @@ func TestCmdSiteUpdate_InputToOptions(t *testing.T) {
 			expectedOutput:     "yaml",
 			expectedTimeout:    time.Minute,
 		},
+		{
+			name:           "options with waiting status",
+			args:           []string{"my-site"},
+			flags:          common.CommandSiteUpdateFlags{Wait: "configured"},
+			expectedStatus: "configured",
+		},
 	}
 
 	for _, test := range testTable {
@@ -420,6 +449,7 @@ func TestCmdSiteUpdate_InputToOptions(t *testing.T) {
 			command.InputToOptions()
 
 			assert.Check(t, command.output == test.expectedOutput)
+			assert.Check(t, command.status == test.expectedStatus)
 		})
 	}
 }
@@ -520,6 +550,7 @@ func TestCmdSiteUpdate_Run(t *testing.T) {
 func TestCmdSiteUpdate_WaitUntil(t *testing.T) {
 	type test struct {
 		name           string
+		status         string
 		k8sObjects     []runtime.Object
 		skupperObjects []runtime.Object
 		siteName       string
@@ -531,6 +562,53 @@ func TestCmdSiteUpdate_WaitUntil(t *testing.T) {
 	testTable := []test{
 		{
 			name:       "site is not ready",
+			status:     "ready",
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+				},
+			},
+			siteName:     "my-site",
+			skupperError: "",
+			errorMessage: "Site \"my-site\" is not ready yet, check the status for more information\n",
+			expectError:  true,
+		},
+		{
+			name:       "site is not ready yet, but user waits for configured",
+			status:     "configured",
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
+				},
+			},
+			siteName:     "my-site",
+			skupperError: "",
+			expectError:  false,
+		},
+		{
+			name:       "user does not wait",
+			status:     "none",
 			k8sObjects: nil,
 			skupperObjects: []runtime.Object{
 				&v2alpha1.Site{
@@ -538,12 +616,53 @@ func TestCmdSiteUpdate_WaitUntil(t *testing.T) {
 						Name:      "my-site",
 						Namespace: "test",
 					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "OK",
+									ObservedGeneration: 1,
+									Reason:             "OK",
+									Status:             "True",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
+				},
+			},
+			skupperError: "",
+			expectError:  false,
+		},
+		{
+			name:       "user waits for configured, but site had some errors while being configured",
+			status:     "configured",
+			k8sObjects: nil,
+			skupperObjects: []runtime.Object{
+				&v2alpha1.Site{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "my-site",
+						Namespace: "test",
+					},
+					Status: v2alpha1.SiteStatus{
+						Status: v2alpha1.Status{
+							Conditions: []v1.Condition{
+								{
+									Message:            "Error",
+									ObservedGeneration: 1,
+									Reason:             "Error",
+									Status:             "False",
+									Type:               "Configured",
+								},
+							},
+						},
+					},
 				},
 			},
 			siteName:     "my-site",
 			skupperError: "",
-			errorMessage: "Site \"my-site\" not ready yet, check the status for more information\n",
 			expectError:  true,
+			errorMessage: "Site \"my-site\" is configured with errors, check the status for more information\n",
 		},
 	}
 
@@ -557,14 +676,15 @@ func TestCmdSiteUpdate_WaitUntil(t *testing.T) {
 		assert.Assert(t, err)
 		command.Client = fakeSkupperClient.GetSkupperClient().SkupperV2alpha1()
 		command.siteName = test.siteName
-		command.timeout = 1
+		command.timeout = time.Second
+		command.status = test.status
 
 		t.Run(test.name, func(t *testing.T) {
 
 			err := command.WaitUntil()
 			if err != nil {
 				assert.Check(t, test.expectError)
-				assert.Check(t, test.errorMessage == err.Error())
+				assert.Equal(t, test.errorMessage, err.Error())
 			}
 
 		})
diff --git a/internal/cmd/skupper/site/site_test.go b/internal/cmd/skupper/site/site_test.go
index a9f62996..f21ce86b 100644
--- a/internal/cmd/skupper/site/site_test.go
+++ b/internal/cmd/skupper/site/site_test.go
@@ -25,9 +25,10 @@ func TestCmdSiteFactory(t *testing.T) {
 				common.FlagNameLinkAccessType:          "",
 				common.FlagNameServiceAccount:          "",
 				common.FlagNameOutput:                  "",
-				common.FlagNameTimeout:                 "1m0s",
+				common.FlagNameTimeout:                 "30s",
 				common.FlagNameBindHost:                "",
 				common.FlagNameSubjectAlternativeNames: "[]",
+				common.FlagNameWait:                    "ready",
 			},
 			command: CmdSiteCreateFactory(types.PlatformKubernetes),
 		},
@@ -39,8 +40,9 @@ func TestCmdSiteFactory(t *testing.T) {
 				common.FlagNameServiceAccount:          "",
 				common.FlagNameOutput:                  "",
 				common.FlagNameBindHost:                "",
-				common.FlagNameTimeout:                 "1m0s",
+				common.FlagNameTimeout:                 "30s",
 				common.FlagNameSubjectAlternativeNames: "[]",
+				common.FlagNameWait:                    "ready",
 			},
 			command: CmdSiteUpdateFactory(types.PlatformKubernetes),
 		},
@@ -48,6 +50,7 @@ func TestCmdSiteFactory(t *testing.T) {
 			name: "CmdSiteDeleteFactory",
 			expectedFlagsWithDefaultValue: map[string]interface{}{
 				common.FlagNameTimeout: "1m0s",
+				common.FlagNameWait:    "true",
 			},
 			command: CmdSiteDeleteFactory(types.PlatformKubernetes),
 		},
diff --git a/pkg/utils/validator/simple_validator_test.go b/pkg/utils/validator/simple_validator_test.go
index 6ecbe050..89cde020 100644
--- a/pkg/utils/validator/simple_validator_test.go
+++ b/pkg/utils/validator/simple_validator_test.go
@@ -13,8 +13,8 @@ func TestNewStringValidator(t *testing.T) {
 
 	t.Run("Test String Validator constructor", func(t *testing.T) {
 
-		validRegexp, _ := regexp.Compile("^\\S*$")
-		expectedResult := &StringValidator{validRegexp}
+		validRegexp := regexp.MustCompile(`^\S*$`)
+		expectedResult := &stringValidator{validRegexp}
 		actualResult := NewStringValidator()
 		assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult))
 	})
@@ -149,8 +149,8 @@ func TestNewResourceStringValidator(t *testing.T) {
 
 	t.Run("Test New Resource String Validator constructor", func(t *testing.T) {
 
-		validRegexp, _ := regexp.Compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$")
-		expectedResult := &StringValidator{validRegexp}
+		validRegexp := regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])*(\.[a-z0-9]([-a-z0-9]*[a-z0-9])*)*$`)
+		expectedResult := &stringValidator{validRegexp}
 		actualResult := NewResourceStringValidator()
 		assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult))
 	})
@@ -188,8 +188,8 @@ func TestNewSelectorStringValidator(t *testing.T) {
 
 	t.Run("Test New Selector String Validator constructor", func(t *testing.T) {
 
-		validRegexp, _ := regexp.Compile("^[A-Za-z0-9=:./-]+$")
-		expectedResult := &StringValidator{validRegexp}
+		validRegexp := regexp.MustCompile("^[A-Za-z0-9=:./-]+$")
+		expectedResult := &stringValidator{validRegexp}
 		actualResult := NewSelectorStringValidator()
 		assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult))
 	})
@@ -229,8 +229,8 @@ func TestNewFilePathStringValidator(t *testing.T) {
 
 	t.Run("Test New File Path String Validator constructor", func(t *testing.T) {
 
-		validRegexp, _ := regexp.Compile("^[A-Za-z0-9./~-]+$")
-		expectedResult := &StringValidator{validRegexp}
+		validRegexp := regexp.MustCompile("^[A-Za-z0-9./~-]+$")
+		expectedResult := &stringValidator{validRegexp}
 		actualResult := NewFilePathStringValidator()
 		assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult))
 	})
@@ -271,7 +271,7 @@ func TestNewWorkloadStringValidator(t *testing.T) {
 
 	t.Run("Test New Workload String Validator constructor", func(t *testing.T) {
 
-		validRegexp, _ := regexp.Compile("^[A-Za-z0-9.-_]+$")
+		validRegexp := regexp.MustCompile("^[A-Za-z0-9.-_]+$")
 		expectedResult := &WorkloadValidator{validRegexp, []string{"a", "b"}}
 		actualResult := NewWorkloadStringValidator([]string{"a", "b"})
 		assert.Assert(t, reflect.DeepEqual(actualResult, expectedResult))