diff --git a/Dockerfile b/Dockerfile index c5b9d6a..881d222 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN go mod download # Copy the go source COPY cmd/main.go cmd/main.go COPY api/ api/ -COPY internal/controller/ internal/controller/ +COPY internal/ internal/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/cmd/main.go b/cmd/main.go index a1613f9..6fbae49 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,6 +37,7 @@ import ( rsctv1alpha1 "github.com/ocp-power-automation/rsct-operator/api/v1alpha1" "github.com/ocp-power-automation/rsct-operator/internal/controller" + rsctwebhook "github.com/ocp-power-automation/rsct-operator/internal/webhook/rsct" //+kubebuilder:scaffold:imports ) @@ -150,6 +151,11 @@ func main() { } //+kubebuilder:scaffold:builder + if err := rsctwebhook.RegisterWebhooks(mgr); err != nil { + setupLog.Error(err, "unable to register webhooks") + os.Exit(1) + } + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) diff --git a/config/samples/rsct_v1alpha1_rsct.yaml b/config/samples/rsct_v1alpha1_rsct.yaml index 8fc0db1..70bcf66 100644 --- a/config/samples/rsct_v1alpha1_rsct.yaml +++ b/config/samples/rsct_v1alpha1_rsct.yaml @@ -2,7 +2,6 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: rsct-operator-privileged - namespace: rsct-operator-system roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole @@ -22,6 +21,5 @@ metadata: app.kubernetes.io/managed-by: kustomize app.kubernetes.io/created-by: rsct-operator name: rsct - namespace: rsct-operator-system spec: {} diff --git a/internal/webhook/rsct/validating.go b/internal/webhook/rsct/validating.go new file mode 100644 index 0000000..17dfacb --- /dev/null +++ b/internal/webhook/rsct/validating.go @@ -0,0 +1,55 @@ +package rsct + +import ( + "context" + "fmt" + + rsctv1alpha1 "github.com/ocp-power-automation/rsct-operator/api/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +type RSCTValidator struct { + Client client.Client +} + +// ValidateCreate implements admission.CustomValidator. +func (r *RSCTValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cr, ok := obj.(*rsctv1alpha1.RSCT) + if !ok { + return nil, fmt.Errorf("expected RSCT, got %T", obj) + } + + var crList rsctv1alpha1.RSCTList + if err := r.Client.List(ctx, &crList); err != nil { + return nil, fmt.Errorf("cannot list RSCT: %w", err) + } + + if len(crList.Items) > 0 { + return nil, fmt.Errorf("only one RSCT instance is allowed (found %d), rejecting creation of %s", len(crList.Items), cr.Name) + } + + return nil, nil +} + +// ValidateDelete implements admission.CustomValidator. +func (r *RSCTValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) { + return nil, nil +} + +// ValidateUpdate implements admission.CustomValidator. +func (r *RSCTValidator) ValidateUpdate(ctx context.Context, oldObj runtime.Object, newObj runtime.Object) (warnings admission.Warnings, err error) { + return nil, nil +} + +var _ admission.CustomValidator = &RSCTValidator{} + +// Register the webhook with the manager +func RegisterWebhooks(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(&rsctv1alpha1.RSCT{}). + WithValidator(&RSCTValidator{Client: mgr.GetClient()}). + Complete() +}