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

Feature: TriggerGroups #1232

Merged
Merged
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
2 changes: 1 addition & 1 deletion cmd/triggerrun/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func processTriggerSpec(kubeClient kubernetes.Interface, client triggersclientse

log := eventLog.With(zap.String(triggers.TriggerLabelKey, r.EventListenerName))

finalPayload, header, iresp, err := r.ExecuteInterceptors(*tri, request, body, log, eventID)
finalPayload, header, iresp, err := r.ExecuteTriggerInterceptors(*tri, request, body, log, eventID, map[string]interface{}{})
if err != nil {
log.Error(err)
return nil, err
Expand Down
85 changes: 85 additions & 0 deletions docs/eventlisteners.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ or more [`Interceptors`](./interceptors.md).
- [Structure of an `EventListener`](#structure-of-an-eventlistener)
- [Specifying the Kubernetes service account](#specifiying-the-kubernetes-service-account)
- [Specifying `Triggers`](#specifying-triggers)
- [Specifying `TriggerGroups`](#specifying-trigger-groups)
- [Specifying `Resources`](#specifying-resources)
- [Specifying a `kubernetesResource` object](#specifying-a-kubernetesresource-object)
- [Specifying `Replicas`](#specifying-replicas)
Expand Down Expand Up @@ -166,6 +167,90 @@ rules:
verbs: ["impersonate"]
```

## Specifying `TriggerGroups`

`TriggerGroups` is a feature that allows you to specify a set of interceptors that will process before a set of
`Trigger` resources are processed by the eventlistener. The goal of this feature is described in
[TEP-0053](https://github.com/tektoncd/community/blob/main/teps/0053-nested-triggers.md). `TriggerGroups` allow for
a common set of interceptors to be defined inline in the `EventListenerSpec` before `Triggers` are invoked.

`TriggerGroups` is currently an `alpha` feature. To use it, you use use the v1beta1 API version with the
`enable-api-fields` [feature flag set to `alpha`](./install.md#Customizing-the-Triggers-Controller-behavior).

You can optionally specify one or more `Triggers` that define the actions to take when the `EventListener` detects a qualifying event. You can specify *either* a reference to an
external `Trigger` object *or* reference/define the `TriggerBindings`, `TriggerTemplates`, and `Interceptors` in the `Trigger` definition. A `TriggerGroup` definition specifies the following fields:

- `name` - (optional) a valid [Kubernetes name](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set) that uniquely identifies the `TriggerGroup`
- `interceptors` - a list of [`Interceptors`](#specifying-interceptors) that will process event payload data before passing it to the downstream `Triggers`
- `triggerSelector` - a combination of a Kubernetes `labelSelector` and a `namespaceSelector` as defined later [in this document](#constraining-eventlisteners-to-specific-namespaces). These two fields work together to define the `Triggers` that will be processed once `Interceptors` processing completes.

Below is an example EventListener that defines an inline `triggerGroup`:

```yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: eventlistener
spec:
triggerGroups:
- name: github-pr-group
interceptors:
- name: "validate GitHub payload and filter on eventType"
ref:
name: "github"
params:
- name: "secretRef"
value:
secretName: github-secret
secretKey: secretToken
- name: "eventTypes"
value: ["pull_request"]
triggerSelector:
labelSelector:
matchLabels:
type: github-pr
```

This configuration would first process any event that is sent to the `EventListener` and determine if it matches
the outlined conditions. If it passes these conditions, it will use the `triggerSelector` matching criteria to determine
the target `Trigger` resources to continue processing.

Any `extensions` fields added during `triggerGroup` processing are passed to the downstream `Trigger` execution. This allows
for shared data across all Triggers that are processed after group execution completes. As an example, `extensions.myfield` would
be available to all `Trigger` resources matched by this group:

```yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: eventlistener
spec:
triggerGroups:
- name: cel-filter-group
interceptors:
- name: "validate body and add field"
ref:
name: "cel"
params:
- name: "filter"
value: "body.action in ['opened', 'reopened']"
- name: "overlays"
value:
- key: myfield
expression: "body.pull_request.head.sha.truncate(7)"
triggerSelector:
namespaceSelector:
matchNames:
- foo
labelSelector:
matchLabels:
type: cel-preprocessed
```

At this time, each `TriggerGroup` determines its own downstream Triggers, so if two separate groups select the same
downstream `Trigger` resources, it may be executed multiple times. If you use this feature, ensure that `Trigger` resources
are labeled to be queried by the appropriate set of `TriggerGroups`.

## Specifying `Resources`

You can optionally customize the sink deployment for your `EventListener` using the `resources` field. It accepts the following types of objects:
Expand Down
22 changes: 22 additions & 0 deletions examples/v1beta1/triggergroups/eventlistener-triggergroup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: triggers.tekton.dev/v1beta1
dibyom marked this conversation as resolved.
Show resolved Hide resolved
kind: EventListener
metadata:
name: listener-triggergroup
spec:
serviceAccountName: tekton-triggers-example-sa
triggerGroups:
- name: github-pr
interceptors:
- ref:
name: "cel"
params:
- name: "filter"
value: "header.match('X-GitHub-Event', 'pull_request')"
- name: "overlays"
value:
- key: truncated_sha
expression: "body.pull_request.head.sha.truncate(7)"
triggerSelector:
labelSelector:
matchLabels:
type: github-pr
1 change: 1 addition & 0 deletions examples/v1beta1/triggergroups/rbac.yaml
55 changes: 55 additions & 0 deletions examples/v1beta1/triggergroups/trigger.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
apiVersion: triggers.tekton.dev/v1beta1
kind: Trigger
metadata:
name: trigger
labels:
type: github-pr
spec:
bindings:
- name: gitrevision
value: $(extensions.truncated_sha)
- name: gitrepositoryurl
value: $(body.repository.url)
- name: contenttype
value: $(header.Content-Type)
template:
ref: pipeline-template
---
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: pipeline-template
spec:
params:
- name: gitrevision
description: The git revision
default: main
- name: gitrepositoryurl
description: The git repository url
- name: message
description: The message to print
default: This is the default message
- name: contenttype
description: The Content-Type of the event
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: simple-pipeline-run-
spec:
pipelineRef:
name: simple-pipeline
params:
- name: message
value: $(tt.params.message)
- name: contenttype
value: $(tt.params.contenttype)
resources:
- name: git-source
resourceSpec:
type: git
params:
- name: revision
value: $(tt.params.gitrevision)
- name: url
value: $(tt.params.gitrepositoryurl)
3 changes: 3 additions & 0 deletions pkg/apis/triggers/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ const (

// TriggerLabelKey is used as the label identifier for a Trigger
TriggerLabelKey = "/trigger"

// TriggerGroupLabelKey is used as a label identifier for a TriggerGroup
TriggerGroupLabelKey = "/triggergroup"
)
23 changes: 19 additions & 4 deletions pkg/apis/triggers/v1beta1/event_listener_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ var _ kmeta.OwnerRefable = (*EventListener)(nil)
type EventListenerSpec struct {
ServiceAccountName string `json:"serviceAccountName,omitempty"`
Triggers []EventListenerTrigger `json:"triggers"`
NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
Resources Resources `json:"resources,omitempty"`
// Trigger groups allow for centralized processing of an interceptor chain
TriggerGroups []EventListenerTriggerGroup `json:"triggerGroups"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to update the JSON schema bundled with the IntelliJ and VSCode tooling but realized both Triggers and TriggerGroups fields are required. Shouldn't we add the omitempty annotation ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup we should - they aren't both required in the sense that the validation will both are not provided.

NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
Resources Resources `json:"resources,omitempty"`
}

type Resources struct {
Expand Down Expand Up @@ -112,6 +114,19 @@ type EventListenerTrigger struct {
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

// EventListenerTriggerGroup defines a group of Triggers that share a common set of interceptors
type EventListenerTriggerGroup struct {
Name string `json:"name"`
Interceptors []*TriggerInterceptor `json:"interceptors"`
TriggerSelector EventListenerTriggerSelector `json:"triggerSelector"`
}

// EventListenerTriggerSelector defines ways to select a group of triggers using their metadata
type EventListenerTriggerSelector struct {
NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
}

// EventInterceptor provides a hook to intercept and pre-process events
type EventInterceptor = TriggerInterceptor

Expand All @@ -123,7 +138,7 @@ type SecretRef struct {
SecretName string `json:"secretName,omitempty"`
}

// EventListenerBinding refers to a particular TriggerBinding or ClusterTriggerBindingresource.
// EventListenerBinding refers to a particular TriggerBinding or ClusterTriggerBinding resource.
type EventListenerBinding = TriggerSpecBinding

// EventListenerTemplate refers to a particular TriggerTemplate resource.
Expand Down
23 changes: 23 additions & 0 deletions pkg/apis/triggers/v1beta1/event_listener_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"encoding/json"
"fmt"

"github.com/tektoncd/triggers/pkg/apis/config"

"github.com/tektoncd/triggers/pkg/apis/triggers"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
Expand Down Expand Up @@ -72,6 +74,27 @@ func (s *EventListenerSpec) validate(ctx context.Context) (errs *apis.FieldError
if s.Resources.CustomResource != nil {
errs = errs.Also(validateCustomObject(s.Resources.CustomResource).ViaField("spec.resources.customResource"))
}

if len(s.TriggerGroups) > 0 {
dibyom marked this conversation as resolved.
Show resolved Hide resolved
err := ValidateEnabledAPIFields(ctx, "spec.triggerGroups", config.AlphaAPIFieldValue)
if err != nil {
errs = errs.Also(err)
} else {
for i, group := range s.TriggerGroups {
errs = errs.Also(group.validate(ctx).ViaField(fmt.Sprintf("spec.triggerGroups[%d]", i)))
}
}
}
return errs
}

func (g *EventListenerTriggerGroup) validate(ctx context.Context) (errs *apis.FieldError) {
if g.TriggerSelector.LabelSelector == nil && len(g.TriggerSelector.NamespaceSelector.MatchNames) == 0 {
errs = errs.Also(apis.ErrMissingOneOf("triggerSelector.labelSelector", "triggerSelector.namespaceSelector"))
}
if len(g.Interceptors) == 0 {
errs = errs.Also(apis.ErrMissingField("interceptors"))
}
return errs
}

Expand Down
Loading