Skip to content

Commit 3947c53

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 269d2a7 commit 3947c53

File tree

23 files changed

+2204
-190
lines changed

23 files changed

+2204
-190
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/gitops_commands.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,89 @@ Pipelines-as-Code supports the concept of `GitOps commands`, which allow users t
99

1010
The advantage of using a `GitOps command` is that it provides a journal of all the executions of your pipeline directly on your Pull Request, near your code.
1111

12+
## Skip CI Commands
13+
14+
Pipelines-as-Code supports skip commands in commit messages that allow you to skip PipelineRun execution for specific commits. This is useful when making documentation changes, minor fixes, or work-in-progress commits where running the full CI pipeline is unnecessary.
15+
16+
### Supported Skip Commands
17+
18+
You can include any of the following commands anywhere in your commit message to skip PipelineRun execution:
19+
20+
- `[skip ci]` - Skip continuous integration
21+
- `[ci skip]` - Alternative format for skipping CI
22+
- `[skip tkn]` - Skip Tekton PipelineRuns
23+
- `[tkn skip]` - Alternative format for skipping Tekton
24+
25+
**Note:** Skip commands are **case-sensitive** and must be in lowercase with brackets.
26+
27+
### Example Usage
28+
29+
```text
30+
docs: update README with installation instructions [skip ci]
31+
```
32+
33+
or
34+
35+
```text
36+
WIP: refactor authentication module
37+
38+
This is still in progress and not ready for testing yet.
39+
40+
[ci skip]
41+
```
42+
43+
### How Skip Commands Work
44+
45+
When a commit message contains a skip command:
46+
47+
1. **Pull Requests**: No PipelineRuns will be created when the PR is opened or updated with that commit
48+
2. **Push Events**: No PipelineRuns will be created when pushing to a branch with that commit message
49+
50+
### GitOps Commands Override Skip CI
51+
52+
**Important:** Skip CI commands can be overridden by using GitOps commands. Even if a commit contains a skip command like `[skip ci]`, you can still manually trigger PipelineRuns using:
53+
54+
- `/test` - Trigger all matching PipelineRuns
55+
- `/test <pipelinerun-name>` - Trigger a specific PipelineRun
56+
- `/retest` - Retrigger failed PipelineRuns
57+
- `/retest <pipelinerun-name>` - Retrigger a specific PipelineRun
58+
- `/ok-to-test` - Allow running CI for external contributors
59+
60+
This allows you to skip automatic CI execution while still maintaining the ability to manually trigger builds when needed.
61+
62+
### Example: Skipping CI Then Manually Triggering
63+
64+
```bash
65+
# Initial commit with skip command
66+
git commit -m "docs: update contributing guide [skip ci]"
67+
git push origin my-feature-branch
68+
# No PipelineRuns are created automatically
69+
70+
# Later, you can manually trigger CI by commenting on the PR:
71+
# /test
72+
# This will create PipelineRuns despite the [skip ci] command
73+
```
74+
75+
### When to Use Skip Commands
76+
77+
Skip commands are useful for:
78+
79+
- Documentation-only changes
80+
- README updates
81+
- Comment or formatting changes
82+
- Work-in-progress commits
83+
- Minor typo fixes
84+
- Configuration file updates that don't affect code
85+
86+
### When NOT to Use Skip Commands
87+
88+
Avoid using skip commands for:
89+
90+
- Code changes that affect functionality
91+
- Changes to CI/CD pipeline definitions
92+
- Dependency updates
93+
- Any changes that should be tested before merging
94+
1295
## GitOps Commands on Pull Requests
1396

1497
For example, when you are on a Pull Request, you may want to restart failed PipelineRuns. To do so, you can add a comment on your Pull Request starting with `/retest`, and all **failed** PipelineRuns attached to that Pull Request will be restarted. If all previous PipelineRuns for the same commit were successful, no new PipelineRuns will be created to avoid unnecessary duplication.

pkg/adapter/sinker.go

Lines changed: 77 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,83 @@ 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 nil
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 nil
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+
// Merge with global repository if available
130+
if s.globalRepo != nil {
131+
repo.Spec.Merge(s.globalRepo.Spec)
132+
}
133+
134+
return repo, nil
135+
}
136+
137+
// setupClient sets up the authenticated client with token scoping for ALL event types.
138+
// This is the primary location where client setup and GitHub App token scoping happens.
139+
// Centralizing this here ensures consistent behavior across all events and enables early
140+
// optimizations like skip-CI detection before expensive processing.
141+
func (s *sinker) setupClient(ctx context.Context, repo *v1alpha1.Repository) error {
142+
return pipelineascode.SetupAuthenticatedClient(
143+
ctx,
144+
s.vcx,
145+
s.kint,
146+
s.run,
147+
s.event,
148+
repo,
149+
s.globalRepo,
150+
s.pacInfo,
151+
s.logger,
152+
)
153+
}

0 commit comments

Comments
 (0)