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 }