Skip to content

Commit

Permalink
Support multinamespace informer filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
jkhelil committed Jul 3, 2024
1 parent 4929c41 commit 61025e6
Show file tree
Hide file tree
Showing 8 changed files with 372 additions and 90 deletions.
21 changes: 17 additions & 4 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ package main

import (
"flag"
"fmt"
"regexp"
"strings"

"github.com/tektoncd/chains/pkg/reconciler/pipelinerun"
"github.com/tektoncd/chains/pkg/reconciler/taskrun"
Expand All @@ -41,7 +44,18 @@ import (

func main() {
flag.IntVar(&controller.DefaultThreadsPerController, "threads-per-controller", controller.DefaultThreadsPerController, "Threads (goroutines) to create per controller")
namespace := flag.String("namespace", "", "Namespace to restrict informer to. Optional, defaults to all namespaces.")
namespaceList := flag.String("namespaces", "", "Comma-separated list of namespaces to restrict informer to. Optional, if empty defaults to all namespaces.")
flag.Parse()

var namespaces []string
if *namespaceList != "" {
// Remove any whitespace from the namespaces string
space := regexp.MustCompile(`\s+`)
namespaces = strings.Split(space.ReplaceAllString(*namespaceList, ""), ",")
fmt.Printf("controller is scopped to the following namespaces: %s\n", namespaces)
} else {
namespaces = nil // Default to all namespaces if the list is empty
}

// This also calls flag.Parse().
cfg := injection.ParseAndGetRESTConfigOrDie()
Expand All @@ -57,8 +71,7 @@ func main() {
cfg.QPS = 2 * cfg.QPS
cfg.Burst = 2 * cfg.Burst

flag.Parse()
ctx := injection.WithNamespaceScope(signals.NewContext(), *namespace)
ctx := injection.WithNamespaceScope(signals.NewContext(), "")

sharedmain.MainWithConfig(ctx, "watcher", cfg, taskrun.NewController, pipelinerun.NewController)
sharedmain.MainWithConfig(ctx, "watcher", cfg, taskrun.NewNamespacesScoppedController(namespaces), pipelinerun.NewNamespacesScoppedController(namespaces))
}
11 changes: 9 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ linkTitle: "Chains Configuration"
weight: 20
---
-->

# Chains Configuration

`Chains` works by observing `TaskRun` and `PipelineRun` executions, capturing relevant information, and storing it in a cryptographically-signed format.

`TaskRuns` and `PipelineRuns` can indicate inputs and outputs which are then captured and surfaced in the `Chains` payload formats, where relevant.
Expand Down Expand Up @@ -140,3 +138,12 @@ chains.tekton.dev/transparency-upload: "true"
| `signers.kms.auth.oidc.role` | Role used for OIDC authentication | |
| `signers.kms.auth.spire.sock` | URI of the Spire socket used for KMS token (e.g. `unix:///tmp/spire-agent/public/api.sock`) | |
| `signers.kms.auth.spire.audience` | Audience for requesting a SVID from Spire | |

## Supporting Namespace Restrictions in Chains Controller
The Chains Controller now supports the ability to restrict its scope to specific namespaces. This feature allows you to specify a list of namespaces that the controller should watch, providing more granular control over its operation. If no namespaces are specified, the controller will monitor all namespaces by default.

Usage
To utilize this feature, you need to pass a comma-separated list of namespaces as an argument to the controller. The controller will then limit its watch to the specified namespaces, filtering the PipelineRun and TaskRun objects accordingly.

Argument Description
--namespaces: A comma-separated list of namespaces that the controller should monitor. If this argument is not provided or is left empty, the controller will watch all namespaces by default.
80 changes: 80 additions & 0 deletions pkg/reconciler/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2024 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 reconciler

import (
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"knative.dev/pkg/controller"
)

// PipelineRunInformerFilterFunc returns a filter function
// for PipelineRuns ensuring PipelineRuns are filtered by list of namespaces membership
func PipelineRunInformerFilterFunc(namespaces []string) func(obj interface{}) bool {
return func(obj interface{}) bool {
// Namespace filter
if len(namespaces) == 0 {
return true
}
if pr, ok := obj.(*v1.PipelineRun); ok {
for _, ns := range namespaces {
if pr.Namespace == ns {
return true
}
}
}
return false
}
}

// TaskRunInformerFilterFunc returns a filter function
// for TaskRuns ensuring TaskRuns are filtered by list of namespaces membership
func TaskRunInformerFilterFunc(namespaces []string) func(obj interface{}) bool {
return func(obj interface{}) bool {
// Namespace filter
if len(namespaces) == 0 {
return true
}
if tr, ok := obj.(*v1.TaskRun); ok {
for _, ns := range namespaces {
if tr.Namespace == ns {
return true
}
}
}
return false
}
}

// TaskRunInformerFilterFuncWithOwnership returns a filter function
// for TaskRuns ensuring Ownership by a PipelineRun and filtered by list of namespaces membership and
func TaskRunInformerFilterFuncWithOwnership(namespaces []string) func(obj interface{}) bool {
return func(obj interface{}) bool {
// Ownership filter
if !controller.FilterController(&v1.PipelineRun{})(obj) {
return false
}
// Namespace filter
if len(namespaces) == 0 {
return true
}
if tr, ok := obj.(*v1.TaskRun); ok {
for _, ns := range namespaces {
if tr.Namespace == ns {
return true
}
}
}
return false
}
}
163 changes: 163 additions & 0 deletions pkg/reconciler/filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package reconciler

import (
"testing"

v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// TestPipelineRunInformerFilterFunc tests the PipelineRunInformerFilterFunc
//
//nolint:all
func TestPipelineRunInformerFilterFunc(t *testing.T) {
tests := []struct {
name string
namespaces []string
obj interface{}
expected bool
}{
{
name: "Empty namespaces, should match",
namespaces: []string{},
obj: &v1.PipelineRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: true,
},
{
name: "Matching namespace",
namespaces: []string{"default", "test"},
obj: &v1.PipelineRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: true,
},
{
name: "Non-matching namespace",
namespaces: []string{"test"},
obj: &v1.PipelineRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: false,
},
{
name: "Non PipelineRun object",
namespaces: []string{"default"},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filterFunc := PipelineRunInformerFilterFunc(tt.namespaces)
result := filterFunc(tt.obj)
if result != tt.expected {
t.Errorf("Reconciler.PipelineRunInformerFilterFunc() result = %v, wanted %v", result, tt.expected)
}
})
}
}

// TestTaskRunInformerFilterFunc tests the TaskRunInformerFilterFunc
//
//nolint:all
func TestTaskRunInformerFilterFunc(t *testing.T) {
tests := []struct {
name string
namespaces []string
obj interface{}
expected bool
}{
{
name: "Empty namespaces, should match",
namespaces: []string{},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: true,
},
{
name: "Matching namespace",
namespaces: []string{"default", "test"},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: true,
},
{
name: "Non-matching namespace",
namespaces: []string{"test"},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: false,
},
{
name: "Non TaskRun object",
namespaces: []string{"default"},
obj: &v1.PipelineRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filterFunc := TaskRunInformerFilterFunc(tt.namespaces)
result := filterFunc(tt.obj)
if result != tt.expected {
t.Errorf("Reconciler.TaskRunInformerFilterFunc() result = %v, wanted %v", result, tt.expected)
}
})
}
}

// TestTaskRunInformerFilterFuncWithOwnership tests the TaskRunInformerFilterFuncWithOwnership
func TestTaskRunInformerFilterFuncWithOwnership(t *testing.T) {
boolValue := true
tests := []struct {
name string
namespaces []string
obj interface{}
expected bool
}{
{
name: "Empty namespaces and ownership, should match",
namespaces: []string{},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
OwnerReferences: []metav1.OwnerReference{
{APIVersion: "tekton.dev/v1", Kind: "PipelineRun", Controller: &boolValue},
},
}},
expected: true,
},
{
name: "Matching namespace and ownership",
namespaces: []string{"default", "test"},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
OwnerReferences: []metav1.OwnerReference{
{APIVersion: "tekton.dev/v1", Kind: "PipelineRun", Controller: &boolValue},
},
}},
expected: true,
},
{
name: "Non-matching namespace and ownership",
namespaces: []string{"test"},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
OwnerReferences: []metav1.OwnerReference{
{APIVersion: "tekton.dev/v1", Kind: "PipelineRun", Controller: &boolValue},
},
}},
expected: false,
},
{
name: "No ownership",
namespaces: []string{"default"},
obj: &v1.TaskRun{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filterFunc := TaskRunInformerFilterFuncWithOwnership(tt.namespaces)
result := filterFunc(tt.obj)
if result != tt.expected {
t.Errorf("Reconciler.TaskRunInformerFilterFuncWithOwnership() result = %v, wanted %v", result, tt.expected)
}
})
}
}
Loading

0 comments on commit 61025e6

Please sign in to comment.