Skip to content

Commit

Permalink
feat: annotation for matching PipelineRun on paths
Browse files Browse the repository at this point in the history
Introduce easier-to-use annotations for matching PipelineRuns by file
path changes:
- `on-path-changed`: Matches PipelineRun if specified paths have
  changes.
- `on-path-changed-ignore`: Matches PipelineRun if specified paths **do
  not** have changes.

Examples:
1. `on-path-changed: ["pkg/*", "cli/*"]` matches if files in `pkg` or
   `cli` changed.
2. `on-path-changed-ignore: ["docs/**"]` matches if no changes occurred
   in the `docs` directory.

Annotations can be combined for more specific use cases:
- `on-path-changed: ["docs/**"]`
- `on-path-changed-ignore: ["docs/generated/**"]`

This setup triggers a PipelineRun when there are changes in the `docs`
directory, except for files under `docs/generated`.

Enhanced annotation options also support:
- Targeting specific events (`on-target-event`: e.g., `pull_request`,
  `push`).
- Matching specific branches (`on-target-branch`: e.g., `main`).

This improves usability over existing CEL-based configuration and makes
defining file-based triggers more intuitive.

**Jira**: https://issues.redhat.com/browse/SRVKP-6464

Signed-off-by: Chmouel Boudjnah <[email protected]>
  • Loading branch information
chmouel committed Nov 21, 2024
1 parent b13ca43 commit eb50fac
Show file tree
Hide file tree
Showing 8 changed files with 575 additions and 37 deletions.
94 changes: 94 additions & 0 deletions docs/content/docs/guide/authoringprs.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,100 @@ too, it will only be matched when a `Pull Request` is opened or updated or on a
`Push` to a branch
{{< /hint >}}

### Matching a PipelineRun to Specific Path Changes

{{< tech_preview "Matching a PipelineRun to specific path changes via annotation" >}}

To trigger a `PipelineRun` based on specific path changes in an event, use the
annotation `pipelinesascode.tekton.dev/on-path-change`.

Multiple paths can be specified, separated by commas. The first glob matching
the files changes in the PR will trigger the `PipelineRun`.

You still need to specify the event type and target branch. If you have a [CEL
expression](#matching-pipelinerun-by-path-change) the `on-path-change`
annotation will be ignored

Example:

```yaml
metadata:
name: pipeline-docs-and-manual
annotations:
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-path-change: "[docs/***.md, manual/***.rst]"
```

This configuration will match and trigger the `PipelineRun` named
`pipeline-docs-and-manual` when a `pull_request` event targets the `main` branch
and includes changes to files with a `.md` suffix in the `docs` directory (and
its subdirectories) or files with a `.rst` suffix in the `manual` directory.

{{< hint info >}}
The patterns used are [glob](https://en.wikipedia.org/wiki/Glob_(programming))
patterns, not regexp. Here are some
[examples](https://github.com/gobwas/glob?tab=readme-ov-file#example) from the
library used for matching.
{{< /hint >}}

### Matching a PipelineRun by Ignoring Specific Path Changes

{{< tech_preview "Matching a PipelineRun to ignore specific path changes via annotation" >}}

Following the same principle as the `on-path-change` annotation, you can use the
reverse annotation `pipelinesascode.tekton.dev/on-path-change-ignore` to trigger
a `PipelineRun` when the specified paths have not changed.

You still need to specify the event type and target branch. If you have a [CEL
expression](#matching-pipelinerun-by-path-change) the `on-path-change-ignore`
annotation will be ignored

This example triggers a `PipelineRun` when there are no changes in the `docs`
directory:

```yaml
metadata:
name: pipeline-not-on-docs-change
annotations:
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-path-change-ignore: "[docs/***]"
```

Furthermore, you can combine `on-path-change` and `on-path-change-ignore`
annotations:

```yaml
metadata:
name: pipeline-docs-not-generated
annotations:
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-path-change: "[docs/***]"
pipelinesascode.tekton.dev/on-path-change-ignore: "[docs/generated/***]"
```

This configuration triggers the `PipelineRun` when there are changes in the
`docs` directory but not in the `docs/generated` directory.

The `on-path-change-ignore` annotation will always take precedence over the
`on-path-change` annotation, It means if you have these annotations:

```yaml
metadata:
name: pipelinerun-go-only-no-markdown-or-yaml
pipelinesascode.tekton.dev/on-target-branch: "[main]"
pipelinesascode.tekton.dev/on-event: "[pull_request]"
pipelinesascode.tekton.dev/on-path-change: "[***.go]"
pipelinesascode.tekton.dev/on-path-change-ignore: "[***.md, ***.yaml]"
```

and you have a `Pull Request` changing the files `.tekton/pipelinerun.yaml`,
`README.md`, and `main.go` the `PipelineRun` will not be triggered since the
`on-path-change-ignore` annotation will ignore the `***.md` and `***.yaml`
files.

## Advanced event matching

If you need to do some advanced matching, `Pipelines-as-Code` supports CEL
Expand Down
70 changes: 36 additions & 34 deletions pkg/apis/pipelinesascode/keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,42 @@ import (
)

const (
ControllerInfo = pipelinesascode.GroupName + "/controller-info"
Task = pipelinesascode.GroupName + "/task"
Pipeline = pipelinesascode.GroupName + "/pipeline"
URLOrg = pipelinesascode.GroupName + "/url-org"
URLRepository = pipelinesascode.GroupName + "/url-repository"
SHA = pipelinesascode.GroupName + "/sha"
Sender = pipelinesascode.GroupName + "/sender"
EventType = pipelinesascode.GroupName + "/event-type"
Branch = pipelinesascode.GroupName + "/branch"
SourceBranch = pipelinesascode.GroupName + "/source-branch"
Repository = pipelinesascode.GroupName + "/repository"
GitProvider = pipelinesascode.GroupName + "/git-provider"
State = pipelinesascode.GroupName + "/state"
ShaTitle = pipelinesascode.GroupName + "/sha-title"
ShaURL = pipelinesascode.GroupName + "/sha-url"
RepoURL = pipelinesascode.GroupName + "/repo-url"
SourceRepoURL = pipelinesascode.GroupName + "/source-repo-url"
PullRequest = pipelinesascode.GroupName + "/pull-request"
InstallationID = pipelinesascode.GroupName + "/installation-id"
GHEURL = pipelinesascode.GroupName + "/ghe-url"
SourceProjectID = pipelinesascode.GroupName + "/source-project-id"
TargetProjectID = pipelinesascode.GroupName + "/target-project-id"
OriginalPRName = pipelinesascode.GroupName + "/original-prname"
GitAuthSecret = pipelinesascode.GroupName + "/git-auth-secret"
CheckRunID = pipelinesascode.GroupName + "/check-run-id"
OnEvent = pipelinesascode.GroupName + "/on-event"
OnComment = pipelinesascode.GroupName + "/on-comment"
OnTargetBranch = pipelinesascode.GroupName + "/on-target-branch"
OnCelExpression = pipelinesascode.GroupName + "/on-cel-expression"
TargetNamespace = pipelinesascode.GroupName + "/target-namespace"
MaxKeepRuns = pipelinesascode.GroupName + "/max-keep-runs"
CancelInProgress = pipelinesascode.GroupName + "/cancel-in-progress"
LogURL = pipelinesascode.GroupName + "/log-url"
ExecutionOrder = pipelinesascode.GroupName + "/execution-order"
ControllerInfo = pipelinesascode.GroupName + "/controller-info"
Task = pipelinesascode.GroupName + "/task"
Pipeline = pipelinesascode.GroupName + "/pipeline"
URLOrg = pipelinesascode.GroupName + "/url-org"
URLRepository = pipelinesascode.GroupName + "/url-repository"
SHA = pipelinesascode.GroupName + "/sha"
Sender = pipelinesascode.GroupName + "/sender"
EventType = pipelinesascode.GroupName + "/event-type"
Branch = pipelinesascode.GroupName + "/branch"
SourceBranch = pipelinesascode.GroupName + "/source-branch"
Repository = pipelinesascode.GroupName + "/repository"
GitProvider = pipelinesascode.GroupName + "/git-provider"
State = pipelinesascode.GroupName + "/state"
ShaTitle = pipelinesascode.GroupName + "/sha-title"
ShaURL = pipelinesascode.GroupName + "/sha-url"
RepoURL = pipelinesascode.GroupName + "/repo-url"
SourceRepoURL = pipelinesascode.GroupName + "/source-repo-url"
PullRequest = pipelinesascode.GroupName + "/pull-request"
InstallationID = pipelinesascode.GroupName + "/installation-id"
GHEURL = pipelinesascode.GroupName + "/ghe-url"
SourceProjectID = pipelinesascode.GroupName + "/source-project-id"
TargetProjectID = pipelinesascode.GroupName + "/target-project-id"
OriginalPRName = pipelinesascode.GroupName + "/original-prname"
GitAuthSecret = pipelinesascode.GroupName + "/git-auth-secret"
CheckRunID = pipelinesascode.GroupName + "/check-run-id"
OnEvent = pipelinesascode.GroupName + "/on-event"
OnComment = pipelinesascode.GroupName + "/on-comment"
OnTargetBranch = pipelinesascode.GroupName + "/on-target-branch"
OnPathChange = pipelinesascode.GroupName + "/on-path-change"
OnPathChangeIgnore = pipelinesascode.GroupName + "/on-path-change-ignore"
OnCelExpression = pipelinesascode.GroupName + "/on-cel-expression"
TargetNamespace = pipelinesascode.GroupName + "/target-namespace"
MaxKeepRuns = pipelinesascode.GroupName + "/max-keep-runs"
CancelInProgress = pipelinesascode.GroupName + "/cancel-in-progress"
LogURL = pipelinesascode.GroupName + "/log-url"
ExecutionOrder = pipelinesascode.GroupName + "/execution-order"
// PublicGithubAPIURL default is "https://api.github.com" but it can be overridden by X-GitHub-Enterprise-Host header.
PublicGithubAPIURL = "https://api.github.com"
GithubApplicationID = "github-application-id"
Expand Down
40 changes: 40 additions & 0 deletions pkg/matcher/annotation_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,46 @@ func MatchPipelinerunByAnnotation(ctx context.Context, logger *zap.SugaredLogger
}
prMatch.Config["target-branch"] = targetBranch
prMatch.Config["target-event"] = targetEvent

if key, ok := prun.GetObjectMeta().GetAnnotations()[keys.OnPathChange]; ok {
changedFiles, err := vcx.GetFiles(ctx, event)
if err != nil {
logger.Errorf("error getting changed files: %v", err)
continue
}
// // TODO(chmou): we use the matchOnAnnotation function, it's
// really made to match git branches but we can still use it for
// our own path changes. we may split up if needed to refine.
matched, err := matchOnAnnotation(key, changedFiles.All, true)
if err != nil {
return matchedPRs, err
}
if !matched {
continue
}
logger.Infof("Matched pipelinerun with name: %s, annotation PathChange: %q", prName, key)
prMatch.Config["path-change"] = key
}

if key, ok := prun.GetObjectMeta().GetAnnotations()[keys.OnPathChangeIgnore]; ok {
changedFiles, err := vcx.GetFiles(ctx, event)
if err != nil {
logger.Errorf("error getting changed files: %v", err)
continue
}
// // TODO(chmou): we use the matchOnAnnotation function, it's
// really made to match git branches but we can still use it for
// our own path changes. we may split up if needed to refine.
matched, err := matchOnAnnotation(key, changedFiles.All, true)
if err != nil {
return matchedPRs, err
}
if matched {
logger.Infof("Skipping pipelinerun with name: %s, annotation PathChangeIgnore: %q", prName, key)
continue
}
prMatch.Config["path-change-ignore"] = key
}
}

logger.Infof("matched pipelinerun with name: %s, annotation Config: %q", prName, prMatch.Config)
Expand Down
Loading

0 comments on commit eb50fac

Please sign in to comment.