mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-09 01:16:55 +00:00
190 lines
7.9 KiB
Go
190 lines
7.9 KiB
Go
/*
|
|
Copyright 2018 The Kubernetes 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 integration
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/dynamic"
|
|
)
|
|
|
|
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
|
|
|
func instantiateCustomResource(t *testing.T, instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1beta1.CustomResourceDefinition) (*unstructured.Unstructured, error) {
|
|
return instantiateVersionedCustomResource(t, instanceToCreate, client, definition, definition.Spec.Versions[0].Name)
|
|
}
|
|
|
|
func instantiateVersionedCustomResource(t *testing.T, instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1beta1.CustomResourceDefinition, version string) (*unstructured.Unstructured, error) {
|
|
createdInstance, err := client.Create(instanceToCreate, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Logf("%#v", createdInstance)
|
|
return nil, err
|
|
}
|
|
createdObjectMeta, err := meta.Accessor(createdInstance)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// it should have a UUID
|
|
if len(createdObjectMeta.GetUID()) == 0 {
|
|
t.Errorf("missing uuid: %#v", createdInstance)
|
|
}
|
|
createdTypeMeta, err := meta.TypeAccessor(createdInstance)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if e, a := definition.Spec.Group+"/"+version, createdTypeMeta.GetAPIVersion(); e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
if e, a := definition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a {
|
|
t.Errorf("expected %v, got %v", e, a)
|
|
}
|
|
return createdInstance, nil
|
|
}
|
|
|
|
func newNamespacedCustomResourceVersionedClient(ns string, client dynamic.Interface, crd *apiextensionsv1beta1.CustomResourceDefinition, version string) dynamic.ResourceInterface {
|
|
gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: version, Resource: crd.Spec.Names.Plural}
|
|
|
|
if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped {
|
|
return client.Resource(gvr).Namespace(ns)
|
|
}
|
|
return client.Resource(gvr)
|
|
}
|
|
|
|
func newNamespacedCustomResourceClient(ns string, client dynamic.Interface, crd *apiextensionsv1beta1.CustomResourceDefinition) dynamic.ResourceInterface {
|
|
return newNamespacedCustomResourceVersionedClient(ns, client, crd, crd.Spec.Versions[0].Name)
|
|
}
|
|
|
|
// UpdateCustomResourceDefinitionWithRetry updates a CRD, retrying up to 5 times on version conflict errors.
|
|
func UpdateCustomResourceDefinitionWithRetry(client clientset.Interface, name string, update func(*apiextensionsv1beta1.CustomResourceDefinition)) (*apiextensionsv1beta1.CustomResourceDefinition, error) {
|
|
for i := 0; i < 5; i++ {
|
|
crd, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get CustomResourceDefinition %q: %v", name, err)
|
|
}
|
|
update(crd)
|
|
crd, err = client.ApiextensionsV1beta1().CustomResourceDefinitions().Update(crd)
|
|
if err == nil {
|
|
return crd, nil
|
|
}
|
|
if !errors.IsConflict(err) {
|
|
return nil, fmt.Errorf("failed to update CustomResourceDefinition %q: %v", name, err)
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("too many retries after conflicts updating CustomResourceDefinition %q", name)
|
|
}
|
|
|
|
// getSchemaForVersion returns the validation schema for given version in given CRD.
|
|
func getSchemaForVersion(crd *apiextensionsv1beta1.CustomResourceDefinition, version string) (*apiextensionsv1beta1.CustomResourceValidation, error) {
|
|
if !hasPerVersionSchema(crd.Spec.Versions) {
|
|
return crd.Spec.Validation, nil
|
|
}
|
|
if crd.Spec.Validation != nil {
|
|
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version schemas must be mutual exclusive", crd.Name, version)
|
|
}
|
|
for _, v := range crd.Spec.Versions {
|
|
if version == v.Name {
|
|
return v.Schema, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
|
}
|
|
|
|
// getSubresourcesForVersion returns the subresources for given version in given CRD.
|
|
func getSubresourcesForVersion(crd *apiextensionsv1beta1.CustomResourceDefinition, version string) (*apiextensionsv1beta1.CustomResourceSubresources, error) {
|
|
if !hasPerVersionSubresources(crd.Spec.Versions) {
|
|
return crd.Spec.Subresources, nil
|
|
}
|
|
if crd.Spec.Subresources != nil {
|
|
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version subresources must be mutual exclusive", crd.Name, version)
|
|
}
|
|
for _, v := range crd.Spec.Versions {
|
|
if version == v.Name {
|
|
return v.Subresources, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
|
}
|
|
|
|
// getColumnsForVersion returns the columns for given version in given CRD.
|
|
// NOTE: the newly logically-defaulted columns is not pointing to the original CRD object.
|
|
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
|
|
// the original CRD object instead.
|
|
func getColumnsForVersion(crd *apiextensionsv1beta1.CustomResourceDefinition, version string) ([]apiextensionsv1beta1.CustomResourceColumnDefinition, error) {
|
|
if !hasPerVersionColumns(crd.Spec.Versions) {
|
|
return serveDefaultColumnsIfEmpty(crd.Spec.AdditionalPrinterColumns), nil
|
|
}
|
|
if len(crd.Spec.AdditionalPrinterColumns) > 0 {
|
|
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version additionalPrinterColumns must be mutual exclusive", crd.Name, version)
|
|
}
|
|
for _, v := range crd.Spec.Versions {
|
|
if version == v.Name {
|
|
return serveDefaultColumnsIfEmpty(v.AdditionalPrinterColumns), nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
|
}
|
|
|
|
// serveDefaultColumnsIfEmpty applies logically defaulting to columns, if the input columns is empty.
|
|
// NOTE: in this way, the newly logically-defaulted columns is not pointing to the original CRD object.
|
|
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
|
|
// the original CRD object instead.
|
|
func serveDefaultColumnsIfEmpty(columns []apiextensionsv1beta1.CustomResourceColumnDefinition) []apiextensionsv1beta1.CustomResourceColumnDefinition {
|
|
if len(columns) > 0 {
|
|
return columns
|
|
}
|
|
return []apiextensionsv1beta1.CustomResourceColumnDefinition{
|
|
{Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"},
|
|
}
|
|
}
|
|
|
|
// hasPerVersionSchema returns true if a CRD uses per-version schema.
|
|
func hasPerVersionSchema(versions []apiextensionsv1beta1.CustomResourceDefinitionVersion) bool {
|
|
for _, v := range versions {
|
|
if v.Schema != nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// hasPerVersionSubresources returns true if a CRD uses per-version subresources.
|
|
func hasPerVersionSubresources(versions []apiextensionsv1beta1.CustomResourceDefinitionVersion) bool {
|
|
for _, v := range versions {
|
|
if v.Subresources != nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// hasPerVersionColumns returns true if a CRD uses per-version columns.
|
|
func hasPerVersionColumns(versions []apiextensionsv1beta1.CustomResourceDefinitionVersion) bool {
|
|
for _, v := range versions {
|
|
if len(v.AdditionalPrinterColumns) > 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|