...

Source file src/edge-infra.dev/pkg/f8n/devinfra/github-client/client.go

Documentation: edge-infra.dev/pkg/f8n/devinfra/github-client

     1  package githubclient
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/google/go-github/v47/github"
    10  )
    11  
    12  type Client struct {
    13  	*github.Client
    14  	cfg   Config
    15  	owner string
    16  	repo  string
    17  }
    18  
    19  // just for testing remove when in review
    20  func (c *Client) ToString() string {
    21  	return fmt.Sprintf("org %s repo %s", c.owner, c.repo)
    22  }
    23  
    24  func (c *Client) Config() Config {
    25  	return c.cfg
    26  }
    27  
    28  // NewClient creates a new github.Client with the provided token
    29  func NewClient(cfg Config) (*Client, error) {
    30  	err := cfg.Validate()
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	if cfg.AccessToken != "" {
    36  		hc, err := cfg.HTTPClient()
    37  		if err != nil {
    38  			return nil, err
    39  		}
    40  		client := github.NewClient(hc)
    41  		owner := cfg.Owner()
    42  		repo := cfg.Repo()
    43  		return &Client{client, cfg, owner, repo}, nil
    44  	}
    45  
    46  	if cfg.InstallationID == 0 {
    47  		cfg.InstallationID, err = getInstallationID(cfg)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  	}
    52  	fmt.Printf("cfg %+v \n", cfg.InstallationID)
    53  
    54  	hc, err := cfg.HTTPClient()
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	client := github.NewClient(hc)
    60  	owner := cfg.Owner()
    61  	repo := cfg.Repo()
    62  	return &Client{client, cfg, owner, repo}, nil
    63  }
    64  
    65  // installationIDCache speeds up the github client, reduces API calls, and lives as a singleton for a few reasons:
    66  //   - since the InstallationID shouldn't change, the cache can live until Chariot restarts.
    67  //   - Github clients are created/destroyed often so an instance variable wouldn't perform well.
    68  //
    69  // Getting the InstallationID is called often enough that bypassing a 304 request is worth creating
    70  // a bespoke cache like this.
    71  var installationIDCache = struct {
    72  	sync.Mutex
    73  	c map[string]int64 // Map[RepositoryURL]BranchName
    74  }{
    75  	c: make(map[string]int64),
    76  }
    77  
    78  func setInstallationIDCache(repository string, id int64) {
    79  	installationIDCache.Lock()
    80  	installationIDCache.c[repository] = id
    81  	installationIDCache.Unlock()
    82  }
    83  
    84  func getInstallationIDCache(repository string) (id int64, found bool) {
    85  	installationIDCache.Lock()
    86  	id, found = installationIDCache.c[repository]
    87  	installationIDCache.Unlock()
    88  	return
    89  }
    90  
    91  // Finds the installation id using the `cfg` provided
    92  //
    93  // This function requires constructing an http client `cfg.HTTPClient()` that works with the Apps service.
    94  // The constructed client can find the InstallationID but can't be reused for other Github API services.
    95  // This function was created to encapsulate the reuse logic since it's not obvious why we create two http
    96  // clients when calling `NewClient`.
    97  //
    98  // This function also caches the InstallationID in memory since they shouldn't change.
    99  func getInstallationID(cfg Config) (int64, error) {
   100  	if cfg.InstallationID != 0 {
   101  		return 0, fmt.Errorf("InstallationID not zero. Got: %d", cfg.InstallationID)
   102  	}
   103  	// check the local cache
   104  	if cached, found := getInstallationIDCache(cfg.Repository); found {
   105  		return cached, nil
   106  	}
   107  	hc, err := cfg.HTTPClient()
   108  	if err != nil {
   109  		return 0, err
   110  	}
   111  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
   112  	defer cancel()
   113  
   114  	apps := github.NewClient(hc).Apps
   115  	installation, _, err := apps.FindRepositoryInstallation(ctx, cfg.Owner(), cfg.Repo())
   116  	if err != nil {
   117  		return 0, err
   118  	}
   119  	id := installation.GetID()
   120  	if id <= 0 {
   121  		return 0, fmt.Errorf("InstallationID less than or equal to 0 means installation does not exist for owner/repo. Got %d", id)
   122  	}
   123  	setInstallationIDCache(cfg.Repository, id)
   124  	return id, nil
   125  }
   126  

View as plain text