Skip to content
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

trigger-argo-workflows: Add PR information as labels #57

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
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
10 changes: 6 additions & 4 deletions actions/trigger-argo-workflow/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ runs:
with:
repository: ${{ env.action_repo }}
ref: ${{ env.action_ref }}
path: ".actions/shared-workflows"

- name: Restore cache
id: restore
Expand Down Expand Up @@ -102,12 +103,12 @@ runs:
with:
check-latest: true
cache-dependency-path: |
actions/trigger-argo-workflow/go.sum
go-version-file: "actions/trigger-argo-workflow/go.mod"
.actions/shared-workflows/actions/trigger-argo-workflow/go.sum
go-version-file: ".actions/shared-workflows/actions/trigger-argo-workflow/go.mod"

- name: Get Argo Token
id: get-argo-token
uses: "./actions/get-vault-secrets"
uses: "./.actions/shared-workflows/actions/get-vault-secrets"
with:
vault_instance: ${{ inputs.instance }}
repo_secrets: |
Expand All @@ -117,7 +118,7 @@ runs:
id: run
shell: bash
run: |
cd actions/trigger-argo-workflow
cd .actions/shared-workflows/actions/trigger-argo-workflow

# Split the parameters into an array and pass them to the action as --parameter PARAM
while read -r line; do
Expand All @@ -141,3 +142,4 @@ runs:
${{ inputs.extra_args }}
env:
ARGO_TOKEN: ${{ env.ARGO_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ func (a App) outputWithURI(input *bytes.Buffer) (string, string) {
return uri, output
}

func (a App) runCmd(md GitHubActionsMetadata) (string, string, error) {
args := a.args(md)
func (a App) runCmd(labelProviders ...LabelsProvider) (string, string, error) {
args := a.args(labelProviders...)

cmd := exec.Command("argo", args...)
cmdOutput := &bytes.Buffer{}
Expand Down Expand Up @@ -125,15 +125,15 @@ func (a *App) openGitHubOutput() io.WriteCloser {
return f
}

func (a *App) Run(md GitHubActionsMetadata) error {
func (a *App) Run(labelProviders ...LabelsProvider) error {
bo := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), a.retries)

var uri string
var out string

run := func() error {
var err error
uri, out, err = a.runCmd(md)
uri, out, err = a.runCmd(labelProviders...)

return err
}
Expand Down
182 changes: 164 additions & 18 deletions actions/trigger-argo-workflow/cmd/trigger-argo-workflow/argoflags.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package main

import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"

"github.com/google/go-github/v60/github"
"github.com/kelseyhightower/envconfig"
)

Expand Down Expand Up @@ -33,12 +41,140 @@ func (r *RepoInfo) Decode(text string) error {
return nil
}

func (r RepoInfo) ToArgs() string {
return fmt.Sprintf(
"trigger-repo-name=%s,trigger-repo-owner=%s",
r.Name,
r.Owner,
)
func (r RepoInfo) ToLabels() []string {
return []string{
fmt.Sprintf("trigger-repo-name=%s", r.Name),
fmt.Sprintf("trigger-repo-owner=%s", r.Owner),
}
}

// PullRequestInfo represents collected information about the pull request this
// action was executed in.
type PullRequestInfo struct {
Number int
CreatedAt *time.Time
FirstCommit *github.RepositoryCommit
}

func (pri *PullRequestInfo) ToLabels() []string {
if pri == nil {
return []string{}
}
result := []string{
fmt.Sprintf("trigger-pr=%d", pri.Number),
}
if pri.CreatedAt != nil {
result = append(result, fmt.Sprintf("trigger-pr-created-at=%d", pri.CreatedAt.UTC().Unix()))
}
if pri.FirstCommit != nil && pri.FirstCommit.Commit != nil && pri.FirstCommit.Commit.Committer != nil {
result = append(result, fmt.Sprintf("trigger-pr-first-commit-date=%d", pri.FirstCommit.Commit.Committer.Date.UTC().Unix()))
}
return result
}

var errPRLookupNotSupported error = errors.New("PR lookup not supported")

func getPullRequestNumberFromHead(ctx context.Context, logger *slog.Logger, workdir string) (int64, error) {
ref := os.Getenv("GITHUB_SHA")
if ref == "" || len(ref) < 40 {
ref = "HEAD"
}
l := logger.With(slog.String("workdir", workdir), slog.String("ref", ref))
l.InfoContext(ctx, "checking PR information in commit")
gitPath, err := exec.LookPath("git")
if err != nil {
return -1, errPRLookupNotSupported
}
cmd := exec.CommandContext(ctx, gitPath, "log", "--pretty=%s", "-1", ref)
cmd.Dir = workdir
raw, err := cmd.CombinedOutput()
if err != nil {
return -1, err
}
output := strings.TrimSpace(string(raw))
re := regexp.MustCompile(`^.*\(#([0-9]+)\)$`)
match := re.FindStringSubmatch(output)
if len(match) < 2 {
l.WarnContext(ctx, "unsupported commit message", slog.String("msg", output))
return -1, nil
}
return strconv.ParseInt(match[1], 10, 64)
}

// NewPullRequestInfo tries to generate a new PullRequestInfo object based on
// information available inside the GitHub API and environment variables. If
// no PR information is available, nil is returned without an error!
func NewPullRequestInfo(ctx context.Context, logger *slog.Logger, gh *github.Client) (*PullRequestInfo, error) {
var err error
var number int64
ref := os.Getenv("GITHUB_REF")
re := regexp.MustCompile(`^refs/pull/([0-9]+)/merge$`)
match := re.FindStringSubmatch(ref)
if len(match) == 0 {
// This is happening outside of a pull request. This means that we
// cannot simply get the pull request number from the ref and need to
// look somewhere else. For our purposes we can also take a look at the
// HEAD commit message and continue from there.
workdir := os.Getenv("GITHUB_WORKSPACE")
if workdir == "" {
workdir = "."
}
number, err = getPullRequestNumberFromHead(ctx, logger, workdir)
if err != nil {
if err == errPRLookupNotSupported {
logger.InfoContext(ctx, "PR Git lookup not supported")
return nil, nil
}
return nil, err
}
if number == -1 {
logger.InfoContext(ctx, "PR Git lookup found no PR")
return nil, nil
}
} else {
number, err = strconv.ParseInt(match[1], 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse PR number: %w", err)
}
}
info := PullRequestInfo{
Number: int(number),
}

repo := &RepoInfo{}
if err := repo.Decode(os.Getenv("GITHUB_REPOSITORY")); err != nil {
return nil, err
}
pr, _, err := gh.PullRequests.Get(ctx, repo.Owner, repo.Name, info.Number)
if err != nil {
return nil, err
}
if pr.CreatedAt != nil {
info.CreatedAt = &pr.CreatedAt.Time
}

// Now let's also try to retrieve the first commit in this PR:
opts := github.ListOptions{
Page: 1,
}
var firstCommit *github.RepositoryCommit
for {
commits, resp, err := gh.PullRequests.ListCommits(ctx, repo.Owner, repo.Name, info.Number, &opts)
if err != nil {
return nil, err
}
if resp.NextPage <= opts.Page {
if len(commits) > 0 {
firstCommit = commits[len(commits)-1]
}
break
}
opts.Page = resp.NextPage
}
if firstCommit != nil {
info.FirstCommit = firstCommit
}
return &info, nil
}

// GitHubActionsMetadata contains the metadata provided by GitHub Actions in
Expand All @@ -60,31 +196,41 @@ func NewGitHubActionsMetadata() (GitHubActionsMetadata, error) {
return m, nil
}

func (m GitHubActionsMetadata) ToArgs() []string {
func (m GitHubActionsMetadata) ToLabels() []string {
var z GitHubActionsMetadata
if m == z {
return []string{}
}

return []string{
"--labels",
strings.Join([]string{
fmt.Sprintf("trigger-build-number=%s", m.BuildNumber),
fmt.Sprintf("trigger-commit=%s", m.Commit),
fmt.Sprintf("trigger-commit-author=%s", m.CommitAuthor),
m.Repo.ToArgs(),
fmt.Sprintf("trigger-event=%s", m.BuildEvent),
}, ","),
repoLabels := m.Repo.ToLabels()

result := []string{
fmt.Sprintf("trigger-build-number=%s", m.BuildNumber),
fmt.Sprintf("trigger-commit=%s", m.Commit),
fmt.Sprintf("trigger-commit-author=%s", m.CommitAuthor),
fmt.Sprintf("trigger-event=%s", m.BuildEvent),
}
return append(result, repoLabels...)
}

type LabelsProvider interface {
ToLabels() []string
}

func (a App) args(m GitHubActionsMetadata) []string {
func (a App) args(providers ...LabelsProvider) []string {
// Force the labels when the command is `submit`
addCILabels := a.addCILabels || a.command == "submit"

var args []string
if addCILabels {
args = m.ToArgs()
var labels []string
for _, prov := range providers {
if prov == nil {
continue
}
labels = append(labels, prov.ToLabels()...)
}
args = append(args, "--labels", strings.Join(labels, ","))
}

if a.workflowTemplate != "" {
Expand Down
Loading
Loading