github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/specs/admissionregistration.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package specs
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  
    10  	"github.com/juju/errors"
    11  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    12  	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
    13  )
    14  
    15  const (
    16  	// K8sWebhookV1Beta1 defines the v1beta1 API version for webhook resources.
    17  	K8sWebhookV1Beta1 APIVersion = "v1beta1"
    18  
    19  	// K8sWebhookV1 defines the v1 API version for webhook resources.
    20  	K8sWebhookV1 APIVersion = "v1"
    21  )
    22  
    23  // K8sMutatingWebhookSpec defines the spec details of MutatingWebhook with the API version.
    24  type K8sMutatingWebhookSpec struct {
    25  	Version     APIVersion
    26  	SpecV1Beta1 admissionregistrationv1beta1.MutatingWebhook
    27  	SpecV1      admissionregistrationv1.MutatingWebhook
    28  }
    29  
    30  // UnmarshalJSON implements the json.Unmarshaller interface.
    31  // NOTE: try v1beta1 first then v1 because admissionregistrationv1
    32  // and admissionregistrationv1beta1 have the same struct but some
    33  // fields might have different required values. To avoid breaking
    34  // existing workloads, we will consider to switch v1 as higher priority in 2.9 instead.
    35  func (wh *K8sMutatingWebhookSpec) UnmarshalJSON(value []byte) (err error) {
    36  	err = unmarshalJSONStrict(value, &wh.SpecV1Beta1)
    37  	if err == nil {
    38  		wh.Version = K8sWebhookV1Beta1
    39  		return nil
    40  	}
    41  	if err2 := unmarshalJSONStrict(value, &wh.SpecV1); err2 == nil {
    42  		wh.Version = K8sWebhookV1
    43  		return nil
    44  	}
    45  	return errors.Trace(err)
    46  }
    47  
    48  // MarshalJSON implements the json.Marshaller interface.
    49  func (wh K8sMutatingWebhookSpec) MarshalJSON() ([]byte, error) {
    50  	switch wh.Version {
    51  	case K8sWebhookV1Beta1:
    52  		return json.Marshal(wh.SpecV1Beta1)
    53  	case K8sWebhookV1:
    54  		return json.Marshal(wh.SpecV1)
    55  	default:
    56  		return []byte{}, errors.NotSupportedf("mutating webhook version %q", wh.Version)
    57  	}
    58  }
    59  
    60  // UpgradeK8sMutatingWebhookSpecV1Beta1 converts a v1beta1 MutatingWebhook to v1.
    61  func UpgradeK8sMutatingWebhookSpecV1Beta1(spec admissionregistrationv1beta1.MutatingWebhook) admissionregistrationv1.MutatingWebhook {
    62  	hook := admissionregistrationv1.MutatingWebhook{
    63  		Name:                    spec.Name,
    64  		NamespaceSelector:       spec.NamespaceSelector,
    65  		ObjectSelector:          spec.ObjectSelector,
    66  		TimeoutSeconds:          spec.TimeoutSeconds,
    67  		AdmissionReviewVersions: spec.AdmissionReviewVersions,
    68  		ClientConfig: admissionregistrationv1.WebhookClientConfig{
    69  			URL:      spec.ClientConfig.URL,
    70  			CABundle: spec.ClientConfig.CABundle,
    71  		},
    72  	}
    73  	if len(hook.AdmissionReviewVersions) == 0 {
    74  		hook.AdmissionReviewVersions = []string{"v1beta1"}
    75  	}
    76  	if spec.ClientConfig.Service != nil {
    77  		hook.ClientConfig.Service = &admissionregistrationv1.ServiceReference{
    78  			Namespace: spec.ClientConfig.Service.Namespace,
    79  			Name:      spec.ClientConfig.Service.Name,
    80  			Path:      spec.ClientConfig.Service.Path,
    81  			Port:      spec.ClientConfig.Service.Port,
    82  		}
    83  	}
    84  	if len(spec.Rules) > 0 {
    85  		for _, rule := range spec.Rules {
    86  			newRule := admissionregistrationv1.RuleWithOperations{
    87  				Rule: admissionregistrationv1.Rule{
    88  					APIGroups:   rule.APIGroups,
    89  					APIVersions: rule.APIVersions,
    90  					Resources:   rule.Resources,
    91  				},
    92  			}
    93  			if rule.Scope != nil {
    94  				scope := *rule.Scope
    95  				newRule.Scope = &scope
    96  			}
    97  			for _, op := range rule.Operations {
    98  				newRule.Operations = append(newRule.Operations, op)
    99  			}
   100  			hook.Rules = append(hook.Rules, newRule)
   101  		}
   102  	}
   103  	if spec.FailurePolicy != nil {
   104  		failurePolicy := admissionregistrationv1.FailurePolicyType(*spec.FailurePolicy)
   105  		hook.FailurePolicy = &failurePolicy
   106  	}
   107  	if spec.MatchPolicy != nil {
   108  		matchPolicy := admissionregistrationv1.MatchPolicyType(*spec.MatchPolicy)
   109  		hook.MatchPolicy = &matchPolicy
   110  	}
   111  	if spec.SideEffects != nil && *spec.SideEffects != "" {
   112  		sideEffects := admissionregistrationv1.SideEffectClass(*spec.SideEffects)
   113  		hook.SideEffects = &sideEffects
   114  	} else {
   115  		sideEffects := admissionregistrationv1.SideEffectClassNoneOnDryRun
   116  		hook.SideEffects = &sideEffects
   117  	}
   118  	if spec.ReinvocationPolicy != nil {
   119  		reinvocationPolicy := admissionregistrationv1.ReinvocationPolicyType(*spec.ReinvocationPolicy)
   120  		hook.ReinvocationPolicy = &reinvocationPolicy
   121  	}
   122  	return hook
   123  }
   124  
   125  // K8sValidatingWebhookSpec defines the spec details of ValidatingWebhook with the API version.
   126  type K8sValidatingWebhookSpec struct {
   127  	Version     APIVersion
   128  	SpecV1Beta1 admissionregistrationv1beta1.ValidatingWebhook
   129  	SpecV1      admissionregistrationv1.ValidatingWebhook
   130  }
   131  
   132  // UnmarshalJSON implements the json.Unmarshaller interface.
   133  // NOTE: try v1beta1 first then v1 because admissionregistrationv1
   134  // and admissionregistrationv1beta1 have the same struct but some
   135  // fields might have different required values. To avoid breaking
   136  // existing workloads, we will consider to switch v1 as higher priority in 2.9 instead.
   137  func (wh *K8sValidatingWebhookSpec) UnmarshalJSON(value []byte) (err error) {
   138  	err = unmarshalJSONStrict(value, &wh.SpecV1Beta1)
   139  	if err == nil {
   140  		wh.Version = K8sWebhookV1Beta1
   141  		return nil
   142  	}
   143  	if err2 := unmarshalJSONStrict(value, &wh.SpecV1); err2 == nil {
   144  		wh.Version = K8sWebhookV1
   145  		return nil
   146  	}
   147  	return errors.Trace(err)
   148  }
   149  
   150  // MarshalJSON implements the json.Marshaller interface.
   151  func (wh K8sValidatingWebhookSpec) MarshalJSON() ([]byte, error) {
   152  	switch wh.Version {
   153  	case K8sWebhookV1Beta1:
   154  		return json.Marshal(wh.SpecV1Beta1)
   155  	case K8sWebhookV1:
   156  		return json.Marshal(wh.SpecV1)
   157  	default:
   158  		return []byte{}, errors.NotSupportedf("validating webhook version %q", wh.Version)
   159  	}
   160  }
   161  
   162  func mutatingWebhookFromV1Beta1(whs []admissionregistrationv1beta1.MutatingWebhook) (o []K8sMutatingWebhookSpec) {
   163  	for _, wh := range whs {
   164  		o = append(o, K8sMutatingWebhookSpec{
   165  			Version:     K8sWebhookV1Beta1,
   166  			SpecV1Beta1: wh,
   167  		})
   168  	}
   169  	return o
   170  }
   171  
   172  func validatingWebhookFromV1Beta1(whs []admissionregistrationv1beta1.ValidatingWebhook) (o []K8sValidatingWebhookSpec) {
   173  	for _, wh := range whs {
   174  		o = append(o, K8sValidatingWebhookSpec{
   175  			Version:     K8sWebhookV1Beta1,
   176  			SpecV1Beta1: wh,
   177  		})
   178  	}
   179  	return o
   180  }
   181  
   182  // UpgradeK8sValidatingWebhookSpecV1Beta1 converts a v1beta1 ValidatingWebhook to v1.
   183  func UpgradeK8sValidatingWebhookSpecV1Beta1(spec admissionregistrationv1beta1.ValidatingWebhook) admissionregistrationv1.ValidatingWebhook {
   184  	hook := admissionregistrationv1.ValidatingWebhook{
   185  		Name:                    spec.Name,
   186  		NamespaceSelector:       spec.NamespaceSelector,
   187  		ObjectSelector:          spec.ObjectSelector,
   188  		TimeoutSeconds:          spec.TimeoutSeconds,
   189  		AdmissionReviewVersions: spec.AdmissionReviewVersions,
   190  		ClientConfig: admissionregistrationv1.WebhookClientConfig{
   191  			URL:      spec.ClientConfig.URL,
   192  			CABundle: spec.ClientConfig.CABundle,
   193  		},
   194  	}
   195  	if len(hook.AdmissionReviewVersions) == 0 {
   196  		hook.AdmissionReviewVersions = []string{"v1beta1"}
   197  	}
   198  	if spec.ClientConfig.Service != nil {
   199  		hook.ClientConfig.Service = &admissionregistrationv1.ServiceReference{
   200  			Namespace: spec.ClientConfig.Service.Namespace,
   201  			Name:      spec.ClientConfig.Service.Name,
   202  			Path:      spec.ClientConfig.Service.Path,
   203  			Port:      spec.ClientConfig.Service.Port,
   204  		}
   205  	}
   206  	if len(spec.Rules) > 0 {
   207  		for _, rule := range spec.Rules {
   208  			newRule := admissionregistrationv1.RuleWithOperations{
   209  				Rule: admissionregistrationv1.Rule{
   210  					APIGroups:   rule.APIGroups,
   211  					APIVersions: rule.APIVersions,
   212  					Resources:   rule.Resources,
   213  				},
   214  			}
   215  			if rule.Scope != nil {
   216  				scope := *rule.Scope
   217  				newRule.Scope = &scope
   218  			}
   219  			for _, op := range rule.Operations {
   220  				newRule.Operations = append(newRule.Operations, op)
   221  			}
   222  			hook.Rules = append(hook.Rules, newRule)
   223  		}
   224  	}
   225  	if spec.FailurePolicy != nil {
   226  		failurePolicy := admissionregistrationv1.FailurePolicyType(*spec.FailurePolicy)
   227  		hook.FailurePolicy = &failurePolicy
   228  	}
   229  	if spec.MatchPolicy != nil {
   230  		matchPolicy := admissionregistrationv1.MatchPolicyType(*spec.MatchPolicy)
   231  		hook.MatchPolicy = &matchPolicy
   232  	}
   233  	if spec.SideEffects != nil {
   234  		sideEffects := admissionregistrationv1.SideEffectClass(*spec.SideEffects)
   235  		hook.SideEffects = &sideEffects
   236  	}
   237  	return hook
   238  }
   239  
   240  // K8sMutatingWebhook defines spec for creating or updating an MutatingWebhook resource.
   241  type K8sMutatingWebhook struct {
   242  	Meta     `json:",inline" yaml:",inline"`
   243  	Webhooks []K8sMutatingWebhookSpec `json:"webhooks" yaml:"webhooks"`
   244  }
   245  
   246  // APIVersion returns the API version.
   247  func (w *K8sMutatingWebhook) APIVersion() APIVersion {
   248  	return w.Webhooks[0].Version
   249  }
   250  
   251  // Validate validates the spec.
   252  func (w K8sMutatingWebhook) Validate() error {
   253  	if err := w.Meta.Validate(); err != nil {
   254  		return errors.Trace(err)
   255  	}
   256  	if len(w.Webhooks) == 0 {
   257  		return errors.NotValidf("empty webhooks %q", w.Name)
   258  	}
   259  	ver := w.APIVersion()
   260  	for _, v := range w.Webhooks[1:] {
   261  		if v.Version != ver {
   262  			return errors.NewNotValid(nil, fmt.Sprintf("more than one version of webhooks in same spec, found %q and %q", ver, v.Version))
   263  		}
   264  	}
   265  	return nil
   266  }
   267  
   268  // K8sValidatingWebhook defines spec for creating or updating an ValidatingWebhook resource.
   269  type K8sValidatingWebhook struct {
   270  	Meta     `json:",inline" yaml:",inline"`
   271  	Webhooks []K8sValidatingWebhookSpec `json:"webhooks" yaml:"webhooks"`
   272  }
   273  
   274  // APIVersion returns the API version.
   275  func (w *K8sValidatingWebhook) APIVersion() APIVersion {
   276  	return w.Webhooks[0].Version
   277  }
   278  
   279  // Validate validates the spec.
   280  func (w *K8sValidatingWebhook) Validate() error {
   281  	if err := w.Meta.Validate(); err != nil {
   282  		return errors.Trace(err)
   283  	}
   284  	if len(w.Webhooks) == 0 {
   285  		return errors.NotValidf("empty webhooks %q", w.Name)
   286  	}
   287  	ver := w.APIVersion()
   288  	for _, v := range w.Webhooks[1:] {
   289  		if v.Version != ver {
   290  			return errors.NewNotValid(nil, fmt.Sprintf("more than one version of webhooks in same spec, found %q and %q", ver, v.Version))
   291  		}
   292  	}
   293  	return nil
   294  }