diff --git a/Makefile b/Makefile index 97da96ce913b..49e71b4e0cef 100644 --- a/Makefile +++ b/Makefile @@ -103,12 +103,10 @@ help: ## Display this help .PHONY: test test: ## Run tests - ## TODO(vincepri): Remove the fetch for external binaries once kubebuilder has a release. source ./scripts/fetch_ext_bins.sh; fetch_tools; setup_envs; go test -v ./... .PHONY: test-integration test-integration: ## Run integration tests - ## TODO(vincepri): Remove the fetch for external binaries once kubebuilder has a release. source ./scripts/fetch_ext_bins.sh; fetch_tools; setup_envs; go test -v -tags=integration ./test/integration/... .PHONY: test-capd-e2e-full diff --git a/bootstrap/kubeadm/config/crd/kustomization.yaml b/bootstrap/kubeadm/config/crd/kustomization.yaml index 7ee4471d53c5..00d1a9dfb892 100644 --- a/bootstrap/kubeadm/config/crd/kustomization.yaml +++ b/bootstrap/kubeadm/config/crd/kustomization.yaml @@ -1,3 +1,7 @@ +commonLabels: + cluster.x-k8s.io/v1alpha2: v1alpha2 + cluster.x-k8s.io/v1alpha3: v1alpha3 + # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/ diff --git a/cmd/clusterctl/pkg/client/cluster/inventory.go b/cmd/clusterctl/pkg/client/cluster/inventory.go index 064a389960d2..bd16d3224452 100644 --- a/cmd/clusterctl/pkg/client/cluster/inventory.go +++ b/cmd/clusterctl/pkg/client/cluster/inventory.go @@ -20,7 +20,7 @@ import ( "time" "github.com/pkg/errors" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/util/sets" diff --git a/cmd/clusterctl/pkg/client/cluster/objectgraph.go b/cmd/clusterctl/pkg/client/cluster/objectgraph.go index 455057fcbd7a..92212fe9cf98 100644 --- a/cmd/clusterctl/pkg/client/cluster/objectgraph.go +++ b/cmd/clusterctl/pkg/client/cluster/objectgraph.go @@ -21,7 +21,7 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/cmd/clusterctl/pkg/internal/scheme/scheme.go b/cmd/clusterctl/pkg/internal/scheme/scheme.go index 52d9db211b22..e492b920ac5c 100644 --- a/cmd/clusterctl/pkg/internal/scheme/scheme.go +++ b/cmd/clusterctl/pkg/internal/scheme/scheme.go @@ -17,7 +17,7 @@ limitations under the License. package scheme import ( - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" diff --git a/cmd/clusterctl/pkg/internal/test/fake_objects.go b/cmd/clusterctl/pkg/internal/test/fake_objects.go index 435e7f82b8d4..d713d7c58207 100644 --- a/cmd/clusterctl/pkg/internal/test/fake_objects.go +++ b/cmd/clusterctl/pkg/internal/test/fake_objects.go @@ -21,7 +21,7 @@ import ( "strings" corev1 "k8s.io/api/core/v1" - apiextensionslv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionslv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" diff --git a/cmd/clusterctl/pkg/internal/test/fake_proxy.go b/cmd/clusterctl/pkg/internal/test/fake_proxy.go index 4dbd4655e948..4fb78e323110 100644 --- a/cmd/clusterctl/pkg/internal/test/fake_proxy.go +++ b/cmd/clusterctl/pkg/internal/test/fake_proxy.go @@ -17,7 +17,7 @@ limitations under the License. package test import ( - apiextensionslv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiextensionslv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" diff --git a/controllers/cluster_controller_phases.go b/controllers/cluster_controller_phases.go index 7cfc6ede464f..1099ffc3be61 100644 --- a/controllers/cluster_controller_phases.go +++ b/controllers/cluster_controller_phases.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/cluster-api/controllers/external" capierrors "sigs.k8s.io/cluster-api/errors" "sigs.k8s.io/cluster-api/util" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/cluster-api/util/kubeconfig" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/cluster-api/util/secret" @@ -62,6 +63,10 @@ func (r *ClusterReconciler) reconcilePhase(_ context.Context, cluster *clusterv1 func (r *ClusterReconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) (external.ReconcileOutput, error) { logger := r.Log.WithValues("cluster", cluster.Name, "namespace", cluster.Namespace) + if err := utilconversion.ConvertReferenceAPIContract(ctx, r.Client, ref); err != nil { + return external.ReconcileOutput{}, err + } + obj, err := external.Get(ctx, r.Client, ref, cluster.Namespace) if err != nil { if apierrors.IsNotFound(errors.Cause(err)) { diff --git a/controllers/cluster_controller_phases_test.go b/controllers/cluster_controller_phases_test.go index f81fd52ed92d..2bf4b5c0dcda 100644 --- a/controllers/cluster_controller_phases_test.go +++ b/controllers/cluster_controller_phases_test.go @@ -23,10 +23,12 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/kubernetes/scheme" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + "sigs.k8s.io/cluster-api/controllers/external" capierrors "sigs.k8s.io/cluster-api/errors" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -50,7 +52,7 @@ func TestClusterReconcilePhases(t *testing.T) { }, InfrastructureRef: &corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "test", }, }, @@ -76,7 +78,7 @@ func TestClusterReconcilePhases(t *testing.T) { name: "returns no error if infra config is marked for deletion", cluster: cluster, infraRef: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "test", @@ -90,7 +92,7 @@ func TestClusterReconcilePhases(t *testing.T) { name: "returns no error if infrastructure is marked ready on cluster", cluster: cluster, infraRef: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "test", @@ -104,7 +106,7 @@ func TestClusterReconcilePhases(t *testing.T) { name: "returns error if infrastructure has the paused annotation", cluster: cluster, infraRef: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "test", @@ -122,13 +124,14 @@ func TestClusterReconcilePhases(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) g.Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) + g.Expect(apiextensionsv1.AddToScheme(scheme.Scheme)).To(Succeed()) var c client.Client if tt.infraRef != nil { infraConfig := &unstructured.Unstructured{Object: tt.infraRef} - c = fake.NewFakeClientWithScheme(scheme.Scheme, tt.cluster, infraConfig) + c = fake.NewFakeClientWithScheme(scheme.Scheme, external.TestGenericInfrastructureCRD, tt.cluster, infraConfig) } else { - c = fake.NewFakeClientWithScheme(scheme.Scheme, tt.cluster) + c = fake.NewFakeClientWithScheme(scheme.Scheme, external.TestGenericInfrastructureCRD, tt.cluster) } r := &ClusterReconciler{ Client: c, diff --git a/controllers/external/testing.go b/controllers/external/testing.go index 6b4e43224a6c..762588ce2064 100644 --- a/controllers/external/testing.go +++ b/controllers/external/testing.go @@ -20,6 +20,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" ) var ( @@ -30,6 +31,9 @@ var ( }, ObjectMeta: metav1.ObjectMeta{ Name: "genericmachines.bootstrap.cluster.x-k8s.io", + Labels: map[string]string{ + clusterv1.GroupVersion.String(): "v1alpha3", + }, }, Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Group: "bootstrap.cluster.x-k8s.io", @@ -69,6 +73,9 @@ var ( }, ObjectMeta: metav1.ObjectMeta{ Name: "genericmachinetemplates.bootstrap.cluster.x-k8s.io", + Labels: map[string]string{ + clusterv1.GroupVersion.String(): "v1alpha3", + }, }, Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Group: "bootstrap.cluster.x-k8s.io", @@ -108,6 +115,9 @@ var ( }, ObjectMeta: metav1.ObjectMeta{ Name: "genericmachines.infrastructure.cluster.x-k8s.io", + Labels: map[string]string{ + clusterv1.GroupVersion.String(): "v1alpha3", + }, }, Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Group: "infrastructure.cluster.x-k8s.io", @@ -147,6 +157,9 @@ var ( }, ObjectMeta: metav1.ObjectMeta{ Name: "genericmachinetemplates.infrastructure.cluster.x-k8s.io", + Labels: map[string]string{ + clusterv1.GroupVersion.String(): "v1alpha3", + }, }, Spec: apiextensionsv1.CustomResourceDefinitionSpec{ Group: "infrastructure.cluster.x-k8s.io", diff --git a/controllers/machine_controller_phases.go b/controllers/machine_controller_phases.go index 6c2fdb8efc89..43b78f85d7ce 100644 --- a/controllers/machine_controller_phases.go +++ b/controllers/machine_controller_phases.go @@ -27,6 +27,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/utils/pointer" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -77,6 +78,10 @@ func (r *MachineReconciler) reconcilePhase(_ context.Context, m *clusterv1.Machi func (r *MachineReconciler) reconcileExternal(ctx context.Context, cluster *clusterv1.Cluster, m *clusterv1.Machine, ref *corev1.ObjectReference) (external.ReconcileOutput, error) { logger := r.Log.WithValues("machine", m.Name, "namespace", m.Namespace) + if err := utilconversion.ConvertReferenceAPIContract(ctx, r.Client, ref); err != nil { + return external.ReconcileOutput{}, err + } + obj, err := external.Get(ctx, r.Client, ref, m.Namespace) if err != nil { if apierrors.IsNotFound(errors.Cause(err)) { @@ -146,6 +151,7 @@ func (r *MachineReconciler) reconcileBootstrap(ctx context.Context, cluster *clu // Call generic external reconciler if we have an external reference. var bootstrapConfig *unstructured.Unstructured if m.Spec.Bootstrap.ConfigRef != nil { + bootstrapReconcileResult, err := r.reconcileExternal(ctx, cluster, m, m.Spec.Bootstrap.ConfigRef) if err != nil { return err diff --git a/controllers/machine_controller_phases_test.go b/controllers/machine_controller_phases_test.go index ef863d143363..be01b897b457 100644 --- a/controllers/machine_controller_phases_test.go +++ b/controllers/machine_controller_phases_test.go @@ -31,6 +31,7 @@ import ( "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/pointer" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + "sigs.k8s.io/cluster-api/controllers/external" "sigs.k8s.io/cluster-api/util/kubeconfig" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log" @@ -64,13 +65,13 @@ var _ = Describe("Reconcile Machine Phases", func() { Bootstrap: clusterv1.Bootstrap{ ConfigRef: &corev1.ObjectReference{ APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3", - Kind: "BootstrapConfig", + Kind: "BootstrapMachine", Name: "bootstrap-config1", }, }, InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, }, @@ -78,7 +79,7 @@ var _ = Describe("Reconcile Machine Phases", func() { defaultBootstrap := &unstructured.Unstructured{ Object: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -91,7 +92,7 @@ var _ = Describe("Reconcile Machine Phases", func() { defaultInfra := &unstructured.Unstructured{ Object: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "infra-config1", @@ -112,7 +113,15 @@ var _ = Describe("Reconcile Machine Phases", func() { infraConfig := defaultInfra.DeepCopy() r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -140,7 +149,15 @@ var _ = Describe("Reconcile Machine Phases", func() { infraConfig := defaultInfra.DeepCopy() r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -166,7 +183,15 @@ var _ = Describe("Reconcile Machine Phases", func() { Expect(err).NotTo(HaveOccurred()) r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -217,7 +242,15 @@ var _ = Describe("Reconcile Machine Phases", func() { machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"} r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -255,7 +288,15 @@ var _ = Describe("Reconcile Machine Phases", func() { machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"} r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -304,7 +345,15 @@ var _ = Describe("Reconcile Machine Phases", func() { machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"} r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -333,7 +382,15 @@ var _ = Describe("Reconcile Machine Phases", func() { machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"} r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -384,7 +441,15 @@ var _ = Describe("Reconcile Machine Phases", func() { machine.SetDeletionTimestamp(&deletionTimestamp) r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, defaultCluster, defaultKubeconfigSecret, machine, bootstrapConfig, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + defaultCluster, + defaultKubeconfigSecret, + machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -411,7 +476,7 @@ func TestReconcileBootstrap(t *testing.T) { Bootstrap: clusterv1.Bootstrap{ ConfigRef: &corev1.ObjectReference{ APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3", - Kind: "BootstrapConfig", + Kind: "BootstrapMachine", Name: "bootstrap-config1", }, }, @@ -435,7 +500,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "new machine, bootstrap config ready with data", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -457,7 +522,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "new machine, bootstrap config ready with no data", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -477,7 +542,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "new machine, bootstrap config not ready", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -494,7 +559,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "new machine, bootstrap config is not found", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -511,7 +576,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "new machine, no bootstrap config or data", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -525,7 +590,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "existing machine, bootstrap data should not change", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -546,7 +611,7 @@ func TestReconcileBootstrap(t *testing.T) { Bootstrap: clusterv1.Bootstrap{ ConfigRef: &corev1.ObjectReference{ APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3", - Kind: "BootstrapConfig", + Kind: "BootstrapMachine", Name: "bootstrap-config1", }, Data: pointer.StringPtr("#!/bin/bash ... data"), @@ -565,7 +630,7 @@ func TestReconcileBootstrap(t *testing.T) { { name: "existing machine, bootstrap provider is to not ready", bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -586,7 +651,7 @@ func TestReconcileBootstrap(t *testing.T) { Bootstrap: clusterv1.Bootstrap{ ConfigRef: &corev1.ObjectReference{ APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3", - Kind: "BootstrapConfig", + Kind: "BootstrapMachine", Name: "bootstrap-config1", }, Data: pointer.StringPtr("#!/bin/bash ... data"), @@ -615,7 +680,12 @@ func TestReconcileBootstrap(t *testing.T) { bootstrapConfig := &unstructured.Unstructured{Object: tc.bootstrapConfig} r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, tc.machine, bootstrapConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + tc.machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + bootstrapConfig, + ), Log: log.Log, scheme: scheme.Scheme, } @@ -647,13 +717,13 @@ func TestReconcileInfrastructure(t *testing.T) { Bootstrap: clusterv1.Bootstrap{ ConfigRef: &corev1.ObjectReference{ APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3", - Kind: "BootstrapConfig", + Kind: "BootstrapMachine", Name: "bootstrap-config1", }, }, InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, }, @@ -679,7 +749,7 @@ func TestReconcileInfrastructure(t *testing.T) { { name: "new machine, infrastructure config ready", infraConfig: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "infra-config1", @@ -719,13 +789,13 @@ func TestReconcileInfrastructure(t *testing.T) { Bootstrap: clusterv1.Bootstrap{ ConfigRef: &corev1.ObjectReference{ APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha3", - Kind: "BootstrapConfig", + Kind: "BootstrapMachine", Name: "bootstrap-config1", }, }, InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, }, @@ -736,7 +806,7 @@ func TestReconcileInfrastructure(t *testing.T) { }, }, bootstrapConfig: map[string]interface{}{ - "kind": "BootstrapConfig", + "kind": "BootstrapMachine", "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "bootstrap-config1", @@ -749,7 +819,7 @@ func TestReconcileInfrastructure(t *testing.T) { }, }, infraConfig: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{}, }, @@ -765,7 +835,7 @@ func TestReconcileInfrastructure(t *testing.T) { { name: "infrastructure ref is paused", infraConfig: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "infra-config1", @@ -811,7 +881,12 @@ func TestReconcileInfrastructure(t *testing.T) { infraConfig := &unstructured.Unstructured{Object: tc.infraConfig} r := &MachineReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, tc.machine, infraConfig), + Client: fake.NewFakeClientWithScheme(scheme.Scheme, + tc.machine, + external.TestGenericBootstrapCRD, + external.TestGenericInfrastructureCRD, + infraConfig, + ), Log: log.Log, scheme: scheme.Scheme, } diff --git a/controllers/machine_controller_test.go b/controllers/machine_controller_test.go index ecfc91076c91..a1bfb8a8cf86 100644 --- a/controllers/machine_controller_test.go +++ b/controllers/machine_controller_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -35,6 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + "sigs.k8s.io/cluster-api/controllers/external" ) func TestMachineFinalizer(t *testing.T) { @@ -266,7 +268,7 @@ func TestMachineOwnerReference(t *testing.T) { func TestReconcileRequest(t *testing.T) { infraConfig := unstructured.Unstructured{ Object: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "infra-config1", @@ -315,7 +317,7 @@ func TestReconcileRequest(t *testing.T) { ClusterName: "test-cluster", InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, Bootstrap: clusterv1.Bootstrap{Data: pointer.StringPtr("data")}, @@ -342,7 +344,7 @@ func TestReconcileRequest(t *testing.T) { ClusterName: "test-cluster", InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, Bootstrap: clusterv1.Bootstrap{Data: pointer.StringPtr("data")}, @@ -373,7 +375,7 @@ func TestReconcileRequest(t *testing.T) { ClusterName: "test-cluster", InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, Bootstrap: clusterv1.Bootstrap{Data: pointer.StringPtr("data")}, @@ -391,11 +393,13 @@ func TestReconcileRequest(t *testing.T) { g := NewWithT(t) g.Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) + g.Expect(apiextensionsv1.AddToScheme(scheme.Scheme)).To(Succeed()) client := fake.NewFakeClientWithScheme( scheme.Scheme, &testCluster, &tc.machine, + external.TestGenericInfrastructureCRD, &infraConfig, ) @@ -435,7 +439,7 @@ func TestReconcileDeleteExternal(t *testing.T) { infraConfig := &unstructured.Unstructured{ Object: map[string]interface{}{ - "kind": "InfrastructureConfig", + "kind": "InfrastructureMachine", "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", "metadata": map[string]interface{}{ "name": "delete-infra", @@ -453,7 +457,7 @@ func TestReconcileDeleteExternal(t *testing.T) { ClusterName: "test-cluster", InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "delete-infra", }, Bootstrap: clusterv1.Bootstrap{ @@ -558,7 +562,7 @@ func TestRemoveMachineFinalizerAfterDeleteReconcile(t *testing.T) { ClusterName: "test-cluster", InfrastructureRef: corev1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha3", - Kind: "InfrastructureConfig", + Kind: "InfrastructureMachine", Name: "infra-config1", }, Bootstrap: clusterv1.Bootstrap{Data: pointer.StringPtr("data")}, diff --git a/controllers/machinedeployment_controller.go b/controllers/machinedeployment_controller.go index d318aa331768..2af83c718447 100644 --- a/controllers/machinedeployment_controller.go +++ b/controllers/machinedeployment_controller.go @@ -131,7 +131,7 @@ func (r *MachineDeploymentReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result return result, nil } -func (r *MachineDeploymentReconciler) reconcile(_ context.Context, cluster *clusterv1.Cluster, d *clusterv1.MachineDeployment) (ctrl.Result, error) { +func (r *MachineDeploymentReconciler) reconcile(ctx context.Context, cluster *clusterv1.Cluster, d *clusterv1.MachineDeployment) (ctrl.Result, error) { logger := r.Log.WithValues("machinedeployment", d.Name, "namespace", d.Namespace) logger.V(4).Info("Reconcile MachineDeployment") @@ -162,6 +162,17 @@ func (r *MachineDeploymentReconciler) reconcile(_ context.Context, cluster *clus return ctrl.Result{}, nil } + // Make sure to reconcile the external infrastructure reference. + if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, &d.Spec.Template.Spec.InfrastructureRef); err != nil { + return ctrl.Result{}, err + } + // Make sure to reconcile the external bootstrap reference, if any. + if d.Spec.Template.Spec.Bootstrap.ConfigRef != nil { + if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, d.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil { + return ctrl.Result{}, err + } + } + msList, err := r.getMachineSetsForDeployment(d) if err != nil { return ctrl.Result{}, err diff --git a/controllers/machineset_controller.go b/controllers/machineset_controller.go index c953d4d31fe1..9126f288346f 100644 --- a/controllers/machineset_controller.go +++ b/controllers/machineset_controller.go @@ -38,6 +38,7 @@ import ( "sigs.k8s.io/cluster-api/controllers/noderefutil" "sigs.k8s.io/cluster-api/controllers/remote" "sigs.k8s.io/cluster-api/util" + utilconversion "sigs.k8s.io/cluster-api/util/conversion" "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -163,12 +164,12 @@ func (r *MachineSetReconciler) reconcile(ctx context.Context, cluster *clusterv1 } // Make sure to reconcile the external infrastructure reference. - if err := r.reconcileExternalReference(ctx, cluster, machineSet.Spec.Template.Spec.InfrastructureRef); err != nil { + if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, &machineSet.Spec.Template.Spec.InfrastructureRef); err != nil { return ctrl.Result{}, err } // Make sure to reconcile the external bootstrap reference, if any. if machineSet.Spec.Template.Spec.Bootstrap.ConfigRef != nil { - if err := r.reconcileExternalReference(ctx, cluster, *machineSet.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil { + if err := reconcileExternalTemplateReference(ctx, r.Client, cluster, machineSet.Spec.Template.Spec.Bootstrap.ConfigRef); err != nil { return ctrl.Result{}, err } } @@ -268,34 +269,6 @@ func (r *MachineSetReconciler) reconcile(ctx context.Context, cluster *clusterv1 return ctrl.Result{}, nil } -func (r *MachineSetReconciler) reconcileExternalReference(ctx context.Context, cluster *clusterv1.Cluster, ref corev1.ObjectReference) error { - if !strings.HasSuffix(ref.Kind, external.TemplateSuffix) { - return nil - } - - obj, err := external.Get(ctx, r.Client, &ref, cluster.Namespace) - if err != nil { - return err - } - - patchHelper, err := patch.NewHelper(obj, r.Client) - if err != nil { - return err - } - - obj.SetOwnerReferences(util.EnsureOwnerRef(obj.GetOwnerReferences(), metav1.OwnerReference{ - APIVersion: clusterv1.GroupVersion.String(), - Kind: "Cluster", - Name: cluster.Name, - UID: cluster.UID, - })) - - if err := patchHelper.Patch(ctx, obj); err != nil { - return err - } - return nil -} - // syncReplicas scales Machine resources up or down. func (r *MachineSetReconciler) syncReplicas(ctx context.Context, ms *clusterv1.MachineSet, machines []*clusterv1.Machine) error { logger := r.Log.WithValues("machineset", ms.Name, "namespace", ms.Namespace) @@ -719,3 +692,35 @@ func (r *MachineSetReconciler) getMachineNode(ctx context.Context, cluster *clus } return node, nil } + +func reconcileExternalTemplateReference(ctx context.Context, c client.Client, cluster *clusterv1.Cluster, ref *corev1.ObjectReference) error { + if !strings.HasSuffix(ref.Kind, external.TemplateSuffix) { + return nil + } + + if err := utilconversion.ConvertReferenceAPIContract(ctx, c, ref); err != nil { + return err + } + + obj, err := external.Get(ctx, c, ref, cluster.Namespace) + if err != nil { + return err + } + + patchHelper, err := patch.NewHelper(obj, c) + if err != nil { + return err + } + + obj.SetOwnerReferences(util.EnsureOwnerRef(obj.GetOwnerReferences(), metav1.OwnerReference{ + APIVersion: clusterv1.GroupVersion.String(), + Kind: "Cluster", + Name: cluster.Name, + UID: cluster.UID, + })) + + if err := patchHelper.Patch(ctx, obj); err != nil { + return err + } + return nil +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 606610e62b47..455892041b39 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -24,6 +24,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -49,6 +50,10 @@ import ( func init() { klog.InitFlags(nil) logf.SetLogger(klogr.New()) + + // Register required object kinds with global scheme. + apiextensionsv1.AddToScheme(scheme.Scheme) + clusterv1.AddToScheme(scheme.Scheme) } const ( @@ -90,8 +95,6 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) - Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) - // +kubebuilder:scaffold:scheme By("setting up a new manager") @@ -105,7 +108,9 @@ var _ = BeforeSuite(func(done Done) { }, }) Expect(err).NotTo(HaveOccurred()) + k8sClient = mgr.GetClient() + clusterReconciler = &ClusterReconciler{ Client: k8sClient, Log: log.Log, diff --git a/controlplane/kubeadm/config/crd/kustomization.yaml b/controlplane/kubeadm/config/crd/kustomization.yaml index c58354e0191b..61134db8c94f 100644 --- a/controlplane/kubeadm/config/crd/kustomization.yaml +++ b/controlplane/kubeadm/config/crd/kustomization.yaml @@ -1,3 +1,6 @@ +commonLabels: + cluster.x-k8s.io/v1alpha3: v1alpha3 + # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/ diff --git a/docs/book/src/developer/providers/v1alpha2-to-v1alpha3.md b/docs/book/src/developer/providers/v1alpha2-to-v1alpha3.md index 434bd3286bcd..91facc6ba802 100644 --- a/docs/book/src/developer/providers/v1alpha2-to-v1alpha3.md +++ b/docs/book/src/developer/providers/v1alpha2-to-v1alpha3.md @@ -154,3 +154,22 @@ unique keys to `failureDomainSpec`s as well as respecting a set `Machine.Spec.Fa instances. Please see the cluster and machine infrastructure provider specifications for more detail. + +# Apply the contract version label `cluster.x-k8s.io/: version1,version2,version3` to your CRDs + +- Providers MUST set `cluster.x-k8s.io/` labels on all Custom Resource Definitions related to Cluster API starting with v1alpha3. +- The label is a map from an API Version of Cluster API (contract) to your Custom Resource Definition versions. + - The value is a comma-delimited list of versions. + - Each value MUST point to an available version in your CRD Spec. +- The label allows Cluster API controllers to perform automatic conversions for object references, the controllers will + pick the last available version in the list if multiple versions are found. +- To apply the label to CRDs it's possible to use `commonLabels` in your `kustomize.yaml` file, usually in `config/crd`. + +In this example we show how to map a particular Cluster API contract version to your own CRD using Kustomize's `commonLabels` feature: + +```yaml +commonLabels: + cluster.x-k8s.io/v1alpha2: v1alpha1 + cluster.x-k8s.io/v1alpha3: v1alpha2 + cluster.x-k8s.io/v1beta1: v1alphaX,v1beta1 +``` diff --git a/scripts/ci-integration.sh b/scripts/ci-integration.sh index a3ae2a4a93c4..e6500cd7ba39 100755 --- a/scripts/ci-integration.sh +++ b/scripts/ci-integration.sh @@ -19,7 +19,7 @@ set -o nounset set -o pipefail REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. -# shellcheck source=./hack/ensure-go.sh +# shellcheck source=../hack/ensure-go.sh source "${REPO_ROOT}/hack/ensure-go.sh" MAKE="make" diff --git a/test/infrastructure/docker/config/crd/kustomization.yaml b/test/infrastructure/docker/config/crd/kustomization.yaml index 14e34ef5657e..257fa4b536de 100644 --- a/test/infrastructure/docker/config/crd/kustomization.yaml +++ b/test/infrastructure/docker/config/crd/kustomization.yaml @@ -1,3 +1,6 @@ +commonLabels: + cluster.x-k8s.io/v1alpha3: v1alpha3 + # This kustomization.yaml is not intended to be run by itself, # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/ diff --git a/test/infrastructure/docker/go.mod b/test/infrastructure/docker/go.mod index 503b072ae84a..be2661a9ee3a 100644 --- a/test/infrastructure/docker/go.mod +++ b/test/infrastructure/docker/go.mod @@ -11,7 +11,7 @@ require ( gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 k8s.io/api v0.17.2 k8s.io/apimachinery v0.17.2 - k8s.io/client-go v0.17.2 + k8s.io/client-go v11.0.0+incompatible k8s.io/klog v1.0.0 sigs.k8s.io/cluster-api v0.2.6-0.20200213153035-a0cdb3b05cda sigs.k8s.io/cluster-api/test/framework v0.0.0-20200212174651-13d44c484542 diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum index 9268d56ec9be..47e36f2338dd 100644 --- a/test/infrastructure/docker/go.sum +++ b/test/infrastructure/docker/go.sum @@ -460,6 +460,8 @@ k8s.io/apiserver v0.17.2 h1:NssVvPALll6SSeNgo1Wk1h2myU1UHNwmhxV0Oxbcl8Y= k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc= k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= +k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/cluster-bootstrap v0.0.0-20190516232516-d7d78ab2cfe7 h1:5wvjieVoU4oovHlkeD256q2M2YYi2P01zk6wxSR2zk0= k8s.io/cluster-bootstrap v0.0.0-20190516232516-d7d78ab2cfe7/go.mod h1:iBSm2nwo3OaiuW8VDvc3ySDXK5SKfUrxwPvBloKG7zg= k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= diff --git a/util/conversion/conversion.go b/util/conversion/conversion.go index 28d81626fd83..54d4aff5bb3a 100644 --- a/util/conversion/conversion.go +++ b/util/conversion/conversion.go @@ -17,11 +17,16 @@ limitations under the License. package conversion import ( + "context" "math/rand" + "sort" + "strings" "testing" fuzz "github.com/google/gofuzz" "github.com/onsi/gomega" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/apitesting/fuzzer" apiequality "k8s.io/apimachinery/pkg/api/equality" metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer" @@ -30,6 +35,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/json" "k8s.io/utils/diff" + clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" + "sigs.k8s.io/cluster-api/util" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" ) @@ -37,6 +45,53 @@ const ( DataAnnotation = "cluster.x-k8s.io/conversion-data" ) +var ( + contract = clusterv1.GroupVersion.String() +) + +// ConvertReferenceAPIContract takes a client and object reference, queries the API Server for +// the Custom Resource Definition and looks which one is the stored version available. +// +// The object passed as input is modified in place if an updated compatible version is found. +func ConvertReferenceAPIContract(ctx context.Context, c client.Client, ref *corev1.ObjectReference) error { + gvk := ref.GroupVersionKind() + crd, err := util.GetCRDWithContract(ctx, c, gvk, contract) + if err != nil { + return err + } + + // If there is no label, return early without changing the reference. + supportedVersions, ok := crd.Labels[contract] + if !ok || supportedVersions == "" { + return errors.Errorf("cannot find any versions matching contract %q for CRD %v", contract, crd.Name) + } + + // Pick the latest version in the slice and validate it. + kubeVersions := util.KubeVersions(strings.Split(supportedVersions, ",")) + sort.Sort(kubeVersions) + chosen := kubeVersions[len(kubeVersions)-1] + + // Validate that the picked version is actually in the CRD spec. + found := false + for _, version := range crd.Spec.Versions { + if version.Name == chosen { + found = true + break + } + } + if !found { + return errors.Errorf("cannot find any versions matching contract %q for CRD %v", contract, crd.Name) + } + + // Modify the GroupVersionKind with the new version. + if gvk.Version != chosen { + gvk.Version = chosen + ref.SetGroupVersionKind(gvk) + } + + return nil +} + // MarshalData stores the source object as json data in the destination object annotations map. func MarshalData(src metav1.Object, dst metav1.Object) error { data, err := json.Marshal(src) diff --git a/util/util.go b/util/util.go index 8279d9a9397f..7b1d17b7ed49 100644 --- a/util/util.go +++ b/util/util.go @@ -30,10 +30,12 @@ import ( "github.com/pkg/errors" v1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/version" "k8s.io/klog" clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3" "sigs.k8s.io/controller-runtime/pkg/client" @@ -410,3 +412,40 @@ func IsPaused(cluster *clusterv1.Cluster, v metav1.Object) bool { _, ok := annotations[clusterv1.PausedAnnotation] return ok } + +// TODO(vincepri): Use label matcher once https://github.com/kubernetes-sigs/controller-runtime/pull/792 merges. +func GetCRDWithContract(ctx context.Context, c client.Client, gvk schema.GroupVersionKind, contract string) (*apiextensionsv1.CustomResourceDefinition, error) { + crdList := &apiextensionsv1.CustomResourceDefinitionList{} + for { + if err := c.List(ctx, crdList, client.Continue(crdList.Continue)); err != nil { + return nil, errors.Wrapf(err, "failed to list CustomResourceDefinitions for %v", gvk) + } + + for _, crd := range crdList.Items { + if crd.Spec.Group == gvk.Group && + crd.Spec.Names.Kind == gvk.Kind { + return crd.DeepCopy(), nil + } + } + + if crdList.Continue == "" { + break + } + } + + return nil, errors.Errorf("failed to find a CustomResourceDefinition for %v with contract %q", gvk, contract) +} + +// KubeVersions is a sortable slice of kube-like version strings. +// +// Kube-like version strings are starting with a v, followed by a major version, +// optional "alpha" or "beta" strings followed by a minor version (e.g. v1, v2beta1). +// Versions will be sorted based on GA/alpha/beta first and then major and minor +// versions. e.g. v2, v1, v1beta2, v1beta1, v1alpha1. +type KubeVersions []string + +func (k KubeVersions) Len() int { return len(k) } +func (k KubeVersions) Swap(i, j int) { k[i], k[j] = k[j], k[i] } +func (k KubeVersions) Less(i, j int) bool { + return version.CompareKubeAwareVersionStrings(k[i], k[j]) < 0 +}