@@ -14,22 +14,23 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- // TODO - remove once spoke webhooks are implemented.
18- //
19- //nolint:all // Required because of `dupl` between this file and spoke_webhook.go
2017package v1beta1
2118
2219import (
2320 "context"
2421 "fmt"
2522
23+ "k8s.io/apimachinery/pkg/api/errors"
2624 "k8s.io/apimachinery/pkg/runtime"
25+ "k8s.io/apimachinery/pkg/util/validation/field"
26+ operatorv1 "open-cluster-management.io/api/operator/v1"
2727 ctrl "sigs.k8s.io/controller-runtime"
28+ "sigs.k8s.io/controller-runtime/pkg/client"
2829 logf "sigs.k8s.io/controller-runtime/pkg/log"
2930 "sigs.k8s.io/controller-runtime/pkg/webhook"
3031 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
3132
32- fleetconfigopenclustermanagementiov1beta1 "github.com/open-cluster-management-io/lab/fleetconfig-controller/api/v1beta1"
33+ "github.com/open-cluster-management-io/lab/fleetconfig-controller/api/v1beta1"
3334)
3435
3536// nolint:unused
@@ -38,14 +39,12 @@ var spokelog = logf.Log.WithName("spoke-resource")
3839
3940// SetupSpokeWebhookWithManager registers the webhook for Spoke in the manager.
4041func SetupSpokeWebhookWithManager (mgr ctrl.Manager ) error {
41- return ctrl .NewWebhookManagedBy (mgr ).For (& fleetconfigopenclustermanagementiov1beta1 .Spoke {}).
42- WithValidator (& SpokeCustomValidator {}).
42+ return ctrl .NewWebhookManagedBy (mgr ).For (& v1beta1 .Spoke {}).
43+ WithValidator (& SpokeCustomValidator {client : mgr . GetClient () }).
4344 WithDefaulter (& SpokeCustomDefaulter {}).
4445 Complete ()
4546}
4647
47- // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
48-
4948// +kubebuilder:webhook:path=/mutate-fleetconfig-open-cluster-management-io-v1beta1-spoke,mutating=true,failurePolicy=fail,sideEffects=None,groups=fleetconfig.open-cluster-management.io,resources=spokes,verbs=create;update,versions=v1beta1,name=mspoke-v1beta1.kb.io,admissionReviewVersions=v1
5049
5150// SpokeCustomDefaulter struct is responsible for setting default values on the custom resource of the
@@ -61,15 +60,13 @@ var _ webhook.CustomDefaulter = &SpokeCustomDefaulter{}
6160
6261// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Spoke.
6362func (d * SpokeCustomDefaulter ) Default (_ context.Context , obj runtime.Object ) error {
64- spoke , ok := obj .(* fleetconfigopenclustermanagementiov1beta1 .Spoke )
63+ spoke , ok := obj .(* v1beta1 .Spoke )
6564
6665 if ! ok {
6766 return fmt .Errorf ("expected an Spoke object but got %T" , obj )
6867 }
6968 spokelog .Info ("Defaulting for Spoke" , "name" , spoke .GetName ())
7069
71- // TODO(user): fill in your defaulting logic.
72-
7370 return nil
7471}
7572
@@ -84,40 +81,79 @@ func (d *SpokeCustomDefaulter) Default(_ context.Context, obj runtime.Object) er
8481// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
8582// as this struct is used only for temporary operations and does not need to be deeply copied.
8683type SpokeCustomValidator struct {
87- // TODO(user): Add more fields as needed for validation
84+ client client. Client
8885}
8986
9087var _ webhook.CustomValidator = & SpokeCustomValidator {}
9188
9289// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Spoke.
93- func (v * SpokeCustomValidator ) ValidateCreate (_ context.Context , obj runtime.Object ) (admission.Warnings , error ) {
94- spoke , ok := obj .(* fleetconfigopenclustermanagementiov1beta1 .Spoke )
90+ func (v * SpokeCustomValidator ) ValidateCreate (ctx context.Context , obj runtime.Object ) (admission.Warnings , error ) {
91+ spoke , ok := obj .(* v1beta1 .Spoke )
9592 if ! ok {
9693 return nil , fmt .Errorf ("expected a Spoke object but got %T" , obj )
9794 }
9895 spokelog .Info ("Validation for Spoke upon creation" , "name" , spoke .GetName ())
9996
100- // TODO(user): fill in your validation logic upon object creation.
97+ var allErrs field.ErrorList
98+
99+ if spoke .Spec .Klusterlet .Mode == string (operatorv1 .InstallModeHosted ) {
100+ if spoke .Spec .Klusterlet .ManagedClusterKubeconfig .SecretReference == nil {
101+ allErrs = append (allErrs , field .Invalid (
102+ field .NewPath ("spec" ).Child ("klusterlet" ).Child ("managedClusterKubeconfig" ).Child ("secretReference" ),
103+ spoke .Name , "managedClusterKubeconfig.secretReference is required in hosted mode" ),
104+ )
105+ } else {
106+ if valid , msg := isKubeconfigValid (spoke .Spec .Klusterlet .ManagedClusterKubeconfig ); ! valid {
107+ allErrs = append (allErrs , field .Invalid (
108+ field .NewPath ("spec" ).Child ("klusterlet" ).Child ("managedClusterKubeconfig" ).Child ("secretReference" ),
109+ spoke .Name , msg ),
110+ )
111+ }
112+ }
113+ }
114+ if valid , msg := isKubeconfigValid (spoke .Spec .Kubeconfig ); ! valid {
115+ allErrs = append (allErrs , field .Invalid (
116+ field .NewPath ("spec" ).Child ("kubeconfig" ), spoke , msg ),
117+ )
118+ }
119+
120+ warn , errs := validateAddons (ctx , v .client , spoke )
121+ allErrs = append (allErrs , errs ... )
101122
102- return nil , nil
123+ if len (allErrs ) > 0 {
124+ return warn , errors .NewInvalid (v1beta1 .HubGroupKind , spoke .Name , allErrs )
125+ }
126+ return warn , nil
103127}
104128
105129// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Spoke.
106- func (v * SpokeCustomValidator ) ValidateUpdate (_ context.Context , _ , newObj runtime.Object ) (admission.Warnings , error ) {
107- spoke , ok := newObj .(* fleetconfigopenclustermanagementiov1beta1.Spoke )
130+ func (v * SpokeCustomValidator ) ValidateUpdate (ctx context.Context , oldObj , newObj runtime.Object ) (admission.Warnings , error ) {
131+ spoke , ok := newObj .(* v1beta1.Spoke )
132+ if ! ok {
133+ return nil , fmt .Errorf ("expected a Spoke object for the newObj but got %T" , newObj )
134+ }
135+ oldSpoke , ok := oldObj .(* v1beta1.Spoke )
108136 if ! ok {
109137 return nil , fmt .Errorf ("expected a Spoke object for the newObj but got %T" , newObj )
110138 }
111139 spokelog .Info ("Validation for Spoke upon update" , "name" , spoke .GetName ())
112140
113- // TODO(user): fill in your validation logic upon object update.
141+ err := allowSpokeUpdate (oldSpoke , spoke )
142+ if err != nil {
143+ return nil , err
144+ }
114145
115- return nil , nil
146+ warn , allErrs := validateAddons (ctx , v .client , spoke )
147+
148+ if len (allErrs ) > 0 {
149+ return warn , errors .NewInvalid (v1beta1 .SpokeGroupKind , spoke .Name , allErrs )
150+ }
151+ return warn , nil
116152}
117153
118154// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Spoke.
119155func (v * SpokeCustomValidator ) ValidateDelete (_ context.Context , obj runtime.Object ) (admission.Warnings , error ) {
120- spoke , ok := obj .(* fleetconfigopenclustermanagementiov1beta1 .Spoke )
156+ spoke , ok := obj .(* v1beta1 .Spoke )
121157 if ! ok {
122158 return nil , fmt .Errorf ("expected a Spoke object but got %T" , obj )
123159 }
0 commit comments