Skip to content

Commit 074ec55

Browse files
authored
🌱 Add webhook to add a name label to ClusterCatalogs (#356)
* Add webhook to name label ClusterCatalogs * Linter fixups * Added bit more webhook test coverage * Use catalogd version & clean up TLS args Signed-off-by: Brett Tofel <[email protected]> * Fix label name Signed-off-by: Brett Tofel <[email protected]> * From stand-alone webhook to operator internal Signed-off-by: Brett Tofel <[email protected]> * Think this fixes e2e-upgrade not sure why PR changes might necessitate this, but it seems legit to delete deployment first Signed-off-by: Brett Tofel <[email protected]> * PR review changes so far... - get MutaingWebhookConfig from kubebuilder; - stop deleting deployment on only-deploy; - use the existing matchLabel, stop adding webhook label to operator; - rename the name label to metadata.name (but not in API yet). Signed-off-by: Brett Tofel <[email protected]> * Restore CA issuer config Signed-off-by: Brett Tofel <[email protected]> * Fix unit tests Signed-off-by: Brett Tofel <[email protected]> * Addresses review comments around TLS cert config Signed-off-by: Brett Tofel <[email protected]> * Move RBAC for webhook to kb directive Signed-off-by: Brett Tofel <[email protected]> * Move allow-direct-injection to secretTemplate Signed-off-by: Brett Tofel <[email protected]> * Make metadata.name an API constant Signed-off-by: Brett Tofel <[email protected]> * Use MetaDataNameLabel API constant in log line Signed-off-by: Brett Tofel <[email protected]> * Combine cert watchers Signed-off-by: Brett Tofel <[email protected]> * Move cert watching back to ctr-rt manager Signed-off-by: Brett Tofel <[email protected]> * Address review comments Signed-off-by: Brett Tofel <[email protected]> * Address review comments 2 Signed-off-by: Brett Tofel <[email protected]> --------- Signed-off-by: Brett Tofel <[email protected]>
1 parent db28a52 commit 074ec55

File tree

12 files changed

+262
-13
lines changed

12 files changed

+262
-13
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ clean: ## Remove binaries and test artifacts
6767
.PHONY: generate
6868
generate: $(CONTROLLER_GEN) ## Generate code and manifests.
6969
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
70-
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/base/crd/bases output:rbac:artifacts:config=config/base/rbac
70+
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/base/crd/bases output:rbac:artifacts:config=config/base/rbac output:webhook:artifacts:config=config/base/manager/webhook/
7171

7272
.PHONY: fmt
7373
fmt: ## Run go fmt against code.

api/core/v1alpha1/clustercatalog_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const (
3535
ReasonUnpackFailed = "UnpackFailed"
3636
ReasonStorageFailed = "FailedToStore"
3737
ReasonStorageDeleteFailed = "FailedToDelete"
38+
39+
MetadataNameLabel = "olm.operatorframework.io/metadata.name"
3840
)
3941

4042
//+kubebuilder:object:root=true

cmd/manager/main.go

+41-3
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"crypto/tls"
2021
"flag"
2122
"fmt"
23+
"log"
2224
"net/url"
2325
"os"
2426
"path/filepath"
@@ -31,10 +33,12 @@ import (
3133
"k8s.io/client-go/metadata"
3234
_ "k8s.io/client-go/plugin/pkg/client/auth"
3335
ctrl "sigs.k8s.io/controller-runtime"
36+
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
3437
"sigs.k8s.io/controller-runtime/pkg/healthz"
3538
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3639
"sigs.k8s.io/controller-runtime/pkg/metrics"
3740
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
41+
crwebhook "sigs.k8s.io/controller-runtime/pkg/webhook"
3842

3943
"github.com/operator-framework/catalogd/api/core/v1alpha1"
4044
corecontrollers "github.com/operator-framework/catalogd/internal/controllers/core"
@@ -45,6 +49,7 @@ import (
4549
"github.com/operator-framework/catalogd/internal/source"
4650
"github.com/operator-framework/catalogd/internal/storage"
4751
"github.com/operator-framework/catalogd/internal/version"
52+
"github.com/operator-framework/catalogd/internal/webhook"
4853
)
4954

5055
var (
@@ -75,6 +80,7 @@ func main() {
7580
gcInterval time.Duration
7681
certFile string
7782
keyFile string
83+
webhookPort int
7884
)
7985
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
8086
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -90,6 +96,7 @@ func main() {
9096
flag.DurationVar(&gcInterval, "gc-interval", 12*time.Hour, "interval in which garbage collection should be run against the catalog content cache")
9197
flag.StringVar(&certFile, "tls-cert", "", "The certificate file used for serving catalog contents over HTTPS. Requires tls-key.")
9298
flag.StringVar(&keyFile, "tls-key", "", "The key file used for serving catalog contents over HTTPS. Requires tls-cert.")
99+
flag.IntVar(&webhookPort, "webhook-server-port", 9443, "The port that the mutating webhook server serves at.")
93100
opts := zap.Options{
94101
Development: true,
95102
}
@@ -119,6 +126,23 @@ func main() {
119126
externalAddr = protocol + externalAddr
120127

121128
cfg := ctrl.GetConfigOrDie()
129+
130+
cw, err := certwatcher.New(certFile, keyFile)
131+
if err != nil {
132+
log.Fatalf("Failed to initialize certificate watcher: %v", err)
133+
}
134+
135+
// Create webhook server and configure TLS
136+
webhookServer := crwebhook.NewServer(crwebhook.Options{
137+
Port: webhookPort,
138+
TLSOpts: []func(*tls.Config){
139+
func(cfg *tls.Config) {
140+
cfg.GetCertificate = cw.GetCertificate
141+
},
142+
},
143+
})
144+
145+
// Create manager
122146
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
123147
Scheme: scheme,
124148
Metrics: metricsserver.Options{
@@ -128,9 +152,17 @@ func main() {
128152
HealthProbeBindAddress: probeAddr,
129153
LeaderElection: enableLeaderElection,
130154
LeaderElectionID: "catalogd-operator-lock",
155+
WebhookServer: webhookServer,
131156
})
132157
if err != nil {
133-
setupLog.Error(err, "unable to start manager")
158+
setupLog.Error(err, "unable to create manager")
159+
os.Exit(1)
160+
}
161+
162+
// Add the certificate watcher to the manager
163+
err = mgr.Add(cw)
164+
if err != nil {
165+
setupLog.Error(err, "unable to add certificate watcher to manager")
134166
os.Exit(1)
135167
}
136168

@@ -174,7 +206,7 @@ func main() {
174206
LocalStorage: localStorage,
175207
}
176208

177-
err = serverutil.AddCatalogServerToManager(mgr, catalogServerConfig)
209+
err = serverutil.AddCatalogServerToManager(mgr, catalogServerConfig, cw)
178210
if err != nil {
179211
setupLog.Error(err, "unable to configure catalog server")
180212
os.Exit(1)
@@ -217,7 +249,13 @@ func main() {
217249
os.Exit(1)
218250
}
219251

220-
setupLog.Info("starting manager")
252+
// mutating webhook that labels ClusterCatalogs with name label
253+
if err = (&webhook.ClusterCatalog{}).SetupWebhookWithManager(mgr); err != nil {
254+
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterCatalog")
255+
os.Exit(1)
256+
}
257+
258+
setupLog.Info("starting mutating webhook manager")
221259
if err := mgr.Start(ctx); err != nil {
222260
setupLog.Error(err, "problem running manager")
223261
os.Exit(1)
+9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
resources:
22
- manager.yaml
33
- catalogserver_service.yaml
4+
- webhook/manifests.yaml
5+
- webhook/catalogd-webhook-service.yaml
46
apiVersion: kustomize.config.k8s.io/v1beta1
57
kind: Kustomization
68
images:
79
- name: controller
810
newName: quay.io/operator-framework/catalogd
911
newTag: devel
12+
patches:
13+
- path: webhook/patch.yaml
14+
target:
15+
group: admissionregistration.k8s.io
16+
kind: MutatingWebhookConfiguration
17+
name: mutating-webhook-configuration
18+
version: v1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: webhook-service
5+
namespace: system
6+
spec:
7+
ports:
8+
- port: 443
9+
targetPort: 9443
10+
selector:
11+
control-plane: catalogd-controller-manager
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
apiVersion: admissionregistration.k8s.io/v1
3+
kind: MutatingWebhookConfiguration
4+
metadata:
5+
name: mutating-webhook-configuration
6+
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /mutate-olm-operatorframework-io-v1alpha1-clustercatalog
14+
failurePolicy: Fail
15+
name: inject-metadata-name.olm.operatorframework.io
16+
rules:
17+
- apiGroups:
18+
- olm.operatorframework.io
19+
apiVersions:
20+
- v1alpha1
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- clustercatalogs
26+
sideEffects: None
27+
timeoutSeconds: 10
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# None of these values can be set via the kubebuilder directive, hence this patch
2+
- op: replace
3+
path: /webhooks/0/clientConfig/service/namespace
4+
value: olmv1-system
5+
- op: replace
6+
path: /webhooks/0/clientConfig/service/name
7+
value: catalogd-webhook-service
8+
- op: add
9+
path: /webhooks/0/clientConfig/service/port
10+
value: 443
11+
- op: add
12+
path: /metadata/annotations/cert-manager.io~1inject-ca-from-secret
13+
value: cert-manager/olmv1-ca

config/components/ca/resources/issuers.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ spec:
1515
isCA: true
1616
commonName: olmv1-ca
1717
secretName: olmv1-ca
18+
secretTemplate:
19+
annotations:
20+
cert-manager.io/allow-direct-injection: "true"
1821
privateKey:
1922
algorithm: ECDSA
2023
size: 256

config/components/tls/resources/certificate.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ spec:
99
dnsNames:
1010
- localhost
1111
- catalogd-catalogserver.olmv1-system.svc
12+
- catalogd-webhook-service.olmv1-system.svc
1213
- catalogd-catalogserver.olmv1-system.svc.cluster.local
14+
- catalogd-webhook-service.olmv1-system.svc.cluster.local
1315
privateKey:
1416
algorithm: ECDSA
1517
size: 256

internal/serverutil/serverutil.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,18 @@ type CatalogServerConfig struct {
2323
LocalStorage storage.Instance
2424
}
2525

26-
func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig) error {
26+
func AddCatalogServerToManager(mgr ctrl.Manager, cfg CatalogServerConfig, tlsFileWatcher *certwatcher.CertWatcher) error {
2727
listener, err := net.Listen("tcp", cfg.CatalogAddr)
2828
if err != nil {
2929
return fmt.Errorf("error creating catalog server listener: %w", err)
3030
}
3131

3232
if cfg.CertFile != "" && cfg.KeyFile != "" {
33-
tlsFileWatcher, err := certwatcher.New(cfg.CertFile, cfg.KeyFile)
34-
if err != nil {
35-
return fmt.Errorf("error creating TLS certificate watcher: %w", err)
36-
}
33+
// Use the passed certificate watcher instead of creating a new one
3734
config := &tls.Config{
3835
GetCertificate: tlsFileWatcher.GetCertificate,
3936
MinVersion: tls.VersionTLS12,
4037
}
41-
err = mgr.Add(tlsFileWatcher)
42-
if err != nil {
43-
return fmt.Errorf("error adding TLS certificate watcher to manager: %w", err)
44-
}
4538
listener = tls.NewListener(listener, config)
4639
}
4740

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package webhook
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"k8s.io/apimachinery/pkg/runtime"
8+
ctrl "sigs.k8s.io/controller-runtime"
9+
"sigs.k8s.io/controller-runtime/pkg/log"
10+
11+
"github.com/operator-framework/catalogd/api/core/v1alpha1"
12+
)
13+
14+
// +kubebuilder:webhook:admissionReviewVersions={v1},failurePolicy=Fail,groups=olm.operatorframework.io,mutating=true,name=inject-metadata-name.olm.operatorframework.io,path=/mutate-olm-operatorframework-io-v1alpha1-clustercatalog,resources=clustercatalogs,verbs=create;update,versions=v1alpha1,sideEffects=None,timeoutSeconds=10
15+
16+
// +kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=get;list;watch;patch;update
17+
18+
// ClusterCatalog wraps the external v1alpha1.ClusterCatalog type and implements admission.Defaulter
19+
type ClusterCatalog struct{}
20+
21+
// Default is the method that will be called by the webhook to apply defaults.
22+
func (r *ClusterCatalog) Default(ctx context.Context, obj runtime.Object) error {
23+
log := log.FromContext(ctx)
24+
log.Info("Invoking Default method for ClusterCatalog", "object", obj)
25+
catalog, ok := obj.(*v1alpha1.ClusterCatalog)
26+
if !ok {
27+
return fmt.Errorf("expected a ClusterCatalog but got a %T", obj)
28+
}
29+
30+
// Defaulting logic: add the "olm.operatorframework.io/metadata.name" label
31+
if catalog.Labels == nil {
32+
catalog.Labels = map[string]string{}
33+
}
34+
catalog.Labels[v1alpha1.MetadataNameLabel] = catalog.GetName()
35+
log.Info("default", v1alpha1.MetadataNameLabel, catalog.Name, "labels", catalog.Labels)
36+
37+
return nil
38+
}
39+
40+
// SetupWebhookWithManager sets up the webhook with the manager
41+
func (r *ClusterCatalog) SetupWebhookWithManager(mgr ctrl.Manager) error {
42+
return ctrl.NewWebhookManagedBy(mgr).
43+
For(&v1alpha1.ClusterCatalog{}).
44+
WithDefaulter(r).
45+
Complete()
46+
}

0 commit comments

Comments
 (0)