github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/specs/customresourcedefinition.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 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 12 apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 13 ) 14 15 const ( 16 // K8sCustomResourceDefinitionV1Beta1 defines the v1beta1 API version for custom resource definition. 17 K8sCustomResourceDefinitionV1Beta1 APIVersion = "v1beta1" 18 19 // K8sCustomResourceDefinitionV1 defines the v1 API version for custom resource definition. 20 K8sCustomResourceDefinitionV1 APIVersion = "v1" 21 ) 22 23 // K8sCustomResourceDefinitionSpec defines the spec details of CustomResourceDefinition with the API version. 24 type K8sCustomResourceDefinitionSpec struct { 25 Version APIVersion 26 SpecV1Beta1 apiextensionsv1beta1.CustomResourceDefinitionSpec 27 SpecV1 apiextensionsv1.CustomResourceDefinitionSpec 28 } 29 30 // UpgradeCustomResourceDefinitionSpecV1Beta1 converts a v1beta1 CustomResourceDefinition to v1. 31 func UpgradeCustomResourceDefinitionSpecV1Beta1(spec apiextensionsv1beta1.CustomResourceDefinitionSpec) (apiextensionsv1.CustomResourceDefinitionSpec, error) { 32 out := apiextensionsv1.CustomResourceDefinitionSpec{ 33 Group: spec.Group, 34 Scope: apiextensionsv1.ResourceScope(spec.Scope), 35 Names: apiextensionsv1.CustomResourceDefinitionNames{ 36 Plural: spec.Names.Plural, 37 Singular: spec.Names.Singular, 38 ShortNames: spec.Names.ShortNames, 39 Kind: spec.Names.Kind, 40 ListKind: spec.Names.ListKind, 41 Categories: spec.Names.Categories, 42 }, 43 } 44 if spec.Version != "" && len(spec.Versions) == 0 { 45 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.NotValidf("custom resource definition group %q", spec.Group) 46 } 47 if spec.Versions != nil { 48 for _, v := range spec.Versions { 49 crd := apiextensionsv1.CustomResourceDefinitionVersion{ 50 Name: v.Name, 51 Served: v.Served, 52 Storage: v.Storage, 53 Deprecated: v.Deprecated, 54 DeprecationWarning: v.DeprecationWarning, 55 } 56 if v.Schema != nil { 57 schemaBytes, err := json.Marshal(v.Schema) 58 if err != nil { 59 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 60 } 61 schema := apiextensionsv1.CustomResourceValidation{} 62 err = json.Unmarshal(schemaBytes, &schema) 63 if err != nil { 64 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 65 } 66 crd.Schema = &schema 67 } 68 if v.Subresources != nil { 69 subresourceBytes, err := json.Marshal(v.Subresources) 70 if err != nil { 71 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 72 } 73 subresource := apiextensionsv1.CustomResourceSubresources{} 74 err = json.Unmarshal(subresourceBytes, &subresource) 75 if err != nil { 76 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 77 } 78 crd.Subresources = &subresource 79 } 80 if len(v.AdditionalPrinterColumns) > 0 { 81 apcBytes, err := json.Marshal(v.AdditionalPrinterColumns) 82 if err != nil { 83 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 84 } 85 var apc []apiextensionsv1.CustomResourceColumnDefinition 86 err = json.Unmarshal(apcBytes, &apc) 87 if err != nil { 88 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 89 } 90 crd.AdditionalPrinterColumns = apc 91 } 92 out.Versions = append(out.Versions, crd) 93 } 94 } 95 if spec.PreserveUnknownFields != nil { 96 out.PreserveUnknownFields = *spec.PreserveUnknownFields 97 } 98 if spec.Conversion != nil { 99 conversion := apiextensionsv1.CustomResourceConversion{ 100 Strategy: apiextensionsv1.ConversionStrategyType(spec.Conversion.Strategy), 101 } 102 if spec.Conversion.WebhookClientConfig != nil { 103 conversion.Webhook = &apiextensionsv1.WebhookConversion{ 104 ConversionReviewVersions: spec.Conversion.ConversionReviewVersions, 105 ClientConfig: &apiextensionsv1.WebhookClientConfig{ 106 URL: spec.Conversion.WebhookClientConfig.URL, 107 CABundle: spec.Conversion.WebhookClientConfig.CABundle, 108 }, 109 } 110 if spec.Conversion.WebhookClientConfig.Service != nil { 111 conversion.Webhook.ClientConfig.Service = &apiextensionsv1.ServiceReference{ 112 Namespace: spec.Conversion.WebhookClientConfig.Service.Namespace, 113 Name: spec.Conversion.WebhookClientConfig.Service.Name, 114 Path: spec.Conversion.WebhookClientConfig.Service.Path, 115 Port: spec.Conversion.WebhookClientConfig.Service.Port, 116 } 117 } 118 } 119 out.Conversion = &conversion 120 } 121 if spec.Validation != nil { 122 schemaBytes, err := json.Marshal(spec.Validation) 123 if err != nil { 124 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 125 } 126 schema := apiextensionsv1.CustomResourceValidation{} 127 err = json.Unmarshal(schemaBytes, &schema) 128 if err != nil { 129 return apiextensionsv1.CustomResourceDefinitionSpec{}, errors.Trace(err) 130 } 131 for i, ver := range out.Versions { 132 if ver.Schema == nil { 133 ver.Schema = &schema 134 } 135 out.Versions[i] = ver 136 } 137 } 138 return out, nil 139 } 140 141 // UnmarshalJSON implements the json.Unmarshaller interface. 142 func (crdSpecs *K8sCustomResourceDefinitionSpec) UnmarshalJSON(value []byte) (err error) { 143 err = unmarshalJSONStrict(value, &crdSpecs.SpecV1) 144 if err == nil { 145 crdSpecs.Version = K8sCustomResourceDefinitionV1 146 return nil 147 } 148 if err2 := unmarshalJSONStrict(value, &crdSpecs.SpecV1Beta1); err2 == nil { 149 crdSpecs.Version = K8sCustomResourceDefinitionV1Beta1 150 return nil 151 } 152 return errors.Trace(err) 153 } 154 155 // MarshalJSON implements the json.Marshaller interface. 156 func (crdSpecs K8sCustomResourceDefinitionSpec) MarshalJSON() ([]byte, error) { 157 switch crdSpecs.Version { 158 case K8sCustomResourceDefinitionV1Beta1: 159 return json.Marshal(crdSpecs.SpecV1Beta1) 160 case K8sCustomResourceDefinitionV1: 161 return json.Marshal(crdSpecs.SpecV1) 162 default: 163 return []byte{}, errors.NotSupportedf("custom resource definition version %q", crdSpecs.Version) 164 } 165 } 166 167 // Validate validates the spec. 168 func (crdSpecs K8sCustomResourceDefinitionSpec) Validate(name string) error { 169 switch crdSpecs.Version { 170 case K8sCustomResourceDefinitionV1Beta1: 171 if crdSpecs.SpecV1Beta1.Scope != apiextensionsv1beta1.NamespaceScoped && crdSpecs.SpecV1Beta1.Scope != apiextensionsv1beta1.ClusterScoped { 172 return errors.NewNotSupported(nil, 173 fmt.Sprintf("custom resource definition %q scope %q is not supported, please use %q or %q scope", 174 name, crdSpecs.SpecV1Beta1.Scope, apiextensionsv1beta1.NamespaceScoped, apiextensionsv1beta1.ClusterScoped), 175 ) 176 } 177 case K8sCustomResourceDefinitionV1: 178 if crdSpecs.SpecV1.Scope != apiextensionsv1.NamespaceScoped && crdSpecs.SpecV1.Scope != apiextensionsv1.ClusterScoped { 179 return errors.NewNotSupported(nil, 180 fmt.Sprintf("custom resource definition %q scope %q is not supported, please use %q or %q scope", 181 name, crdSpecs.SpecV1.Scope, apiextensionsv1.NamespaceScoped, apiextensionsv1.ClusterScoped), 182 ) 183 } 184 default: 185 return errors.NotSupportedf("custom resource definition %q version %q", name, crdSpecs.Version) 186 } 187 return nil 188 } 189 190 // K8sCustomResourceDefinition defines spec for creating or updating an CustomResourceDefinition resource. 191 type K8sCustomResourceDefinition struct { 192 Meta `json:",inline" yaml:",inline"` 193 Spec K8sCustomResourceDefinitionSpec `json:"spec" yaml:"spec"` 194 } 195 196 // Validate validates the spec. 197 func (crd K8sCustomResourceDefinition) Validate() error { 198 if err := crd.Meta.Validate(); err != nil { 199 return errors.Trace(err) 200 } 201 202 if err := crd.Spec.Validate(crd.Name); err != nil { 203 return errors.Trace(err) 204 } 205 return nil 206 }