-
Notifications
You must be signed in to change notification settings - Fork 391
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Andy Goldstein <andy.goldstein@redhat.com>
- Loading branch information
Showing
52 changed files
with
3,695 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
--- | ||
apiVersion: apiextensions.k8s.io/v1 | ||
kind: CustomResourceDefinition | ||
metadata: | ||
annotations: | ||
controller-gen.kubebuilder.io/version: v0.10.0 | ||
creationTimestamp: null | ||
name: apiconversions.apis.kcp.io | ||
spec: | ||
group: apis.kcp.io | ||
names: | ||
categories: | ||
- kcp | ||
kind: APIConversion | ||
listKind: APIConversionList | ||
plural: apiconversions | ||
singular: apiconversion | ||
scope: Cluster | ||
versions: | ||
- additionalPrinterColumns: | ||
- jsonPath: .metadata.creationTimestamp | ||
name: Age | ||
type: date | ||
name: v1alpha1 | ||
schema: | ||
openAPIV3Schema: | ||
description: APIConversion contains rules to convert between different API | ||
versions in an APIResourceSchema. The name must match the name of the APIResourceSchema | ||
for the conversions to take effect. | ||
properties: | ||
apiVersion: | ||
description: 'APIVersion defines the versioned schema of this representation | ||
of an object. Servers should convert recognized schemas to the latest | ||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' | ||
type: string | ||
kind: | ||
description: 'Kind is a string value representing the REST resource this | ||
object represents. Servers may infer this from the endpoint the client | ||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' | ||
type: string | ||
metadata: | ||
type: object | ||
spec: | ||
description: Spec holds the desired state. | ||
properties: | ||
conversions: | ||
description: conversions specify rules to convert between different | ||
API versions in an APIResourceSchema. | ||
items: | ||
description: APIVersionConversion contains rules to convert between | ||
two specific API versions in an APIResourceSchema. Additionally, | ||
to avoid data loss when round-tripping from a version that contains | ||
a new field to one that doesn't and back again, you can specify | ||
a list of fields to preserve (these are stored in annotations). | ||
properties: | ||
from: | ||
description: from is the source version. | ||
minLength: 1 | ||
pattern: ^v[1-9][0-9]*([a-z]+[1-9][0-9]*)?$ | ||
type: string | ||
preserve: | ||
description: preserve contains a list of JSONPath expressions | ||
to fields to preserve in the originating version of the object, | ||
relative to its root, such as '.spec.name.first'. | ||
items: | ||
type: string | ||
type: array | ||
rules: | ||
description: rules contains field-specific conversion expressions. | ||
items: | ||
description: APIConversionRule specifies how to convert a | ||
single field. | ||
properties: | ||
destination: | ||
description: destination is a JSONPath expression to the | ||
field in the target version of the object, relative | ||
to its root, such as '.spec.name.first'. | ||
minLength: 1 | ||
type: string | ||
field: | ||
description: field is a JSONPath expression to the field | ||
in the originating version of the object, relative to | ||
its root, such as '.spec.name.first'. | ||
minLength: 1 | ||
type: string | ||
transformation: | ||
description: transformation is an optional CEL expression | ||
used to execute user-specified rules to transform the | ||
originating field -- identified by 'self' -- to the | ||
destination field. | ||
type: string | ||
required: | ||
- destination | ||
- field | ||
type: object | ||
type: array | ||
x-kubernetes-list-map-keys: | ||
- destination | ||
x-kubernetes-list-type: map | ||
to: | ||
description: to is the target version. | ||
minLength: 1 | ||
pattern: ^v[1-9][0-9]*([a-z]+[1-9][0-9]*)?$ | ||
type: string | ||
required: | ||
- from | ||
- rules | ||
- to | ||
type: object | ||
type: array | ||
x-kubernetes-list-map-keys: | ||
- from | ||
- to | ||
x-kubernetes-list-type: map | ||
required: | ||
- conversions | ||
type: object | ||
required: | ||
- metadata | ||
- spec | ||
type: object | ||
served: true | ||
storage: true | ||
subresources: {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
Copyright 2022 The KCP Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package apiconversion | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/kcp-dev/logicalcluster/v3" | ||
|
||
apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" | ||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" | ||
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/util/validation/field" | ||
"k8s.io/apiserver/pkg/admission" | ||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" | ||
|
||
"github.com/kcp-dev/kcp/pkg/admission/initializers" | ||
apisv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/apis/v1alpha1" | ||
kcpinformers "github.com/kcp-dev/kcp/pkg/client/informers/externalversions" | ||
"github.com/kcp-dev/kcp/pkg/conversion" | ||
"github.com/kcp-dev/kcp/pkg/reconciler/apis/apibinding" | ||
) | ||
|
||
const ( | ||
PluginName = "apis.kcp.io/APIConversion" | ||
) | ||
|
||
func Register(plugins *admission.Plugins) { | ||
plugins.Register(PluginName, | ||
func(_ io.Reader) (admission.Interface, error) { | ||
return &apiConversionAdmission{ | ||
Handler: admission.NewHandler(admission.Create, admission.Update), | ||
}, nil | ||
}) | ||
} | ||
|
||
type apiConversionAdmission struct { | ||
*admission.Handler | ||
|
||
getAPIResourceSchema func(clusterName logicalcluster.Name, name string) (*apisv1alpha1.APIResourceSchema, error) | ||
} | ||
|
||
// Ensure that the required admission interfaces are implemented. | ||
var ( | ||
_ admission.ValidationInterface = (*apiConversionAdmission)(nil) | ||
_ admission.InitializationValidator = (*apiConversionAdmission)(nil) | ||
_ initializers.WantsKcpInformers = (*apiConversionAdmission)(nil) | ||
) | ||
|
||
// Validate ensures all the conversion rules specified in an APIConversion are correct. | ||
func (o *apiConversionAdmission) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error { | ||
if a.GetResource().GroupResource() != apisv1alpha1.Resource("apiconversions") { | ||
return nil | ||
} | ||
|
||
cluster, err := genericapirequest.ValidClusterFrom(ctx) | ||
if err != nil { | ||
return admission.NewForbidden(a, fmt.Errorf("error determining workspace: %w", err)) | ||
} | ||
|
||
if cluster.Name == apibinding.SystemBoundCRDsClusterName { | ||
// TODO(ncdc): do we also want to validate these conversions? They've already been validated once (in their | ||
// original logical cluster). | ||
return nil | ||
} | ||
|
||
u, ok := a.GetObject().(*unstructured.Unstructured) | ||
if !ok { | ||
return fmt.Errorf("unexpected type %T", a.GetObject()) | ||
} | ||
|
||
apiResourceSchema, err := o.getAPIResourceSchema(cluster.Name, u.GetName()) | ||
if err != nil { | ||
return admission.NewForbidden(a, fmt.Errorf("error getting APIResourceSchema %s: %w", u.GetName(), err)) | ||
} | ||
|
||
apiConversion := &apisv1alpha1.APIConversion{} | ||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, apiConversion); err != nil { | ||
return fmt.Errorf("failed to convert unstructured to APIConversion: %w", err) | ||
} | ||
|
||
structuralSchemas := map[string]*structuralschema.Structural{} | ||
for i, v := range apiResourceSchema.Spec.Versions { | ||
schema, err := v.GetSchema() | ||
if err != nil { | ||
return admission.NewForbidden(a, field.Required(field.NewPath("spec", "versions").Index(i).Child("schema"), "is required")) | ||
} | ||
|
||
internalJSONSchemaProps := &apiextensionsinternal.JSONSchemaProps{} | ||
if err := apiextensionsv1.Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps(schema, internalJSONSchemaProps, nil); err != nil { | ||
return fmt.Errorf("failed converting version %s validation to internal version: %w", v.Name, err) | ||
} | ||
|
||
structuralSchema, err := structuralschema.NewStructural(internalJSONSchemaProps) | ||
if err != nil { | ||
return fmt.Errorf("error getting structural schema for version %s: %w", v.Name, err) | ||
} | ||
|
||
structuralSchemas[v.Name] = structuralSchema | ||
} | ||
|
||
if _, err := conversion.Compile(apiConversion, structuralSchemas); err != nil { | ||
return fmt.Errorf("error compiling conversion rules: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ValidateInitialization ensures the required injected fields are set. | ||
func (o *apiConversionAdmission) ValidateInitialization() error { | ||
if o.getAPIResourceSchema == nil { | ||
return fmt.Errorf("getAPIResourceSchema is unset") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (o *apiConversionAdmission) SetKcpInformers(local, global kcpinformers.SharedInformerFactory) { | ||
o.getAPIResourceSchema = func(clusterName logicalcluster.Name, name string) (*apisv1alpha1.APIResourceSchema, error) { | ||
apiResourceSchema, err := local.Apis().V1alpha1().APIResourceSchemas().Lister().Cluster(clusterName).Get(name) | ||
if err == nil { | ||
return apiResourceSchema, nil | ||
} | ||
return global.Apis().V1alpha1().APIResourceSchemas().Lister().Cluster(clusterName).Get(name) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.