Skip to content

Commit

Permalink
Add validating webhook while creating ptpoperatorconfig
Browse files Browse the repository at this point in the history
Signed-off-by: Aneesh Puttur <[email protected]>
  • Loading branch information
aneeshkp authored and josephdrichard committed May 16, 2024
1 parent 0e50d98 commit 402929d
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 31 deletions.
35 changes: 11 additions & 24 deletions api/v1/ptpoperatorconfig_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ package v1
import (
"context"
"errors"
"net/url"

storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -42,28 +40,11 @@ func (r *PtpOperatorConfig) SetupWebhookWithManager(mgr ctrl.Manager, client cli
Complete()
}

//+kubebuilder:webhook:path=/validate-ptp-openshift-io-v1-ptpoperatorconfig,mutating=false,failurePolicy=fail,sideEffects=None,groups=ptp.openshift.io,resources=ptpoperatorconfigs,verbs=update,versions=v1,name=vptpoperatorconfig.kb.io,admissionReviewVersions=v1

const (
AmqScheme = "amqp"
// storageTypeEmptyDir is used for developer tests to map pubsubstore volume to emptyDir
storageTypeEmptyDir = "emptyDir"
)
//+kubebuilder:webhook:path=/validate-ptp-openshift-io-v1-ptpoperatorconfig,mutating=false,failurePolicy=fail,sideEffects=None,groups=ptp.openshift.io,resources=ptpoperatorconfigs,verbs=create;update,versions=v1,name=vptpoperatorconfig.kb.io,admissionReviewVersions=v1

func (r *PtpOperatorConfig) validate() error {
eventConfig := r.Spec.EventConfig
if eventConfig != nil && eventConfig.EnableEventPublisher {
transportUrl, err := url.Parse(eventConfig.TransportHost)
if err == nil && transportUrl.Scheme == AmqScheme {
return nil
}
if eventConfig.StorageType == "" {
// default to emptyDir to pass the check since cloud-event-proxy overwrites this to configMap for HTTP transport
eventConfig.StorageType = storageTypeEmptyDir
}
if eventConfig.StorageType != storageTypeEmptyDir && !r.checkStorageClass(eventConfig.StorageType) {
return errors.New("ptpEventConfig.storageType is set to StorageClass " + eventConfig.StorageType + " which does not exist")
}
if r.GetName() != "default" {
return errors.New("PtpOperatorConfig name must be 'default'. Only one 'default' PtpOperatorConfig configuration is allowed")
}
return nil
}
Expand All @@ -73,13 +54,19 @@ var _ webhook.Validator = &PtpOperatorConfig{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *PtpOperatorConfig) ValidateCreate() (admission.Warnings, error) {
ptpoperatorconfiglog.Info("validate create", "name", r.Name)
return nil, nil
if err := r.validate(); err != nil {
return admission.Warnings{}, err
}
return admission.Warnings{}, nil
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *PtpOperatorConfig) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
ptpoperatorconfiglog.Info("validate update", "name", r.Name)
return nil, r.validate()
if err := r.validate(); err != nil {
return admission.Warnings{}, err
}
return admission.Warnings{}, nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
Expand Down
1 change: 1 addition & 0 deletions bundle/manifests/ptp-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ spec:
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- ptpoperatorconfigs
Expand Down
1 change: 1 addition & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ webhooks:
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- ptpoperatorconfigs
Expand Down
78 changes: 71 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,25 @@ import (
"crypto/tls"
"flag"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"net/http"
"os"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"strings"
"time"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand All @@ -45,6 +48,7 @@ import (
"github.com/k8snetworkplumbingwg/ptp-operator/controllers"
"github.com/k8snetworkplumbingwg/ptp-operator/pkg/leaderelection"
"github.com/k8snetworkplumbingwg/ptp-operator/pkg/names"

)

var (
Expand Down Expand Up @@ -148,35 +152,58 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "PtpConfig")
os.Exit(1)
}

if err = (&ptpv1.PtpConfig{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "PtpConfig")
os.Exit(1)
}

// +kubebuilder:scaffold:builder

if err = (&ptpv1.PtpOperatorConfig{}).SetupWebhookWithManager(mgr, mgr.GetClient()); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "PtpOperatorConfig")
os.Exit(1)
}

err = createDefaultOperatorConfig(ctrl.GetConfigOrDie())
if err != nil {
setupLog.Error(err, "unable to create default PtpOperatorConfig")
checker := mgr.GetWebhookServer().StartedChecker()
//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", checker); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", checker); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}

go func() {
// Wait until the webhook server is ready.
setupLog.Info("waiting for validating webhook to be ready")
err = waitForWebhookServer(checker)
if err != nil {
setupLog.Error(err, "unable to create default PtpOperatorConfig due to webhook not ready")
} else {
// create default before the webhook are setup
err = createDefaultOperatorConfig(ctrl.GetConfigOrDie())
if err != nil {
setupLog.Error(err, "unable to create default PtpOperatorConfig")
}
}
}()
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
if err = mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}

}

func createDefaultOperatorConfig(cfg *rest.Config) error {
logger := setupLog.WithName("createDefaultOperatorConfig")
c, err := client.New(cfg, client.Options{Scheme: scheme})
if err != nil {
return fmt.Errorf("Couldn't create client: %v", err)
return fmt.Errorf("couldn't create client: %v", err)
}
config := &ptpv1.PtpOperatorConfig{
Spec: ptpv1.PtpOperatorConfigSpec{
Expand All @@ -201,3 +228,40 @@ func createDefaultOperatorConfig(cfg *rest.Config) error {
}
return nil
}

func setupChecks(mgr ctrl.Manager, checker healthz.Checker) {
if err := mgr.AddReadyzCheck("webhook", checker); err != nil {
setupLog.Error(err, "unable to create ready check")
os.Exit(1)
}
if err := mgr.AddHealthzCheck("webhook", checker); err != nil {
setupLog.Error(err, "unable to create health check")
os.Exit(1)
}
}

// waitForWebhookServer waits until the webhook server is ready.
func waitForWebhookServer(checker func(req *http.Request) error) error {
const (
timeout = 30 * time.Second // Adjust timeout as needed
pollingFreq = 1 * time.Second // Polling frequency
)
start := time.Now()

// Create an HTTP request to check the readiness of the webhook server.
req, err := http.NewRequest("GET", "https://localhost:9443/healthz", nil)
if err != nil {
return err
}

// Poll the checker function until it returns nil (indicating success)
// or until the timeout is reached.
for {
if err = checker(req); err == nil {
return nil
} else if time.Since(start) > timeout {
return fmt.Errorf("timeout waiting for webhook server to start")
}
time.Sleep(pollingFreq) // Poll every second
}
}
1 change: 1 addition & 0 deletions manifests/stable/ptp-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ spec:
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- ptpoperatorconfigs
Expand Down

0 comments on commit 402929d

Please sign in to comment.