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

[TEP-0137] Add events config map #6883

Merged
merged 1 commit into from
Jul 3, 2023
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
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: "https://events.sink/cdevents"
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.
Copy link
Member

Choose a reason for hiding this comment

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

could you add this to the deprecations table?

Copy link
Member Author

@afrittoli afrittoli Jun 28, 2023

Choose a reason for hiding this comment

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

I definitely will. I was a bit put off by the fact that I need to put things like date and release number in there, but I can start with a basic entry and update it once things are merged and released

Copy link
Member Author

Choose a reason for hiding this comment

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

Done - deprecating a config parameter in favour of a new one that provides the same functionality is not really covered (or in the scope of) the API compatibility policy. Since the config option has been around for a while and there's no real extra maintenance burden in keeping it around, I marked it for removal in 9 months


```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 docs/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The following features are deprecated but have not yet been removed.
| [ClusterTask is deprecated](https://github.com/tektoncd/pipeline/issues/4476) | v0.41.0 | Beta | July 13, 2023 |
| [`pipelineRef.bundle` and `taskRef.bundle` are deprecated](https://github.com/tektoncd/pipeline/issues/5514) | v0.41.0 | Alpha | July 13, 2023 |
| [The `config-trusted-resources` configMap is deprecated](https://github.com/tektoncd/pipeline/issues/5852) | v0.45.0 | Alpha | v0.46.0 |

| [The `default-cloud-events-sink` setting in the `config-defaults` configMap is deprecated](https://github.com/tektoncd/pipeline/pull/6883) in favour of the new `config-events` configMap. | v0.50.0 | N/A | v0.59.0 |
## Removed features

The features listed below have been removed but may still be supported in releases that have not reached their EOL.
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
185 changes: 185 additions & 0 deletions pkg/apis/config/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
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 (
// TODO(afrittoli): only one valid format for now, more to come
// See TEP-0137 https://github.com/tektoncd/community/pull/1028
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
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{}
Copy link
Member

Choose a reason for hiding this comment

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

nit: could https://pkg.go.dev/k8s.io/[email protected]/pkg/util/sets#Set be used here? It looks like the main thing missing is just a string representation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I'm pretty sure we could use that.
Since I use very little of the Set functionality I thought it was not needed, but it would at least save me the need for the Equals method.

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried a bit, but go stuck trying to wrap my own type around the Set.
I've check Set implementation and it's a map[T]Empty where Empty is struct{} - so identically to what I have except for the "generics" bit, which is not needed here, so I think I'll stick to the current implementation this time.


// 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 representation 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