diff --git a/internal/github/data.go b/internal/github/data.go index 0d21290..a827e52 100644 --- a/internal/github/data.go +++ b/internal/github/data.go @@ -2,6 +2,7 @@ package github import ( "errors" + "os" "github.com/rh-ecosystem-edge/gitstream/internal/process" ) @@ -14,6 +15,7 @@ type Commit struct { type BaseData struct { AppName string Commit Commit + JobID string Markup string UpstreamURL string } @@ -34,3 +36,16 @@ func (is *IssueData) ProcessError() *process.Error { } type PRData BaseData + +// GetJobID returns the Job ID from environment variables. +// It first tries GITHUB_RUN_ID (GitHub Actions), then falls back to JOB_ID (generic CI). +// Returns empty string if no Job ID is found. +func GetJobID() string { + if jobID, exists := os.LookupEnv("GITHUB_RUN_ID"); exists && jobID != "" { + return jobID + } + if jobID, exists := os.LookupEnv("JOB_ID"); exists && jobID != "" { + return jobID + } + return "" +} diff --git a/internal/github/data_test.go b/internal/github/data_test.go new file mode 100644 index 0000000..d2fc778 --- /dev/null +++ b/internal/github/data_test.go @@ -0,0 +1,65 @@ +package github + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetJobID(t *testing.T) { + tests := []struct { + name string + githubRunID string + jobID string + expectedResult string + }{ + { + name: "GitHub Actions environment", + githubRunID: "12345678", + jobID: "", + expectedResult: "12345678", + }, + { + name: "Generic CI environment", + githubRunID: "", + jobID: "ci-job-987", + expectedResult: "ci-job-987", + }, + { + name: "GitHub Actions takes precedence", + githubRunID: "github-123", + jobID: "generic-456", + expectedResult: "github-123", + }, + { + name: "No job ID available", + githubRunID: "", + jobID: "", + expectedResult: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Clear environment variables + os.Unsetenv("GITHUB_RUN_ID") + os.Unsetenv("JOB_ID") + + // Set test environment variables + if tt.githubRunID != "" { + os.Setenv("GITHUB_RUN_ID", tt.githubRunID) + } + if tt.jobID != "" { + os.Setenv("JOB_ID", tt.jobID) + } + + result := GetJobID() + assert.Equal(t, tt.expectedResult, result) + + // Clean up + os.Unsetenv("GITHUB_RUN_ID") + os.Unsetenv("JOB_ID") + }) + } +} \ No newline at end of file diff --git a/internal/github/issue.go b/internal/github/issue.go index e37a76e..bf85ab3 100644 --- a/internal/github/issue.go +++ b/internal/github/issue.go @@ -42,6 +42,7 @@ func (ih *IssueHelperImpl) Create(ctx context.Context, err error, upstreamURL st Message: commit.Message, SHA: sha, }, + JobID: GetJobID(), Markup: ih.markup, UpstreamURL: upstreamURL, }, diff --git a/internal/github/issue_test.go b/internal/github/issue_test.go index 1671d1e..caa73dd 100644 --- a/internal/github/issue_test.go +++ b/internal/github/issue_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "os" "os/exec" "testing" @@ -20,6 +21,14 @@ import ( ) func TestIssueHelper_Create(t *testing.T) { + // Clear any existing job ID environment variables for consistent testing + os.Unsetenv("GITHUB_RUN_ID") + os.Unsetenv("JOB_ID") + defer func() { + os.Unsetenv("GITHUB_RUN_ID") + os.Unsetenv("JOB_ID") + }() + const expectedTitle = "Cherry-picking error for `e3229f3c533ed51070beff092e5c7694a8ee81f0`" issue := &github.Issue{Number: github.Int(456)} @@ -161,6 +170,76 @@ func TestIssueHelper_Create(t *testing.T) { }) } +func TestIssueHelper_CreateWithJobID(t *testing.T) { + // Set job ID environment variable + testJobID := "test-job-123" + os.Setenv("GITHUB_RUN_ID", testJobID) + defer os.Unsetenv("GITHUB_RUN_ID") + + const expectedTitle = "Cherry-picking error for `e3229f3c533ed51070beff092e5c7694a8ee81f0`" + + issue := &github.Issue{Number: github.Int(456)} + repoName := &gh.RepoName{Owner: "owner", Repo: "repo"} + + commit := &object.Commit{ + Hash: plumbing.NewHash("e3229f3c533ed51070beff092e5c7694a8ee81f0"), + Message: "Some commit message\nspanning over two lines.", + } + + t.Run("regular error with job ID", func(t *testing.T) { + expectedBodyWithJobID := "gitstream tried to cherry-pick commit `e3229f3c533ed51070beff092e5c7694a8ee81f0` from `some-upstream-url` but was unable to do so.\n" + + "\n" + + "**Job ID**: " + testJobID + "\n\n" + + "Commit message:\n" + + "```\n" + + "Some commit message\n" + + "spanning over two lines.\n" + + "```\n\n" + + "Please cherry-pick the commit manually.\n\n" + + "---\n\n" + + "**Error**:\n" + + "```\n" + + "random error\n" + + "```\n\n\n" + + "---\n\n" + + "Markup: e3229f3c533ed51070beff092e5c7694a8ee81f0" + + c := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PostReposIssuesByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + m := make(map[string]interface{}) + + assert.NoError( + t, + json.NewDecoder(r.Body).Decode(&m), + ) + + assert.Equal(t, expectedBodyWithJobID, m["body"]) + assert.Equal(t, expectedTitle, m["title"]) + assert.Contains(t, m["labels"], "gitstream") + assert.NoError( + t, + json.NewEncoder(w).Encode(issue), + ) + }), + ), + ) + + gc := github.NewClient(c) + + res, err := gh.NewIssueHelper(gc, "Markup", repoName).Create( + context.Background(), + errors.New("random error"), + "some-upstream-url", + commit, + ) + + assert.NoError(t, err) + assert.Equal(t, issue, res) + }) +} + func TestIssueHelper_Assign(t *testing.T) { issue := &github.Issue{Number: github.Int(456)} diff --git a/internal/github/pr.go b/internal/github/pr.go index e25c78b..006cea9 100644 --- a/internal/github/pr.go +++ b/internal/github/pr.go @@ -48,6 +48,7 @@ func (ph *PRHelperImpl) Create(ctx context.Context, branch, base, upstreamURL st Message: commit.Message, SHA: sha, }, + JobID: GetJobID(), Markup: ph.markup, UpstreamURL: upstreamURL, } diff --git a/internal/github/pr_test.go b/internal/github/pr_test.go index 2201f6d..9339d6c 100644 --- a/internal/github/pr_test.go +++ b/internal/github/pr_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "os" "testing" "github.com/go-git/go-git/v5/plumbing" @@ -16,6 +17,14 @@ import ( ) func TestPRHelperImpl_Create(t *testing.T) { + // Clear any existing job ID environment variables for consistent testing + os.Unsetenv("GITHUB_RUN_ID") + os.Unsetenv("JOB_ID") + defer func() { + os.Unsetenv("GITHUB_RUN_ID") + os.Unsetenv("JOB_ID") + }() + const ( draft = true expectedBody = "This is an automated cherry-pick by gitstream of `e3229f3c533ed51070beff092e5c7694a8ee81f0` from `some-upstream-url`.\n\n" + @@ -95,3 +104,91 @@ func TestPRHelperImpl_Create(t *testing.T) { assert.NoError(t, err) assert.Equal(t, pr, res) } + +func TestPRHelperImpl_CreateWithJobID(t *testing.T) { + // Set job ID environment variable + testJobID := "test-job-123" + os.Setenv("GITHUB_RUN_ID", testJobID) + defer os.Unsetenv("GITHUB_RUN_ID") + + const ( + draft = true + expectedTitle = "Cherry-pick `e3229f3c533ed51070beff092e5c7694a8ee81f0` from upstream" + owner = "owner" + prNumber = 456 + repo = "repo" + ) + + expectedBodyWithJobID := "This is an automated cherry-pick by gitstream of `e3229f3c533ed51070beff092e5c7694a8ee81f0` from `some-upstream-url`.\n\n" + + "**Job ID**: " + testJobID + "\n\n" + + "Commit message:\n" + + "```\n" + + "Some commit message\n" + + "spreading over two lines.\n" + + "```\n\n" + + "---\n\n" + + "Markup: e3229f3c533ed51070beff092e5c7694a8ee81f0" + + pr := &github.PullRequest{ + Number: github.Int(prNumber), + } + + c := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PostReposPullsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + m := make(map[string]interface{}) + + assert.NoError( + t, + json.NewDecoder(r.Body).Decode(&m), + ) + + assert.Equal(t, expectedBodyWithJobID, m["body"]) + assert.Equal(t, expectedTitle, m["title"]) + assert.Equal(t, draft, m["draft"]) + + assert.NoError( + t, + json.NewEncoder(w).Encode(pr), + ) + }), + ), + mock.WithRequestMatchHandler( + mock.PostReposIssuesLabelsByOwnerByRepoByIssueNumber, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + m := make([]string, 0) + + assert.NoError( + t, + json.NewDecoder(r.Body).Decode(&m), + ) + + assert.Equal( + t, + fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, prNumber), + r.RequestURI, + ) + + assert.Equal(t, []string{"gitstream"}, m) + }), + ), + ) + + gc := github.NewClient(c) + + res, err := gh.NewPRHelper(gc, nil, "Markup", &gh.RepoName{Owner: owner, Repo: repo}).Create( + context.Background(), + "some-branch", + "main", + "some-upstream-url", + &object.Commit{ + Hash: plumbing.NewHash("e3229f3c533ed51070beff092e5c7694a8ee81f0"), + Message: "Some commit message\nspreading over two lines.", + }, + draft, + ) + + assert.NoError(t, err) + assert.Equal(t, pr, res) +} diff --git a/internal/github/templates/issue.tmpl b/internal/github/templates/issue.tmpl index fd7857e..e405134 100644 --- a/internal/github/templates/issue.tmpl +++ b/internal/github/templates/issue.tmpl @@ -1,5 +1,9 @@ {{- /*gotype: github.com/rh-ecosystem-edge/gitstream/internal/github.IssueData*/ -}} {{ .AppName }} tried to cherry-pick commit `{{ .Commit.SHA }}` from `{{ .UpstreamURL }}` but was unable to do so. +{{- if .JobID }} + +**Job ID**: {{ .JobID }} +{{- end }} Commit message: ``` diff --git a/internal/github/templates/pr.tmpl b/internal/github/templates/pr.tmpl index 0e43b09..1c16d64 100644 --- a/internal/github/templates/pr.tmpl +++ b/internal/github/templates/pr.tmpl @@ -1,5 +1,9 @@ {{- /*gotype: github.com/rh-ecosystem-edge/gitstream/internal/github.PRData*/ -}} This is an automated cherry-pick by {{ .AppName }} of `{{ .Commit.SHA }}` from `{{ .UpstreamURL }}`. +{{- if .JobID }} + +**Job ID**: {{ .JobID }} +{{- end }} Commit message: ```