github.com/verrazzano/verrazzano@v1.7.0/tools/vz/cmd/helpers/vpo.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package helpers
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"os"
    13  	"regexp"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/spf13/cobra"
    18  	vzconstants "github.com/verrazzano/verrazzano/pkg/constants"
    19  	"github.com/verrazzano/verrazzano/pkg/k8s/ready"
    20  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    21  	"github.com/verrazzano/verrazzano/pkg/semver"
    22  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    23  	vpoconst "github.com/verrazzano/verrazzano/platform-operator/constants"
    24  	"github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    25  	"github.com/verrazzano/verrazzano/tools/vz/pkg/helpers"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/selection"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/client-go/kubernetes"
    34  	clipkg "sigs.k8s.io/controller-runtime/pkg/client"
    35  )
    36  
    37  const VpoSimpleLogFormatRegexp = `"level":"(.*?)","@timestamp":"(.*?)",(.*?)"message":"(.*?)",`
    38  const accessErrorMsg = "Failed to access the Verrazzano operator.yaml file %s: %s"
    39  const applyErrorMsg = "Failed to apply the Verrazzano operator.yaml file %s: %s"
    40  
    41  // deleteLeftoverPlatformOperatorSig is a function needed for unit test override
    42  type deleteLeftoverPlatformOperatorSig func(client clipkg.Client) error
    43  
    44  // DeleteFunc is the default deleteLeftoverPlatformOperator function
    45  var DeleteFunc deleteLeftoverPlatformOperatorSig = deleteLeftoverPlatformOperator
    46  
    47  func SetDeleteFunc(f deleteLeftoverPlatformOperatorSig) {
    48  	DeleteFunc = f
    49  }
    50  
    51  func SetDefaultDeleteFunc() {
    52  	DeleteFunc = deleteLeftoverPlatformOperator
    53  }
    54  
    55  func FakeDeleteFunc(client clipkg.Client) error {
    56  	return nil
    57  }
    58  
    59  // applyYAMLSig Allow overriding the applyYAML function for unit testing
    60  type applyYAMLSig func(filename string, client clipkg.Client, vzHelper helpers.VZHelper) error
    61  
    62  var applyYAMLFunc applyYAMLSig = applyYAML
    63  
    64  func SetApplyYAMLFunc(f applyYAMLSig) {
    65  	applyYAMLFunc = f
    66  }
    67  
    68  func SetDefaultApplyYAMLFunc() {
    69  	applyYAMLFunc = applyYAML
    70  }
    71  
    72  // vpoIsReadySig Allow overriding the vpoIsReady function for unit testing
    73  type vpoIsReadySig func(client clipkg.Client) (bool, error)
    74  
    75  var vpoIsReadyFunc vpoIsReadySig = vpoIsReady
    76  
    77  func SetVPOIsReadyFunc(f vpoIsReadySig) {
    78  	vpoIsReadyFunc = f
    79  }
    80  
    81  func SetDefaultVPOIsReadyFunc() {
    82  	vpoIsReadyFunc = vpoIsReady
    83  }
    84  
    85  // GetExistingVPODeployment - get existing Verrazzano Platform operator Deployment from the cluster
    86  func GetExistingVPODeployment(client clipkg.Client) (*appsv1.Deployment, error) {
    87  	deploy := appsv1.Deployment{}
    88  	namespacedName := types.NamespacedName{Name: constants.VerrazzanoPlatformOperator, Namespace: vzconstants.VerrazzanoInstallNamespace}
    89  	if err := client.Get(context.TODO(), namespacedName, &deploy); err != nil {
    90  		if errors.IsNotFound(err) {
    91  			return nil, nil
    92  		}
    93  		return nil, failedToGetVPODeployment(err)
    94  
    95  	}
    96  	return &deploy, nil
    97  }
    98  
    99  // GetExistingVPOWebhookDeployment - get existing Verrazzano Platform operator webhook deployment from the cluster
   100  func GetExistingVPOWebhookDeployment(client clipkg.Client) (*appsv1.Deployment, error) {
   101  	deploy := appsv1.Deployment{}
   102  	namespacedName := types.NamespacedName{Name: constants.VerrazzanoPlatformOperatorWebhook, Namespace: vzconstants.VerrazzanoInstallNamespace}
   103  	if err := client.Get(context.TODO(), namespacedName, &deploy); err != nil {
   104  		if errors.IsNotFound(err) {
   105  			return nil, nil
   106  		}
   107  		return nil, fmt.Errorf("Failed to get existing %s deployment: %s", constants.VerrazzanoPlatformOperatorWebhook, err.Error())
   108  
   109  	}
   110  	return &deploy, nil
   111  }
   112  
   113  // GetExistingPrivateRegistrySettings gets the private registry env var settings on existing
   114  // VPO Deployment, if present
   115  func getExistingPrivateRegistrySettings(vpoDeploy *appsv1.Deployment) (string, string) {
   116  	registry := ""
   117  	imagePrefix := ""
   118  	for _, container := range vpoDeploy.Spec.Template.Spec.Containers {
   119  		if container.Name == constants.VerrazzanoPlatformOperator {
   120  			for _, env := range container.Env {
   121  				if env.Name == vpoconst.RegistryOverrideEnvVar {
   122  					registry = env.Value
   123  				} else if env.Name == vpoconst.ImageRepoOverrideEnvVar {
   124  					imagePrefix = env.Value
   125  				}
   126  			}
   127  		}
   128  	}
   129  	return registry, imagePrefix
   130  }
   131  
   132  // UsePlatformOperatorUninstallJob determines whether the version of the platform operator is using an uninstall job.
   133  // The uninstall job was removed with Verrazzano 1.4.0.
   134  func UsePlatformOperatorUninstallJob(client clipkg.Client) (bool, error) {
   135  	deployment := &appsv1.Deployment{}
   136  	err := client.Get(context.TODO(), types.NamespacedName{Namespace: vzconstants.VerrazzanoInstallNamespace, Name: constants.VerrazzanoPlatformOperator}, deployment)
   137  	if err != nil {
   138  		return false, fmt.Errorf("Failed to find %s/%s: %s", vzconstants.VerrazzanoInstallNamespace, constants.VerrazzanoPlatformOperator, err.Error())
   139  	}
   140  
   141  	// label does not exist therefore uninstall job is being used
   142  	version, ok := deployment.Labels["app.kubernetes.io/version"]
   143  	if !ok {
   144  		return true, nil
   145  	}
   146  
   147  	minVersion := semver.SemVersion{Major: 1, Minor: 4, Patch: 0}
   148  	vzVersion, err := semver.NewSemVersion(version)
   149  	if err != nil {
   150  		return false, err
   151  	}
   152  
   153  	// Version of platform operator is less than  1.4.0 therefore uninstall job is being used
   154  	if vzVersion.IsLessThan(&minVersion) {
   155  		return true, nil
   156  	}
   157  
   158  	return false, nil
   159  }
   160  
   161  // ApplyPlatformOperatorYaml applies a given version of the platform operator yaml file
   162  func ApplyPlatformOperatorYaml(cmd *cobra.Command, client clipkg.Client, vzHelper helpers.VZHelper, version string) error {
   163  	localOperatorFilename, userVisibleFilename, isTempFile, err := getOrDownloadOperatorYAML(cmd, version, vzHelper)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	if isTempFile {
   168  		// the operator YAML is a temporary file that must be deleted after applying it
   169  		defer os.Remove(localOperatorFilename)
   170  	}
   171  
   172  	if localOperatorFilename, err = processOperatorYAMLPrivateRegistry(cmd, localOperatorFilename); err != nil {
   173  		return err
   174  	}
   175  
   176  	if _, err := os.Stat(localOperatorFilename); err != nil {
   177  		return err
   178  	}
   179  
   180  	// Delete previous verrazzano-platform-operator deployments when we have successfully downloaded new one.
   181  	// This allows for the verrazzano-platform-operator validatingWebhookConfiguration to be updated with the correct caBundle.
   182  	if err = DeleteFunc(client); err != nil {
   183  		return err
   184  	}
   185  
   186  	// Apply the Verrazzano operator.yaml
   187  	fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Applying the file %s\n", userVisibleFilename))
   188  	return applyYAMLFunc(localOperatorFilename, client, vzHelper)
   189  }
   190  
   191  func applyYAML(filename string, client clipkg.Client, vzHelper helpers.VZHelper) error {
   192  	yamlApplier := k8sutil.NewYAMLApplier(client, "")
   193  	err := yamlApplier.ApplyF(filename)
   194  	if err != nil {
   195  		return fmt.Errorf(applyErrorMsg, filename, err.Error())
   196  	}
   197  
   198  	// Dump out the object result messages
   199  	for _, result := range yamlApplier.ObjectResultMsgs() {
   200  		fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("%s\n", strings.ToLower(result)))
   201  	}
   202  	return nil
   203  }
   204  
   205  // processOperatorYAMLPrivateRegistry - examines private registry related command flags and processes
   206  // the operator YAML file as needed
   207  func processOperatorYAMLPrivateRegistry(cmd *cobra.Command, operatorFilename string) (string, error) {
   208  	// check for private registry flags
   209  	if !cmd.PersistentFlags().Changed(constants.ImageRegistryFlag) &&
   210  		!cmd.PersistentFlags().Changed(constants.ImagePrefixFlag) {
   211  		return operatorFilename, nil
   212  	}
   213  	var imageRegistry string
   214  	var imagePrefix string
   215  	var err error
   216  	if imageRegistry, err = cmd.PersistentFlags().GetString(constants.ImageRegistryFlag); err != nil {
   217  		return operatorFilename, err
   218  	}
   219  	if imagePrefix, err = cmd.PersistentFlags().GetString(constants.ImagePrefixFlag); err != nil {
   220  		return operatorFilename, err
   221  	}
   222  
   223  	return updateOperatorYAMLPrivateRegistry(operatorFilename, imageRegistry, imagePrefix)
   224  }
   225  
   226  func getOrDownloadOperatorYAML(cmd *cobra.Command, version string, vzHelper helpers.VZHelper) (string, string, bool, error) {
   227  	// Was an operator-file passed on the command line?
   228  	operatorFile, err := getOperatorFileFromFlag(cmd)
   229  	if err != nil {
   230  		return "", "", false, err
   231  	}
   232  
   233  	isTempFile := false
   234  	// If the operatorFile was specified, is it a local or remote file?
   235  	url := ""
   236  	localOperatorFilename := ""
   237  	if len(operatorFile) > 0 {
   238  		if strings.HasPrefix(strings.ToLower(operatorFile), "https://") {
   239  			url = operatorFile
   240  		} else {
   241  			localOperatorFilename = operatorFile
   242  		}
   243  	} else {
   244  		url, err = helpers.GetOperatorYaml(version)
   245  		if err != nil {
   246  			return "", "", false, err
   247  		}
   248  	}
   249  
   250  	userVisibleFilename := operatorFile
   251  	// if we have a URL, download the file
   252  	if len(url) > 0 {
   253  		isTempFile = true
   254  		userVisibleFilename = url
   255  		if localOperatorFilename, err = downloadOperatorYAML(url, vzHelper); err != nil {
   256  			return localOperatorFilename, userVisibleFilename, isTempFile, err
   257  		}
   258  	}
   259  	return localOperatorFilename, userVisibleFilename, isTempFile, nil
   260  }
   261  
   262  // downloadOperatorYAML downloads the operator YAML file from the given URL and returns the
   263  // path to the temp file where it is stored.
   264  func downloadOperatorYAML(url string, vzHelper helpers.VZHelper) (string, error) {
   265  	// Get the Verrazzano operator.yaml and store it in a temp file
   266  	httpClient := vzHelper.GetHTTPClient()
   267  	resp, err := httpClient.Get(url)
   268  	if err != nil {
   269  		return "", fmt.Errorf(accessErrorMsg, url, err.Error())
   270  	}
   271  	if resp.StatusCode != http.StatusOK {
   272  		return "", fmt.Errorf(accessErrorMsg, url, resp.Status)
   273  	}
   274  	// Store response in a temporary file
   275  	tmpFile, err := os.CreateTemp("", "vz")
   276  	if err != nil {
   277  		return "", fmt.Errorf(applyErrorMsg, url, err.Error())
   278  	}
   279  	_, err = tmpFile.ReadFrom(resp.Body)
   280  	if err != nil {
   281  		os.Remove(tmpFile.Name())
   282  		return "", fmt.Errorf(applyErrorMsg, url, err.Error())
   283  	}
   284  	return tmpFile.Name(), nil
   285  }
   286  
   287  // WaitForPlatformOperator waits for the verrazzano-platform-operator to be ready
   288  func WaitForPlatformOperator(client clipkg.Client, vzHelper helpers.VZHelper, condType v1beta1.ConditionType, timeout time.Duration) (string, error) {
   289  	// Provide the user with feedback while waiting for the verrazzano-platform-operator to be ready
   290  	feedbackChan := make(chan bool)
   291  	defer close(feedbackChan)
   292  	go func(outputStream io.Writer) {
   293  		seconds := 0
   294  		for {
   295  			select {
   296  			case <-feedbackChan:
   297  				fmt.Fprint(outputStream, "\n")
   298  				return
   299  			default:
   300  				time.Sleep(constants.VerrazzanoPlatformOperatorWait * time.Second)
   301  				seconds += constants.VerrazzanoPlatformOperatorWait
   302  				fmt.Fprintf(outputStream, fmt.Sprintf("\rWaiting for %s to be ready before starting %s - %d seconds", constants.VerrazzanoPlatformOperator, getOperationString(condType), seconds))
   303  			}
   304  		}
   305  	}(vzHelper.GetOutputStream())
   306  
   307  	// Wait for the verrazzano-platform-operator pod to be found
   308  	secondsWaited := 0
   309  	maxSecondsToWait := int(timeout.Seconds())
   310  	for {
   311  		ready, err := vpoIsReadyFunc(client)
   312  		if ready {
   313  			break
   314  		}
   315  
   316  		if secondsWaited > maxSecondsToWait {
   317  			feedbackChan <- true
   318  			return "", fmt.Errorf("Waiting for %s pod in namespace %s: %v", constants.VerrazzanoPlatformOperator, vzconstants.VerrazzanoInstallNamespace, err)
   319  		}
   320  		time.Sleep(constants.VerrazzanoPlatformOperatorWait * time.Second)
   321  		secondsWaited += constants.VerrazzanoPlatformOperatorWait
   322  	}
   323  	feedbackChan <- true
   324  
   325  	// Return the platform operator pod name
   326  	return GetVerrazzanoPlatformOperatorPodName(client)
   327  }
   328  
   329  // WaitForOperationToComplete waits for the Verrazzano install/upgrade to complete and
   330  // shows the logs of the ongoing Verrazzano install/upgrade.
   331  func WaitForOperationToComplete(client clipkg.Client, kubeClient kubernetes.Interface, vzHelper helpers.VZHelper, namespacedName types.NamespacedName, timeout time.Duration, vpoTimeout time.Duration, logFormat LogFormat, condType v1beta1.ConditionType) error {
   332  	resChan := make(chan error, 1)
   333  	defer close(resChan)
   334  
   335  	feedbackChan := make(chan bool)
   336  	defer close(feedbackChan)
   337  
   338  	// goroutine to stream log file output - this goroutine will be left running when this
   339  	// function is exited because there is no way to cancel the blocking read to the input stream.
   340  	re := regexp.MustCompile(VpoSimpleLogFormatRegexp)
   341  	go func(kubeClient kubernetes.Interface, outputStream io.Writer) {
   342  		var sc *bufio.Scanner
   343  		var err error
   344  		secondsWaited := 0
   345  		maxSecondsToWait := int(vpoTimeout.Seconds())
   346  		const secondsPerRetry = 10
   347  
   348  		for {
   349  			if sc == nil {
   350  				sc, err = getScanner(client, kubeClient)
   351  				if err != nil {
   352  					fmt.Fprintf(outputStream, fmt.Sprintf("Failed to connect to the console output, waited %d of %d seconds to recover: %v\n", secondsWaited, maxSecondsToWait, err))
   353  					secondsWaited += secondsPerRetry
   354  					if secondsWaited > maxSecondsToWait {
   355  						return
   356  					}
   357  					time.Sleep(secondsPerRetry * time.Second)
   358  					continue
   359  				}
   360  				secondsWaited = 0
   361  				sc.Split(bufio.ScanLines)
   362  			}
   363  
   364  			scannedOk := sc.Scan()
   365  			if !scannedOk {
   366  				errText := ""
   367  				if sc.Err() != nil {
   368  					errText = fmt.Sprintf(": %v", sc.Err())
   369  				}
   370  				fmt.Fprintf(outputStream, fmt.Sprintf("Lost connection to the console output, attempting to reconnect%s\n", errText))
   371  				sc = nil
   372  				continue
   373  			}
   374  			if logFormat == LogFormatSimple {
   375  				PrintSimpleLogFormat(sc, outputStream, re)
   376  			} else if logFormat == LogFormatJSON {
   377  				fmt.Fprintf(outputStream, fmt.Sprintf("%s\n", sc.Text()))
   378  			}
   379  		}
   380  	}(kubeClient, vzHelper.GetOutputStream())
   381  
   382  	startTime := time.Now().UTC()
   383  
   384  	// goroutine to wait for the completion of the operation
   385  	go func() {
   386  		for {
   387  			// Pause before each status check
   388  			time.Sleep(1 * time.Second)
   389  			select {
   390  			case <-feedbackChan:
   391  				return
   392  			default:
   393  				// Return when the Verrazzano operation has completed
   394  				vz, err := helpers.GetVerrazzanoResource(client, namespacedName)
   395  				if err != nil {
   396  					// Retry if there is a problem getting the resource.  It is ok to keep retrying since
   397  					// WaitForOperationToComplete main routine will timeout.
   398  					time.Sleep(10 * time.Second)
   399  					continue
   400  				}
   401  				for _, condition := range vz.Status.Conditions {
   402  					// Operation condition met for install/upgrade
   403  					if condition.Type == condType {
   404  						condTime, err := time.Parse(time.RFC3339, condition.LastTransitionTime)
   405  						if err != nil {
   406  							resChan <- fmt.Errorf("Failed parsing status condition lastTransitionTime: %s", err.Error())
   407  							return
   408  						}
   409  						// There can be multiple conditions with the same type.  Make sure we find a match
   410  						// beyond the start time.
   411  						if condTime.After(startTime) {
   412  							resChan <- nil
   413  							return
   414  						}
   415  					}
   416  				}
   417  			}
   418  		}
   419  	}()
   420  
   421  	select {
   422  	case result := <-resChan:
   423  		return result
   424  	case <-time.After(timeout):
   425  		if timeout.Nanoseconds() != 0 {
   426  			return fmt.Errorf("Timeout %v exceeded waiting for %s to complete", timeout.String(), getOperationString(condType))
   427  		}
   428  	}
   429  
   430  	return nil
   431  }
   432  
   433  func getScanner(client clipkg.Client, kubeClient kubernetes.Interface) (*bufio.Scanner, error) {
   434  	vpoPodName, err := GetVerrazzanoPlatformOperatorPodName(client)
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  
   439  	rc, err := GetVpoLogStream(kubeClient, vpoPodName)
   440  	if err != nil {
   441  		return nil, fmt.Errorf("failed to stream log output: %v", err)
   442  	}
   443  
   444  	return bufio.NewScanner(rc), nil
   445  }
   446  
   447  // GetVerrazzanoPlatformOperatorPodName returns the VPO pod name
   448  func GetVerrazzanoPlatformOperatorPodName(client clipkg.Client) (string, error) {
   449  	appLabel, _ := labels.NewRequirement("app", selection.Equals, []string{constants.VerrazzanoPlatformOperator})
   450  	labelSelector := labels.NewSelector()
   451  	labelSelector = labelSelector.Add(*appLabel)
   452  	podList := corev1.PodList{}
   453  	err := client.List(
   454  		context.TODO(),
   455  		&podList,
   456  		&clipkg.ListOptions{
   457  			Namespace:     vzconstants.VerrazzanoInstallNamespace,
   458  			LabelSelector: labelSelector,
   459  		})
   460  	if err != nil {
   461  		return "", fmt.Errorf("Waiting for %s, failed to list pods: %s", constants.VerrazzanoPlatformOperator, err.Error())
   462  	}
   463  	if len(podList.Items) == 0 {
   464  		return "", fmt.Errorf("Failed to find the Verrazzano platform operator in namespace %s", vzconstants.VerrazzanoInstallNamespace)
   465  	}
   466  	if len(podList.Items) > 1 {
   467  		return "", fmt.Errorf("Waiting for %s, more than one %s pod was found in namespace %s", constants.VerrazzanoPlatformOperator, constants.VerrazzanoPlatformOperator, vzconstants.VerrazzanoInstallNamespace)
   468  	}
   469  
   470  	return podList.Items[0].Name, nil
   471  }
   472  
   473  // GetVpoLogStream returns the stream to the verrazzano-platform-operator log file
   474  func GetVpoLogStream(kubeClient kubernetes.Interface, vpoPodName string) (io.ReadCloser, error) {
   475  	// Tail the log messages from the verrazzano-platform-operator starting at the current time.
   476  	//
   477  	// The stream is intentionally not closed due to not being able to cancel a blocking read.  The calls to
   478  	// read input from this stream (sc.Scan) are blocking.  If you try to close the stream, it hangs until the
   479  	// next read is satisfied, which may never occur if there is no more log output.
   480  	sinceTime := metav1.Now()
   481  	rc, err := kubeClient.CoreV1().Pods(vzconstants.VerrazzanoInstallNamespace).GetLogs(vpoPodName, &corev1.PodLogOptions{
   482  		Container: constants.VerrazzanoPlatformOperator,
   483  		Follow:    true,
   484  		SinceTime: &sinceTime,
   485  	}).Stream(context.TODO())
   486  	if err != nil {
   487  		return nil, fmt.Errorf("Failed to read the %s log file: %s", constants.VerrazzanoPlatformOperator, err.Error())
   488  	}
   489  	return rc, nil
   490  }
   491  
   492  // PrintSimpleLogFormat display a VPO log message with the simple log format
   493  func PrintSimpleLogFormat(sc *bufio.Scanner, outputStream io.Writer, regexp *regexp.Regexp) {
   494  	res := regexp.FindAllStringSubmatch(sc.Text(), -1)
   495  	// res[0][2] is the timestamp
   496  	// res[0][1] is the level
   497  	// res[0][4] is the message
   498  
   499  	if res != nil && isAcceptableMessageForSimpleLogFormat(res[0][1], res[0][4]) {
   500  		// Print each log message in the form "timestamp level message".
   501  		// For example, "2022-06-03T00:05:10.042Z info Component keycloak successfully installed"
   502  		fmt.Fprintf(outputStream, fmt.Sprintf("%s %s %s\n", res[0][2], res[0][1], res[0][4]))
   503  	}
   504  }
   505  
   506  // isNotAcceptableMessageForSimpleLogFormat returns true if a message should be filtered out from the console
   507  func isAcceptableMessageForSimpleLogFormat(level string, message string) bool {
   508  	if (level != "error" && level != "info") || strings.Contains(message, "Starting EventSource") {
   509  		return false
   510  	}
   511  	if level == "info" && (strings.Contains(message, "replica") || strings.Contains(message, "certificate") || strings.Contains(message, "Associating NetworkPolicy") || strings.Contains(message, "Updating the Labels and Annotations") || strings.Contains(message, "Resetting initial MySQL pod readiness check") || strings.Contains(message, "waiting for readiness gates") || strings.Contains(message, "required ingresses") || strings.Contains(message, "waiting for") || strings.Contains(message, "Waiting for") || strings.Contains(message, "Certificate") || strings.Contains(message, "Replica") || strings.Contains(message, "Starting Controller") || strings.Contains(message, "Starting workers") || strings.Contains(message, "Helm") || strings.Contains(message, "helm") || strings.Contains(message, "Applying yaml") || strings.Contains(message, "Successfully deleted")) {
   512  		return false
   513  	}
   514  	return true
   515  
   516  }
   517  
   518  // return the operation string to display
   519  func getOperationString(condType v1beta1.ConditionType) string {
   520  	operation := "install"
   521  	if condType == v1beta1.CondUpgradeComplete {
   522  		operation = "upgrade"
   523  	}
   524  	return operation
   525  }
   526  
   527  // vpoIsReady check that the named deployments have the minimum number of specified replicas ready and available
   528  func vpoIsReady(client clipkg.Client) (bool, error) {
   529  	var expectedReplicas int32 = 1
   530  	deployment := appsv1.Deployment{}
   531  	namespacedName := types.NamespacedName{Name: constants.VerrazzanoPlatformOperator, Namespace: vzconstants.VerrazzanoInstallNamespace}
   532  	if err := client.Get(context.TODO(), namespacedName, &deployment); err != nil {
   533  		if errors.IsNotFound(err) {
   534  			return false, nil
   535  		}
   536  		return false, fmt.Errorf("Failed getting deployment %s: %s", constants.VerrazzanoPlatformOperator, err.Error())
   537  	}
   538  	if deployment.Status.UpdatedReplicas < expectedReplicas {
   539  		return false, nil
   540  	}
   541  	if deployment.Status.AvailableReplicas < expectedReplicas {
   542  		return false, nil
   543  	}
   544  
   545  	if !ready.PodsReadyDeployment(nil, client, namespacedName, deployment.Spec.Selector, expectedReplicas, constants.VerrazzanoPlatformOperator) {
   546  		return false, nil
   547  	}
   548  
   549  	return true, nil
   550  }
   551  
   552  func failedToGetVPODeployment(err error) error {
   553  	return fmt.Errorf("Failed to get existing %s deployment: %s", constants.VerrazzanoPlatformOperator, err.Error())
   554  }
   555  
   556  // deleteLeftoverPlatformOperator deletes leftover verrazzano-platform-operator deployments after an abort.
   557  // This allows for the verrazzano-platform-operator validatingWebhookConfiguration to be updated with an updated caBundle.
   558  func deleteLeftoverPlatformOperator(client clipkg.Client) error {
   559  	vpoDeployment := appsv1.Deployment{
   560  		ObjectMeta: metav1.ObjectMeta{
   561  			Namespace: vzconstants.VerrazzanoInstallNamespace,
   562  			Name:      constants.VerrazzanoPlatformOperator,
   563  		},
   564  	}
   565  	if err := client.Delete(context.TODO(), &vpoDeployment); err != nil {
   566  		if !errors.IsNotFound(err) {
   567  			return fmt.Errorf("Failed to delete leftover %s deployment: %s", constants.VerrazzanoPlatformOperator, err.Error())
   568  		}
   569  	}
   570  	vpoWebHookDeployment := appsv1.Deployment{
   571  		ObjectMeta: metav1.ObjectMeta{
   572  			Namespace: vzconstants.VerrazzanoInstallNamespace,
   573  			Name:      constants.VerrazzanoPlatformOperatorWebhook,
   574  		},
   575  	}
   576  	if err := client.Delete(context.TODO(), &vpoWebHookDeployment); err != nil {
   577  		if !errors.IsNotFound(err) {
   578  			return fmt.Errorf("Failed to delete leftover %s deployment: %s", constants.VerrazzanoPlatformOperatorWebhook, err.Error())
   579  		}
   580  	}
   581  
   582  	return nil
   583  }
   584  
   585  // ValidatePrivateRegistry - Validate private registry settings in command against
   586  // those in existing VPO deployment, if any
   587  func ValidatePrivateRegistry(cmd *cobra.Command, client clipkg.Client) error {
   588  	vpoDeploy, err := GetExistingVPODeployment(client)
   589  	if err != nil {
   590  		return fmt.Errorf("Failed to get existing %s deployment: %v",
   591  			constants.VerrazzanoPlatformOperator, err)
   592  	}
   593  	if vpoDeploy == nil {
   594  		// no existing VPO deployment, nothing to validate
   595  		return nil
   596  	}
   597  	existingImageRegistry, existingImagePrefix := getExistingPrivateRegistrySettings(vpoDeploy)
   598  	newRegistry, err := cmd.PersistentFlags().GetString(constants.ImageRegistryFlag)
   599  	if err != nil {
   600  		return err
   601  	}
   602  	newImagePrefix, err := cmd.PersistentFlags().GetString(constants.ImagePrefixFlag)
   603  	if err != nil {
   604  		return err
   605  	}
   606  	if existingImageRegistry != newRegistry || existingImagePrefix != newImagePrefix {
   607  		return fmt.Errorf(
   608  			imageRegistryMismatchError(existingImageRegistry, existingImagePrefix, newRegistry, newImagePrefix))
   609  	}
   610  	return nil
   611  }
   612  
   613  func imageRegistryMismatchError(existingRegistry, existingPrefix, newRegistry, newPrefix string) string {
   614  	existingRegistryMsg := ""
   615  	newRegistryMsg := ""
   616  	if existingRegistry == "" && existingPrefix == "" {
   617  		existingRegistryMsg = "the public Verrazzano image repository"
   618  	} else {
   619  		existingRegistryMsg = fmt.Sprintf("image-registry %s and image-prefix %s", existingRegistry, existingPrefix)
   620  	}
   621  	if newRegistry == "" && newPrefix == "" {
   622  		newRegistryMsg = "the public Verrazzano image repository"
   623  	} else {
   624  		newRegistryMsg = fmt.Sprintf("image-registry %s and image-prefix %s", newRegistry, newPrefix)
   625  	}
   626  	return fmt.Sprintf("The existing Verrazzano installation uses %s, but you provided %s", existingRegistryMsg, newRegistryMsg)
   627  }