294
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/crd.go
generated
vendored
294
vendor/sigs.k8s.io/controller-runtime/pkg/envtest/crd.go
generated
vendored
@@ -20,12 +20,16 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -34,18 +38,31 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// CRDInstallOptions are the options for installing CRDs
|
||||
// CRDInstallOptions are the options for installing CRDs.
|
||||
type CRDInstallOptions struct {
|
||||
// Scheme is used to determine if conversion webhooks should be enabled
|
||||
// for a particular CRD / object.
|
||||
//
|
||||
// Conversion webhooks are going to be enabled if an object in the scheme
|
||||
// implements Hub and Spoke conversions.
|
||||
//
|
||||
// If nil, scheme.Scheme is used.
|
||||
Scheme *runtime.Scheme
|
||||
|
||||
// Paths is a list of paths to the directories or files containing CRDs
|
||||
Paths []string
|
||||
|
||||
// CRDs is a list of CRDs to install
|
||||
CRDs []runtime.Object
|
||||
CRDs []client.Object
|
||||
|
||||
// ErrorIfPathMissing will cause an error if a Path does not exist
|
||||
ErrorIfPathMissing bool
|
||||
@@ -60,34 +77,45 @@ type CRDInstallOptions struct {
|
||||
// uninstalled when terminating the test environment.
|
||||
// Defaults to false.
|
||||
CleanUpAfterUse bool
|
||||
|
||||
// WebhookOptions contains the conversion webhook information to install
|
||||
// on the CRDs. This field is usually inherited by the EnvTest options.
|
||||
//
|
||||
// If you're passing this field manually, you need to make sure that
|
||||
// the CA information and host port is filled in properly.
|
||||
WebhookOptions WebhookInstallOptions
|
||||
}
|
||||
|
||||
const defaultPollInterval = 100 * time.Millisecond
|
||||
const defaultMaxWait = 10 * time.Second
|
||||
|
||||
// InstallCRDs installs a collection of CRDs into a cluster by reading the crd yaml files from a directory
|
||||
func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]runtime.Object, error) {
|
||||
// InstallCRDs installs a collection of CRDs into a cluster by reading the crd yaml files from a directory.
|
||||
func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]client.Object, error) {
|
||||
defaultCRDOptions(&options)
|
||||
|
||||
// Read the CRD yamls into options.CRDs
|
||||
if err := readCRDFiles(&options); err != nil {
|
||||
return nil, fmt.Errorf("unable to read CRD files: %w", err)
|
||||
}
|
||||
|
||||
if err := modifyConversionWebhooks(options.CRDs, options.Scheme, options.WebhookOptions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the CRDs in the apiserver
|
||||
if err := CreateCRDs(config, options.CRDs); err != nil {
|
||||
return options.CRDs, err
|
||||
return options.CRDs, fmt.Errorf("unable to create CRD instances: %w", err)
|
||||
}
|
||||
|
||||
// Wait for the CRDs to appear as Resources in the apiserver
|
||||
if err := WaitForCRDs(config, options.CRDs, options); err != nil {
|
||||
return options.CRDs, err
|
||||
return options.CRDs, fmt.Errorf("something went wrong waiting for CRDs to appear as API resources: %w", err)
|
||||
}
|
||||
|
||||
return options.CRDs, nil
|
||||
}
|
||||
|
||||
// readCRDFiles reads the directories of CRDs in options.Paths and adds the CRD structs to options.CRDs
|
||||
// readCRDFiles reads the directories of CRDs in options.Paths and adds the CRD structs to options.CRDs.
|
||||
func readCRDFiles(options *CRDInstallOptions) error {
|
||||
if len(options.Paths) > 0 {
|
||||
crdList, err := renderCRDs(options)
|
||||
@@ -100,8 +128,11 @@ func readCRDFiles(options *CRDInstallOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultCRDOptions sets the default values for CRDs
|
||||
// defaultCRDOptions sets the default values for CRDs.
|
||||
func defaultCRDOptions(o *CRDInstallOptions) {
|
||||
if o.Scheme == nil {
|
||||
o.Scheme = scheme.Scheme
|
||||
}
|
||||
if o.MaxTime == 0 {
|
||||
o.MaxTime = defaultMaxWait
|
||||
}
|
||||
@@ -110,8 +141,8 @@ func defaultCRDOptions(o *CRDInstallOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForCRDs waits for the CRDs to appear in discovery
|
||||
func WaitForCRDs(config *rest.Config, crds []runtime.Object, options CRDInstallOptions) error {
|
||||
// WaitForCRDs waits for the CRDs to appear in discovery.
|
||||
func WaitForCRDs(config *rest.Config, crds []client.Object, options CRDInstallOptions) error {
|
||||
// Add each CRD to a map of GroupVersion to Resource
|
||||
waitingFor := map[schema.GroupVersion]*sets.String{}
|
||||
for _, crd := range runtimeCRDListToUnstructured(crds) {
|
||||
@@ -128,14 +159,17 @@ func WaitForCRDs(config *rest.Config, crds []runtime.Object, options CRDInstallO
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if crdVersion != "" {
|
||||
gvs = append(gvs, schema.GroupVersion{Group: crdGroup, Version: crdVersion})
|
||||
}
|
||||
|
||||
versions, _, err := unstructured.NestedSlice(crd.Object, "spec", "versions")
|
||||
versions, found, err := unstructured.NestedSlice(crd.Object, "spec", "versions")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// gvs should be added here only if single version is found. If multiple version is found we will add those version
|
||||
// based on the version is served or not.
|
||||
if crdVersion != "" && !found {
|
||||
gvs = append(gvs, schema.GroupVersion{Group: crdGroup, Version: crdVersion})
|
||||
}
|
||||
|
||||
for _, version := range versions {
|
||||
versionMap, ok := version.(map[string]interface{})
|
||||
if !ok {
|
||||
@@ -170,7 +204,7 @@ func WaitForCRDs(config *rest.Config, crds []runtime.Object, options CRDInstallO
|
||||
return wait.PollImmediate(options.PollInterval, options.MaxTime, p.poll)
|
||||
}
|
||||
|
||||
// poller checks if all the resources have been found in discovery, and returns false if not
|
||||
// poller checks if all the resources have been found in discovery, and returns false if not.
|
||||
type poller struct {
|
||||
// config is used to get discovery
|
||||
config *rest.Config
|
||||
@@ -179,7 +213,7 @@ type poller struct {
|
||||
waitingFor map[schema.GroupVersion]*sets.String
|
||||
}
|
||||
|
||||
// poll checks if all the resources have been found in discovery, and returns false if not
|
||||
// poll checks if all the resources have been found in discovery, and returns false if not.
|
||||
func (p *poller) poll() (done bool, err error) {
|
||||
// Create a new clientset to avoid any client caching of discovery
|
||||
cs, err := clientset.NewForConfig(p.config)
|
||||
@@ -199,7 +233,7 @@ func (p *poller) poll() (done bool, err error) {
|
||||
// TODO: Maybe the controller-runtime client should be able to do this...
|
||||
resourceList, err := cs.Discovery().ServerResourcesForGroupVersion(gv.Group + "/" + gv.Version)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
return false, nil //nolint:nilerr
|
||||
}
|
||||
|
||||
// Remove each found resource from the resources set that we are waiting for
|
||||
@@ -215,9 +249,8 @@ func (p *poller) poll() (done bool, err error) {
|
||||
return allFound, nil
|
||||
}
|
||||
|
||||
// UninstallCRDs uninstalls a collection of CRDs by reading the crd yaml files from a directory
|
||||
// UninstallCRDs uninstalls a collection of CRDs by reading the crd yaml files from a directory.
|
||||
func UninstallCRDs(config *rest.Config, options CRDInstallOptions) error {
|
||||
|
||||
// Read the CRD yamls into options.CRDs
|
||||
if err := readCRDFiles(&options); err != nil {
|
||||
return err
|
||||
@@ -243,11 +276,11 @@ func UninstallCRDs(config *rest.Config, options CRDInstallOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCRDs creates the CRDs
|
||||
func CreateCRDs(config *rest.Config, crds []runtime.Object) error {
|
||||
// CreateCRDs creates the CRDs.
|
||||
func CreateCRDs(config *rest.Config, crds []client.Object) error {
|
||||
cs, err := client.New(config, client.Options{})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unable to create client: %w", err)
|
||||
}
|
||||
|
||||
// Create each CRD
|
||||
@@ -258,14 +291,19 @@ func CreateCRDs(config *rest.Config, crds []runtime.Object) error {
|
||||
switch {
|
||||
case apierrors.IsNotFound(err):
|
||||
if err := cs.Create(context.TODO(), crd); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("unable to create CRD %q: %w", crd.GetName(), err)
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
return fmt.Errorf("unable to get CRD %q to check if it exists: %w", crd.GetName(), err)
|
||||
default:
|
||||
log.V(1).Info("CRD already exists, updating", "crd", crd.GetName())
|
||||
crd.SetResourceVersion(existingCrd.GetResourceVersion())
|
||||
if err := cs.Update(context.TODO(), crd); err != nil {
|
||||
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err := cs.Get(context.TODO(), client.ObjectKey{Name: crd.GetName()}, existingCrd); err != nil {
|
||||
return err
|
||||
}
|
||||
crd.SetResourceVersion(existingCrd.GetResourceVersion())
|
||||
return cs.Update(context.TODO(), crd)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -274,7 +312,7 @@ func CreateCRDs(config *rest.Config, crds []runtime.Object) error {
|
||||
}
|
||||
|
||||
// renderCRDs iterate through options.Paths and extract all CRD files.
|
||||
func renderCRDs(options *CRDInstallOptions) ([]runtime.Object, error) {
|
||||
func renderCRDs(options *CRDInstallOptions) ([]client.Object, error) {
|
||||
var (
|
||||
err error
|
||||
info os.FileInfo
|
||||
@@ -301,10 +339,8 @@ func renderCRDs(options *CRDInstallOptions) ([]runtime.Object, error) {
|
||||
|
||||
if !info.IsDir() {
|
||||
filePath, files = filepath.Dir(path), []os.FileInfo{info}
|
||||
} else {
|
||||
if files, err = ioutil.ReadDir(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if files, err = ioutil.ReadDir(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.V(1).Info("reading CRDs from path", "path", path)
|
||||
@@ -325,14 +361,200 @@ func renderCRDs(options *CRDInstallOptions) ([]runtime.Object, error) {
|
||||
}
|
||||
|
||||
// Converting map to a list to return
|
||||
var res []runtime.Object
|
||||
res := []client.Object{}
|
||||
for _, obj := range crds {
|
||||
res = append(res, obj)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// readCRDs reads the CRDs from files and Unmarshals them into structs
|
||||
// modifyConversionWebhooks takes all the registered CustomResourceDefinitions and applies modifications
|
||||
// to conditionally enable webhooks if the type is registered within the scheme.
|
||||
//
|
||||
// The complexity of this function is high mostly due to all the edge cases that we need to handle:
|
||||
// CRDv1beta1, CRDv1, and their unstructured counterpart.
|
||||
//
|
||||
// We should be able to simplify this code once we drop support for v1beta1 and standardize around the typed CRDv1 object.
|
||||
func modifyConversionWebhooks(crds []client.Object, scheme *runtime.Scheme, webhookOptions WebhookInstallOptions) error { //nolint:gocyclo
|
||||
if len(webhookOptions.LocalServingCAData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine all registered convertible types.
|
||||
convertibles := map[schema.GroupKind]struct{}{}
|
||||
for gvk := range scheme.AllKnownTypes() {
|
||||
obj, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok, err := conversion.IsConvertible(scheme, obj); ok && err == nil {
|
||||
convertibles[gvk.GroupKind()] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// generate host port.
|
||||
hostPort, err := webhookOptions.generateHostPort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := pointer.StringPtr(fmt.Sprintf("https://%s/convert", hostPort))
|
||||
|
||||
for _, crd := range crds {
|
||||
switch c := crd.(type) {
|
||||
case *apiextensionsv1beta1.CustomResourceDefinition:
|
||||
// Continue if we're preserving unknown fields.
|
||||
//
|
||||
// preserveUnknownFields defaults to true if `nil` in v1beta1.
|
||||
if c.Spec.PreserveUnknownFields == nil || *c.Spec.PreserveUnknownFields {
|
||||
continue
|
||||
}
|
||||
// Continue if the GroupKind isn't registered as being convertible.
|
||||
if _, ok := convertibles[schema.GroupKind{
|
||||
Group: c.Spec.Group,
|
||||
Kind: c.Spec.Names.Kind,
|
||||
}]; !ok {
|
||||
continue
|
||||
}
|
||||
c.Spec.Conversion.Strategy = apiextensionsv1beta1.WebhookConverter
|
||||
c.Spec.Conversion.WebhookClientConfig.Service = nil
|
||||
c.Spec.Conversion.WebhookClientConfig = &apiextensionsv1beta1.WebhookClientConfig{
|
||||
Service: nil,
|
||||
URL: url,
|
||||
CABundle: webhookOptions.LocalServingCAData,
|
||||
}
|
||||
case *apiextensionsv1.CustomResourceDefinition:
|
||||
// Continue if we're preserving unknown fields.
|
||||
if c.Spec.PreserveUnknownFields {
|
||||
continue
|
||||
}
|
||||
// Continue if the GroupKind isn't registered as being convertible.
|
||||
if _, ok := convertibles[schema.GroupKind{
|
||||
Group: c.Spec.Group,
|
||||
Kind: c.Spec.Names.Kind,
|
||||
}]; !ok {
|
||||
continue
|
||||
}
|
||||
c.Spec.Conversion.Strategy = apiextensionsv1.WebhookConverter
|
||||
c.Spec.Conversion.Webhook.ClientConfig.Service = nil
|
||||
c.Spec.Conversion.Webhook.ClientConfig = &apiextensionsv1.WebhookClientConfig{
|
||||
Service: nil,
|
||||
URL: url,
|
||||
CABundle: webhookOptions.LocalServingCAData,
|
||||
}
|
||||
case *unstructured.Unstructured:
|
||||
webhookClientConfig := map[string]interface{}{
|
||||
"url": *url,
|
||||
"caBundle": base64.StdEncoding.EncodeToString(webhookOptions.LocalServingCAData),
|
||||
}
|
||||
|
||||
switch c.GroupVersionKind().Version {
|
||||
case "v1beta1":
|
||||
// Continue if we're preserving unknown fields.
|
||||
//
|
||||
// preserveUnknownFields defaults to true if `nil` in v1beta1.
|
||||
if preserve, found, err := unstructured.NestedBool(c.Object, "spec", "preserveUnknownFields"); preserve || !found {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Continue if the GroupKind isn't registered as being convertible.
|
||||
group, found, err := unstructured.NestedString(c.Object, "spec", "group")
|
||||
if !found {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
kind, found, err := unstructured.NestedString(c.Object, "spec", "names", "kind")
|
||||
if !found {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := convertibles[schema.GroupKind{
|
||||
Group: group,
|
||||
Kind: kind,
|
||||
}]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the strategy.
|
||||
if err := unstructured.SetNestedField(
|
||||
c.Object,
|
||||
string(apiextensionsv1beta1.WebhookConverter),
|
||||
"spec", "conversion", "strategy"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set the conversion review versions.
|
||||
if err := unstructured.SetNestedStringSlice(
|
||||
c.Object,
|
||||
[]string{"v1beta1"},
|
||||
"spec", "conversion", "webhook", "clientConfig"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set the client configuration.
|
||||
if err := unstructured.SetNestedMap(
|
||||
c.Object,
|
||||
webhookClientConfig,
|
||||
"spec", "conversion", "webhookClientConfig"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "v1":
|
||||
if preserve, _, err := unstructured.NestedBool(c.Object, "spec", "preserveUnknownFields"); preserve {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Continue if the GroupKind isn't registered as being convertible.
|
||||
group, found, err := unstructured.NestedString(c.Object, "spec", "group")
|
||||
if !found {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
kind, found, err := unstructured.NestedString(c.Object, "spec", "names", "kind")
|
||||
if !found {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := convertibles[schema.GroupKind{
|
||||
Group: group,
|
||||
Kind: kind,
|
||||
}]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the strategy.
|
||||
if err := unstructured.SetNestedField(
|
||||
c.Object,
|
||||
string(apiextensionsv1.WebhookConverter),
|
||||
"spec", "conversion", "strategy"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set the conversion review versions.
|
||||
if err := unstructured.SetNestedStringSlice(
|
||||
c.Object,
|
||||
[]string{"v1", "v1beta1"},
|
||||
"spec", "conversion", "webhook", "conversionReviewVersions"); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set the client configuration.
|
||||
if err := unstructured.SetNestedMap(
|
||||
c.Object,
|
||||
webhookClientConfig,
|
||||
"spec", "conversion", "webhook", "clientConfig"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readCRDs reads the CRDs from files and Unmarshals them into structs.
|
||||
func readCRDs(basePath string, files []os.FileInfo) ([]*unstructured.Unstructured, error) {
|
||||
var crds []*unstructured.Unstructured
|
||||
|
||||
@@ -378,9 +600,9 @@ func readCRDs(basePath string, files []os.FileInfo) ([]*unstructured.Unstructure
|
||||
return crds, nil
|
||||
}
|
||||
|
||||
// readDocuments reads documents from file
|
||||
// readDocuments reads documents from file.
|
||||
func readDocuments(fp string) ([][]byte, error) {
|
||||
b, err := ioutil.ReadFile(fp)
|
||||
b, err := ioutil.ReadFile(fp) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user