Skip to content
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
106 changes: 106 additions & 0 deletions website/content/en/docs/kubebuilder/references/event-filtering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!-- ---
title: Using Predicates for Event Filtering with Operator SDK
linkTitle: Using Predicates for Event Filtering
weight: 2
--- -->

[Events][doc_event] are produced by [Sources][doc_source] assigned to resources a controller is watching. These events are transformed into Requests by [EventHandlers][doc_eventhandler] and passed to `Reconcile()`. [Predicates][doc_predicate] allow controllers to filter events before they are provided to EventHandlers. Filtering is useful because your controller may only want to handle specific types of events. Filtering also helps reduce chattiness with the API server, as `Reconcile()` is only called for events transformed by EventHandlers.

## Predicate types

A Predicate implements the following methods that take an event of a particular type and return true if the event should be processed by `Reconcile()`:

```Go
// Predicate filters events before enqueuing the keys.
type Predicate interface {
Create(event.CreateEvent) bool
Delete(event.DeleteEvent) bool
Update(event.UpdateEvent) bool
Generic(event.GenericEvent) bool
}

// Funcs implements Predicate.
type Funcs struct {
CreateFunc func(event.CreateEvent) bool
DeleteFunc func(event.DeleteEvent) bool
UpdateFunc func(event.UpdateEvent) bool
GenericFunc func(event.GenericEvent) bool
}
```

For example, all Create events for any watched resource will be passed to `Funcs.Create()` and filtered out if the method evaluates to `false`. If you do not register a Predicate method for a particular type, events of that type will not be filtered.

All event types contain Kubernetes [metadata][doc_object_metadata] about the object that triggered the event, and the object itself. Predicate logic uses these data to make decisions about what should be filtered. Some event types include other fields pertaining to the semantics of that event. For example, `event.UpdateEvent` includes both old and new metadata and objects:

```Go
type UpdateEvent struct {
// MetaOld is the ObjectMeta of the Kubernetes Type that was updated (before the update).
MetaOld v1.Object

// ObjectOld is the object from the event.
ObjectOld runtime.Object

// MetaNew is the ObjectMeta of the Kubernetes Type that was updated (after the update).
MetaNew v1.Object

// ObjectNew is the object from the event.
ObjectNew runtime.Object
}
```

You can find all type definitions in the `event` package [documentation][doc_event].

## Using Predicates

Any number of Predicates can be set for a controller via the builder method `WithEventFilter()`, which will filter an event if any of those Predicates evaluates to `false`. This first example is an implementation of a `memcached-operator` controller that simply filters Delete events on Pods that have been confirmed deleted; the controller receives all Delete events that occur, and we may only care about resources that have not been completely deleted:

```Go
import (
"context"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"

cachev1alpha1 "github.com/example-inc/app-operator/apis/cache/v1alpha1"
)

...

func ignoreDeletionPredicate() predicate.Predicate {
return predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
// Ignore updates to CR status in which case metadata.Generation does not change
return e.MetaOld.GetGeneration() != e.MetaNew.GetGeneration()
},
DeleteFunc: func(e event.DeleteEvent) bool {
// Evaluates to false if the object has been confirmed deleted.
return !e.DeleteStateUnknown
},
}
}

func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cachev1alpha1.Memcached{}).
Owns(&corev1.Pod{}).
WithEventFilter(ignoreDeletionPredicate()).
Complete(r)
}
...
}
```

## Use cases

Predicates are not necessary for many operators, although filtering reduces the amount of chatter to the API server from `Reconcile()`. They are particularly useful for controllers that watch resources cluster-wide, i.e. without a namespace.

[doc_event]:https://godoc.org/sigs.k8s.io/controller-runtime/pkg/event
[doc_source]:https://godoc.org/sigs.k8s.io/controller-runtime/pkg/source#Source
[doc_eventhandler]:https://godoc.org/sigs.k8s.io/controller-runtime/pkg/handler#EventHandler
[doc_predicate]:https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/predicate
[doc_object_metadata]:https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Object
208 changes: 208 additions & 0 deletions website/content/en/docs/kubebuilder/references/markers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<!-- ---
title: API Markers
linkTitle: API Markers
weight: 5
--- -->

This document describes [code markers][markers] supported by the SDK.

## ClusterServiceVersion markers

This section details ClusterServiceVersion (CSV) [code markers][code-markers-design] and lists available markers.

**Note:** CSV markers can only be used in Go Operator projects. Annotations for Ansible and Helm Operator projects will be added in the future.

## Usage

All markers have a `+operator-sdk:gen-csv` prefix, denoting that they're parsed while executing [`operator-sdk generate csv`][generate-csv-cli].

### Paths

Paths are dot-separated string hierarchies with the above prefix that map to CSV [`spec`][csv-spec] field names.

Example: `+operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Pod Count"`

#### customresourcedefinitions

- `customresourcedefinitions`: child path token
- `displayName`: quoted string or string literal
- `resources`: quoted string or string literal, in the format `"kind,version,\"name\""` or `` `kind,version,"name"` ``, where `kind`, `version`, and `name` are fields in each CSV `resources` entry
- `specDescriptors`, `statusDescriptors`: bool, or child path token
- `displayName`: quoted string or string literal
- `x-descriptors`: quoted string or string literal comma-separated list of [`x-descriptor`][csv-x-desc] UI hints.

**NOTES**
- `specDescriptors` and `statusDescriptors` with a value of `true` is required for each field to be included in their respective `customresourcedefinitions` CSV fields. See the examples below.
- `customresourcedefinitions` top-level `kind`, `name`, and `version` fields are parsed from API code.
- All `description` fields are parsed from type declaration and `struct` type field comments.
- `path` is parsed out of a field's JSON tag and merged with parent field path's in dot-hierarchy notation.

### Examples

These examples assume `Memcached`, `MemcachedSpec`, and `MemcachedStatus` are the example projects' kind, spec, and status.

1. Set a display name for a `customresourcedefinitions` kind entry:

```go
// +operator-sdk:gen-csv:customresourcedefinitions.displayName="Memcached App"
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}
```

2. Set `displayName`, `path`, `x-descriptors`, and `description` on a field for a `customresourcedefinitions.specDescriptors` entry:

```go
type MemcachedSpec struct {
// Size is the size of the memcached deployment. <-- This will become Size's specDescriptors.description.
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.displayName="Pod Count"
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors.x-descriptors="urn:alm:descriptor:com.tectonic.ui:podCount,urn:alm:descriptor:io.kubernetes:custom"
Size int32 `json:"size"` // <-- Size's specDescriptors.path is inferred from this JSON tag.
}
```

3. Let the SDK infer all unmarked paths on a field for a `customresourcedefinitions.specDescriptors` entry:

```go
type MemcachedSpec struct {
// Size is the size of the memcached deployment.
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
Size int32 `json:"size"`
}
```

The SDK uses the `Size` fields' `json` tag name as `path`, `Size` as `displayName`, and field comments as `description`.

The SDK also checks `path` elements against a list of well-known path to x-descriptor string mappings and either uses a match as `x-descriptors`, or does not set `x-descriptors`. Supported mappings:

#### Spec x-descriptors

{{<table "table table-striped table-bordered">}}
| Path | x-descriptor |
|-----|-----|
| `size` | `urn:alm:descriptor:com.tectonic.ui:podCount` |
| `podCount` | `urn:alm:descriptor:com.tectonic.ui:podCount` |
| `endpoints` | `urn:alm:descriptor:com.tectonic.ui:endpointList` |
| `endpointList` | `urn:alm:descriptor:com.tectonic.ui:endpointList` |
| `label` | `urn:alm:descriptor:com.tectonic.ui:label` |
| `resources` | `urn:alm:descriptor:com.tectonic.ui:resourceRequirements` |
| `resourceRequirements` | `urn:alm:descriptor:com.tectonic.ui:resourceRequirements` |
| `selector` | `urn:alm:descriptor:com.tectonic.ui:selector:` |
| `namespaceSelector` | `urn:alm:descriptor:com.tectonic.ui:namespaceSelector` |
| none | `urn:alm:descriptor:io.kubernetes:` |
| `booleanSwitch` | `urn:alm:descriptor:com.tectonic.ui:booleanSwitch` |
| `password` | `urn:alm:descriptor:com.tectonic.ui:password` |
| `checkbox` | `urn:alm:descriptor:com.tectonic.ui:checkbox` |
| `imagePullPolicy` | `urn:alm:descriptor:com.tectonic.ui:imagePullPolicy` |
| `updateStrategy` | `urn:alm:descriptor:com.tectonic.ui:updateStrategy` |
| `text` | `urn:alm:descriptor:com.tectonic.ui:text` |
| `number` | `urn:alm:descriptor:com.tectonic.ui:number` |
| `nodeAffinity` | `urn:alm:descriptor:com.tectonic.ui:nodeAffinity` |
| `podAffinity` | `urn:alm:descriptor:com.tectonic.ui:podAffinity` |
| `podAntiAffinity` | `urn:alm:descriptor:com.tectonic.ui:podAntiAffinity` |
| none | `urn:alm:descriptor:com.tectonic.ui:fieldGroup:` |
| none | `urn:alm:descriptor:com.tectonic.ui:arrayFieldGroup:` |
| none | `urn:alm:descriptor:com.tectonic.ui:select:` |
| `advanced` | `urn:alm:descriptor:com.tectonic.ui:advanced` |
{{</table>}}

#### Status x-descriptors

{{<table "table table-striped table-bordered">}}
| Path | x-descriptor |
|-----|-----|
| `podStatuses` | `urn:alm:descriptor:com.tectonic.ui:podStatuses` |
| `size` | `urn:alm:descriptor:com.tectonic.ui:podCount` |
| `podCount` | `urn:alm:descriptor:com.tectonic.ui:podCount` |
| `link` | `urn:alm:descriptor:org.w3:link` |
| `w3link` | `urn:alm:descriptor:org.w3:link` |
| `conditions` | `urn:alm:descriptor:io.kubernetes.conditions` |
| `text` | `urn:alm:descriptor:text` |
| `prometheusEndpoint` | `urn:alm:descriptor:prometheusEndpoint` |
| `phase` | `urn:alm:descriptor:io.kubernetes.phase` |
| `k8sPhase` | `urn:alm:descriptor:io.kubernetes.phase` |
| `reason` | `urn:alm:descriptor:io.kubernetes.phase:reason` |
| `k8sReason` | `urn:alm:descriptor:io.kubernetes.phase:reason` |
| none | `urn:alm:descriptor:io.kubernetes:` |
{{</table>}}

**NOTE:** any x-descriptor that ends in `:` will not be inferred by `path` element, ex. `urn:alm:descriptor:io.kubernetes:`. Use the `x-descriptors` marker if you want to enable one for your type.

4. A comprehensive example:
- Infer `path`, `description`, `displayName`, and `x-descriptors` for `specDescriptors` and `statusDescriptors` entries.
- Create three `resources` entries each with `kind`, `version`, and `name` values.

```go
// Represents a cluster of Memcached apps
// +operator-sdk:gen-csv:customresourcedefinitions.displayName="Memcached App"
// +operator-sdk:gen-csv:customresourcedefinitions.resources="Deployment,v1,\"memcached-operator\""
// +operator-sdk:gen-csv:customresourcedefinitions.resources=`Service,v1,"memcached-operator"`
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}

type MemcachedSpec struct {
Pods MemcachedPods `json:"pods"`
}

type MemcachedStatus struct {
Pods MemcachedPods `json:"podStatuses"`
}

type MemcachedPods struct {
// Size is the size of the memcached deployment.
// +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true
// +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors=true
Size int32 `json:"size"`
}
```

The generated `customresourcedefinitions` will look like:

```yaml
customresourcedefinitions:
owned:
- description: Represents a cluster of Memcached apps
displayName: Memcached App
kind: Memcached
name: memcacheds.cache.example.com
version: v1alpha1
resources:
- kind: Deployment
name: A Kubernetes Deployment
version: v1
- kind: ReplicaSet
name: A Kubernetes ReplicaSet
version: v1beta2
- kind: Pod
name: A Kubernetes Pod
version: v1
specDescriptors:
- description: The desired number of member Pods for the deployment.
displayName: Size
path: pods.size
x-descriptors:
- 'urn:alm:descriptor:com.tectonic.ui:podCount'
statusDescriptors:
- description: The desired number of member Pods for the deployment.
displayName: Size
path: podStatuses.size
x-descriptors:
- 'urn:alm:descriptor:com.tectonic.ui:podStatuses'
- 'urn:alm:descriptor:com.tectonic.ui:podCount'
```

[markers]:https://pkg.go.dev/sigs.k8s.io/controller-tools/pkg/markers
[code-markers-design]:https://github.com/operator-framework/operator-sdk/blob/master/proposals/sdk-code-annotations.md
[generate-csv-cli]:/docs/cli/operator-sdk_generate_csv
[csv-x-desc]:https://github.com/openshift/console/blob/feabd61/frontend/packages/operator-lifecycle-manager/src/components/descriptors/types.ts#L3-L39
[csv-spec]:https://github.com/operator-framework/operator-lifecycle-manager/blob/e0eea22/doc/design/building-your-csv.md