Skip to content

Commit 3205a19

Browse files
committed
feat: Added GitHub app authentication
1 parent 239f9e2 commit 3205a19

File tree

6 files changed

+158
-4
lines changed

6 files changed

+158
-4
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
.sequence
2-
2+
*.pem
33
.idea/
44
.vscode/

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ This exporter is setup to take input from environment variables. All variables a
1414
the format "user1, user2".
1515
* `GITHUB_TOKEN` If supplied, enables the user to supply a github authentication token that allows the API to be queried more often. Optional, but recommended.
1616
* `GITHUB_TOKEN_FILE` If supplied _instead of_ `GITHUB_TOKEN`, enables the user to supply a path to a file containing a github authentication token that allows the API to be queried more often. Optional, but recommended.
17+
* `GITHUB_APP` If true , authenticates ass GitHub app to the API.
18+
* `GITHUB_APP_ID` The APP ID of the GitHub App.
19+
* `GITHUB_APP_INSTALLATION_ID` The INSTALLATION ID of the GitHub App.
20+
* `GITHUB_APP_KEY_PATH` The path to the github private key.
21+
* `GITHUB_RATE_LIMIT` The RATE LIMIT that suppose to be for github app (default is 15,000). If the exporter sees the value is below this variable it generating new token for the app.
1722
* `API_URL` Github API URL, shouldn't need to change this. Defaults to `https://api.github.com`
1823
* `LISTEN_PORT` The port you wish to run the container on, the Dockerfile defaults this to `9171`
1924
* `METRICS_PATH` the metrics URL path you wish to use, defaults to `/metrics`
@@ -27,10 +32,14 @@ Run manually from Docker Hub:
2732
docker run -d --restart=always -p 9171:9171 -e REPOS="infinityworks/ranch-eye, infinityworks/prom-conf" infinityworks/github-exporter
2833
```
2934

35+
Run manually from Docker Hub (With GitHub App):
36+
```
37+
docker run -d --restart=always -p 9171:9171 --read-only -v ./key.pem:/key.pem -e GITHUB_APP=true -e GITHUB_APP_ID= -e GITHUB_APP_INSTALLATION_ID= -e GITHUB_APP_KEY_PATH=/key.pem <IMAGE_NAME>
38+
```
39+
3040
Build a docker image:
3141
```
3242
docker build -t <image-name> .
33-
docker run -d --restart=always -p 9171:9171 -e REPOS="infinityworks/ranch-eye, infinityworks/prom-conf" <image-name>
3443
```
3544

3645
## Docker compose
@@ -47,6 +56,29 @@ github-exporter:
4756
environment:
4857
- REPOS=<REPOS you want to monitor>
4958
- GITHUB_TOKEN=<your github api token>
59+
```
60+
61+
## Docker compose (GitHub App)
62+
63+
```
64+
github-exporter-github-app:
65+
tty: true
66+
stdin_open: true
67+
expose:
68+
- 9171
69+
ports:
70+
- 9171:9171
71+
build: .
72+
environment:
73+
- LOG_LEVEL=debug
74+
- LISTEN_PORT=9171
75+
- GITHUB_APP=true
76+
- GITHUB_APP_ID=
77+
- GITHUB_APP_INSTALLATION_ID=
78+
- GITHUB_APP_KEY_PATH=/key.pem
79+
restart: unless-stopped
80+
volumes:
81+
- "./key.pem:/key.pem:ro"
5082
5183
```
5284

config/config.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package config
22

33
import (
4+
"context"
45
"io/ioutil"
6+
"net/http"
57
"net/url"
68
"path"
9+
"strconv"
710
"strings"
811

12+
"github.com/bradleyfalzon/ghinstallation"
913
log "github.com/sirupsen/logrus"
1014

1115
"os"
@@ -22,6 +26,7 @@ type Config struct {
2226
users []string
2327
apiToken string
2428
targetURLs []string
29+
gitHubApp bool
2530
}
2631

2732
// Init populates the Config struct based on environmental runtime configuration
@@ -39,6 +44,7 @@ func Init() Config {
3944
nil,
4045
"",
4146
nil,
47+
false,
4248
}
4349

4450
err := appConfig.SetAPIURL(cfg.GetEnv("API_URL", "https://api.github.com"))
@@ -57,6 +63,15 @@ func Init() Config {
5763
if users != "" {
5864
appConfig.SetUsers(strings.Split(users, ", "))
5965
}
66+
67+
gitHubApp := os.Getenv("GITHUB_APP")
68+
if gitHubApp == "true" {
69+
gitHubAppKeyPath := os.Getenv("GITHUB_APP_KEY_PATH")
70+
gitHubAppId, _ := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)
71+
gitHubAppInstalaltionId, _ := strconv.ParseInt(os.Getenv("GITHUB_APP_INSTALLATION_ID"), 10, 64)
72+
appConfig.SetAPITokenFromGitHubApp(gitHubAppId, gitHubAppInstalaltionId, gitHubAppKeyPath)
73+
}
74+
6075
tokenEnv := os.Getenv("GITHUB_TOKEN")
6176
tokenFile := os.Getenv("GITHUB_TOKEN_FILE")
6277
if tokenEnv != "" {
@@ -67,7 +82,6 @@ func Init() Config {
6782
log.Errorf("Error initialising Configuration, Error: %v", err)
6883
}
6984
}
70-
7185
return appConfig
7286
}
7387

@@ -126,6 +140,20 @@ func (c *Config) SetAPITokenFromFile(tokenFile string) error {
126140
return nil
127141
}
128142

143+
// SetAPITokenFromGitHubApp generating api token from github app configuration.
144+
func (c *Config) SetAPITokenFromGitHubApp(gitHubAppId int64, gitHubAppInstalaltionId int64, gitHubAppKeyPath string) error {
145+
itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, gitHubAppId, gitHubAppInstalaltionId, gitHubAppKeyPath)
146+
if err != nil {
147+
return err
148+
}
149+
strToken, err := itr.Token(context.Background())
150+
if err != nil {
151+
return err
152+
}
153+
c.SetAPIToken(strToken)
154+
return nil
155+
}
156+
129157
// Init populates the Config struct based on environmental runtime configuration
130158
// All URL's are added to the TargetURL's string array
131159
func (c *Config) setScrapeURLs() error {

exporter/prometheus.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package exporter
22

33
import (
4+
"context"
5+
"errors"
6+
"net/http"
7+
"os"
8+
"path"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/bradleyfalzon/ghinstallation"
413
"github.com/prometheus/client_golang/prometheus"
514
log "github.com/sirupsen/logrus"
615
)
@@ -17,9 +26,20 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
1726
// Collect function, called on by Prometheus Client library
1827
// This function is called when a scrape is peformed on the /metrics page
1928
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
20-
2129
data := []*Datum{}
2230
var err error
31+
32+
gitHubApp := os.Getenv("GITHUB_APP")
33+
if strings.ToLower(gitHubApp) == "true" {
34+
needReAuth, err := e.isTokenExpired()
35+
if err != nil {
36+
log.Errorf("Error checking token expiration status: %v", err)
37+
return
38+
}
39+
if needReAuth {
40+
e.reAuth()
41+
}
42+
}
2343
// Scrape the Data from Github
2444
if len(e.TargetURLs()) > 0 {
2545
data, err = e.gatherData()
@@ -46,3 +66,55 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
4666
log.Info("All Metrics successfully collected")
4767

4868
}
69+
70+
func (e *Exporter) isTokenExpired() (bool, error) {
71+
u := *e.APIURL()
72+
u.Path = path.Join(u.Path, "rate_limit")
73+
74+
resp, err := getHTTPResponse(u.String(), e.APIToken())
75+
76+
if err != nil {
77+
return false, err
78+
}
79+
defer resp.Body.Close()
80+
// Triggers if rate-limiting isn't enabled on private Github Enterprise installations
81+
if resp.StatusCode == 404 {
82+
return false, errors.New("404 Error")
83+
}
84+
85+
limit, err := strconv.ParseFloat(resp.Header.Get("X-RateLimit-Limit"), 64)
86+
87+
if err != nil {
88+
return false, err
89+
}
90+
91+
defaultLimit := os.Getenv("GITHUB_RATE_LIMIT")
92+
if len(defaultLimit) == 0 {
93+
defaultLimit = "15000"
94+
}
95+
defaultLimitInt, err := strconv.ParseInt(defaultLimit, 10, 64)
96+
if err != nil {
97+
return false, err
98+
}
99+
if limit < float64(defaultLimitInt) {
100+
return true, nil
101+
}
102+
return false, nil
103+
104+
}
105+
106+
func (e *Exporter) reAuth() error {
107+
gitHubAppKeyPath := os.Getenv("GITHUB_APP_KEY_PATH")
108+
gitHubAppId, _ := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)
109+
gitHubAppInstalaltionId, _ := strconv.ParseInt(os.Getenv("GITHUB_APP_INSTALLATION_ID"), 10, 64)
110+
itr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, gitHubAppId, gitHubAppInstalaltionId, gitHubAppKeyPath)
111+
if err != nil {
112+
return err
113+
}
114+
strToken, err := itr.Token(context.Background())
115+
if err != nil {
116+
return err
117+
}
118+
e.Config.SetAPIToken(strToken)
119+
return nil
120+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/infinityworks/github-exporter
33
go 1.19
44

55
require (
6+
github.com/bradleyfalzon/ghinstallation v1.1.1
67
github.com/infinityworks/go-common v0.0.0-20170820165359-7f20a140fd37
78
github.com/prometheus/client_golang v1.14.0
89
github.com/sirupsen/logrus v1.9.0
@@ -14,14 +15,18 @@ require (
1415
github.com/beorn7/perks v1.0.1 // indirect
1516
github.com/cespare/xxhash/v2 v2.2.0 // indirect
1617
github.com/davecgh/go-spew v1.1.1 // indirect
18+
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
1719
github.com/golang/protobuf v1.5.2 // indirect
20+
github.com/google/go-github/v29 v29.0.2 // indirect
21+
github.com/google/go-querystring v1.0.0 // indirect
1822
github.com/kr/pretty v0.3.1 // indirect
1923
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
2024
github.com/pmezard/go-difflib v1.0.0 // indirect
2125
github.com/prometheus/client_model v0.3.0 // indirect
2226
github.com/prometheus/common v0.39.0 // indirect
2327
github.com/prometheus/procfs v0.9.0 // indirect
2428
github.com/stretchr/testify v1.8.0 // indirect
29+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
2530
golang.org/x/sys v0.4.0 // indirect
2631
google.golang.org/protobuf v1.28.1 // indirect
2732
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
22
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
3+
github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I=
4+
github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug=
35
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
46
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
57
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
68
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
79
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
810
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11+
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
12+
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
913
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
14+
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
1015
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
1116
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
1217
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
1318
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
19+
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
1420
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1521
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
22+
github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
23+
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
24+
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
25+
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
1626
github.com/infinityworks/go-common v0.0.0-20170820165359-7f20a140fd37 h1:Lm6kyC3JBiJQvJrus66He0E4viqDc/m5BdiFNSkIFfU=
1727
github.com/infinityworks/go-common v0.0.0-20170820165359-7f20a140fd37/go.mod h1:+OaHNKQvQ9oOCr+DgkF95PkiDx20fLHpzMp8SmRPQTg=
1828
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -47,11 +57,18 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
4757
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
4858
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
4959
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
60+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
61+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
62+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
63+
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
5064
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
65+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5166
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5267
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
5368
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
69+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
5470
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
71+
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
5572
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
5673
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
5774
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=

0 commit comments

Comments
 (0)