Skip to content

Commit

Permalink
[TEP-0137] Add events config map
Browse files Browse the repository at this point in the history
Add a new "events" config map. The config map supersedes the
cloudevent settings from the "defaults" config map. The current
settings is deprecated but still honoured, so the change is
transparent for existing users. They shall migrate to the
new settings before the old one is removed.

This change is in preparation for TEP-0137, where new format
of events will be introduced, and configured via the new events
configuration map.

The current config map is only read from within the events
package, so the change to the logic is localised there, it only
propagates into tests.

Signed-off-by: Andrea Frittoli <andrea.frittoli@uk.ibm.com>
  • Loading branch information
afrittoli committed Jun 27, 2023
1 parent 9586306 commit b3ec85b
Show file tree
Hide file tree
Showing 17 changed files with 779 additions and 32 deletions.
49 changes: 49 additions & 0 deletions config/config-events.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2023 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: ConfigMap
metadata:
name: config-events
namespace: tekton-pipelines
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pipelines
data:
_example: |
################################
# #
# EXAMPLE CONFIGURATION #
# #
################################
# This block is not actually functional configuration,
# but serves to illustrate the available configuration
# options and document them in a way that is accessible
# to users that `kubectl edit` this config map.
#
# These sample configuration options may be copied out of
# this example block and unindented to be in the data block
# to actually change the configuration.
# formats contains a comma seperated list of event formats to be used
# the only format supported today is "tektonv1". An empty string is not
# a valid configuration. To disable events, do not specify the sink.
formats: "tektonv1"
# sink contains the event sink to be used for TaskRun, PipelineRun and
# CustomRun. If no sink is specified, no CloudEvent is generated.
# This setting supercedes the "default-cloud-events-sink" from the
# "config-defaults" config map
sink: ""
21 changes: 19 additions & 2 deletions docs/additional-configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,26 @@ namespace:
## Configuring CloudEvents notifications

When configured so, Tekton can generate `CloudEvents` for `TaskRun`,
`PipelineRun` and `Run`lifecycle events. The main configuration parameter is the
`PipelineRun` and `CustomRun`lifecycle events. The main configuration parameter is the
URL of the sink. When not set, no notification is generated.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-events
namespace: tekton-pipelines
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pipelines
data:
formats: tektonv1
sink: https://my-sink-url
```
The sink used to be configured in the `config-defaults` config map.
This option is still available, but deprecated, and will be removed.

```yaml
apiVersion: v1
kind: ConfigMap
Expand All @@ -69,7 +86,7 @@ data:
default-cloud-events-sink: https://my-sink-url
```

Additionally, CloudEvents for `Runs` require an extra configuration to be
Additionally, CloudEvents for `CustomRuns` require an extra configuration to be
enabled. This setting exists to avoid collisions with CloudEvents that might
be sent by custom task controllers:

Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type Defaults struct {
DefaultManagedByLabelValue string
DefaultPodTemplate *pod.Template
DefaultAAPodTemplate *pod.AffinityAssistantTemplate
DefaultCloudEventsSink string
DefaultCloudEventsSink string // Deprecated. Use the events package instead
DefaultTaskRunWorkspaceBinding string
DefaultMaxMatrixCombinationsCount int
DefaultForbiddenEnv []string
Expand Down
186 changes: 186 additions & 0 deletions pkg/apis/config/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
Copyright 2023 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"fmt"
"os"
"sort"
"strings"

corev1 "k8s.io/api/core/v1"
)

const (
// FormatTektonV1 represents the "v1" events in Tekton custom format
FormatTektonV1 EventFormat = "tektonv1"

// DefaultSink is the default value for "sink"
DefaultSink = ""

formatsKey = "formats"
sinkKey = "sink"
)

var (
// Note(afrittoli): only one valid format for now, more to come
validFormats = EventFormats{FormatTektonV1: struct{}{}}

// DefaultFormat is the default value for "formats"
DefaultFormats = EventFormats{FormatTektonV1: struct{}{}}

// DefaultConfig holds all the default configurations for the config.
DefaultEvents, _ = NewEventsFromMap(map[string]string{})
)

// Events holds the events configurations
// +k8s:deepcopy-gen=true
//
//nolint:musttag
type Events struct {
Sink string
Formats EventFormats
}

// EventFormat is a single event format
type EventFormat string

// EventFormats is a set of event formats
type EventFormats map[EventFormat]struct{}

// String is a string representation of an EventFormat
func (ef EventFormat) String() string {
return string(ef)
}

// IsValid returns true is the EventFormat one of the valid ones
func (ef EventFormat) IsValid() bool {
_, ok := validFormats[ef]
return ok
}

// String is a string represenation of an EventFormats
func (efs EventFormats) String() string {
// Make an array of map keys
keys := make([]string, len(efs))

i := 0
for k := range efs {
keys[i] = k.String()
i++
}
// Sorting helps with testing
sort.Strings(keys)

// Build a comma separated list
return strings.Join(keys[:], ",")
}

// Equals defines identity between EventFormats
func (efs EventFormats) Equals(other EventFormats) bool {
if len(efs) != len(other) {
return false
}
for key := range efs {
if _, ok := other[key]; !ok {
return false
}
}
return true
}

// ParseEventFormats converts a comma separated list into a EventFormats set
func ParseEventFormats(formats string) (EventFormats, error) {
// An empty string is not a valid configuration
if formats == "" {
return EventFormats{}, fmt.Errorf("formats cannot be empty")
}
stringFormats := strings.Split(formats, ",")
var eventFormats EventFormats = make(map[EventFormat]struct{}, len(stringFormats))
for _, format := range stringFormats {
if !EventFormat(format).IsValid() {
return EventFormats{}, fmt.Errorf("invalid format: %s", format)
}
// If already in the map (duplicate), fail
if _, ok := eventFormats[EventFormat(format)]; ok {
return EventFormats{}, fmt.Errorf("duplicate format: %s", format)
}
eventFormats[EventFormat(format)] = struct{}{}
}
return eventFormats, nil
}

// GetEventsConfigName returns the name of the configmap containing all
// feature flags.
func GetEventsConfigName() string {
if e := os.Getenv("CONFIG_EVENTS_NAME"); e != "" {
return e
}
return "config-events"
}

// NewEventsFromMap returns a Config given a map corresponding to a ConfigMap
func NewEventsFromMap(cfgMap map[string]string) (*Events, error) {
// for any string field with no extra validation
setField := func(key string, defaultValue string, field *string) {
if cfg, ok := cfgMap[key]; ok {
*field = cfg
} else {
*field = defaultValue
}
}

events := Events{}
err := setFormats(cfgMap, DefaultFormats, &events.Formats)
if err != nil {
return nil, err
}
setField(sinkKey, DefaultSink, &events.Sink)
return &events, nil
}

func setFormats(cfgMap map[string]string, defaultValue EventFormats, field *EventFormats) error {
value := defaultValue
if cfg, ok := cfgMap[formatsKey]; ok {
v, err := ParseEventFormats(cfg)
if err != nil {
return err
}
value = v
}
*field = value
return nil
}

// NewEventsFromConfigMap returns a Config for the given configmap
func NewEventsFromConfigMap(config *corev1.ConfigMap) (*Events, error) {
return NewEventsFromMap(config.Data)
}

// Equals returns true if two Configs are identical
func (cfg *Events) Equals(other *Events) bool {
if cfg == nil && other == nil {
return true
}

if cfg == nil || other == nil {
return false
}

return other.Sink == cfg.Sink &&
other.Formats.Equals(cfg.Formats)
}
Loading

0 comments on commit b3ec85b

Please sign in to comment.