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  }