sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/plugins.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package plugins
    18  
    19  import (
    20  	"context"
    21  	_ "embed"
    22  	"errors"
    23  	"fmt"
    24  	"io/fs"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  
    33  	"sigs.k8s.io/prow/pkg/genyaml"
    34  
    35  	"github.com/prometheus/client_golang/prometheus"
    36  	"github.com/sirupsen/logrus"
    37  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    38  	"k8s.io/client-go/kubernetes"
    39  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    40  	"sigs.k8s.io/yaml"
    41  
    42  	"sigs.k8s.io/prow/pkg/bugzilla"
    43  	prowv1 "sigs.k8s.io/prow/pkg/client/clientset/versioned/typed/prowjobs/v1"
    44  	"sigs.k8s.io/prow/pkg/commentpruner"
    45  	"sigs.k8s.io/prow/pkg/config"
    46  	"sigs.k8s.io/prow/pkg/git/v2"
    47  	"sigs.k8s.io/prow/pkg/github"
    48  	"sigs.k8s.io/prow/pkg/jira"
    49  	"sigs.k8s.io/prow/pkg/pluginhelp"
    50  	"sigs.k8s.io/prow/pkg/repoowners"
    51  	"sigs.k8s.io/prow/pkg/slack"
    52  	"sigs.k8s.io/prow/pkg/version"
    53  )
    54  
    55  var (
    56  	pluginHelp                 = map[string]HelpProvider{}
    57  	genericCommentHandlers     = map[string]GenericCommentHandler{}
    58  	issueHandlers              = map[string]IssueHandler{}
    59  	issueCommentHandlers       = map[string]IssueCommentHandler{}
    60  	pullRequestHandlers        = map[string]PullRequestHandler{}
    61  	pushEventHandlers          = map[string]PushEventHandler{}
    62  	reviewEventHandlers        = map[string]ReviewEventHandler{}
    63  	reviewCommentEventHandlers = map[string]ReviewCommentEventHandler{}
    64  	statusEventHandlers        = map[string]StatusEventHandler{}
    65  	// CommentMap is used by many plugins for printing help messages defined in
    66  	// config.go.
    67  	CommentMap, _ = genyaml.NewCommentMap(nil)
    68  
    69  	//go:embed config.go
    70  	embededConfigGoFileContent []byte
    71  )
    72  
    73  func init() {
    74  	// This requires the source code to be present and to be in the right relative
    75  	// location to the working directory. Don't even bother to try outside of the
    76  	// hook binary, otherwise all components that load the plugin config initially
    77  	// show an error which is confusing.
    78  	if version.Name != "hook" {
    79  		return
    80  	}
    81  
    82  	if cm, err := genyaml.NewCommentMap(map[string][]byte{"prow/plugins/config.go": embededConfigGoFileContent}); err == nil {
    83  		CommentMap = cm
    84  	} else {
    85  		logrus.WithError(err).Error("Failed to initialize commentMap")
    86  	}
    87  }
    88  
    89  // HelpProvider defines the function type that construct a pluginhelp.PluginHelp for enabled
    90  // plugins. It takes into account the plugins configuration and enabled repositories.
    91  type HelpProvider func(config *Configuration, enabledRepos []config.OrgRepo) (*pluginhelp.PluginHelp, error)
    92  
    93  // HelpProviders returns the map of registered plugins with their associated HelpProvider.
    94  func HelpProviders() map[string]HelpProvider {
    95  	return pluginHelp
    96  }
    97  
    98  // IssueHandler defines the function contract for a github.IssueEvent handler.
    99  type IssueHandler func(Agent, github.IssueEvent) error
   100  
   101  // RegisterIssueHandler registers a plugin's github.IssueEvent handler.
   102  func RegisterIssueHandler(name string, fn IssueHandler, help HelpProvider) {
   103  	pluginHelp[name] = help
   104  	issueHandlers[name] = fn
   105  }
   106  
   107  // IssueCommentHandler defines the function contract for a github.IssueCommentEvent handler.
   108  type IssueCommentHandler func(Agent, github.IssueCommentEvent) error
   109  
   110  // RegisterIssueCommentHandler registers a plugin's github.IssueCommentEvent handler.
   111  func RegisterIssueCommentHandler(name string, fn IssueCommentHandler, help HelpProvider) {
   112  	pluginHelp[name] = help
   113  	issueCommentHandlers[name] = fn
   114  }
   115  
   116  // PullRequestHandler defines the function contract for a github.PullRequestEvent handler.
   117  type PullRequestHandler func(Agent, github.PullRequestEvent) error
   118  
   119  // RegisterPullRequestHandler registers a plugin's github.PullRequestEvent handler.
   120  func RegisterPullRequestHandler(name string, fn PullRequestHandler, help HelpProvider) {
   121  	pluginHelp[name] = help
   122  	pullRequestHandlers[name] = fn
   123  }
   124  
   125  // StatusEventHandler defines the function contract for a github.StatusEvent handler.
   126  type StatusEventHandler func(Agent, github.StatusEvent) error
   127  
   128  // RegisterStatusEventHandler registers a plugin's github.StatusEvent handler.
   129  func RegisterStatusEventHandler(name string, fn StatusEventHandler, help HelpProvider) {
   130  	pluginHelp[name] = help
   131  	statusEventHandlers[name] = fn
   132  }
   133  
   134  // PushEventHandler defines the function contract for a github.PushEvent handler.
   135  type PushEventHandler func(Agent, github.PushEvent) error
   136  
   137  // RegisterPushEventHandler registers a plugin's github.PushEvent handler.
   138  func RegisterPushEventHandler(name string, fn PushEventHandler, help HelpProvider) {
   139  	pluginHelp[name] = help
   140  	pushEventHandlers[name] = fn
   141  }
   142  
   143  // ReviewEventHandler defines the function contract for a github.ReviewEvent handler.
   144  type ReviewEventHandler func(Agent, github.ReviewEvent) error
   145  
   146  // RegisterReviewEventHandler registers a plugin's github.ReviewEvent handler.
   147  func RegisterReviewEventHandler(name string, fn ReviewEventHandler, help HelpProvider) {
   148  	pluginHelp[name] = help
   149  	reviewEventHandlers[name] = fn
   150  }
   151  
   152  // ReviewCommentEventHandler defines the function contract for a github.ReviewCommentEvent handler.
   153  type ReviewCommentEventHandler func(Agent, github.ReviewCommentEvent) error
   154  
   155  // RegisterReviewCommentEventHandler registers a plugin's github.ReviewCommentEvent handler.
   156  func RegisterReviewCommentEventHandler(name string, fn ReviewCommentEventHandler, help HelpProvider) {
   157  	pluginHelp[name] = help
   158  	reviewCommentEventHandlers[name] = fn
   159  }
   160  
   161  // GenericCommentHandler defines the function contract for a github.GenericCommentEvent handler.
   162  type GenericCommentHandler func(Agent, github.GenericCommentEvent) error
   163  
   164  // RegisterGenericCommentHandler registers a plugin's github.GenericCommentEvent handler.
   165  func RegisterGenericCommentHandler(name string, fn GenericCommentHandler, help HelpProvider) {
   166  	pluginHelp[name] = help
   167  	genericCommentHandlers[name] = fn
   168  }
   169  
   170  type PluginGitHubClient interface {
   171  	github.Client
   172  	Query(ctx context.Context, q interface{}, vars map[string]interface{}) error
   173  }
   174  
   175  // Agent may be used concurrently, so each entry must be thread-safe.
   176  type Agent struct {
   177  	GitHubClient              PluginGitHubClient
   178  	ProwJobClient             prowv1.ProwJobInterface
   179  	KubernetesClient          kubernetes.Interface
   180  	BuildClusterCoreV1Clients map[string]corev1.CoreV1Interface
   181  	GitClient                 git.ClientFactory
   182  	SlackClient               *slack.Client
   183  	BugzillaClient            bugzilla.Client
   184  	JiraClient                jira.Client
   185  
   186  	OwnersClient repoowners.Interface
   187  
   188  	// Metrics exposes metrics that can be updated by plugins
   189  	Metrics *Metrics
   190  
   191  	// Config provides information about the jobs
   192  	// that we know how to run for repos.
   193  	Config *config.Config
   194  	// PluginConfig provides plugin-specific options
   195  	PluginConfig *Configuration
   196  
   197  	Logger *logrus.Entry
   198  
   199  	// may be nil if not initialized
   200  	commentPruner *commentpruner.EventClient
   201  }
   202  
   203  // NewAgent bootstraps a new config.Agent struct from the passed dependencies.
   204  func NewAgent(configAgent *config.Agent, pluginConfigAgent *ConfigAgent, clientAgent *ClientAgent, githubOrg string, metrics *Metrics, logger *logrus.Entry, plugin string) Agent {
   205  	logger = logger.WithField("plugin", plugin)
   206  	prowConfig := configAgent.Config()
   207  	pluginConfig := pluginConfigAgent.Config()
   208  	gitHubClient := &githubV4OrgAddingWrapper{org: githubOrg, Client: clientAgent.GitHubClient.WithFields(logger.Data).ForPlugin(plugin)}
   209  	jiraClient := clientAgent.JiraClient
   210  	if jiraClient != nil {
   211  		jiraClient = clientAgent.JiraClient.WithFields(logger.Data).ForPlugin(plugin)
   212  	}
   213  	return Agent{
   214  		GitHubClient:              gitHubClient,
   215  		KubernetesClient:          clientAgent.KubernetesClient,
   216  		BuildClusterCoreV1Clients: clientAgent.BuildClusterCoreV1Clients,
   217  		ProwJobClient:             clientAgent.ProwJobClient,
   218  		GitClient:                 clientAgent.GitClient,
   219  		SlackClient:               clientAgent.SlackClient,
   220  		OwnersClient:              clientAgent.OwnersClient.WithFields(logger.Data).WithGitHubClient(gitHubClient).ForPlugin(plugin),
   221  		BugzillaClient:            clientAgent.BugzillaClient.WithFields(logger.Data).ForPlugin(plugin),
   222  		JiraClient:                jiraClient,
   223  		Metrics:                   metrics,
   224  		Config:                    prowConfig,
   225  		PluginConfig:              pluginConfig,
   226  		Logger:                    logger,
   227  	}
   228  }
   229  
   230  // InitializeCommentPruner attaches a commentpruner.EventClient to the agent to handle
   231  // pruning comments.
   232  func (a *Agent) InitializeCommentPruner(org, repo string, pr int) {
   233  	a.commentPruner = commentpruner.NewEventClient(
   234  		a.GitHubClient, a.Logger.WithField("client", "commentpruner"),
   235  		org, repo, pr,
   236  	)
   237  }
   238  
   239  // TookAction indicates whether any client with implemented Used() function was used
   240  func (a *Agent) TookAction() bool {
   241  	jiraClientTookAction := false
   242  	if a.JiraClient != nil {
   243  		jiraClientTookAction = a.JiraClient.Used()
   244  	}
   245  	return a.GitHubClient.Used() || a.OwnersClient.Used() || a.BugzillaClient.Used() || jiraClientTookAction
   246  }
   247  
   248  // CommentPruner will return the commentpruner.EventClient attached to the agent or an error
   249  // if one is not attached.
   250  func (a *Agent) CommentPruner() (*commentpruner.EventClient, error) {
   251  	if a.commentPruner == nil {
   252  		return nil, errors.New("comment pruner client never initialized")
   253  	}
   254  	return a.commentPruner, nil
   255  }
   256  
   257  // ClientAgent contains the various clients that are attached to the Agent.
   258  type ClientAgent struct {
   259  	GitHubClient              github.Client
   260  	ProwJobClient             prowv1.ProwJobInterface
   261  	KubernetesClient          kubernetes.Interface
   262  	BuildClusterCoreV1Clients map[string]corev1.CoreV1Interface
   263  	GitClient                 git.ClientFactory
   264  	SlackClient               *slack.Client
   265  	OwnersClient              repoowners.Interface
   266  	BugzillaClient            bugzilla.Client
   267  	JiraClient                jira.Client
   268  }
   269  
   270  // ConfigAgent contains the agent mutex and the Agent configuration.
   271  type ConfigAgent struct {
   272  	mut           sync.Mutex
   273  	configuration *Configuration
   274  }
   275  
   276  func NewFakeConfigAgent() ConfigAgent {
   277  	return ConfigAgent{configuration: &Configuration{}}
   278  }
   279  
   280  // Load attempts to load config from the path. It returns an error if either
   281  // the file can't be read or the configuration is invalid.
   282  // If checkUnknownPlugins is true, unrecognized plugin names will make config
   283  // loading fail.
   284  // If skipResolveConfigUpdater is true, the ConfigUpdater of the config will not be resolved.
   285  func (pa *ConfigAgent) Load(path string, supplementalPluginConfigDirs []string, supplementalPluginConfigFileSuffix string, checkUnknownPlugins, skipResolveConfigUpdater bool) error {
   286  	b, err := os.ReadFile(path)
   287  	if err != nil {
   288  		return err
   289  	}
   290  	np := &Configuration{}
   291  	if err := yaml.Unmarshal(b, np); err != nil {
   292  		return err
   293  	}
   294  
   295  	var errs []error
   296  	for _, supplementalPluginConfigDir := range supplementalPluginConfigDirs {
   297  		if supplementalPluginConfigFileSuffix == "" {
   298  			break
   299  		}
   300  		if err := filepath.Walk(supplementalPluginConfigDir, func(path string, info fs.FileInfo, err error) error {
   301  			if err != nil {
   302  				return err
   303  			}
   304  
   305  			// Kubernetes configmap mounts create symlinks for the configmap keys that point to files prefixed with '..'.
   306  			// This allows it to do  atomic changes by changing the symlink to a new target when the configmap content changes.
   307  			// This means that we should ignore the '..'-prefixed files, otherwise we might end up reading a half-written file and will
   308  			// get duplicate data.
   309  			if strings.HasPrefix(info.Name(), "..") {
   310  				if info.IsDir() {
   311  					return filepath.SkipDir
   312  				}
   313  				return nil
   314  			}
   315  
   316  			if info.IsDir() || !strings.HasSuffix(path, supplementalPluginConfigFileSuffix) {
   317  				return nil
   318  			}
   319  
   320  			data, err := os.ReadFile(path)
   321  			if err != nil {
   322  				errs = append(errs, fmt.Errorf("failed to read %s: %w", path, err))
   323  				return nil
   324  			}
   325  
   326  			cfg := &Configuration{}
   327  			if err := yaml.Unmarshal(data, cfg); err != nil {
   328  				errs = append(errs, fmt.Errorf("failed to unmarshal %s: %w", path, err))
   329  				return nil
   330  			}
   331  
   332  			if err := np.mergeFrom(cfg); err != nil {
   333  				errs = append(errs, fmt.Errorf("failed to merge config from %s into main config: %w", path, err))
   334  			}
   335  
   336  			return nil
   337  
   338  		}); err != nil {
   339  			errs = append(errs, fmt.Errorf("failed to walk %s: %w", supplementalPluginConfigDir, err))
   340  		}
   341  	}
   342  	if err := utilerrors.NewAggregate(errs); err != nil {
   343  		return err
   344  	}
   345  
   346  	if err := np.Validate(); err != nil {
   347  		return err
   348  	}
   349  	if checkUnknownPlugins {
   350  		if err := np.ValidatePluginsUnknown(); err != nil {
   351  			return err
   352  		}
   353  	}
   354  	if !skipResolveConfigUpdater {
   355  		if err := np.ConfigUpdater.resolve(); err != nil {
   356  			return err
   357  		}
   358  	}
   359  
   360  	pa.Set(np)
   361  	return nil
   362  }
   363  
   364  // Config returns the agent current Configuration.
   365  func (pa *ConfigAgent) Config() *Configuration {
   366  	pa.mut.Lock()
   367  	defer pa.mut.Unlock()
   368  	return pa.configuration
   369  }
   370  
   371  // Set attempts to set the plugins that are enabled on repos. Plugins are listed
   372  // as a map from repositories to the list of plugins that are enabled on them.
   373  // Specifying simply an org name will also work, and will enable the plugin on
   374  // all repos in the org.
   375  func (pa *ConfigAgent) Set(pc *Configuration) {
   376  	pa.mut.Lock()
   377  	defer pa.mut.Unlock()
   378  	pa.configuration = pc
   379  }
   380  
   381  // Start starts polling path for plugin config. If the first attempt fails,
   382  // then start returns the error. Future errors will halt updates but not stop.
   383  // If checkUnknownPlugins is true, unrecognized plugin names will make config
   384  // loading fail.
   385  func (pa *ConfigAgent) Start(path string, supplementalPluginConfigDirs []string, supplementalPluginConfigFileSuffix string, checkUnknownPlugins, skipResolveConfigUpdater bool) error {
   386  	if err := pa.Load(path, supplementalPluginConfigDirs, supplementalPluginConfigFileSuffix, checkUnknownPlugins, skipResolveConfigUpdater); err != nil {
   387  		return err
   388  	}
   389  	ticker := time.NewTicker(time.Minute)
   390  	go func() {
   391  		for range ticker.C {
   392  			if err := pa.Load(path, supplementalPluginConfigDirs, supplementalPluginConfigFileSuffix, checkUnknownPlugins, skipResolveConfigUpdater); err != nil {
   393  				logrus.WithField("path", path).WithError(err).Error("Error loading plugin config.")
   394  			}
   395  		}
   396  	}()
   397  	return nil
   398  }
   399  
   400  // GenericCommentHandlers returns a map of plugin names to handlers for the repo.
   401  func (pa *ConfigAgent) GenericCommentHandlers(owner, repo string) map[string]GenericCommentHandler {
   402  	pa.mut.Lock()
   403  	defer pa.mut.Unlock()
   404  
   405  	hs := map[string]GenericCommentHandler{}
   406  	for _, p := range pa.getPlugins(owner, repo) {
   407  		if h, ok := genericCommentHandlers[p]; ok {
   408  			hs[p] = h
   409  		}
   410  	}
   411  	return hs
   412  }
   413  
   414  // IssueHandlers returns a map of plugin names to handlers for the repo.
   415  func (pa *ConfigAgent) IssueHandlers(owner, repo string) map[string]IssueHandler {
   416  	pa.mut.Lock()
   417  	defer pa.mut.Unlock()
   418  
   419  	hs := map[string]IssueHandler{}
   420  	for _, p := range pa.getPlugins(owner, repo) {
   421  		if h, ok := issueHandlers[p]; ok {
   422  			hs[p] = h
   423  		}
   424  	}
   425  	return hs
   426  }
   427  
   428  // IssueCommentHandlers returns a map of plugin names to handlers for the repo.
   429  func (pa *ConfigAgent) IssueCommentHandlers(owner, repo string) map[string]IssueCommentHandler {
   430  	pa.mut.Lock()
   431  	defer pa.mut.Unlock()
   432  
   433  	hs := map[string]IssueCommentHandler{}
   434  	for _, p := range pa.getPlugins(owner, repo) {
   435  		if h, ok := issueCommentHandlers[p]; ok {
   436  			hs[p] = h
   437  		}
   438  	}
   439  
   440  	return hs
   441  }
   442  
   443  // PullRequestHandlers returns a map of plugin names to handlers for the repo.
   444  func (pa *ConfigAgent) PullRequestHandlers(owner, repo string) map[string]PullRequestHandler {
   445  	pa.mut.Lock()
   446  	defer pa.mut.Unlock()
   447  
   448  	hs := map[string]PullRequestHandler{}
   449  	for _, p := range pa.getPlugins(owner, repo) {
   450  		if h, ok := pullRequestHandlers[p]; ok {
   451  			hs[p] = h
   452  		}
   453  	}
   454  
   455  	return hs
   456  }
   457  
   458  // ReviewEventHandlers returns a map of plugin names to handlers for the repo.
   459  func (pa *ConfigAgent) ReviewEventHandlers(owner, repo string) map[string]ReviewEventHandler {
   460  	pa.mut.Lock()
   461  	defer pa.mut.Unlock()
   462  
   463  	hs := map[string]ReviewEventHandler{}
   464  	for _, p := range pa.getPlugins(owner, repo) {
   465  		if h, ok := reviewEventHandlers[p]; ok {
   466  			hs[p] = h
   467  		}
   468  	}
   469  
   470  	return hs
   471  }
   472  
   473  // ReviewCommentEventHandlers returns a map of plugin names to handlers for the repo.
   474  func (pa *ConfigAgent) ReviewCommentEventHandlers(owner, repo string) map[string]ReviewCommentEventHandler {
   475  	pa.mut.Lock()
   476  	defer pa.mut.Unlock()
   477  
   478  	hs := map[string]ReviewCommentEventHandler{}
   479  	for _, p := range pa.getPlugins(owner, repo) {
   480  		if h, ok := reviewCommentEventHandlers[p]; ok {
   481  			hs[p] = h
   482  		}
   483  	}
   484  
   485  	return hs
   486  }
   487  
   488  // StatusEventHandlers returns a map of plugin names to handlers for the repo.
   489  func (pa *ConfigAgent) StatusEventHandlers(owner, repo string) map[string]StatusEventHandler {
   490  	pa.mut.Lock()
   491  	defer pa.mut.Unlock()
   492  
   493  	hs := map[string]StatusEventHandler{}
   494  	for _, p := range pa.getPlugins(owner, repo) {
   495  		if h, ok := statusEventHandlers[p]; ok {
   496  			hs[p] = h
   497  		}
   498  	}
   499  
   500  	return hs
   501  }
   502  
   503  // PushEventHandlers returns a map of plugin names to handlers for the repo.
   504  func (pa *ConfigAgent) PushEventHandlers(owner, repo string) map[string]PushEventHandler {
   505  	pa.mut.Lock()
   506  	defer pa.mut.Unlock()
   507  
   508  	hs := map[string]PushEventHandler{}
   509  	for _, p := range pa.getPlugins(owner, repo) {
   510  		if h, ok := pushEventHandlers[p]; ok {
   511  			hs[p] = h
   512  		}
   513  	}
   514  
   515  	return hs
   516  }
   517  
   518  // getPlugins returns a list of plugins that are enabled on a given (org, repository).
   519  func (pa *ConfigAgent) getPlugins(owner, repo string) []string {
   520  	var plugins []string
   521  
   522  	fullName := fmt.Sprintf("%s/%s", owner, repo)
   523  	if !sets.NewString(pa.configuration.Plugins[owner].ExcludedRepos...).Has(repo) {
   524  		plugins = append(plugins, pa.configuration.Plugins[owner].Plugins...)
   525  	}
   526  	plugins = append(plugins, pa.configuration.Plugins[fullName].Plugins...)
   527  
   528  	return plugins
   529  }
   530  
   531  // EventsForPlugin returns the registered events for the passed plugin.
   532  func EventsForPlugin(name string) []string {
   533  	var events []string
   534  	if _, ok := issueHandlers[name]; ok {
   535  		events = append(events, "issue")
   536  	}
   537  	if _, ok := issueCommentHandlers[name]; ok {
   538  		events = append(events, "issue_comment")
   539  	}
   540  	if _, ok := pullRequestHandlers[name]; ok {
   541  		events = append(events, "pull_request")
   542  	}
   543  	if _, ok := pushEventHandlers[name]; ok {
   544  		events = append(events, "push")
   545  	}
   546  	if _, ok := reviewEventHandlers[name]; ok {
   547  		events = append(events, "pull_request_review")
   548  	}
   549  	if _, ok := reviewCommentEventHandlers[name]; ok {
   550  		events = append(events, "pull_request_review_comment")
   551  	}
   552  	if _, ok := statusEventHandlers[name]; ok {
   553  		events = append(events, "status")
   554  	}
   555  	if _, ok := genericCommentHandlers[name]; ok {
   556  		events = append(events, "GenericCommentEvent (any event for user text)")
   557  	}
   558  	return events
   559  }
   560  
   561  var configMapSizeGauges = prometheus.NewGaugeVec(prometheus.GaugeOpts{
   562  	Name: "prow_configmap_size_bytes",
   563  	Help: "Size of data fields in ConfigMaps updated automatically by Prow in bytes.",
   564  }, []string{"name", "namespace"})
   565  
   566  func init() {
   567  	prometheus.MustRegister(configMapSizeGauges)
   568  }
   569  
   570  // Metrics is a set of metrics that are gathered by plugins.
   571  // It is up the consumers of these metrics to ensure that they
   572  // update the values in a thread-safe manner.
   573  type Metrics struct {
   574  	ConfigMapGauges *prometheus.GaugeVec
   575  }
   576  
   577  // NewMetrics returns a reference to the metrics plugins manage
   578  func NewMetrics() *Metrics {
   579  	return &Metrics{
   580  		ConfigMapGauges: configMapSizeGauges,
   581  	}
   582  }
   583  
   584  type githubV4OrgAddingWrapper struct {
   585  	org string
   586  	github.Client
   587  }
   588  
   589  func (c *githubV4OrgAddingWrapper) Query(ctx context.Context, q interface{}, args map[string]interface{}) error {
   590  	return c.QueryWithGitHubAppsSupport(ctx, q, args, c.org)
   591  }