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 }