diff --git a/README.md b/README.md index 236c7e7..1d535b3 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ USAGE: OPTIONS: --commit-info-file value Path to a file which the commit info will be written. The file will be overwritten if it already exists. --exclude value, -x value [ --exclude value, -x value ] Exclude the status of a specific CI check from failing the wait. By default, a failed status check will exit the pr wait command. [$GITHUB_CI_EXCLUDE] + --stop-on-failed-ci Stop and exit with error when CI fails. Defaults to true. [$GITHUB_STOP_ON_FAILED_CI] --help, -h show help ``` @@ -68,6 +69,9 @@ This command will wait for the given PR (URL or owner/repo/number) to be merged or closed. If merged, it will exit with code `0` (success) and if closed without being merged it will exit with code `1` (failure). +By default, the command will also exit with code `1` if the CI checks on the PR +fail. This behavior can be disabled by setting `--stop-on-failed-ci=false`. + #### `ci` ``` @@ -155,6 +159,11 @@ which are not added immediately. Recheck interval (i.e. poll this often) in golang duration format. Optional. Default is `30s`. +#### `stop-on-failed-ci` + +Stop and exit with error when CI fails. Only used when `wait-for` is set to +`"pr"`. Optional. Default is `true`. + #### `owner` GitHub repo owner. Optional. Default is the current repository's owner, diff --git a/action.yml b/action.yml index 7dd1cc1..0d0f453 100644 --- a/action.yml +++ b/action.yml @@ -42,6 +42,10 @@ inputs: checks-to-wait-for: description: 'The comma-separated names of the checks to wait for. Only used when wait-for is "ci"' required: false + stop-on-failed-ci: + description: 'Stop and exit with error when CI fails. Only used when wait-for is "pr". Defaults to true for backwards compatibility' + required: false + default: "true" runs: using: composite @@ -68,6 +72,7 @@ runs: GITHUB_TOKEN: ${{ inputs.token }} GITHUB_CI_CHECKS: ${{ inputs.checks-to-wait-for }} GITHUB_CI_EXCLUDE: ${{ inputs.exclude-checks }} + GITHUB_STOP_ON_FAILED_CI: ${{ inputs.stop-on-failed-ci }} GITHUB_APP_ID: ${{ inputs.app-id }} GITHUB_APP_INSTALLATION_ID: ${{ inputs.app-installation-id }} GITHUB_APP_PRIVATE_KEY: ${{ inputs.app-private-key }} diff --git a/cmd/wait-for-github/pr.go b/cmd/wait-for-github/pr.go index 28e43d6..24f7400 100644 --- a/cmd/wait-for-github/pr.go +++ b/cmd/wait-for-github/pr.go @@ -50,6 +50,7 @@ type prConfig struct { commitInfoFile string excludes []string + stopOnFailedCI bool writer fileWriter } @@ -122,6 +123,7 @@ func parsePRArguments(ctx context.Context, cmd *cli.Command, logger *slog.Logger pr: n, commitInfoFile: cmd.String("commit-info-file"), excludes: cmd.StringSlice("exclude"), + stopOnFailedCI: cmd.Bool("stop-on-failed-ci"), writer: osFileWriter{}, }, nil } @@ -178,6 +180,10 @@ func (pr prCheck) Check(ctx context.Context) error { return cli.Exit("PR is closed", 1) } + if !pr.stopOnFailedCI { + return nil + } + // not merged, not closed, let's see what the CI status is. If that's bad, // we can exit early. sha, err := pr.githubClient.GetPRHeadSHA(ctx, pr.owner, pr.repo, pr.pr) @@ -233,6 +239,14 @@ func prCommand(cfg *config) *cli.Command { cli.EnvVar("GITHUB_CI_EXCLUDE"), ), }, + &cli.BoolFlag{ + Name: "stop-on-failed-ci", + Usage: "Stop and exit with error when CI fails. Defaults to true.", + Value: true, + Sources: cli.NewValueSourceChain( + cli.EnvVar("GITHUB_STOP_ON_FAILED_CI"), + ), + }, }, Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) { var err error diff --git a/cmd/wait-for-github/pr_test.go b/cmd/wait-for-github/pr_test.go index 64570af..edc2f5e 100644 --- a/cmd/wait-for-github/pr_test.go +++ b/cmd/wait-for-github/pr_test.go @@ -60,6 +60,7 @@ func TestPRCheck(t *testing.T) { name string fakeClient fakeGithubClientPRCheck expectedExitCode *int + allowFailedCI bool }{ { name: "PR is merged", @@ -90,12 +91,30 @@ func TestPRCheck(t *testing.T) { }, }, { - name: "Not merged, but CI failed", + name: "Not merged, but CI failed (stop-on-failed-ci enabled)", fakeClient: fakeGithubClientPRCheck{ CIStatus: github.CIStatusFailed, }, expectedExitCode: &one, }, + { + name: "Merged, CI failed", + fakeClient: fakeGithubClientPRCheck{ + CIStatus: github.CIStatusFailed, + MergedCommit: "abc123", + MergedAt: 1234567890, + }, + expectedExitCode: &zero, + }, + { + name: "Closed, CI failed (stop-on-failed-ci disabled)", + fakeClient: fakeGithubClientPRCheck{ + Closed: true, + CIStatus: github.CIStatusFailed, + }, + allowFailedCI: true, + expectedExitCode: &one, + }, { name: "Not merged, getting PR head SHA failed", fakeClient: fakeGithubClientPRCheck{ @@ -122,9 +141,10 @@ func TestPRCheck(t *testing.T) { cancel() prConfig := prConfig{ - owner: "owner", - repo: "repo", - pr: 1, + owner: "owner", + repo: "repo", + pr: 1, + stopOnFailedCI: !tt.allowFailedCI, } err := checkPRMerged(ctx, fakePRStatusChecker, cfg, &prConfig) @@ -237,20 +257,22 @@ func TestParsePRArguments(t *testing.T) { name: "Valid pull request URL", args: []string{"https://github.com/owner/repo/pull/1"}, want: prConfig{ - owner: "owner", - repo: "repo", - pr: 1, - writer: osFileWriter{}, + owner: "owner", + repo: "repo", + pr: 1, + stopOnFailedCI: false, + writer: osFileWriter{}, }, }, { name: "Valid arguments owner, repo, pr", args: []string{"owner", "repo", "1"}, want: prConfig{ - owner: "owner", - repo: "repo", - pr: 1, - writer: osFileWriter{}, + owner: "owner", + repo: "repo", + pr: 1, + stopOnFailedCI: false, + writer: osFileWriter{}, }, }, { @@ -278,6 +300,12 @@ func TestParsePRArguments(t *testing.T) { rootCmd := &cli.Command{} prCmd := &cli.Command{ Name: "pr", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "stop-on-failed-ci", + Value: false, + }, + }, Action: func(ctx context.Context, cmd *cli.Command) error { got, err := parsePRArguments(ctx, cmd, testLogger) if tt.wantErr != nil {