github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/step/verify/step_verify_preinstall.go (about)

     1  package verify
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/blang/semver"
    12  
    13  	"github.com/olli-ai/jx/v2/pkg/cloud/amazon/session"
    14  	"github.com/olli-ai/jx/v2/pkg/prow"
    15  	"sigs.k8s.io/yaml"
    16  
    17  	"github.com/jenkins-x/jx-logging/pkg/log"
    18  	"github.com/olli-ai/jx/v2/pkg/boot"
    19  	"github.com/olli-ai/jx/v2/pkg/cloud"
    20  	"github.com/olli-ai/jx/v2/pkg/cloud/amazon"
    21  	"github.com/olli-ai/jx/v2/pkg/cloud/buckets"
    22  	"github.com/olli-ai/jx/v2/pkg/cloud/factory"
    23  	"github.com/olli-ai/jx/v2/pkg/cloud/gke"
    24  	"github.com/olli-ai/jx/v2/pkg/cmd/create"
    25  	"github.com/olli-ai/jx/v2/pkg/cmd/helper"
    26  	"github.com/olli-ai/jx/v2/pkg/cmd/namespace"
    27  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    28  	"github.com/olli-ai/jx/v2/pkg/cmd/opts/step"
    29  	"github.com/olli-ai/jx/v2/pkg/config"
    30  	"github.com/olli-ai/jx/v2/pkg/gits"
    31  	"github.com/olli-ai/jx/v2/pkg/io/secrets"
    32  	"github.com/olli-ai/jx/v2/pkg/kube"
    33  	"github.com/olli-ai/jx/v2/pkg/kube/cluster"
    34  	"github.com/olli-ai/jx/v2/pkg/kube/naming"
    35  	"github.com/olli-ai/jx/v2/pkg/packages"
    36  	"github.com/olli-ai/jx/v2/pkg/util"
    37  	"github.com/pkg/errors"
    38  	"github.com/spf13/cobra"
    39  	corev1 "k8s.io/api/core/v1"
    40  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  	"k8s.io/client-go/kubernetes"
    42  )
    43  
    44  // StepVerifyPreInstallOptions contains the command line flags
    45  type StepVerifyPreInstallOptions struct {
    46  	StepVerifyOptions
    47  	Debug                 bool
    48  	Dir                   string
    49  	LazyCreate            bool
    50  	DisableVerifyHelm     bool
    51  	DisableVerifyPackages bool
    52  	LazyCreateFlag        string
    53  	Namespace             string
    54  	ProviderValuesDir     string
    55  	TestKanikoSecretData  string
    56  	TestVeleroSecretData  string
    57  	WorkloadIdentity      bool
    58  }
    59  
    60  // NewCmdStepVerifyPreInstall creates the `jx step verify pod` command
    61  func NewCmdStepVerifyPreInstall(commonOpts *opts.CommonOptions) *cobra.Command {
    62  
    63  	options := &StepVerifyPreInstallOptions{
    64  		StepVerifyOptions: StepVerifyOptions{
    65  			StepOptions: step.StepOptions{
    66  				CommonOptions: commonOpts,
    67  			},
    68  		},
    69  	}
    70  
    71  	cmd := &cobra.Command{
    72  		Use:     "preinstall",
    73  		Aliases: []string{"pre-install", "pre"},
    74  		Short:   "Verifies all of the cloud infrastructure is setup before we try to boot up a cluster via 'jx boot'",
    75  		Run: func(cmd *cobra.Command, args []string) {
    76  			options.Cmd = cmd
    77  			options.Args = args
    78  			err := options.Run()
    79  			helper.CheckErr(err)
    80  		},
    81  	}
    82  	cmd.Flags().BoolVarP(&options.Debug, "debug", "", false, "Output logs of any failed pod")
    83  	cmd.Flags().StringVarP(&options.Dir, "dir", "d", ".", "the directory to look for the install requirements file")
    84  	cmd.Flags().StringVarP(&options.LazyCreateFlag, "lazy-create", "", "", fmt.Sprintf("Specify true/false as to whether to lazily create missing resources. If not specified it is enabled if Terraform is not specified in the %s file", config.RequirementsConfigFileName))
    85  	cmd.Flags().StringVarP(&options.Namespace, "namespace", "", "", "the namespace that Jenkins X will be booted into. If not specified it defaults to $DEPLOY_NAMESPACE")
    86  	cmd.Flags().StringVarP(&options.ProviderValuesDir, "provider-values-dir", "", "", "The optional directory of kubernetes provider specific files")
    87  	cmd.Flags().BoolVarP(&options.WorkloadIdentity, "workload-identity", "", false, "Enable this if using GKE Workload Identity to avoid reconnecting to the Cluster.")
    88  	cmd.Flags().BoolVarP(&options.DisableVerifyPackages, "disable-verify-packages", "", false, "Disable packages verification, helpful when testing different package versions.")
    89  	cmd.Flags().BoolVarP(&options.DisableVerifyHelm, "disable-verify-helm", "", false, "Disable Helm verification, helpful when testing different Helm versions.")
    90  
    91  	return cmd
    92  }
    93  
    94  // Run implements this command
    95  func (o *StepVerifyPreInstallOptions) Run() error {
    96  	info := util.ColorInfo
    97  	requirements, requirementsFileName, err := config.LoadRequirementsConfig(o.Dir, config.DefaultFailOnValidationError)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	err = o.ConfigureCommonOptions(requirements)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	requirements, err = o.gatherRequirements(requirements, requirementsFileName)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	err = o.ValidateRequirements(requirements, requirementsFileName)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	o.LazyCreate, err = requirements.IsLazyCreateSecrets(o.LazyCreateFlag)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	// lets find the namespace to use
   123  	ns, err := o.GetDeployNamespace(o.Namespace)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	kubeClient, err := o.KubeClient()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	err = o.verifyTLS(requirements)
   133  	if err != nil {
   134  		return errors.WithStack(err)
   135  	}
   136  
   137  	o.SetDevNamespace(ns)
   138  
   139  	log.Logger().Infof("Verifying the kubernetes cluster before we try to boot Jenkins X in namespace: %s", info(ns))
   140  	if o.LazyCreate {
   141  		log.Logger().Infof("Trying to lazily create any missing resources to get the current cluster ready to boot Jenkins X")
   142  	} else {
   143  		log.Logger().Warn("Lazy create of cloud resources is disabled")
   144  	}
   145  
   146  	err = o.verifyDevNamespace(kubeClient, ns)
   147  	if err != nil {
   148  		if o.LazyCreate {
   149  			log.Logger().Infof("Attempting to lazily create the deploy namespace %s", info(ns))
   150  
   151  			err = kube.EnsureDevNamespaceCreatedWithoutEnvironment(kubeClient, ns)
   152  			if err != nil {
   153  				return errors.Wrapf(err, "failed to lazily create the namespace %s", ns)
   154  			}
   155  			// lets rerun the verify step to ensure its all sorted now
   156  			err = o.verifyDevNamespace(kubeClient, ns)
   157  			if err != nil {
   158  				return errors.Wrapf(err, "failed to verify the namespace %s", ns)
   159  			}
   160  		}
   161  	}
   162  
   163  	err = o.verifyIngress(requirements, requirementsFileName)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	no := &namespace.NamespaceOptions{}
   168  	no.CommonOptions = o.CommonOptions
   169  	no.Args = []string{ns}
   170  	err = no.Run()
   171  	if err != nil {
   172  		return err
   173  	}
   174  	log.Logger().Info("\n")
   175  
   176  	err = o.installMissingDependencies()
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	po := &StepVerifyPackagesOptions{}
   182  	po.CommonOptions = o.CommonOptions
   183  	po.Packages = []string{"kubectl", "git", "helm"}
   184  	po.Dir = o.Dir
   185  	if !o.DisableVerifyPackages {
   186  		err = po.Run()
   187  		if err != nil {
   188  			return err
   189  		}
   190  		log.Logger().Info("\n")
   191  	}
   192  
   193  	err = o.VerifyInstallConfig(kubeClient, ns, requirements, requirementsFileName)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	err = o.verifyStorage(requirements, requirementsFileName)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	log.Logger().Info("\n")
   202  	if !o.DisableVerifyHelm && !requirements.Helmfile {
   203  		err = o.verifyHelm(ns)
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  	if requirements.Kaniko {
   209  		if requirements.Cluster.Provider == cloud.GKE {
   210  			log.Logger().Infof("Validating Kaniko secret in namespace %s", info(ns))
   211  
   212  			err = o.validateKaniko(ns)
   213  			if err != nil {
   214  				if o.LazyCreate {
   215  					log.Logger().Infof("attempting to lazily create the deploy namespace %s", info(ns))
   216  
   217  					err = o.lazyCreateKanikoSecret(requirements, ns)
   218  					if err != nil {
   219  						return errors.Wrapf(err, "failed to lazily create the kaniko secret in: %s", ns)
   220  					}
   221  					// lets rerun the verify step to ensure its all sorted now
   222  					err = o.validateKaniko(ns)
   223  				}
   224  			}
   225  			if err != nil {
   226  				return err
   227  			}
   228  			log.Logger().Info("\n")
   229  		}
   230  	}
   231  
   232  	if vns := requirements.Velero.Namespace; vns != "" {
   233  		if requirements.Cluster.Provider == cloud.GKE {
   234  			log.Logger().Infof("Validating the velero secret in namespace %s", info(vns))
   235  
   236  			err = o.validateVelero(vns)
   237  			if err != nil {
   238  				if o.LazyCreate {
   239  					log.Logger().Infof("Attempting to lazily create the deploy namespace %s", info(vns))
   240  
   241  					err = o.lazyCreateVeleroSecret(requirements, vns)
   242  					if err != nil {
   243  						return errors.Wrapf(err, "failed to lazily create the velero secret in: %s", vns)
   244  					}
   245  					// lets rerun the verify step to ensure its all sorted now
   246  					err = o.validateVelero(vns)
   247  				}
   248  			}
   249  			if err != nil {
   250  				return err
   251  			}
   252  			log.Logger().Info("\n")
   253  		}
   254  	}
   255  
   256  	if requirements.Webhook == config.WebhookTypeLighthouse {
   257  		// we don't need the ConfigMaps for prow yet
   258  		err = o.verifyProwConfigMaps(kubeClient, ns)
   259  		if err != nil {
   260  			return err
   261  		}
   262  	}
   263  
   264  	if requirements.Cluster.Provider == cloud.EKS && o.LazyCreate {
   265  		if !cluster.IsInCluster() || os.Getenv("OVERRIDE_IRSA_IN_CLUSTER") == "true" {
   266  			log.Logger().Info("Attempting to lazily create the IAM Role for Service Accounts permissions")
   267  			err = amazon.EnableIRSASupportInCluster(requirements)
   268  			if err != nil {
   269  				return errors.Wrap(err, "error enabling IRSA in cluster")
   270  			}
   271  			err = amazon.CreateIRSAManagedServiceAccounts(requirements, o.ProviderValuesDir)
   272  			if err != nil {
   273  				return errors.Wrap(err, "error creating the IRSA managed Service Accounts")
   274  			}
   275  		} else {
   276  			log.Logger().Info("Running in cluster, not recreating permissions")
   277  		}
   278  	}
   279  
   280  	// Lets update the TeamSettings with the VersionStream data from the jx-requirements.yml file so we make sure
   281  	// we are upgrading with the latest versions
   282  	log.Logger().Infof("Cluster looks good, you are ready to '%s' now!", info("jx boot"))
   283  	fmt.Println()
   284  	return nil
   285  }
   286  
   287  // installMissingDependencies installs missing deps like helm and kubectl
   288  func (o *StepVerifyPreInstallOptions) installMissingDependencies() error {
   289  	var deps []string
   290  	deps = packages.AddRequiredBinary("kubectl", deps)
   291  	deps = packages.AddRequiredBinary("helm", deps)
   292  	return o.DoInstallMissingDependencies(deps)
   293  }
   294  
   295  // EnsureHelm ensures helm is installed
   296  func (o *StepVerifyPreInstallOptions) verifyHelm(ns string) error {
   297  	log.Logger().Debug("Verifying Helm...")
   298  	// lets make sure we don't try use tiller
   299  	o.EnableRemoteKubeCluster()
   300  	v, err := o.Helm().Version(false)
   301  	if err != nil {
   302  		err = o.InstallHelm()
   303  		if err != nil {
   304  			return errors.Wrap(err, "failed to install Helm")
   305  		}
   306  		v, err = o.Helm().Version(false)
   307  		if err != nil {
   308  			return errors.Wrap(err, "failed to get Helm version after install")
   309  		}
   310  	}
   311  	currVersion, err := semver.Make(v)
   312  	if err != nil {
   313  		return errors.Wrapf(err, "unable to parse semantic version %s", v)
   314  	}
   315  	noInitRequiredVersion := semver.MustParse("3.0.0")
   316  	if currVersion.LT(noInitRequiredVersion) {
   317  		cfg := opts.InitHelmConfig{
   318  			Namespace:       ns,
   319  			OnlyHelmClient:  true,
   320  			Helm3:           false,
   321  			SkipTiller:      true,
   322  			GlobalTiller:    false,
   323  			TillerNamespace: "",
   324  			TillerRole:      "",
   325  		}
   326  		err = o.InitHelm(cfg)
   327  		if err != nil {
   328  			return errors.Wrapf(err, "initializing helm with config: %v", cfg)
   329  		}
   330  	}
   331  
   332  	o.EnableRemoteKubeCluster()
   333  
   334  	_, err = o.AddHelmBinaryRepoIfMissing(kube.DefaultChartMuseumURL, kube.DefaultChartMuseumJxRepoName, "", "")
   335  	if err != nil {
   336  		return errors.Wrapf(err, "adding '%s' helm charts repository", kube.DefaultChartMuseumURL)
   337  	}
   338  	log.Logger().Infof("Ensuring Helm chart repository %s is configured\n", kube.DefaultChartMuseumURL)
   339  
   340  	return nil
   341  }
   342  
   343  func (o *StepVerifyPreInstallOptions) verifyDevNamespace(kubeClient kubernetes.Interface, ns string) error {
   344  	log.Logger().Debug("Verifying Dev Namespace...")
   345  	ns, envName, err := kube.GetDevNamespace(kubeClient, ns)
   346  	if err != nil {
   347  		return err
   348  	}
   349  	if ns == "" {
   350  		return fmt.Errorf("no dev namespace name found")
   351  	}
   352  	if envName == "" {
   353  		return fmt.Errorf("namespace %s has no team label", ns)
   354  	}
   355  	return nil
   356  }
   357  
   358  func (o *StepVerifyPreInstallOptions) lazyCreateKanikoSecret(requirements *config.RequirementsConfig, ns string) error {
   359  	log.Logger().Debugf("Lazily creating the kaniko secret")
   360  	io := &create.InstallOptions{}
   361  	io.CommonOptions = o.CommonOptions
   362  	io.Flags.Kaniko = true
   363  	io.Flags.Namespace = ns
   364  	io.Flags.Provider = requirements.Cluster.Provider
   365  	io.SetInstallValues(map[string]string{
   366  		kube.ClusterName: requirements.Cluster.ClusterName,
   367  		kube.ProjectID:   requirements.Cluster.ProjectID,
   368  	})
   369  	if o.TestKanikoSecretData != "" {
   370  		io.AdminSecretsService.Flags.KanikoSecret = o.TestKanikoSecretData
   371  	} else {
   372  		err := io.ConfigureKaniko()
   373  		if err != nil {
   374  			return err
   375  		}
   376  	}
   377  	data := io.AdminSecretsService.Flags.KanikoSecret
   378  	if data == "" {
   379  		return fmt.Errorf("failed to create the kaniko secret data")
   380  	}
   381  	return o.createSecret(ns, kube.SecretKaniko, kube.SecretKaniko, data)
   382  }
   383  
   384  func (o *StepVerifyPreInstallOptions) lazyCreateVeleroSecret(requirements *config.RequirementsConfig, ns string) error {
   385  	log.Logger().Debugf("Lazily creating the velero secret")
   386  	var data string
   387  	var err error
   388  	if o.TestVeleroSecretData != "" {
   389  		data = o.TestVeleroSecretData
   390  	} else {
   391  		data, err = o.configureVelero(requirements)
   392  		if err != nil {
   393  			return errors.Wrap(err, "failed to create the velero secret data")
   394  		}
   395  	}
   396  	if data == "" {
   397  		return nil
   398  	}
   399  	return o.createSecret(ns, kube.SecretVelero, "cloud", data)
   400  }
   401  
   402  // ConfigureVelero configures the velero SA and secret
   403  func (o *StepVerifyPreInstallOptions) configureVelero(requirements *config.RequirementsConfig) (string, error) {
   404  	if requirements.Cluster.Provider != cloud.GKE {
   405  		log.Logger().Infof("we are assuming your IAM roles are setup so that Velero has cluster-admin\n")
   406  		return "", nil
   407  	}
   408  
   409  	serviceAccountDir, err := ioutil.TempDir("", "gke")
   410  	if err != nil {
   411  		return "", errors.Wrap(err, "creating a temporary folder where the service account will be stored")
   412  	}
   413  	defer os.RemoveAll(serviceAccountDir)
   414  
   415  	clusterName := requirements.Cluster.ClusterName
   416  	projectID := requirements.Cluster.ProjectID
   417  	if projectID == "" || clusterName == "" {
   418  		if kubeClient, ns, err := o.KubeClientAndDevNamespace(); err == nil {
   419  			if data, err := kube.ReadInstallValues(kubeClient, ns); err == nil && data != nil {
   420  				if projectID == "" {
   421  					projectID = data[kube.ProjectID]
   422  				}
   423  				if clusterName == "" {
   424  					clusterName = data[kube.ClusterName]
   425  				}
   426  			}
   427  		}
   428  	}
   429  	if projectID == "" {
   430  		projectID, err = o.GetGoogleProjectID("")
   431  		if err != nil {
   432  			return "", errors.Wrap(err, "getting the GCP project ID")
   433  		}
   434  		requirements.Cluster.ProjectID = projectID
   435  	}
   436  	if clusterName == "" {
   437  		clusterName, err = o.GetGKEClusterNameFromContext()
   438  		if err != nil {
   439  			return "", errors.Wrap(err, "gettting the GKE cluster name from current context")
   440  		}
   441  		requirements.Cluster.ClusterName = clusterName
   442  	}
   443  
   444  	serviceAccountName := requirements.Velero.ServiceAccount
   445  	if serviceAccountName == "" {
   446  		serviceAccountName = naming.ToValidNameTruncated(fmt.Sprintf("%s-vo", clusterName), 30)
   447  		requirements.Velero.ServiceAccount = serviceAccountName
   448  	}
   449  	log.Logger().Infof("Configuring Velero service account %s for project %s", util.ColorInfo(serviceAccountName), util.ColorInfo(projectID))
   450  	serviceAccountPath, err := o.GCloud().GetOrCreateServiceAccount(serviceAccountName, projectID, serviceAccountDir, gke.VeleroServiceAccountRoles)
   451  	if err != nil {
   452  		return "", errors.Wrap(err, "creating the service account")
   453  	}
   454  
   455  	bucket := requirements.Storage.Backup.URL
   456  	if bucket == "" {
   457  		return "", fmt.Errorf("missing requirements.storage.backup.url")
   458  	}
   459  	err = o.GCloud().ConfigureBucketRoles(projectID, serviceAccountName, bucket, gke.VeleroServiceAccountRoles)
   460  	if err != nil {
   461  		return "", errors.Wrap(err, "associate the IAM roles to the bucket")
   462  	}
   463  
   464  	serviceAccount, err := ioutil.ReadFile(serviceAccountPath)
   465  	if err != nil {
   466  		return "", errors.Wrapf(err, "reading the service account from file '%s'", serviceAccountPath)
   467  	}
   468  	return string(serviceAccount), nil
   469  }
   470  
   471  // VerifyInstallConfig lets ensure we modify the install ConfigMap with the requirements
   472  func (o *StepVerifyPreInstallOptions) VerifyInstallConfig(kubeClient kubernetes.Interface, ns string, requirements *config.RequirementsConfig, requirementsFileName string) error {
   473  	log.Logger().Debug("Verifying Install Config...")
   474  	_, err := kube.DefaultModifyConfigMap(kubeClient, ns, kube.ConfigMapNameJXInstallConfig,
   475  		func(configMap *corev1.ConfigMap) error {
   476  			secretsLocation := string(secrets.FileSystemLocationKind)
   477  			if requirements.SecretStorage == config.SecretStorageTypeVault {
   478  				secretsLocation = string(secrets.VaultLocationKind)
   479  			}
   480  			modifyMapIfNotBlank(configMap.Data, kube.KubeProvider, requirements.Cluster.Provider)
   481  			modifyMapIfNotBlank(configMap.Data, kube.ProjectID, requirements.Cluster.ProjectID)
   482  			modifyMapIfNotBlank(configMap.Data, kube.ClusterName, requirements.Cluster.ClusterName)
   483  			modifyMapIfNotBlank(configMap.Data, secrets.SecretsLocationKey, secretsLocation)
   484  			modifyMapIfNotBlank(configMap.Data, kube.Region, requirements.Cluster.Region)
   485  			modifyMapIfNotBlank(configMap.Data, kube.Zone, requirements.Cluster.Zone)
   486  			return nil
   487  		}, nil)
   488  	if err != nil {
   489  		return errors.Wrapf(err, "saving secrets location in ConfigMap %s in namespace %s", kube.ConfigMapNameJXInstallConfig, ns)
   490  	}
   491  	return nil
   492  }
   493  
   494  // gatherRequirements gathers cluster requirements and connects to the cluster if required
   495  func (o *StepVerifyPreInstallOptions) gatherRequirements(requirements *config.RequirementsConfig, requirementsFileName string) (*config.RequirementsConfig, error) {
   496  	log.Logger().Debug("Gathering Requirements...")
   497  	if o.BatchMode {
   498  		msg := "please specify '%s' in jx-requirements when running  in  batch mode"
   499  		if requirements.Cluster.Provider == "" {
   500  			return nil, errors.Errorf(msg, "provider")
   501  		}
   502  		if requirements.Cluster.Provider == cloud.EKS || requirements.Cluster.Provider == cloud.AWS {
   503  			if requirements.Cluster.Region == "" {
   504  				return nil, errors.Errorf(msg, "region")
   505  			}
   506  		}
   507  		if requirements.Cluster.Provider == cloud.GKE {
   508  			if requirements.Cluster.ProjectID == "" {
   509  				return nil, errors.Errorf(msg, "project")
   510  			}
   511  			if requirements.Cluster.Zone == "" {
   512  				return nil, errors.Errorf(msg, "zone")
   513  			}
   514  		}
   515  		if requirements.Cluster.EnvironmentGitOwner == "" {
   516  			return nil, errors.Errorf(msg, "environmentGitOwner")
   517  		}
   518  		if requirements.Cluster.ClusterName == "" {
   519  			return nil, errors.Errorf(msg, "clusterName")
   520  		}
   521  	}
   522  	var err error
   523  	if requirements.Cluster.Provider == "" {
   524  		requirements.Cluster.Provider, err = util.PickName(cloud.KubernetesProviders, "Select Kubernetes provider", "the type of Kubernetes installation", o.GetIOFileHandles())
   525  		if err != nil {
   526  			return nil, errors.Wrap(err, "selecting Kubernetes provider")
   527  		}
   528  	}
   529  
   530  	if requirements.Cluster.Provider != cloud.GKE && requirements.Cluster.Provider != cloud.EKS {
   531  		// lets check we want to try installation as we've only tested on GKE at the moment
   532  		if answer, err := o.showProvideFeedbackMessage(); err != nil {
   533  			return requirements, err
   534  		} else if !answer {
   535  			return requirements, errors.New("finishing execution")
   536  		}
   537  	}
   538  
   539  	if requirements.Cluster.Provider == cloud.GKE {
   540  		var currentProject, currentZone, currentClusterName string
   541  		autoAcceptDefaults := false
   542  		if requirements.Cluster.ProjectID == "" || requirements.Cluster.Zone == "" || requirements.Cluster.ClusterName == "" {
   543  			kubeConfig, _, err := o.Kube().LoadConfig()
   544  			if err != nil {
   545  				return nil, errors.Wrapf(err, "loading kubeconfig")
   546  			}
   547  			context := kube.Cluster(kubeConfig)
   548  			currentProject, currentZone, currentClusterName, err = gke.ParseContext(context)
   549  			if err != nil {
   550  				return nil, errors.Wrapf(err, "")
   551  			}
   552  			if currentClusterName != "" && currentProject != "" && currentZone != "" {
   553  				log.Logger().Infof("Currently connected cluster is %s in %s in project %s", util.ColorInfo(currentClusterName), util.ColorInfo(currentZone), util.ColorInfo(currentProject))
   554  				autoAcceptDefaults, err = util.Confirm(fmt.Sprintf("Do you want to jx boot the %s cluster?", util.ColorInfo(currentClusterName)), true, "Enter Y to use the currently connected cluster or enter N to specify a different cluster", o.GetIOFileHandles())
   555  				if err != nil {
   556  					return nil, err
   557  				}
   558  			} else {
   559  				log.Logger().Infof("Enter the cluster you want to jx boot")
   560  			}
   561  		}
   562  
   563  		if requirements.Cluster.ProjectID == "" {
   564  			if autoAcceptDefaults && currentProject != "" {
   565  				requirements.Cluster.ProjectID = currentProject
   566  			} else {
   567  				requirements.Cluster.ProjectID, err = o.GetGoogleProjectID(currentProject)
   568  				if err != nil {
   569  					return nil, errors.Wrap(err, "getting project ID")
   570  				}
   571  			}
   572  		}
   573  		if requirements.Cluster.Zone == "" {
   574  			if autoAcceptDefaults && currentZone != "" {
   575  				requirements.Cluster.Zone = currentZone
   576  			} else {
   577  				requirements.Cluster.Zone, err = o.GetGoogleZone(requirements.Cluster.ProjectID, currentZone)
   578  				if err != nil {
   579  					return nil, errors.Wrap(err, "getting GKE Zone")
   580  				}
   581  			}
   582  		}
   583  		if requirements.Cluster.ClusterName == "" {
   584  			if autoAcceptDefaults && currentClusterName != "" {
   585  				requirements.Cluster.ClusterName = currentClusterName
   586  			} else {
   587  				requirements.Cluster.ClusterName, err = util.PickValue("Cluster name", currentClusterName, true,
   588  					"The name for your cluster", o.GetIOFileHandles())
   589  				if err != nil {
   590  					return nil, errors.Wrap(err, "getting cluster name")
   591  				}
   592  				if requirements.Cluster.ClusterName == "" {
   593  					return nil, errors.Errorf("no cluster name provided")
   594  				}
   595  			}
   596  		}
   597  		if !autoAcceptDefaults {
   598  			if !o.WorkloadIdentity && !o.InCluster() {
   599  				// connect to the specified cluster if different from the currently connected one
   600  				log.Logger().Infof("Connecting to cluster %s", util.ColorInfo(requirements.Cluster.ClusterName))
   601  				err = o.GCloud().ConnectToCluster(requirements.Cluster.ProjectID, requirements.Cluster.Zone, requirements.Cluster.ClusterName)
   602  				if err != nil {
   603  					return nil, err
   604  				}
   605  			} else {
   606  				log.Logger().Info("no need to reconnect to cluster")
   607  			}
   608  		}
   609  	} else if requirements.Cluster.Provider == cloud.EKS || requirements.Cluster.Provider == cloud.AWS {
   610  		var currentRegion, currentClusterName string
   611  		var autoAcceptDefaults bool
   612  		if requirements.Cluster.Region == "" || requirements.Cluster.ClusterName == "" {
   613  			currentClusterName, currentRegion, err = session.GetCurrentlyConnectedRegionAndClusterName()
   614  			if err != nil {
   615  				return requirements, errors.Wrap(err, "there was a problem obtaining the current cluster name and region")
   616  			}
   617  			if currentClusterName != "" && currentRegion != "" {
   618  				log.Logger().Infof("")
   619  				log.Logger().Infof("Currently connected cluster is %s in region %s", util.ColorInfo(currentClusterName), util.ColorInfo(currentRegion))
   620  				autoAcceptDefaults, err = util.Confirm(fmt.Sprintf("Do you want to jx boot the %s cluster?", util.ColorInfo(currentClusterName)), true, "Enter Y to use the currently connected cluster or enter N to specify a different cluster", o.GetIOFileHandles())
   621  				if err != nil {
   622  					return nil, err
   623  				}
   624  			} else {
   625  				log.Logger().Infof("Enter the cluster you want to jx boot")
   626  			}
   627  		}
   628  
   629  		if requirements.Cluster.Region == "" {
   630  			if autoAcceptDefaults && currentRegion != "" {
   631  				requirements.Cluster.Region = currentRegion
   632  			}
   633  		}
   634  		if requirements.Cluster.ClusterName == "" {
   635  			if autoAcceptDefaults && currentClusterName != "" {
   636  				requirements.Cluster.ClusterName = currentClusterName
   637  			} else {
   638  				requirements.Cluster.ClusterName, err = util.PickValue("Cluster name", currentClusterName, true,
   639  					"The name for your cluster", o.GetIOFileHandles())
   640  				if err != nil {
   641  					return nil, errors.Wrap(err, "getting cluster name")
   642  				}
   643  			}
   644  		}
   645  	}
   646  
   647  	if requirements.Cluster.ClusterName == "" && !o.BatchMode {
   648  		requirements.Cluster.ClusterName, err = util.PickValue("Cluster name", "", true,
   649  			"The name for your cluster", o.GetIOFileHandles())
   650  		if err != nil {
   651  			return nil, errors.Wrap(err, "getting cluster name")
   652  		}
   653  		if requirements.Cluster.ClusterName == "" {
   654  			return nil, errors.Errorf("no cluster name provided")
   655  		}
   656  	}
   657  
   658  	requirements.Cluster.Provider = strings.TrimSpace(strings.ToLower(requirements.Cluster.Provider))
   659  	requirements.Cluster.ProjectID = strings.TrimSpace(requirements.Cluster.ProjectID)
   660  	requirements.Cluster.Zone = strings.TrimSpace(strings.ToLower(requirements.Cluster.Zone))
   661  	requirements.Cluster.Region = strings.TrimSpace(strings.ToLower(requirements.Cluster.Region))
   662  	requirements.Cluster.ClusterName = strings.TrimSpace(strings.ToLower(requirements.Cluster.ClusterName))
   663  
   664  	err = o.gatherGitRequirements(requirements)
   665  	if err != nil {
   666  		return nil, errors.Wrap(err, "error gathering git requirements")
   667  	}
   668  
   669  	// Lock the version stream to a tag
   670  	if requirements.VersionStream.Ref == "" {
   671  		requirements.VersionStream.Ref = os.Getenv(boot.VersionsRepoBaseRefEnvVarName)
   672  	}
   673  	if requirements.VersionStream.URL == "" {
   674  		requirements.VersionStream.URL = os.Getenv(boot.VersionsRepoURLEnvVarName)
   675  	}
   676  
   677  	// attempt to resolve the version stream ref to a tag
   678  	_, ref, err := o.CloneJXVersionsRepo(requirements.VersionStream.URL, requirements.VersionStream.Ref)
   679  	if err != nil {
   680  		return nil, errors.Wrapf(err, "resolving version stream ref")
   681  	}
   682  	if ref != "" && ref != requirements.VersionStream.Ref {
   683  		log.Logger().Infof("Locking version stream %s to release %s. Jenkins X will use this release rather than %s to resolve all versions from now on.", util.ColorInfo(requirements.VersionStream.URL), util.ColorInfo(ref), requirements.VersionStream.Ref)
   684  		requirements.VersionStream.Ref = ref
   685  	}
   686  
   687  	err = o.SaveConfig(requirements, requirementsFileName)
   688  	if err != nil {
   689  		return nil, errors.Wrap(err, "error saving requirements file")
   690  	}
   691  
   692  	err = o.writeOwnersFile(requirements)
   693  	if err != nil {
   694  		return nil, errors.Wrapf(err, "writing approvers to OWNERS file in %s", o.Dir)
   695  	}
   696  
   697  	return requirements, nil
   698  }
   699  
   700  func (o *StepVerifyPreInstallOptions) writeOwnersFile(requirements *config.RequirementsConfig) error {
   701  	if len(requirements.Cluster.DevEnvApprovers) > 0 {
   702  		path := filepath.Join(o.Dir, "OWNERS")
   703  		filename, err := filepath.Abs(path)
   704  		if err != nil {
   705  			return errors.Wrapf(err, "failed to resolve path %s", path)
   706  		}
   707  		data := prow.Owners{}
   708  		for _, approver := range requirements.Cluster.DevEnvApprovers {
   709  			data.Approvers = append(data.Approvers, approver)
   710  			data.Reviewers = append(data.Reviewers, approver)
   711  		}
   712  		ownersYaml, err := yaml.Marshal(data)
   713  		if err != nil {
   714  			return err
   715  		}
   716  		err = ioutil.WriteFile(filename, ownersYaml, 0600)
   717  		if err != nil {
   718  			return err
   719  		}
   720  		log.Logger().Infof("writing the following to the OWNERS file for the development environment repository:\n%s", string(ownersYaml))
   721  	}
   722  	return nil
   723  }
   724  
   725  func (o *StepVerifyPreInstallOptions) gatherGitRequirements(requirements *config.RequirementsConfig) error {
   726  	requirements.Cluster.EnvironmentGitOwner = strings.TrimSpace(requirements.Cluster.EnvironmentGitOwner)
   727  
   728  	// lets fix up any missing or incorrect git kinds for public git servers
   729  	if gits.IsGitHubServerURL(requirements.Cluster.GitServer) {
   730  		requirements.Cluster.GitKind = "github"
   731  	} else if gits.IsGitLabServerURL(requirements.Cluster.GitServer) {
   732  		requirements.Cluster.GitKind = "gitlab"
   733  	}
   734  
   735  	var err error
   736  	if requirements.Cluster.EnvironmentGitOwner == "" {
   737  		requirements.Cluster.EnvironmentGitOwner, err = util.PickValue(
   738  			"Git Owner name for environment repositories",
   739  			"",
   740  			true,
   741  			"Jenkins X leverages GitOps to track and control what gets deployed into environments.  "+
   742  				"This requires a Git repository per environment. "+
   743  				"This question is asking for the Git Owner where these repositories will live.",
   744  			o.GetIOFileHandles())
   745  		if err != nil {
   746  			return errors.Wrap(err, "error configuring git owner for env repositories")
   747  		}
   748  
   749  		if requirements.Cluster.EnvironmentGitPublic {
   750  			log.Logger().Infof("Environment repos will be %s, if you want to create %s environment repos, please set %s to %s jx-requirements.yml", util.ColorInfo("public"), util.ColorInfo("private"), util.ColorInfo("environmentGitPublic"), util.ColorInfo("false"))
   751  		} else {
   752  			log.Logger().Infof("Environment repos will be %s, if you want to create %s environment repos, please set %s to %s in jx-requirements.yml", util.ColorInfo("private"), util.ColorInfo("public"), util.ColorInfo("environmentGitPublic"), util.ColorInfo("true"))
   753  		}
   754  	}
   755  	if len(requirements.Cluster.DevEnvApprovers) == 0 && !o.BatchMode {
   756  		approversString, err := util.PickValue(
   757  			"Comma-separated git provider usernames of approvers for development environment repository",
   758  			"",
   759  			true,
   760  			"Pull requests to the development environment repository require approval by one or more "+
   761  				"users, specified in the 'OWNERS' file in the repository. Please specify a comma-separated "+
   762  				"list of usernames for your Git provider to be used as approvers.",
   763  			o.GetIOFileHandles())
   764  		if err != nil {
   765  			return errors.Wrap(err, "configuring approvers for development environment repository")
   766  		}
   767  		for _, a := range strings.Split(approversString, ",") {
   768  			requirements.Cluster.DevEnvApprovers = append(requirements.Cluster.DevEnvApprovers, strings.TrimSpace(a))
   769  		}
   770  	}
   771  	return nil
   772  }
   773  
   774  // verifyStorage verifies the associated buckets exist or if enabled lazily create them
   775  func (o *StepVerifyPreInstallOptions) verifyStorage(requirements *config.RequirementsConfig, requirementsFileName string) error {
   776  	log.Logger().Info("Verifying Storage...")
   777  	storage := &requirements.Storage
   778  	err := o.verifyStorageEntry(requirements, requirementsFileName, &storage.Logs, "logs", "Long term log storage")
   779  	if err != nil {
   780  		return err
   781  	}
   782  	err = o.verifyStorageEntry(requirements, requirementsFileName, &storage.Reports, "reports", "Long term report storage")
   783  	if err != nil {
   784  		return err
   785  	}
   786  	err = o.verifyStorageEntry(requirements, requirementsFileName, &storage.Repository, "repository", "Chart repository")
   787  	if err != nil {
   788  		return err
   789  	}
   790  	err = o.verifyStorageEntry(requirements, requirementsFileName, &storage.Backup, "backup", "backup storage")
   791  	if err != nil {
   792  		return err
   793  	}
   794  	log.Logger().Infof("Storage configuration looks good\n")
   795  	return nil
   796  }
   797  
   798  func (o *StepVerifyPreInstallOptions) verifyTLS(requirements *config.RequirementsConfig) error {
   799  	if !requirements.Ingress.TLS.Enabled {
   800  		confirm := false
   801  		// an existing Vault URL indicated an external Vault instance in which case the following warning is not necessary
   802  		if requirements.SecretStorage == config.SecretStorageTypeVault && requirements.Vault.URL == "" {
   803  			log.Logger().Warnf("Vault is enabled and TLS is not. This means your secrets will be sent to and from your cluster in the clear. See %s for more information", config.TLSDocURL)
   804  			confirm = true
   805  		}
   806  		if requirements.Webhook != config.WebhookTypeNone {
   807  			log.Logger().Warnf("TLS is not enabled so your webhooks will be called using HTTP. This means your webhook secret will be sent to your cluster in the clear. See %s for more information", config.TLSDocURL)
   808  			confirm = true
   809  		}
   810  		if os.Getenv(boot.OverrideTLSWarningEnvVarName) == "true" {
   811  			confirm = false
   812  		}
   813  		if confirm && !o.BatchMode {
   814  			message := fmt.Sprintf("Do you wish to continue?")
   815  			help := fmt.Sprintf("Jenkins X needs TLS enabled to send secrets securely. We strongly recommend enabling TLS.")
   816  			if answer, err := util.Confirm(message, false, help, o.GetIOFileHandles()); err != nil {
   817  				return err
   818  			} else if !answer {
   819  				return errors.Errorf("cannot continue because TLS is not enabled.")
   820  			}
   821  		}
   822  	}
   823  	return nil
   824  }
   825  
   826  func (o *StepVerifyPreInstallOptions) verifyStorageEntry(requirements *config.RequirementsConfig, requirementsFileName string, storageEntryConfig *config.StorageEntryConfig, name string, text string) error {
   827  	kubeProvider := requirements.Cluster.Provider
   828  	if !storageEntryConfig.Enabled {
   829  		if requirements.IsCloudProvider() {
   830  			log.Logger().Warnf("Your requirements have not enabled cloud storage for %s - we recommend enabling this for kubernetes provider %s", name, kubeProvider)
   831  		}
   832  		return nil
   833  	}
   834  
   835  	provider := factory.NewBucketProvider(requirements)
   836  
   837  	if storageEntryConfig.URL == "" {
   838  		// lets allow the storage bucket to be entered or created
   839  		if o.BatchMode {
   840  			log.Logger().Warnf("No URL provided for storage: %s", name)
   841  			return nil
   842  		}
   843  		scheme := buckets.KubeProviderToBucketScheme(kubeProvider)
   844  		if scheme == "" {
   845  			scheme = "s3"
   846  		}
   847  		message := fmt.Sprintf("%s bucket URL. Press enter to create and use a new bucket", text)
   848  		help := fmt.Sprintf("please enter the URL of the bucket to use for storage using the format %s://<bucket-name>", scheme)
   849  		value, err := util.PickValue(message, "", false, help, o.GetIOFileHandles())
   850  		if err != nil {
   851  			return errors.Wrapf(err, "failed to pick storage bucket for %s", name)
   852  		}
   853  
   854  		if value == "" {
   855  			if provider == nil {
   856  				log.Logger().Warnf("the kubernetes provider %s has no BucketProvider in jx yet so we cannot lazily create buckets", kubeProvider)
   857  				log.Logger().Warnf("long term storage for %s will be disabled until you provide an existing bucket URL", name)
   858  				return nil
   859  			}
   860  			safeClusterName := naming.ToValidName(requirements.Cluster.ClusterName)
   861  			safeName := naming.ToValidName(name)
   862  			value, err = provider.CreateNewBucketForCluster(safeClusterName, safeName)
   863  			if err != nil {
   864  				return errors.Wrapf(err, "failed to create a dynamic bucket for cluster %s and name %s", safeClusterName, safeName)
   865  			}
   866  		}
   867  		if value != "" {
   868  			storageEntryConfig.URL = value
   869  
   870  			err = o.SaveConfig(requirements, requirementsFileName)
   871  			if err != nil {
   872  				return errors.Wrapf(err, "failed to save changes to file: %s", requirementsFileName)
   873  			}
   874  		}
   875  	}
   876  
   877  	if storageEntryConfig.URL != "" {
   878  		if provider == nil {
   879  			log.Logger().Warnf("the kubernetes provider %s has no BucketProvider in jx yet - so you have to manually setup and verify your bucket URLs exist", kubeProvider)
   880  			log.Logger().Infof("please verify this bucket exists: %s", util.ColorInfo(storageEntryConfig.URL))
   881  			return nil
   882  		}
   883  
   884  		err := provider.EnsureBucketIsCreated(storageEntryConfig.URL)
   885  		if err != nil {
   886  			return errors.Wrapf(err, "failed to ensure the bucket URL %s is created", storageEntryConfig.URL)
   887  		}
   888  	}
   889  	return nil
   890  }
   891  
   892  func (o *StepVerifyPreInstallOptions) verifyProwConfigMaps(kubeClient kubernetes.Interface, ns string) error {
   893  	err := o.verifyConfigMapExists(kubeClient, ns, "config", "config.yaml", "pod_namespace: jx")
   894  	if err != nil {
   895  		return err
   896  	}
   897  	return o.verifyConfigMapExists(kubeClient, ns, "plugins", "plugins.yaml", "cat: {}")
   898  }
   899  
   900  func (o *StepVerifyPreInstallOptions) verifyConfigMapExists(kubeClient kubernetes.Interface, ns string, name string, key string, defaultValue string) error {
   901  	info := util.ColorInfo
   902  	configMapInterface := kubeClient.CoreV1().ConfigMaps(ns)
   903  	cm, err := configMapInterface.Get(name, metav1.GetOptions{})
   904  	if err != nil {
   905  		// lets try create it
   906  		cm = &corev1.ConfigMap{
   907  			ObjectMeta: metav1.ObjectMeta{
   908  				Name: name,
   909  			},
   910  			Data: map[string]string{
   911  				key: defaultValue,
   912  			},
   913  		}
   914  		cm, err = configMapInterface.Create(cm)
   915  		if err != nil {
   916  			// maybe someone else just created it - lets try one more time
   917  			cm2, err2 := configMapInterface.Get(name, metav1.GetOptions{})
   918  			if err == nil {
   919  				log.Logger().Infof("created ConfigMap %s in namespace %s", info(name), info(ns))
   920  			}
   921  			if err2 != nil {
   922  				return fmt.Errorf("failed to create the ConfigMap %s in namespace %s due to: %s - we cannot get it either: %s", name, ns, err.Error(), err2.Error())
   923  			}
   924  			cm = cm2
   925  			err = nil
   926  		}
   927  	}
   928  	if err != nil {
   929  		return err
   930  	}
   931  
   932  	// lets verify that there is an entry
   933  	if cm.Data == nil {
   934  		cm.Data = map[string]string{}
   935  	}
   936  	_, ok := cm.Data[key]
   937  	if !ok {
   938  		cm.Data[key] = defaultValue
   939  		cm.Name = name
   940  
   941  		_, err = configMapInterface.Update(cm)
   942  		if err != nil {
   943  			return fmt.Errorf("failed to update the ConfigMap %s in namespace %s to add key %s due to: %s", name, ns, key, err.Error())
   944  		}
   945  	}
   946  	log.Logger().Infof("verified there is a ConfigMap %s in namespace %s", info(name), info(ns))
   947  	return nil
   948  }
   949  
   950  func (o *StepVerifyPreInstallOptions) verifyIngress(requirements *config.RequirementsConfig, requirementsFileName string) error {
   951  	log.Logger().Info("Verifying Ingress...")
   952  	domain := requirements.Ingress.Domain
   953  	if requirements.Ingress.IsAutoDNSDomain() && !requirements.Ingress.IgnoreLoadBalancer {
   954  		log.Logger().Infof("Clearing the domain %s as when using auto-DNS domains we need to regenerate to ensure its always accurate in case the cluster or ingress service is recreated", util.ColorInfo(domain))
   955  		requirements.Ingress.Domain = ""
   956  		err := o.SaveConfig(requirements, requirementsFileName)
   957  		if err != nil {
   958  			return errors.Wrapf(err, "failed to save changes to file: %s", requirementsFileName)
   959  		}
   960  	}
   961  	log.Logger().Info("\n")
   962  	return nil
   963  }
   964  
   965  // ValidateRequirements validate the requirements; e.g. the webhook and git provider
   966  func (o *StepVerifyPreInstallOptions) ValidateRequirements(requirements *config.RequirementsConfig, fileName string) error {
   967  	if requirements.Webhook == config.WebhookTypeProw {
   968  		kind := requirements.Cluster.GitKind
   969  		server := requirements.Cluster.GitServer
   970  		if (kind != "" && kind != "github") || (server != "" && !gits.IsGitHubServerURL(server)) {
   971  			return fmt.Errorf("invalid requirements in file %s cannot use prow as a webhook for git kind: %s server: %s. Please try using lighthouse instead", fileName, kind, server)
   972  		}
   973  	}
   974  	if requirements.Repository == config.RepositoryTypeBucketRepo && requirements.Cluster.ChartRepository == "" {
   975  		requirements.Cluster.ChartRepository = "http://bucketrepo/bucketrepo/charts/"
   976  		err := o.SaveConfig(requirements, fileName)
   977  		if err != nil {
   978  			return errors.Wrapf(err, "failed to save changes to file: %s", fileName)
   979  		}
   980  	}
   981  
   982  	// lets verify that we have a repository name defined for every environment
   983  	modified := false
   984  	for i, env := range requirements.Environments {
   985  		if env.Repository == "" {
   986  			clusterName := requirements.Cluster.ClusterName
   987  			if clusterName != "" {
   988  				clusterName = clusterName + "-"
   989  			}
   990  			repoName := "environment-" + clusterName + env.Key
   991  			requirements.Environments[i].Repository = naming.ToValidName(repoName)
   992  			modified = true
   993  		}
   994  	}
   995  	if modified {
   996  		err := o.SaveConfig(requirements, fileName)
   997  		if err != nil {
   998  			return errors.Wrapf(err, "failed to save changes to file: %s", fileName)
   999  		}
  1000  	}
  1001  	o.showPermissionsModeMessage(requirements)
  1002  	return nil
  1003  }
  1004  
  1005  // SaveConfig saves the configuration file to the given project directory
  1006  func (o *StepVerifyPreInstallOptions) SaveConfig(c *config.RequirementsConfig, fileName string) error {
  1007  	data, err := yaml.Marshal(c)
  1008  	if err != nil {
  1009  		return err
  1010  	}
  1011  	err = ioutil.WriteFile(fileName, data, util.DefaultWritePermissions)
  1012  	if err != nil {
  1013  		return errors.Wrapf(err, "failed to save file %s", fileName)
  1014  	}
  1015  
  1016  	if c.Helmfile {
  1017  		y := config.RequirementsValues{
  1018  			RequirementsConfig: c,
  1019  		}
  1020  		data, err = yaml.Marshal(y)
  1021  		if err != nil {
  1022  			return err
  1023  		}
  1024  
  1025  		err = ioutil.WriteFile(path.Join(path.Dir(fileName), config.RequirementsValuesFileName), data, util.DefaultWritePermissions)
  1026  		if err != nil {
  1027  			return errors.Wrapf(err, "failed to save file %s", config.RequirementsValuesFileName)
  1028  		}
  1029  	}
  1030  	return nil
  1031  }
  1032  
  1033  func modifyMapIfNotBlank(m map[string]string, key string, value string) {
  1034  	if m != nil {
  1035  		if value != "" {
  1036  			m[key] = value
  1037  		} else {
  1038  			log.Logger().Debugf("Cannot update key %s, value is nil", key)
  1039  		}
  1040  	} else {
  1041  		log.Logger().Debugf("Cannot update key %s, map is nil", key)
  1042  	}
  1043  }
  1044  
  1045  func (o *StepVerifyPreInstallOptions) showProvideFeedbackMessage() (bool, error) {
  1046  	log.Logger().Info("jx boot has only been validated on GKE and EKS, we'd love feedback and contributions for other Kubernetes providers")
  1047  	if !o.BatchMode {
  1048  		return util.Confirm("Continue execution anyway?",
  1049  			true, "", o.GetIOFileHandles())
  1050  	}
  1051  	log.Logger().Info("Running in Batch Mode, execution will continue")
  1052  	return true, nil
  1053  }
  1054  
  1055  func (o *StepVerifyPreInstallOptions) showPermissionsModeMessage(requirementsConfig *config.RequirementsConfig) {
  1056  	if requirementsConfig.Cluster.StrictPermissions && requirementsConfig.Cluster.Provider != cloud.OPENSHIFT {
  1057  		log.Logger().Info(`The provided requirements file has 'strictPermissions' enabled but 'provider' is not Openshift.
  1058  This feature is only supported on Openshift`)
  1059  	}
  1060  }