github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/opts/install.go (about) 1 package opts 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "runtime" 8 "strings" 9 "time" 10 11 "github.com/olli-ai/jx/v2/pkg/cloud/openshift" 12 "github.com/olli-ai/jx/v2/pkg/dependencymatrix" 13 14 "github.com/olli-ai/jx/v2/pkg/brew" 15 16 "github.com/olli-ai/jx/v2/pkg/ksync" 17 18 "github.com/olli-ai/jx/v2/pkg/cloud/amazon" 19 20 "github.com/olli-ai/jx/v2/pkg/cloud/iks" 21 22 randomdata "github.com/Pallinder/go-randomdata" 23 "github.com/blang/semver" 24 jenkinsv1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1" 25 "github.com/jenkins-x/jx-logging/pkg/log" 26 "github.com/olli-ai/jx/v2/pkg/cloud" 27 "github.com/olli-ai/jx/v2/pkg/cloud/gke" 28 "github.com/olli-ai/jx/v2/pkg/cloud/gke/externaldns" 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/helm" 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/services" 35 "github.com/olli-ai/jx/v2/pkg/packages" 36 "github.com/olli-ai/jx/v2/pkg/prow" 37 "github.com/olli-ai/jx/v2/pkg/util" 38 "github.com/olli-ai/jx/v2/pkg/versionstream" 39 "github.com/pkg/errors" 40 survey "gopkg.in/AlecAivazis/survey.v1" 41 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 42 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 43 ) 44 45 var ( 46 groovy = ` 47 // imports 48 import jenkins.model.Jenkins 49 import jenkins.model.JenkinsLocationConfiguration 50 51 // parameters 52 def jenkinsParameters = [ 53 url: '%s/' 54 ] 55 56 // get Jenkins location configuration 57 def jenkinsLocationConfiguration = JenkinsLocationConfiguration.get() 58 59 // set Jenkins URL 60 jenkinsLocationConfiguration.setUrl(jenkinsParameters.url) 61 62 // set Jenkins admin email address 63 jenkinsLocationConfiguration.setAdminAddress(jenkinsParameters.email) 64 65 // save current Jenkins state to disk 66 jenkinsLocationConfiguration.save() 67 ` 68 ) 69 70 const ( 71 AdminSecretsFile = "adminSecrets.yaml" 72 ExtraValuesFile = "extraValues.yaml" 73 ValuesFile = "values.yaml" 74 JXInstallConfig = "jx-install-config" 75 CloudEnvValuesFile = "myvalues.yaml" 76 CloudEnvSecretsFile = "secrets.yaml" 77 CloudEnvSopsConfigFile = ".sops.yaml" 78 DefaultInstallTimeout = "6000" 79 DefaultCloudEnvironmentsURL = "https://github.com/jenkins-x/cloud-environments" 80 ) 81 82 // DoInstallMissingDependencies install missing dependencies from the given list 83 func (o *CommonOptions) DoInstallMissingDependencies(install []string) error { 84 for _, i := range install { 85 log.Logger().Infof("Installing %s", util.ColorInfo(i)) 86 var err error 87 switch i { 88 case "az": 89 o.InstallAzureCli() 90 case "kubectl": 91 err = packages.InstallKubectl(false) 92 case "gcloud": 93 o.InstallGcloud() 94 case "helm": 95 err = o.InstallHelm() 96 case "ibmcloud": 97 err = iks.InstallIBMCloud(false) 98 case "glooctl": 99 err = o.InstallGlooctl() 100 case "tiller": 101 err = o.InstallTiller() 102 case "helm3": 103 err = o.InstallHelm3() 104 case "ksync": 105 _, err = ksync.InstallKSync() 106 case "oc": 107 err = openshift.InstallOc() 108 case "oci": 109 err = o.InstallOciCli() 110 case "aws": 111 // Not yet implemented 112 case "eksctl": 113 err = amazon.InstallEksCtl(false) 114 case "aws-iam-authenticator": 115 err = amazon.InstallAwsIamAuthenticator(false) 116 case "kustomize": 117 err = o.InstallKustomize() 118 default: 119 return fmt.Errorf("unknown dependency to install %s\n", i) 120 } 121 if err != nil { 122 return fmt.Errorf("error installing %s: %v\n", i, err) 123 } 124 } 125 return nil 126 } 127 128 // InstallGlooctl Installs glooctl tool 129 func (o *CommonOptions) InstallGlooctl() error { 130 binDir, err := util.JXBinLocation() 131 if err != nil { 132 return err 133 } 134 fileName := "glooctl" 135 flag, err := packages.ShouldInstallBinary("glooctl") 136 if err != nil || !flag { 137 return err 138 } 139 140 suffix := runtime.GOARCH 141 if runtime.GOOS == "windows" { 142 suffix += ".exe" 143 } 144 clientURL := fmt.Sprintf("https://github.com/solo-io/gloo/releases/download/v%v/glooctl-%s-%s", packages.GlooVersion, runtime.GOOS, suffix) 145 fullPath := filepath.Join(binDir, fileName) 146 tmpFile := fullPath + ".tmp" 147 err = packages.DownloadFile(clientURL, tmpFile) 148 if err != nil { 149 return err 150 } 151 err = util.RenameFile(tmpFile, fullPath) 152 if err != nil { 153 return err 154 } 155 return os.Chmod(fullPath, 0755) 156 } 157 158 // InstallKustomize installs kustomize 159 func (o *CommonOptions) InstallKustomize() error { 160 binDir, err := util.JXBinLocation() 161 if err != nil { 162 return errors.Wrapf(err, "unable to find JXBinLocation") 163 } 164 165 fullBinaryPath := filepath.Join(binDir, "kustomize") 166 exists, err := util.FileExists(fullBinaryPath) 167 if err != nil { 168 return errors.Wrapf(err, "unable to verify if binary exists") 169 } 170 if exists { 171 log.Logger().Debugf("binary %s already exists", fullBinaryPath) 172 return nil 173 } 174 175 clientURL := fmt.Sprintf("https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%%2Fv%s/kustomize_v%s_%s_%s.tar.gz", packages.KustomizeVersion, packages.KustomizeVersion, runtime.GOOS, runtime.GOARCH) 176 tmpDir := filepath.Join(binDir, "kustomize.tmp") 177 err = os.MkdirAll(tmpDir, util.DefaultWritePermissions) 178 if err != nil { 179 return errors.Wrapf(err, "failed to create tmp directory") 180 } 181 182 defer func() { 183 err = os.RemoveAll(tmpDir) 184 if err != nil { 185 log.Logger().Warnf("Failed to Remove tmp directory: %v", err) 186 } 187 }() 188 189 fullPath := filepath.Join(binDir, "kustomize") 190 tarFile := filepath.Join(tmpDir, "kustomize.tar.gz") 191 defer func() { 192 err = os.Remove(tarFile) 193 if err != nil { 194 log.Logger().Warnf("failed to Remove tarFile : %v", err) 195 } 196 }() 197 198 err = packages.DownloadFile(clientURL, tarFile) 199 if err != nil { 200 return errors.Wrapf(err, "failed to Download File") 201 } 202 err = util.UnTargz(tarFile, tmpDir, []string{"kustomize"}) 203 if err != nil { 204 return errors.Wrapf(err, "failed to Un-tar file") 205 } 206 207 err = os.Rename(filepath.Join(tmpDir, "kustomize"), fullPath) 208 if err != nil { 209 return errors.Wrapf(err, "failed to rename file") 210 } 211 212 return os.Chmod(fullPath, 0755) 213 } 214 215 // InstallHelm install helm cli 216 func (o *CommonOptions) InstallHelm() error { 217 binary := "helm" 218 binDir, err := util.JXBinLocation() 219 if err != nil { 220 return err 221 } 222 223 flag, err := packages.ShouldInstallBinary(binary) 224 if err != nil || !flag { 225 return err 226 } 227 228 clientURL := fmt.Sprintf("https://get.helm.sh/helm-v%s-%s-%s.tar.gz", packages.Helm2Version, runtime.GOOS, runtime.GOARCH) 229 fullPath := filepath.Join(binDir, binary) 230 tarFile := fullPath + ".tgz" 231 err = packages.DownloadFile(clientURL, tarFile) 232 if err != nil { 233 return err 234 } 235 err = util.UnTargz(tarFile, binDir, []string{binary, binary}) 236 if err != nil { 237 return err 238 } 239 err = os.Remove(tarFile) 240 if err != nil { 241 return err 242 } 243 err = os.Chmod(fullPath, 0755) 244 if err != nil { 245 return err 246 } 247 return o.installHelmSecretsPlugin(fullPath, true) 248 } 249 250 // InstallTiller installs tiller 251 func (o *CommonOptions) InstallTiller() error { 252 binDir, err := util.JXBinLocation() 253 if err != nil { 254 return err 255 } 256 binary := "tiller" 257 fileName := binary 258 if runtime.GOOS == "windows" { 259 fileName += ".exe" 260 } 261 262 clientURL := fmt.Sprintf("https://get.helm.sh/helm-v%s-%s-%s.tar.gz", packages.Helm2Version, runtime.GOOS, runtime.GOARCH) 263 fullPath := filepath.Join(binDir, fileName) 264 helmFullPath := filepath.Join(binDir, "helm") 265 tarFile := fullPath + ".tgz" 266 err = packages.DownloadFile(clientURL, tarFile) 267 if err != nil { 268 return err 269 } 270 err = util.UnTargz(tarFile, binDir, []string{binary, fileName, "helm"}) 271 if err != nil { 272 return err 273 } 274 err = os.Remove(tarFile) 275 if err != nil { 276 return err 277 } 278 err = os.Chmod(fullPath, 0755) 279 if err != nil { 280 return err 281 } 282 err = helm.StartLocalTillerIfNotRunning() 283 if err != nil { 284 return err 285 } 286 return o.installHelmSecretsPlugin(helmFullPath, true) 287 } 288 289 // InstallHelm3 installs helm3 cli 290 func (o *CommonOptions) InstallHelm3() error { 291 binDir, err := util.JXBinLocation() 292 if err != nil { 293 return err 294 } 295 binary := "helm3" 296 flag, err := packages.ShouldInstallBinary(binary) 297 if err != nil || !flag { 298 return err 299 } 300 301 clientURL := fmt.Sprintf("https://get.helm.sh/helm-v%v-%s-%s.tar.gz", packages.Helm3Version, runtime.GOOS, runtime.GOARCH) 302 303 tmpDir := filepath.Join(binDir, "helm3.tmp") 304 err = os.MkdirAll(tmpDir, util.DefaultWritePermissions) 305 if err != nil { 306 return err 307 } 308 fullPath := filepath.Join(binDir, binary) 309 tarFile := filepath.Join(tmpDir, binary+".tgz") 310 err = packages.DownloadFile(clientURL, tarFile) 311 if err != nil { 312 return err 313 } 314 err = util.UnTargz(tarFile, tmpDir, []string{"helm", "helm"}) 315 if err != nil { 316 return err 317 } 318 err = os.Remove(tarFile) 319 if err != nil { 320 return err 321 } 322 err = os.Rename(filepath.Join(tmpDir, "helm"), fullPath) 323 if err != nil { 324 return err 325 } 326 err = os.RemoveAll(tmpDir) 327 if err != nil { 328 return err 329 } 330 331 err = os.Chmod(fullPath, 0755) 332 if err != nil { 333 return err 334 } 335 return o.installHelmSecretsPlugin(fullPath, false) 336 } 337 338 func (o *CommonOptions) installHelmSecretsPlugin(helmBinary string, clientOnly bool) error { 339 log.Logger().Infof("Installing %s", util.ColorInfo("helm secrets plugin")) 340 var err error 341 if !strings.Contains(helmBinary, "helm3") { 342 err := o.Helm().Init(clientOnly, "", "", false) 343 if err != nil { 344 return errors.Wrap(err, "failed to initialize helm") 345 } 346 } 347 // remove the plugin just in case is already installed 348 cmd := util.Command{ 349 Name: helmBinary, 350 Args: []string{"plugin", "remove", "secrets"}, 351 } 352 _, err = cmd.RunWithoutRetry() 353 if err != nil && !strings.Contains(err.Error(), "secrets not found") { 354 return errors.Wrap(err, "failed to remove helm secrets") 355 } 356 cmd = util.Command{ 357 Name: helmBinary, 358 Args: []string{"plugin", "install", "https://github.com/futuresimple/helm-secrets"}, 359 } 360 _, err = cmd.RunWithoutRetry() 361 // Workaround for Helm install on Windows caused by https://github.com/helm/helm/issues/4418 362 if err != nil && runtime.GOOS == "windows" && strings.Contains(err.Error(), "Error: symlink") { 363 // The install _does_ seem to work, but we get an error - catch this on windows and lob it in the bin 364 return nil 365 } 366 // End of Workaround 367 return err 368 } 369 370 // GetLatestJXVersion returns latest jx version 371 func (o *CommonOptions) GetLatestJXVersion(resolver versionstream.Streamer) (semver.Version, error) { 372 if config.LatestVersionStringsBucket != "" { 373 err := o.InstallRequirements(cloud.GKE) 374 if err != nil { 375 return semver.Version{}, err 376 } 377 gcloudOpts := &gke.GCloud{} 378 latestVersionStrings, err := gcloudOpts.ListObjects(config.LatestVersionStringsBucket, "binaries/jx") 379 if err != nil { 380 return semver.Version{}, nil 381 } 382 return util.GetLatestVersionStringFromBucketURLs(latestVersionStrings) 383 } 384 385 dir := resolver.GetVersionsDir() 386 matrix, err := dependencymatrix.LoadDependencyMatrix(dir) 387 if err != nil { 388 return semver.Version{}, errors.Wrapf(err, "failed to load dependency matrix from version stream at %s", dir) 389 } 390 for _, dep := range matrix.Dependencies { 391 if dep.Host == "github.com" && dep.Owner == "jenkins-x" && dep.Repo == "jx" { 392 v := dep.Version 393 if v == "" { 394 return semver.Version{}, fmt.Errorf("no version specified in the dependency matrix for version stream at %s", dir) 395 } 396 log.Logger().Debugf("found version %s of jx from the version stream", v) 397 return semver.Make(v) 398 } 399 } 400 log.Logger().Warnf("could not find the version of jx in the dependency matrix of the version stream at %s", dir) 401 402 if runtime.GOOS == "darwin" && !o.NoBrew { 403 log.Logger().Debugf("Locating latest JX version from HomeBrew") 404 // incase auto-update is not enabled, lets perform an explicit brew update first 405 brewUpdate, err := o.GetCommandOutput("", "brew", "update") 406 if err != nil { 407 log.Logger().Errorf("unable to update brew - %s", brewUpdate) 408 return semver.Version{}, err 409 } 410 log.Logger().Debugf("updating brew - %s", brewUpdate) 411 412 brewInfo, err := o.GetCommandOutput("", "brew", "info", "--json", "jx") 413 if err != nil { 414 log.Logger().Errorf("unable to get brew info for jx - %s", brewInfo) 415 return semver.Version{}, err 416 } 417 418 v, err := brew.LatestJxBrewVersion(brewInfo) 419 if err != nil { 420 return semver.Version{}, err 421 } 422 423 return semver.Make(v) 424 } 425 log.Logger().Debugf("Locating latest JX version from GitHub") 426 return util.GetLatestVersionFromGitHub("jenkins-x", "jx") 427 } 428 429 // InstallJx installs jx cli 430 func (o *CommonOptions) InstallJx(upgrade bool, version string) error { 431 log.Logger().Debugf("installing jx %s", version) 432 if runtime.GOOS == "darwin" && !o.NoBrew { 433 if upgrade { 434 return o.RunCommand("brew", "upgrade", "jx") 435 } else { 436 return o.RunCommand("brew", "install", "jx") 437 } 438 } 439 binDir, err := util.JXBinLocation() 440 if err != nil { 441 return err 442 } 443 // Check for jx binary in non standard path and install there instead if found... 444 nonStandardBinDir, err := util.JXBinaryLocation() 445 if err == nil && binDir != nonStandardBinDir { 446 binDir = nonStandardBinDir 447 } 448 binary := "jx" 449 fileName := binary 450 if !upgrade { 451 flag, err := packages.ShouldInstallBinary(binary) 452 if err != nil || !flag { 453 return err 454 } 455 } 456 org := "jenkins-x" 457 repo := "jx" 458 if version == "" { 459 latestVersion, err := util.GetLatestVersionFromGitHub(org, repo) 460 if err != nil { 461 return err 462 } 463 version = fmt.Sprintf("%s", latestVersion) 464 } 465 extension := "tar.gz" 466 if runtime.GOOS == "windows" { 467 extension = "zip" 468 } 469 clientURL := fmt.Sprintf("%s%s/"+binary+"-%s-%s.%s", config.BinaryDownloadBaseURL, version, runtime.GOOS, runtime.GOARCH, extension) 470 fullPath := filepath.Join(binDir, fileName) 471 if runtime.GOOS == "windows" { 472 fullPath += ".exe" 473 } 474 tmpArchiveFile := fullPath + ".tmp" 475 err = packages.DownloadFile(clientURL, tmpArchiveFile) 476 if err != nil { 477 return err 478 } 479 // Untar the new binary into a temp directory 480 jxHome, err := util.ConfigDir() 481 if err != nil { 482 return err 483 } 484 485 if runtime.GOOS != "windows" { 486 err = util.UnTargz(tmpArchiveFile, jxHome, []string{binary, fileName}) 487 if err != nil { 488 return err 489 } 490 err = os.Remove(tmpArchiveFile) 491 if err != nil { 492 return err 493 } 494 err = os.Remove(filepath.Join(binDir, "jx")) 495 if err != nil && o.Verbose { 496 log.Logger().Infof("Skipping removal of old jx binary: %s", err) 497 } 498 // Copy over the new binary 499 err = os.Rename(filepath.Join(jxHome, "jx"), filepath.Join(binDir, "jx")) 500 if err != nil { 501 return err 502 } 503 } else { // windows 504 windowsBinaryFromArchive := "jx-windows-amd64.exe" 505 err = util.UnzipSpecificFiles(tmpArchiveFile, jxHome, windowsBinaryFromArchive) 506 if err != nil { 507 return err 508 } 509 err = os.Remove(tmpArchiveFile) 510 if err != nil { 511 return err 512 } 513 // A standard remove and rename (or overwrite) will not work as the file will be locked as windows is running it 514 // the trick is to rename to a tempfile :-o 515 // this will leave old files around but well at least it updates. 516 // we could schedule the file for cleanup at next boot but.... 517 // HKLM\System\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations 518 err = os.Rename(filepath.Join(binDir, "jx.exe"), filepath.Join(binDir, "jx.exe.deleteme")) 519 // if we can not rename it this i pretty fatal as we won;t be able to overwrite either 520 if err != nil { 521 return err 522 } 523 // Copy over the new binary 524 err = os.Rename(filepath.Join(jxHome, windowsBinaryFromArchive), filepath.Join(binDir, "jx.exe")) 525 if err != nil { 526 return err 527 } 528 } 529 log.Logger().Infof("Jenkins X client has been installed into %s", util.ColorInfo(fullPath)) 530 return os.Chmod(fullPath, 0755) 531 } 532 533 // InstallGcloud installs gcloud cli 534 func (o *CommonOptions) InstallGcloud() { 535 log.Logger().Infof("please install missing gcloud sdk - see https://cloud.google.com/sdk/downloads#interactive") 536 } 537 538 // InstallAzureCli installs azure cli 539 func (o *CommonOptions) InstallAzureCli() { 540 log.Logger().Infof("please install missing azure cli https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest") 541 } 542 543 // InstallOciCli installs oci cli 544 func (o *CommonOptions) InstallOciCli() error { 545 var err error 546 filePath := "./install.sh" 547 log.Logger().Info("Installing OCI CLI...") 548 err = o.RunCommand("curl", "-LO", "https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh") 549 550 if err != nil { 551 return err 552 } 553 err = os.Chmod(filePath, 0755) 554 if err != nil { 555 return err 556 } 557 558 err = o.RunCommandVerbose(filePath, "--accept-all-defaults") 559 if err != nil { 560 return err 561 } 562 563 return os.Remove(filePath) 564 } 565 566 // GetCloudProvider returns the cloud provider 567 func (o *CommonOptions) GetCloudProvider(p string) (string, error) { 568 surveyOpts := survey.WithStdio(o.In, o.Out, o.Err) 569 if p != "" { 570 if !util.Contains(cloud.KubernetesProviders, p) { 571 return "", util.InvalidArg(p, cloud.KubernetesProviders) 572 } 573 } 574 575 if p == "" { 576 prompt := &survey.Select{ 577 Message: "Cloud Provider", 578 Options: cloud.KubernetesProviders, 579 Help: "Cloud service providing the Kubernetes cluster, Google (GKE), Oracle (OKE), Azure (AKS)", 580 } 581 582 err := survey.AskOne(prompt, &p, nil, surveyOpts) 583 if err != nil { 584 return "", err 585 } 586 } 587 return p, nil 588 } 589 590 // GetClusterDependencies returns the dependencies for a cloud provider 591 func (o *CommonOptions) GetClusterDependencies(depsToInstall []string) []string { 592 deps := packages.FilterInstalledDependencies(depsToInstall) 593 d := packages.BinaryShouldBeInstalled("kubectl") 594 if d != "" && util.StringArrayIndex(deps, d) < 0 { 595 deps = append(deps, d) 596 } 597 598 d = packages.BinaryShouldBeInstalled("helm") 599 if d != "" && util.StringArrayIndex(deps, d) < 0 { 600 deps = append(deps, d) 601 } 602 603 // Platform specific deps 604 if runtime.GOOS == "darwin" { 605 if !o.NoBrew { 606 d = packages.BinaryShouldBeInstalled("brew") 607 if d != "" && util.StringArrayIndex(deps, d) < 0 { 608 deps = append(deps, d) 609 } 610 } 611 } 612 return deps 613 } 614 615 // InstallMissingDependencies installs missing dependencies 616 func (o *CommonOptions) InstallMissingDependencies(providerSpecificDeps []string) error { 617 deps := o.GetClusterDependencies(providerSpecificDeps) 618 if len(deps) == 0 { 619 return nil 620 } 621 622 install := []string{} 623 624 if o.InstallDependencies { 625 install = append(install, deps...) 626 } else { 627 surveyOpts := survey.WithStdio(o.In, o.Out, o.Err) 628 if o.BatchMode { 629 return errors.New(fmt.Sprintf("run without batch mode or manually install missing dependencies %v\n", deps)) 630 } 631 632 prompt := &survey.MultiSelect{ 633 Message: "Missing required dependencies, deselect to avoid auto installing:", 634 Options: deps, 635 Default: deps, 636 } 637 err := survey.AskOne(prompt, &install, nil, surveyOpts) 638 if err != nil { 639 return err 640 } 641 } 642 643 return o.DoInstallMissingDependencies(install) 644 } 645 646 // InstallRequirements installs any requirements for the given provider kind 647 func (o *CommonOptions) InstallRequirements(cloudProvider string, extraDependencies ...string) error { 648 var deps []string 649 switch cloudProvider { 650 case cloud.IKS: 651 deps = packages.AddRequiredBinary("ibmcloud", deps) 652 case cloud.AWS: 653 deps = packages.AddRequiredBinary("kops", deps) 654 case cloud.EKS: 655 deps = packages.AddRequiredBinary("eksctl", deps) 656 deps = packages.AddRequiredBinary("aws-iam-authenticator", deps) 657 case cloud.AKS: 658 deps = packages.AddRequiredBinary("az", deps) 659 case cloud.GKE: 660 deps = packages.AddRequiredBinary("gcloud", deps) 661 case cloud.OKE: 662 deps = packages.AddRequiredBinary("oci", deps) 663 } 664 665 for _, dep := range extraDependencies { 666 deps = packages.AddRequiredBinary(dep, deps) 667 } 668 669 return o.InstallMissingDependencies(deps) 670 } 671 672 // CreateClusterAdmin creates a cluster admin 673 func (o *CommonOptions) CreateClusterAdmin() error { 674 675 content := []byte( 676 `apiVersion: rbac.authorization.k8s.io/v1 677 kind: ClusterRole 678 metadata: 679 creationTimestamp: null 680 name: cluster-admin 681 annotations: 682 rbac.authorization.kubernetes.io/autoupdate: "true" 683 rules: 684 - apiGroups: 685 - '*' 686 resources: 687 - '*' 688 verbs: 689 - '*' 690 - nonResourceURLs: 691 - '*' 692 verbs: 693 - '*'`) 694 695 fileName := randomdata.SillyName() + ".yml" 696 fileName = filepath.Join(os.TempDir(), fileName) 697 tmpfile, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 698 if err != nil { 699 return err 700 } 701 702 defer os.Remove(tmpfile.Name()) // clean up 703 704 if _, err := tmpfile.Write(content); err != nil { 705 return err 706 } 707 if err := tmpfile.Close(); err != nil { 708 return err 709 } 710 711 _, err1 := o.GetCommandOutput("", "kubectl", "create", "clusterrolebinding", "kube-system-cluster-admin", "--clusterrole", "cluster-admin", "--serviceaccount", "kube-system:default") 712 if err1 != nil { 713 if strings.Contains(err1.Error(), "AlreadyExists") { 714 log.Logger().Info("role cluster-admin already exists for the cluster") 715 } else { 716 return err1 717 } 718 } 719 720 _, err2 := o.GetCommandOutput("", "kubectl", "create", "-f", tmpfile.Name()) 721 if err2 != nil { 722 if strings.Contains(err2.Error(), "AlreadyExists") { 723 log.Logger().Info("clusterroles.rbac.authorization.k8s.io 'cluster-admin' already exists") 724 } else { 725 return err2 726 } 727 } 728 729 return nil 730 } 731 732 // GetClusterUserName returns cluster and user name 733 func (o *CommonOptions) GetClusterUserName() (string, error) { 734 username, _ := o.GetCommandOutput("", "gcloud", "config", "get-value", "core/account") 735 736 if username != "" { 737 return cluster.GetSafeUsername(username), nil 738 } 739 740 config, _, err := o.Kube().LoadConfig() 741 if err != nil { 742 return username, errors.Wrap(err, "loading kube config") 743 } 744 if config == nil || config.Contexts == nil || len(config.Contexts) == 0 { 745 return username, fmt.Errorf("No Kubernetes contexts available! Try create or connect to cluster?") 746 } 747 contextName := config.CurrentContext 748 if contextName == "" { 749 return username, fmt.Errorf("No Kubernetes context selected. Please select one (e.g. via jx context) first") 750 } 751 context := config.Contexts[contextName] 752 if context == nil { 753 return username, fmt.Errorf("No Kubernetes context available for context %s", contextName) 754 } 755 username = context.AuthInfo 756 757 return username, nil 758 } 759 760 // InstallProw installs prow 761 func (o *CommonOptions) InstallProw(useTekton bool, useExternalDNS bool, isGitOps bool, gitOpsEnvDir string, gitUsername string, valuesFiles []string) error { 762 if o.ReleaseName == "" { 763 o.ReleaseName = kube.DefaultProwReleaseName 764 } 765 766 if o.Chart == "" { 767 o.Chart = kube.ChartProw 768 } 769 770 var err error 771 if o.HMACToken == "" { 772 // why 41? seems all examples so far have a random token of 41 chars 773 o.HMACToken, err = util.RandStringBytesMaskImprSrc(41) 774 if err != nil { 775 return fmt.Errorf("cannot create a random hmac token for Prow") 776 } 777 } 778 779 if o.OAUTHToken == "" { 780 authConfigSvc, err := o.GitAuthConfigService() 781 if err != nil { 782 return errors.Wrap(err, "creating git auth config svc") 783 } 784 785 config := authConfigSvc.Config() 786 // lets assume github.com for now so ignore config.CurrentServer 787 server := config.GetOrCreateServer("https://github.com") 788 message := fmt.Sprintf("%s bot user for CI/CD pipelines (not your personal Git user):", server.Label()) 789 userAuth, err := config.PickServerUserAuth(server, message, o.BatchMode, "", o.GetIOFileHandles()) 790 if err != nil { 791 return errors.Wrap(err, "picking bot user auth") 792 } 793 o.OAUTHToken = userAuth.ApiToken 794 } 795 796 if o.Username == "" { 797 o.Username, err = o.GetClusterUserName() 798 if err != nil { 799 return errors.Wrap(err, "retrieving the cluster user name") 800 } 801 } 802 if gitUsername == "" { 803 gitUsername = o.Username 804 } 805 806 client, devNamespace, err := o.KubeClientAndDevNamespace() 807 if err != nil { 808 return errors.Wrap(err, "creating kube client") 809 } 810 811 setValues := strings.Split(o.SetValues, ",") 812 813 settings, err := o.TeamSettings() 814 if err != nil { 815 return errors.Wrap(err, "reading the team settings") 816 } 817 818 if !isGitOps { 819 err = prow.AddDummyApplication(client, devNamespace, settings) 820 if err != nil { 821 return errors.Wrap(err, "adding dummy application") 822 } 823 } 824 825 log.Logger().Infof("Installing Tekton into namespace %s", util.ColorInfo(devNamespace)) 826 827 ksecretValues := []string{} 828 if settings.HelmTemplate || settings.NoTiller || settings.HelmBinary != "helm" { 829 // lets disable tiller 830 setValues = append(setValues, "tillerNamespace=") 831 } 832 833 prowVersion := o.Version 834 835 setValues = append(setValues, 836 "auth.git.username="+gitUsername, 837 "webhook.enabled=false") 838 839 ksecretValues = append(ksecretValues, 840 "auth.git.password="+o.OAUTHToken) 841 842 err = o.Retry(2, time.Second, func() (err error) { 843 return o.InstallChartOrGitOps(isGitOps, gitOpsEnvDir, kube.DefaultTektonReleaseName, 844 kube.ChartTekton, "tekton", "", devNamespace, true, setValues, ksecretValues, valuesFiles, "") 845 }) 846 if err != nil { 847 return errors.Wrap(err, "failed to install Tekton") 848 } 849 850 setValues = append(setValues, 851 "buildnum.enabled=false", 852 "build.enabled=false", 853 "pipelinerunner.enabled=true", 854 ) 855 856 if useExternalDNS && strings.Contains(o.Domain, "nip.io") { 857 log.Logger().Warnf("Skipping install of External DNS, %s domain is not supported while using External DNS", util.ColorInfo(o.Domain)) 858 log.Logger().Warnf("External DNS only supports the use of personally operated domains") 859 } else if useExternalDNS && o.Domain != "" { 860 log.Logger().Infof("Preparing to install ExternalDNS into namespace %s", util.ColorInfo(devNamespace)) 861 log.Logger().Infof("External DNS for Jenkins X is currently only supoorted on GKE") 862 863 err = o.installExternalDNSGKE() 864 if err != nil { 865 return errors.Wrap(err, "failed to install external-dns") 866 } 867 } 868 869 log.Logger().Infof("Installing Prow into namespace %s", util.ColorInfo(devNamespace)) 870 871 for _, value := range valuesFiles { 872 log.Logger().Infof("with values file %s", util.ColorInfo(value)) 873 } 874 875 secretValues := []string{"user=" + gitUsername, "oauthToken=" + o.OAUTHToken, "hmacToken=" + o.HMACToken} 876 err = o.Retry(2, time.Second, func() (err error) { 877 return o.InstallChartOrGitOps(isGitOps, gitOpsEnvDir, o.ReleaseName, 878 o.Chart, "prow", prowVersion, devNamespace, true, setValues, secretValues, valuesFiles, "") 879 }) 880 if err != nil { 881 return errors.Wrap(err, "failed to install Prow") 882 } 883 884 if !useTekton { 885 log.Logger().Infof("\nInstalling BuildTemplates into namespace %s", util.ColorInfo(devNamespace)) 886 err = o.Retry(2, time.Second, func() (err error) { 887 return o.InstallChartOrGitOps(isGitOps, gitOpsEnvDir, kube.DefaultBuildTemplatesReleaseName, 888 kube.ChartBuildTemplates, "jxbuildtemplates", "", devNamespace, true, nil, nil, nil, "") 889 }) 890 if err != nil { 891 return errors.Wrap(err, "failed to install JX Build Templates") 892 } 893 } 894 return nil 895 } 896 897 // CreateWebhookProw create a webhook for prow using the given git provider 898 func (o *CommonOptions) CreateWebhookProw(gitURL string, gitProvider gits.GitProvider) error { 899 client, err := o.KubeClient() 900 if err != nil { 901 return err 902 } 903 ns, _, err := kube.GetDevNamespace(client, o.currentNamespace) 904 if err != nil { 905 return err 906 } 907 gitInfo, err := gits.ParseGitURL(gitURL) 908 if err != nil { 909 return err 910 } 911 baseURL, err := services.FindServiceURL(client, ns, "hook") 912 if err != nil { 913 return errors.Wrapf(err, "in namespace %s", ns) 914 } 915 if baseURL == "" { 916 return fmt.Errorf("failed to find external URL of service hook in namespace %s", ns) 917 } 918 webhookUrl := util.UrlJoin(baseURL, "hook") 919 920 hmacToken, err := o.GetHMACTokenSecret() 921 if err != nil { 922 return err 923 } 924 isInsecureSSL, err := o.IsInsecureSSLWebhooks() 925 if err != nil { 926 return errors.Wrapf(err, "failed to check if we need to setup insecure SSL webhook") 927 } 928 webhook := &gits.GitWebHookArguments{ 929 Owner: gitInfo.Organisation, 930 Repo: gitInfo, 931 URL: webhookUrl, 932 Secret: hmacToken, 933 InsecureSSL: isInsecureSSL, 934 } 935 return gitProvider.CreateWebHook(webhook) 936 } 937 938 // GetHMACTokenSecret gets the appropriate HMAC secret, for either Prow or Lighthouse 939 func (o *CommonOptions) GetHMACTokenSecret() (string, error) { 940 client, err := o.KubeClient() 941 if err != nil { 942 return "", err 943 } 944 ns, _, err := kube.GetDevNamespace(client, o.currentNamespace) 945 if err != nil { 946 return "", err 947 } 948 hmacTokenSecret, err := client.CoreV1().Secrets(ns).Get("hmac-token", metav1.GetOptions{}) 949 if err != nil && k8sErrors.IsNotFound(err) { 950 // Try again with the Lighthouse HMAC token name 951 hmacTokenSecret, err = client.CoreV1().Secrets(ns).Get("lighthouse-hmac-token", metav1.GetOptions{}) 952 } 953 if err != nil { 954 return "", err 955 } 956 return string(hmacTokenSecret.Data["hmac"]), nil 957 } 958 959 // IsProw checks if prow is available in the cluster 960 func (o *CommonOptions) IsProw() (bool, error) { 961 ns := o.devNamespace 962 jxClient, devNs, err := o.JXClientAndDevNamespace() 963 if err != nil { 964 return false, err 965 } 966 if ns == "" { 967 ns = devNs 968 } 969 env, err := kube.GetEnvironment(jxClient, ns, "dev") 970 if err != nil { 971 return false, err 972 } 973 974 return env.Spec.TeamSettings.PromotionEngine == jenkinsv1.PromotionEngineProw, nil 975 } 976 977 func (o *CommonOptions) installExternalDNSGKE() error { 978 979 if o.ReleaseName == "" { 980 o.ReleaseName = kube.DefaultExternalDNSReleaseName 981 } 982 983 if o.Chart == "" { 984 o.Chart = kube.ChartExternalDNS 985 } 986 987 var err error 988 989 client, err := o.KubeClient() 990 if err != nil { 991 return err 992 } 993 994 devNamespace, _, err := kube.GetDevNamespace(client, o.currentNamespace) 995 if err != nil { 996 return fmt.Errorf("cannot find a dev team namespace to get existing exposecontroller config from. %v", err) 997 } 998 999 clusterName, err := cluster.Name(o.Kube()) 1000 if err != nil { 1001 return errors.Wrap(err, "failed to get clusterName") 1002 } 1003 1004 err = o.helm.AddRepo(kube.ChartOwnerExternalDNS, kube.ChartURLExternalDNS, "", "") 1005 if err != nil { 1006 return errors.Wrapf(err, "adding helm repo") 1007 } 1008 1009 googleProjectID, err := gke.GetCurrentProject() 1010 if err != nil { 1011 return errors.Wrap(err, "failed to get project") 1012 } 1013 1014 // Create managed zone for external dns if it doesn't exist 1015 var nameServers = []string{} 1016 gcloud := o.GCloud() 1017 err = gcloud.CreateManagedZone(googleProjectID, o.Domain) 1018 if err != nil { 1019 return errors.Wrap(err, "while trying to creating a CloudDNS managed zone for external-dns") 1020 } 1021 _, nameServers, err = gcloud.GetManagedZoneNameServers(googleProjectID, o.Domain) 1022 if err != nil { 1023 return errors.Wrap(err, "while trying to retrieve the managed zone name servers for external-dns") 1024 } 1025 1026 o.NameServers = nameServers 1027 1028 var gcpServiceAccountSecretName string 1029 gcpServiceAccountSecretName, err = externaldns.CreateExternalDNSGCPServiceAccount(o.GCloud(), client, 1030 kube.DefaultExternalDNSReleaseName, devNamespace, clusterName, googleProjectID) 1031 if err != nil { 1032 return errors.Wrap(err, "failed to create service account for ExternalDNS") 1033 } 1034 1035 err = gcloud.EnableAPIs(googleProjectID, "dns") 1036 if err != nil { 1037 return errors.Wrap(err, "unable to enable 'dns' api") 1038 } 1039 1040 sources := []string{ 1041 "ingress", 1042 } 1043 1044 sourcesList := "{" + strings.Join(sources, ", ") + "}" 1045 1046 values := []string{ 1047 "provider=" + "google", 1048 "sources=" + sourcesList, 1049 "rbac.create=" + "true", 1050 "google.serviceAccountSecret=" + gcpServiceAccountSecretName, 1051 "txt-owner-id=" + "jx-external-dns", 1052 "domainFilters=" + "{" + o.Domain + "}", 1053 } 1054 1055 log.Logger().Infof("\nInstalling External DNS into namespace %s", util.ColorInfo(devNamespace)) 1056 err = o.Retry(2, time.Second, func() (err error) { 1057 return o.InstallChartOrGitOps(false, "", kube.DefaultExternalDNSReleaseName, kube.ChartExternalDNS, 1058 kube.ChartExternalDNS, "", devNamespace, true, values, nil, nil, "") 1059 }) 1060 if err != nil { 1061 return errors.Wrap(err, "failed to install External DNS") 1062 } 1063 1064 return nil 1065 }