generated from broadinstitute/golang-project-template
-
Notifications
You must be signed in to change notification settings - Fork 1
/
github.go
145 lines (123 loc) · 4.45 KB
/
github.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package github
import (
"context"
"net/http"
"github.com/broadinstitute/thelma/internal/thelma/app/config"
"github.com/broadinstitute/thelma/internal/thelma/app/credentials"
"github.com/google/go-github/github"
vault "github.com/hashicorp/vault/api"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"golang.org/x/oauth2"
)
// config prefix for github client setup
const configPrefix = "github"
const credentialsKey = "github-repo-pat"
var (
errorVaultKeyNotExists = errors.New("unable to retrieve access token from vault: key does not exist")
errorBadTokenFormat = errors.New("unable to convert token from vault to string: bad format")
errorUnknownAuthMethod = errors.New("unsupported github auth method, options are local | vault")
)
type Client struct {
client *github.Client
}
// githubConfig contains configuration for initializing a github api client
type githubConfig struct {
// AccessToken is used to reader a github Personal Access ReturnString out of an environment variable,
// otherwise one will be pulled from vault
Auth struct {
Type string `default:"local"`
Vault struct {
Path string `default:"secret/suitable/github/broadbot/tokens/ci-automation"`
Key string `default:"token"`
}
}
}
// New creates a new instantiation of a github API client
func New(options ...func(*Client) error) (*Client, error) {
log.Debug().Msg("initializing github client")
githubClient := &Client{}
for _, option := range options {
if err := option(githubClient); err != nil {
return nil, err
}
}
return githubClient, nil
}
// WithClient will create a github API client using the provided API client
func WithClient(client *http.Client) func(*Client) error {
return func(c *Client) error {
c.client = github.NewClient(client)
return nil
}
}
// WithDefaults will use Thelma's configuration and credential store to construct an http.Client with sane defaults that will be used in a github api client
func WithDefaults(config config.Config, creds credentials.Credentials, vaultClientFactory func() (*vault.Client, error)) func(*Client) error {
return func(c *Client) error {
var cfg githubConfig
if err := config.Unmarshal(configPrefix, &cfg); err != nil {
return err
}
var tokenProvider credentials.TokenProvider
if cfg.Auth.Type == "local" {
log.Debug().Msg("using local github pat token provider")
tokenProvider = buildLocalGithubTokenProvider(creds)
} else if cfg.Auth.Type == "vault" {
log.Debug().Msg("using vault github pat token provider")
vault, err := vaultClientFactory()
if err != nil {
return err
}
tokenProvider = buildVaultGithubTokenProvider(creds, vault, cfg.Auth.Vault.Path, cfg.Auth.Vault.Key)
} else {
return errorUnknownAuthMethod
}
token, err := tokenProvider.Get()
if err != nil {
return err
}
ctx := context.Background()
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: string(token)})
client := oauth2.NewClient(ctx, tokenSource)
c.client = github.NewClient(client)
return nil
}
}
func buildLocalGithubTokenProvider(creds credentials.Credentials) credentials.TokenProvider {
return creds.GetTokenProvider(credentialsKey, func(options *credentials.TokenOptions) {
options.PromptEnabled = true
options.PromptMessage = `
A Github Personal Access ReturnString is required in order to interact with the Github API.
You can generate a new PAT at https://github.com/settings/tokens (select ONLY the read:org scope).
Enter Personal Access ReturnString: `
})
}
func buildVaultGithubTokenProvider(creds credentials.Credentials, vault *vault.Client, path, key string) credentials.TokenProvider {
return creds.GetTokenProvider(credentialsKey, func(options *credentials.TokenOptions) {
options.IssueFn = func() ([]byte, error) {
accessTokenSecret, err := vault.Logical().Read(path)
if err != nil {
return nil, err
}
tokenI, exists := accessTokenSecret.Data[key]
if !exists {
return nil, errorVaultKeyNotExists
}
// cast interface{} to string
token, isByteSlice := tokenI.(string)
if !isByteSlice {
return nil, errorBadTokenFormat
}
return []byte(token), nil
}
})
}
// GetCallingUser looks up the authenticated identity of the user that will be issuing github API commands
func (c *Client) GetCallingUser(ctx context.Context) (string, error) {
// passing "" to this method will turn user info for the authenticated user
caller, _, err := c.client.Users.Get(ctx, "")
if err != nil {
return "", err
}
return caller.GetLogin(), nil
}