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

     1  package upgrade
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
     8  	"github.com/olli-ai/jx/v2/pkg/cmd/update"
     9  
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/olli-ai/jx/v2/pkg/gits"
    14  	"github.com/olli-ai/jx/v2/pkg/kube/pki"
    15  	"github.com/olli-ai/jx/v2/pkg/kube/services"
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/jenkins-x/jx-logging/pkg/log"
    19  	"github.com/olli-ai/jx/v2/pkg/kube"
    20  	"github.com/olli-ai/jx/v2/pkg/util"
    21  	survey "gopkg.in/AlecAivazis/survey.v1"
    22  	v1 "k8s.io/api/core/v1"
    23  	"k8s.io/api/extensions/v1beta1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  )
    26  
    27  const (
    28  	exposecontroller = "exposecontroller"
    29  
    30  	certsIssuedReadyTimeout = 5 * time.Minute
    31  )
    32  
    33  // UpgradeIngressOptions the options for the create spring command
    34  type UpgradeIngressOptions struct {
    35  	*opts.CommonOptions
    36  
    37  	SkipCertManager     bool
    38  	Cluster             bool
    39  	Force               bool
    40  	Namespaces          []string
    41  	Version             string
    42  	TargetNamespaces    []string
    43  	Services            []string
    44  	SkipResourcesUpdate bool
    45  	WaitForCerts        bool
    46  	ConfigNamespace     string
    47  
    48  	IngressConfig kube.IngressConfig
    49  }
    50  
    51  // Run implements the command
    52  func (o *UpgradeIngressOptions) Run() error {
    53  	client, devNamespace, err := o.KubeClientAndDevNamespace()
    54  	if err != nil {
    55  		return fmt.Errorf("cannot connect to Kubernetes cluster: %v", err)
    56  	}
    57  
    58  	jxClient, ns, err := o.JXClient()
    59  	if err != nil {
    60  		return errors.Wrap(err, "error obtaining the JX Client")
    61  	}
    62  
    63  	devEnv, err := jxClient.JenkinsV1().Environments(ns).Get("dev", metav1.GetOptions{})
    64  	if err != nil {
    65  		return errors.Wrap(err, "error obtaining the ")
    66  	}
    67  
    68  	//Todo: Possibly used with jx install, if so, it should not be removed now
    69  	if devEnv.Spec.TeamSettings.BootRequirements != "" {
    70  		return errors.New(`jx upgrade ingress shouldn't be used in a Jenkins X Boot cluster.
    71  For more documentation on Ingress configuration see: [https://jenkins-x.io/docs/getting-started/setup/boot/#ingress](https://jenkins-x.io/docs/getting-started/setup/boot/#ingress)`)
    72  	}
    73  
    74  	previousWebHookEndpoint := ""
    75  	if !o.SkipResourcesUpdate {
    76  		previousWebHookEndpoint, err = o.GetWebHookEndpoint()
    77  		if err != nil {
    78  			return errors.Wrap(err, "getting the webhook endpoint")
    79  		}
    80  	}
    81  
    82  	// if existing ingress exist in the namespaces ask do you want to delete them?
    83  	ingressToDelete, err := o.getExistingIngressRules()
    84  	if err != nil {
    85  		return errors.Wrap(err, "getting the existing ingress rules")
    86  	}
    87  
    88  	// wizard to ask for config values
    89  	err = o.confirmExposecontrollerConfig()
    90  	if err != nil {
    91  		return errors.Wrap(err, ""+
    92  			"configure exposecontroller")
    93  	}
    94  
    95  	// confirm values
    96  	if !o.BatchMode {
    97  		if answer, err := util.Confirm(fmt.Sprintf("Using config values %v, ok?", o.IngressConfig), true, "", o.GetIOFileHandles()); err != nil {
    98  			return err
    99  		} else if !answer {
   100  			log.Logger().Infof("Terminating")
   101  			return nil
   102  		}
   103  	}
   104  
   105  	// save details to a configmap
   106  	_, err = kube.SaveAsConfigMap(client, kube.ConfigMapIngressConfig, devNamespace, o.IngressConfig)
   107  	if err != nil {
   108  		return errors.Wrap(err, "saving ingress config into a configmap")
   109  	}
   110  
   111  	// ensure cert-manager is installed
   112  	if o.IngressConfig.TLS {
   113  		err = o.ensureCertmanagerSetup()
   114  		if err != nil {
   115  			return errors.Wrap(err, "ensure cert-manager setup")
   116  		}
   117  	}
   118  
   119  	// clear the service annotations
   120  	err = o.CleanServiceAnnotations(o.Services...)
   121  	if err != nil {
   122  		return errors.Wrap(err, "cleaning service annotations")
   123  	}
   124  
   125  	// annotate any service that has expose=true with correct cert-manager staging / prod annotation
   126  	var services []*v1.Service
   127  	if o.IngressConfig.TLS {
   128  		services, err = o.AnnotateExposedServicesWithCertManager(o.Services...)
   129  		if err != nil {
   130  			return errors.Wrap(err, "annotating the exposed service with cert-manager")
   131  		}
   132  	}
   133  
   134  	// remove the ingress resource in order to allow the ingress-controller to recreate them
   135  	for name, namespace := range ingressToDelete {
   136  		log.Logger().Infof("Deleting ingress %s/%s", namespace, name)
   137  		err := client.ExtensionsV1beta1().Ingresses(namespace).Delete(name, &metav1.DeleteOptions{})
   138  		if err != nil {
   139  			return fmt.Errorf("cannot delete ingress rule %s in namespace %s: %v", name, namespace, err)
   140  		}
   141  	}
   142  
   143  	// start watching and collecting ready certificates
   144  	var notReadyCertsCh <-chan map[pki.Certificate]bool
   145  	ctx, cancel := context.WithTimeout(context.Background(), certsIssuedReadyTimeout)
   146  	defer cancel()
   147  	if o.IngressConfig.TLS && o.WaitForCerts {
   148  		certsCh, err := o.watchReadyCertificates(ctx)
   149  		if err != nil {
   150  			return errors.Wrap(err, "start watching ready certificates")
   151  		}
   152  		notReadyCertsCh = o.startCollectingReadyCertificates(ctx, services, certsCh)
   153  	}
   154  
   155  	// run the expose-controller to create the ingress rules
   156  	err = o.createIngressRules()
   157  	if err != nil {
   158  		return errors.Wrap(err, "creating the ingress rules")
   159  	}
   160  
   161  	log.Logger().Info("Ingress rules recreated")
   162  
   163  	if o.IngressConfig.TLS {
   164  		if o.WaitForCerts {
   165  			log.Logger().Info("Waiting for TLS certificates to be issued...")
   166  			select {
   167  			case certs := <-notReadyCertsCh:
   168  				cancel()
   169  				if len(certs) == 0 {
   170  					log.Logger().Info("All TLS certificates are ready")
   171  				} else {
   172  					log.Logger().Warn("Following TLS certificates are not ready:")
   173  					for cert := range certs {
   174  						log.Logger().Warnf("%s", cert)
   175  					}
   176  					return errors.New("not all TLS certificates are ready")
   177  				}
   178  			case <-ctx.Done():
   179  				log.Logger().Warn("Timeout reached while waiting for TLS certificates to be ready")
   180  			}
   181  		} else {
   182  			log.Logger().Warn("It can take around 5 minutes for Cert Manager to get certificates from Lets Encrypt and update Ingress rules")
   183  			log.Logger().Info("Use the following commands to diagnose any issues:")
   184  			log.Logger().Infof("jx logs %s -n %s", pki.CertManagerDeployment, pki.CertManagerNamespace)
   185  			log.Logger().Info("kubectl describe certificates")
   186  			log.Logger().Info("kubectl describe issuers\n")
   187  		}
   188  	}
   189  
   190  	// update all resource dependent to the ingress endpoints
   191  	if !o.SkipResourcesUpdate {
   192  		err = o.updateResources(previousWebHookEndpoint)
   193  		if err != nil {
   194  			return errors.Wrap(err, "unable to update resources for webhook change")
   195  		}
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  func (o *UpgradeIngressOptions) watchReadyCertificates(ctx context.Context) (<-chan pki.Certificate, error) {
   202  	client, err := o.CertManagerClient()
   203  	if err != nil {
   204  		return nil, errors.Wrap(err, "creating the cert-manager client")
   205  	}
   206  
   207  	// watch certificates across all namesapces
   208  	namespace := ""
   209  	certsCh, err := pki.WatchCertificatesIssuedReady(ctx, client, namespace)
   210  	if err != nil {
   211  		return nil, errors.Wrap(err, "start watching certificates")
   212  	}
   213  	return certsCh, nil
   214  }
   215  
   216  func (o *UpgradeIngressOptions) startCollectingReadyCertificates(ctx context.Context, services []*v1.Service,
   217  	certsCh <-chan pki.Certificate) <-chan map[pki.Certificate]bool {
   218  	resultCh := make(chan map[pki.Certificate]bool)
   219  	go func() {
   220  		certs := pki.ToCertificates(services)
   221  		certsMap := make(map[pki.Certificate]bool)
   222  		for _, cert := range certs {
   223  			certsMap[cert] = true
   224  		}
   225  
   226  		log.Logger().Infof("Expecting certificates: %v", certs)
   227  
   228  		for {
   229  			select {
   230  			case cert := <-certsCh:
   231  				log.Logger().Infof("Ready Cert: %s", util.ColorInfo(cert))
   232  				delete(certsMap, cert)
   233  				// check if all expected certificates are received
   234  				if len(certsMap) == 0 {
   235  					// send a map with no certificates to indicate success
   236  					resultCh <- certsMap
   237  					return
   238  				}
   239  			case <-ctx.Done():
   240  				// send the current state of the certificates map
   241  				resultCh <- certsMap
   242  				return
   243  			}
   244  		}
   245  	}()
   246  	return resultCh
   247  }
   248  
   249  func (o *UpgradeIngressOptions) updateResources(previousWebHookEndpoint string) error {
   250  	_, _, err := o.JXClient()
   251  	if err != nil {
   252  		return errors.Wrap(err, "failed to get jxclient")
   253  	}
   254  
   255  	isProwEnabled, err := o.IsProw()
   256  	if err != nil {
   257  		return errors.Wrap(err, "checking if is prow")
   258  	}
   259  
   260  	if !isProwEnabled {
   261  		err = o.UpdateJenkinsURL(o.TargetNamespaces)
   262  		if err != nil {
   263  			return errors.Wrap(err, "upgrade jenkins URL")
   264  		}
   265  	}
   266  
   267  	updatedWebHookEndpoint, err := o.GetWebHookEndpoint()
   268  	if err != nil {
   269  		return errors.Wrap(err, "retrieving the webhook endpoint")
   270  	}
   271  
   272  	log.Logger().Infof("Previous webhook endpoint %s", previousWebHookEndpoint)
   273  	log.Logger().Infof("Updated webhook endpoint %s", updatedWebHookEndpoint)
   274  	updateWebHooks := true
   275  	if !o.BatchMode {
   276  		if answer, err := util.Confirm("Do you want to update all existing webhooks?", true, "", o.GetIOFileHandles()); err != nil {
   277  			return err
   278  		} else if !answer {
   279  			updateWebHooks = false
   280  		}
   281  	}
   282  
   283  	if updateWebHooks {
   284  		err := o.updateWebHooks(previousWebHookEndpoint, updatedWebHookEndpoint)
   285  		if err != nil {
   286  			return errors.Wrap(err, "unable to update webhooks")
   287  		}
   288  	}
   289  	return nil
   290  }
   291  
   292  func (o *UpgradeIngressOptions) isIngressForServices(ingress *v1beta1.Ingress) bool {
   293  	services := o.Services
   294  	if len(services) == 0 {
   295  		// allow all ingresses if no services filter is defined
   296  		return true
   297  	}
   298  	rules := ingress.Spec.Rules
   299  	for _, rule := range rules {
   300  		http := rule.IngressRuleValue.HTTP
   301  		if http == nil {
   302  			continue
   303  		}
   304  		for _, path := range http.Paths {
   305  			service := path.Backend.ServiceName
   306  			i := util.StringArrayIndex(services, service)
   307  			if i >= 0 {
   308  				return true
   309  			}
   310  		}
   311  	}
   312  	return false
   313  }
   314  
   315  func (o *UpgradeIngressOptions) getExistingIngressRules() (map[string]string, error) {
   316  	surveyOpts := survey.WithStdio(o.In, o.Out, o.Err)
   317  	existingIngressNames := map[string]string{}
   318  	client, currentNamespace, err := o.KubeClientAndNamespace()
   319  	if err != nil {
   320  		return existingIngressNames, err
   321  	}
   322  	var confirmMessage string
   323  	if o.Cluster {
   324  		confirmMessage = "Existing ingress rules found in the cluster.  Confirm to delete all and recreate them"
   325  
   326  		ings, err := client.ExtensionsV1beta1().Ingresses("").List(metav1.ListOptions{})
   327  		if err != nil {
   328  			return existingIngressNames, fmt.Errorf("cannot list all ingresses in cluster: %v", err)
   329  		}
   330  		for _, i := range ings.Items {
   331  			item := i
   332  			if item.Annotations[services.ExposeGeneratedByAnnotation] == exposecontroller {
   333  				if o.isIngressForServices(&item) {
   334  					existingIngressNames[item.Name] = item.Namespace
   335  				}
   336  			}
   337  		}
   338  
   339  		nsList, err := client.CoreV1().Namespaces().List(metav1.ListOptions{})
   340  		for _, n := range nsList.Items {
   341  			o.TargetNamespaces = append(o.TargetNamespaces, n.Name)
   342  		}
   343  
   344  	} else if len(o.Namespaces) > 0 {
   345  		confirmMessage = fmt.Sprintf("Existing ingress rules found in namespaces %v namespace.  Confirm to delete and recreate them", o.Namespaces)
   346  		// loop round each
   347  		for _, n := range o.Namespaces {
   348  			ings, err := client.ExtensionsV1beta1().Ingresses(n).List(metav1.ListOptions{})
   349  			if err != nil {
   350  				return existingIngressNames, fmt.Errorf("cannot list all ingresses in cluster: %v", err)
   351  			}
   352  			for _, i := range ings.Items {
   353  				item := i
   354  				if i.Annotations[services.ExposeGeneratedByAnnotation] == exposecontroller {
   355  					if o.isIngressForServices(&item) {
   356  						existingIngressNames[item.Name] = item.Namespace
   357  					}
   358  				}
   359  			}
   360  			o.TargetNamespaces = append(o.TargetNamespaces, n)
   361  		}
   362  	} else {
   363  		confirmMessage = "Existing ingress rules found in current namespace.  Confirm to delete and recreate them"
   364  		// fall back to current ns only
   365  		log.Logger().Infof("Looking for existing ingress rules in current namespace %s", currentNamespace)
   366  
   367  		ings, err := client.ExtensionsV1beta1().Ingresses(currentNamespace).List(metav1.ListOptions{})
   368  		if err != nil {
   369  			return existingIngressNames, fmt.Errorf("cannot list all ingresses in cluster: %v", err)
   370  		}
   371  		for _, i := range ings.Items {
   372  			item := i
   373  			if i.Annotations[services.ExposeGeneratedByAnnotation] == exposecontroller {
   374  				if o.isIngressForServices(&item) {
   375  					existingIngressNames[item.Name] = item.Namespace
   376  				}
   377  			}
   378  		}
   379  		o.TargetNamespaces = append(o.TargetNamespaces, currentNamespace)
   380  	}
   381  
   382  	if len(existingIngressNames) == 0 {
   383  		return existingIngressNames, nil
   384  	}
   385  
   386  	if !o.BatchMode {
   387  		confirm := &survey.Confirm{
   388  			Message: confirmMessage,
   389  			Default: true,
   390  		}
   391  		flag := true
   392  		err = survey.AskOne(confirm, &flag, nil, surveyOpts)
   393  		if err != nil {
   394  			return existingIngressNames, err
   395  		}
   396  		if !flag {
   397  			return existingIngressNames, errors.New("Not able to automatically delete existing ingress rules.  Either delete manually or change the scope the command should run in")
   398  		}
   399  	}
   400  
   401  	return existingIngressNames, nil
   402  }
   403  
   404  func (o *UpgradeIngressOptions) confirmExposecontrollerConfig() error {
   405  	// get current ingress config to use as existing defaults
   406  	client, currentNamespace, err := o.KubeClientAndNamespace()
   407  	if err != nil {
   408  		return err
   409  	}
   410  
   411  	// select the namespace from where to read the ingress-config config map
   412  	devNamespace, _, err := kube.GetDevNamespace(client, currentNamespace)
   413  	if err != nil {
   414  		return fmt.Errorf("cannot find a dev team namespace to get existing exposecontroller config from. %v", err)
   415  	}
   416  	configNamespace := devNamespace
   417  	if o.ConfigNamespace != "" {
   418  		configNamespace = o.ConfigNamespace
   419  	}
   420  
   421  	// Overwrites the ingress config with the values from config map only if this config map exists
   422  	urlTemplate := o.IngressConfig.UrlTemplate
   423  	domain := o.IngressConfig.Domain
   424  	ic, err := kube.GetIngressConfig(client, configNamespace)
   425  	if err == nil {
   426  		// TODO: Add the rest of the Ingress-related info as arguments and assign to `o.IngressConfig` only those that were not specified, instead of the whole `ic`.`
   427  		o.IngressConfig = ic
   428  		if urlTemplate != "" {
   429  			// Template must be surrounded by quotes
   430  			if !strings.HasPrefix(urlTemplate, "\"") && !strings.HasPrefix(urlTemplate, "'") {
   431  				urlTemplate = "\"" + urlTemplate + "\""
   432  			}
   433  			o.IngressConfig.UrlTemplate = urlTemplate
   434  		}
   435  		if domain != "" {
   436  			o.IngressConfig.Domain = domain
   437  		}
   438  	}
   439  
   440  	if o.BatchMode {
   441  		if err := checkEmtptyIngressConfig(o.IngressConfig.Exposer, "exposer"); err != nil {
   442  			return err
   443  		}
   444  		if err := checkEmtptyIngressConfig(o.IngressConfig.Domain, "domain"); err != nil {
   445  			return err
   446  		}
   447  		if o.IngressConfig.TLS {
   448  			if err := checkEmtptyIngressConfig(o.IngressConfig.Issuer, "issuer"); err != nil {
   449  				return err
   450  			}
   451  			if err := checkEmtptyIngressConfig(o.IngressConfig.Email, "email"); err != nil {
   452  				return err
   453  			}
   454  		}
   455  	} else {
   456  		o.IngressConfig.Exposer, err = util.PickNameWithDefault([]string{"Ingress", "Route"}, "Expose type", o.IngressConfig.Exposer, "", o.GetIOFileHandles())
   457  		if err != nil {
   458  			return err
   459  		}
   460  
   461  		o.IngressConfig.Domain, err = util.PickValue("Domain:", o.IngressConfig.Domain, true, "", o.GetIOFileHandles())
   462  		if err != nil {
   463  			return err
   464  		}
   465  
   466  		if !strings.HasSuffix(o.IngressConfig.Domain, "nip.io") {
   467  			if !o.BatchMode {
   468  				o.IngressConfig.TLS, err = util.Confirm("If your network is publicly available would you like to enable cluster wide TLS?", true, "Enables cert-manager and configures TLS with signed certificates from LetsEncrypt", o.GetIOFileHandles())
   469  				if err != nil {
   470  					return err
   471  				}
   472  			}
   473  
   474  			if o.IngressConfig.TLS {
   475  				log.Logger().Infof("If testing LetsEncrypt you should use staging as you may be rate limited using production.")
   476  				clusterIssuer, err := util.PickNameWithDefault([]string{"staging", "production"}, "Use LetsEncrypt staging or production?", "production", "", o.GetIOFileHandles())
   477  				// if the cluster issuer is production the string needed by letsencrypt is prod
   478  				if clusterIssuer == "production" {
   479  					clusterIssuer = "prod"
   480  				}
   481  				if err != nil {
   482  					return err
   483  				}
   484  				o.IngressConfig.Issuer = "letsencrypt-" + clusterIssuer
   485  
   486  				if o.IngressConfig.Email == "" {
   487  					email1, err := o.GetCommandOutput("", "git", "config", "user.email")
   488  					if err != nil {
   489  						return err
   490  					}
   491  
   492  					o.IngressConfig.Email = strings.TrimSpace(email1)
   493  				}
   494  
   495  				o.IngressConfig.Email, err = util.PickValue("Email address to register with LetsEncrypt:", o.IngressConfig.Email, true, "", o.GetIOFileHandles())
   496  				if err != nil {
   497  					return err
   498  				}
   499  			}
   500  		}
   501  		o.IngressConfig.UrlTemplate, err = util.PickValue("URLTemplate (press <Enter> to keep the current value):", o.IngressConfig.UrlTemplate, false, "", o.GetIOFileHandles())
   502  		if err != nil {
   503  			return err
   504  		}
   505  	}
   506  
   507  	return nil
   508  }
   509  
   510  func checkEmtptyIngressConfig(value string, name string) error {
   511  	if value == "" {
   512  		return fmt.Errorf("%v config value must not be empty", name)
   513  	}
   514  	return nil
   515  }
   516  
   517  func (o *UpgradeIngressOptions) createIngressRules() error {
   518  	client, currentNamespace, err := o.KubeClientAndNamespace()
   519  	if err != nil {
   520  		return err
   521  	}
   522  	certmngClient, err := o.CertManagerClient()
   523  	if err != nil {
   524  		return errors.Wrap(err, "creating the cert-manager client")
   525  	}
   526  	devNamespace, _, err := kube.GetDevNamespace(client, currentNamespace)
   527  	if err != nil {
   528  		return fmt.Errorf("cannot find a dev team namespace to get existing exposecontroller config from. %v", err)
   529  	}
   530  	for _, n := range o.TargetNamespaces {
   531  		o.CleanExposecontrollerReources(n)
   532  
   533  		if len(o.Services) > 0 {
   534  			services, err := services.GetServicesByName(client, n, o.Services)
   535  			if err != nil {
   536  				return err
   537  			}
   538  			certs := pki.ToCertificates(services)
   539  			err = pki.CleanCerts(client, certmngClient, n, certs)
   540  			if err != nil {
   541  				return err
   542  			}
   543  		} else {
   544  			err := pki.CleanAllCerts(client, certmngClient, n)
   545  			if err != nil {
   546  				return err
   547  			}
   548  		}
   549  
   550  		err := pki.CreateCertManagerResources(certmngClient, n, o.IngressConfig)
   551  		if err != nil {
   552  			return err
   553  		}
   554  
   555  		err = o.RunExposecontroller(devNamespace, n, o.IngressConfig, o.Services...)
   556  		if err != nil {
   557  			return err
   558  		}
   559  	}
   560  	return nil
   561  }
   562  
   563  func (o *UpgradeIngressOptions) ensureCertmanagerSetup() error {
   564  	if !o.SkipCertManager {
   565  		return o.EnsureCertManager()
   566  	}
   567  	return nil
   568  }
   569  
   570  // AnnotateExposedServicesWithCertManager annotates exposed services with cert manager
   571  func (o *UpgradeIngressOptions) AnnotateExposedServicesWithCertManager(svcs ...string) ([]*v1.Service, error) {
   572  	result := make([]*v1.Service, 0)
   573  	client, err := o.KubeClient()
   574  	if err != nil {
   575  		return result, err
   576  	}
   577  	for _, n := range o.TargetNamespaces {
   578  		issuer := o.IngressConfig.Issuer
   579  		if issuer == "" {
   580  			return result, fmt.Errorf("no issuer was configured for cert manager")
   581  		}
   582  		clusterIssuer := o.IngressConfig.ClusterIssuer
   583  		services, err := services.AnnotateServicesWithCertManagerIssuer(client, n, issuer, clusterIssuer, svcs...)
   584  		if err != nil {
   585  			return result, err
   586  		}
   587  		result = append(result, services...)
   588  	}
   589  	return result, nil
   590  }
   591  
   592  // CleanServiceAnnotations cleans service annotations
   593  func (o *UpgradeIngressOptions) CleanServiceAnnotations(svcs ...string) error {
   594  	client, err := o.KubeClient()
   595  	if err != nil {
   596  		return err
   597  	}
   598  	for _, n := range o.TargetNamespaces {
   599  		err := services.CleanServiceAnnotations(client, n, svcs...)
   600  		if err != nil {
   601  			return err
   602  		}
   603  	}
   604  
   605  	return nil
   606  }
   607  
   608  func (o *UpgradeIngressOptions) updateWebHooks(oldHookEndpoint string, newHookEndpoint string) error {
   609  	if oldHookEndpoint == newHookEndpoint && !o.Force {
   610  		log.Logger().Infof("Webhook URL unchanged. Use %s to force updating", util.ColorInfo("--force"))
   611  		return nil
   612  	}
   613  
   614  	log.Logger().Infof("Updating all webHooks from %s to %s", util.ColorInfo(oldHookEndpoint), util.ColorInfo(newHookEndpoint))
   615  
   616  	updateWebHook := update.UpdateWebhooksOptions{
   617  		CommonOptions: o.CommonOptions,
   618  	}
   619  
   620  	authConfigService, err := o.GitAuthConfigService()
   621  	if err != nil {
   622  		return errors.Wrap(err, "failed to create git auth service")
   623  	}
   624  
   625  	gitServer := authConfigService.Config().CurrentServer
   626  	git, err := o.GitProviderForGitServerURL(gitServer, "github", "")
   627  	if err != nil {
   628  		return errors.Wrap(err, "unable to determine git provider")
   629  	}
   630  
   631  	// user
   632  	userAuth := git.UserAuth()
   633  	username := userAuth.Username
   634  
   635  	// organisation
   636  	organisation, err := gits.PickOrganisation(git, username, o.GetIOFileHandles())
   637  	updateWebHook.Username = ReturnUserNameIfPicked(organisation, username)
   638  	if err != nil {
   639  		return errors.Wrap(err, "unable to determine git provider")
   640  	}
   641  
   642  	if o.CommonOptions.Verbose {
   643  		log.Logger().Infof("Updating all webHooks for org %s and/or username %s", organisation, updateWebHook.Username)
   644  	}
   645  
   646  	updateWebHook.PreviousHookUrl = oldHookEndpoint
   647  	updateWebHook.Org = organisation
   648  	updateWebHook.DryRun = false
   649  
   650  	return updateWebHook.Run()
   651  }
   652  
   653  // ReturnUserNameIfPicked checks to see if PickOrganisation returned ""
   654  // this will happen if you picked the username as organization
   655  // which is valid in this scenario and allows code further down
   656  // to select the appropriate API to call (user or org based)
   657  func ReturnUserNameIfPicked(organisation string, username string) string {
   658  	if organisation == "" && username != "" {
   659  		return username
   660  	}
   661  	return ""
   662  }