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

     1  package initcmd
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/olli-ai/jx/v2/pkg/versionstream"
    10  
    11  	"github.com/olli-ai/jx/v2/pkg/cmd/helper"
    12  	"github.com/olli-ai/jx/v2/pkg/kube/naming"
    13  	survey "gopkg.in/AlecAivazis/survey.v1"
    14  
    15  	"github.com/olli-ai/jx/v2/pkg/cloud"
    16  	"github.com/olli-ai/jx/v2/pkg/kube/services"
    17  
    18  	"github.com/jenkins-x/jx-logging/pkg/log"
    19  	"github.com/olli-ai/jx/v2/pkg/cloud/iks"
    20  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    21  	"github.com/olli-ai/jx/v2/pkg/cmd/templates"
    22  	"github.com/olli-ai/jx/v2/pkg/helm"
    23  	"github.com/olli-ai/jx/v2/pkg/kube"
    24  	"github.com/olli-ai/jx/v2/pkg/util"
    25  	"github.com/pkg/errors"
    26  	"github.com/spf13/cobra"
    27  	rbacv1 "k8s.io/api/rbac/v1"
    28  
    29  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  )
    32  
    33  // InitOptions the options for running init
    34  type InitOptions struct {
    35  	*opts.CommonOptions
    36  	Client clientset.Clientset
    37  	Flags  InitFlags
    38  }
    39  
    40  // InitFlags the flags for running init
    41  type InitFlags struct {
    42  	Domain                     string
    43  	Provider                   string
    44  	Namespace                  string
    45  	UserClusterRole            string
    46  	TillerClusterRole          string
    47  	IngressClusterRole         string
    48  	TillerNamespace            string
    49  	IngressNamespace           string
    50  	IngressService             string
    51  	IngressDeployment          string
    52  	ExternalIP                 string
    53  	VersionsRepository         string
    54  	VersionsGitRef             string
    55  	DraftClient                bool
    56  	HelmClient                 bool
    57  	Helm3                      bool
    58  	HelmBin                    string
    59  	RecreateExistingDraftRepos bool
    60  	NoTiller                   bool
    61  	RemoteTiller               bool
    62  	GlobalTiller               bool
    63  	SkipIngress                bool
    64  	SkipTiller                 bool
    65  	SkipClusterRole            bool
    66  	OnPremise                  bool
    67  	Http                       bool
    68  	NoGitValidate              bool
    69  	ExternalDNS                bool
    70  }
    71  
    72  const (
    73  	optionUsername        = "username"
    74  	optionNamespace       = "namespace"
    75  	optionTillerNamespace = "tiller-namespace"
    76  
    77  	// JenkinsBuildPackURL URL of Draft packs for Jenkins X
    78  	JenkinsBuildPackURL = "https://github.com/jenkins-x/draft-packs.git"
    79  )
    80  
    81  var (
    82  	initLong = templates.LongDesc(`
    83  		This command initializes the connected Kubernetes cluster for Jenkins X platform installation
    84  `)
    85  
    86  	initExample = templates.Examples(`
    87  		jx init
    88  `)
    89  )
    90  
    91  // NewCmdInit creates a command object for the generic "init" action, which
    92  // primes a Kubernetes cluster so it's ready for Jenkins X to be installed
    93  func NewCmdInit(commonOpts *opts.CommonOptions) *cobra.Command {
    94  	options := &InitOptions{
    95  		CommonOptions: commonOpts,
    96  	}
    97  
    98  	cmd := &cobra.Command{
    99  		Use:     "init",
   100  		Short:   "Init Jenkins X",
   101  		Long:    initLong,
   102  		Example: initExample,
   103  		Run: func(cmd *cobra.Command, args []string) {
   104  			options.Cmd = cmd
   105  			options.Args = args
   106  			err := options.Run()
   107  			helper.CheckErr(err)
   108  		},
   109  	}
   110  
   111  	cmd.Flags().StringVarP(&options.Flags.Provider, "provider", "", "", "Cloud service providing the Kubernetes cluster.  Supported providers: "+cloud.KubernetesProviderOptions())
   112  	cmd.Flags().StringVarP(&options.Flags.Namespace, optionNamespace, "", "jx", "The namespace the Jenkins X platform should be installed into")
   113  	options.AddInitFlags(cmd)
   114  	return cmd
   115  }
   116  
   117  func (o *InitOptions) AddInitFlags(cmd *cobra.Command) {
   118  	o.AddIngressFlags(cmd)
   119  
   120  	cmd.Flags().StringVarP(&o.Username, optionUsername, "", "", "The Kubernetes username used to initialise helm. Usually your email address for your Kubernetes account")
   121  	cmd.Flags().StringVarP(&o.Flags.UserClusterRole, "user-cluster-role", "", "cluster-admin", "The cluster role for the current user to be able to administer helm")
   122  	cmd.Flags().StringVarP(&o.Flags.TillerClusterRole, "tiller-cluster-role", "", opts.DefaultTillerRole, "The cluster role for Helm's tiller")
   123  	cmd.Flags().StringVarP(&o.Flags.TillerNamespace, optionTillerNamespace, "", opts.DefaultTillerNamesapce, "The namespace for the Tiller when using a global tiller")
   124  	cmd.Flags().BoolVarP(&o.Flags.DraftClient, "draft-client-only", "", false, "Only install draft client")
   125  	cmd.Flags().BoolVarP(&o.Flags.HelmClient, "helm-client-only", "", opts.DefaultOnlyHelmClient, "Only install helm client")
   126  	cmd.Flags().BoolVarP(&o.Flags.RecreateExistingDraftRepos, "recreate-existing-draft-repos", "", false, "Delete existing helm repos used by Jenkins X under ~/draft/packs")
   127  	cmd.Flags().BoolVarP(&o.Flags.GlobalTiller, "global-tiller", "", opts.DefaultGlobalTiller, "Whether or not to use a cluster global tiller")
   128  	cmd.Flags().BoolVarP(&o.Flags.RemoteTiller, "remote-tiller", "", opts.DefaultRemoteTiller, "If enabled and we are using tiller for helm then run tiller remotely in the kubernetes cluster. Otherwise we run the tiller process locally.")
   129  	cmd.Flags().BoolVarP(&o.Flags.NoTiller, "no-tiller", "", true, "Whether to disable the use of tiller with helm. If disabled we use 'helm template' to generate the YAML from helm charts then we use 'kubectl apply' to install it to avoid using tiller completely.")
   130  	cmd.Flags().BoolVarP(&o.Flags.SkipTiller, "skip-setup-tiller", "", opts.DefaultSkipTiller, "Don't setup the Helm Tiller service - lets use whatever tiller is already setup for us.")
   131  	cmd.Flags().BoolVarP(&o.Flags.SkipClusterRole, "skip-cluster-role", "", opts.DefaultSkipClusterRole, "Don't enable cluster admin role for user")
   132  	cmd.Flags().BoolVarP(&o.Flags.ExternalDNS, "external-dns", "", false, "Installs external-dns into the cluster. ExternalDNS manages service DNS records for your cluster, providing you've setup your domain record")
   133  	cmd.Flags().BoolVarP(&o.Flags.Helm3, "helm3", "", opts.DefaultHelm3, "Use helm3 to install Jenkins X which does not use Tiller")
   134  	cmd.Flags().BoolVarP(&o.AdvancedMode, "advanced-mode", "", false, "Advanced install options. This will prompt for advanced install options")
   135  }
   136  
   137  func (o *InitOptions) AddIngressFlags(cmd *cobra.Command) {
   138  	cmd.Flags().StringVarP(&o.Flags.Domain, "domain", "", "", "Domain to expose ingress endpoints.  Example: jenkinsx.io")
   139  	cmd.Flags().StringVarP(&o.Flags.IngressClusterRole, "ingress-cluster-role", "", "cluster-admin", "The cluster role for the Ingress controller")
   140  	cmd.Flags().StringVarP(&o.Flags.IngressNamespace, "ingress-namespace", "", opts.DefaultIngressNamesapce, "The namespace for the Ingress controller")
   141  	cmd.Flags().StringVarP(&o.Flags.IngressService, "ingress-service", "", opts.DefaultIngressServiceName, "The name of the Ingress controller Service")
   142  	cmd.Flags().StringVarP(&o.Flags.IngressDeployment, "ingress-deployment", "", opts.DefaultIngressServiceName, "The name of the Ingress controller Deployment")
   143  	cmd.Flags().StringVarP(&o.Flags.ExternalIP, "external-ip", "", "", "The external IP used to access ingress endpoints from outside the Kubernetes cluster. For bare metal on premise clusters this is often the IP of the Kubernetes master. For cloud installations this is often the external IP of the ingress LoadBalancer.")
   144  	cmd.Flags().BoolVarP(&o.Flags.SkipIngress, "skip-ingress", "", false, "Skips the installation of ingress controller. Note that a ingress controller must already be installed into the cluster in order for the installation to succeed")
   145  	cmd.Flags().BoolVarP(&o.Flags.OnPremise, "on-premise", "", false, "If installing on an on premise cluster then lets default the 'external-ip' to be the Kubernetes master IP address")
   146  }
   147  
   148  func (o *InitOptions) checkOptions() error {
   149  	if o.Flags.Helm3 {
   150  		o.Flags.SkipTiller = true
   151  		o.Flags.NoTiller = true
   152  	}
   153  
   154  	if !o.Flags.SkipTiller {
   155  		tillerNamespace := o.Flags.TillerNamespace
   156  		if o.Flags.GlobalTiller {
   157  			if tillerNamespace == "" {
   158  				return util.MissingOption(optionTillerNamespace)
   159  			}
   160  		} else {
   161  			ns := o.Flags.Namespace
   162  			if ns == "" {
   163  				_, curNs, err := o.KubeClientAndNamespace()
   164  				if err != nil {
   165  					return err
   166  				}
   167  				ns = curNs
   168  			}
   169  			if ns == "" {
   170  				return util.MissingOption(optionNamespace)
   171  			}
   172  			o.Flags.Namespace = ns
   173  		}
   174  	}
   175  
   176  	if o.Flags.SkipIngress {
   177  		if o.Flags.ExternalIP == "" {
   178  			log.Logger().Warnf("Expecting ingress controller to be installed in %s",
   179  				util.ColorInfo(fmt.Sprintf("%s/%s", o.Flags.IngressNamespace, o.Flags.IngressDeployment)))
   180  		}
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  // Run performs initialization
   187  func (o *InitOptions) Run() error {
   188  	var err error
   189  	if !o.Flags.RemoteTiller || o.Flags.NoTiller {
   190  		o.Flags.HelmClient = true
   191  		o.Flags.SkipTiller = true
   192  		o.Flags.GlobalTiller = false
   193  	}
   194  	o.Flags.Provider, err = o.GetCloudProvider(o.Flags.Provider)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	if !o.Flags.NoGitValidate {
   200  		err = o.ValidateGit()
   201  		if err != nil {
   202  			return err
   203  		}
   204  	}
   205  
   206  	err = o.EnableClusterAdminRole()
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	// So a user doesn't need to specify ingress options if provider is ICP: we will use ICP's own ingress controller
   212  	// and by default, the tiller namespace "jx"
   213  	if o.Flags.Provider == cloud.ICP {
   214  		o.configureForICP()
   215  	}
   216  
   217  	// Needs to be done early as is an ingress availablility is an indicator of cluster readyness
   218  	if o.Flags.Provider == cloud.IKS {
   219  		err = o.initIKSIngress()
   220  		if err != nil {
   221  			return err
   222  		}
   223  	}
   224  	// setup the configuration for helm init
   225  	err = o.checkOptions()
   226  	if err != nil {
   227  		return err
   228  	}
   229  	cfg := opts.InitHelmConfig{
   230  		Namespace:       o.Flags.Namespace,
   231  		OnlyHelmClient:  o.Flags.HelmClient,
   232  		Helm3:           o.Flags.Helm3,
   233  		SkipTiller:      o.Flags.SkipTiller,
   234  		GlobalTiller:    o.Flags.GlobalTiller,
   235  		TillerNamespace: o.Flags.TillerNamespace,
   236  		TillerRole:      o.Flags.TillerClusterRole,
   237  	}
   238  	// helm init, this has been seen to fail intermittently on public clouds, so let's retry a couple of times
   239  	err = o.Retry(3, 2*time.Second, func() (err error) {
   240  		err = o.InitHelm(cfg)
   241  		return
   242  	})
   243  
   244  	if err != nil {
   245  		log.Logger().Fatalf("helm init failed: %v", err)
   246  		return err
   247  	}
   248  
   249  	// draft init
   250  	_, _, err = o.InitBuildPacks(nil)
   251  	if err != nil {
   252  		log.Logger().Fatalf("initialise build packs failed: %v", err)
   253  		return err
   254  	}
   255  
   256  	// configure options for external-dns
   257  	if o.Flags.ExternalDNS {
   258  		o.configureOptionsForExternalDNS()
   259  	}
   260  
   261  	// install ingress
   262  	if !o.Flags.SkipIngress {
   263  		err = o.InitIngress()
   264  		if err != nil {
   265  			log.Logger().Fatalf("ingress init failed: %v", err)
   266  			return err
   267  		}
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  func (o *InitOptions) EnableClusterAdminRole() error {
   274  	if o.Flags.SkipClusterRole {
   275  		return nil
   276  	}
   277  	client, err := o.KubeClient()
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	if o.Username == "" {
   283  		o.Username, err = o.GetClusterUserName()
   284  		if err != nil {
   285  			return err
   286  		}
   287  	}
   288  	if o.Username == "" {
   289  		return util.MissingOption(optionUsername)
   290  	}
   291  	userFormatted := naming.ToValidName(o.Username)
   292  
   293  	clusterRoleBindingName := naming.ToValidName(userFormatted + "-" + o.Flags.UserClusterRole + "-binding")
   294  
   295  	clusterRoleBindingInterface := client.RbacV1().ClusterRoleBindings()
   296  	clusterRoleBinding := &rbacv1.ClusterRoleBinding{
   297  		ObjectMeta: metav1.ObjectMeta{
   298  			Name: clusterRoleBindingName,
   299  		},
   300  		Subjects: []rbacv1.Subject{
   301  			{
   302  				APIGroup: "rbac.authorization.k8s.io",
   303  				Kind:     "User",
   304  				Name:     o.Username,
   305  			},
   306  		},
   307  		RoleRef: rbacv1.RoleRef{
   308  			APIGroup: "rbac.authorization.k8s.io",
   309  			Kind:     "ClusterRole",
   310  			Name:     o.Flags.UserClusterRole,
   311  		},
   312  	}
   313  
   314  	return o.Retry(3, 10*time.Second, func() (err error) {
   315  		_, err = clusterRoleBindingInterface.Get(clusterRoleBindingName, metav1.GetOptions{})
   316  		if err != nil {
   317  			log.Logger().Debugf("Trying to create ClusterRoleBinding %s for role: %s for user %s\n %v", clusterRoleBindingName, o.Flags.UserClusterRole, o.Username, err)
   318  
   319  			//args := []string{"create", "clusterrolebinding", clusterRoleBindingName, "--clusterrole=" + role, "--user=" + user}
   320  
   321  			_, err = clusterRoleBindingInterface.Create(clusterRoleBinding)
   322  			if err == nil {
   323  				log.Logger().Debugf("Created ClusterRoleBinding %s", clusterRoleBindingName)
   324  			}
   325  		}
   326  		return err
   327  	})
   328  }
   329  
   330  func (o *InitOptions) configureOptionsForExternalDNS() {
   331  	if !(o.BatchMode) {
   332  		surveyOpts := survey.WithStdio(o.In, o.Out, o.Err)
   333  		ExternalDNSDomain := ""
   334  		prompt := &survey.Input{
   335  			Message: "Provide the domain Jenkins X should be available at:",
   336  			Default: "",
   337  			Help:    "",
   338  		}
   339  
   340  		survey.AskOne(prompt, &ExternalDNSDomain, nil, surveyOpts) //nolint:errcheck
   341  
   342  		o.Flags.Domain = ExternalDNSDomain
   343  	}
   344  }
   345  
   346  func (o *InitOptions) configureForICP() {
   347  	icpDefaultTillerNS := "default"
   348  	icpDefaultNS := "jx"
   349  
   350  	log.Logger().Info("")
   351  	log.Logger().Info(util.ColorInfo("IBM Cloud Private installation of Jenkins X"))
   352  	log.Logger().Info("Configuring Jenkins X options for IBM Cloud Private: ensure your Kubernetes context is already " +
   353  		"configured to point to the cluster jx will be installed into.")
   354  	log.Logger().Info("")
   355  
   356  	log.Logger().Info(util.ColorInfo("Permitting image repositories to be used"))
   357  	log.Logger().Info("If you have a clusterimagepolicy, ensure that this policy permits pulling from the following additional repositories: " +
   358  		"the scope of which can be narrowed down once you are sure only images from certain repositories are being used:")
   359  	log.Logger().Info("- name: docker.io/* \n" +
   360  		"- name: gcr.io/* \n" +
   361  		"- name: quay.io/* \n" +
   362  		"- name: k8s.gcr.io/* \n" +
   363  		"- name: <your ICP cluster name>:8500/* ")
   364  
   365  	log.Logger().Info(util.ColorInfo("IBM Cloud Private defaults"))
   366  	log.Logger().Info("By default, with IBM Cloud Private the Tiller namespace for jx will be \"" + icpDefaultTillerNS + "\" and the namespace " +
   367  		"where Jenkins X resources will be installed into is \"" + icpDefaultNS + "\".")
   368  	log.Logger().Info("")
   369  
   370  	log.Logger().Info(util.ColorInfo("Using the IBM Cloud Private Docker registry"))
   371  	log.Logger().Info("To use the IBM Cloud Private Docker registry, when environments (namespaces) are created, " +
   372  		"create a Docker registry secret and patch the default service account in the created namespace to use the secret, adding it as an ImagePullSecret. " +
   373  		"This is required so that pods in the created namespace can pull images from the registry.")
   374  	log.Logger().Info("")
   375  
   376  	o.Flags.IngressNamespace = "kube-system"
   377  	o.Flags.IngressDeployment = "default-backend"
   378  	o.Flags.IngressService = "default-backend"
   379  	o.Flags.TillerNamespace = icpDefaultTillerNS
   380  	o.Flags.Namespace = icpDefaultNS
   381  
   382  	surveyOpts := survey.WithStdio(o.In, o.Out, o.Err)
   383  	ICPExternalIP := ""
   384  	ICPDomain := ""
   385  
   386  	if !(o.BatchMode) {
   387  		if o.Flags.ExternalIP != "" {
   388  			log.Logger().Info("An external IP has already been specified: otherwise you will be prompted for one to use")
   389  			return
   390  		}
   391  
   392  		prompt := &survey.Input{
   393  			Message: "Provide the external IP Jenkins X should use: typically your IBM Cloud Private proxy node IP address",
   394  			Default: "", // Would be useful to set this as the public IP automatically
   395  			Help:    "",
   396  		}
   397  		survey.AskOne(prompt, &ICPExternalIP, nil, surveyOpts) //nolint:errcheck
   398  
   399  		o.Flags.ExternalIP = ICPExternalIP
   400  
   401  		prompt = &survey.Input{
   402  			Message: "Provide the domain Jenkins X should be available at: typically your IBM Cloud Private proxy node IP address but with a domain added to the end",
   403  			Default: ICPExternalIP + ".nip.io",
   404  			Help:    "",
   405  		}
   406  
   407  		survey.AskOne(prompt, &ICPDomain, nil, surveyOpts) //nolint:errcheck
   408  
   409  		o.Flags.Domain = ICPDomain
   410  	}
   411  }
   412  
   413  func (o *InitOptions) initIKSIngress() error {
   414  	log.Logger().Info("Wait for Ingress controller to be injected into IBM Kubernetes Service Cluster")
   415  	kubeClient, err := o.KubeClient()
   416  	if err != nil {
   417  		return err
   418  	}
   419  
   420  	ingressNamespace := o.Flags.IngressNamespace
   421  
   422  	clusterID, err := iks.GetKubeClusterID(kubeClient)
   423  	if err != nil || clusterID == "" {
   424  		clusterID, err = iks.GetClusterID()
   425  		if err != nil {
   426  			return err
   427  		}
   428  	}
   429  	o.Flags.IngressDeployment = "public-cr" + strings.ToLower(clusterID) + "-alb1"
   430  	o.Flags.IngressService = "public-cr" + strings.ToLower(clusterID) + "-alb1"
   431  
   432  	return kube.WaitForDeploymentToBeCreatedAndReady(kubeClient, o.Flags.IngressDeployment, ingressNamespace, 30*time.Minute)
   433  }
   434  
   435  func (o *InitOptions) InitIngress() error {
   436  	surveyOpts := survey.WithStdio(o.In, o.Out, o.Err)
   437  	client, err := o.KubeClient()
   438  	if err != nil {
   439  		return err
   440  	}
   441  
   442  	ingressNamespace := o.Flags.IngressNamespace
   443  
   444  	err = kube.EnsureNamespaceCreated(client, ingressNamespace, map[string]string{"jenkins.io/kind": "ingress"}, nil)
   445  	if err != nil {
   446  		return fmt.Errorf("Failed to ensure the ingress namespace %s is created: %s\nIs this an RBAC issue on your cluster?", ingressNamespace, err)
   447  	}
   448  
   449  	if isOpenShiftProvider(o.Flags.Provider) {
   450  		log.Logger().Info("Not installing ingress as using OpenShift which uses Route and its own mechanism of ingress")
   451  		return nil
   452  	}
   453  
   454  	if o.Flags.Provider == cloud.ALIBABA {
   455  		if o.Flags.IngressDeployment == opts.DefaultIngressServiceName {
   456  			o.Flags.IngressDeployment = "nginx-ingress-controller"
   457  		}
   458  		if o.Flags.IngressService == opts.DefaultIngressServiceName {
   459  			o.Flags.IngressService = "nginx-ingress-lb"
   460  		}
   461  	}
   462  
   463  	podCount, err := kube.DeploymentPodCount(client, o.Flags.IngressDeployment, ingressNamespace)
   464  	if podCount == 0 {
   465  		installIngressController := false
   466  		if o.BatchMode {
   467  			installIngressController = true
   468  		} else if o.AdvancedMode {
   469  			prompt := &survey.Confirm{
   470  				Message: "No existing ingress controller found in the " + ingressNamespace + " namespace, shall we install one?",
   471  				Default: true,
   472  				Help:    "An ingress controller works with an external loadbalancer so you can access Jenkins X and your applications",
   473  			}
   474  			err = survey.AskOne(prompt, &installIngressController, nil, surveyOpts)
   475  			if err != nil {
   476  				return err
   477  			}
   478  		} else {
   479  			installIngressController = true
   480  			log.Logger().Infof(util.QuestionAnswer("No existing ingress controller found in the %s namespace, installing one", util.YesNo(installIngressController)), ingressNamespace)
   481  		}
   482  
   483  		if !installIngressController {
   484  			return nil
   485  		}
   486  
   487  		values := []string{"rbac.create=true", fmt.Sprintf("controller.extraArgs.publish-service=%s/%s", ingressNamespace, opts.DefaultIngressServiceName) /*,"rbac.serviceAccountName="+ingressServiceAccount*/}
   488  		valuesFiles := []string{}
   489  		valuesFiles, err = helm.AppendMyValues(valuesFiles)
   490  		if err != nil {
   491  			return errors.Wrap(err, "failed to append the myvalues file")
   492  		}
   493  		if o.Flags.Provider == cloud.AWS || o.Flags.Provider == cloud.EKS {
   494  			yamlText := `---
   495  rbac:
   496   create: true
   497  
   498  controller:
   499   service:
   500     annotations:
   501       service.beta.kubernetes.io/aws-load-balancer-type: nlb
   502     enableHttp: true
   503     enableHttps: true
   504  `
   505  
   506  			f, err := ioutil.TempFile("", "ing-values-")
   507  			if err != nil {
   508  				return err
   509  			}
   510  			fileName := f.Name()
   511  			err = ioutil.WriteFile(fileName, []byte(yamlText), util.DefaultWritePermissions)
   512  			if err != nil {
   513  				return err
   514  			}
   515  			log.Logger().Infof("Using helm values file: %s", fileName)
   516  			valuesFiles = append(valuesFiles, fileName)
   517  		}
   518  		chartName := "stable/nginx-ingress"
   519  
   520  		version, err := o.GetVersionNumber(versionstream.KindChart, chartName, o.Flags.VersionsRepository, o.Flags.VersionsGitRef)
   521  		if err != nil {
   522  			return errors.Wrapf(err, "failed to load version of chart %s", chartName)
   523  		}
   524  
   525  		i := 0
   526  		for {
   527  			log.Logger().Debugf("Installing using helm binary: %s", util.ColorInfo(o.Helm().HelmBinary()))
   528  			helmOptions := helm.InstallChartOptions{
   529  				Chart:       chartName,
   530  				ReleaseName: "jxing",
   531  				Version:     version,
   532  				Ns:          ingressNamespace,
   533  				SetValues:   values,
   534  				ValueFiles:  valuesFiles,
   535  				HelmUpdate:  true,
   536  			}
   537  			err = o.InstallChartWithOptions(helmOptions)
   538  			if err != nil {
   539  				if i >= 3 {
   540  					log.Logger().Errorf("Failed to install ingress chart: %s", err)
   541  					break
   542  				}
   543  				i++
   544  				time.Sleep(time.Second)
   545  			} else {
   546  				break
   547  			}
   548  		}
   549  		err = kube.WaitForDeploymentToBeReady(client, o.Flags.IngressDeployment, ingressNamespace, 10*time.Minute)
   550  		if err != nil {
   551  			return err
   552  		}
   553  
   554  	} else {
   555  		log.Logger().Info("existing ingress controller found, no need to install a new one")
   556  	}
   557  
   558  	if o.Flags.Provider != cloud.OPENSHIFT {
   559  		if o.Flags.Provider == cloud.OKE {
   560  			log.Logger().Infof("Note: this loadbalancer will fail to be provisioned if you have insufficient quotas, this can happen easily on a OCI free account")
   561  		}
   562  
   563  		if o.Flags.Provider == cloud.GKE {
   564  			log.Logger().Infof("Note: this loadbalancer will fail to be provisioned if you have insufficient quotas, this can happen easily on a GKE free account.\nTo view quotas run: %s", util.ColorInfo("gcloud compute project-info describe"))
   565  		}
   566  
   567  		log.Logger().Infof("Waiting for external loadbalancer to be created and update the nginx-ingress-controller service in %s namespace", ingressNamespace)
   568  
   569  		externalIP := o.Flags.ExternalIP
   570  		if externalIP == "" && o.Flags.OnPremise {
   571  			// lets find the Kubernetes master IP
   572  			config, _, err := o.Kube().LoadConfig()
   573  			if err != nil {
   574  				return err
   575  			}
   576  			if config == nil {
   577  				return errors.New("empty kubernetes config")
   578  			}
   579  			host := kube.CurrentServer(config)
   580  			if host == "" {
   581  				log.Logger().Warnf("No API server host is defined in the local kube config!")
   582  			} else {
   583  				externalIP, err = util.UrlHostNameWithoutPort(host)
   584  				if err != nil {
   585  					return fmt.Errorf("Could not parse Kubernetes master URI: %s as got: %s\nTry specifying the external IP address directly via: --external-ip", host, err)
   586  				}
   587  			}
   588  		}
   589  
   590  		if externalIP == "" {
   591  			err = services.WaitForExternalIP(client, o.Flags.IngressService, ingressNamespace, 10*time.Minute)
   592  			if err != nil {
   593  				return err
   594  			}
   595  			log.Logger().Infof("External loadbalancer created")
   596  		} else {
   597  			log.Logger().Infof("Using external IP: %s", util.ColorInfo(externalIP))
   598  		}
   599  
   600  		o.Flags.Domain, err = o.GetDomain(client, o.Flags.Domain, o.Flags.Provider, ingressNamespace, o.Flags.IngressService, externalIP)
   601  		o.CommonOptions.Domain = o.Flags.Domain
   602  		if err != nil {
   603  			return err
   604  		}
   605  	}
   606  
   607  	log.Logger().Info("nginx ingress controller installed and configured")
   608  
   609  	return nil
   610  }
   611  
   612  // ValidateGit validates that git is configured correctly
   613  func (o *InitOptions) ValidateGit() error {
   614  	// lets ignore errors which indicate no value set
   615  	userName, _ := o.Git().Username("")
   616  	userEmail, _ := o.Git().Email("")
   617  	var err error
   618  	if userName == "" {
   619  		if !o.BatchMode {
   620  			userName, err = util.PickValue("Please enter the name you wish to use with git: ", "", true, "", o.GetIOFileHandles())
   621  			if err != nil {
   622  				return err
   623  			}
   624  		}
   625  		if userName == "" {
   626  			return fmt.Errorf("No Git user.name is defined. Please run the command: git config --global --add user.name \"MyName\"")
   627  		}
   628  		err = o.Git().SetUsername("", userName)
   629  		if err != nil {
   630  			return err
   631  		}
   632  	}
   633  	if userEmail == "" {
   634  		if !o.BatchMode {
   635  			userEmail, err = util.PickValue("Please enter the email address you wish to use with git: ", "", true, "", o.GetIOFileHandles())
   636  			if err != nil {
   637  				return err
   638  			}
   639  		}
   640  		if userEmail == "" {
   641  			return fmt.Errorf("No Git user.email is defined. Please run the command: git config --global --add user.email \"me@acme.com\"")
   642  		}
   643  		err = o.Git().SetEmail("", userEmail)
   644  		if err != nil {
   645  			return err
   646  		}
   647  	}
   648  	log.Logger().Infof("Git configured for user: %s and email %s", util.ColorInfo(userName), util.ColorInfo(userEmail))
   649  	return nil
   650  }
   651  
   652  // HelmBinary returns name of configured Helm binary
   653  func (o *InitOptions) HelmBinary() string {
   654  	if o.Flags.Helm3 {
   655  		return "helm3"
   656  	}
   657  	testHelmBin := o.Flags.HelmBin
   658  	if testHelmBin != "" {
   659  		return testHelmBin
   660  	}
   661  	return "helm"
   662  }
   663  
   664  func isOpenShiftProvider(provider string) bool {
   665  	switch provider {
   666  	case cloud.OPENSHIFT:
   667  		return true
   668  	default:
   669  		return false
   670  	}
   671  }