Skip to content

Commit 54531c3

Browse files
committed
feat: add skip CI command support
Implement support for skip CI commands in commit messages to allow users to skip PipelineRun execution. Supports [skip ci], [ci skip], [skip tkn], and [tkn skip] commands. When a skip command is detected in the commit message, PipelineRun execution is skipped. However, GitOps commands (/test, /retest, etc.) will still trigger PipelineRuns regardless of the skip command, allowing users to manually trigger CI when needed. Jira: https://issues.redhat.com/browse/SRVKP-8933 Signed-off-by: Akshay Pant <akshay.akshaypant@gmail.com>
1 parent d0425d9 commit 54531c3

File tree

22 files changed

+2003
-197
lines changed

22 files changed

+2003
-197
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Before getting started with Pipelines-as-Code, ensure you have:
6565
- **Multi-provider support**: Works with GitHub (via GitHub App & Webhook), GitLab, Gitea, Bitbucket Data Center & Cloud via webhooks.
6666
- **Annotation-driven workflows**: Target specific events, branches, or CEL expressions and gate untrusted PRs with `/ok-to-test` and `OWNERS`; see [Running the PipelineRun](https://pipelinesascode.com/docs/guide/running/).
6767
- **ChatOps style control**: `/test`, `/retest`, `/cancel`, and branch or tag selectors let you rerun or stop PipelineRuns from PR comments or commit messages; see [GitOps Commands](https://pipelinesascode.com/docs/guide/gitops_commands/).
68+
- **Skip CI support**: Use `[skip ci]`, `[ci skip]`, `[skip tkn]`, or `[tkn skip]` in commit messages to skip automatic PipelineRun execution for documentation updates or minor changes; GitOps commands can still override and trigger runs manually; see [Skip CI Commands](https://pipelinesascode.com/docs/guide/gitops_commands/#skip-ci-commands).
6869
- **Feedback**: GitHub Checks capture per-task timing, log snippets, and optional error annotations while redacting secrets; see [PipelineRun status](https://pipelinesascode.com/docs/guide/statuses/).
6970
- **Inline resolution**: The resolver bundles `.tekton/` resources, inlines remote tasks from Artifact Hub or Tekton Hub, and validates YAML before cluster submission; see [Resolver](https://pipelinesascode.com/docs/guide/resolver/).
7071
- **CLI**: `tkn pac` bootstraps installs, manages Repository CRDs, inspects logs, and resolves runs locally; see the [CLI guide](https://pipelinesascode.com/docs/guide/cli/).

docs/content/docs/guide/matchingevents.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,97 @@ main and the branch called `release,nightly` you can do this:
486486
```yaml
487487
pipelinesascode.tekton.dev/on-target-branch: [main, release&#44;nightly]
488488
```
489+
490+
## Skip CI Commands
491+
492+
Pipelines-as-Code supports skip commands in commit messages that allow you to skip
493+
PipelineRun execution for specific commits. This is useful when making documentation
494+
changes, minor fixes, or work-in-progress commits where running the full CI pipeline
495+
is unnecessary.
496+
497+
### Supported Skip Commands
498+
499+
You can include any of the following commands anywhere in your commit message to skip
500+
PipelineRun execution:
501+
502+
* `[skip ci]` - Skip continuous integration
503+
* `[ci skip]` - Alternative format for skipping CI
504+
* `[skip tkn]` - Skip Tekton PipelineRuns
505+
* `[tkn skip]` - Alternative format for skipping Tekton
506+
507+
**Note:** Skip commands are **case-sensitive** and must be in lowercase with brackets.
508+
509+
### Example Usage
510+
511+
```text
512+
docs: update README with installation instructions [skip ci]
513+
```
514+
515+
or
516+
517+
```text
518+
WIP: refactor authentication module
519+
520+
This is still in progress and not ready for testing yet.
521+
522+
[ci skip]
523+
```
524+
525+
### How Skip Commands Work
526+
527+
When a commit message contains a skip command:
528+
529+
1. **Pull Requests**: No PipelineRuns will be created when the PR is opened or updated and HEAD commit contains skip command. A neutral status check will be displayed on the PR indicating that CI was skipped.
530+
2. **Push Events**: No PipelineRuns will be created when pushing to a branch with that commit message. A neutral status check will be displayed on the commit.
531+
532+
**Note:** A neutral status check is created on your git provider to provide visibility that the commit was acknowledged but CI was intentionally skipped. This helps distinguish between commits that were ignored due to skip commands versus commits where CI hasn't run.
533+
534+
### GitOps Commands Override Skip CI
535+
536+
**Important:** Skip CI commands can be overridden by using GitOps commands. Even if
537+
a commit contains a skip command like `[skip ci]`, you can still manually trigger
538+
PipelineRuns using:
539+
540+
* `/test` - Trigger all matching PipelineRuns
541+
* `/test <pipelinerun-name>` - Trigger a specific PipelineRun
542+
* `/retest` - Retrigger failed PipelineRuns
543+
* `/retest <pipelinerun-name>` - Retrigger a specific PipelineRun
544+
* `/ok-to-test` - Allow running CI for external contributors
545+
* `/custom-comment` - Trigger PipelineRun having on-comment annotation
546+
547+
This allows you to skip automatic CI execution while still maintaining the ability
548+
to manually trigger builds when needed.
549+
550+
### Example: Skipping CI Then Manually Triggering
551+
552+
```bash
553+
# Initial commit with skip command
554+
git commit -m "docs: update contributing guide [skip ci]"
555+
git push origin my-feature-branch
556+
# No PipelineRuns are created automatically
557+
# A neutral status check is displayed on the commit/PR
558+
559+
# Later, you can manually trigger CI by commenting on the PR:
560+
# /test
561+
# This will create PipelineRuns despite the [skip ci] command
562+
```
563+
564+
### Examples of When to Use Skip Commands
565+
566+
Skip commands are useful for:
567+
568+
* Documentation-only changes
569+
* README updates
570+
* Comment or formatting changes
571+
* Work-in-progress commits
572+
* Minor typo fixes
573+
* Configuration file updates that don't affect code
574+
575+
### Examples of When NOT to Use Skip Commands
576+
577+
Avoid using skip commands for:
578+
579+
* Code changes that affect functionality
580+
* Changes to CI/CD pipeline definitions
581+
* Dependency updates
582+
* Any changes that should be tested before merging

pkg/adapter/sinker.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package adapter
33
import (
44
"bytes"
55
"context"
6+
"fmt"
67
"net/http"
78

89
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
910
"github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction"
11+
"github.com/openshift-pipelines/pipelines-as-code/pkg/matcher"
1012
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
1113
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1214
"github.com/openshift-pipelines/pipelines-as-code/pkg/pipelineascode"
@@ -69,8 +71,98 @@ func (s *sinker) processEvent(ctx context.Context, request *http.Request) error
6971
if err := s.processEventPayload(ctx, request); err != nil {
7072
return err
7173
}
74+
75+
// For ALL events: Setup authenticated client early (including token scoping)
76+
// This centralizes client setup and token scoping in one place for all event types
77+
repo, err := s.findMatchingRepository(ctx)
78+
if err != nil {
79+
// Continue with normal flow - repository matching will be handled in matchRepoPR
80+
s.logger.Debugf("Could not find matching repository for early client setup: %v", err)
81+
} else {
82+
// We found the repository, now setup client with token scoping
83+
// If setup fails here, it's a configuration error and we should fail fast
84+
if err := s.setupClient(ctx, repo); err != nil {
85+
return fmt.Errorf("client setup failed: %w", err)
86+
}
87+
s.logger.Debugf("Client setup completed early in sinker for event type: %s", s.event.EventType)
88+
}
89+
90+
// For PUSH events: commit message is already in event.SHATitle from the webhook payload
91+
// We can check immediately without any API calls or repository lookups
92+
if s.event.EventType == "push" && provider.SkipCI(s.event.SHATitle) {
93+
s.logger.Infof("CI skipped for push event: commit %s contains skip command in message", s.event.SHA)
94+
return s.createSkipCIStatus(ctx)
95+
}
96+
97+
// For PULL REQUEST events: commit message needs to be fetched via API
98+
// Get commit info for skip-CI detection (only if we successfully set up client above)
99+
if s.event.EventType == "pull_request" && repo != nil {
100+
// Get commit info (including commit message) via API
101+
if err := s.vcx.GetCommitInfo(ctx, s.event); err != nil {
102+
return fmt.Errorf("could not get commit info: %w", err)
103+
}
104+
// Check for skip-ci commands in pull request events
105+
if s.event.HasSkipCommand {
106+
s.logger.Infof("CI skipped for pull request event: commit %s contains skip command in message", s.event.SHA)
107+
return s.createSkipCIStatus(ctx)
108+
}
109+
}
72110
}
73111

74112
p := pipelineascode.NewPacs(s.event, s.vcx, s.run, s.pacInfo, s.kint, s.logger, s.globalRepo)
75113
return p.Run(ctx)
76114
}
115+
116+
// findMatchingRepository finds the Repository CR that matches the event.
117+
// This is a lightweight lookup to get credentials for early skip-ci checks.
118+
// Uses the canonical matcher implementation to avoid code duplication.
119+
func (s *sinker) findMatchingRepository(ctx context.Context) (*v1alpha1.Repository, error) {
120+
// Use canonical matcher to find repository (empty string searches all namespaces)
121+
repo, err := matcher.MatchEventURLRepo(ctx, s.run, s.event, "")
122+
if err != nil {
123+
return nil, fmt.Errorf("failed to match repository: %w", err)
124+
}
125+
if repo == nil {
126+
return nil, fmt.Errorf("no repository found matching URL: %s", s.event.URL)
127+
}
128+
129+
return repo, nil
130+
}
131+
132+
// setupClient sets up the authenticated client with token scoping for ALL event types.
133+
// This is the primary location where client setup and GitHub App token scoping happens.
134+
// Centralizing this here ensures consistent behavior across all events and enables early
135+
// optimizations like skip-CI detection before expensive processing.
136+
func (s *sinker) setupClient(ctx context.Context, repo *v1alpha1.Repository) error {
137+
return pipelineascode.SetupAuthenticatedClient(
138+
ctx,
139+
s.vcx,
140+
s.kint,
141+
s.run,
142+
s.event,
143+
repo,
144+
s.globalRepo,
145+
s.pacInfo,
146+
s.logger,
147+
)
148+
}
149+
150+
// createSkipCIStatus creates a neutral status check on the git provider when CI is skipped.
151+
func (s *sinker) createSkipCIStatus(ctx context.Context) error {
152+
statusOpts := provider.StatusOpts{
153+
Status: "completed",
154+
Conclusion: "neutral",
155+
Title: "CI Skipped",
156+
Summary: fmt.Sprintf("%s - CI has been skipped", s.pacInfo.ApplicationName),
157+
Text: "Commit contains a skip CI command. Use /test or /retest to manually trigger CI if needed.",
158+
DetailsURL: s.run.Clients.ConsoleUI().URL(),
159+
}
160+
161+
if err := s.vcx.CreateStatus(ctx, s.event, statusOpts); err != nil {
162+
s.logger.Warnf("Failed to create skip-CI status: %v", err)
163+
// Don't return error - skip-CI should succeed even if status creation fails
164+
return nil
165+
}
166+
167+
return nil
168+
}

0 commit comments

Comments
 (0)