func (o Options) Run(ctx context.Context) (int, error) {
	spec, err := downwardapi.ResolveSpecFromEnv()
	if err != nil {
		return 0, fmt.Errorf("could not resolve job spec: %v", err)
	}

	ctx, cancel := context.WithCancel(ctx)

	// If we are being asked to terminate by the kubelet but we have
	// NOT seen the test process exit cleanly, we need a to start
	// uploading artifacts to GCS immediately. If we notice the process
	// exit while doing this best-effort upload, we can race with the
	// second upload but we can tolerate this as we'd rather get SOME
	// data into GCS than attempt to cancel these uploads and get none.
	interrupt := make(chan os.Signal)
	signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
	go func() {
		select {
		case s := <-interrupt:
			logrus.Errorf("Received an interrupt: %s, cancelling...", s)
			cancel()
		case <-ctx.Done():
		}
	}()

	if o.DeprecatedWrapperOptions != nil {
		// This only fires if the prowjob controller and sidecar are at different commits
		logrus.Warnf("Using deprecated wrapper_options instead of entries. Please update prow/pod-utils/decorate before June 2019")
	}
	entries := o.entries()
	passed, aborted, failures := wait(ctx, entries)

	cancel()
	// If we are being asked to terminate by the kubelet but we have
	// seen the test process exit cleanly, we need a chance to upload
	// artifacts to GCS. The only valid way for this program to exit
	// after a SIGINT or SIGTERM in this situation is to finish
	// uploading, so we ignore the signals.
	signal.Ignore(os.Interrupt, syscall.SIGTERM)

	buildLog := logReader(entries)
	metadata := combineMetadata(entries)
	return failures, o.doUpload(spec, passed, aborted, metadata, buildLog)
}

// Run will watch for the process being wrapped to exit
// and then post the status of that process and any artifacts
// to cloud storage.

Run will watch for the process being wrapped to exit and then post the status of that process and any artifacts to cloud storage .


func (s *Storage) AddConfig(conf common.ResourcesConfig) error {
	return s.configs.Add(conf)
}

func ( s * Storage ) AddConfig ( conf common . ResourcesConfig ) error { return s . configs . Add ( conf ) }


func (s *Storage) DeleteConfig(name string) error {
	return s.configs.Delete(name)
}

func ( s * Storage ) DeleteConfig ( name string ) error { return s . configs . Delete ( name ) }

// DeleteConfig deletes an existing config if it exists or fail otherwise


func (s *Storage) UpdateConfig(conf common.ResourcesConfig) error {
	return s.configs.Update(conf)
}

func ( s * Storage ) UpdateConfig ( conf common . ResourcesConfig ) error { return s . configs . Update ( conf ) }

// UpdateConfig updates a given if it exists or fail otherwise


func (s *Storage) GetConfig(name string) (common.ResourcesConfig, error) {
	i, err := s.configs.Get(name)
	if err != nil {
		return common.ResourcesConfig{}, err
	}
	var conf common.ResourcesConfig
	conf, err = common.ItemToResourcesConfig(i)
	if err != nil {
		return common.ResourcesConfig{}, err
	}
	return conf, nil
}

func ( s * Storage ) GetConfig ( name string ) ( common . ResourcesConfig , error ) { i , err := s . configs . Get ( name ) if err != nil { return common . ResourcesConfig { } , err } var conf common . ResourcesConfig conf , err = common . ItemToResourcesConfig ( i ) if err != nil { return common . ResourcesConfig { } , err } return conf , nil }

// GetConfig returns an existing if it exists errors out otherwise


func (s *Storage) GetConfigs() ([]common.ResourcesConfig, error) {
	var configs []common.ResourcesConfig
	items, err := s.configs.List()
	if err != nil {
		return configs, err
	}
	for _, i := range items {
		var conf common.ResourcesConfig
		conf, err = common.ItemToResourcesConfig(i)
		if err != nil {
			return nil, err
		}
		configs = append(configs, conf)
	}
	return configs, nil
}

func ( s * Storage ) GetConfigs ( ) ( [ ] common . ResourcesConfig , error ) { var configs [ ] common . ResourcesConfig items , err := s . configs . List ( ) if err != nil { return configs , err } for _ , i := range items { var conf common . ResourcesConfig conf , err = common . ItemToResourcesConfig ( i ) if err != nil { return nil , err } configs = append ( configs , conf ) } return configs , nil }


func (s *Storage) SyncConfigs(newConfigs []common.ResourcesConfig) error {
	s.configsLock.Lock()
	defer s.configsLock.Unlock()

	currentConfigs, err := s.GetConfigs()
	if err != nil {
		logrus.WithError(err).Error("cannot find configs")
		return err
	}

	currentSet := mapset.NewSet()
	newSet := mapset.NewSet()
	toUpdate := mapset.NewSet()

	configs := map[string]common.ResourcesConfig{}

	for _, c := range currentConfigs {
		currentSet.Add(c.Name)
		configs[c.Name] = c
	}

	for _, c := range newConfigs {
		newSet.Add(c.Name)
		if old, exists := configs[c.Name]; exists {
			if !reflect.DeepEqual(old, c) {
				toUpdate.Add(c.Name)
				configs[c.Name] = c
			}
		} else {
			configs[c.Name] = c
		}
	}

	var finalError error

	toDelete := currentSet.Difference(newSet)
	toAdd := newSet.Difference(currentSet)

	for _, n := range toDelete.ToSlice() {
		logrus.Infof("Deleting config %s", n.(string))
		if err := s.DeleteConfig(n.(string)); err != nil {
			logrus.WithError(err).Errorf("failed to delete config %s", n)
			finalError = multierror.Append(finalError, err)
		}
	}

	for _, n := range toAdd.ToSlice() {
		rc := configs[n.(string)]
		logrus.Infof("Adding config %s", n.(string))
		if err := s.AddConfig(rc); err != nil {
			logrus.WithError(err).Errorf("failed to create resources %s", n)
			finalError = multierror.Append(finalError, err)
		}
	}

	for _, n := range toUpdate.ToSlice() {
		rc := configs[n.(string)]
		logrus.Infof("Updating config %s", n.(string))
		if err := s.UpdateConfig(rc); err != nil {
			logrus.WithError(err).Errorf("failed to update resources %s", n)
			finalError = multierror.Append(finalError, err)
		}
	}

	return finalError
}

func ( s * Storage ) SyncConfigs ( newConfigs [ ] common . ResourcesConfig ) error { s . configsLock . Lock ( ) defer s . configsLock . Unlock ( ) currentConfigs , err := s . GetConfigs ( ) if err != nil { logrus . WithError ( err ) . Error ( " " ) return err } currentSet := mapset . NewSet ( ) newSet := mapset . NewSet ( ) toUpdate := mapset . NewSet ( ) configs := map [ string ] common . ResourcesConfig { } for _ , c := range currentConfigs { currentSet . Add ( c . Name ) configs [ c . Name ] = c } for _ , c := range newConfigs { newSet . Add ( c . Name ) if old , exists := configs [ c . Name ] ; exists { if ! reflect . DeepEqual ( old , c ) { toUpdate . Add ( c . Name ) configs [ c . Name ] = c } } else { configs [ c . Name ] = c } } var finalError error toDelete := currentSet . Difference ( newSet ) toAdd := newSet . Difference ( currentSet ) for _ , n := range toDelete . ToSlice ( ) { logrus . Infof ( " " , n . ( string ) ) if err := s . DeleteConfig ( n . ( string ) ) ; err != nil { logrus . WithError ( err ) . Errorf ( " " , n ) finalError = multierror . Append ( finalError , err ) } } for _ , n := range toAdd . ToSlice ( ) { rc := configs [ n . ( string ) ] logrus . Infof ( " " , n . ( string ) ) if err := s . AddConfig ( rc ) ; err != nil { logrus . WithError ( err ) . Errorf ( " " , n ) finalError = multierror . Append ( finalError , err ) } } for _ , n := range toUpdate . ToSlice ( ) { rc := configs [ n . ( string ) ] logrus . Infof ( " " , n . ( string ) ) if err := s . UpdateConfig ( rc ) ; err != nil { logrus . WithError ( err ) . Errorf ( " " , n ) finalError = multierror . Append ( finalError , err ) } } return finalError }


func addKnownTypes(scheme *runtime.Scheme) error {
	scheme.AddKnownTypes(SchemeGroupVersion,
		&ProwJob{},
		&ProwJobList{},
	)
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}

func addKnownTypes ( scheme * runtime . Scheme ) error { scheme . AddKnownTypes ( SchemeGroupVersion , & ProwJob { } , & ProwJobList { } , ) metav1 . AddToGroupVersion ( scheme , SchemeGroupVersion ) return nil }

// Adds the list of known types to the Scheme.

Adds the list of known types to the Scheme .


func NewController(continueOnError bool, addedPresubmitBlacklist sets.String, prowJobClient prowv1.ProwJobInterface, githubClient *github.Client, configAgent *config.Agent, pluginAgent *plugins.ConfigAgent) *Controller {
	return &Controller{
		continueOnError:         continueOnError,
		addedPresubmitBlacklist: addedPresubmitBlacklist,
		prowJobTriggerer: &kubeProwJobTriggerer{
			prowJobClient: prowJobClient,
			githubClient:  githubClient,
			configAgent:   configAgent,
		},
		githubClient: githubClient,
		statusMigrator: &gitHubMigrator{
			githubClient:    githubClient,
			continueOnError: continueOnError,
		},
		trustedChecker: &githubTrustedChecker{
			githubClient: githubClient,
			pluginAgent:  pluginAgent,
		},
	}
}

func NewController ( continueOnError bool , addedPresubmitBlacklist sets . String , prowJobClient prowv1 . ProwJobInterface , githubClient * github . Client , configAgent * config . Agent , pluginAgent * plugins . ConfigAgent ) * Controller { return & Controller { continueOnError : continueOnError , addedPresubmitBlacklist : addedPresubmitBlacklist , prowJobTriggerer : & kubeProwJobTriggerer { prowJobClient : prowJobClient , githubClient : githubClient , configAgent : configAgent , } , githubClient : githubClient , statusMigrator : & gitHubMigrator { githubClient : githubClient , continueOnError : continueOnError , } , trustedChecker : & githubTrustedChecker { githubClient : githubClient , pluginAgent : pluginAgent , } , } }

// NewController constructs a new controller to reconcile stauses on config change


func (c *Controller) Run(stop <-chan os.Signal, changes <-chan config.Delta) {
	for {
		select {
		case change := <-changes:
			start := time.Now()
			if err := c.reconcile(change); err != nil {
				logrus.WithError(err).Error("Error reconciling statuses.")
			}
			logrus.WithField("duration", fmt.Sprintf("%v", time.Since(start))).Info("Statuses reconciled")
		case <-stop:
			logrus.Info("status-reconciler is shutting down...")
			return
		}
	}
}

func ( c * Controller ) Run ( stop <- chan os . Signal , changes <- chan config . Delta ) { for { select { case change := <- changes : start := time . Now ( ) if err := c . reconcile ( change ) ; err != nil { logrus . WithError ( err ) . Error ( " " ) } logrus . WithField ( " " , fmt . Sprintf ( " " , time . Since ( start ) ) ) . Info ( " " ) case <- stop : logrus . Info ( " " ) return } } }

// Run monitors the incoming configuration changes to determine when statuses need to be
// reconciled on PRs in flight when blocking presubmits change

Run monitors the incoming configuration changes to determine when statuses need to be reconciled on PRs in flight when blocking presubmits change


func addedBlockingPresubmits(old, new map[string][]config.Presubmit) map[string][]config.Presubmit {
	added := map[string][]config.Presubmit{}

	for repo, oldPresubmits := range old {
		added[repo] = []config.Presubmit{}
		for _, newPresubmit := range new[repo] {
			if !newPresubmit.ContextRequired() || newPresubmit.NeedsExplicitTrigger() {
				continue
			}
			var found bool
			for _, oldPresubmit := range oldPresubmits {
				if oldPresubmit.Name == newPresubmit.Name {
					if oldPresubmit.SkipReport && !newPresubmit.SkipReport {
						added[repo] = append(added[repo], newPresubmit)
						logrus.WithFields(logrus.Fields{
							"repo": repo,
							"name": oldPresubmit.Name,
						}).Debug("Identified a newly-reporting blocking presubmit.")
					}
					if oldPresubmit.RunIfChanged != newPresubmit.RunIfChanged {
						added[repo] = append(added[repo], newPresubmit)
						logrus.WithFields(logrus.Fields{
							"repo": repo,
							"name": oldPresubmit.Name,
						}).Debug("Identified a blocking presubmit running over a different set of files.")
					}
					found = true
					break
				}
			}
			if !found {
				added[repo] = append(added[repo], newPresubmit)
				logrus.WithFields(logrus.Fields{
					"repo": repo,
					"name": newPresubmit.Name,
				}).Debug("Identified an added blocking presubmit.")
			}
		}
	}

	var numAdded int
	for _, presubmits := range added {
		numAdded += len(presubmits)
	}
	logrus.Infof("Identified %d added blocking presubmits.", numAdded)
	return added
}

func addedBlockingPresubmits ( old , new map [ string ] [ ] config . Presubmit ) map [ string ] [ ] config . Presubmit { added := map [ string ] [ ] config . Presubmit { } for repo , oldPresubmits := range old { added [ repo ] = [ ] config . Presubmit { } for _ , newPresubmit := range new [ repo ] { if ! newPresubmit . ContextRequired ( ) || newPresubmit . NeedsExplicitTrigger ( ) { continue } var found bool for _ , oldPresubmit := range oldPresubmits { if oldPresubmit . Name == newPresubmit . Name { if oldPresubmit . SkipReport && ! newPresubmit . SkipReport { added [ repo ] = append ( added [ repo ] , newPresubmit ) logrus . WithFields ( logrus . Fields { " " : repo , " " : oldPresubmit . Name , } ) . Debug ( " " ) } if oldPresubmit . RunIfChanged != newPresubmit . RunIfChanged { added [ repo ] = append ( added [ repo ] , newPresubmit ) logrus . WithFields ( logrus . Fields { " " : repo , " " : oldPresubmit . Name , } ) . Debug ( " " ) } found = true break } } if ! found { added [ repo ] = append ( added [ repo ] , newPresubmit ) logrus . WithFields ( logrus . Fields { " " : repo , " " : newPresubmit . Name , } ) . Debug ( " " ) } } } var numAdded int for _ , presubmits := range added { numAdded += len ( presubmits ) } logrus . Infof ( " " , numAdded ) return added }

// addedBlockingPresubmits determines new blocking presubmits based on a
// config update. New blocking presubmits are either brand-new presubmits
// or extant presubmits that are now reporting. Previous presubmits that
// reported but were optional that are no longer optional require no action
// as their contexts will already exist on PRs.

addedBlockingPresubmits determines new blocking presubmits based on a config update . New blocking presubmits are either brand - new presubmits or extant presubmits that are now reporting . Previous presubmits that reported but were optional that are no longer optional require no action as their contexts will already exist on PRs .


func removedBlockingPresubmits(old, new map[string][]config.Presubmit) map[string][]config.Presubmit {
	removed := map[string][]config.Presubmit{}

	for repo, oldPresubmits := range old {
		removed[repo] = []config.Presubmit{}
		for _, oldPresubmit := range oldPresubmits {
			if !oldPresubmit.ContextRequired() {
				continue
			}
			var found bool
			for _, newPresubmit := range new[repo] {
				if oldPresubmit.Name == newPresubmit.Name {
					found = true
					break
				}
			}
			if !found {
				removed[repo] = append(removed[repo], oldPresubmit)
				logrus.WithFields(logrus.Fields{
					"repo": repo,
					"name": oldPresubmit.Name,
				}).Debug("Identified a removed blocking presubmit.")
			}
		}
	}

	var numRemoved int
	for _, presubmits := range removed {
		numRemoved += len(presubmits)
	}
	logrus.Infof("Identified %d removed blocking presubmits.", numRemoved)
	return removed
}

func removedBlockingPresubmits ( old , new map [ string ] [ ] config . Presubmit ) map [ string ] [ ] config . Presubmit { removed := map [ string ] [ ] config . Presubmit { } for repo , oldPresubmits := range old { removed [ repo ] = [ ] config . Presubmit { } for _ , oldPresubmit := range oldPresubmits { if ! oldPresubmit . ContextRequired ( ) { continue } var found bool for _ , newPresubmit := range new [ repo ] { if oldPresubmit . Name == newPresubmit . Name { found = true break } } if ! found { removed [ repo ] = append ( removed [ repo ] , oldPresubmit ) logrus . WithFields ( logrus . Fields { " " : repo , " " : oldPresubmit . Name , } ) . Debug ( " " ) } } } var numRemoved int for _ , presubmits := range removed { numRemoved += len ( presubmits ) } logrus . Infof ( " " , numRemoved ) return removed }

// removedBlockingPresubmits determines stale blocking presubmits based on a
// config update. Presubmits that are no longer blocking due to no longer
// reporting or being optional require no action as Tide will honor those
// statuses correctly.

removedBlockingPresubmits determines stale blocking presubmits based on a config update . Presubmits that are no longer blocking due to no longer reporting or being optional require no action as Tide will honor those statuses correctly .


func migratedBlockingPresubmits(old, new map[string][]config.Presubmit) map[string][]presubmitMigration {
	migrated := map[string][]presubmitMigration{}

	for repo, oldPresubmits := range old {
		migrated[repo] = []presubmitMigration{}
		for _, newPresubmit := range new[repo] {
			if !newPresubmit.ContextRequired() {
				continue
			}
			for _, oldPresubmit := range oldPresubmits {
				if oldPresubmit.Context != newPresubmit.Context && oldPresubmit.Name == newPresubmit.Name {
					migrated[repo] = append(migrated[repo], presubmitMigration{from: oldPresubmit, to: newPresubmit})
					logrus.WithFields(logrus.Fields{
						"repo": repo,
						"name": oldPresubmit.Name,
						"from": oldPresubmit.Context,
						"to":   newPresubmit.Context,
					}).Debug("Identified a migrated blocking presubmit.")
				}
			}
		}
	}

	var numMigrated int
	for _, presubmits := range migrated {
		numMigrated += len(presubmits)
	}
	logrus.Infof("Identified %d migrated blocking presubmits.", numMigrated)
	return migrated
}

func migratedBlockingPresubmits ( old , new map [ string ] [ ] config . Presubmit ) map [ string ] [ ] presubmitMigration { migrated := map [ string ] [ ] presubmitMigration { } for repo , oldPresubmits := range old { migrated [ repo ] = [ ] presubmitMigration { } for _ , newPresubmit := range new [ repo ] { if ! newPresubmit . ContextRequired ( ) { continue } for _ , oldPresubmit := range oldPresubmits { if oldPresubmit . Context != newPresubmit . Context && oldPresubmit . Name == newPresubmit . Name { migrated [ repo ] = append ( migrated [ repo ] , presubmitMigration { from : oldPresubmit , to : newPresubmit } ) logrus . WithFields ( logrus . Fields { " " : repo , " " : oldPresubmit . Name , " " : oldPresubmit . Context , " " : newPresubmit . Context , } ) . Debug ( " " ) } } } } var numMigrated int for _ , presubmits := range migrated { numMigrated += len ( presubmits ) } logrus . Infof ( " " , numMigrated ) return migrated }

// migratedBlockingPresubmits determines blocking presubmits that have had
// their status contexts migrated. This is a best-effort evaluation as we
// can only track a presubmit between configuration versions by its name.
// A presubmit "migration" that had its underlying job and context changed
// will be treated as a deletion and creation.

migratedBlockingPresubmits determines blocking presubmits that have had their status contexts migrated . This is a best - effort evaluation as we can only track a presubmit between configuration versions by its name . A presubmit migration that had its underlying job and context changed will be treated as a deletion and creation .


func Load(loader OptionLoader) error {
	if jsonConfig, provided := os.LookupEnv(loader.ConfigVar()); provided {
		if err := loader.LoadConfig(jsonConfig); err != nil {
			return fmt.Errorf("could not load config from JSON var %s: %v", loader.ConfigVar(), err)
		}
		return nil
	}

	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	loader.AddFlags(fs)
	fs.Parse(os.Args[1:])
	loader.Complete(fs.Args())

	return nil
}

func Load ( loader OptionLoader ) error { if jsonConfig , provided := os . LookupEnv ( loader . ConfigVar ( ) ) ; provided { if err := loader . LoadConfig ( jsonConfig ) ; err != nil { return fmt . Errorf ( " " , loader . ConfigVar ( ) , err ) } return nil } fs := flag . NewFlagSet ( os . Args [ 0 ] , flag . ExitOnError ) loader . AddFlags ( fs ) fs . Parse ( os . Args [ 1 : ] ) loader . Complete ( fs . Args ( ) ) return nil }

// Load loads the set of options, preferring to use
// JSON config from an env var, but falling back to
// command line flags if not possible.

Load loads the set of options preferring to use JSON config from an env var but falling back to command line flags if not possible .


func (c *Controller) canExecuteConcurrently(pj *prowapi.ProwJob) bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	if max := c.config().MaxConcurrency; max > 0 {
		var running int
		for _, num := range c.pendingJobs {
			running += num
		}
		if running >= max {
			c.log.WithFields(pjutil.ProwJobFields(pj)).Debugf("Not starting another job, already %d running.", running)
			return false
		}
	}

	if pj.Spec.MaxConcurrency == 0 {
		c.pendingJobs[pj.Spec.Job]++
		return true
	}

	numPending := c.pendingJobs[pj.Spec.Job]
	if numPending >= pj.Spec.MaxConcurrency {
		c.log.WithFields(pjutil.ProwJobFields(pj)).Debugf("Not starting another instance of %s, already %d running.", pj.Spec.Job, numPending)
		return false
	}
	c.pendingJobs[pj.Spec.Job]++
	return true
}

func ( c * Controller ) canExecuteConcurrently ( pj * prowapi . ProwJob ) bool { c . lock . Lock ( ) defer c . lock . Unlock ( ) if max := c . config ( ) . MaxConcurrency ; max > 0 { var running int for _ , num := range c . pendingJobs { running += num } if running >= max { c . log . WithFields ( pjutil . ProwJobFields ( pj ) ) . Debugf ( " " , running ) return false } } if pj . Spec . MaxConcurrency == 0 { c . pendingJobs [ pj . Spec . Job ] ++ return true } numPending := c . pendingJobs [ pj . Spec . Job ] if numPending >= pj . Spec . MaxConcurrency { c . log . WithFields ( pjutil . ProwJobFields ( pj ) ) . Debugf ( " " , pj . Spec . Job , numPending ) return false } c . pendingJobs [ pj . Spec . Job ] ++ return true }

// canExecuteConcurrently checks whether the provided ProwJob can
// be executed concurrently.

canExecuteConcurrently checks whether the provided ProwJob can be executed concurrently .


func getJenkinsJobs(pjs []prowapi.ProwJob) []BuildQueryParams {
	jenkinsJobs := []BuildQueryParams{}

	for _, pj := range pjs {
		if pj.Complete() {
			continue
		}

		jenkinsJobs = append(jenkinsJobs, BuildQueryParams{
			JobName:   getJobName(&pj.Spec),
			ProwJobID: pj.Name,
		})
	}

	return jenkinsJobs
}

func getJenkinsJobs ( pjs [ ] prowapi . ProwJob ) [ ] BuildQueryParams { jenkinsJobs := [ ] BuildQueryParams { } for _ , pj := range pjs { if pj . Complete ( ) { continue } jenkinsJobs = append ( jenkinsJobs , BuildQueryParams { JobName : getJobName ( & pj . Spec ) , ProwJobID : pj . Name , } ) } return jenkinsJobs }

// getJenkinsJobs returns all the Jenkins jobs for all active
// prowjobs from the provided list. It handles deduplication.

getJenkinsJobs returns all the Jenkins jobs for all active prowjobs from the provided list . It handles deduplication .


func (c *Controller) terminateDupes(pjs []prowapi.ProwJob, jbs map[string]Build) error {
	// "job org/repo#number" -> newest job
	dupes := make(map[string]int)
	for i, pj := range pjs {
		if pj.Complete() || pj.Spec.Type != prowapi.PresubmitJob {
			continue
		}
		n := fmt.Sprintf("%s %s/%s#%d", pj.Spec.Job, pj.Spec.Refs.Org, pj.Spec.Refs.Repo, pj.Spec.Refs.Pulls[0].Number)
		prev, ok := dupes[n]
		if !ok {
			dupes[n] = i
			continue
		}
		cancelIndex := i
		if (&pjs[prev].Status.StartTime).Before(&pj.Status.StartTime) {
			cancelIndex = prev
			dupes[n] = i
		}
		toCancel := pjs[cancelIndex]
		// Allow aborting presubmit jobs for commits that have been superseded by
		// newer commits in GitHub pull requests.
		if c.config().AllowCancellations {
			build, buildExists := jbs[toCancel.ObjectMeta.Name]
			// Avoid cancelling enqueued builds.
			if buildExists && build.IsEnqueued() {
				continue
			}
			// Otherwise, abort it.
			if buildExists {
				if err := c.jc.Abort(getJobName(&toCancel.Spec), &build); err != nil {
					c.log.WithError(err).WithFields(pjutil.ProwJobFields(&toCancel)).Warn("Cannot cancel Jenkins build")
				}
			}
		}
		toCancel.SetComplete()
		prevState := toCancel.Status.State
		toCancel.Status.State = prowapi.AbortedState
		c.log.WithFields(pjutil.ProwJobFields(&toCancel)).
			WithField("from", prevState).
			WithField("to", toCancel.Status.State).Info("Transitioning states.")
		npj, err := c.prowJobClient.Update(&toCancel)
		if err != nil {
			return err
		}
		pjs[cancelIndex] = *npj
	}
	return nil
}

// terminateDupes aborts presubmits that have a newer version. It modifies pjs
// in-place when it aborts.

terminateDupes aborts presubmits that have a newer version . It modifies pjs in - place when it aborts .


func (c *Client) Throttle(hourlyTokens, burst int) {
	c.log("Throttle", hourlyTokens, burst)
	c.throttle.lock.Lock()
	defer c.throttle.lock.Unlock()
	previouslyThrottled := c.throttle.ticker != nil
	if hourlyTokens <= 0 || burst <= 0 { // Disable throttle
		if previouslyThrottled { // Unwrap clients if necessary
			c.client = c.throttle.http
			c.gqlc = c.throttle.graph
			c.throttle.ticker.Stop()
			c.throttle.ticker = nil
		}
		return
	}
	rate := time.Hour / time.Duration(hourlyTokens)
	ticker := time.NewTicker(rate)
	throttle := make(chan time.Time, burst)
	for i := 0; i < burst; i++ { // Fill up the channel
		throttle <- time.Now()
	}
	go func() {
		// Refill the channel
		for t := range ticker.C {
			select {
			case throttle <- t:
			default:
			}
		}
	}()
	if !previouslyThrottled { // Wrap clients if we haven't already
		c.throttle.http = c.client
		c.throttle.graph = c.gqlc
		c.client = &c.throttle
		c.gqlc = &c.throttle
	}
	c.throttle.ticker = ticker
	c.throttle.throttle = throttle
}

func
(
c
*
Client
)
Throttle
(
hourlyTokens
,
burst
int
)
{
c
.
log
(
"
"
,
hourlyTokens
,
burst
)
c
.
throttle
.
lock
.
Lock
(
)
defer
c
.
throttle
.
lock
.
Unlock
(
)
previouslyThrottled
:=
c
.
throttle
.
ticker
!=
if
hourlyTokens
<=
0
||
burst
<=
0
{
// Disable throttle
if
previouslyThrottled
{
// Unwrap clients if necessary
c
.
client
=
c
.
throttle
.
http
c
.
gqlc
=
c
.
throttle
.
graph
c
.
throttle
.
ticker
.
Stop
(
)
c
.
throttle
.
ticker
=
}
return
}
rate
:=
time
.
Hour
/
time
.
Duration
(
hourlyTokens
)
ticker
:=
time
.
NewTicker
(
rate
)
throttle
:=
make
(
chan
time
.
Time
,
burst
)
for
i
:=
0
;
i
<
burst
;
i
++
{
// Fill up the channel
throttle
<-
time
.
Now
(
)
}
go
func
(
)
{
// Refill the channel
for
t
:=
range
ticker
.
C
{
select
{
case
throttle
<-
t
:
default
:
}
}
}
(
)
if
!
previouslyThrottled
{
// Wrap clients if we haven't already
c
.
throttle
.
http
=
c
.
client
c
.
throttle
.
graph
=
c
.
gqlc
c
.
client
=
&
c
.
throttle
c
.
gqlc
=
&
c
.
throttle
}
c
.
throttle
.
ticker
=
ticker
c
.
throttle
.
throttle
=
throttle
}

// Throttle client to a rate of at most hourlyTokens requests per hour,
// allowing burst tokens.

Throttle client to a rate of at most hourlyTokens requests per hour allowing burst tokens .


func NewClientWithFields(fields logrus.Fields, getToken func() []byte, graphqlEndpoint string, bases ...string) *Client {
	return &Client{
		logger: logrus.WithFields(fields).WithField("client", "github"),
		time:   &standardTime{},
		gqlc: githubql.NewEnterpriseClient(
			graphqlEndpoint,
			&http.Client{
				Timeout:   maxRequestTime,
				Transport: &oauth2.Transport{Source: newReloadingTokenSource(getToken)},
			}),
		client:   &http.Client{Timeout: maxRequestTime},
		bases:    bases,
		getToken: getToken,
		dry:      false,
	}
}

func NewClientWithFields ( fields logrus . Fields , getToken func ( ) [ ] byte , graphqlEndpoint string , bases ... string ) * Client { return & Client { logger : logrus . WithFields ( fields ) . WithField ( " " , " " ) , time : & standardTime { } , gqlc : githubql . NewEnterpriseClient ( graphqlEndpoint , & http . Client { Timeout : maxRequestTime , Transport : & oauth2 . Transport { Source : newReloadingTokenSource ( getToken ) } , } ) , client : & http . Client { Timeout : maxRequestTime } , bases : bases , getToken : getToken , dry : false , } }

// NewClientWithFields creates a new fully operational GitHub client. With
// added logging fields.
// 'getToken' is a generator for the GitHub access token to use.
// 'bases' is a variadic slice of endpoints to use in order of preference.
//   An endpoint is used when all preceding endpoints have returned a conn err.
//   This should be used when using the ghproxy GitHub proxy cache to allow
//   this client to bypass the cache if it is temporarily unavailable.

NewClientWithFields creates a new fully operational GitHub client . With added logging fields . getToken is a generator for the GitHub access token to use . bases is a variadic slice of endpoints to use in order of preference . An endpoint is used when all preceding endpoints have returned a conn err . This should be used when using the ghproxy GitHub proxy cache to allow this client to bypass the cache if it is temporarily unavailable .


func NewClient(getToken func() []byte, graphqlEndpoint string, bases ...string) *Client {
	return NewClientWithFields(logrus.Fields{}, getToken, graphqlEndpoint, bases...)
}

func NewClient ( getToken func ( ) [ ] byte , graphqlEndpoint string , bases ... string ) * Client { return NewClientWithFields ( logrus . Fields { } , getToken , graphqlEndpoint , bases ... ) }

// NewClient creates a new fully operational GitHub client.

NewClient creates a new fully operational GitHub client .


func NewDryRunClient(getToken func() []byte, graphqlEndpoint string, bases ...string) *Client {
	return NewDryRunClientWithFields(logrus.Fields{}, getToken, graphqlEndpoint, bases...)
}

func NewDryRunClient ( getToken func ( ) [ ] byte , graphqlEndpoint string , bases ... string ) * Client { return NewDryRunClientWithFields ( logrus . Fields { } , getToken , graphqlEndpoint , bases ... ) }

// NewDryRunClient creates a new client that will not perform mutating actions
// such as setting statuses or commenting, but it will still query GitHub and
// use up API tokens.
// 'getToken' is a generator the GitHub access token to use.
// 'bases' is a variadic slice of endpoints to use in order of preference.
//   An endpoint is used when all preceding endpoints have returned a conn err.
//   This should be used when using the ghproxy GitHub proxy cache to allow
//   this client to bypass the cache if it is temporarily unavailable.

NewDryRunClient creates a new client that will not perform mutating actions such as setting statuses or commenting but it will still query GitHub and use up API tokens . getToken is a generator the GitHub access token to use . bases is a variadic slice of endpoints to use in order of preference . An endpoint is used when all preceding endpoints have returned a conn err . This should be used when using the ghproxy GitHub proxy cache to allow this client to bypass the cache if it is temporarily unavailable .


func NewFakeClient() *Client {
	return &Client{
		logger: logrus.WithField("client", "github"),
		time:   &standardTime{},
		fake:   true,
		dry:    true,
	}
}

func NewFakeClient ( ) * Client { return & Client { logger : logrus . WithField ( " " , " " ) , time : & standardTime { } , fake : true , dry : true , } }

// NewFakeClient creates a new client that will not perform any actions at all.

NewFakeClient creates a new client that will not perform any actions at all .


func (c *Client) request(r *request, ret interface{}) (int, error) {
	statusCode, b, err := c.requestRaw(r)
	if err != nil {
		return statusCode, err
	}
	if ret != nil {
		if err := json.Unmarshal(b, ret); err != nil {
			return statusCode, err
		}
	}
	return statusCode, nil
}

func ( c * Client ) request ( r * request , ret interface { } ) ( int , error ) { statusCode , b , err := c . requestRaw ( r ) if err != nil { return statusCode , err } if ret != nil { if err := json . Unmarshal ( b , ret ) ; err != nil { return statusCode , err } } return statusCode , nil }

// Make a request with retries. If ret is not nil, unmarshal the response body
// into it. Returns an error if the exit code is not one of the provided codes.

Make a request with retries . If ret is not nil unmarshal the response body into it . Returns an error if the exit code is not one of the provided codes .


func (c *Client) requestRaw(r *request) (int, []byte, error) {
	if c.fake || (c.dry && r.method != http.MethodGet) {
		return r.exitCodes[0], nil, nil
	}
	resp, err := c.requestRetry(r.method, r.path, r.accept, r.requestBody)
	if err != nil {
		return 0, nil, err
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, nil, err
	}
	var okCode bool
	for _, code := range r.exitCodes {
		if code == resp.StatusCode {
			okCode = true
			break
		}
	}
	if !okCode {
		clientError := unmarshalClientError(b)
		err = requestError{
			ClientError: clientError,
			ErrorString: fmt.Sprintf("status code %d not one of %v, body: %s", resp.StatusCode, r.exitCodes, string(b)),
		}
	}
	return resp.StatusCode, b, err
}

func ( c * Client ) requestRaw ( r * request ) ( int , [ ] byte , error ) { if c . fake || ( c . dry && r . method != http . MethodGet ) { return r . exitCodes [ 0 ] , nil , nil } resp , err := c . requestRetry ( r . method , r . path , r . accept , r . requestBody ) if err != nil { return 0 , nil , err } defer resp . Body . Close ( ) b , err := ioutil . ReadAll ( resp . Body ) if err != nil { return 0 , nil , err } var okCode bool for _ , code := range r . exitCodes { if code == resp . StatusCode { okCode = true break } } if ! okCode { clientError := unmarshalClientError ( b ) err = requestError { ClientError : clientError , ErrorString : fmt . Sprintf ( " " , resp . StatusCode , r . exitCodes , string ( b ) ) , } } return resp . StatusCode , b , err }

// requestRaw makes a request with retries and returns the response body.
// Returns an error if the exit code is not one of the provided codes.

requestRaw makes a request with retries and returns the response body . Returns an error if the exit code is not one of the provided codes .


func (c *Client) getUserData() error {
	c.log("User")
	var u User
	_, err := c.request(&request{
		method:    http.MethodGet,
		path:      "/user",
		exitCodes: []int{200},
	}, &u)
	if err != nil {
		return err
	}
	c.botName = u.Login
	// email needs to be publicly accessible via the profile
	// of the current account. Read below for more info
	// https://developer.github.com/v3/users/#get-a-single-user
	c.email = u.Email
	return nil
}

// Not thread-safe - callers need to hold c.mut.

Not thread - safe - callers need to hold c . mut .


func (c *Client) readPaginatedResultsWithValues(path string, values url.Values, accept string, newObj func() interface{}, accumulate func(interface{})) error {
	pagedPath := path
	if len(values) > 0 {
		pagedPath += "?" + values.Encode()
	}
	for {
		resp, err := c.requestRetry(http.MethodGet, pagedPath, accept, nil)
		if err != nil {
			return err
		}
		defer resp.Body.Close()
		if resp.StatusCode < 200 || resp.StatusCode > 299 {
			return fmt.Errorf("return code not 2XX: %s", resp.Status)
		}

		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}

		obj := newObj()
		if err := json.Unmarshal(b, obj); err != nil {
			return err
		}

		accumulate(obj)

		link := parseLinks(resp.Header.Get("Link"))["next"]
		if link == "" {
			break
		}
		u, err := url.Parse(link)
		if err != nil {
			return fmt.Errorf("failed to parse 'next' link: %v", err)
		}
		pagedPath = u.RequestURI()
	}
	return nil
}

func ( c * Client ) readPaginatedResultsWithValues ( path string , values url . Values , accept string , newObj func ( ) interface { } , accumulate func ( interface { } ) ) error { pagedPath := path if len ( values ) > 0 { pagedPath += " " + values . Encode ( ) } for { resp , err := c . requestRetry ( http . MethodGet , pagedPath , accept , nil ) if err != nil { return err } defer resp . Body . Close ( ) if resp . StatusCode < 200 || resp . StatusCode > 299 { return fmt . Errorf ( " " , resp . Status ) } b , err := ioutil . ReadAll ( resp . Body ) if err != nil { return err } obj := newObj ( ) if err := json . Unmarshal ( b , obj ) ; err != nil { return err } accumulate ( obj ) link := parseLinks ( resp . Header . Get ( " " ) ) [ " " ] if link == " " { break } u , err := url . Parse ( link ) if err != nil { return fmt . Errorf ( " " , err ) } pagedPath = u . RequestURI ( ) } return nil }

// readPaginatedResultsWithValues is an override that allows control over the query string.

readPaginatedResultsWithValues is an override that allows control over the query string .


func (c *Client) UpdatePullRequest(org, repo string, number int, title, body *string, open *bool, branch *string, canModify *bool) error {
	c.log("UpdatePullRequest", org, repo, title)
	data := struct {
		State *string `json:"state,omitempty"`
		Title *string `json:"title,omitempty"`
		Body  *string `json:"body,omitempty"`
		Base  *string `json:"base,omitempty"`
		// MaintainerCanModify allows maintainers of the repo to modify this
		// pull request, eg. push changes to it before merging.
		MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"`
	}{
		Title:               title,
		Body:                body,
		Base:                branch,
		MaintainerCanModify: canModify,
	}
	if open != nil && *open {
		op := "open"
		data.State = &op
	} else if open != nil {
		cl := "clossed"
		data.State = &cl
	}
	_, err := c.request(&request{
		// allow the description and draft fields
		// https://developer.github.com/changes/2018-02-22-label-description-search-preview/
		// https://developer.github.com/changes/2019-02-14-draft-pull-requests/
		accept:      "application/vnd.github.symmetra-preview+json, application/vnd.github.shadow-cat-preview",
		method:      http.MethodPatch,
		path:        fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number),
		requestBody: &data,
		exitCodes:   []int{200},
	}, nil)
	return err
}

// UpdatePullRequest modifies the title, body, open state

UpdatePullRequest modifies the title body open state


func (c *Client) getLabels(path string) ([]Label, error) {
	var labels []Label
	if c.fake {
		return labels, nil
	}
	err := c.readPaginatedResults(
		path,
		"application/vnd.github.symmetra-preview+json", // allow the description field -- https://developer.github.com/changes/2018-02-22-label-description-search-preview/
		func() interface{} {
			return &[]Label{}
		},
		func(obj interface{}) {
			labels = append(labels, *(obj.(*[]Label))...)
		},
	)
	if err != nil {
		return nil, err
	}
	return labels, nil
}

// getLabels is a helper function that retrieves a paginated list of labels from a github URI path.

getLabels is a helper function that retrieves a paginated list of labels from a github URI path .


func stateCannotBeChangedOrOriginalError(err error) error {
	requestErr, ok := err.(requestError)
	if ok {
		for _, errorMsg := range requestErr.ErrorMessages() {
			if strings.Contains(errorMsg, stateCannotBeChangedMessagePrefix) {
				return StateCannotBeChanged{
					Message: errorMsg,
				}
			}
		}
	}
	return err
}

func stateCannotBeChangedOrOriginalError ( err error ) error { requestErr , ok := err . ( requestError ) if ok { for _ , errorMsg := range requestErr . ErrorMessages ( ) { if strings . Contains ( errorMsg , stateCannotBeChangedMessagePrefix ) { return StateCannotBeChanged { Message : errorMsg , } } } } return err }

// convert to a StateCannotBeChanged if appropriate or else return the original error


func (c *Client) IsMergeable(org, repo string, number int, SHA string) (bool, error) {
	backoff := time.Second * 3
	maxTries := 3
	for try := 0; try < maxTries; try++ {
		pr, err := c.GetPullRequest(org, repo, number)
		if err != nil {
			return false, err
		}
		if pr.Head.SHA != SHA {
			return false, fmt.Errorf("pull request head changed while checking mergeability (%s -> %s)", SHA, pr.Head.SHA)
		}
		if pr.Merged {
			return false, errors.New("pull request was merged while checking mergeability")
		}
		if pr.Mergable != nil {
			return *pr.Mergable, nil
		}
		if try+1 < maxTries {
			c.time.Sleep(backoff)
			backoff *= 2
		}
	}
	return false, fmt.Errorf("reached maximum number of retries (%d) checking mergeability", maxTries)
}

func ( c * Client ) IsMergeable ( org , repo string , number int , SHA string ) ( bool , error ) { backoff := time . Second * 3 maxTries := 3 for try := 0 ; try < maxTries ; try ++ { pr , err := c . GetPullRequest ( org , repo , number ) if err != nil { return false , err } if pr . Head . SHA != SHA { return false , fmt . Errorf ( " " , SHA , pr . Head . SHA ) } if pr . Merged { return false , errors . New ( " " ) } if pr . Mergable != nil { return * pr . Mergable , nil } if try + 1 < maxTries { c . time . Sleep ( backoff ) backoff *= 2 } } return false , fmt . Errorf ( " " , maxTries ) }

// IsMergeable determines if a PR can be merged.
// Mergeability is calculated by a background job on GitHub and is not immediately available when
// new commits are added so the PR must be polled until the background job completes.

IsMergeable determines if a PR can be merged . Mergeability is calculated by a background job on GitHub and is not immediately available when new commits are added so the PR must be polled until the background job completes .


func (s *reloadingTokenSource) Token() (*oauth2.Token, error) {
	return &oauth2.Token{
		AccessToken: string(s.getToken()),
	}, nil
}

func ( s * reloadingTokenSource ) Token ( ) ( * oauth2 . Token , error ) { return & oauth2 . Token { AccessToken : string ( s . getToken ( ) ) , } , nil }

// Token is an implementation for oauth2.TokenSource interface.

Token is an implementation for oauth2 . TokenSource interface .


func (s *Spyglass) ListArtifacts(src string) ([]string, error) {
	keyType, key, err := splitSrc(src)
	if err != nil {
		return []string{}, fmt.Errorf("error parsing src: %v", err)
	}
	gcsKey := ""
	switch keyType {
	case gcsKeyType:
		gcsKey = key
	case prowKeyType:
		if gcsKey, err = s.prowToGCS(key); err != nil {
			logrus.Warningf("Failed to get gcs source for prow job: %v", err)
		}
	default:
		return nil, fmt.Errorf("Unrecognized key type for src: %v", src)
	}

	artifactNames, err := s.GCSArtifactFetcher.artifacts(gcsKey)
	logFound := false
	for _, name := range artifactNames {
		if name == "build-log.txt" {
			logFound = true
			break
		}
	}
	if err != nil || !logFound {
		artifactNames = append(artifactNames, "build-log.txt")
	}
	return artifactNames, nil
}

func ( s * Spyglass ) ListArtifacts ( src string ) ( [ ] string , error ) { keyType , key , err := splitSrc ( src ) if err != nil { return [ ] string { } , fmt . Errorf ( " " , err ) } gcsKey := " " switch keyType { case gcsKeyType : gcsKey = key case prowKeyType : if gcsKey , err = s . prowToGCS ( key ) ; err != nil { logrus . Warningf ( " " , err ) } default : return nil , fmt . Errorf ( " " , src ) } artifactNames , err := s . GCSArtifactFetcher . artifacts ( gcsKey ) logFound := false for _ , name := range artifactNames { if name == " " { logFound = true break } } if err != nil || ! logFound { artifactNames = append ( artifactNames , " " ) } return artifactNames , nil }

// ListArtifacts gets the names of all artifacts available from the given source


func (*Spyglass) KeyToJob(src string) (jobName string, buildID string, err error) {
	src = strings.Trim(src, "/")
	parsed := strings.Split(src, "/")
	if len(parsed) < 2 {
		return "", "", fmt.Errorf("expected at least two path components in %q", src)
	}
	jobName = parsed[len(parsed)-2]
	buildID = parsed[len(parsed)-1]
	return jobName, buildID, nil
}

func ( * Spyglass ) KeyToJob ( src string ) ( jobName string , buildID string , err error ) { src = strings . Trim ( src , " " ) parsed := strings . Split ( src , " " ) if len ( parsed ) < 2 { return " " , " " , fmt . Errorf ( " " , src ) } jobName = parsed [ len ( parsed ) - 2 ] buildID = parsed [ len ( parsed ) - 1 ] return jobName , buildID , nil }

// KeyToJob takes a spyglass URL and returns the jobName and buildID.

KeyToJob takes a spyglass URL and returns the jobName and buildID .


func (s *Spyglass) prowToGCS(prowKey string) (string, error) {
	jobName, buildID, err := s.KeyToJob(prowKey)
	if err != nil {
		return "", fmt.Errorf("could not get GCS src: %v", err)
	}

	job, err := s.jobAgent.GetProwJob(jobName, buildID)
	if err != nil {
		return "", fmt.Errorf("Failed to get prow job from src %q: %v", prowKey, err)
	}

	url := job.Status.URL
	prefix := s.config().Plank.GetJobURLPrefix(job.Spec.Refs)
	if !strings.HasPrefix(url, prefix) {
		return "", fmt.Errorf("unexpected job URL %q when finding GCS path: expected something starting with %q", url, prefix)
	}
	return url[len(prefix):], nil
}

func ( s * Spyglass ) prowToGCS ( prowKey string ) ( string , error ) { jobName , buildID , err := s . KeyToJob ( prowKey ) if err != nil { return " " , fmt . Errorf ( " " , err ) } job , err := s . jobAgent . GetProwJob ( jobName , buildID ) if err != nil { return " " , fmt . Errorf ( " " , prowKey , err ) } url := job . Status . URL prefix := s . config ( ) . Plank . GetJobURLPrefix ( job . Spec . Refs ) if ! strings . HasPrefix ( url , prefix ) { return " " , fmt . Errorf ( " " , url , prefix ) } return url [ len ( prefix ) : ] , nil }

// prowToGCS returns the GCS key corresponding to the given prow key


func (s *Spyglass) FetchArtifacts(src string, podName string, sizeLimit int64, artifactNames []string) ([]lenses.Artifact, error) {
	artStart := time.Now()
	arts := []lenses.Artifact{}
	keyType, key, err := splitSrc(src)
	if err != nil {
		return arts, fmt.Errorf("error parsing src: %v", err)
	}
	jobName, buildID, err := s.KeyToJob(src)
	if err != nil {
		return arts, fmt.Errorf("could not derive job: %v", err)
	}
	gcsKey := ""
	switch keyType {
	case gcsKeyType:
		gcsKey = strings.TrimSuffix(key, "/")
	case prowKeyType:
		if gcsKey, err = s.prowToGCS(key); err != nil {
			logrus.Warningln(err)
		}
	default:
		return nil, fmt.Errorf("invalid src: %v", src)
	}

	podLogNeeded := false
	for _, name := range artifactNames {
		art, err := s.GCSArtifactFetcher.artifact(gcsKey, name, sizeLimit)
		if err == nil {
			// Actually try making a request, because calling GCSArtifactFetcher.artifact does no I/O.
			// (these files are being explicitly requested and so will presumably soon be accessed, so
			// the extra network I/O should not be too problematic).
			_, err = art.Size()
		}
		if err != nil {
			if name == "build-log.txt" {
				podLogNeeded = true
			}
			continue
		}
		arts = append(arts, art)
	}

	if podLogNeeded {
		art, err := s.PodLogArtifactFetcher.artifact(jobName, buildID, sizeLimit)
		if err != nil {
			logrus.Errorf("Failed to fetch pod log: %v", err)
		} else {
			arts = append(arts, art)
		}
	}

	logrus.WithField("duration", time.Since(artStart)).Infof("Retrieved artifacts for %v", src)
	return arts, nil
}

// FetchArtifacts constructs and returns Artifact objects for each artifact name in the list.
// This includes getting any handles needed for read write operations, direct artifact links, etc.

FetchArtifacts constructs and returns Artifact objects for each artifact name in the list . This includes getting any handles needed for read write operations direct artifact links etc .


func (in *DecorationConfig) DeepCopy() *DecorationConfig {
	if in == nil {
		return nil
	}
	out := new(DecorationConfig)
	in.DeepCopyInto(out)
	return out
}

func ( in * DecorationConfig ) DeepCopy ( ) * DecorationConfig { if in == nil { return nil } out := new ( DecorationConfig ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DecorationConfig.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new DecorationConfig .


func (in *GCSConfiguration) DeepCopy() *GCSConfiguration {
	if in == nil {
		return nil
	}
	out := new(GCSConfiguration)
	in.DeepCopyInto(out)
	return out
}

func ( in * GCSConfiguration ) DeepCopy ( ) * GCSConfiguration { if in == nil { return nil } out := new ( GCSConfiguration ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCSConfiguration.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new GCSConfiguration .


func (in *JenkinsSpec) DeepCopy() *JenkinsSpec {
	if in == nil {
		return nil
	}
	out := new(JenkinsSpec)
	in.DeepCopyInto(out)
	return out
}

func ( in * JenkinsSpec ) DeepCopy ( ) * JenkinsSpec { if in == nil { return nil } out := new ( JenkinsSpec ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsSpec.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new JenkinsSpec .


func (in *ProwJob) DeepCopy() *ProwJob {
	if in == nil {
		return nil
	}
	out := new(ProwJob)
	in.DeepCopyInto(out)
	return out
}

func ( in * ProwJob ) DeepCopy ( ) * ProwJob { if in == nil { return nil } out := new ( ProwJob ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProwJob.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new ProwJob .


func (in *ProwJobList) DeepCopy() *ProwJobList {
	if in == nil {
		return nil
	}
	out := new(ProwJobList)
	in.DeepCopyInto(out)
	return out
}

func ( in * ProwJobList ) DeepCopy ( ) * ProwJobList { if in == nil { return nil } out := new ( ProwJobList ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProwJobList.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new ProwJobList .


func (in *ProwJobSpec) DeepCopy() *ProwJobSpec {
	if in == nil {
		return nil
	}
	out := new(ProwJobSpec)
	in.DeepCopyInto(out)
	return out
}

func ( in * ProwJobSpec ) DeepCopy ( ) * ProwJobSpec { if in == nil { return nil } out := new ( ProwJobSpec ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProwJobSpec.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new ProwJobSpec .


func (in *ProwJobStatus) DeepCopy() *ProwJobStatus {
	if in == nil {
		return nil
	}
	out := new(ProwJobStatus)
	in.DeepCopyInto(out)
	return out
}

func ( in * ProwJobStatus ) DeepCopy ( ) * ProwJobStatus { if in == nil { return nil } out := new ( ProwJobStatus ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProwJobStatus.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new ProwJobStatus .


func (in *Pull) DeepCopy() *Pull {
	if in == nil {
		return nil
	}
	out := new(Pull)
	in.DeepCopyInto(out)
	return out
}

func ( in * Pull ) DeepCopy ( ) * Pull { if in == nil { return nil } out := new ( Pull ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pull.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new Pull .


func (in *Refs) DeepCopy() *Refs {
	if in == nil {
		return nil
	}
	out := new(Refs)
	in.DeepCopyInto(out)
	return out
}

func ( in * Refs ) DeepCopy ( ) * Refs { if in == nil { return nil } out := new ( Refs ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Refs.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new Refs .


func (in *UtilityImages) DeepCopy() *UtilityImages {
	if in == nil {
		return nil
	}
	out := new(UtilityImages)
	in.DeepCopyInto(out)
	return out
}

func ( in * UtilityImages ) DeepCopy ( ) * UtilityImages { if in == nil { return nil } out := new ( UtilityImages ) in . DeepCopyInto ( out ) return out }

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UtilityImages.

DeepCopy is an autogenerated deepcopy function copying the receiver creating a new UtilityImages .


func upload(rsClient *resultstore.Client, inv resultstore.Invocation, target resultstore.Target, test resultstore.Test) (string, error) {

	targetID := test.Name
	const configID = resultstore.Default
	invName, err := rsClient.Invocations().Create(inv)
	if err != nil {
		return "", fmt.Errorf("create invocation: %v", err)
	}
	targetName, err := rsClient.Targets(invName).Create(targetID, target)
	if err != nil {
		return resultstore.URL(invName), fmt.Errorf("create target: %v", err)
	}
	url := resultstore.URL(targetName)
	_, err = rsClient.Configurations(invName).Create(configID)
	if err != nil {
		return url, fmt.Errorf("create configuration: %v", err)
	}
	ctName, err := rsClient.ConfiguredTargets(targetName, configID).Create(test.Action)
	if err != nil {
		return url, fmt.Errorf("create configured target: %v", err)
	}
	_, err = rsClient.Actions(ctName).Create("primary", test)
	if err != nil {
		return url, fmt.Errorf("create action: %v", err)
	}
	return url, nil
}

func upload ( rsClient * resultstore . Client , inv resultstore . Invocation , target resultstore . Target , test resultstore . Test ) ( string , error ) { targetID := test . Name const configID = resultstore . Default invName , err := rsClient . Invocations ( ) . Create ( inv ) if err != nil { return " " , fmt . Errorf ( " " , err ) } targetName , err := rsClient . Targets ( invName ) . Create ( targetID , target ) if err != nil { return resultstore . URL ( invName ) , fmt . Errorf ( " " , err ) } url := resultstore . URL ( targetName ) _ , err = rsClient . Configurations ( invName ) . Create ( configID ) if err != nil { return url , fmt . Errorf ( " " , err ) } ctName , err := rsClient . ConfiguredTargets ( targetName , configID ) . Create ( test . Action ) if err != nil { return url , fmt . Errorf ( " " , err ) } _ , err = rsClient . Actions ( ctName ) . Create ( " " , test ) if err != nil { return url , fmt . Errorf ( " " , err ) } return url , nil }

// upload the result downloaded from path into project.

upload the result downloaded from path into project .


func (d *DecorationConfig) ApplyDefault(def *DecorationConfig) *DecorationConfig {
	if d == nil && def == nil {
		return nil
	}
	var merged DecorationConfig
	if d != nil {
		merged = *d
	} else {
		merged = *def
	}
	if d == nil || def == nil {
		return &merged
	}
	merged.UtilityImages = merged.UtilityImages.ApplyDefault(def.UtilityImages)
	merged.GCSConfiguration = merged.GCSConfiguration.ApplyDefault(def.GCSConfiguration)

	if merged.Timeout.Duration == 0 {
		merged.Timeout = def.Timeout
	}
	if merged.GracePeriod.Duration == 0 {
		merged.GracePeriod = def.GracePeriod
	}
	if merged.GCSCredentialsSecret == "" {
		merged.GCSCredentialsSecret = def.GCSCredentialsSecret
	}
	if len(merged.SSHKeySecrets) == 0 {
		merged.SSHKeySecrets = def.SSHKeySecrets
	}
	if len(merged.SSHHostFingerprints) == 0 {
		merged.SSHHostFingerprints = def.SSHHostFingerprints
	}
	if merged.SkipCloning == nil {
		merged.SkipCloning = def.SkipCloning
	}
	if merged.CookiefileSecret == "" {
		merged.CookiefileSecret = def.CookiefileSecret
	}

	return &merged
}

func ( d * DecorationConfig ) ApplyDefault ( def * DecorationConfig ) * DecorationConfig { if d == nil && def == nil { return nil } var merged DecorationConfig if d != nil { merged = * d } else { merged = * def } if d == nil || def == nil { return & merged } merged . UtilityImages = merged . UtilityImages . ApplyDefault ( def . UtilityImages ) merged . GCSConfiguration = merged . GCSConfiguration . ApplyDefault ( def . GCSConfiguration ) if merged . Timeout . Duration == 0 { merged . Timeout = def . Timeout } if merged . GracePeriod . Duration == 0 { merged . GracePeriod = def . GracePeriod } if merged . GCSCredentialsSecret == " " { merged . GCSCredentialsSecret = def . GCSCredentialsSecret } if len ( merged . SSHKeySecrets ) == 0 { merged . SSHKeySecrets = def . SSHKeySecrets } if len ( merged . SSHHostFingerprints ) == 0 { merged . SSHHostFingerprints = def . SSHHostFingerprints } if merged . SkipCloning == nil { merged . SkipCloning = def . SkipCloning } if merged . CookiefileSecret == " " { merged . CookiefileSecret = def . CookiefileSecret } return & merged }

// ApplyDefault applies the defaults for the ProwJob decoration. If a field has a zero value, it
// replaces that with the value set in def.

ApplyDefault applies the defaults for the ProwJob decoration . If a field has a zero value it replaces that with the value set in def .


func (d *DecorationConfig) Validate() error {
	if d.UtilityImages == nil {
		return errors.New("utility image config is not specified")
	}
	var missing []string
	if d.UtilityImages.CloneRefs == "" {
		missing = append(missing, "clonerefs")
	}
	if d.UtilityImages.InitUpload == "" {
		missing = append(missing, "initupload")
	}
	if d.UtilityImages.Entrypoint == "" {
		missing = append(missing, "entrypoint")
	}
	if d.UtilityImages.Sidecar == "" {
		missing = append(missing, "sidecar")
	}
	if len(missing) > 0 {
		return fmt.Errorf("the following utility images are not specified: %q", missing)
	}

	if d.GCSConfiguration == nil {
		return errors.New("GCS upload configuration is not specified")
	}
	if d.GCSCredentialsSecret == "" {
		return errors.New("GCS upload credential secret is not specified")
	}
	if err := d.GCSConfiguration.Validate(); err != nil {
		return fmt.Errorf("GCS configuration is invalid: %v", err)
	}
	return nil
}

func ( d * DecorationConfig ) Validate ( ) error { if d . UtilityImages == nil { return errors . New ( " " ) } var missing [ ] string if d . UtilityImages . CloneRefs == " " { missing = append ( missing , " " ) } if d . UtilityImages . InitUpload == " " { missing = append ( missing , " " ) } if d . UtilityImages . Entrypoint == " " { missing = append ( missing , " " ) } if d . UtilityImages . Sidecar == " " { missing = append ( missing , " " ) } if len ( missing ) > 0 { return fmt . Errorf ( " " , missing ) } if d . GCSConfiguration == nil { return errors . New ( " " ) } if d . GCSCredentialsSecret == " " { return errors . New ( " " ) } if err := d . GCSConfiguration . Validate ( ) ; err != nil { return fmt . Errorf ( " " , err ) } return nil }

// Validate ensures all the values set in the DecorationConfig are valid.

Validate ensures all the values set in the DecorationConfig are valid .


func (u *UtilityImages) ApplyDefault(def *UtilityImages) *UtilityImages {
	if u == nil {
		return def
	} else if def == nil {
		return u
	}

	merged := *u
	if merged.CloneRefs == "" {
		merged.CloneRefs = def.CloneRefs
	}
	if merged.InitUpload == "" {
		merged.InitUpload = def.InitUpload
	}
	if merged.Entrypoint == "" {
		merged.Entrypoint = def.Entrypoint
	}
	if merged.Sidecar == "" {
		merged.Sidecar = def.Sidecar
	}
	return &merged
}

func ( u * UtilityImages ) ApplyDefault ( def * UtilityImages ) * UtilityImages { if u == nil { return def } else if def == nil { return u } merged := * u if merged . CloneRefs == " " { merged . CloneRefs = def . CloneRefs } if merged . InitUpload == " " { merged . InitUpload = def . InitUpload } if merged . Entrypoint == " " { merged . Entrypoint = def . Entrypoint } if merged . Sidecar == " " { merged . Sidecar = def . Sidecar } return & merged }

// ApplyDefault applies the defaults for the UtilityImages decorations. If a field has a zero value,
// it replaces that with the value set in def.

ApplyDefault applies the defaults for the UtilityImages decorations . If a field has a zero value it replaces that with the value set in def .


func (g *GCSConfiguration) ApplyDefault(def *GCSConfiguration) *GCSConfiguration {
	if g == nil && def == nil {
		return nil
	}
	var merged GCSConfiguration
	if g != nil {
		merged = *g
	} else {
		merged = *def
	}
	if g == nil || def == nil {
		return &merged
	}

	if merged.Bucket == "" {
		merged.Bucket = def.Bucket
	}
	if merged.PathPrefix == "" {
		merged.PathPrefix = def.PathPrefix
	}
	if merged.PathStrategy == "" {
		merged.PathStrategy = def.PathStrategy
	}
	if merged.DefaultOrg == "" {
		merged.DefaultOrg = def.DefaultOrg
	}
	if merged.DefaultRepo == "" {
		merged.DefaultRepo = def.DefaultRepo
	}
	return &merged
}

func ( g * GCSConfiguration ) ApplyDefault ( def * GCSConfiguration ) * GCSConfiguration { if g == nil && def == nil { return nil } var merged GCSConfiguration if g != nil { merged = * g } else { merged = * def } if g == nil || def == nil { return & merged } if merged . Bucket == " " { merged . Bucket = def . Bucket } if merged . PathPrefix == " " { merged . PathPrefix = def . PathPrefix } if merged . PathStrategy == " " { merged . PathStrategy = def . PathStrategy } if merged . DefaultOrg == " " { merged . DefaultOrg = def . DefaultOrg } if merged . DefaultRepo == " " { merged . DefaultRepo = def . DefaultRepo } return & merged }

// ApplyDefault applies the defaults for GCSConfiguration decorations. If a field has a zero value,
// it replaces that with the value set in def.

ApplyDefault applies the defaults for GCSConfiguration decorations . If a field has a zero value it replaces that with the value set in def .


func (g *GCSConfiguration) Validate() error {
	if g.PathStrategy != PathStrategyLegacy && g.PathStrategy != PathStrategyExplicit && g.PathStrategy != PathStrategySingle {
		return fmt.Errorf("gcs_path_strategy must be one of %q, %q, or %q", PathStrategyLegacy, PathStrategyExplicit, PathStrategySingle)
	}
	if g.PathStrategy != PathStrategyExplicit && (g.DefaultOrg == "" || g.DefaultRepo == "") {
		return fmt.Errorf("default org and repo must be provided for GCS strategy %q", g.PathStrategy)
	}
	return nil
}

func ( g * GCSConfiguration ) Validate ( ) error { if g . PathStrategy != PathStrategyLegacy && g . PathStrategy != PathStrategyExplicit && g . PathStrategy != PathStrategySingle { return fmt . Errorf ( " " , PathStrategyLegacy , PathStrategyExplicit , PathStrategySingle ) } if g . PathStrategy != PathStrategyExplicit && ( g . DefaultOrg == " " || g . DefaultRepo == " " ) { return fmt . Errorf ( " " , g . PathStrategy ) } return nil }

// Validate ensures all the values set in the GCSConfiguration are valid.

Validate ensures all the values set in the GCSConfiguration are valid .


func (j *ProwJob) ClusterAlias() string {
	if j.Spec.Cluster == "" {
		return DefaultClusterAlias
	}
	return j.Spec.Cluster
}

func ( j * ProwJob ) ClusterAlias ( ) string { if j . Spec . Cluster == " " { return DefaultClusterAlias } return j . Spec . Cluster }

// ClusterAlias specifies the key in the clusters map to use.
//
// This allows scheduling a prow job somewhere aside from the default build cluster.

ClusterAlias specifies the key in the clusters map to use . This allows scheduling a prow job somewhere aside from the default build cluster .


func NewResource(name, rtype, state, owner string, t time.Time) Resource {
	return Resource{
		Name:       name,
		Type:       rtype,
		State:      state,
		Owner:      owner,
		LastUpdate: t,
		UserData:   &UserData{},
	}
}

func NewResource ( name , rtype , state , owner string , t time . Time ) Resource { return Resource { Name : name , Type : rtype , State : state , Owner : owner , LastUpdate : t , UserData : & UserData { } , } }

// NewResource creates a new Boskos Resource.

NewResource creates a new Boskos Resource .


func NewResourcesFromConfig(e ResourceEntry) []Resource {
	var resources []Resource
	for _, name := range e.Names {
		resources = append(resources, NewResource(name, e.Type, e.State, "", time.Time{}))
	}
	return resources
}

func NewResourcesFromConfig ( e ResourceEntry ) [ ] Resource { var resources [ ] Resource for _ , name := range e . Names { resources = append ( resources , NewResource ( name , e . Type , e . State , " " , time . Time { } ) ) } return resources }

// NewResourcesFromConfig parse the a ResourceEntry into a list of resources


func UserDataFromMap(m UserDataMap) *UserData {
	ud := &UserData{}
	for k, v := range m {
		ud.Store(k, v)
	}
	return ud
}

func UserDataFromMap ( m UserDataMap ) * UserData { ud := & UserData { } for k , v := range m { ud . Store ( k , v ) } return ud }

// UserDataFromMap returns a UserData from a map


func (r *CommaSeparatedStrings) Set(value string) error {
	if len(*r) > 0 {
		return errors.New("resTypes flag already set")
	}
	for _, rtype := range strings.Split(value, ",") {
		*r = append(*r, rtype)
	}
	return nil
}

func ( r * CommaSeparatedStrings ) Set ( value string ) error { if len ( * r ) > 0 { return errors . New ( " " ) } for _ , rtype := range strings . Split ( value , " " ) { * r = append ( * r , rtype ) } return nil }

// Set parses the flag value into a CommaSeparatedStrings


func (ud *UserData) UnmarshalJSON(data []byte) error {
	tmpMap := UserDataMap{}
	if err := json.Unmarshal(data, &tmpMap); err != nil {
		return err
	}
	ud.FromMap(tmpMap)
	return nil
}

func ( ud * UserData ) UnmarshalJSON ( data [ ] byte ) error { tmpMap := UserDataMap { } if err := json . Unmarshal ( data , & tmpMap ) ; err != nil { return err } ud . FromMap ( tmpMap ) return nil }

// UnmarshalJSON implements JSON Unmarshaler interface


func (ud *UserData) Extract(id string, out interface{}) error {
	content, ok := ud.Load(id)
	if !ok {
		return &UserDataNotFound{id}
	}
	return yaml.Unmarshal([]byte(content.(string)), out)
}

func ( ud * UserData ) Extract ( id string , out interface { } ) error { content , ok := ud . Load ( id ) if ! ok { return & UserDataNotFound { id } } return yaml . Unmarshal ( [ ] byte ( content . ( string ) ) , out ) }

// Extract unmarshalls a string a given struct if it exists


func (ud *UserData) Set(id string, in interface{}) error {
	b, err := yaml.Marshal(in)
	if err != nil {
		return err
	}
	ud.Store(id, string(b))
	return nil
}

func ( ud * UserData ) Set ( id string , in interface { } ) error { b , err := yaml . Marshal ( in ) if err != nil { return err } ud . Store ( id , string ( b ) ) return nil }

// User Data are used to store custom information mainly by Mason and Masonable implementation.
// Mason used a LeasedResource keys to store information about other resources that used to
// create the given resource.
// Set marshalls a struct to a string into the UserData

User Data are used to store custom information mainly by Mason and Masonable implementation . Mason used a LeasedResource keys to store information about other resources that used to create the given resource . Set marshalls a struct to a string into the UserData


func (ud *UserData) Update(new *UserData) {
	if new == nil {
		return
	}
	new.Range(func(key, value interface{}) bool {
		if value.(string) != "" {
			ud.Store(key, value)
		} else {
			ud.Delete(key)
		}
		return true
	})
}

func ( ud * UserData ) Update ( new * UserData ) { if new == nil { return } new . Range ( func ( key , value interface { } ) bool { if value . ( string ) != " " { ud . Store ( key , value ) } else { ud . Delete ( key ) } return true } ) }

// Update updates existing UserData with new UserData.
// If a key as an empty string, the key will be deleted

Update updates existing UserData with new UserData . If a key as an empty string the key will be deleted


func (ud *UserData) ToMap() UserDataMap {
	m := UserDataMap{}
	ud.Range(func(key, value interface{}) bool {
		m[key.(string)] = value.(string)
		return true
	})
	return m
}

func ( ud * UserData ) ToMap ( ) UserDataMap { m := UserDataMap { } ud . Range ( func ( key , value interface { } ) bool { m [ key . ( string ) ] = value . ( string ) return true } ) return m }

// ToMap converts a UserData to UserDataMap


func (ud *UserData) FromMap(m UserDataMap) {
	for key, value := range m {
		ud.Store(key, value)
	}
}

func ( ud * UserData ) FromMap ( m UserDataMap ) { for key , value := range m { ud . Store ( key , value ) } }

// FromMap feels updates user data from a map


func ItemToResource(i Item) (Resource, error) {
	res, ok := i.(Resource)
	if !ok {
		return Resource{}, fmt.Errorf("cannot construct Resource from received object %v", i)
	}
	return res, nil
}

func ItemToResource ( i Item ) ( Resource , error ) { res , ok := i . ( Resource ) if ! ok { return Resource { } , fmt . Errorf ( " " , i ) } return res , nil }

// ItemToResource casts a Item back to a Resource


func (o Options) Run() error {
	var env []string
	if len(o.KeyFiles) > 0 {
		var err error
		env, err = addSSHKeys(o.KeyFiles)
		if err != nil {
			logrus.WithError(err).Error("Failed to add SSH keys.")
			// Continue on error. Clones will fail with an appropriate error message
			// that initupload can consume whereas quitting without writing the clone
			// record log is silent and results in an errored prow job instead of a
			// failed one.
		}
	}
	if len(o.HostFingerprints) > 0 {
		if err := addHostFingerprints(o.HostFingerprints); err != nil {
			logrus.WithError(err).Error("failed to add host fingerprints")
		}
	}

	var numWorkers int
	if o.MaxParallelWorkers != 0 {
		numWorkers = o.MaxParallelWorkers
	} else {
		numWorkers = len(o.GitRefs)
	}

	wg := &sync.WaitGroup{}
	wg.Add(numWorkers)

	input := make(chan prowapi.Refs)
	output := make(chan clone.Record, len(o.GitRefs))
	for i := 0; i < numWorkers; i++ {
		go func() {
			defer wg.Done()
			for ref := range input {
				output <- cloneFunc(ref, o.SrcRoot, o.GitUserName, o.GitUserEmail, o.CookiePath, env)
			}
		}()
	}

	for _, ref := range o.GitRefs {
		input <- ref
	}

	close(input)
	wg.Wait()
	close(output)

	var results []clone.Record
	for record := range output {
		results = append(results, record)
	}

	logData, err := json.Marshal(results)
	if err != nil {
		return fmt.Errorf("failed to marshal clone records: %v", err)
	}

	if err := ioutil.WriteFile(o.Log, logData, 0755); err != nil {
		return fmt.Errorf("failed to write clone records: %v", err)
	}

	return nil
}


func addSSHKeys(paths []string) ([]string, error) {
	vars, err := exec.Command("ssh-agent").CombinedOutput()
	if err != nil {
		return []string{}, fmt.Errorf("failed to start ssh-agent: %v", err)
	}
	logrus.Info("Started SSH agent")
	// ssh-agent will output three lines of text, in the form:
	// SSH_AUTH_SOCK=xxx; export SSH_AUTH_SOCK;
	// SSH_AGENT_PID=xxx; export SSH_AGENT_PID;
	// echo Agent pid xxx;
	// We need to parse out the environment variables from that.
	parts := strings.Split(string(vars), ";")
	env := []string{strings.TrimSpace(parts[0]), strings.TrimSpace(parts[2])}
	for _, keyPath := range paths {
		// we can be given literal paths to keys or paths to dirs
		// that are mounted from a secret, so we need to check which
		// we have
		if err := filepath.Walk(keyPath, func(path string, info os.FileInfo, err error) error {
			if strings.HasPrefix(info.Name(), "..") {
				// kubernetes volumes also include files we
				// should not look be looking into for keys
				if info.IsDir() {
					return filepath.SkipDir
				}
				return nil
			}
			if info.IsDir() {
				return nil
			}

			cmd := exec.Command("ssh-add", path)
			cmd.Env = append(cmd.Env, env...)
			if output, err := cmd.CombinedOutput(); err != nil {
				return fmt.Errorf("failed to add ssh key at %s: %v: %s", path, err, output)
			}
			logrus.Infof("Added SSH key at %s", path)
			return nil
		}); err != nil {
			return env, fmt.Errorf("error walking path %q: %v", keyPath, err)
		}
	}
	return env, nil
}

// addSSHKeys will start the ssh-agent and add all the specified
// keys, returning the ssh-agent environment variables for reuse

addSSHKeys will start the ssh - agent and add all the specified keys returning the ssh - agent environment variables for reuse


func (f *TriageFiler) Issues(c *creator.IssueCreator) ([]creator.Issue, error) {
	f.creator = c
	rawjson, err := ReadHTTP(clusterDataURL)
	if err != nil {
		return nil, err
	}
	clusters, err := f.loadClusters(rawjson)
	if err != nil {
		return nil, err
	}
	topclusters := topClusters(clusters, f.topClustersCount)
	issues := make([]creator.Issue, 0, len(topclusters))
	for _, clust := range topclusters {
		issues = append(issues, clust)
	}
	return issues, nil
}

func ( f * TriageFiler ) Issues ( c * creator . IssueCreator ) ( [ ] creator . Issue , error ) { f . creator = c rawjson , err := ReadHTTP ( clusterDataURL ) if err != nil { return nil , err } clusters , err := f . loadClusters ( rawjson ) if err != nil { return nil , err } topclusters := topClusters ( clusters , f . topClustersCount ) issues := make ( [ ] creator . Issue , 0 , len ( topclusters ) ) for _ , clust := range topclusters { issues = append ( issues , clust ) } return issues , nil }

// Issues is the main work function of the TriageFiler.  It fetches and parses cluster data,
// then syncs the top issues to github with the IssueCreator.

Issues is the main work function of the TriageFiler . It fetches and parses cluster data then syncs the top issues to github with the IssueCreator .


func (f *TriageFiler) loadClusters(jsonIn []byte) ([]*Cluster, error) {
	var err error
	f.data, err = parseTriageData(jsonIn)
	if err != nil {
		return nil, err
	}
	if err = f.filterAndValidate(f.windowDays); err != nil {
		return nil, err
	}

	// Aggregate failing builds in each cluster by job (independent of tests).
	for _, clust := range f.data.Clustered {
		clust.filer = f
		clust.jobs = make(map[string][]int)

		for _, test := range clust.Tests {
			for _, job := range test.Jobs {
				for _, buildnum := range job.Builds {
					found := false
					for _, oldBuild := range clust.jobs[job.Name] {
						if oldBuild == buildnum {
							found = true
							break
						}
					}
					if !found {
						clust.jobs[job.Name] = append(clust.jobs[job.Name], buildnum)
					}
				}
			}
		}
		clust.totalJobs = len(clust.jobs)
		clust.totalTests = len(clust.Tests)
		clust.totalBuilds = 0
		for _, builds := range clust.jobs {
			clust.totalBuilds += len(builds)
		}
	}
	return f.data.Clustered, nil
}

// loadClusters parses and filters the json data, then populates every Cluster struct with
// aggregated job data and totals. The job data specifies all jobs that failed in a cluster and the
// builds that failed for each job, independent of which tests the jobs or builds failed.

loadClusters parses and filters the json data then populates every Cluster struct with aggregated job data and totals . The job data specifies all jobs that failed in a cluster and the builds that failed for each job independent of which tests the jobs or builds failed .


func parseTriageData(jsonIn []byte) (*triageData, error) {
	var data triageData
	if err := json.Unmarshal(jsonIn, &data); err != nil {
		return nil, err
	}

	if data.Builds.Cols.Started == nil {
		return nil, fmt.Errorf("triage data json is missing the builds.cols.started key")
	}
	if data.Builds.JobsRaw == nil {
		return nil, fmt.Errorf("triage data is missing the builds.jobs key")
	}
	if data.Builds.JobPaths == nil {
		return nil, fmt.Errorf("triage data is missing the builds.job_paths key")
	}
	if data.Clustered == nil {
		return nil, fmt.Errorf("triage data is missing the clustered key")
	}
	// Populate 'Jobs' with the BuildIndexer for each job.
	data.Builds.Jobs = make(map[string]BuildIndexer)
	for jobID, mapper := range data.Builds.JobsRaw {
		switch mapper := mapper.(type) {
		case []interface{}:
			// In this case mapper is a 3 member array. 0:first buildnum, 1:number of builds, 2:start index.
			data.Builds.Jobs[jobID] = ContigIndexer{
				startBuild: int(mapper[0].(float64)),
				count:      int(mapper[1].(float64)),
				startRow:   int(mapper[2].(float64)),
			}
		case map[string]interface{}:
			// In this case mapper is a dictionary.
			data.Builds.Jobs[jobID] = DictIndexer(mapper)
		default:
			return nil, fmt.Errorf("the build number to row index mapping for job '%s' is not an accepted type. Type is: %v", jobID, reflect.TypeOf(mapper))
		}
	}
	return &data, nil
}

// parseTriageData unmarshals raw json data into a triageData struct and creates a BuildIndexer for
// every job.

parseTriageData unmarshals raw json data into a triageData struct and creates a BuildIndexer for every job .


func topClusters(clusters []*Cluster, count int) []*Cluster {
	less := func(i, j int) bool { return clusters[i].totalBuilds > clusters[j].totalBuilds }
	sort.SliceStable(clusters, less)

	if len(clusters) < count {
		count = len(clusters)
	}
	return clusters[0:count]
}

func topClusters ( clusters [ ] * Cluster , count int ) [ ] * Cluster { less := func ( i , j int ) bool { return clusters [ i ] . totalBuilds > clusters [ j ] . totalBuilds } sort . SliceStable ( clusters , less ) if len ( clusters ) < count { count = len ( clusters ) } return clusters [ 0 : count ] }

// topClusters gets the 'count' most important clusters from a slice of clusters based on number of build failures.

topClusters gets the count most important clusters from a slice of clusters based on number of build failures .


func (c *Cluster) topJobsFailed(count int) []*Job {
	slice := make([]*Job, len(c.jobs))
	i := 0
	for jobName, builds := range c.jobs {
		slice[i] = &Job{Name: jobName, Builds: builds}
		i++
	}
	less := func(i, j int) bool { return len(slice[i].Builds) > len(slice[j].Builds) }
	sort.SliceStable(slice, less)

	if len(slice) < count {
		count = len(slice)
	}
	return slice[0:count]
}

func ( c * Cluster ) topJobsFailed ( count int ) [ ] * Job { slice := make ( [ ] * Job , len ( c . jobs ) ) i := 0 for jobName , builds := range c . jobs { slice [ i ] = & Job { Name : jobName , Builds : builds } i ++ } less := func ( i , j int ) bool { return len ( slice [ i ] . Builds ) > len ( slice [ j ] . Builds ) } sort . SliceStable ( slice , less ) if len ( slice ) < count { count = len ( slice ) } return slice [ 0 : count ] }

// topJobsFailed returns the top 'count' job names sorted by number of failing builds.

topJobsFailed returns the top count job names sorted by number of failing builds .


func (c *Cluster) Title() string {
	return fmt.Sprintf("Failure cluster [%s...] failed %d builds, %d jobs, and %d tests over %d days",
		c.Identifier[0:6],
		c.totalBuilds,
		c.totalJobs,
		c.totalTests,
		c.filer.windowDays,
	)
}

func ( c * Cluster ) Title ( ) string { return fmt . Sprintf ( " " , c . Identifier [ 0 : 6 ] , c . totalBuilds , c . totalJobs , c . totalTests , c . filer . windowDays , ) }

// Title is the string to use as the github issue title.

Title is the string to use as the github issue title .


func (c *Cluster) Labels() []string {
	labels := []string{"kind/flake"}

	topTests := make([]string, len(c.Tests))
	for i, test := range c.topTestsFailed(len(c.Tests)) {
		topTests[i] = test.Name
	}
	for sig := range c.filer.creator.TestsSIGs(topTests) {
		labels = append(labels, "sig/"+sig)
	}

	return labels
}

func ( c * Cluster ) Labels ( ) [ ] string { labels := [ ] string { " " } topTests := make ( [ ] string , len ( c . Tests ) ) for i , test := range c . topTestsFailed ( len ( c . Tests ) ) { topTests [ i ] = test . Name } for sig := range c . filer . creator . TestsSIGs ( topTests ) { labels = append ( labels , " " + sig ) } return labels }

// Labels returns the labels to apply to the issue created for this cluster on github.

Labels returns the labels to apply to the issue created for this cluster on github .


func New() *Cron {
	return &Cron{
		cronAgent: cron.New(),
		jobs:      map[string]*jobStatus{},
		logger:    logrus.WithField("client", "cron"),
	}
}

func New ( ) * Cron { return & Cron { cronAgent : cron . New ( ) , jobs : map [ string ] * jobStatus { } , logger : logrus . WithField ( " " , " " ) , } }


func (c *Cron) QueuedJobs() []string {
	c.lock.Lock()
	defer c.lock.Unlock()

	res := []string{}
	for k, v := range c.jobs {
		if v.triggered {
			res = append(res, k)
		}
		c.jobs[k].triggered = false
	}
	return res
}

func ( c * Cron ) QueuedJobs ( ) [ ] string { c . lock . Lock ( ) defer c . lock . Unlock ( ) res := [ ] string { } for k , v := range c . jobs { if v . triggered { res = append ( res , k ) } c . jobs [ k ] . triggered = false } return res }

// QueuedJobs returns a list of jobs that need to be triggered
// and reset trigger in jobStatus

QueuedJobs returns a list of jobs that need to be triggered and reset trigger in jobStatus


func (c *Cron) HasJob(name string) bool {
	c.lock.Lock()
	defer c.lock.Unlock()

	_, ok := c.jobs[name]
	return ok
}

func ( c * Cron ) HasJob ( name string ) bool { c . lock . Lock ( ) defer c . lock . Unlock ( ) _ , ok := c . jobs [ name ] return ok }

// HasJob returns if a job has been scheduled in cronAgent or not


func (c *Cron) addJob(name, cron string) error {
	id, err := c.cronAgent.AddFunc("TZ=UTC "+cron, func() {
		c.lock.Lock()
		defer c.lock.Unlock()

		c.jobs[name].triggered = true
		c.logger.Infof("Triggering cron job %s.", name)
	})

	if err != nil {
		return fmt.Errorf("cronAgent fails to add job %s with cron %s: %v", name, cron, err)
	}

	c.jobs[name] = &jobStatus{
		entryID: id,
		cronStr: cron,
		// try to kick of a periodic trigger right away
		triggered: strings.HasPrefix(cron, "@every"),
	}

	c.logger.Infof("Added new cron job %s with trigger %s.", name, cron)
	return nil
}

// addJob adds a cron entry for a job to cronAgent


func (c *Cron) removeJob(name string) error {
	job, ok := c.jobs[name]
	if !ok {
		return fmt.Errorf("job %s has not been added to cronAgent yet", name)
	}
	c.cronAgent.Remove(job.entryID)
	delete(c.jobs, name)
	c.logger.Infof("Removed previous cron job %s.", name)
	return nil
}

func ( c * Cron ) removeJob ( name string ) error { job , ok := c . jobs [ name ] if ! ok { return fmt . Errorf ( " " , name ) } c . cronAgent . Remove ( job . entryID ) delete ( c . jobs , name ) c . logger . Infof ( " " , name ) return nil }

// removeJob removes the job from cronAgent


func UpdateComments(issueID int, pullRequest bool, db *gorm.DB, client ClientInterface) {
	latest := findLatestCommentUpdate(issueID, db, client.RepositoryName())

	updateIssueComments(issueID, latest, db, client)
	if pullRequest {
		updatePullComments(issueID, latest, db, client)
	}
}

func UpdateComments ( issueID int , pullRequest bool , db * gorm . DB , client ClientInterface ) { latest := findLatestCommentUpdate ( issueID , db , client . RepositoryName ( ) ) updateIssueComments ( issueID , latest , db , client ) if pullRequest { updatePullComments ( issueID , latest , db , client ) } }

// UpdateComments downloads issue and pull-request comments and save in DB

UpdateComments downloads issue and pull - request comments and save in DB


func GatherProwJobMetrics(pjs []prowapi.ProwJob) {
	// map of job to job type to state to count
	metricMap := make(map[string]map[string]map[string]float64)

	for _, pj := range pjs {
		if metricMap[pj.Spec.Job] == nil {
			metricMap[pj.Spec.Job] = make(map[string]map[string]float64)
		}
		if metricMap[pj.Spec.Job][string(pj.Spec.Type)] == nil {
			metricMap[pj.Spec.Job][string(pj.Spec.Type)] = make(map[string]float64)
		}
		metricMap[pj.Spec.Job][string(pj.Spec.Type)][string(pj.Status.State)]++
	}

	// This may be racing with the prometheus server but we need to remove
	// stale metrics like triggered or pending jobs that are now complete.
	prowJobs.Reset()

	for job, jobMap := range metricMap {
		for jobType, typeMap := range jobMap {
			for state, count := range typeMap {
				prowJobs.WithLabelValues(job, jobType, state).Set(count)
			}
		}
	}
}

// GatherProwJobMetrics gathers prometheus metrics for prowjobs.

GatherProwJobMetrics gathers prometheus metrics for prowjobs .


func optionOrDefault(option, defaultValue time.Duration) time.Duration {
	if option == 0 {
		return defaultValue
	}

	return option
}

func optionOrDefault ( option , defaultValue time . Duration ) time . Duration { if option == 0 { return defaultValue } return option }

// optionOrDefault defaults to a value if option
// is the zero value

optionOrDefault defaults to a value if option is the zero value


func newGCSJobSource(src string) (*gcsJobSource, error) {
	gcsURL, err := url.Parse(fmt.Sprintf("gs://%s", src))
	if err != nil {
		return &gcsJobSource{}, ErrCannotParseSource
	}
	gcsPath := &gcs.Path{}
	err = gcsPath.SetURL(gcsURL)
	if err != nil {
		return &gcsJobSource{}, ErrCannotParseSource
	}

	tokens := strings.FieldsFunc(gcsPath.Object(), func(c rune) bool { return c == '/' })
	if len(tokens) < 2 {
		return &gcsJobSource{}, ErrCannotParseSource
	}
	buildID := tokens[len(tokens)-1]
	name := tokens[len(tokens)-2]
	return &gcsJobSource{
		source:     src,
		linkPrefix: "gs://",
		bucket:     gcsPath.Bucket(),
		jobPrefix:  path.Clean(gcsPath.Object()) + "/",
		jobName:    name,
		buildID:    buildID,
	}, nil
}

func newGCSJobSource ( src string ) ( * gcsJobSource , error ) { gcsURL , err := url . Parse ( fmt . Sprintf ( " " , src ) ) if err != nil { return & gcsJobSource { } , ErrCannotParseSource } gcsPath := & gcs . Path { } err = gcsPath . SetURL ( gcsURL ) if err != nil { return & gcsJobSource { } , ErrCannotParseSource } tokens := strings . FieldsFunc ( gcsPath . Object ( ) , func ( c rune ) bool { return c == '/' } ) if len ( tokens ) < 2 { return & gcsJobSource { } , ErrCannotParseSource } buildID := tokens [ len ( tokens ) - 1 ] name := tokens [ len ( tokens ) - 2 ] return & gcsJobSource { source : src , linkPrefix : " " , bucket : gcsPath . Bucket ( ) , jobPrefix : path . Clean ( gcsPath . Object ( ) ) + " " , jobName : name , buildID : buildID , } , nil }

// newGCSJobSource creates a new gcsJobSource from a given bucket and jobPrefix


func (af *GCSArtifactFetcher) artifacts(key string) ([]string, error) {
	src, err := newGCSJobSource(key)
	if err != nil {
		return nil, fmt.Errorf("Failed to get GCS job source from %s: %v", key, err)
	}

	listStart := time.Now()
	bucketName, prefix := extractBucketPrefixPair(src.jobPath())
	artifacts := []string{}
	bkt := af.client.Bucket(bucketName)
	q := storage.Query{
		Prefix:   prefix,
		Versions: false,
	}
	objIter := bkt.Objects(context.Background(), &q)
	wait := []time.Duration{16, 32, 64, 128, 256, 256, 512, 512}
	for i := 0; ; {
		oAttrs, err := objIter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			logrus.WithFields(fieldsForJob(src)).WithError(err).Error("Error accessing GCS artifact.")
			if i >= len(wait) {
				return artifacts, fmt.Errorf("timed out: error accessing GCS artifact: %v", err)
			}
			time.Sleep((wait[i] + time.Duration(rand.Intn(10))) * time.Millisecond)
			i++
			continue
		}
		artifacts = append(artifacts, strings.TrimPrefix(oAttrs.Name, prefix))
		i = 0
	}
	listElapsed := time.Since(listStart)
	logrus.WithField("duration", listElapsed).Infof("Listed %d artifacts.", len(artifacts))
	return artifacts, nil
}

func ( af * GCSArtifactFetcher ) artifacts ( key string ) ( [ ] string , error ) { src , err := newGCSJobSource ( key ) if err != nil { return nil , fmt . Errorf ( " " , key , err ) } listStart := time . Now ( ) bucketName , prefix := extractBucketPrefixPair ( src . jobPath ( ) ) artifacts := [ ] string { } bkt := af . client . Bucket ( bucketName ) q := storage . Query { Prefix : prefix , Versions : false , } objIter := bkt . Objects ( context . Background ( ) , & q ) wait := [ ] time . Duration { 16 , 32 , 64 , 128 , 256 , 256 , 512 , 512 } for i := 0 ; ; { oAttrs , err := objIter . Next ( ) if err == iterator . Done { break } if err != nil { logrus . WithFields ( fieldsForJob ( src ) ) . WithError ( err ) . Error ( " " ) if i >= len ( wait ) { return artifacts , fmt . Errorf ( " " , err ) } time . Sleep ( ( wait [ i ] + time . Duration ( rand . Intn ( 10 ) ) ) * time . Millisecond ) i ++ continue } artifacts = append ( artifacts , strings . TrimPrefix ( oAttrs . Name , prefix ) ) i = 0 } listElapsed := time . Since ( listStart ) logrus . WithField ( " " , listElapsed ) . Infof ( " " , len ( artifacts ) ) return artifacts , nil }

// Artifacts lists all artifacts available for the given job source


func (src *gcsJobSource) canonicalLink() string {
	return path.Join(src.linkPrefix, src.bucket, src.jobPrefix)
}

func ( src * gcsJobSource ) canonicalLink ( ) string { return path . Join ( src . linkPrefix , src . bucket , src . jobPrefix ) }

// CanonicalLink gets a link to the location of job-specific artifacts in GCS

CanonicalLink gets a link to the location of job - specific artifacts in GCS


func (src *gcsJobSource) jobPath() string {
	return fmt.Sprintf("%s/%s", src.bucket, src.jobPrefix)
}

func ( src * gcsJobSource ) jobPath ( ) string { return fmt . Sprintf ( " " , src . bucket , src . jobPrefix ) }

// JobPath gets the prefix to all artifacts in GCS in the job


func targetURL(c config.Getter, pr *PullRequest, log *logrus.Entry) string {
	var link string
	if tideURL := c().Tide.TargetURL; tideURL != "" {
		link = tideURL
	} else if baseURL := c().Tide.PRStatusBaseURL; baseURL != "" {
		parseURL, err := url.Parse(baseURL)
		if err != nil {
			log.WithError(err).Error("Failed to parse PR status base URL")
		} else {
			prQuery := fmt.Sprintf("is:pr repo:%s author:%s head:%s", pr.Repository.NameWithOwner, pr.Author.Login, pr.HeadRefName)
			values := parseURL.Query()
			values.Set("query", prQuery)
			parseURL.RawQuery = values.Encode()
			link = parseURL.String()
		}
	}
	return link
}

func targetURL ( c config . Getter , pr * PullRequest , log * logrus . Entry ) string { var link string if tideURL := c ( ) . Tide . TargetURL ; tideURL != " " { link = tideURL } else if baseURL := c ( ) . Tide . PRStatusBaseURL ; baseURL != " " { parseURL , err := url . Parse ( baseURL ) if err != nil { log . WithError ( err ) . Error ( " " ) } else { prQuery := fmt . Sprintf ( " " , pr . Repository . NameWithOwner , pr . Author . Login , pr . HeadRefName ) values := parseURL . Query ( ) values . Set ( " " , prQuery ) parseURL . RawQuery = values . Encode ( ) link = parseURL . String ( ) } } return link }

// targetURL determines the URL used for more details in the status
// context on GitHub. If no PR dashboard is configured, we will use
// the administrative Prow overview.

targetURL determines the URL used for more details in the status context on GitHub . If no PR dashboard is configured we will use the administrative Prow overview .


func newBuildConfig(cfg rest.Config, stop chan struct{}) (*buildConfig, error) {
	bc, err := buildset.NewForConfig(&cfg)
	if err != nil {
		return nil, err
	}

	// Ensure the knative-build CRD is deployed
	// TODO(fejta): probably a better way to do this
	_, err = bc.BuildV1alpha1().Builds("").List(metav1.ListOptions{Limit: 1})
	if err != nil {
		return nil, err
	}
	// Assume watches receive updates, but resync every 30m in case something wonky happens
	bif := buildinfo.NewSharedInformerFactory(bc, 30*time.Minute)
	bif.Build().V1alpha1().Builds().Lister()
	go bif.Start(stop)
	return &buildConfig{
		client:   bc,
		informer: bif.Build().V1alpha1().Builds(),
	}, nil
}

// newBuildConfig returns a client and informer capable of mutating and monitoring the specified config.

newBuildConfig returns a client and informer capable of mutating and monitoring the specified config .


func NewClient(token string, dryRun bool) *Client {
	httpClient := &http.Client{
		Transport: &oauth2.Transport{
			Base:   http.DefaultTransport,
			Source: oauth2.ReuseTokenSource(nil, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})),
		},
	}
	client := github.NewClient(httpClient)
	return &Client{
		issueService:        client.Issues,
		prService:           client.PullRequests,
		repoService:         client.Repositories,
		userService:         client.Users,
		retries:             5,
		retryInitialBackoff: time.Second,
		tokenReserve:        50,
		dryRun:              dryRun,
	}
}

func NewClient ( token string , dryRun bool ) * Client { httpClient := & http . Client { Transport : & oauth2 . Transport { Base : http . DefaultTransport , Source : oauth2 . ReuseTokenSource ( nil , oauth2 . StaticTokenSource ( & oauth2 . Token { AccessToken : token } ) ) , } , } client := github . NewClient ( httpClient ) return & Client { issueService : client . Issues , prService : client . PullRequests , repoService : client . Repositories , userService : client . Users , retries : 5 , retryInitialBackoff : time . Second , tokenReserve : 50 , dryRun : dryRun , } }

// NewClient makes a new Client with the specified token and dry-run status.

NewClient makes a new Client with the specified token and dry - run status .


func (c *Client) retry(action string, call func() (*github.Response, error)) (*github.Response, error) {
	var err error
	var resp *github.Response

	for retryCount := 0; retryCount <= c.retries; retryCount++ {
		if resp, err = call(); err == nil {
			c.limitRate(&resp.Rate)
			return resp, nil
		}
		switch err := err.(type) {
		case *github.RateLimitError:
			c.limitRate(&err.Rate)
		case *github.TwoFactorAuthError:
			return resp, err
		case *retryAbort:
			return resp, err
		}

		if retryCount == c.retries {
			return resp, err
		}
		glog.Errorf("error %s: %v. Will retry.\n", action, err)
		c.sleepForAttempt(retryCount)
	}
	return resp, err
}

func ( c * Client ) retry ( action string , call func ( ) ( * github . Response , error ) ) ( * github . Response , error ) { var err error var resp * github . Response for retryCount := 0 ; retryCount <= c . retries ; retryCount ++ { if resp , err = call ( ) ; err == nil { c . limitRate ( & resp . Rate ) return resp , nil } switch err := err . ( type ) { case * github . RateLimitError : c . limitRate ( & err . Rate ) case * github . TwoFactorAuthError : return resp , err case * retryAbort : return resp , err } if retryCount == c . retries { return resp , err } glog . Errorf ( " \n " , action , err ) c . sleepForAttempt ( retryCount ) } return resp , err }

// retry handles rate limiting and retry logic for a github API call.

retry handles rate limiting and retry logic for a github API call .


func (c *Client) depaginate(action string, opts *github.ListOptions, call func() ([]interface{}, *github.Response, error)) ([]interface{}, error) {
	var allItems []interface{}
	wrapper := func() (*github.Response, error) {
		items, resp, err := call()
		if err == nil {
			allItems = append(allItems, items...)
		}
		return resp, err
	}

	opts.Page = 1
	opts.PerPage = 100
	lastPage := 1
	for ; opts.Page <= lastPage; opts.Page++ {
		resp, err := c.retry(action, wrapper)
		if err != nil {
			return allItems, fmt.Errorf("error while depaginating page %d/%d: %v", opts.Page, lastPage, err)
		}
		if resp.LastPage > 0 {
			lastPage = resp.LastPage
		}
	}
	return allItems, nil
}

func ( c * Client ) depaginate ( action string , opts * github . ListOptions , call func ( ) ( [ ] interface { } , * github . Response , error ) ) ( [ ] interface { } , error ) { var allItems [ ] interface { } wrapper := func ( ) ( * github . Response , error ) { items , resp , err := call ( ) if err == nil { allItems = append ( allItems , items ... ) } return resp , err } opts . Page = 1 opts . PerPage = 100 lastPage := 1 for ; opts . Page <= lastPage ; opts . Page ++ { resp , err := c . retry ( action , wrapper ) if err != nil { return allItems , fmt . Errorf ( " " , opts . Page , lastPage , err ) } if resp . LastPage > 0 { lastPage = resp . LastPage } } return allItems , nil }

// depaginate adds depagination on top of the retry and rate limiting logic provided by retry.

depaginate adds depagination on top of the retry and rate limiting logic provided by retry .


func NewHelpAgent(pa pluginAgent, ghc githubClient) *HelpAgent {
	l := logrus.WithField("client", "plugin-help")
	return &HelpAgent{
		log: l,
		pa:  pa,
		oa:  newOrgAgent(l, ghc, newRepoDetectionLimit),
	}
}

func NewHelpAgent ( pa pluginAgent , ghc githubClient ) * HelpAgent { l := logrus . WithField ( " " , " " ) return & HelpAgent { log : l , pa : pa , oa : newOrgAgent ( l , ghc , newRepoDetectionLimit ) , } }

// NewHelpAgent constructs a new HelpAgent.

NewHelpAgent constructs a new HelpAgent .


func (ha *HelpAgent) GeneratePluginHelp() *pluginhelp.Help {
	config := ha.pa.Config()
	orgToRepos := ha.oa.orgToReposMap(config)

	normalRevMap, externalRevMap := reversePluginMaps(config, orgToRepos)

	allPlugins, pluginHelp := ha.generateNormalPluginHelp(config, normalRevMap)

	allExternalPlugins, externalPluginHelp := ha.generateExternalPluginHelp(config, externalRevMap)

	// Load repo->plugins maps from config
	repoPlugins := map[string][]string{
		"": allPlugins,
	}
	for repo, plugins := range config.Plugins {
		repoPlugins[repo] = plugins
	}
	repoExternalPlugins := map[string][]string{
		"": allExternalPlugins,
	}
	for repo, exts := range config.ExternalPlugins {
		for _, ext := range exts {
			repoExternalPlugins[repo] = append(repoExternalPlugins[repo], ext.Name)
		}
	}

	return &pluginhelp.Help{
		AllRepos:            allRepos(config, orgToRepos),
		RepoPlugins:         repoPlugins,
		RepoExternalPlugins: repoExternalPlugins,
		PluginHelp:          pluginHelp,
		ExternalPluginHelp:  externalPluginHelp,
	}
}

func
(
ha
*
HelpAgent
)
GeneratePluginHelp
(
)
*
pluginhelp
.
Help
{
config
:=
ha
.
pa
.
Config
(
)
orgToRepos
:=
ha
.
oa
.
orgToReposMap
(
config
)
normalRevMap
,
externalRevMap
:=
reversePluginMaps
(
config
,
orgToRepos
)
allPlugins
,
pluginHelp
:=
ha
.
generateNormalPluginHelp
(
config
,
normalRevMap
)
allExternalPlugins
,
externalPluginHelp
:=
ha
.
generateExternalPluginHelp
(
config
,
externalRevMap
)
// Load repo->plugins maps from config
repoPlugins
:=
map
[
string
]
[
]
string
{
"
"
:
allPlugins
,
}
for
repo
,
plugins
:=
range
config
.
Plugins
{
repoPlugins
[
repo
]
=
plugins
}
repoExternalPlugins
:=
map
[
string
]
[
]
string
{
"
"
:
allExternalPlugins
,
}
for
repo
,
exts
:=
range
config
.
ExternalPlugins
{
for
_
,
ext
:=
range
exts
{
repoExternalPlugins
[
repo
]
=
append
(
repoExternalPlugins
[
repo
]
,
ext
.
Name
)
}
}
return
&
pluginhelp
.
Help
{
AllRepos
:
allRepos
(
config
,
orgToRepos
)
,
RepoPlugins
:
repoPlugins
,
RepoExternalPlugins
:
repoExternalPlugins
,
PluginHelp
:
pluginHelp
,
ExternalPluginHelp
:
externalPluginHelp
,
}
}

// GeneratePluginHelp compiles and returns the help information for all plugins.

GeneratePluginHelp compiles and returns the help information for all plugins .


func getPullCommitHash(pull string) (string, error) {
	match := pullCommitRe.FindStringSubmatch(pull)
	if len(match) != 2 {
		expected := "branch:hash,pullNumber:hash"
		return "", fmt.Errorf("unable to parse pull %q (expected %q)", pull, expected)
	}
	return match[1], nil
}

func getPullCommitHash ( pull string ) ( string , error ) { match := pullCommitRe . FindStringSubmatch ( pull ) if len ( match ) != 2 { expected := " " return " " , fmt . Errorf ( " " , pull , expected ) } return match [ 1 ] , nil }

// gets the pull commit hash from metadata


func listJobBuilds(bucket storageBucket, jobPrefixes []string) []jobBuilds {
	jobch := make(chan jobBuilds)
	defer close(jobch)
	for i, jobPrefix := range jobPrefixes {
		go func(i int, jobPrefix string) {
			buildPrefixes, err := bucket.listSubDirs(jobPrefix)
			if err != nil {
				logrus.WithError(err).Warningf("Error getting builds for job %s", jobPrefix)
			}
			jobch <- jobBuilds{
				name:          path.Base(jobPrefix),
				buildPrefixes: buildPrefixes,
			}
		}(i, jobPrefix)
	}
	jobs := []jobBuilds{}
	for range jobPrefixes {
		job := <-jobch
		jobs = append(jobs, job)
	}
	return jobs
}

func listJobBuilds ( bucket storageBucket , jobPrefixes [ ] string ) [ ] jobBuilds { jobch := make ( chan jobBuilds ) defer close ( jobch ) for i , jobPrefix := range jobPrefixes { go func ( i int , jobPrefix string ) { buildPrefixes , err := bucket . listSubDirs ( jobPrefix ) if err != nil { logrus . WithError ( err ) . Warningf ( " " , jobPrefix ) } jobch <- jobBuilds { name : path . Base ( jobPrefix ) , buildPrefixes : buildPrefixes , } } ( i , jobPrefix ) } jobs := [ ] jobBuilds { } for range jobPrefixes { job := <- jobch jobs = append ( jobs , job ) } return jobs }

// listJobBuilds concurrently lists builds for the given job prefixes that have been run on a PR