Skip to content

deployed-labeler: improve error handling, add GET endpoint #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions plugins/deployed-labeler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Accepts `POST` requests, while requiring to parameters:
- `commit`: Commit that has just been deployed to production.
- `team`: Which team just deployed to production.

`deployed-labeler` will look for the last 100 commits of the repository's default branch, alongside their associated Pull Requests and labels.
`deployed-labeler` will look for the last 500 commits of the repository's default branch, alongside their associated Pull Requests and labels.

![image](https://user-images.githubusercontent.com/24193764/139254510-9f8ed8e1-e9ac-4177-b447-49932b804edd.png)

Expand Down Expand Up @@ -51,9 +51,15 @@ docker run \

And `curl` the endpoint

```sh
curl -XPOST "http://localhost:8080/deployed?commit=01f4897c5323433e7831ca948f7d340c3c762885&team=webapp"
```
- Check for unlabeled pull requests:
```sh
curl "http://localhost:8080/deployed?commit=01f4897c5323433e7831ca948f7d340c3c762885&team=webapp"
```

- Upload pull request labels:
```sh
curl -XPOST "http://localhost:8080/deployed?commit=01f4897c5323433e7831ca948f7d340c3c762885&team=webapp"
```

## Deploying

Expand Down
127 changes: 106 additions & 21 deletions plugins/deployed-labeler/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type options struct {
dryRun bool
github prowflagutil.GitHubOptions
hmacSecret string
logLevel string
}

func newOptions() *options {
Expand All @@ -36,6 +37,7 @@ func newOptions() *options {
fs.IntVar(&o.port, "port", 8080, "Port to listen to.")
fs.BoolVar(&o.dryRun, "dry-run", false, "Dry run for testing (uses API tokens but does not mutate).")
fs.StringVar(&o.hmacSecret, "hmac", "/etc/webhook/hmac", "Path to the file containing the GitHub HMAC secret.")
fs.StringVar(&o.logLevel, "log-level", "debug", "Application log level")

for _, group := range []flagutil.OptionGroup{&o.github} {
group.AddFlags(fs)
Expand All @@ -56,7 +58,7 @@ func main() {
o := newOptions()

logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetLevel(logrus.DebugLevel)
logrus.ParseLevel(o.logLevel)
log := logrus.StandardLogger().WithField("plugin", pluginName)

secretAgent := &secret.Agent{}
Expand Down Expand Up @@ -90,35 +92,75 @@ func main() {
}

func (s *server) markDeployedPR(w http.ResponseWriter, req *http.Request) {
var commitSHA string
var team string
for k, v := range req.URL.Query() {
switch k {
case "commit":
commitSHA = v[0]
case "team":
team = v[0]
default:
s.log.Warnf("Unrecognized parameter received: %s", k)
}
}

if team == "" || commitSHA == "" {
s.log.WithFields(
logrus.Fields{
"team": team,
"commit": commitSHA,
},
).Error("team and commit are required parameters")

w.WriteHeader(http.StatusPreconditionFailed)
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.Encode(map[string]string{
"error": "team and commit are required parameters",
})
return
}
var err error
switch req.Method {
case "POST":
var commitSHA string
var team string
for k, v := range req.URL.Query() {
switch k {
case "commit":
commitSHA = v[0]
case "team":
team = v[0]
default:
s.log.Warnf("Unrecognized parameter received: %s", k)
case "GET":
var msg struct {
PRs struct {
Labeled []string `json:"labeled"`
Unlabeled []string `json:"unlabeled"`
}
Error string `json:"error"`
}
if team == "" || commitSHA == "" {
w.WriteHeader(http.StatusPreconditionFailed)
w.Write([]byte(http.StatusText(http.StatusPreconditionFailed)))
s.log.Errorf("team and commit are required parameters. Team: %v, commit: %v", team, commitSHA)
break

msg.PRs.Labeled, msg.PRs.Unlabeled, err = s.handleGetUnmarkedPRs(req.Context(), commitSHA, team)
if err != nil {
msg.Error = err.Error()
w.WriteHeader(http.StatusUnprocessableEntity)
}

enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.Encode(msg)

case "POST":
var errs []error
var msg struct {
DeployedPRs struct {
Team []string `json:"team"`
All []string `json:"all"`
} `json:"deployedPRs"`
Errors []error `json:"errors"`
Errors []string `json:"errors"`
}
msg.DeployedPRs.Team, msg.DeployedPRs.All, errs = s.handleMarkDeployedPRs(req.Context(), commitSHA, team)

msg.Errors = make([]string, 0, len(errs))
for _, err := range errs {
msg.Errors = append(msg.Errors, err.Error())
}

if len(msg.Errors) > 0 {
w.WriteHeader(http.StatusUnprocessableEntity)
}
msg.DeployedPRs.Team, msg.DeployedPRs.All, msg.Errors = s.handleMarkDeployedPRs(req.Context(), commitSHA, team)

enc := json.NewEncoder(w)
enc.SetIndent("", " ")
Expand All @@ -140,6 +182,17 @@ func (s *server) handleMarkDeployedPRs(ctx context.Context, commitSHA, team stri
return s.updatePullRequests(prs, team)
}

func (s *server) handleGetUnmarkedPRs(ctx context.Context, commitSHA, team string) (labeledPRs, unlabeledPRs []string, err error) {
prs, err := s.getMergedPRs(ctx, commitSHA)
if err != nil {
return nil, nil, err
}

labeledPRs, unlabeledPRs = s.findTeamPullRequests(prs, team)

return labeledPRs, unlabeledPRs, nil
}

const (
org = "gitpod-io"
repo = "gitpod"
Expand Down Expand Up @@ -193,6 +246,26 @@ func (s *server) updatePullRequests(prs []pullRequest, team string) (teamDeploye
return
}

// Find labeled and unlabeled pull requests for the given team.
func (s *server) findTeamPullRequests(prs []pullRequest, team string) (labeled, unlabeled []string) {
for _, pr := range prs {

lblTeam := teamLabel(team)
if _, belongs := pr.Labels[lblTeam]; !belongs {
s.log.Infof("PR %v does not belong to %v, skipping it", pr.Number, team)
continue
}

teamDeployedLabel := deployedLabel(team)
if _, hasLabel := pr.Labels[teamDeployedLabel]; !hasLabel {
unlabeled = append(unlabeled, pr.URL)
} else {
labeled = append(labeled, pr.URL)
}
}
return
}

func deployedLabel(team string) string { return fmt.Sprintf("%s: %s", labelDeployed, team) }
func teamLabel(team string) string { return labelPrefixTeam + team }

Expand All @@ -212,8 +285,12 @@ func (s *server) getMergedPRs(ctx context.Context, commitSHA string) ([]pullRequ
var commits []commitNodes

// we get 100 commits per page
// 3x100 = 300 in total
for i := 0; i < 3; i++ {
// 5x100 = 500 in total
//
// Note that this value is sensitive to the commit rate of the given repo and the interval at which teams deploy;
// if more than 500 commits are merged within a week and a given team only deploys on a weekly basis then some commits
// might not be labeled.
for i := 0; i < 5; i++ {
err := s.gh.Query(ctx, &q, variables)
if err != nil {
s.log.WithError(err).Error("Error running query.")
Expand All @@ -239,6 +316,14 @@ func (s *server) getMergedPRs(ctx context.Context, commitSHA string) ([]pullRequ
pr.Labels[string(lbl.Name)] = struct{}{}
}
res = append(res, pr)

s.log.WithFields(
logrus.Fields{
"Number": pr.Number,
"URL": pr.URL,
"Labels": pr.Labels,
},
).Info("Added pull request for commit %s", c)
}
}

Expand Down