Skip to content

Commit 986bf28

Browse files
committed
Add bulletproof automerger
commit-id:c2d84b0c
1 parent 482e8a3 commit 986bf28

15 files changed

+1736
-32
lines changed

.editorconfig

+4
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ trim_trailing_whitespace = true
1313
[*.md]
1414
max_line_length = 0
1515
trim_trailing_whitespace = false
16+
17+
[{go.mod,go.sum,*.go}]
18+
indent_style = tab
19+
indent_size = 4

.github/PULL_REQUEST_TEMPLATE.md

+25
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,28 @@ At minimum each entry should have
1919
- Logo: (logo should be uploaded under assets/mainnet/<mint address>/\*.<png/svg>)
2020
- Link to the official homepage of token:
2121
- Coingecko ID if available (https://www.coingecko.com/api/documentations/v3#/coins/get_coins__id_):
22+
23+
## Auto merge requirements
24+
25+
Your pull request will be automatically merged if the following conditions are met:
26+
27+
- Your pull request **only adds new tokens** to the list. Any modification to existing
28+
tokens will require manual review to prevent unwanted modifications.
29+
30+
- Your pull request does **not touch unrelated code**. In particular, reformatting changes to unrelated
31+
code will cause the auto merge to reject your PR.
32+
33+
- Any **asset files added correspond to the token address** you are adding. Asset files
34+
must be PNG, JPG or SVG files.
35+
36+
- Your change is **valid JSON** and **conforms to the schema**. If your change failed validation,
37+
read the error message carefully and update your PR accordingly.
38+
39+
- No other tokens shares the **same name, symbol or address**.
40+
41+
For example, this change would be rejected due to unrelated changes:
42+
43+
<img src=https://i.imgur.com/qB9RNO4.png width=600px>
44+
45+
The bot runs **every 30 minutes** and bulk-merges all open pull requests to prevent conflicts.
46+
This means that you need to wait up to 30 minutes for your pull request to be merged or reprocessed.

.github/workflows/automerge.yml

+8-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
name: Automerge
2-
31
on:
42
pull_request_target:
53
push:
@@ -18,30 +16,11 @@ jobs:
1816
auto-merge:
1917
runs-on: ubuntu-latest
2018
steps:
21-
- name: Checkout
22-
uses: actions/checkout@v2
23-
with:
24-
ref: ${{ github.event.pull_request.head.sha }}
25-
26-
- uses: actions/setup-node@v1
27-
with:
28-
node-version: 12
29-
- run: yarn
30-
- run: yarn test
31-
32-
- name: Get changes
33-
id: git_diff
34-
uses: swmd/[email protected]
35-
with:
36-
github-token: ${{ secrets.GITHUB_TOKEN }}
37-
38-
- uses: actions-ecosystem/action-add-labels@v1
39-
if: ${{ steps.git_diff.outputs.valid == 'VALID' }}
40-
with:
41-
labels: automerge
42-
43-
# - name: Merge pull request
44-
# if: ${{ steps.git_diff.outputs.valid == 'VALID' }}
45-
# uses: 'pascalgn/[email protected]'
46-
# env:
47-
# GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
19+
- uses: LouisBrunner/[email protected]
20+
if: always()
21+
with:
22+
token: ${{ secrets.GITHUB_TOKEN }}
23+
name: Automerge / auto-merge (pull_request_target)
24+
conclusion: neutral
25+
output: |
26+
{"summary": "Disregard - please see new automerge" }

.github/workflows/automerge_new.yml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: AutomergeCron
2+
3+
on:
4+
workflow_dispatch:
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v2
10+
11+
- uses: actions/setup-go@v2
12+
with:
13+
go-version: '^1.17.3'
14+
15+
# Required for automerge to clone from the local working copy
16+
- run: git checkout -b work
17+
18+
- run: cd automerge && go build -o ~/automerge github.com/solana-labs/token-list/automerge
19+
20+
- run: ~/automerge -v=1
21+
env:
22+
GITHUB_APP_PEM: "${{ secrets.GITHUB_APP_PEM }}"
23+
24+
- run: git push -f origin automerge-pending:automerge-pending

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ jobs:
1818
- run: yarn
1919
- run: yarn test
2020
- run: go install cuelang.org/go/cmd/[email protected]
21-
- run: PATH=$PATH:~/go/bin cd src/tokens && ./validate.sh
21+
- run: PATH=$PATH:~/go/bin ./validate.sh

automerge/auth/auth.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"encoding/pem"
8+
"errors"
9+
"fmt"
10+
"github.com/dgrijalva/jwt-go"
11+
"github.com/google/go-github/v40/github"
12+
"golang.org/x/oauth2"
13+
"k8s.io/klog/v2"
14+
"time"
15+
)
16+
17+
var ErrInvalidKey = errors.New("invalid key")
18+
19+
// signJWTFromPEM returns a signed JWT from a PEM-encoded private key.
20+
func signJWTFromPEM(key []byte, appId int64) (string, error) {
21+
// decode PEM
22+
block, _ := pem.Decode(key)
23+
if block == nil {
24+
return "", ErrInvalidKey
25+
}
26+
27+
// parse key
28+
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
29+
if err != nil {
30+
return "", err
31+
}
32+
33+
// sign
34+
return signJWT(privateKey, appId)
35+
}
36+
37+
func signJWT(privateKey *rsa.PrivateKey, appId int64) (string, error) {
38+
// sign
39+
token := jwt.NewWithClaims(jwt.SigningMethodRS256,
40+
jwt.MapClaims{
41+
"exp": time.Now().Add(10 * time.Minute).Unix(),
42+
"iat": time.Now().Unix(),
43+
"iss": fmt.Sprintf("%d", appId),
44+
})
45+
46+
tokenString, err := token.SignedString(privateKey)
47+
if err != nil {
48+
return "", err
49+
}
50+
51+
return tokenString, nil
52+
}
53+
54+
func GetInstallationToken(privateKey []byte, appId int64) (string, error) {
55+
token, err := signJWTFromPEM(privateKey, appId)
56+
if err != nil {
57+
klog.Exitf("failed to sign JWT: %v", err)
58+
}
59+
60+
// get installation access token for app
61+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
62+
defer cancel()
63+
ts := oauth2.StaticTokenSource(
64+
&oauth2.Token{AccessToken: token},
65+
)
66+
tc := oauth2.NewClient(ctx, ts)
67+
client := github.NewClient(tc)
68+
is, _, err := client.Apps.ListInstallations(ctx, nil)
69+
if err != nil {
70+
klog.Exitf("failed to list installations: %v", err)
71+
}
72+
73+
for _, i := range is {
74+
if i.GetAppID() == appId {
75+
klog.Infof("installation id: %v", i.GetID())
76+
klog.Infof("installed on %s: %s", i.GetTargetType(), i.GetAccount().GetLogin())
77+
}
78+
79+
// Get an installation token
80+
it, _, err := client.Apps.CreateInstallationToken(ctx, i.GetID(), nil)
81+
if err != nil {
82+
klog.Exitf("failed to create installation token: %v", err)
83+
}
84+
85+
return it.GetToken(), nil
86+
}
87+
88+
return "", errors.New("no installation found")
89+
}

0 commit comments

Comments
 (0)