Skip to content

Commit 8f6bbbb

Browse files
authored
Merge pull request #155 from grafana/jh/backport-with-inputs
Make backport action accept PR as input instead of using event info
2 parents 5a34130 + 95d01a6 commit 8f6bbbb

File tree

9 files changed

+201
-124
lines changed

9 files changed

+201
-124
lines changed

backport/action.yml

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,37 @@ inputs:
1313
path:
1414
required: false
1515
default: "."
16+
pr_label:
17+
description: If specified, the backport will only be created for this label
18+
required: false
19+
pr_number:
20+
description: The number of the PR to backport
21+
required: false
22+
repo_owner:
23+
description: The owner of the repository the PR is in
24+
required: false
25+
repo_name:
26+
description: The name of the repository the PR is in
27+
required: false
28+
1629
runs:
1730
using: composite
1831
steps:
19-
- shell: bash
20-
env:
21-
GITHUB_TOKEN: ${{inputs.token}}
22-
RELEASE_TAG: ${{inputs.binary_release_tag}}
23-
INPUT_LABELS_TO_ADD: ${{inputs.labels_to_add}}
24-
PR: ${{inputs.pr}}
25-
DIR: ${{ inputs.path }}
26-
run: |
27-
set -e
28-
# Download the action from the store
29-
curl --fail -L -o /tmp/backport https://github.com/grafana/grafana-github-actions-go/releases/download/${RELEASE_TAG}/backport
30-
chmod +x /tmp/backport
31-
cd $DIR
32-
# Execute action
33-
/tmp/backport ${PR}
32+
- shell: bash
33+
env:
34+
GITHUB_TOKEN: ${{inputs.token}}
35+
RELEASE_TAG: ${{inputs.binary_release_tag}}
36+
INPUT_LABELS_TO_ADD: ${{inputs.labels_to_add}}
37+
DIR: ${{ inputs.path }}
38+
PR_LABEL: ${{ inputs.pr_label }}
39+
PR_NUMBER: ${{ inputs.pr_number }}
40+
REPO_OWNER: ${{ inputs.repo_owner }}
41+
REPO_NAME: ${{ inputs.repo_name }}
42+
run: |
43+
set -e
44+
# Download the action from the store
45+
curl --fail -L -o /tmp/backport https://github.com/grafana/grafana-github-actions-go/releases/download/${RELEASE_TAG}/backport
46+
chmod +x /tmp/backport
47+
cd $DIR
48+
# Execute action
49+
/tmp/backport

backport/backport.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7-
"regexp"
87
"strings"
98
"time"
109

1110
"github.com/google/go-github/v50/github"
1211
"github.com/grafana/grafana-github-actions-go/pkg/ghutil"
1312
)
1413

15-
var semverRegex = regexp.MustCompile(`^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>x|0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
16-
1714
type BackportOpts struct {
1815
// PullRequestNumber is the integer ID of the pull request being backported
1916
PullRequestNumber int
@@ -164,9 +161,7 @@ func backport(ctx context.Context, log *slog.Logger, client BackportClient, issu
164161
func Backport(ctx context.Context, log *slog.Logger, backportClient BackportClient, commentClient CommentClient, issueClient IssueClient, execClient CommandRunner, opts BackportOpts) (*github.PullRequest, error) {
165162
// Remove any `backport` related labels from the original PR, and mark this PR as a "backport"
166163
labels := []*github.Label{
167-
&github.Label{
168-
Name: github.String("backport"),
169-
},
164+
{Name: github.String("backport")},
170165
}
171166

172167
for _, v := range opts.Labels {

backport/backport_test.go

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ func TestMostRecentBranch(t *testing.T) {
7878
}
7979

8080
func TestBackportTarget(t *testing.T) {
81-
assertError := func(t *testing.T, label *github.Label, branches []*github.Branch) {
81+
assertError := func(t *testing.T, label string, branches []*github.Branch) {
8282
t.Helper()
8383
b, err := BackportTarget(label, branches)
8484
assert.Error(t, err)
8585
assert.Empty(t, b)
8686
}
8787

88-
assertBranch := func(t *testing.T, label *github.Label, branches []*github.Branch, branch string) {
88+
assertBranch := func(t *testing.T, label string, branches []*github.Branch, branch string) {
8989
t.Helper()
9090
b, err := BackportTarget(label, branches)
9191
assert.NoError(t, err)
@@ -107,36 +107,16 @@ func TestBackportTarget(t *testing.T) {
107107
{Name: github.String("release-12.2.12")},
108108
}
109109

110-
assertError(t, &github.Label{
111-
Name: github.String("backport v3.2.x"),
112-
}, branches)
113-
assertError(t, &github.Label{
114-
Name: github.String("backport v4.0.x"),
115-
}, branches)
116-
assertError(t, &github.Label{
117-
Name: github.String("backport v13.0.x"),
118-
}, branches)
119-
assertError(t, &github.Label{
120-
Name: github.String("backport v10.5.x"),
121-
}, branches)
122-
assertError(t, &github.Label{
123-
Name: github.String("backport v11.8.x"),
124-
}, branches)
125-
assertBranch(t, &github.Label{
126-
Name: github.String("backport v11.0.x"),
127-
}, branches, "release-11.0.1")
128-
assertBranch(t, &github.Label{
129-
Name: github.String("backport v12.1.x"),
130-
}, branches, "release-12.1.15")
131-
assertBranch(t, &github.Label{
132-
Name: github.String("backport v12.0.x"),
133-
}, branches, "release-12.0.15")
134-
assertBranch(t, &github.Label{
135-
Name: github.String("backport v1.2.x"),
136-
}, branches, "release-1.2.3")
137-
assertBranch(t, &github.Label{
138-
Name: github.String("backport v10.2.x"),
139-
}, branches, "release-10.2.4")
110+
assertError(t, "backport v3.2.x", branches)
111+
assertError(t, "backport v4.0.x", branches)
112+
assertError(t, "backport v13.0.x", branches)
113+
assertError(t, "backport v10.5.x", branches)
114+
assertError(t, "backport v11.8.x", branches)
115+
assertBranch(t, "backport v11.0.x", branches, "release-11.0.1")
116+
assertBranch(t, "backport v12.1.x", branches, "release-12.1.15")
117+
assertBranch(t, "backport v12.0.x", branches, "release-12.0.15")
118+
assertBranch(t, "backport v1.2.x", branches, "release-1.2.3")
119+
assertBranch(t, "backport v10.2.x", branches, "release-10.2.4")
140120
}
141121

142122
type TestBackportClient struct {

backport/main.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package main
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"log/slog"
78
"os"
9+
"strconv"
810
"strings"
911

1012
"github.com/google/go-github/v50/github"
@@ -47,35 +49,37 @@ func main() {
4749
}
4850

4951
var (
50-
ctx = context.Background()
51-
token = os.Getenv("GITHUB_TOKEN")
52-
client = github.NewTokenClient(ctx, token)
53-
inputs = GetInputs()
54-
payload = &github.PullRequestTargetEvent{}
52+
ctx = context.Background()
53+
token = os.Getenv("GITHUB_TOKEN")
54+
client = github.NewTokenClient(ctx, token)
55+
inputs = GetInputs()
56+
57+
// If specified, takes precedence over event data
58+
repoOwner = os.Getenv("REPO_OWNER")
59+
repoName = os.Getenv("REPO_NAME")
60+
prNumber, _ = strconv.Atoi(os.Getenv("PR_NUMBER"))
61+
prLabel = os.Getenv("PR_LABEL")
5562
)
5663

5764
if token == "" {
5865
panic("token can not be empty")
5966
}
6067

61-
if err := UnmarshalEventData(ghctx, &payload); err != nil {
62-
log.Error("error reading github payload", "error", err)
68+
prInfo, err := GetBackportPrInfo(ctx, log, client, ghctx, repoOwner, repoName, prNumber, prLabel)
69+
if err != nil {
70+
log.Error("error getting PR info", "error", err)
6371
panic(err)
6472
}
6573

66-
var (
67-
owner = payload.GetRepo().GetOwner().GetLogin()
68-
repo = payload.GetRepo().GetName()
69-
)
74+
log = log.With("repo", fmt.Sprintf("%s/%s", prInfo.RepoOwner, prInfo.RepoName), "pull_request", prInfo.Pr.GetNumber())
7075

71-
log = log.With("pull_request", payload.GetNumber())
72-
branches, err := ghutil.GetReleaseBranches(ctx, client.Repositories, owner, repo)
76+
branches, err := ghutil.GetReleaseBranches(ctx, log, client.Repositories, prInfo.RepoOwner, prInfo.RepoName)
7377
if err != nil {
7478
log.Error("error getting branches", "error", err)
7579
panic(err)
7680
}
7781

78-
targets, err := BackportTargetsFromPayload(branches, payload)
82+
targets, err := BackportTargetsFromPayload(branches, prInfo)
7983
if err != nil {
8084
if errors.Is(err, ErrorNotMerged) {
8185
log.Warn("pull request is not merged; nothing to do")
@@ -88,28 +92,31 @@ func main() {
8892

8993
for _, target := range targets {
9094
log := log.With("target", target)
91-
mergeBase, err := MergeBase(ctx, client.Repositories, owner, repo, target.Name, *payload.GetPullRequest().Base.Ref)
95+
mergeBase, err := MergeBase(ctx, client.Repositories, prInfo.RepoOwner, prInfo.RepoName, target.Name, prInfo.Pr.GetBase().GetRef())
9296
if err != nil {
9397
log.Error("error finding merge-base", "error", err)
9498
}
9599

96100
opts := BackportOpts{
97-
PullRequestNumber: payload.GetPullRequest().GetNumber(),
98-
SourceSHA: payload.GetPullRequest().GetMergeCommitSHA(),
99-
SourceCommitDate: payload.GetPullRequest().MergedAt.Time,
100-
SourceTitle: payload.GetPullRequest().GetTitle(),
101-
SourceBody: payload.GetPullRequest().GetBody(),
101+
PullRequestNumber: prInfo.Pr.GetNumber(),
102+
SourceSHA: prInfo.Pr.GetMergeCommitSHA(),
103+
SourceCommitDate: prInfo.Pr.GetMergedAt().Time,
104+
SourceTitle: prInfo.Pr.GetTitle(),
105+
SourceBody: prInfo.Pr.GetBody(),
102106
Target: target,
103-
Labels: append(inputs.Labels, payload.GetPullRequest().Labels...),
104-
Owner: owner,
105-
Repository: repo,
107+
Labels: append(inputs.Labels, prInfo.Pr.Labels...),
108+
Owner: prInfo.RepoOwner,
109+
Repository: prInfo.RepoName,
106110
MergeBase: mergeBase,
107111
}
108-
pr, err := Backport(ctx, log, client.PullRequests, client.Issues, client.Issues, NewShellCommandRunner(log), opts)
112+
113+
commandRunner := NewShellCommandRunner(log)
114+
prOut, err := Backport(ctx, log, client.PullRequests, client.Issues, client.Issues, commandRunner, opts)
109115
if err != nil {
110116
log.Error("backport failed", "error", err)
111117
continue
112118
}
113-
log.Info("backport successful", "url", pr.GetURL())
119+
120+
log.Info("backport successful", "url", prOut.GetURL())
114121
}
115122
}

backport/pr_info.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
8+
"github.com/google/go-github/v50/github"
9+
"github.com/sethvargo/go-githubactions"
10+
)
11+
12+
type PrInfo struct {
13+
Pr *github.PullRequest
14+
15+
// Contains the relevant labels for the backport.
16+
// When triggered by being labeled, it should be only the applied label.
17+
// When triggered by being closed, it should be all labels on the PR.
18+
Labels []string
19+
20+
RepoOwner string
21+
RepoName string
22+
}
23+
24+
func GetBackportPrInfo(ctx context.Context, log *slog.Logger, client *github.Client, ghctx *githubactions.GitHubContext, repoOwner string, repoName string, prNumber int, prLabel string) (PrInfo, error) {
25+
log.Debug("getting PR info", "event_path", ghctx.EventPath, "env_pr_label", prLabel, "env_pr_number", prNumber, "env_repo_owner", repoOwner, "env_repo_name", repoName)
26+
27+
// Prefer using the env vars and API if they are set
28+
if prNumber != 0 && repoOwner != "" && repoName != "" {
29+
log.Debug("getting PR info from API")
30+
return getFromApi(ctx, client, prLabel, repoOwner, repoName, prNumber)
31+
}
32+
33+
// Fall back to event data if present
34+
if ghctx.EventPath != "" {
35+
log.Debug("getting PR info from event")
36+
return getFromEvent(ghctx)
37+
}
38+
39+
return PrInfo{}, fmt.Errorf("no PR info found")
40+
}
41+
42+
func getFromEvent(ghctx *githubactions.GitHubContext) (PrInfo, error) {
43+
payload := &github.PullRequestTargetEvent{}
44+
45+
if err := UnmarshalEventData(ghctx, &payload); err != nil {
46+
return PrInfo{}, err
47+
}
48+
49+
if payload.PullRequest == nil {
50+
return PrInfo{}, fmt.Errorf("pull request is nil")
51+
}
52+
53+
pr := payload.GetPullRequest()
54+
prInfo := PrInfo{
55+
Pr: pr,
56+
RepoOwner: payload.GetRepo().GetOwner().GetLogin(),
57+
RepoName: payload.GetRepo().GetName(),
58+
}
59+
60+
switch action := payload.GetAction(); action {
61+
case "labeled":
62+
prInfo.Labels = []string{payload.GetLabel().GetName()}
63+
case "closed":
64+
prInfo.Labels = labelsToStrings(pr.Labels)
65+
default:
66+
return PrInfo{}, fmt.Errorf("unsupported action: %s", action)
67+
}
68+
69+
return prInfo, nil
70+
}
71+
72+
func getFromApi(ctx context.Context, client *github.Client, prLabel, repoOwner, repoName string, prNumber int) (PrInfo, error) {
73+
pr, _, err := client.PullRequests.Get(ctx, repoOwner, repoName, prNumber)
74+
if err != nil {
75+
return PrInfo{}, err
76+
}
77+
78+
prInfo := PrInfo{
79+
Pr: pr,
80+
RepoOwner: repoOwner,
81+
RepoName: repoName,
82+
}
83+
84+
if prLabel != "" {
85+
prInfo.Labels = []string{prLabel}
86+
} else {
87+
prInfo.Labels = labelsToStrings(pr.Labels)
88+
}
89+
90+
return prInfo, nil
91+
}
92+
93+
func labelsToStrings(labels []*github.Label) []string {
94+
strings := make([]string, len(labels))
95+
for i, label := range labels {
96+
strings[i] = label.GetName()
97+
}
98+
return strings
99+
}

0 commit comments

Comments
 (0)