diff --git a/PROJECT b/PROJECT index dae5207e4..b32b42c7e 100644 --- a/PROJECT +++ b/PROJECT @@ -72,4 +72,15 @@ resources: webhooks: validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + domain: cluster.x-k8s.io + group: infrastructure + kind: LinodeCluster + path: github.com/linode/cluster-api-provider-linode/api/v1alpha2 + version: v1alpha2 + webhooks: + conversion: true + webhookVersion: v1 version: "3" diff --git a/api/v1alpha1/linodecluster_conversion.go b/api/v1alpha1/linodecluster_conversion.go new file mode 100644 index 000000000..947c5f4fc --- /dev/null +++ b/api/v1alpha1/linodecluster_conversion.go @@ -0,0 +1,85 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha1 + +import ( + "errors" + + "sigs.k8s.io/controller-runtime/pkg/conversion" + + infrastructurev1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" +) + +// ConvertTo converts this LinodeCluster to the Hub version (v1alpha2). +func (src *LinodeCluster) ConvertTo(dstRaw conversion.Hub) error { + dst, ok := dstRaw.(*infrastructurev1alpha2.LinodeCluster) + if !ok { + return errors.New("failed to convert LinodeCluster version from v1alpha1 to v1alpha2") + } + + // ObjectMeta + dst.ObjectMeta = src.ObjectMeta + + // Spec + dst.Spec.Network = infrastructurev1alpha2.NetworkSpec{ + LoadBalancerType: src.Spec.Network.LoadBalancerType, + ApiserverLoadBalancerPort: src.Spec.Network.LoadBalancerPort, + NodeBalancerID: src.Spec.Network.NodeBalancerID, + ApiserverNodeBalancerConfigID: src.Spec.Network.NodeBalancerConfigID, + } + dst.Spec.ControlPlaneEndpoint = src.Spec.ControlPlaneEndpoint + dst.Spec.Region = src.Spec.Region + dst.Spec.VPCRef = src.Spec.VPCRef + dst.Spec.CredentialsRef = src.Spec.CredentialsRef + + // Status + dst.Status.Ready = src.Status.Ready + dst.Status.Conditions = src.Status.Conditions + dst.Status.FailureMessage = src.Status.FailureMessage + dst.Status.FailureReason = src.Status.FailureReason + + return nil +} + +// ConvertFrom converts from the Hub version (v1alpha2) to this version. +func (dst *LinodeCluster) ConvertFrom(srcRaw conversion.Hub) error { + src, ok := srcRaw.(*infrastructurev1alpha2.LinodeCluster) + if !ok { + return errors.New("failed to convert LinodeCluster version from v1alpha2 to v1alpha1") + } + + // ObjectMeta + dst.ObjectMeta = src.ObjectMeta + + // Spec + dst.Spec.Network.LoadBalancerPort = src.Spec.Network.ApiserverLoadBalancerPort + dst.Spec.Network.LoadBalancerType = src.Spec.Network.LoadBalancerType + dst.Spec.Network.NodeBalancerID = src.Spec.Network.NodeBalancerID + dst.Spec.Network.NodeBalancerConfigID = src.Spec.Network.ApiserverNodeBalancerConfigID + dst.Spec.ControlPlaneEndpoint = src.Spec.ControlPlaneEndpoint + dst.Spec.Region = src.Spec.Region + dst.Spec.VPCRef = src.Spec.VPCRef + dst.Spec.CredentialsRef = src.Spec.CredentialsRef + + // Status + dst.Status.Ready = src.Status.Ready + dst.Status.Conditions = src.Status.Conditions + dst.Status.FailureMessage = src.Status.FailureMessage + dst.Status.FailureReason = src.Status.FailureReason + + return nil +} diff --git a/api/v1alpha1/linodecluster_conversion_test.go b/api/v1alpha1/linodecluster_conversion_test.go new file mode 100644 index 000000000..3c3c4b8d1 --- /dev/null +++ b/api/v1alpha1/linodecluster_conversion_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha1 + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" + "github.com/linode/cluster-api-provider-linode/mock" + + . "github.com/linode/cluster-api-provider-linode/mock/mocktest" +) + +func TestConvertTo(t *testing.T) { + t.Parallel() + + src := &LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Spec: LinodeClusterSpec{ + Network: NetworkSpec{ + LoadBalancerType: "test-type", + LoadBalancerPort: 12345, + NodeBalancerID: ptr.To(1234), + NodeBalancerConfigID: ptr.To(2345), + }, + ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "1.2.3.4"}, + Region: "test-region", + }, + } + expectedDst := &infrav1alpha2.LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + LoadBalancerType: "test-type", + NodeBalancerID: ptr.To(1234), + ApiserverLoadBalancerPort: 12345, + ApiserverNodeBalancerConfigID: ptr.To(2345), + }, + ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "1.2.3.4"}, + Region: "test-region", + }, + } + dst := &infrav1alpha2.LinodeCluster{} + + NewSuite(t, mock.MockLinodeClient{}).Run( + OneOf( + Path( + Call("convert v1alpha1 to v1alpha2", func(ctx context.Context, mck Mock) { + err := src.ConvertTo(dst) + if err != nil { + t.Fatalf("ConvertTo failed: %v", err) + } + }), + Result("conversion succeeded", func(ctx context.Context, mck Mock) { + if diff := cmp.Diff(expectedDst, dst); diff != "" { + t.Errorf("ConvertTo() mismatch (-expected +got):\n%s", diff) + } + }), + ), + ), + ) +} + +func TestConvertFrom(t *testing.T) { + t.Parallel() + + src := &infrav1alpha2.LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + LoadBalancerType: "test-type", + NodeBalancerID: ptr.To(1234), + ApiserverLoadBalancerPort: 12345, + ApiserverNodeBalancerConfigID: ptr.To(2345), + }, + ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "1.2.3.4"}, + Region: "test-region", + }, + } + expectedDst := &LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Spec: LinodeClusterSpec{ + Network: NetworkSpec{ + LoadBalancerType: "test-type", + LoadBalancerPort: 12345, + NodeBalancerID: ptr.To(1234), + NodeBalancerConfigID: ptr.To(2345), + }, + ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "1.2.3.4"}, + Region: "test-region", + }, + } + dst := &LinodeCluster{} + + NewSuite(t, mock.MockLinodeClient{}).Run( + OneOf( + Path( + Call("convert v1alpha2 to v1alpha1", func(ctx context.Context, mck Mock) { + err := dst.ConvertFrom(src) + if err != nil { + t.Fatalf("ConvertFrom failed: %v", err) + } + }), + Result("conversion succeeded", func(ctx context.Context, mck Mock) { + if diff := cmp.Diff(expectedDst, dst); diff != "" { + t.Errorf("ConvertFrom() mismatch (-expected +got):\n%s", diff) + } + }), + ), + ), + ) +} diff --git a/api/v1alpha2/groupversion_info.go b/api/v1alpha2/groupversion_info.go new file mode 100644 index 000000000..13fa19bac --- /dev/null +++ b/api/v1alpha2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha2 contains API Schema definitions for the infrastructure v1alpha2 API group +// +kubebuilder:object:generate=true +// +groupName=infrastructure.cluster.x-k8s.io +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1alpha2/linodecluster_conversion.go b/api/v1alpha2/linodecluster_conversion.go new file mode 100644 index 000000000..369681b1c --- /dev/null +++ b/api/v1alpha2/linodecluster_conversion.go @@ -0,0 +1,20 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha2 + +// Hub marks LinodeCluster as a conversion hub. +func (*LinodeCluster) Hub() {} diff --git a/api/v1alpha2/linodecluster_types.go b/api/v1alpha2/linodecluster_types.go new file mode 100644 index 000000000..919ed2674 --- /dev/null +++ b/api/v1alpha2/linodecluster_types.go @@ -0,0 +1,129 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha2 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/errors" +) + +// LinodeClusterSpec defines the desired state of LinodeCluster +type LinodeClusterSpec struct { + // The Linode Region the LinodeCluster lives in. + Region string `json:"region"` + + // ControlPlaneEndpoint represents the endpoint used to communicate with the LinodeCluster control plane. + // If ControlPlaneEndpoint is unset then the Nodebalancer ip will be used. + // +optional + ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` + + // NetworkSpec encapsulates all things related to Linode network. + // +optional + Network NetworkSpec `json:"network"` + + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // +optional + VPCRef *corev1.ObjectReference `json:"vpcRef,omitempty"` + + // CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not + // supplied then the credentials of the controller will be used. + // +optional + CredentialsRef *corev1.SecretReference `json:"credentialsRef,omitempty"` +} + +// LinodeClusterStatus defines the observed state of LinodeCluster +type LinodeClusterStatus struct { + // Ready denotes that the cluster (infrastructure) is ready. + // +optional + Ready bool `json:"ready"` + + // FailureReason will be set in the event that there is a terminal problem + // reconciling the LinodeCluster and will contain a succinct value suitable + // for machine interpretation. + // +optional + FailureReason *errors.ClusterStatusError `json:"failureReason,omitempty"` + + // FailureMessage will be set in the event that there is a terminal problem + // reconciling the LinodeCluster and will contain a more verbose string suitable + // for logging and human consumption. + // +optional + FailureMessage *string `json:"failureMessage,omitempty"` + + // Conditions defines current service state of the LinodeCluster. + // +optional + Conditions clusterv1.Conditions `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=linodeclusters,scope=Namespaced,categories=cluster-api,shortName=lc +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Cluster",type="string",JSONPath=".metadata.labels.cluster\\.x-k8s\\.io/cluster-name",description="Cluster to which this LinodeCluster belongs" +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="Cluster infrastructure is ready for Linode instances" +// +kubebuilder:printcolumn:name="Endpoint",type="string",JSONPath=".spec.ControlPlaneEndpoint",description="API Endpoint",priority=1 +// +kubebuilder:storageversion + +// LinodeCluster is the Schema for the linodeclusters API +type LinodeCluster struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec LinodeClusterSpec `json:"spec,omitempty"` + Status LinodeClusterStatus `json:"status,omitempty"` +} + +func (lm *LinodeCluster) GetConditions() clusterv1.Conditions { + return lm.Status.Conditions +} + +func (lm *LinodeCluster) SetConditions(conditions clusterv1.Conditions) { + lm.Status.Conditions = conditions +} + +// NetworkSpec encapsulates Linode networking resources. +type NetworkSpec struct { + // LoadBalancerType is the type of load balancer to use, defaults to NodeBalancer if not otherwise set + // +kubebuilder:validation:Enum=NodeBalancer + // +optional + LoadBalancerType string `json:"loadBalancerType,omitempty"` + // apiserverLoadBalancerPort used by the api server. It must be valid ports range (1-65535). + // If omitted, default value is 6443. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + // +optional + ApiserverLoadBalancerPort int `json:"apiserverLoadBalancerPort,omitempty"` + // NodeBalancerID is the id of NodeBalancer. + // +optional + NodeBalancerID *int `json:"nodeBalancerID,omitempty"` + // apiserverNodeBalancerConfigID is the config ID of api server NodeBalancer fonfig. + // +optional + ApiserverNodeBalancerConfigID *int `json:"apiserverNodeBalancerConfigID,omitempty"` +} + +// +kubebuilder:object:root=true + +// LinodeClusterList contains a list of LinodeCluster +type LinodeClusterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []LinodeCluster `json:"items"` +} + +func init() { + SchemeBuilder.Register(&LinodeCluster{}, &LinodeClusterList{}) +} diff --git a/api/v1alpha2/linodecluster_webhook.go b/api/v1alpha2/linodecluster_webhook.go new file mode 100644 index 000000000..042222608 --- /dev/null +++ b/api/v1alpha2/linodecluster_webhook.go @@ -0,0 +1,102 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha2 + +import ( + "context" + "slices" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + . "github.com/linode/cluster-api-provider-linode/clients" +) + +// log is for logging in this package. +var linodeclusterlog = logf.Log.WithName("linodecluster-resource") + +// SetupWebhookWithManager will setup the manager to manage the webhooks +func (r *LinodeCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable updation and deletion validation. +//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha2-linodecluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodeclusters,verbs=create,versions=v1alpha2,name=vlinodecluster.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &LinodeCluster{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *LinodeCluster) ValidateCreate() (admission.Warnings, error) { + linodeclusterlog.Info("validate create", "name", r.Name) + + ctx, cancel := context.WithTimeout(context.Background(), defaultWebhookTimeout) + defer cancel() + + return nil, r.validateLinodeCluster(ctx, &defaultLinodeClient) +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *LinodeCluster) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + linodeclusterlog.Info("validate update", "name", r.Name) + + // TODO(user): fill in your validation logic upon object update. + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *LinodeCluster) ValidateDelete() (admission.Warnings, error) { + linodeclusterlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} + +func (r *LinodeCluster) validateLinodeCluster(ctx context.Context, client LinodeClient) error { + var errs field.ErrorList + + if err := r.validateLinodeClusterSpec(ctx, client); err != nil { + errs = slices.Concat(errs, err) + } + + if len(errs) == 0 { + return nil + } + return apierrors.NewInvalid( + schema.GroupKind{Group: "infrastructure.cluster.x-k8s.io", Kind: "LinodeCluster"}, + r.Name, errs) +} + +func (r *LinodeCluster) validateLinodeClusterSpec(ctx context.Context, client LinodeClient) field.ErrorList { + var errs field.ErrorList + + if err := validateRegion(ctx, client, r.Spec.Region, field.NewPath("spec").Child("region")); err != nil { + errs = append(errs, err) + } + + if len(errs) == 0 { + return nil + } + return errs +} diff --git a/api/v1alpha2/linodecluster_webhook_test.go b/api/v1alpha2/linodecluster_webhook_test.go new file mode 100644 index 000000000..70d70168d --- /dev/null +++ b/api/v1alpha2/linodecluster_webhook_test.go @@ -0,0 +1,105 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha2 + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/linode/cluster-api-provider-linode/mock" + + . "github.com/linode/cluster-api-provider-linode/mock/mocktest" +) + +func TestValidateLinodeCluster(t *testing.T) { + t.Parallel() + + var ( + cluster = LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "example", + }, + Spec: LinodeClusterSpec{ + Region: "example", + Network: NetworkSpec{ + LoadBalancerType: "NodeBalancer", + }, + }, + } + ) + + NewSuite(t, mock.MockLinodeClient{}).Run( + OneOf( + Path( + Call("valid", func(ctx context.Context, mck Mock) { + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() + }), + Result("success", func(ctx context.Context, mck Mock) { + assert.NoError(t, cluster.validateLinodeCluster(ctx, mck.LinodeClient)) + }), + ), + ), + OneOf( + Path(Call("invalid region", func(ctx context.Context, mck Mock) { + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, errors.New("invalid region")).AnyTimes() + })), + ), + Result("error", func(ctx context.Context, mck Mock) { + assert.Error(t, cluster.validateLinodeCluster(ctx, mck.LinodeClient)) + }), + ) +} + +func TestValidateCreate(t *testing.T) { + t.Parallel() + + var ( + cluster = LinodeCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "example", + }, + Spec: LinodeClusterSpec{ + Region: "example", + Network: NetworkSpec{ + LoadBalancerType: "NodeBalancer", + }, + }, + } + ) + + NewSuite(t, mock.MockLinodeClient{}).Run( + OneOf( + Path( + Call("invalid region", func(ctx context.Context, mck Mock) { + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(nil, errors.New("invalid region")).AnyTimes() + }), + Result("error", func(ctx context.Context, mck Mock) { + //nolint:contextcheck // no context passed + _, err := cluster.ValidateCreate() + assert.Error(t, err) + }), + ), + ), + ) +} diff --git a/api/v1alpha2/webhook_helpers.go b/api/v1alpha2/webhook_helpers.go new file mode 100644 index 000000000..326072da9 --- /dev/null +++ b/api/v1alpha2/webhook_helpers.go @@ -0,0 +1,57 @@ +/* +Copyright 2023 Akamai Technologies, Inc. + +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 v1alpha2 + +import ( + "context" + "fmt" + "net/http" + "slices" + "time" + + "github.com/linode/linodego" + "k8s.io/apimachinery/pkg/util/validation/field" + + . "github.com/linode/cluster-api-provider-linode/clients" +) + +const ( + // defaultWebhookTimeout is the default timeout for an admission request + defaultWebhookTimeout = time.Second * 10 + // defaultClientTimeout is the default timeout for a client Linode API call + defaultClientTimeout = time.Second * 10 +) + +var ( + // defaultLinodeClient is an unauthenticated Linode client + defaultLinodeClient = linodego.NewClient(&http.Client{Timeout: defaultClientTimeout}) +) + +func validateRegion(ctx context.Context, client LinodeClient, id string, path *field.Path, capabilities ...string) *field.Error { + region, err := client.GetRegion(ctx, id) + if err != nil { + return field.NotFound(path, id) + } + + for _, capability := range capabilities { + if !slices.Contains(region.Capabilities, capability) { + return field.Invalid(path, id, fmt.Sprintf("no capability: %s", capability)) + } + } + + return nil +} diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 000000000..e17bdc7d9 --- /dev/null +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,171 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2023 Akamai Technologies, Inc. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/errors" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinodeCluster) DeepCopyInto(out *LinodeCluster) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeCluster. +func (in *LinodeCluster) DeepCopy() *LinodeCluster { + if in == nil { + return nil + } + out := new(LinodeCluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LinodeCluster) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinodeClusterList) DeepCopyInto(out *LinodeClusterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LinodeCluster, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeClusterList. +func (in *LinodeClusterList) DeepCopy() *LinodeClusterList { + if in == nil { + return nil + } + out := new(LinodeClusterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LinodeClusterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinodeClusterSpec) DeepCopyInto(out *LinodeClusterSpec) { + *out = *in + out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + in.Network.DeepCopyInto(&out.Network) + if in.VPCRef != nil { + in, out := &in.VPCRef, &out.VPCRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.CredentialsRef != nil { + in, out := &in.CredentialsRef, &out.CredentialsRef + *out = new(v1.SecretReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeClusterSpec. +func (in *LinodeClusterSpec) DeepCopy() *LinodeClusterSpec { + if in == nil { + return nil + } + out := new(LinodeClusterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinodeClusterStatus) DeepCopyInto(out *LinodeClusterStatus) { + *out = *in + if in.FailureReason != nil { + in, out := &in.FailureReason, &out.FailureReason + *out = new(errors.ClusterStatusError) + **out = **in + } + if in.FailureMessage != nil { + in, out := &in.FailureMessage, &out.FailureMessage + *out = new(string) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(v1beta1.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeClusterStatus. +func (in *LinodeClusterStatus) DeepCopy() *LinodeClusterStatus { + if in == nil { + return nil + } + out := new(LinodeClusterStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) { + *out = *in + if in.NodeBalancerID != nil { + in, out := &in.NodeBalancerID, &out.NodeBalancerID + *out = new(int) + **out = **in + } + if in.ApiserverNodeBalancerConfigID != nil { + in, out := &in.ApiserverNodeBalancerConfigID, &out.ApiserverNodeBalancerConfigID + *out = new(int) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSpec. +func (in *NetworkSpec) DeepCopy() *NetworkSpec { + if in == nil { + return nil + } + out := new(NetworkSpec) + in.DeepCopyInto(out) + return out +} diff --git a/cloud/scope/cluster.go b/cloud/scope/cluster.go index 9a4312107..94147fbb3 100644 --- a/cloud/scope/cluster.go +++ b/cloud/scope/cluster.go @@ -25,7 +25,7 @@ import ( "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" . "github.com/linode/cluster-api-provider-linode/clients" ) @@ -34,7 +34,7 @@ import ( type ClusterScopeParams struct { Client K8sClient Cluster *clusterv1.Cluster - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster } func validateClusterScopeParams(params ClusterScopeParams) error { @@ -88,7 +88,7 @@ type ClusterScope struct { PatchHelper *patch.Helper LinodeClient LinodeClient Cluster *clusterv1.Cluster - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster } // PatchObject persists the cluster configuration and status. @@ -104,7 +104,7 @@ func (s *ClusterScope) Close(ctx context.Context) error { // AddFinalizer adds a finalizer if not present and immediately patches the // object to avoid any race conditions. func (s *ClusterScope) AddFinalizer(ctx context.Context) error { - if controllerutil.AddFinalizer(s.LinodeCluster, infrav1alpha1.GroupVersion.String()) { + if controllerutil.AddFinalizer(s.LinodeCluster, infrav1alpha2.GroupVersion.String()) { return s.Close(ctx) } diff --git a/cloud/scope/cluster_test.go b/cloud/scope/cluster_test.go index 861108949..f0da1717c 100644 --- a/cloud/scope/cluster_test.go +++ b/cloud/scope/cluster_test.go @@ -30,7 +30,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" - infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/mock" ) @@ -49,7 +49,7 @@ func TestValidateClusterScopeParams(t *testing.T) { args{ params: ClusterScopeParams{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, }, }, false, @@ -75,7 +75,7 @@ func TestValidateClusterScopeParams(t *testing.T) { "Invalid ClusterScopeParams - no Cluster in ClusterScopeParams", args{ params: ClusterScopeParams{ - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, }, }, true, @@ -96,7 +96,7 @@ func TestClusterScopeMethods(t *testing.T) { t.Parallel() type fields struct { Cluster *clusterv1.Cluster - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster } tests := []struct { @@ -108,7 +108,7 @@ func TestClusterScopeMethods(t *testing.T) { name: "Success - finalizer should be added to the Linode Cluster object", fields: fields{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", }, @@ -117,7 +117,7 @@ func TestClusterScopeMethods(t *testing.T) { expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }).Times(2) mock.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) @@ -127,17 +127,17 @@ func TestClusterScopeMethods(t *testing.T) { name: "AddFinalizer error - finalizer should not be added to the Linode Cluster object. Function returns nil since it was already present", fields: fields{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", - Finalizers: []string{infrav1alpha1.GroupVersion.String()}, + Finalizers: []string{infrav1alpha2.GroupVersion.String()}, }, }, }, expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }).Times(1) }, @@ -171,7 +171,7 @@ func TestClusterScopeMethods(t *testing.T) { t.Errorf("ClusterScope.AddFinalizer() error = %v", err) } - if cScope.LinodeCluster.Finalizers[0] != infrav1alpha1.GroupVersion.String() { + if cScope.LinodeCluster.Finalizers[0] != infrav1alpha2.GroupVersion.String() { t.Errorf("Finalizer was not added") } }) @@ -196,14 +196,14 @@ func TestNewClusterScope(t *testing.T) { apiKey: "test-key", params: ClusterScopeParams{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, }, }, expectedError: nil, expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) }, @@ -215,8 +215,8 @@ func TestNewClusterScope(t *testing.T) { params: ClusterScopeParams{ Client: nil, Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ - Spec: infrav1alpha1.LinodeClusterSpec{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ + Spec: infrav1alpha2.LinodeClusterSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -229,7 +229,7 @@ func TestNewClusterScope(t *testing.T) { expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) mock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error { @@ -258,7 +258,7 @@ func TestNewClusterScope(t *testing.T) { apiKey: "test-key", params: ClusterScopeParams{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, }, }, expectedError: fmt.Errorf("failed to init patch helper:"), @@ -273,8 +273,8 @@ func TestNewClusterScope(t *testing.T) { params: ClusterScopeParams{ Client: nil, Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ - Spec: infrav1alpha1.LinodeClusterSpec{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ + Spec: infrav1alpha2.LinodeClusterSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -294,7 +294,7 @@ func TestNewClusterScope(t *testing.T) { apiKey: "", params: ClusterScopeParams{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, }, }, expectedError: fmt.Errorf("failed to create linode client: missing Linode API key"), @@ -331,7 +331,7 @@ func TestClusterAddCredentialsRefFinalizer(t *testing.T) { t.Parallel() type fields struct { Cluster *clusterv1.Cluster - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster } tests := []struct { @@ -343,11 +343,11 @@ func TestClusterAddCredentialsRefFinalizer(t *testing.T) { name: "Success - finalizer should be added to the Linode Cluster credentials Secret", fields: fields{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", }, - Spec: infrav1alpha1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -358,7 +358,7 @@ func TestClusterAddCredentialsRefFinalizer(t *testing.T) { expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) mock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error { @@ -382,7 +382,7 @@ func TestClusterAddCredentialsRefFinalizer(t *testing.T) { name: "No-op - no Linode Cluster credentials Secret", fields: fields{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", }, @@ -391,7 +391,7 @@ func TestClusterAddCredentialsRefFinalizer(t *testing.T) { expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) }, @@ -432,7 +432,7 @@ func TestRemoveCredentialsRefFinalizer(t *testing.T) { t.Parallel() type fields struct { Cluster *clusterv1.Cluster - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster } tests := []struct { @@ -444,11 +444,11 @@ func TestRemoveCredentialsRefFinalizer(t *testing.T) { name: "Success - finalizer should be removed from the Linode Cluster credentials Secret", fields: fields{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", }, - Spec: infrav1alpha1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -459,7 +459,7 @@ func TestRemoveCredentialsRefFinalizer(t *testing.T) { expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) mock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error { @@ -483,7 +483,7 @@ func TestRemoveCredentialsRefFinalizer(t *testing.T) { name: "No-op - no Linode Cluster credentials Secret", fields: fields{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", }, @@ -492,7 +492,7 @@ func TestRemoveCredentialsRefFinalizer(t *testing.T) { expects: func(mock *mock.MockK8sClient) { mock.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) }, diff --git a/cloud/scope/common_test.go b/cloud/scope/common_test.go index 99f45a4a8..a612528b0 100644 --- a/cloud/scope/common_test.go +++ b/cloud/scope/common_test.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/mock" ) @@ -440,7 +441,7 @@ func Test_toFinalizer(t *testing.T) { Version: infrav1alpha1.GroupVersion.Version, Kind: "LinodeCluster", }, - &infrav1alpha1.LinodeCluster{ + &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "example", @@ -455,7 +456,7 @@ func Test_toFinalizer(t *testing.T) { Version: infrav1alpha1.GroupVersion.Version, Kind: "LinodeCluster", }, - &infrav1alpha1.LinodeCluster{ + &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "example", // NOTE: Fake a cluster resource by setting Namespace to the default value diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go index 4d48903c9..f118a70f9 100644 --- a/cloud/scope/machine.go +++ b/cloud/scope/machine.go @@ -12,6 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" . "github.com/linode/cluster-api-provider-linode/clients" ) @@ -20,7 +21,7 @@ type MachineScopeParams struct { Client K8sClient Cluster *clusterv1.Cluster Machine *clusterv1.Machine - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster LinodeMachine *infrav1alpha1.LinodeMachine } @@ -30,7 +31,7 @@ type MachineScope struct { Cluster *clusterv1.Cluster Machine *clusterv1.Machine LinodeClient LinodeClient - LinodeCluster *infrav1alpha1.LinodeCluster + LinodeCluster *infrav1alpha2.LinodeCluster LinodeMachine *infrav1alpha1.LinodeMachine } diff --git a/cloud/scope/machine_test.go b/cloud/scope/machine_test.go index 5b72e49cd..6fab2c3ad 100644 --- a/cloud/scope/machine_test.go +++ b/cloud/scope/machine_test.go @@ -19,6 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/mock" . "github.com/linode/cluster-api-provider-linode/mock/mocktest" @@ -41,7 +42,7 @@ func TestValidateMachineScopeParams(t *testing.T) { params: MachineScopeParams{ Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }, }, @@ -71,7 +72,7 @@ func TestValidateMachineScopeParams(t *testing.T) { params: MachineScopeParams{ Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, }, }, true, @@ -81,7 +82,7 @@ func TestValidateMachineScopeParams(t *testing.T) { args{ params: MachineScopeParams{ Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }, }, @@ -92,7 +93,7 @@ func TestValidateMachineScopeParams(t *testing.T) { args{ params: MachineScopeParams{ Cluster: &clusterv1.Cluster{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }, }, @@ -134,7 +135,7 @@ func TestMachineScopeAddFinalizer(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{ ObjectMeta: metav1.ObjectMeta{ Finalizers: []string{infrav1alpha1.GroupVersion.String()}, @@ -157,7 +158,7 @@ func TestMachineScopeAddFinalizer(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }) require.NoError(t, err) @@ -175,7 +176,7 @@ func TestMachineScopeAddFinalizer(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }) require.NoError(t, err) @@ -202,7 +203,7 @@ func TestNewMachineScope(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }) require.ErrorContains(t, err, "failed to create linode client") @@ -217,7 +218,7 @@ func TestNewMachineScope(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{ Spec: infrav1alpha1.LinodeMachineSpec{ CredentialsRef: &corev1.SecretReference{ @@ -249,7 +250,7 @@ func TestNewMachineScope(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }) require.ErrorContains(t, err, "failed to init patch helper") @@ -274,7 +275,7 @@ func TestNewMachineScope(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{}, }) require.NoError(t, err) @@ -287,7 +288,7 @@ func TestNewMachineScope(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: &infrav1alpha1.LinodeMachine{ Spec: infrav1alpha1.LinodeMachineSpec{ CredentialsRef: &corev1.SecretReference{ @@ -305,8 +306,8 @@ func TestNewMachineScope(t *testing.T) { Client: mck.K8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{ - Spec: infrav1alpha1.LinodeClusterSpec{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ + Spec: infrav1alpha2.LinodeClusterSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -473,7 +474,7 @@ func TestMachineAddCredentialsRefFinalizer(t *testing.T) { Client: mockK8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: testcase.fields.LinodeMachine, }, ) @@ -566,7 +567,7 @@ func TestMachineRemoveCredentialsRefFinalizer(t *testing.T) { Client: mockK8sClient, Cluster: &clusterv1.Cluster{}, Machine: &clusterv1.Machine{}, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: testcase.fields.LinodeMachine, }, ) diff --git a/cloud/services/loadbalancers.go b/cloud/services/loadbalancers.go index 6547e599c..4e1e47217 100644 --- a/cloud/services/loadbalancers.go +++ b/cloud/services/loadbalancers.go @@ -16,7 +16,7 @@ import ( ) const ( - defaultLBPort = 6443 + defaultApiserverLBPort = 6443 ) // CreateNodeBalancer creates a new NodeBalancer if one doesn't exist @@ -71,37 +71,36 @@ func CreateNodeBalancer(ctx context.Context, clusterScope *scope.ClusterScope, l return linodeNB, nil } -// CreateNodeBalancerConfig creates NodeBalancer config if it does not exist -func CreateNodeBalancerConfig( +// CreateNodeBalancerConfigs creates NodeBalancer configs if it does not exist +func CreateNodeBalancerConfigs( ctx context.Context, clusterScope *scope.ClusterScope, logger logr.Logger, -) (*linodego.NodeBalancerConfig, error) { - var linodeNBConfig *linodego.NodeBalancerConfig - var err error - - lbPort := defaultLBPort - if clusterScope.LinodeCluster.Spec.Network.LoadBalancerPort != 0 { - lbPort = clusterScope.LinodeCluster.Spec.Network.LoadBalancerPort - } - createConfig := linodego.NodeBalancerConfigCreateOptions{ - Port: lbPort, +) ([]*linodego.NodeBalancerConfig, error) { + nbConfigs := []*linodego.NodeBalancerConfig{} + apiLBPort := defaultApiserverLBPort + if clusterScope.LinodeCluster.Spec.Network.ApiserverLoadBalancerPort != 0 { + apiLBPort = clusterScope.LinodeCluster.Spec.Network.ApiserverLoadBalancerPort + } + apiserverCreateConfig := linodego.NodeBalancerConfigCreateOptions{ + Port: apiLBPort, Protocol: linodego.ProtocolTCP, Algorithm: linodego.AlgorithmRoundRobin, Check: linodego.CheckConnection, } - if linodeNBConfig, err = clusterScope.LinodeClient.CreateNodeBalancerConfig( + apiserverLinodeNBConfig, err := clusterScope.LinodeClient.CreateNodeBalancerConfig( ctx, *clusterScope.LinodeCluster.Spec.Network.NodeBalancerID, - createConfig, - ); err != nil { + apiserverCreateConfig, + ) + if err != nil { logger.Info("Failed to create Linode NodeBalancer config", "error", err.Error()) - return nil, err } + nbConfigs = append(nbConfigs, apiserverLinodeNBConfig) - return linodeNBConfig, nil + return nbConfigs, nil } // AddNodeToNB adds a backend Node on the Node Balancer configuration @@ -129,12 +128,12 @@ func AddNodeToNB( return err } - lbPort := defaultLBPort - if machineScope.LinodeCluster.Spec.Network.LoadBalancerPort != 0 { - lbPort = machineScope.LinodeCluster.Spec.Network.LoadBalancerPort + apiserverLBPort := defaultApiserverLBPort + if machineScope.LinodeCluster.Spec.Network.ApiserverLoadBalancerPort != 0 { + apiserverLBPort = machineScope.LinodeCluster.Spec.Network.ApiserverLoadBalancerPort } - if machineScope.LinodeCluster.Spec.Network.NodeBalancerConfigID == nil { + if machineScope.LinodeCluster.Spec.Network.ApiserverNodeBalancerConfigID == nil { err := errors.New("nil NodeBalancer Config ID") logger.Error(err, "config ID for NodeBalancer is nil") @@ -144,16 +143,15 @@ func AddNodeToNB( _, err = machineScope.LinodeClient.CreateNodeBalancerNode( ctx, *machineScope.LinodeCluster.Spec.Network.NodeBalancerID, - *machineScope.LinodeCluster.Spec.Network.NodeBalancerConfigID, + *machineScope.LinodeCluster.Spec.Network.ApiserverNodeBalancerConfigID, linodego.NodeBalancerNodeCreateOptions{ Label: machineScope.Cluster.Name, - Address: fmt.Sprintf("%s:%d", addresses.IPv4.Private[0].Address, lbPort), + Address: fmt.Sprintf("%s:%d", addresses.IPv4.Private[0].Address, apiserverLBPort), Mode: linodego.ModeAccept, }, ) if err != nil { logger.Error(err, "Failed to update Node Balancer") - return err } @@ -180,7 +178,7 @@ func DeleteNodeFromNB( err := machineScope.LinodeClient.DeleteNodeBalancerNode( ctx, *machineScope.LinodeCluster.Spec.Network.NodeBalancerID, - *machineScope.LinodeCluster.Spec.Network.NodeBalancerConfigID, + *machineScope.LinodeCluster.Spec.Network.ApiserverNodeBalancerConfigID, *machineScope.LinodeMachine.Spec.InstanceID, ) if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil { diff --git a/cloud/services/loadbalancers_test.go b/cloud/services/loadbalancers_test.go index 65e04b9f8..26a36ac67 100644 --- a/cloud/services/loadbalancers_test.go +++ b/cloud/services/loadbalancers_test.go @@ -14,6 +14,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/mock" ) @@ -30,13 +31,13 @@ func TestCreateNodeBalancer(t *testing.T) { { name: "Success - Create NodeBalancer", clusterScope: &scope.ClusterScope{ - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, @@ -55,13 +56,13 @@ func TestCreateNodeBalancer(t *testing.T) { { name: "Success - List NodeBalancers returns one nodebalancer and we return that", clusterScope: &scope.ClusterScope{ - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, @@ -85,13 +86,13 @@ func TestCreateNodeBalancer(t *testing.T) { { name: "Error - List NodeBalancers returns one nodebalancer but there is a nodebalancer conflict", clusterScope: &scope.ClusterScope{ - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, @@ -116,13 +117,13 @@ func TestCreateNodeBalancer(t *testing.T) { { name: "Error - List NodeBalancers returns an error", clusterScope: &scope.ClusterScope{ - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, @@ -136,13 +137,13 @@ func TestCreateNodeBalancer(t *testing.T) { { name: "Error - Create NodeBalancer returns an error", clusterScope: &scope.ClusterScope{ - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, @@ -180,42 +181,44 @@ func TestCreateNodeBalancer(t *testing.T) { } } -func TestCreateNodeBalancerConfig(t *testing.T) { +func TestCreateNodeBalancerConfigs(t *testing.T) { t.Parallel() tests := []struct { - name string - clusterScope *scope.ClusterScope - expectedConfig *linodego.NodeBalancerConfig - expectedError error - expects func(*mock.MockLinodeClient) + name string + clusterScope *scope.ClusterScope + expectedConfigs []*linodego.NodeBalancerConfig + expectedError error + expects func(*mock.MockLinodeClient) }{ { - name: "Success - Create NodeBalancerConfig using default LB port", + name: "Success - Create NodeBalancerConfig using default LB ports", clusterScope: &scope.ClusterScope{ LinodeClient: nil, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, }, }, - expectedConfig: &linodego.NodeBalancerConfig{ - Port: defaultLBPort, - Protocol: linodego.ProtocolTCP, - Algorithm: linodego.AlgorithmRoundRobin, - Check: linodego.CheckConnection, - NodeBalancerID: 1234, + expectedConfigs: []*linodego.NodeBalancerConfig{ + { + Port: defaultApiserverLBPort, + Protocol: linodego.ProtocolTCP, + Algorithm: linodego.AlgorithmRoundRobin, + Check: linodego.CheckConnection, + NodeBalancerID: 1234, + }, }, expects: func(mockClient *mock.MockLinodeClient) { mockClient.EXPECT().CreateNodeBalancerConfig(gomock.Any(), gomock.Any(), gomock.Any()).Return(&linodego.NodeBalancerConfig{ - Port: defaultLBPort, + Port: defaultApiserverLBPort, Protocol: linodego.ProtocolTCP, Algorithm: linodego.AlgorithmRoundRobin, Check: linodego.CheckConnection, @@ -224,28 +227,30 @@ func TestCreateNodeBalancerConfig(t *testing.T) { }, }, { - name: "Success - Create NodeBalancerConfig using assigned LB port", + name: "Success - Create NodeBalancerConfig using assigned LB ports", clusterScope: &scope.ClusterScope{ LinodeClient: nil, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1234), - LoadBalancerPort: 80, + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1234), + ApiserverLoadBalancerPort: 80, }, }, }, }, - expectedConfig: &linodego.NodeBalancerConfig{ - Port: 80, - Protocol: linodego.ProtocolTCP, - Algorithm: linodego.AlgorithmRoundRobin, - Check: linodego.CheckConnection, - NodeBalancerID: 1234, + expectedConfigs: []*linodego.NodeBalancerConfig{ + { + Port: 80, + Protocol: linodego.ProtocolTCP, + Algorithm: linodego.AlgorithmRoundRobin, + Check: linodego.CheckConnection, + NodeBalancerID: 1234, + }, }, expects: func(mockClient *mock.MockLinodeClient) { mockClient.EXPECT().CreateNodeBalancerConfig(gomock.Any(), gomock.Any(), gomock.Any()).Return(&linodego.NodeBalancerConfig{ @@ -258,27 +263,29 @@ func TestCreateNodeBalancerConfig(t *testing.T) { }, }, { - name: "Error - CreateNodeBalancerConfig() returns and error", + name: "Error - CreateNodeBalancerConfig() returns an error when creating nbconfig for apiserver", clusterScope: &scope.ClusterScope{ LinodeClient: nil, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: ptr.To(1234), }, }, }, }, - expectedConfig: &linodego.NodeBalancerConfig{ - Port: defaultLBPort, - Protocol: linodego.ProtocolTCP, - Algorithm: linodego.AlgorithmRoundRobin, - Check: linodego.CheckConnection, - NodeBalancerID: 1234, + expectedConfigs: []*linodego.NodeBalancerConfig{ + { + Port: defaultApiserverLBPort, + Protocol: linodego.ProtocolTCP, + Algorithm: linodego.AlgorithmRoundRobin, + Check: linodego.CheckConnection, + NodeBalancerID: 1234, + }, }, expectedError: fmt.Errorf("error creating NodeBalancerConfig"), expects: func(mockClient *mock.MockLinodeClient) { @@ -300,12 +307,12 @@ func TestCreateNodeBalancerConfig(t *testing.T) { testcase.expects(MockLinodeClient) - got, err := CreateNodeBalancerConfig(context.Background(), testcase.clusterScope, logr.Discard()) + got, err := CreateNodeBalancerConfigs(context.Background(), testcase.clusterScope, logr.Discard()) if testcase.expectedError != nil { assert.ErrorContains(t, err, testcase.expectedError.Error()) } else { assert.NotEmpty(t, got) - assert.Equal(t, testcase.expectedConfig, got) + assert.Equal(t, testcase.expectedConfigs, got) } }) } @@ -321,7 +328,7 @@ func TestAddNodeToNBConditions(t *testing.T) { expects func(*mock.MockLinodeClient) }{ { - name: "Error - NodeBalancerConfigID are is set", + name: "Error - ApiserverNodeBalancerConfigID is not set", machineScope: &scope.MachineScope{ Machine: &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ @@ -332,16 +339,16 @@ func TestAddNodeToNBConditions(t *testing.T) { }, }, }, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1234), - NodeBalancerConfigID: nil, - LoadBalancerPort: 6443, + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1234), + ApiserverNodeBalancerConfigID: nil, + ApiserverLoadBalancerPort: defaultApiserverLBPort, }, }, }, @@ -493,15 +500,15 @@ func TestAddNodeToNBFullWorkflow(t *testing.T) { UID: "test-uid", }, }, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1234), - NodeBalancerConfigID: ptr.To(5678), + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1234), + ApiserverNodeBalancerConfigID: ptr.To(5678), }, }, }, @@ -546,15 +553,15 @@ func TestAddNodeToNBFullWorkflow(t *testing.T) { UID: "test-uid", }, }, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1234), - NodeBalancerConfigID: ptr.To(5678), + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1234), + ApiserverNodeBalancerConfigID: ptr.To(5678), }, }, }, @@ -653,12 +660,12 @@ func TestDeleteNodeFromNB(t *testing.T) { InstanceID: ptr.To(123), }, }, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: ""}, }, }, @@ -686,16 +693,16 @@ func TestDeleteNodeFromNB(t *testing.T) { InstanceID: ptr.To(123), }, }, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "1.2.3.4"}, - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1234), - NodeBalancerConfigID: ptr.To(5678), + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1234), + ApiserverNodeBalancerConfigID: ptr.To(5678), }, }, }, @@ -705,7 +712,7 @@ func TestDeleteNodeFromNB(t *testing.T) { }, }, { - name: "Error - Deleting Node from NodeBalancer", + name: "Error - Deleting Apiserver Node from NodeBalancer", machineScope: &scope.MachineScope{ Machine: &clusterv1.Machine{ ObjectMeta: metav1.ObjectMeta{ @@ -725,16 +732,16 @@ func TestDeleteNodeFromNB(t *testing.T) { InstanceID: ptr.To(123), }, }, - LinodeCluster: &infrav1alpha1.LinodeCluster{ + LinodeCluster: &infrav1alpha2.LinodeCluster{ ObjectMeta: metav1.ObjectMeta{ Name: "test-cluster", UID: "test-uid", }, - Spec: infrav1alpha1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "1.2.3.4"}, - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1234), - NodeBalancerConfigID: ptr.To(5678), + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1234), + ApiserverNodeBalancerConfigID: ptr.To(5678), }, }, }, diff --git a/cmd/main.go b/cmd/main.go index f71b5a5f5..a05251636 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,9 +29,11 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" infrastructurev1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrastructurev1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" controller2 "github.com/linode/cluster-api-provider-linode/controller" "github.com/linode/cluster-api-provider-linode/version" @@ -51,10 +53,10 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(capi.AddToScheme(scheme)) utilruntime.Must(infrastructurev1alpha1.AddToScheme(scheme)) + utilruntime.Must(infrastructurev1alpha2.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } -//nolint:cyclop // main func main() { var ( // Environment variables @@ -151,22 +153,7 @@ func main() { os.Exit(1) } if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err = (&infrastructurev1alpha1.LinodeCluster{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "LinodeCluster") - os.Exit(1) - } - if err = (&infrastructurev1alpha1.LinodeMachine{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "LinodeMachine") - os.Exit(1) - } - if err = (&infrastructurev1alpha1.LinodeVPC{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "LinodeVPC") - os.Exit(1) - } - if err = (&infrastructurev1alpha1.LinodeObjectStorageBucket{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "LinodeObjectStorageBucket") - os.Exit(1) - } + setupWebhooks(mgr) } // +kubebuilder:scaffold:builder @@ -185,3 +172,27 @@ func main() { os.Exit(1) } } + +func setupWebhooks(mgr manager.Manager) { + var err error + if err = (&infrastructurev1alpha1.LinodeCluster{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LinodeCluster") + os.Exit(1) + } + if err = (&infrastructurev1alpha2.LinodeCluster{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LinodeCluster") + os.Exit(1) + } + if err = (&infrastructurev1alpha1.LinodeMachine{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LinodeMachine") + os.Exit(1) + } + if err = (&infrastructurev1alpha1.LinodeVPC{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LinodeVPC") + os.Exit(1) + } + if err = (&infrastructurev1alpha1.LinodeObjectStorageBucket{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LinodeObjectStorageBucket") + os.Exit(1) + } +} diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml index 59d181510..6944bc412 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclusters.yaml @@ -252,6 +252,240 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Cluster to which this LinodeCluster belongs + jsonPath: .metadata.labels.cluster\.x-k8s\.io/cluster-name + name: Cluster + type: string + - description: Cluster infrastructure is ready for Linode instances + jsonPath: .status.ready + name: Ready + type: string + - description: API Endpoint + jsonPath: .spec.ControlPlaneEndpoint + name: Endpoint + priority: 1 + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: LinodeCluster is the Schema for the linodeclusters API + 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: LinodeClusterSpec defines the desired state of LinodeCluster + properties: + controlPlaneEndpoint: + description: |- + ControlPlaneEndpoint represents the endpoint used to communicate with the LinodeCluster control plane. + If ControlPlaneEndpoint is unset then the Nodebalancer ip will be used. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not + supplied then the credentials of the controller will be used. + properties: + name: + description: name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: namespace defines the space within which the secret + name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + network: + description: NetworkSpec encapsulates all things related to Linode + network. + properties: + apiserverLoadBalancerPort: + description: |- + apiserverLoadBalancerPort used by the api server. It must be valid ports range (1-65535). + If omitted, default value is 6443. + maximum: 65535 + minimum: 1 + type: integer + apiserverNodeBalancerConfigID: + description: apiserverNodeBalancerConfigID is the config ID of + api server NodeBalancer fonfig. + type: integer + loadBalancerType: + description: LoadBalancerType is the type of load balancer to + use, defaults to NodeBalancer if not otherwise set + enum: + - NodeBalancer + type: string + nodeBalancerID: + description: NodeBalancerID is the id of NodeBalancer. + type: integer + type: object + region: + description: The Linode Region the LinodeCluster lives in. + type: string + vpcRef: + description: |- + ObjectReference contains enough information to let you inspect or modify the referred object. + --- + New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. + 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. + 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular + restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". + Those cannot be well described when embedded. + 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. + 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity + during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple + and the version of the actual struct is irrelevant. + 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type + will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. + + + Instead of using this type, create a locally provided and used type that is well-focused on your reference. + For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + required: + - region + type: object + status: + description: LinodeClusterStatus defines the observed state of LinodeCluster + properties: + conditions: + description: Conditions defines current service state of the LinodeCluster. + items: + description: Condition defines an observation of a Cluster API resource + operational state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + A human readable message indicating details about the transition. + This field may be empty. + type: string + reason: + description: |- + The reason for the condition's last transition in CamelCase. + The specific API may choose whether or not this field is considered a guaranteed API. + This field may not be empty. + type: string + severity: + description: |- + Severity provides an explicit classification of Reason code, so the users or machines can immediately + understand the current situation and act accordingly. + The Severity field MUST be set only when Status=False. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: |- + Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions + can be useful (see .node.status.conditions), the ability to deconflict is important. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + failureMessage: + description: |- + FailureMessage will be set in the event that there is a terminal problem + reconciling the LinodeCluster and will contain a more verbose string suitable + for logging and human consumption. + type: string + failureReason: + description: |- + FailureReason will be set in the event that there is a terminal problem + reconciling the LinodeCluster and will contain a succinct value suitable + for machine interpretation. + type: string + ready: + description: Ready denotes that the cluster (infrastructure) is ready. + type: boolean + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index f99ee0a5c..bb807bd28 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,7 +2,6 @@ # https://cluster-api.sigs.k8s.io/developer/providers/contracts.html#api-version-labels labels: - pairs: - cluster.x-k8s.io/v1beta1: v1alpha1 cluster.x-k8s.io/provider: "infrastructure-linode" visualizer.cluster.x-k8s.io: "" @@ -27,8 +26,16 @@ patches: #- path: patches/webhook_in_linodeclustertemplates.yaml - path: patches/webhook_in_linodevpcs.yaml - path: patches/webhook_in_linodeobjectstoragebuckets.yaml +- path: patches/webhook_in_infrastructure_linodeclusters.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch +- path: patches/capicontract_in_linodeclusters.yaml +- path: patches/capicontract_in_linodemachines.yaml +- path: patches/capicontract_in_linodemachinetemplates.yaml +- path: patches/capicontract_in_linodeclustertemplates.yaml +- path: patches/capicontract_in_linodeobjectstoragebuckets.yaml +- path: patches/capicontract_in_linodevpcs.yaml + # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD - path: patches/cainjection_in_linodeclusters.yaml @@ -37,6 +44,7 @@ patches: #- path: patches/cainjection_in_linodeclustertemplates.yaml - path: patches/cainjection_in_linodevpcs.yaml - path: patches/cainjection_in_linodeobjectstoragebuckets.yaml +- path: patches/cainjection_in_infrastructure_linodeclusters.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # [VALIDATION] diff --git a/config/crd/patches/cainjection_in_infrastructure_linodeclusters.yaml b/config/crd/patches/cainjection_in_infrastructure_linodeclusters.yaml new file mode 100644 index 000000000..9ed40ba4b --- /dev/null +++ b/config/crd/patches/cainjection_in_infrastructure_linodeclusters.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: linodeclusters.infrastructure.cluster.x-k8s.io diff --git a/config/crd/patches/capicontract_in_linodeclusters.yaml b/config/crd/patches/capicontract_in_linodeclusters.yaml new file mode 100644 index 000000000..8a11a7ef6 --- /dev/null +++ b/config/crd/patches/capicontract_in_linodeclusters.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodeclusters.infrastructure.cluster.x-k8s.io + labels: + cluster.x-k8s.io/v1beta1: v1alpha2 diff --git a/config/crd/patches/capicontract_in_linodeclustertemplates.yaml b/config/crd/patches/capicontract_in_linodeclustertemplates.yaml new file mode 100644 index 000000000..54b35826c --- /dev/null +++ b/config/crd/patches/capicontract_in_linodeclustertemplates.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodeclustertemplates.infrastructure.cluster.x-k8s.io + labels: + cluster.x-k8s.io/v1beta1: v1alpha1 diff --git a/config/crd/patches/capicontract_in_linodemachines.yaml b/config/crd/patches/capicontract_in_linodemachines.yaml new file mode 100644 index 000000000..2fa76546b --- /dev/null +++ b/config/crd/patches/capicontract_in_linodemachines.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodemachines.infrastructure.cluster.x-k8s.io + labels: + cluster.x-k8s.io/v1beta1: v1alpha1 diff --git a/config/crd/patches/capicontract_in_linodemachinetemplates.yaml b/config/crd/patches/capicontract_in_linodemachinetemplates.yaml new file mode 100644 index 000000000..1c8ba1b05 --- /dev/null +++ b/config/crd/patches/capicontract_in_linodemachinetemplates.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodemachinetemplates.infrastructure.cluster.x-k8s.io + labels: + cluster.x-k8s.io/v1beta1: v1alpha1 diff --git a/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml b/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml new file mode 100644 index 000000000..49426ee49 --- /dev/null +++ b/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodeobjectstoragebuckets.infrastructure.cluster.x-k8s.io + labels: + cluster.x-k8s.io/v1beta1: v1alpha1 diff --git a/config/crd/patches/capicontract_in_linodevpcs.yaml b/config/crd/patches/capicontract_in_linodevpcs.yaml new file mode 100644 index 000000000..186394177 --- /dev/null +++ b/config/crd/patches/capicontract_in_linodevpcs.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodevpcs.infrastructure.cluster.x-k8s.io + labels: + cluster.x-k8s.io/v1beta1: v1alpha1 diff --git a/config/crd/patches/webhook_in_infrastructure_linodeclusters.yaml b/config/crd/patches/webhook_in_infrastructure_linodeclusters.yaml new file mode 100644 index 000000000..76c3ff5e3 --- /dev/null +++ b/config/crd/patches/webhook_in_infrastructure_linodeclusters.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: linodeclusters.infrastructure.cluster.x-k8s.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/infrastructure_linodecluster_editor_role.yaml b/config/rbac/infrastructure_linodecluster_editor_role.yaml new file mode 100644 index 000000000..723896e51 --- /dev/null +++ b/config/rbac/infrastructure_linodecluster_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit linodeclusters. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: linodecluster-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: cluster-api-provider-linode + app.kubernetes.io/part-of: cluster-api-provider-linode + app.kubernetes.io/managed-by: kustomize + name: linodecluster-editor-role +rules: +- apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - linodeclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - linodeclusters/status + verbs: + - get diff --git a/config/rbac/infrastructure_linodecluster_viewer_role.yaml b/config/rbac/infrastructure_linodecluster_viewer_role.yaml new file mode 100644 index 000000000..4e857910b --- /dev/null +++ b/config/rbac/infrastructure_linodecluster_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view linodeclusters. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: linodecluster-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: cluster-api-provider-linode + app.kubernetes.io/part-of: cluster-api-provider-linode + app.kubernetes.io/managed-by: kustomize + name: linodecluster-viewer-role +rules: +- apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - linodeclusters + verbs: + - get + - list + - watch +- apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - linodeclusters/status + verbs: + - get diff --git a/config/samples/infrastructure_v1alpha2_linodecluster.yaml b/config/samples/infrastructure_v1alpha2_linodecluster.yaml new file mode 100644 index 000000000..b76169e74 --- /dev/null +++ b/config/samples/infrastructure_v1alpha2_linodecluster.yaml @@ -0,0 +1,9 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 +kind: LinodeCluster +metadata: + labels: + app.kubernetes.io/name: cluster-api-provider-linode + app.kubernetes.io/managed-by: kustomize + name: linodecluster-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index cbc113d74..1b6746a21 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -6,4 +6,5 @@ resources: - infrastructure_v1alpha1_linodeclustertemplate.yaml - infrastructure_v1alpha1_linodevpc.yaml - infrastructure_v1alpha1_linodeobjectstoragebucket.yaml +- infrastructure_v1alpha2_linodecluster.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 39efa456a..cab8e5b7d 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -4,6 +4,25 @@ kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1alpha2-linodecluster + failurePolicy: Fail + name: vlinodecluster.kb.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1alpha2 + operations: + - CREATE + resources: + - linodeclusters + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/controller/linodecluster_controller.go b/controller/linodecluster_controller.go index 7c3729378..e8b7e0840 100644 --- a/controller/linodecluster_controller.go +++ b/controller/linodecluster_controller.go @@ -38,7 +38,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" - infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/cloud/services" "github.com/linode/cluster-api-provider-linode/util" @@ -66,7 +66,7 @@ func (r *LinodeClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques defer cancel() logger := ctrl.LoggerFrom(ctx).WithName("LinodeClusterReconciler").WithValues("name", req.NamespacedName.String()) - linodeCluster := &infrav1alpha1.LinodeCluster{} + linodeCluster := &infrav1alpha2.LinodeCluster{} if err := r.Client.Get(ctx, req.NamespacedName, linodeCluster); err != nil { logger.Info("Failed to fetch Linode cluster", "error", err.Error()) @@ -189,18 +189,18 @@ func (r *LinodeClusterReconciler) reconcileCreate(ctx context.Context, logger lo clusterScope.LinodeCluster.Spec.Network.NodeBalancerID = &linodeNB.ID - linodeNBConfig, err := services.CreateNodeBalancerConfig(ctx, clusterScope, logger) + configs, err := services.CreateNodeBalancerConfigs(ctx, clusterScope, logger) if err != nil { logger.Error(err, "failed to create nodebalancer config") setFailureReason(clusterScope, cerrs.CreateClusterError, err, r) return err } - clusterScope.LinodeCluster.Spec.Network.NodeBalancerConfigID = util.Pointer(linodeNBConfig.ID) + clusterScope.LinodeCluster.Spec.Network.ApiserverNodeBalancerConfigID = util.Pointer(configs[0].ID) clusterScope.LinodeCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{ Host: *linodeNB.IPv4, - Port: int32(linodeNBConfig.Port), + Port: int32(configs[0].Port), } return nil @@ -216,7 +216,7 @@ func (r *LinodeClusterReconciler) reconcileDelete(ctx context.Context, logger lo setFailureReason(clusterScope, cerrs.DeleteClusterError, err, r) return err } - controllerutil.RemoveFinalizer(clusterScope.LinodeCluster, infrav1alpha1.GroupVersion.String()) + controllerutil.RemoveFinalizer(clusterScope.LinodeCluster, infrav1alpha2.GroupVersion.String()) r.Recorder.Event(clusterScope.LinodeCluster, corev1.EventTypeWarning, "NodeBalancerIDMissing", "NodeBalancer ID is missing, nothing to do") return nil @@ -233,14 +233,14 @@ func (r *LinodeClusterReconciler) reconcileDelete(ctx context.Context, logger lo r.Recorder.Event(clusterScope.LinodeCluster, corev1.EventTypeNormal, clusterv1.DeletedReason, "Load balancer deleted") clusterScope.LinodeCluster.Spec.Network.NodeBalancerID = nil - clusterScope.LinodeCluster.Spec.Network.NodeBalancerConfigID = nil + clusterScope.LinodeCluster.Spec.Network.ApiserverNodeBalancerConfigID = nil if err := clusterScope.RemoveCredentialsRefFinalizer(ctx); err != nil { logger.Error(err, "failed to remove credentials finalizer") setFailureReason(clusterScope, cerrs.DeleteClusterError, err, r) return err } - controllerutil.RemoveFinalizer(clusterScope.LinodeCluster, infrav1alpha1.GroupVersion.String()) + controllerutil.RemoveFinalizer(clusterScope.LinodeCluster, infrav1alpha2.GroupVersion.String()) return nil } @@ -248,12 +248,12 @@ func (r *LinodeClusterReconciler) reconcileDelete(ctx context.Context, logger lo // SetupWithManager sets up the controller with the Manager. func (r *LinodeClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { err := ctrl.NewControllerManagedBy(mgr). - For(&infrav1alpha1.LinodeCluster{}). + For(&infrav1alpha2.LinodeCluster{}). WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(mgr.GetLogger(), r.WatchFilterValue)). Watches( &clusterv1.Cluster{}, handler.EnqueueRequestsFromMapFunc( - kutil.ClusterToInfrastructureMapFunc(context.TODO(), infrav1alpha1.GroupVersion.WithKind("LinodeCluster"), mgr.GetClient(), &infrav1alpha1.LinodeCluster{}), + kutil.ClusterToInfrastructureMapFunc(context.TODO(), infrav1alpha2.GroupVersion.WithKind("LinodeCluster"), mgr.GetClient(), &infrav1alpha2.LinodeCluster{}), ), builder.WithPredicates(predicates.ClusterUnpausedAndInfrastructureReady(mgr.GetLogger())), ).Complete(r) diff --git a/controller/linodecluster_controller_test.go b/controller/linodecluster_controller_test.go index 68282b6f3..ddf27d126 100644 --- a/controller/linodecluster_controller_test.go +++ b/controller/linodecluster_controller_test.go @@ -30,7 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/mock" rec "github.com/linode/cluster-api-provider-linode/util/reconciler" @@ -58,9 +58,9 @@ var _ = Describe("cluster-lifecycle", Ordered, Label("cluster", "cluster-lifecyc Namespace: clusterNameSpace, OwnerReferences: ownerRefs, } - linodeCluster := infrav1.LinodeCluster{ + linodeCluster := infrav1alpha2.LinodeCluster{ ObjectMeta: metadata, - Spec: infrav1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ Region: "us-ord", }, } @@ -242,11 +242,11 @@ var _ = Describe("cluster-delete", Ordered, Label("cluster", "cluster-delete"), OwnerReferences: ownerRefs, } - linodeCluster := infrav1.LinodeCluster{ + linodeCluster := infrav1alpha2.LinodeCluster{ ObjectMeta: metadata, - Spec: infrav1.LinodeClusterSpec{ + Spec: infrav1alpha2.LinodeClusterSpec{ Region: "us-ord", - Network: infrav1.NetworkSpec{ + Network: infrav1alpha2.NetworkSpec{ NodeBalancerID: &nodebalancerID, }, }, diff --git a/controller/linodemachine_controller.go b/controller/linodemachine_controller.go index ad8267950..db92e09aa 100644 --- a/controller/linodemachine_controller.go +++ b/controller/linodemachine_controller.go @@ -45,6 +45,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/cloud/services" "github.com/linode/cluster-api-provider-linode/util" @@ -142,7 +143,7 @@ func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reques Client: r.Client, Cluster: cluster, Machine: machine, - LinodeCluster: &infrav1alpha1.LinodeCluster{}, + LinodeCluster: &infrav1alpha2.LinodeCluster{}, LinodeMachine: linodeMachine, }, ) diff --git a/controller/linodemachine_controller_helpers.go b/controller/linodemachine_controller_helpers.go index 2e0a715a3..1b3a8c35f 100644 --- a/controller/linodemachine_controller_helpers.go +++ b/controller/linodemachine_controller_helpers.go @@ -38,6 +38,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/cloud/services" "github.com/linode/cluster-api-provider-linode/util" @@ -199,7 +200,7 @@ func (r *LinodeMachineReconciler) linodeClusterToLinodeMachines(logger logr.Logg ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout) defer cancel() - linodeCluster, ok := o.(*infrav1alpha1.LinodeCluster) + linodeCluster, ok := o.(*infrav1alpha2.LinodeCluster) if !ok { logger.Info("Failed to cast object to Cluster") diff --git a/controller/linodemachine_controller_test.go b/controller/linodemachine_controller_test.go index 312c320c3..6de7dddfb 100644 --- a/controller/linodemachine_controller_test.go +++ b/controller/linodemachine_controller_test.go @@ -35,6 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/mock" rutil "github.com/linode/cluster-api-provider-linode/util/reconciler" @@ -60,11 +61,11 @@ var _ = Describe("create", Label("machine", "create"), func() { }, } - linodeCluster := infrav1alpha1.LinodeCluster{ - Spec: infrav1alpha1.LinodeClusterSpec{ - Network: infrav1alpha1.NetworkSpec{ - NodeBalancerID: ptr.To(1), - NodeBalancerConfigID: ptr.To(2), + linodeCluster := infrav1alpha2.LinodeCluster{ + Spec: infrav1alpha2.LinodeClusterSpec{ + Network: infrav1alpha2.NetworkSpec{ + NodeBalancerID: ptr.To(1), + ApiserverNodeBalancerConfigID: ptr.To(2), }, }, } diff --git a/controller/suite_test.go b/controller/suite_test.go index 77cc08110..716069154 100644 --- a/controller/suite_test.go +++ b/controller/suite_test.go @@ -36,6 +36,7 @@ import ( clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" //+kubebuilder:scaffold:imports @@ -135,6 +136,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) Expect(infrav1.AddToScheme(scheme.Scheme)).To(Succeed()) + Expect(infrav2.AddToScheme(scheme.Scheme)).To(Succeed()) Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) //+kubebuilder:scaffold:scheme diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index c3e779b38..790aa9d15 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,13 +7,13 @@ - [Addons](./topics/addons.md) - [Flavors](./topics/flavors/flavors.md) - [Default (kubeadm)](./topics/flavors/default.md) - - [Dual-stack (kubeadm)](./topics/flavors/dual-stack.md) - - [Etcd-disk (kubeadm)](./topics/flavors/etcd-disk.md) + - [Dual-stack](./topics/flavors/dual-stack.md) + - [Etcd-disk](./topics/flavors/etcd-disk.md) - [ClusterClass kubeadm](./topics/flavors/clusterclass-kubeadm.md) - - [Cluster Autoscaler (kubeadm)](./topics/flavors/cluster-autoscaler.md) + - [Cluster Autoscaler](./topics/flavors/cluster-autoscaler.md) - [k3s](./topics/flavors/k3s.md) - [rke2](./topics/flavors/rke2.md) - - [vpcless (kubeadm)](./topics/flavors/vpcless.md) + - [vpcless](./topics/flavors/vpcless.md) - [Etcd](./topics/etcd.md) - [Backups](./topics/backups.md) - [Multi-Tenancy](./topics/multi-tenancy.md) diff --git a/e2e/linodecluster-controller/minimal-linodecluster/assert-linodecluster.yaml b/e2e/linodecluster-controller/minimal-linodecluster/assert-linodecluster.yaml index 0270ba293..b98749959 100644 --- a/e2e/linodecluster-controller/minimal-linodecluster/assert-linodecluster.yaml +++ b/e2e/linodecluster-controller/minimal-linodecluster/assert-linodecluster.yaml @@ -1,9 +1,9 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ($cluster) finalizers: - - infrastructure.cluster.x-k8s.io/v1alpha1 + - infrastructure.cluster.x-k8s.io/v1alpha2 spec: region: us-sea status: diff --git a/e2e/linodecluster-controller/minimal-linodecluster/chainsaw-test.yaml b/e2e/linodecluster-controller/minimal-linodecluster/chainsaw-test.yaml index 1527c83a6..bb1703336 100755 --- a/e2e/linodecluster-controller/minimal-linodecluster/chainsaw-test.yaml +++ b/e2e/linodecluster-controller/minimal-linodecluster/chainsaw-test.yaml @@ -41,7 +41,7 @@ spec: file: assert-linodecluster.yaml catch: - describe: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster - name: Check if the nodebalancer exists try: diff --git a/e2e/linodecluster-controller/minimal-linodecluster/check-linodecluster-deleted.yaml b/e2e/linodecluster-controller/minimal-linodecluster/check-linodecluster-deleted.yaml index ff0a23f66..582ecdb39 100644 --- a/e2e/linodecluster-controller/minimal-linodecluster/check-linodecluster-deleted.yaml +++ b/e2e/linodecluster-controller/minimal-linodecluster/check-linodecluster-deleted.yaml @@ -1,4 +1,4 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ($cluster) diff --git a/e2e/linodecluster-controller/minimal-linodecluster/create-cluster.yaml b/e2e/linodecluster-controller/minimal-linodecluster/create-cluster.yaml index 7dcc8ee39..3b2be492c 100644 --- a/e2e/linodecluster-controller/minimal-linodecluster/create-cluster.yaml +++ b/e2e/linodecluster-controller/minimal-linodecluster/create-cluster.yaml @@ -4,6 +4,6 @@ metadata: name: ($cluster) spec: infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster name: ($cluster) diff --git a/e2e/linodecluster-controller/minimal-linodecluster/create-linodecluster.yaml b/e2e/linodecluster-controller/minimal-linodecluster/create-linodecluster.yaml index d0bae6e10..48eeeb6f7 100644 --- a/e2e/linodecluster-controller/minimal-linodecluster/create-linodecluster.yaml +++ b/e2e/linodecluster-controller/minimal-linodecluster/create-linodecluster.yaml @@ -1,4 +1,4 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ($cluster) diff --git a/e2e/linodemachine-controller/minimal-linodemachine/create-cluster.yaml b/e2e/linodemachine-controller/minimal-linodemachine/create-cluster.yaml index 1914a1737..34e1af29b 100644 --- a/e2e/linodemachine-controller/minimal-linodemachine/create-cluster.yaml +++ b/e2e/linodemachine-controller/minimal-linodemachine/create-cluster.yaml @@ -9,11 +9,11 @@ spec: kind: KubeadmControlPlane name: ($cluster) infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster name: ($cluster) --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ($cluster) diff --git a/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml b/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml index eb7fabafd..cc7871dc7 100644 --- a/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml +++ b/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml @@ -9,11 +9,11 @@ spec: kind: KubeadmControlPlane name: ($cluster) infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster name: ($cluster) --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ($cluster) diff --git a/templates/flavors/clusterclass-kubeadm/deleteTransformer.yaml b/templates/flavors/clusterclass-kubeadm/deleteTransformer.yaml index 0a818c5af..492b32391 100644 --- a/templates/flavors/clusterclass-kubeadm/deleteTransformer.yaml +++ b/templates/flavors/clusterclass-kubeadm/deleteTransformer.yaml @@ -6,7 +6,7 @@ metadata: name: LinodeCluster-patch-delete patch: |- $patch: delete - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ${CLUSTER_NAME} diff --git a/templates/flavors/kubeadm/vpcless/kustomization.yaml b/templates/flavors/kubeadm/vpcless/kustomization.yaml index 102d5984c..116c7c1a6 100644 --- a/templates/flavors/kubeadm/vpcless/kustomization.yaml +++ b/templates/flavors/kubeadm/vpcless/kustomization.yaml @@ -46,7 +46,7 @@ patches: name: ${VPC_NAME:=${CLUSTER_NAME}} - target: group: infrastructure.cluster.x-k8s.io - version: v1alpha1 + version: v1alpha2 kind: LinodeCluster patch: |- - op: remove diff --git a/templates/infra/cluster.yaml b/templates/infra/cluster.yaml index 4a0791a6f..478a55493 100644 --- a/templates/infra/cluster.yaml +++ b/templates/infra/cluster.yaml @@ -15,6 +15,6 @@ spec: kind: REPLACEME name: ${CLUSTER_NAME}-control-plane infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster name: ${CLUSTER_NAME} diff --git a/templates/infra/linodeCluster.yaml b/templates/infra/linodeCluster.yaml index 565a89ff3..eaf2ba0e4 100644 --- a/templates/infra/linodeCluster.yaml +++ b/templates/infra/linodeCluster.yaml @@ -1,5 +1,5 @@ --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster metadata: name: ${CLUSTER_NAME}