Skip to content

Commit

Permalink
Use GitHub app for the PR/MR workflow (#199)
Browse files Browse the repository at this point in the history
* feat(github): replace github pat by a github app

Remove the GitHub PAT authentication scheme by a proper Github app

* docs(github): add github app documentation

Add the required documentation to configure the Github App for the PR/MR workflow

* docs(github): add badge information

Add link to configure a badge for the Github App

* test(github): fix test

Add missing variables in test data

* fix(github): add missing variables

* feat(revert): add apitoken config

* feat(revert): add github token support

* docs(github): add api token doc
  • Loading branch information
marcantoinegodde authored Jan 10, 2024
1 parent 90c46a6 commit 4eb97de
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 23 deletions.
6 changes: 6 additions & 0 deletions deploy/charts/burrito/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ config:
healthProbeBindAddress: ":8081"
kubernetesWebhookPort: 9443
githubConfig:
# -- Prefer override with the BURRITO_CONTROLLER_GITHUBCONFIG_APPID environment variable
appId: ""
# -- Prefer override with the BURRITO_CONTROLLER_GITHUBCONFIG_INSTALLATIONID environment variable
installationId: ""
# -- Prefer override with the BURRITO_CONTROLLER_GITHUBCONFIG_PRIVATEKEY environment variable
privateKey: ""
# -- Prefer override with the BURRITO_CONTROLLER_GITHUBCONFIG_APITOKEN environment variable
apiToken: ""
gitlabConfig:
Expand Down
Binary file added docs/assets/pr-mr-workflow/github_app_id.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
157 changes: 148 additions & 9 deletions docs/operator-manual/pr-mr-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
<p align="center"><img src="../../assets/design/pr-mr-workflow.excalidraw.png" width="1000px" /></p>

!!! info
In this documentation all references to pull requests can be change to merge requests for GitLab. However, the resulting Kubernetes object will still be named `TerraformPullRequest`.
In this documentation all references to pull requests can be change to merge requests for GitLab. However, the resulting Kubernetes object will still be named `TerraformPullRequest`.

## Components

### The server

!!! info
For more information about the server, see the [architectural overview](./architecture.md) documentation.
For more information about the server, see the [architectural overview](./architecture.md) documentation.

Upon receiving a Pull Request creation event, the server creates a `TerraformPullRequest` resource.

Expand All @@ -35,18 +35,157 @@ The status of a `TerraformPulLRequest` is defined using the [conditions standard
- `IsCommentUpToDate`. This condition is used to check if the controller needs to send a comment to a pull request. This is checked by comparing the last discovered commit and the last commit for which a comment was already sent.

!!! info
We use annotations to store information.
We use annotations to store information.

With those 3 conditions, we defined 3 states:

- `Idle`. This is the state of a pull request if nothing needs to be done.
- `DiscoveryNeeded`. This is the state of a pull request if the controller needs to check which layers are affected on the given pull request.
- `CommentNeeded`. This is the state of a pull request if the controller needs to send a comment to the git provider's API.
- `CommentNeeded`. This is the state of a pull request if the controller needs to send a comment to the git provider's API.

## Configuration

| Environment variable | Description |
| :----------------------------------------: | :-------------------------------------------: |
| `BURRITO_CONTROLLER_GITHUBCONFIG_APITOKEN` | the API token to send comment to GitHub's API |
| `BURRITO_CONTROLLER_GITLABCONFIG_APITOKEN` | the API token to send comment to GitLab's API |
| `BURRITO_CONTROLLER_GITLABCONFIG_URL` | the URL of the GitLab instance |
### GitHub with a dedicated GitHub App

#### Create the GitHub App

You can create and register GitHub Apps in your personal GitHub account or in any GitHub organization where you have administrative access.

Follow the instructions in the GitHub documentation on [Creating a GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app). Populate the settings as follows:

- **GitHub App Name**: Choose a name for your GitHub App. For example, something featuring `burrito`.
- **Homepage URL**: Enter https://padok-team.github.io/burrito.
- **Webhook**: Deselect Active. The app doesn't use this webhook events mechanism at the moment.
- **Permissions**: Configure the following **Repository Permissions**.
- **Metadata**: Select Read-only.
- **Pull requests**: Select Read & write. This is required to issue comments on pull requests.
- Where can this GitHub App be installed: Select **Any account**.

#### Creating a custom badge for your GitHub App

You can create a custom badge for your GitHub App to display on your GitHub repository. Follow the instructions in the GitHub documentation on [Creating a custom badge for your GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/creating-a-custom-badge-for-your-github-app).

We suggest using the following one:

<p align="center"><img src="../../assets/icon/burrito.png" width="200px" /></p>

#### Install the GitHub App

Follow the instructions in the GitHub documentation on [Installing your own GitHub App](https://docs.github.com/en/apps/using-github-apps/installing-your-own-github-app), and note the following:

- For Repository access, select **Only select repositories**, and then select the repos you want to connect with Burrito.

#### Get the Installation ID and App ID

You need the **Installation ID** and **App ID** to configure Burrito.

1. Get the **Installation ID** from the URL of the installed app, such as:

<p align="center"><img src="../../assets/pr-mr-workflow/github_installation_id.png" /></p>

2. Get the **App ID** from the app's General tab.

<p align="center"><img src="../../assets/pr-mr-workflow/github_app_id.png" /></p>

#### Generate a private key

You need a private key for your GitHub app to configure Burrito.

- Follow the instructions in the GitHub documentation for [generating private keys for GitHub Apps](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps#generating-private-keys)

- Save the private key file to your local machine. GitHub only stores the public portion of the key.

#### Configure Burrito

Add the following environment variables to your Burrito controller deployment:

- `BURRITO_CONTROLLER_GITHUBCONFIG_APPID`: The App ID of your GitHub app.
- `BURRITO_CONTROLLER_GITHUBCONFIG_INSTALLATIONID`: The Installation ID of your GitHub app.
- `BURRITO_CONTROLLER_GITHUBCONFIG_PRIVATEKEY`: The private key of your GitHub app.

For example:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: burrito-github-config
namespace: burrito
type: Opaque
stringData:
BURRITO_CONTROLLER_GITHUBCONFIG_APPID: "123456"
BURRITO_CONTROLLER_GITHUBCONFIG_INSTALLATIONID: "12345678"
BURRITO_CONTROLLER_GITHUBCONFIG_PRIVATEKEY: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
```
### GitHub with a personal access token
#### Generate a personal access token
You need a personal access token to configure Burrito. You can generate a personal access token in your GitHub account.
Follow the instructions in the GitHub documentation for [creating a personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token):
- It should be a **fine-grained token**.
- **Permissions**: Configure the following **Repository Permissions**.
- **Metadata**: Select Read-only.
- **Pull requests**: Select Read & write. This is required to issue comments on pull requests.
- Under **Repository access**, select which repositories you want the token to access.
#### Configure Burrito
Add the following environment variables to your Burrito controller deployment:
- `BURRITO_CONTROLLER_GITHUBCONFIG_APITOKEN`: The personal access token of your GitHub app.

For example:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: burrito-github-config
namespace: burrito
type: Opaque
stringData:
BURRITO_CONTROLLER_GITHUBCONFIG_APITOKEN: github_pat_123456
```

### GitLab

#### Generate a private token

You need a private token for your GitLab app to configure Burrito. You can generate a private token in your GitLab account. Follow the instructions in the GitLab documentation for [generating a private token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#creating-a-personal-access-token).

#### Configure Burrito

Add the following environment variables to your Burrito controller deployment:

- `BURRITO_CONTROLLER_GITLABCONFIG_APITOKEN`: The private token of your GitLab app.
- `BURRITO_CONTROLLER_GITLABCONFIG_URL`: The URL of your GitLab instance.

For example:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: burrito-gitlab-config
namespace: burrito
type: Opaque
stringData:
BURRITO_CONTROLLER_GITLABCONFIG_APITOKEN: "123456"
BURRITO_CONTROLLER_GITLABCONFIG_URL: "https://gitlab.example.com"
```

| Environment variable | Description |
| :----------------------------------------------: | :--------------------------------------------------------: |
| `BURRITO_CONTROLLER_GITHUBCONFIG_APPID` | the GtiHub App ID to send comment to GitHub's API |
| `BURRITO_CONTROLLER_GITHUBCONFIG_INSTALLATIONID` | the GitHub Installation ID to send comment to GitHub's API |
| `BURRITO_CONTROLLER_GITHUBCONFIG_PRIVATEKEY` | the GitHub App private key to send comment to GitHub's API |
| `BURRITO_CONTROLLER_GITHUBCONFIG_APITOKEN` | the API token to send comment to GitHub's API |
| `BURRITO_CONTROLLER_GITLABCONFIG_APITOKEN` | the API token to send comment to GitLab's API |
| `BURRITO_CONTROLLER_GITLABCONFIG_URL` | the URL of the GitLab instance |
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/padok-team/burrito
go 1.19

require (
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/terraform-json v0.17.1
github.com/onsi/ginkgo/v2 v2.13.1
Expand All @@ -28,7 +29,9 @@ require (
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-github/v56 v56.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bombsimon/logrusr/v4 v4.0.0 h1:Pm0InGphX0wMhPqC02t31onlq9OVyJ98eP/Vh63t1Oo=
github.com/bombsimon/logrusr/v4 v4.0.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8=
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0 h1:yUmoVv70H3J4UOqxqsee39+KlXxNEDfTbAp8c/qULKk=
github.com/bradleyfalzon/ghinstallation/v2 v2.8.0/go.mod h1:fmPmvCiBWhJla3zDv9ZTQSZc8AbwyRnGW1yg5ep1Pcs=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
Expand Down Expand Up @@ -133,6 +135,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -184,6 +188,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk=
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
github.com/google/go-github/v56 v56.0.0 h1:TysL7dMa/r7wsQi44BjqlwaHvwlFlqkK8CtBWCX3gb4=
github.com/google/go-github/v56 v56.0.0/go.mod h1:D8cdcX98YWJvi7TLo7zM4/h8ZTx6u6fwGEkCdisopo0=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
5 changes: 4 additions & 1 deletion internal/burrito/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ type ControllerConfig struct {
}

type GithubConfig struct {
APIToken string `mapstructure:"apiToken"`
AppId int64 `mapstructure:"appId"`
InstallationId int64 `mapstructure:"installationId"`
PrivateKey string `mapstructure:"privateKey"`
APIToken string `mapstructure:"apiToken"`
}

type GitlabConfig struct {
Expand Down
13 changes: 11 additions & 2 deletions internal/burrito/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ func TestConfig_FromYamlFile(t *testing.T) {
HealthProbeBindAddress: ":8081",
KubernetesWebhookPort: 9443,
GithubConfig: config.GithubConfig{
APIToken: "github-token",
AppId: 123456,
InstallationId: 12345678,
PrivateKey: "private-key",
APIToken: "github-token",
},
GitlabConfig: config.GitlabConfig{
APIToken: "gitlab-token",
Expand Down Expand Up @@ -146,6 +149,9 @@ func TestConfig_EnvVarOverrides(t *testing.T) {
setEnvVar(t, "BURRITO_CONTROLLER_TIMERS_FAILUREGRACEPERIOD", "1m", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_TERRAFORMMAXRETRIES", "32", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_LEADERELECTION_ID", "other-leader-id", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_GITHUBCONFIG_APPID", "123456", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_GITHUBCONFIG_INSTALLATIONID", "12345678", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_GITHUBCONFIG_PRIVATEKEY", "private-key", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_GITHUBCONFIG_APITOKEN", "pr-github-token", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_GITLABCONFIG_APITOKEN", "mr-gitlab-token", &envVarList)
setEnvVar(t, "BURRITO_CONTROLLER_GITLABCONFIG_URL", "https://gitlab.com", &envVarList)
Expand Down Expand Up @@ -204,7 +210,10 @@ func TestConfig_EnvVarOverrides(t *testing.T) {
HealthProbeBindAddress: ":8081",
KubernetesWebhookPort: 9443,
GithubConfig: config.GithubConfig{
APIToken: "pr-github-token",
AppId: 123456,
InstallationId: 12345678,
PrivateKey: "private-key",
APIToken: "pr-github-token",
},
GitlabConfig: config.GitlabConfig{
APIToken: "mr-gitlab-token",
Expand Down
3 changes: 3 additions & 0 deletions internal/burrito/config/testdata/test-config-1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ controller:
healthProbeBindAddress: ":8081"
kubernetesWebhookPort: 9443
githubConfig:
appId: 123456
installationId: 12345678
privateKey: "private-key"
apiToken: "github-token"
gitlabConfig:
apiToken: "gitlab-token"
Expand Down
39 changes: 28 additions & 11 deletions internal/controllers/terraformpullrequest/github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package github
import (
"context"
"errors"
"net/http"
"strconv"
"strings"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v50/github"
configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1"
"github.com/padok-team/burrito/internal/annotations"
Expand All @@ -20,22 +22,37 @@ type Github struct {
*github.Client
}

func (g *Github) IsConfigPresent(c *config.Config) bool {
return c.Controller.GithubConfig.APIToken != ""
func (g *Github) IsAppConfigPresent(c *config.Config) bool {
return c.Controller.GithubConfig.AppId != 0 && c.Controller.GithubConfig.InstallationId != 0 && len(c.Controller.GithubConfig.PrivateKey) != 0
}

func (g *Github) IsAPITokenConfigPresent(c *config.Config) bool {
return len(c.Controller.GithubConfig.APIToken) != 0
}

func (g *Github) Init(c *config.Config) error {
ctx := context.Background()
if !g.IsConfigPresent(c) {
if g.IsAppConfigPresent(c) {
itr, err := ghinstallation.New(http.DefaultTransport, c.Controller.GithubConfig.AppId, c.Controller.GithubConfig.InstallationId, []byte(c.Controller.GithubConfig.PrivateKey))

if err != nil {
return errors.New("error while creating github installation client: " + err.Error())
}

g.Client = github.NewClient(&http.Client{Transport: itr})
return nil
} else if g.IsAPITokenConfigPresent(c) {
ctx := context.Background()

ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: c.Controller.GithubConfig.APIToken},
)
tc := oauth2.NewClient(ctx, ts)

g.Client = github.NewClient(tc)
return nil
} else {
return errors.New("github config is not present")
}
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: c.Controller.GithubConfig.APIToken},
)
tc := oauth2.NewClient(ctx, ts)

g.Client = github.NewClient(tc)
return nil
}

func (g *Github) IsFromProvider(pr *configv1alpha1.TerraformPullRequest) bool {
Expand Down

0 comments on commit 4eb97de

Please sign in to comment.