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
9 changes: 5 additions & 4 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ This sets up a Kind cluster with Tekton Pruner, Prometheus, and Jaeger. Access v
- Jaeger: http://localhost:16686
- Pruner Metrics: http://localhost:9090/metrics

### Redeploy controller

As you make changes to the code, you can redeploy your controller with:

### Redeploy components
As you make changes to the code, you can redeploy components individually:
```shell
# Redeploy the controller
ko apply -f config/controller.yaml
# Redeploy the webhook
ko apply -f config/webhook.yaml
```

### Tear it down
Expand Down
91 changes: 91 additions & 0 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Copyright 2025 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 main

import (
"context"

"github.com/tektoncd/pruner/pkg/webhook"
"k8s.io/client-go/tools/cache"
kubeclient "knative.dev/pkg/client/injection/kube/client"
secretinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/secret"
"knative.dev/pkg/configmap"
"knative.dev/pkg/controller"
"knative.dev/pkg/injection/sharedmain"
"knative.dev/pkg/logging"
"knative.dev/pkg/signals"
"knative.dev/pkg/system"
pkgwebhook "knative.dev/pkg/webhook"
"knative.dev/pkg/webhook/certificates"
)

func main() {
// Create signal context
ctx := signals.NewContext()

// Disable leader election for stateless webhook
ctx = sharedmain.WithHADisabled(ctx)

ctx = pkgwebhook.WithOptions(ctx, pkgwebhook.Options{
ServiceName: "tekton-pruner-webhook",
Port: pkgwebhook.PortFromEnv(8443),
SecretName: pkgwebhook.SecretNameFromEnv("tekton-pruner-webhook-certs"),
})

// Start webhook server with certificate controller
// The certificate controller ensures the webhook-certs secret exists and injects the CA bundle into webhook configurations
sharedmain.MainWithContext(ctx, "pruner-webhook",
certificates.NewController,
NewConfigMapValidationWebhook,
)
}

// create the webhook admission controller
func NewConfigMapValidationWebhook(ctx context.Context, _ configmap.Watcher) *controller.Impl {
logger := logging.FromContext(ctx)
logger.Info("Setting up Pruner ConfigMap validation webhook")

// Get webhook options for secret name
opts := pkgwebhook.GetOptions(ctx)
client := kubeclient.Get(ctx)
secretInformer := secretinformer.Get(ctx)

webhookName := "validation.webhook.pruner.tekton.dev"

// Create the admission controller with client and configuration
wh := &webhook.ValidateConfigMap{
Client: client,
SecretName: opts.SecretName,
WebhookName: webhookName,
}

// Create the controller
c := controller.NewContext(ctx, wh, controller.ControllerOptions{
Logger: logger,
WorkQueueName: "configmap-validation",
})

// Watch the secret for changes to trigger reconciliation
if _, err := secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: controller.FilterWithNameAndNamespace(system.Namespace(), opts.SecretName),
Handler: controller.HandleAll(c.Enqueue),
}); err != nil {
logger.Fatalw("Failed to add event handler for secret informer", "error", err)
}

return c
}
12 changes: 11 additions & 1 deletion config/200-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ rules:
- list
- watch

# permissions restricted to specific ConfigMap
# permissions restricted to specific ConfigMap
- apiGroups:
- ""
resources:
Expand All @@ -99,3 +99,13 @@ rules:
- tekton-pruner-namespace-spec
verbs:
- get

# used in webhook for certificate management
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
4 changes: 4 additions & 0 deletions config/600-tekton-pruner-default-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ kind: ConfigMap
metadata:
name: tekton-pruner-default-spec
namespace: tekton-pipelines
labels:
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/release: "devel"
pruner.tekton.dev/config-type: global
data:
global-config: |
enforcedConfigLevel: global
Expand Down
101 changes: 101 additions & 0 deletions config/601-webhook-configuration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright 2025 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: Secret
metadata:
name: tekton-pruner-webhook-certs
namespace: tekton-pipelines
labels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/release: "devel"
# The data is populated at install time.

---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validation.webhook.pruner.tekton.dev
labels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/release: "devel"
webhooks:
# Webhook 1: Validates global/cluster-level pruner configuration
# Triggers: When tekton-pruner-default-spec ConfigMap with proper labels is created or updated
# This ConfigMap is mandatory and must exist with valid configuration when pruner is installed
- admissionReviewVersions: ["v1"]
clientConfig:
service:
name: tekton-pruner-webhook
namespace: tekton-pipelines
path: /validate-configmap
failurePolicy: Fail
sideEffects: None
timeoutSeconds: 5
name: validation.webhook.pruner.tekton.dev.global
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE", "DELETE"]
resources: ["configmaps"]
scope: "Namespaced"
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- tekton-pipelines
objectSelector:
matchLabels:
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/config-type: global
# Webhook 2: Validates namespace-level pruner configuration
# Triggers: When ConfigMap with proper labels is created or updated in any user namespace
# This ConfigMap is OPTIONAL - namespaces can choose to create it for custom pruning policies
# If not present, the namespace will use the global configuration
- admissionReviewVersions: ["v1"]
clientConfig:
service:
name: tekton-pruner-webhook
namespace: tekton-pipelines
path: /validate-configmap
failurePolicy: Fail
sideEffects: None
timeoutSeconds: 5
name: validation.webhook.pruner.tekton.dev.namespace
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE", "DELETE"]
resources: ["configmaps"]
scope: "Namespaced"
namespaceSelector:
matchExpressions:
# Exclude system namespaces - webhook only processes user namespaces
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system
- kube-public
- kube-node-lease
- tekton-pipelines
- default
objectSelector:
matchLabels:
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/config-type: namespace
1 change: 1 addition & 0 deletions config/config-logging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ metadata:
name: config-logging-tekton-pruner
namespace: tekton-pipelines
labels:
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/release: "devel"
data:
_example: |
Expand Down
5 changes: 3 additions & 2 deletions config/config-observability.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ metadata:
name: config-observability-tekton-pruner
namespace: tekton-pipelines
labels:
app.kubernetes.io/part-of: tekton-pruner
pruner.tekton.dev/release: "devel"
data:
# New Knative observability config format (required for v0.0.0-20250811181739-e06d4c9af190+)
metrics-protocol: "prometheus"
metrics-protocol: "prometheus"
metrics-endpoint: ":9090"
tracing-protocol: "none"

# Legacy format for backward compatibility - TODO: Remove these after confirming new format works everywhere
metrics.backend-destination: "prometheus"
metrics.request-metrics-backend-destination: "prometheus"
Expand Down
100 changes: 100 additions & 0 deletions config/webhook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Copyright 2025 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: apps/v1
kind: Deployment
metadata:
name: tekton-pruner-webhook
namespace: tekton-pipelines
labels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
template:
metadata:
labels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
spec:
serviceAccountName: tekton-pruner-controller
containers:
- name: webhook
# This is the Go import path for the binary that is containerized
# and substituted here.
image: ko://github.com/tektoncd/pruner/cmd/webhook
env:
- name: SYSTEM_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
# Logging configuration
- name: CONFIG_LOGGING_NAME
value: config-logging
- name: CONFIG_OBSERVABILITY_NAME
value: config-observability
- name: METRICS_DOMAIN
value: tekton.dev/pruner
# Webhook configuration
- name: WEBHOOK_PORT
value: "8443"
- name: WEBHOOK_SECRET_NAME
value: tekton-pruner-webhook-certs
- name: KUBERNETES_MIN_VERSION
value: "1.0.0"
ports:
- name: metrics
containerPort: 9090
- name: profiling
containerPort: 8008
- name: https-webhook
containerPort: 8443
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefault

---
apiVersion: v1
kind: Service
metadata:
name: tekton-pruner-webhook
namespace: tekton-pipelines
labels:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
spec:
ports:
- name: https-webhook
port: 443
targetPort: 8443
selector:
app.kubernetes.io/component: webhook
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: tekton-pruner
Loading
Loading