chore: add support for non-github urls such as forgejo
This commit is contained in:
parent
d8289abf87
commit
4d47ec5958
2 changed files with 91 additions and 33 deletions
57
approval.go
57
approval.go
|
|
@ -51,11 +51,11 @@ func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner strin
|
|||
}
|
||||
|
||||
func (a approvalEnvironment) runURL() string {
|
||||
baseUrl := a.client.BaseURL.String()
|
||||
if strings.Contains(baseUrl, "github.com") {
|
||||
baseUrl = "https://github.com/"
|
||||
serverUrl := os.Getenv("GITHUB_SERVER_URL")
|
||||
if serverUrl == "" {
|
||||
serverUrl = "https://github.com"
|
||||
}
|
||||
return fmt.Sprintf("%s%s/actions/runs/%d", baseUrl, a.repoFullName, a.runID)
|
||||
return fmt.Sprintf("%s/%s/actions/runs/%d", strings.TrimRight(serverUrl, "/"), a.repoFullName, a.runID)
|
||||
}
|
||||
|
||||
func (a *approvalEnvironment) createApprovalIssue(ctx context.Context) error {
|
||||
|
|
@ -96,25 +96,44 @@ func (a *approvalEnvironment) createApprovalIssue(ctx context.Context) error {
|
|||
a.issueApprovers,
|
||||
issueBody,
|
||||
)
|
||||
a.approvalIssue, _, err = a.client.Issues.Create(ctx, a.targetRepoOwner, a.targetRepoName, &github.IssueRequest{
|
||||
Title: &issueTitle,
|
||||
Body: &issueBody,
|
||||
Assignees: &a.issueApprovers,
|
||||
})
|
||||
// Use NewRequest+Do with a minimal response struct rather than client.Issues.Create.
|
||||
// Forgejo's issue response includes "repository.owner" as a plain string, but
|
||||
// go-github's Repository.Owner is a *User struct, causing an unmarshal error.
|
||||
// Our minimal struct omits Repository entirely, so the field is ignored.
|
||||
type createIssueResponse struct {
|
||||
Number int `json:"number"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
req, err := a.client.NewRequest("POST",
|
||||
fmt.Sprintf("repos/%s/%s/issues", a.targetRepoOwner, a.targetRepoName),
|
||||
&github.IssueRequest{
|
||||
Title: &issueTitle,
|
||||
Body: &issueBody,
|
||||
Assignees: &a.issueApprovers,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.approvalIssueNumber = a.approvalIssue.GetNumber()
|
||||
var created createIssueResponse
|
||||
if _, err = a.client.Do(ctx, req, &created); err != nil {
|
||||
return err
|
||||
}
|
||||
a.approvalIssueNumber = created.Number
|
||||
a.approvalIssue = &github.Issue{
|
||||
Number: &created.Number,
|
||||
HTMLURL: &created.HTMLURL,
|
||||
}
|
||||
|
||||
bodyChunks := splitLongString(a.issueBody)
|
||||
for _, chunk := range bodyChunks {
|
||||
_, _, err = a.client.Issues.CreateComment(ctx, a.targetRepoOwner, a.targetRepoName, *a.approvalIssue.Number, &github.IssueComment{
|
||||
Body: &chunk,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add comment chunk to issue: %w", err)
|
||||
}
|
||||
}
|
||||
bodyChunks := splitLongString(a.issueBody)
|
||||
for _, chunk := range bodyChunks {
|
||||
_, _, err = a.client.Issues.CreateComment(ctx, a.targetRepoOwner, a.targetRepoName, created.Number, &github.IssueComment{
|
||||
Body: &chunk,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add comment chunk to issue: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Issue created: %s\n", a.approvalIssue.GetHTMLURL())
|
||||
return nil
|
||||
|
|
|
|||
67
main.go
67
main.go
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
|
|
@ -13,6 +14,22 @@ import (
|
|||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// patchIssueState closes or otherwise updates an issue's state without decoding
|
||||
// the response body into a github.Issue. go-github's Issues.Edit decodes the
|
||||
// response into github.Issue, which fails against Forgejo because its issue
|
||||
// response embeds "repository.owner" as a plain string rather than a User object.
|
||||
func patchIssueState(ctx context.Context, client *github.Client, owner, repo string, number int, state string) error {
|
||||
req, err := client.NewRequest("PATCH",
|
||||
fmt.Sprintf("repos/%s/%s/issues/%d", owner, repo, number),
|
||||
&github.IssueRequest{State: &state},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.Do(ctx, req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func handleInterrupt(ctx context.Context, client *github.Client, apprv *approvalEnvironment) {
|
||||
newState := "closed"
|
||||
closeComment := "Workflow cancelled, closing issue."
|
||||
|
|
@ -25,8 +42,7 @@ func handleInterrupt(ctx context.Context, client *github.Client, apprv *approval
|
|||
fmt.Printf("error commenting on issue: %v\n", err)
|
||||
return
|
||||
}
|
||||
_, _, err = client.Issues.Edit(ctx, apprv.targetRepoOwner, apprv.targetRepoName, apprv.approvalIssueNumber, &github.IssueRequest{State: &newState})
|
||||
if err != nil {
|
||||
if err = patchIssueState(ctx, client, apprv.targetRepoOwner, apprv.targetRepoName, apprv.approvalIssueNumber, newState); err != nil {
|
||||
fmt.Printf("error closing issue: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
|
@ -41,6 +57,7 @@ func newCommentLoopChannel(ctx context.Context, apprv *approvalEnvironment, clie
|
|||
fmt.Printf("error getting comments: %v\n", err)
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
|
||||
approved, err := approvalFromComments(comments, apprv.issueApprovers, apprv.minimumApprovals)
|
||||
|
|
@ -48,6 +65,7 @@ func newCommentLoopChannel(ctx context.Context, apprv *approvalEnvironment, clie
|
|||
fmt.Printf("error getting approval from comments: %v\n", err)
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Workflow status: %s\n", approved)
|
||||
switch approved {
|
||||
|
|
@ -61,16 +79,18 @@ func newCommentLoopChannel(ctx context.Context, apprv *approvalEnvironment, clie
|
|||
fmt.Printf("error commenting on issue: %v\n", err)
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
_, _, err = client.Issues.Edit(ctx, apprv.targetRepoOwner, apprv.targetRepoName, apprv.approvalIssueNumber, &github.IssueRequest{State: &newState})
|
||||
if err != nil {
|
||||
if err = patchIssueState(ctx, client, apprv.targetRepoOwner, apprv.targetRepoName, apprv.approvalIssueNumber, newState); err != nil {
|
||||
fmt.Printf("error closing issue: %v\n", err)
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
channel <- 0
|
||||
fmt.Println("Workflow manual approval completed")
|
||||
close(channel)
|
||||
return
|
||||
case approvalStatusDenied:
|
||||
newState := "closed"
|
||||
closeComment := "Request denied. Closing issue "
|
||||
|
|
@ -88,15 +108,17 @@ func newCommentLoopChannel(ctx context.Context, apprv *approvalEnvironment, clie
|
|||
fmt.Printf("error commenting on issue: %v\n", err)
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
_, _, err = client.Issues.Edit(ctx, apprv.targetRepoOwner, apprv.targetRepoName, apprv.approvalIssueNumber, &github.IssueRequest{State: &newState})
|
||||
if err != nil {
|
||||
if err = patchIssueState(ctx, client, apprv.targetRepoOwner, apprv.targetRepoName, apprv.approvalIssueNumber, newState); err != nil {
|
||||
fmt.Printf("error closing issue: %v\n", err)
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
channel <- 1
|
||||
close(channel)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(pollingInterval)
|
||||
|
|
@ -115,13 +137,30 @@ func newGithubClient(ctx context.Context) (*github.Client, error) {
|
|||
serverUrl, serverUrlPresent := os.LookupEnv("GITHUB_SERVER_URL")
|
||||
apiUrl, apiUrlPresent := os.LookupEnv("GITHUB_API_URL")
|
||||
|
||||
if serverUrlPresent {
|
||||
if !apiUrlPresent {
|
||||
apiUrl = serverUrl
|
||||
}
|
||||
return github.NewEnterpriseClient(apiUrl, serverUrl, tc)
|
||||
if !serverUrlPresent && !apiUrlPresent {
|
||||
return github.NewClient(tc), nil
|
||||
}
|
||||
return github.NewClient(tc), nil
|
||||
|
||||
if !apiUrlPresent {
|
||||
// Only GITHUB_SERVER_URL is set; assume GitHub Enterprise with the
|
||||
// default /api/v3 path and let NewEnterpriseClient append it.
|
||||
return github.NewEnterpriseClient(serverUrl, serverUrl, tc)
|
||||
}
|
||||
|
||||
// GITHUB_API_URL is set. github.NewEnterpriseClient appends "/api/v3/" to
|
||||
// any URL whose path doesn't already end with it. This breaks Forgejo/Gitea
|
||||
// instances whose API lives at "/api/v1/". Instead, set BaseURL directly so
|
||||
// the URL is used as-is, which works for GitHub.com, GHES, and Forgejo alike.
|
||||
if !strings.HasSuffix(apiUrl, "/") {
|
||||
apiUrl += "/"
|
||||
}
|
||||
baseURL, err := url.Parse(apiUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid GITHUB_API_URL %q: %w", apiUrl, err)
|
||||
}
|
||||
client := github.NewClient(tc)
|
||||
client.BaseURL = baseURL
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func validateInput() error {
|
||||
|
|
@ -243,7 +282,7 @@ func main() {
|
|||
|
||||
err = apprv.createApprovalIssue(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("error creating issue: %v", err)
|
||||
fmt.Printf("error creating issue: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +292,7 @@ func main() {
|
|||
}
|
||||
_, err = apprv.SetActionOutputs(outputs)
|
||||
if err != nil {
|
||||
fmt.Printf("error saving output: %v", err)
|
||||
fmt.Printf("error saving output: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue