diff --git a/PROJECT b/PROJECT index 06dcb9287..6b1308ab3 100644 --- a/PROJECT +++ b/PROJECT @@ -141,4 +141,16 @@ resources: webhooks: conversion: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + domain: cluster.x-k8s.io + group: infrastructure + kind: LinodeObjectStorageBucket + path: github.com/linode/cluster-api-provider-linode/api/v1alpha2 + version: v1alpha2 + webhooks: + conversion: true + validation: true + webhookVersion: v1 version: "3" diff --git a/api/v1alpha1/conversion.go b/api/v1alpha1/conversion.go index d64d33d7d..35f0a593e 100644 --- a/api/v1alpha1/conversion.go +++ b/api/v1alpha1/conversion.go @@ -17,6 +17,8 @@ limitations under the License. package v1alpha1 import ( + "strings" + "k8s.io/apimachinery/pkg/conversion" infrastructurev1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" @@ -43,3 +45,30 @@ func Convert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in *infras // Ok to use the auto-generated conversion function, it simply drops the PlacementGroupRef, and copies everything else return autoConvert_v1alpha2_LinodeMachineSpec_To_v1alpha1_LinodeMachineSpec(in, out, s) } + +func Convert_v1alpha1_LinodeObjectStorageBucketSpec_To_v1alpha2_LinodeObjectStorageBucketSpec(in *LinodeObjectStorageBucketSpec, out *infrastructurev1alpha2.LinodeObjectStorageBucketSpec, s conversion.Scope) error { + // WARNING: in.Cluster requires manual conversion: does not exist in peer-type + out.Region = in.Cluster + out.CredentialsRef = in.CredentialsRef + out.KeyGeneration = in.KeyGeneration + out.SecretType = in.SecretType + return nil +} + +func Convert_v1alpha2_LinodeObjectStorageBucketSpec_To_v1alpha1_LinodeObjectStorageBucketSpec(in *infrastructurev1alpha2.LinodeObjectStorageBucketSpec, out *LinodeObjectStorageBucketSpec, s conversion.Scope) error { + // WARNING: in.Region requires manual conversion: does not exist in peer-type + out.Cluster = in.Region + out.CredentialsRef = in.CredentialsRef + out.KeyGeneration = in.KeyGeneration + out.SecretType = in.SecretType + return nil +} + +func Convert_v1alpha2_LinodeObjectStorageBucket_To_v1alpha1_LinodeObjectStorageBucket(in *infrastructurev1alpha2.LinodeObjectStorageBucket, out *LinodeObjectStorageBucket, scope conversion.Scope) error { + if in.Status.Hostname != nil && *in.Status.Hostname != "" { + in.Spec.Region = strings.Split(*in.Status.Hostname, ".")[1] + } else { + in.Spec.Region += "-1" + } + return autoConvert_v1alpha2_LinodeObjectStorageBucket_To_v1alpha1_LinodeObjectStorageBucket(in, out, scope) +} diff --git a/api/v1alpha1/linodemachinetemplate_types.go b/api/v1alpha1/linodemachinetemplate_types.go index 5f537d6c3..80afb7040 100644 --- a/api/v1alpha1/linodemachinetemplate_types.go +++ b/api/v1alpha1/linodemachinetemplate_types.go @@ -32,6 +32,7 @@ type LinodeMachineTemplateResource struct { // +kubebuilder:object:root=true // +kubebuilder:resource:path=linodemachinetemplates,scope=Namespaced,categories=cluster-api,shortName=lmt +// +kubebuilder:metadata:labels="clusterctl.cluster.x-k8s.io/move-hierarchy=true" // LinodeMachineTemplate is the Schema for the linodemachinetemplates API type LinodeMachineTemplate struct { diff --git a/api/v1alpha1/linodeobjectstoragebucket_conversion.go b/api/v1alpha1/linodeobjectstoragebucket_conversion.go new file mode 100644 index 000000000..49d6f5a4a --- /dev/null +++ b/api/v1alpha1/linodeobjectstoragebucket_conversion.go @@ -0,0 +1,65 @@ +/* +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" + + utilconversion "sigs.k8s.io/cluster-api/util/conversion" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + infrastructurev1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" +) + +// ConvertTo converts this LinodeObjectStorageBucket to the Hub version (v1alpha2). +func (src *LinodeObjectStorageBucket) ConvertTo(dstRaw conversion.Hub) error { + dst, ok := dstRaw.(*infrastructurev1alpha2.LinodeObjectStorageBucket) + if !ok { + return errors.New("failed to convert LinodeObjectStorageBucket version from v1alpha1 to v1alpha2") + } + + if err := Convert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket(src, dst, nil); err != nil { + return err + } + + // Manually restore data from annotations + restored := &LinodeObjectStorageBucket{} + if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { + return err + } + + return nil +} + +// ConvertFrom converts from the Hub version (v1alpha2) to this version. +func (dst *LinodeObjectStorageBucket) ConvertFrom(srcRaw conversion.Hub) error { + src, ok := srcRaw.(*infrastructurev1alpha2.LinodeObjectStorageBucket) + if !ok { + return errors.New("failed to convert LinodeObjectStorageBucket version from v1alpha2 to v1alpha1") + } + + if err := Convert_v1alpha2_LinodeObjectStorageBucket_To_v1alpha1_LinodeObjectStorageBucket(src, dst, nil); err != nil { + return err + } + + // Preserve Hub data on down-conversion. + if err := utilconversion.MarshalData(src, dst); err != nil { + return err + } + + return nil +} diff --git a/api/v1alpha1/linodeobjectstoragebucket_conversion_test.go b/api/v1alpha1/linodeobjectstoragebucket_conversion_test.go new file mode 100644 index 000000000..5863efc22 --- /dev/null +++ b/api/v1alpha1/linodeobjectstoragebucket_conversion_test.go @@ -0,0 +1,141 @@ +/* +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" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + 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" +) + +const ( + ConversionDataAnnotation = "cluster.x-k8s.io/conversion-data" +) + +func TestLinodeObjectStorageBucketConvertTo(t *testing.T) { + t.Parallel() + + src := &LinodeObjectStorageBucket{ + ObjectMeta: metav1.ObjectMeta{Name: "test-bucket"}, + Spec: LinodeObjectStorageBucketSpec{ + Cluster: "us-mia-1", + CredentialsRef: &corev1.SecretReference{ + Namespace: "default", + Name: "cred-secret", + }, + KeyGeneration: ptr.To(1), + SecretType: "Opaque", + }, + Status: LinodeObjectStorageBucketStatus{}, + } + expectedDst := &infrav1alpha2.LinodeObjectStorageBucket{ + ObjectMeta: metav1.ObjectMeta{Name: "test-bucket"}, + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "us-mia-1", + CredentialsRef: &corev1.SecretReference{ + Namespace: "default", + Name: "cred-secret", + }, + KeyGeneration: ptr.To(1), + SecretType: "Opaque", + }, + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{}, + } + dst := &infrav1alpha2.LinodeObjectStorageBucket{} + + 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 TestLinodeObjectStorageBucketFrom(t *testing.T) { + t.Parallel() + + src := &infrav1alpha2.LinodeObjectStorageBucket{ + ObjectMeta: metav1.ObjectMeta{Name: "test-bucket"}, + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "us-mia", + CredentialsRef: &corev1.SecretReference{ + Namespace: "default", + Name: "cred-secret", + }, + KeyGeneration: ptr.To(1), + SecretType: "Opaque", + }, + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{}, + } + expectedDst := &LinodeObjectStorageBucket{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-bucket", + Annotations: map[string]string{ + ConversionDataAnnotation: `{"spec":{"credentialsRef":{"name":"cred-secret","namespace":"default"},"keyGeneration":1,"region":"us-mia-1","secretType":"Opaque"},"status":{"ready":false}}`, + }, + }, + Spec: LinodeObjectStorageBucketSpec{ + Cluster: "us-mia-1", + CredentialsRef: &corev1.SecretReference{ + Namespace: "default", + Name: "cred-secret", + }, + KeyGeneration: ptr.To(1), + SecretType: "Opaque", + }, + Status: LinodeObjectStorageBucketStatus{}, + } + dst := &LinodeObjectStorageBucket{} + + 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/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index 80549ec0b..309baa6d4 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -26,6 +26,7 @@ import ( v1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2" linodego "github.com/linode/linodego" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" v1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -224,6 +225,31 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*LinodeObjectStorageBucket)(nil), (*v1alpha2.LinodeObjectStorageBucket)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket(a.(*LinodeObjectStorageBucket), b.(*v1alpha2.LinodeObjectStorageBucket), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*LinodeObjectStorageBucketList)(nil), (*v1alpha2.LinodeObjectStorageBucketList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LinodeObjectStorageBucketList_To_v1alpha2_LinodeObjectStorageBucketList(a.(*LinodeObjectStorageBucketList), b.(*v1alpha2.LinodeObjectStorageBucketList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1alpha2.LinodeObjectStorageBucketList)(nil), (*LinodeObjectStorageBucketList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_LinodeObjectStorageBucketList_To_v1alpha1_LinodeObjectStorageBucketList(a.(*v1alpha2.LinodeObjectStorageBucketList), b.(*LinodeObjectStorageBucketList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*LinodeObjectStorageBucketStatus)(nil), (*v1alpha2.LinodeObjectStorageBucketStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus(a.(*LinodeObjectStorageBucketStatus), b.(*v1alpha2.LinodeObjectStorageBucketStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1alpha2.LinodeObjectStorageBucketStatus)(nil), (*LinodeObjectStorageBucketStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_LinodeObjectStorageBucketStatus_To_v1alpha1_LinodeObjectStorageBucketStatus(a.(*v1alpha2.LinodeObjectStorageBucketStatus), b.(*LinodeObjectStorageBucketStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*LinodeVPC)(nil), (*v1alpha2.LinodeVPC)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_LinodeVPC_To_v1alpha2_LinodeVPC(a.(*LinodeVPC), b.(*v1alpha2.LinodeVPC), scope) }); err != nil { @@ -284,6 +310,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*LinodeObjectStorageBucketSpec)(nil), (*v1alpha2.LinodeObjectStorageBucketSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LinodeObjectStorageBucketSpec_To_v1alpha2_LinodeObjectStorageBucketSpec(a.(*LinodeObjectStorageBucketSpec), b.(*v1alpha2.LinodeObjectStorageBucketSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*NetworkSpec)(nil), (*v1alpha2.NetworkSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_NetworkSpec_To_v1alpha2_NetworkSpec(a.(*NetworkSpec), b.(*v1alpha2.NetworkSpec), scope) }); err != nil { @@ -294,6 +325,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1alpha2.LinodeObjectStorageBucketSpec)(nil), (*LinodeObjectStorageBucketSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_LinodeObjectStorageBucketSpec_To_v1alpha1_LinodeObjectStorageBucketSpec(a.(*v1alpha2.LinodeObjectStorageBucketSpec), b.(*LinodeObjectStorageBucketSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1alpha2.LinodeObjectStorageBucket)(nil), (*LinodeObjectStorageBucket)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_LinodeObjectStorageBucket_To_v1alpha1_LinodeObjectStorageBucket(a.(*v1alpha2.LinodeObjectStorageBucket), b.(*LinodeObjectStorageBucket), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1alpha2.NetworkSpec)(nil), (*NetworkSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_NetworkSpec_To_v1alpha1_NetworkSpec(a.(*v1alpha2.NetworkSpec), b.(*NetworkSpec), scope) }); err != nil { @@ -900,6 +941,125 @@ func Convert_v1alpha2_LinodeMachineTemplateSpec_To_v1alpha1_LinodeMachineTemplat return autoConvert_v1alpha2_LinodeMachineTemplateSpec_To_v1alpha1_LinodeMachineTemplateSpec(in, out, s) } +func autoConvert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket(in *LinodeObjectStorageBucket, out *v1alpha2.LinodeObjectStorageBucket, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_LinodeObjectStorageBucketSpec_To_v1alpha2_LinodeObjectStorageBucketSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket is an autogenerated conversion function. +func Convert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket(in *LinodeObjectStorageBucket, out *v1alpha2.LinodeObjectStorageBucket, s conversion.Scope) error { + return autoConvert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket(in, out, s) +} + +func autoConvert_v1alpha2_LinodeObjectStorageBucket_To_v1alpha1_LinodeObjectStorageBucket(in *v1alpha2.LinodeObjectStorageBucket, out *LinodeObjectStorageBucket, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha2_LinodeObjectStorageBucketSpec_To_v1alpha1_LinodeObjectStorageBucketSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha2_LinodeObjectStorageBucketStatus_To_v1alpha1_LinodeObjectStorageBucketStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_LinodeObjectStorageBucketList_To_v1alpha2_LinodeObjectStorageBucketList(in *LinodeObjectStorageBucketList, out *v1alpha2.LinodeObjectStorageBucketList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1alpha2.LinodeObjectStorageBucket, len(*in)) + for i := range *in { + if err := Convert_v1alpha1_LinodeObjectStorageBucket_To_v1alpha2_LinodeObjectStorageBucket(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1alpha1_LinodeObjectStorageBucketList_To_v1alpha2_LinodeObjectStorageBucketList is an autogenerated conversion function. +func Convert_v1alpha1_LinodeObjectStorageBucketList_To_v1alpha2_LinodeObjectStorageBucketList(in *LinodeObjectStorageBucketList, out *v1alpha2.LinodeObjectStorageBucketList, s conversion.Scope) error { + return autoConvert_v1alpha1_LinodeObjectStorageBucketList_To_v1alpha2_LinodeObjectStorageBucketList(in, out, s) +} + +func autoConvert_v1alpha2_LinodeObjectStorageBucketList_To_v1alpha1_LinodeObjectStorageBucketList(in *v1alpha2.LinodeObjectStorageBucketList, out *LinodeObjectStorageBucketList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LinodeObjectStorageBucket, len(*in)) + for i := range *in { + if err := Convert_v1alpha2_LinodeObjectStorageBucket_To_v1alpha1_LinodeObjectStorageBucket(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1alpha2_LinodeObjectStorageBucketList_To_v1alpha1_LinodeObjectStorageBucketList is an autogenerated conversion function. +func Convert_v1alpha2_LinodeObjectStorageBucketList_To_v1alpha1_LinodeObjectStorageBucketList(in *v1alpha2.LinodeObjectStorageBucketList, out *LinodeObjectStorageBucketList, s conversion.Scope) error { + return autoConvert_v1alpha2_LinodeObjectStorageBucketList_To_v1alpha1_LinodeObjectStorageBucketList(in, out, s) +} + +func autoConvert_v1alpha1_LinodeObjectStorageBucketSpec_To_v1alpha2_LinodeObjectStorageBucketSpec(in *LinodeObjectStorageBucketSpec, out *v1alpha2.LinodeObjectStorageBucketSpec, s conversion.Scope) error { + // WARNING: in.Cluster requires manual conversion: does not exist in peer-type + out.CredentialsRef = (*v1.SecretReference)(unsafe.Pointer(in.CredentialsRef)) + out.KeyGeneration = (*int)(unsafe.Pointer(in.KeyGeneration)) + out.SecretType = in.SecretType + return nil +} + +func autoConvert_v1alpha2_LinodeObjectStorageBucketSpec_To_v1alpha1_LinodeObjectStorageBucketSpec(in *v1alpha2.LinodeObjectStorageBucketSpec, out *LinodeObjectStorageBucketSpec, s conversion.Scope) error { + // WARNING: in.Region requires manual conversion: does not exist in peer-type + out.CredentialsRef = (*v1.SecretReference)(unsafe.Pointer(in.CredentialsRef)) + out.KeyGeneration = (*int)(unsafe.Pointer(in.KeyGeneration)) + out.SecretType = in.SecretType + return nil +} + +func autoConvert_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus(in *LinodeObjectStorageBucketStatus, out *v1alpha2.LinodeObjectStorageBucketStatus, s conversion.Scope) error { + out.Ready = in.Ready + out.FailureMessage = (*string)(unsafe.Pointer(in.FailureMessage)) + out.Conditions = *(*v1beta1.Conditions)(unsafe.Pointer(&in.Conditions)) + out.Hostname = (*string)(unsafe.Pointer(in.Hostname)) + out.CreationTime = (*metav1.Time)(unsafe.Pointer(in.CreationTime)) + out.LastKeyGeneration = (*int)(unsafe.Pointer(in.LastKeyGeneration)) + out.KeySecretName = (*string)(unsafe.Pointer(in.KeySecretName)) + out.AccessKeyRefs = *(*[]int)(unsafe.Pointer(&in.AccessKeyRefs)) + return nil +} + +// Convert_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus is an autogenerated conversion function. +func Convert_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus(in *LinodeObjectStorageBucketStatus, out *v1alpha2.LinodeObjectStorageBucketStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus(in, out, s) +} + +func autoConvert_v1alpha2_LinodeObjectStorageBucketStatus_To_v1alpha1_LinodeObjectStorageBucketStatus(in *v1alpha2.LinodeObjectStorageBucketStatus, out *LinodeObjectStorageBucketStatus, s conversion.Scope) error { + out.Ready = in.Ready + out.FailureMessage = (*string)(unsafe.Pointer(in.FailureMessage)) + out.Conditions = *(*v1beta1.Conditions)(unsafe.Pointer(&in.Conditions)) + out.Hostname = (*string)(unsafe.Pointer(in.Hostname)) + out.CreationTime = (*metav1.Time)(unsafe.Pointer(in.CreationTime)) + out.LastKeyGeneration = (*int)(unsafe.Pointer(in.LastKeyGeneration)) + out.KeySecretName = (*string)(unsafe.Pointer(in.KeySecretName)) + out.AccessKeyRefs = *(*[]int)(unsafe.Pointer(&in.AccessKeyRefs)) + return nil +} + +// Convert_v1alpha2_LinodeObjectStorageBucketStatus_To_v1alpha1_LinodeObjectStorageBucketStatus is an autogenerated conversion function. +func Convert_v1alpha2_LinodeObjectStorageBucketStatus_To_v1alpha1_LinodeObjectStorageBucketStatus(in *v1alpha2.LinodeObjectStorageBucketStatus, out *LinodeObjectStorageBucketStatus, s conversion.Scope) error { + return autoConvert_v1alpha2_LinodeObjectStorageBucketStatus_To_v1alpha1_LinodeObjectStorageBucketStatus(in, out, s) +} + func autoConvert_v1alpha1_LinodeVPC_To_v1alpha2_LinodeVPC(in *LinodeVPC, out *v1alpha2.LinodeVPC, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha1_LinodeVPCSpec_To_v1alpha2_LinodeVPCSpec(&in.Spec, &out.Spec, s); err != nil { diff --git a/api/v1alpha2/linodemachinetemplate_types.go b/api/v1alpha2/linodemachinetemplate_types.go index a183c0d35..95f27b64c 100644 --- a/api/v1alpha2/linodemachinetemplate_types.go +++ b/api/v1alpha2/linodemachinetemplate_types.go @@ -33,6 +33,7 @@ type LinodeMachineTemplateResource struct { // +kubebuilder:object:root=true // +kubebuilder:storageversion // +kubebuilder:resource:path=linodemachinetemplates,scope=Namespaced,categories=cluster-api,shortName=lmt +// +kubebuilder:metadata:labels="clusterctl.cluster.x-k8s.io/move-hierarchy=true" // LinodeMachineTemplate is the Schema for the linodemachinetemplates API type LinodeMachineTemplate struct { diff --git a/api/v1alpha2/linodeobjectstoragebucket_conversion.go b/api/v1alpha2/linodeobjectstoragebucket_conversion.go new file mode 100644 index 000000000..a911f388d --- /dev/null +++ b/api/v1alpha2/linodeobjectstoragebucket_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 LinodeObjectStorageBucketS as a conversion hub. +func (*LinodeObjectStorageBucket) Hub() {} diff --git a/api/v1alpha2/linodeobjectstoragebucket_types.go b/api/v1alpha2/linodeobjectstoragebucket_types.go new file mode 100644 index 000000000..91e59957f --- /dev/null +++ b/api/v1alpha2/linodeobjectstoragebucket_types.go @@ -0,0 +1,134 @@ +/* +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" +) + +const ( + // ObjectStorageBucketFinalizer allows ReconcileLinodeObjectStorageBucket to clean up Linode resources associated + // with LinodeObjectStorageBucket before removing it from the apiserver. + ObjectStorageBucketFinalizer = "linodeobjectstoragebucket.infrastructure.cluster.x-k8s.io" +) + +// LinodeObjectStorageBucketSpec defines the desired state of LinodeObjectStorageBucket +type LinodeObjectStorageBucketSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Region is the ID of the Object Storage region for the bucket. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + Region string `json:"region"` + + // CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning the bucket. + // If not supplied then the credentials of the controller will be used. + // +optional + CredentialsRef *corev1.SecretReference `json:"credentialsRef"` + + // KeyGeneration may be modified to trigger rotations of access keys created for the bucket. + // +optional + // +kubebuilder:default=0 + KeyGeneration *int `json:"keyGeneration,omitempty"` + + // SecretType sets the type for the bucket-details secret that will be generated by the controller. + // +optional + // +kubebuilder:default=addons.cluster.x-k8s.io/resource-set + SecretType string `json:"secretType,omitempty"` +} + +// LinodeObjectStorageBucketStatus defines the observed state of LinodeObjectStorageBucket +type LinodeObjectStorageBucketStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Ready denotes that the bucket has been provisioned along with access keys. + // +optional + // +kubebuilder:default=false + Ready bool `json:"ready"` + + // FailureMessage will be set in the event that there is a terminal problem + // reconciling the Object Storage Bucket and will contain a verbose string + // suitable for logging and human consumption. + // +optional + FailureMessage *string `json:"failureMessage,omitempty"` + + // Conditions specify the service state of the LinodeObjectStorageBucket. + // +optional + Conditions clusterv1.Conditions `json:"conditions,omitempty"` + + // Hostname is the address assigned to the bucket. + // +optional + Hostname *string `json:"hostname,omitempty"` + + // CreationTime specifies the creation timestamp for the bucket. + // +optional + CreationTime *metav1.Time `json:"creationTime,omitempty"` + + // LastKeyGeneration tracks the last known value of .spec.keyGeneration. + // +optional + LastKeyGeneration *int `json:"lastKeyGeneration,omitempty"` + + // KeySecretName specifies the name of the Secret containing access keys for the bucket. + // +optional + KeySecretName *string `json:"keySecretName,omitempty"` + + // AccessKeyRefs stores IDs for Object Storage keys provisioned along with the bucket. + // +optional + AccessKeyRefs []int `json:"accessKeyRefs,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:resource:path=linodeobjectstoragebuckets,scope=Namespaced,categories=cluster-api,shortName=lobj +// +kubebuilder:subresource:status +// +kubebuilder:metadata:labels="clusterctl.cluster.x-k8s.io/move-hierarchy=true" +// +kubebuilder:printcolumn:name="Label",type="string",JSONPath=".spec.label",description="The name of the bucket" +// +kubebuilder:printcolumn:name="Region",type="string",JSONPath=".spec.region",description="The ID of the Object Storage region for the bucket" +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="Bucket and keys have been provisioned" + +// LinodeObjectStorageBucket is the Schema for the linodeobjectstoragebuckets API +type LinodeObjectStorageBucket struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec LinodeObjectStorageBucketSpec `json:"spec,omitempty"` + Status LinodeObjectStorageBucketStatus `json:"status,omitempty"` +} + +func (b *LinodeObjectStorageBucket) GetConditions() clusterv1.Conditions { + return b.Status.Conditions +} + +func (b *LinodeObjectStorageBucket) SetConditions(conditions clusterv1.Conditions) { + b.Status.Conditions = conditions +} + +// +kubebuilder:object:root=true + +// LinodeObjectStorageBucketList contains a list of LinodeObjectStorageBucket +type LinodeObjectStorageBucketList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []LinodeObjectStorageBucket `json:"items"` +} + +func init() { + SchemeBuilder.Register(&LinodeObjectStorageBucket{}, &LinodeObjectStorageBucketList{}) +} diff --git a/api/v1alpha2/linodeobjectstoragebucket_webhook.go b/api/v1alpha2/linodeobjectstoragebucket_webhook.go new file mode 100644 index 000000000..5305dc548 --- /dev/null +++ b/api/v1alpha2/linodeobjectstoragebucket_webhook.go @@ -0,0 +1,112 @@ +/* +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" +) + +var ( + // The capability string indicating a region supports Object Storage: [Object Storage Availability] + // + // [Object Storage Availability]: https://www.linode.com/docs/products/storage/object-storage/#availability + LinodeObjectStorageCapability = "Object Storage" +) + +// log is for logging in this package. +var linodeobjectstoragebucketlog = logf.Log.WithName("linodeobjectstoragebucket-resource") + +// SetupWebhookWithManager will setup the manager to manage the webhooks +func (r *LinodeObjectStorageBucket) 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 update and deletion validation. +//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1alpha2-linodeobjectstoragebucket,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=linodeobjectstoragebuckets,verbs=create,versions=v1alpha2,name=validation.linodeobjectstoragebucket.infrastructure.cluster.x-k8s.io,admissionReviewVersions=v1;v1alpha1;v1alpha2 + +var _ webhook.Validator = &LinodeObjectStorageBucket{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *LinodeObjectStorageBucket) ValidateCreate() (admission.Warnings, error) { + linodeobjectstoragebucketlog.Info("validate create", "name", r.Name) + + ctx, cancel := context.WithTimeout(context.Background(), defaultWebhookTimeout) + defer cancel() + + return nil, r.validateLinodeObjectStorageBucket(ctx, &defaultLinodeClient) +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *LinodeObjectStorageBucket) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + linodeobjectstoragebucketlog.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 *LinodeObjectStorageBucket) ValidateDelete() (admission.Warnings, error) { + linodeobjectstoragebucketlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} + +func (r *LinodeObjectStorageBucket) validateLinodeObjectStorageBucket(ctx context.Context, client LinodeClient) error { + // TODO: instrument with tracing, might need refactor to preserve readibility + var errs field.ErrorList + + if err := r.validateLinodeObjectStorageBucketSpec(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: "LinodeObjectStorageBucket"}, + r.Name, errs) +} + +func (r *LinodeObjectStorageBucket) validateLinodeObjectStorageBucketSpec(ctx context.Context, client LinodeClient) field.ErrorList { + // TODO: instrument with tracing, might need refactor to preserve readibility + // Handle this + var errs field.ErrorList + + if err := validateObjectStorageRegion(ctx, client, r.Spec.Region, field.NewPath("spec").Child("cluster")); err != nil { + errs = append(errs, err) + } + + if len(errs) == 0 { + return nil + } + return errs +} diff --git a/api/v1alpha2/linodeobjectstoragebucket_webhook_test.go b/api/v1alpha2/linodeobjectstoragebucket_webhook_test.go new file mode 100644 index 000000000..dc47ddd79 --- /dev/null +++ b/api/v1alpha2/linodeobjectstoragebucket_webhook_test.go @@ -0,0 +1,90 @@ +/* +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" + "testing" + + "github.com/linode/linodego" + "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 TestValidateLinodeObjectStorageBucket(t *testing.T) { + t.Parallel() + + var ( + bucket = LinodeObjectStorageBucket{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "example", + }, + Spec: LinodeObjectStorageBucketSpec{ + Region: "example", + }, + } + region = linodego.Region{ID: "test"} + capabilities = []string{LinodeObjectStorageCapability} + capabilities_zero = []string{} + ) + + NewSuite(t, mock.MockLinodeClient{}).Run( + OneOf( + Path( + Call("valid", func(ctx context.Context, mck Mock) { + region := region + region.Capabilities = slices.Clone(capabilities) + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(®ion, nil).AnyTimes() + }), + Result("success", func(ctx context.Context, mck Mock) { + assert.NoError(t, bucket.validateLinodeObjectStorageBucket(ctx, mck.LinodeClient)) + }), + ), + ), + OneOf( + Path( + Call("invalid cluster format", func(ctx context.Context, mck Mock) { + region := region + region.Capabilities = slices.Clone(capabilities) + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(®ion, nil).AnyTimes() + }), + Result("error", func(ctx context.Context, mck Mock) { + bucket := bucket + bucket.Spec.Region = "123invalid" + assert.Error(t, bucket.validateLinodeObjectStorageBucket(ctx, mck.LinodeClient)) + }), + ), + Path( + Call("region not supported", func(ctx context.Context, mck Mock) { + region := region + region.Capabilities = slices.Clone(capabilities_zero) + mck.LinodeClient.EXPECT().GetRegion(gomock.Any(), gomock.Any()).Return(®ion, nil).AnyTimes() + }), + Result("error", func(ctx context.Context, mck Mock) { + assert.Error(t, bucket.validateLinodeObjectStorageBucket(ctx, mck.LinodeClient)) + }), + ), + ), + ) +} diff --git a/api/v1alpha2/webhook_helpers.go b/api/v1alpha2/webhook_helpers.go index c07957e5d..ffb1f431d 100644 --- a/api/v1alpha2/webhook_helpers.go +++ b/api/v1alpha2/webhook_helpers.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/http" + "regexp" "slices" "time" @@ -71,3 +72,26 @@ func validateLinodeType(ctx context.Context, client LinodeClient, id string, pat return plan, nil } + +// validateObjectStorageRegion validates an Object Storage deployment's region ID via the following rules: +// - The Region ID is in the form: REGION_ID. +// - The region has Object Storage support. +// +// NOTE: This implementation intended to bypass the authentication requirement for the [Clusters List] and [Cluster +// View] endpoints in the Linode API, thereby reusing a [github.com/linode/linodego.Client] (and its caching if enabled) +// across many admission requests. +// +// [Clusters List]: https://www.linode.com/docs/api/object-storage/#clusters-list +// [Cluster View]: https://www.linode.com/docs/api/object-storage/#cluster-view +func validateObjectStorageRegion(ctx context.Context, client LinodeClient, id string, path *field.Path) *field.Error { + // TODO: instrument with tracing, might need refactor to preserve readibility + + cexp := regexp.MustCompile("^(([[:lower:]]+-)*[[:lower:]]+)$") + cexp1 := regexp.MustCompile(`^(([[:lower:]]+-)*[[:lower:]]+)-\d+$`) + if !cexp.MatchString(id) && !cexp1.MatchString(id) { + return field.Invalid(path, id, "must be in form: region_id or region_id-ordinal") + } + + region := cexp.FindStringSubmatch(id)[1] + return validateRegion(ctx, client, region, path, LinodeObjectStorageCapability) +} diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 12b9f1045..fa9e821bc 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -611,6 +611,141 @@ func (in *LinodeNBPortConfig) DeepCopy() *LinodeNBPortConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinodeObjectStorageBucket) DeepCopyInto(out *LinodeObjectStorageBucket) { + *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 LinodeObjectStorageBucket. +func (in *LinodeObjectStorageBucket) DeepCopy() *LinodeObjectStorageBucket { + if in == nil { + return nil + } + out := new(LinodeObjectStorageBucket) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LinodeObjectStorageBucket) 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 *LinodeObjectStorageBucketList) DeepCopyInto(out *LinodeObjectStorageBucketList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LinodeObjectStorageBucket, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeObjectStorageBucketList. +func (in *LinodeObjectStorageBucketList) DeepCopy() *LinodeObjectStorageBucketList { + if in == nil { + return nil + } + out := new(LinodeObjectStorageBucketList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LinodeObjectStorageBucketList) 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 *LinodeObjectStorageBucketSpec) DeepCopyInto(out *LinodeObjectStorageBucketSpec) { + *out = *in + if in.CredentialsRef != nil { + in, out := &in.CredentialsRef, &out.CredentialsRef + *out = new(v1.SecretReference) + **out = **in + } + if in.KeyGeneration != nil { + in, out := &in.KeyGeneration, &out.KeyGeneration + *out = new(int) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeObjectStorageBucketSpec. +func (in *LinodeObjectStorageBucketSpec) DeepCopy() *LinodeObjectStorageBucketSpec { + if in == nil { + return nil + } + out := new(LinodeObjectStorageBucketSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinodeObjectStorageBucketStatus) DeepCopyInto(out *LinodeObjectStorageBucketStatus) { + *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]) + } + } + if in.Hostname != nil { + in, out := &in.Hostname, &out.Hostname + *out = new(string) + **out = **in + } + if in.CreationTime != nil { + in, out := &in.CreationTime, &out.CreationTime + *out = (*in).DeepCopy() + } + if in.LastKeyGeneration != nil { + in, out := &in.LastKeyGeneration, &out.LastKeyGeneration + *out = new(int) + **out = **in + } + if in.KeySecretName != nil { + in, out := &in.KeySecretName, &out.KeySecretName + *out = new(string) + **out = **in + } + if in.AccessKeyRefs != nil { + in, out := &in.AccessKeyRefs, &out.AccessKeyRefs + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeObjectStorageBucketStatus. +func (in *LinodeObjectStorageBucketStatus) DeepCopy() *LinodeObjectStorageBucketStatus { + if in == nil { + return nil + } + out := new(LinodeObjectStorageBucketStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LinodePlacementGroup) DeepCopyInto(out *LinodePlacementGroup) { *out = *in diff --git a/cloud/scope/object_storage_bucket.go b/cloud/scope/object_storage_bucket.go index d915e77fb..3ace48183 100644 --- a/cloud/scope/object_storage_bucket.go +++ b/cloud/scope/object_storage_bucket.go @@ -15,7 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "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" ) @@ -36,13 +36,13 @@ stringData: type ObjectStorageBucketScopeParams struct { Client K8sClient - Bucket *infrav1alpha1.LinodeObjectStorageBucket + Bucket *infrav1alpha2.LinodeObjectStorageBucket Logger *logr.Logger } type ObjectStorageBucketScope struct { Client K8sClient - Bucket *infrav1alpha1.LinodeObjectStorageBucket + Bucket *infrav1alpha2.LinodeObjectStorageBucket Logger logr.Logger LinodeClient LinodeClient PatchHelper *patch.Helper @@ -111,7 +111,7 @@ func (s *ObjectStorageBucketScope) Close(ctx context.Context) error { // AddFinalizer adds a finalizer if not present and immediately patches the // object to avoid any race conditions. func (s *ObjectStorageBucketScope) AddFinalizer(ctx context.Context) error { - if controllerutil.AddFinalizer(s.Bucket, infrav1alpha1.ObjectStorageBucketFinalizer) { + if controllerutil.AddFinalizer(s.Bucket, infrav1alpha2.ObjectStorageBucketFinalizer) { return s.Close(ctx) } diff --git a/cloud/scope/object_storage_bucket_test.go b/cloud/scope/object_storage_bucket_test.go index ace73ec84..e2ecb4e46 100644 --- a/cloud/scope/object_storage_bucket_test.go +++ b/cloud/scope/object_storage_bucket_test.go @@ -21,7 +21,7 @@ import ( "sigs.k8s.io/cluster-api/util/patch" "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/clients" @@ -38,7 +38,7 @@ func TestValidateObjectStorageBucketScopeParams(t *testing.T) { { name: "Success - Valid ObjectStorageBucketScopeParams", params: ObjectStorageBucketScopeParams{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, Logger: &logr.Logger{}, }, expectedErr: nil, @@ -46,7 +46,7 @@ func TestValidateObjectStorageBucketScopeParams(t *testing.T) { { name: "Failure - Invalid ObjectStorageBucketScopeParams. Logger is nil", params: ObjectStorageBucketScopeParams{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, Logger: nil, }, expectedErr: fmt.Errorf("logger is required when creating an ObjectStorageBucketScope"), @@ -93,7 +93,7 @@ func TestNewObjectStorageBucketScope(t *testing.T) { apiKey: "apikey", params: ObjectStorageBucketScopeParams{ Client: nil, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, Logger: &logr.Logger{}, }, }, @@ -101,7 +101,7 @@ func TestNewObjectStorageBucketScope(t *testing.T) { expects: func(k8s *mock.MockK8sClient) { k8s.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) }, @@ -112,8 +112,8 @@ func TestNewObjectStorageBucketScope(t *testing.T) { apiKey: "apikey", params: ObjectStorageBucketScopeParams{ Client: nil, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -127,7 +127,7 @@ func TestNewObjectStorageBucketScope(t *testing.T) { expects: func(k8s *mock.MockK8sClient) { k8s.EXPECT().Scheme().DoAndReturn(func() *runtime.Scheme { s := runtime.NewScheme() - infrav1alpha1.AddToScheme(s) + infrav1alpha2.AddToScheme(s) return s }) k8s.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, name types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error { @@ -156,7 +156,7 @@ func TestNewObjectStorageBucketScope(t *testing.T) { apiKey: "apikey", params: ObjectStorageBucketScopeParams{ Client: nil, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, Logger: &logr.Logger{}, }, }, @@ -171,8 +171,8 @@ func TestNewObjectStorageBucketScope(t *testing.T) { apiKey: "test-key", params: ObjectStorageBucketScopeParams{ Client: nil, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ CredentialsRef: &corev1.SecretReference{ Name: "example", Namespace: "test", @@ -193,7 +193,7 @@ func TestNewObjectStorageBucketScope(t *testing.T) { apiKey: "", params: ObjectStorageBucketScopeParams{ Client: nil, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, Logger: &logr.Logger{}, }, }, @@ -230,16 +230,16 @@ func TestObjectStorageBucketScopeMethods(t *testing.T) { t.Parallel() tests := []struct { name string - Bucket *infrav1alpha1.LinodeObjectStorageBucket + Bucket *infrav1alpha2.LinodeObjectStorageBucket expects func(mock *mock.MockK8sClient) }{ { name: "Success - finalizer should be added to the Linode Object Storage Bucket object", - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, 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) @@ -247,15 +247,15 @@ func TestObjectStorageBucketScopeMethods(t *testing.T) { }, { name: "Failure - finalizer should not be added to the Bucket object. Function returns nil since it was already present", - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ - Finalizers: []string{infrav1alpha1.ObjectStorageBucketFinalizer}, + Finalizers: []string{infrav1alpha2.ObjectStorageBucketFinalizer}, }, }, 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) }, @@ -289,7 +289,7 @@ func TestObjectStorageBucketScopeMethods(t *testing.T) { t.Errorf("ClusterScope.AddFinalizer() error = %v", err) } - if objScope.Bucket.Finalizers[0] != infrav1alpha1.ObjectStorageBucketFinalizer { + if objScope.Bucket.Finalizers[0] != infrav1alpha2.ObjectStorageBucketFinalizer { t.Errorf("Finalizer was not added") } }) @@ -300,19 +300,19 @@ func TestGenerateKeySecret(t *testing.T) { t.Parallel() tests := []struct { name string - Bucket *infrav1alpha1.LinodeObjectStorageBucket + Bucket *infrav1alpha2.LinodeObjectStorageBucket keys [NumAccessKeys]*linodego.ObjectStorageKey expectedErr error expects func(mock *mock.MockK8sClient) }{ { name: "happy path", - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", Namespace: "test-namespace", }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ KeySecretName: ptr.To("test-bucket-bucket-details"), }, }, @@ -349,7 +349,7 @@ func TestGenerateKeySecret(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(1) }, @@ -357,12 +357,12 @@ func TestGenerateKeySecret(t *testing.T) { }, { name: "missing one or more keys", - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", Namespace: "test-namespace", }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ KeySecretName: ptr.To("test-bucket-bucket-details"), }, }, @@ -385,8 +385,8 @@ func TestGenerateKeySecret(t *testing.T) { expectedErr: errors.New("expected two non-nil object storage keys"), }, { - name: "client scheme does not have infrav1alpha1", - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + name: "client scheme does not have infrav1alpha2", + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", Namespace: "test-namespace", @@ -464,16 +464,16 @@ func TestShouldInitKeys(t *testing.T) { tests := []struct { name string want bool - Bucket *infrav1alpha1.LinodeObjectStorageBucket + Bucket *infrav1alpha2.LinodeObjectStorageBucket }{ { name: "should init keys", want: true, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ KeyGeneration: ptr.To(1), }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: nil, }, }, @@ -506,16 +506,16 @@ func TestShouldRotateKeys(t *testing.T) { tests := []struct { name string want bool - Bucket *infrav1alpha1.LinodeObjectStorageBucket + Bucket *infrav1alpha2.LinodeObjectStorageBucket }{ { name: "should rotate keys", want: true, - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ KeyGeneration: ptr.To(1), }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: ptr.To(0), }, }, @@ -547,15 +547,15 @@ func TestShouldRestoreKeySecret(t *testing.T) { t.Parallel() tests := []struct { name string - bucket *infrav1alpha1.LinodeObjectStorageBucket + bucket *infrav1alpha2.LinodeObjectStorageBucket expects func(k8s *mock.MockK8sClient) want bool expectedErr error }{ { name: "status has no secret name", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ KeySecretName: nil, }, }, @@ -563,12 +563,12 @@ func TestShouldRestoreKeySecret(t *testing.T) { }, { name: "status has secret name and secret exists", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", Namespace: "ns", }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ KeySecretName: ptr.To("secret"), }, }, @@ -581,12 +581,12 @@ func TestShouldRestoreKeySecret(t *testing.T) { }, { name: "status has secret name and secret is missing", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", Namespace: "ns", }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ KeySecretName: ptr.To("secret"), }, }, @@ -599,12 +599,12 @@ func TestShouldRestoreKeySecret(t *testing.T) { }, { name: "non-404 api error", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", Namespace: "ns", }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ KeySecretName: ptr.To("secret"), }, }, diff --git a/cloud/services/object_storage_buckets.go b/cloud/services/object_storage_buckets.go index 38cf8ccd9..cb118ef82 100644 --- a/cloud/services/object_storage_buckets.go +++ b/cloud/services/object_storage_buckets.go @@ -16,11 +16,11 @@ import ( func EnsureObjectStorageBucket(ctx context.Context, bScope *scope.ObjectStorageBucketScope) (*linodego.ObjectStorageBucket, error) { bucket, err := bScope.LinodeClient.GetObjectStorageBucket( ctx, - bScope.Bucket.Spec.Cluster, + bScope.Bucket.Spec.Region, bScope.Bucket.Name, ) if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil { - return nil, fmt.Errorf("failed to get bucket from cluster %s: %w", bScope.Bucket.Spec.Cluster, err) + return nil, fmt.Errorf("failed to get bucket from region %s: %w", bScope.Bucket.Spec.Region, err) } if bucket != nil { bScope.Logger.Info("Bucket exists") @@ -29,7 +29,7 @@ func EnsureObjectStorageBucket(ctx context.Context, bScope *scope.ObjectStorageB } opts := linodego.ObjectStorageBucketCreateOptions{ - Region: bScope.Bucket.Spec.Cluster, + Region: bScope.Bucket.Spec.Region, Label: bScope.Bucket.Name, ACL: linodego.ACLPrivate, } @@ -78,7 +78,7 @@ func createObjectStorageKey(ctx context.Context, bScope *scope.ObjectStorageBuck BucketAccess: &[]linodego.ObjectStorageKeyBucketAccess{ { BucketName: bScope.Bucket.Name, - Region: bScope.Bucket.Spec.Cluster, + Region: bScope.Bucket.Spec.Region, Permissions: permission, }, }, diff --git a/cloud/services/object_storage_buckets_test.go b/cloud/services/object_storage_buckets_test.go index 7e09e726a..554c50a86 100644 --- a/cloud/services/object_storage_buckets_test.go +++ b/cloud/services/object_storage_buckets_test.go @@ -15,7 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" - 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" ) @@ -33,12 +33,12 @@ func TestEnsureObjectStorageBucket(t *testing.T) { { name: "Success - Successfully get the OBJ bucket", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ - Cluster: "test-cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "test-region", }, }, }, @@ -54,29 +54,29 @@ func TestEnsureObjectStorageBucket(t *testing.T) { { name: "Error - Unable to get the OBJ bucket", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ - Cluster: "test-cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "test-region", }, }, }, expects: func(c *mock.MockLinodeClient) { c.EXPECT().GetObjectStorageBucket(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error in getting object storage bucket")) }, - expectedError: fmt.Errorf("failed to get bucket from cluster"), + expectedError: fmt.Errorf("failed to get bucket from region"), }, { name: "Success - Successfully create the OBJ bucket", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ - Cluster: "test-cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "test-region", }, }, }, @@ -93,12 +93,12 @@ func TestEnsureObjectStorageBucket(t *testing.T) { { name: "Error - unable to create the OBJ bucket", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ - Cluster: "test-cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "test-region", }, }, }, @@ -146,15 +146,15 @@ func TestRotateObjectStorageKeysCreation(t *testing.T) { { name: "Creates new access keys", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ - Cluster: "test-cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "test-region", KeyGeneration: ptr.To(1), }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: ptr.To(0), AccessKeyRefs: []int{ 11, @@ -188,12 +188,12 @@ func TestRotateObjectStorageKeysCreation(t *testing.T) { { name: "Error creating keys", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "test-bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ - Cluster: "test-cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "test-region", }, }, }, @@ -233,19 +233,19 @@ func TestRotateObjectStorageKeysRevocation(t *testing.T) { tests := []struct { name string - bucket *infrav1alpha1.LinodeObjectStorageBucket + bucket *infrav1alpha2.LinodeObjectStorageBucket expects func(*mock.MockLinodeClient) }{ { name: "should revoke existing keys", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ KeyGeneration: ptr.To(1), }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: ptr.To(0), AccessKeyRefs: []int{0, 1}, }, @@ -261,39 +261,39 @@ func TestRotateObjectStorageKeysRevocation(t *testing.T) { }, { name: "shouldInitKeys", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: nil, }, }, }, { name: "not shouldRotateKeys", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ KeyGeneration: ptr.To(1), }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: ptr.To(1), }, }, }, { name: "unable to revoke keys", - bucket: &infrav1alpha1.LinodeObjectStorageBucket{ + bucket: &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "bucket", }, - Spec: infrav1alpha1.LinodeObjectStorageBucketSpec{ + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ KeyGeneration: ptr.To(1), }, - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ LastKeyGeneration: ptr.To(0), AccessKeyRefs: []int{0, 1}, }, @@ -349,8 +349,8 @@ func TestGetObjectStorageKeys(t *testing.T) { { name: "happy path", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ AccessKeyRefs: []int{0, 1}, }, }, @@ -371,15 +371,15 @@ func TestGetObjectStorageKeys(t *testing.T) { { name: "not two key refs in status", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{}, + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{}, }, wantErr: "expected two object storage key IDs in .status.accessKeyRefs", }, { name: "one client error", bScope: &scope.ObjectStorageBucketScope{ - Bucket: &infrav1alpha1.LinodeObjectStorageBucket{ - Status: infrav1alpha1.LinodeObjectStorageBucketStatus{ + Bucket: &infrav1alpha2.LinodeObjectStorageBucket{ + Status: infrav1alpha2.LinodeObjectStorageBucketStatus{ AccessKeyRefs: []int{0, 1}, }, }, diff --git a/cmd/main.go b/cmd/main.go index 55efa284d..5828ed28d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -280,6 +280,10 @@ func setupWebhooks(mgr manager.Manager) { setupLog.Error(err, "unable to create webhook", "webhook", "LinodeVPC") os.Exit(1) } + if err = (&infrastructurev1alpha2.LinodeObjectStorageBucket{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LinodeObjectStorageBucket") + 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_linodemachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml index 525a03fea..964a0208d 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml @@ -4,6 +4,8 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.14.0 + labels: + clusterctl.cluster.x-k8s.io/move-hierarchy: "true" name: linodemachinetemplates.infrastructure.cluster.x-k8s.io spec: group: infrastructure.cluster.x-k8s.io diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeobjectstoragebuckets.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeobjectstoragebuckets.yaml index 1b2989c1a..475cc4cee 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeobjectstoragebuckets.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeobjectstoragebuckets.yaml @@ -179,6 +179,168 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The name of the bucket + jsonPath: .spec.label + name: Label + type: string + - description: The ID of the Object Storage region for the bucket + jsonPath: .spec.region + name: Region + type: string + - description: Bucket and keys have been provisioned + jsonPath: .status.ready + name: Ready + type: string + name: v1alpha2 + schema: + openAPIV3Schema: + description: LinodeObjectStorageBucket is the Schema for the linodeobjectstoragebuckets + 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: LinodeObjectStorageBucketSpec defines the desired state of + LinodeObjectStorageBucket + properties: + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning the bucket. + 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 + keyGeneration: + default: 0 + description: KeyGeneration may be modified to trigger rotations of + access keys created for the bucket. + type: integer + region: + description: Region is the ID of the Object Storage region for the + bucket. + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + secretType: + default: addons.cluster.x-k8s.io/resource-set + description: SecretType sets the type for the bucket-details secret + that will be generated by the controller. + type: string + required: + - region + type: object + status: + description: LinodeObjectStorageBucketStatus defines the observed state + of LinodeObjectStorageBucket + properties: + accessKeyRefs: + description: AccessKeyRefs stores IDs for Object Storage keys provisioned + along with the bucket. + items: + type: integer + type: array + conditions: + description: Conditions specify the service state of the LinodeObjectStorageBucket. + 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 + creationTime: + description: CreationTime specifies the creation timestamp for the + bucket. + format: date-time + type: string + failureMessage: + description: |- + FailureMessage will be set in the event that there is a terminal problem + reconciling the Object Storage Bucket and will contain a verbose string + suitable for logging and human consumption. + type: string + hostname: + description: Hostname is the address assigned to the bucket. + type: string + keySecretName: + description: KeySecretName specifies the name of the Secret containing + access keys for the bucket. + type: string + lastKeyGeneration: + description: LastKeyGeneration tracks the last known value of .spec.keyGeneration. + type: integer + ready: + default: false + description: Ready denotes that the bucket has been provisioned along + with access keys. + 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 660d564d4..c1fd8a61e 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -32,6 +32,7 @@ patches: - path: patches/webhook_in_linodemachines.yaml - path: patches/webhook_in_linodevpcs.yaml - path: patches/webhook_in_linodevpcs.yaml +- path: patches/webhook_in_linodeobjectstoragebuckets.yaml - path: patches/webhook_in_linodeclustertemplates.yaml - path: patches/webhook_in_linodemachinetemplates.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch @@ -54,6 +55,7 @@ patches: - path: patches/cainjection_in_infrastructure_linodeclusters.yaml #- path: patches/cainjection_in_linodemachines.yaml #- path: patches/cainjection_in_linodevpcs.yaml +#- path: patches/cainjection_in_linodeobjectstoragebuckets.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # [VALIDATION] diff --git a/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml b/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml index 49426ee49..cc8d8762c 100644 --- a/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml +++ b/config/crd/patches/capicontract_in_linodeobjectstoragebuckets.yaml @@ -3,4 +3,4 @@ kind: CustomResourceDefinition metadata: name: linodeobjectstoragebuckets.infrastructure.cluster.x-k8s.io labels: - cluster.x-k8s.io/v1beta1: v1alpha1 + cluster.x-k8s.io/v1beta1: v1alpha2 diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index a65a526ee..d822a1f4c 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -20,6 +20,8 @@ resources: # default, aiding admins in cluster management. Those roles are # not used by the Project itself. You can comment the following lines # if you do not want those helpers be installed with your Project. +- linodeobjectstoragebucket_editor_role.yaml +- linodeobjectstoragebucket_viewer_role.yaml - linodemachinetemplate_editor_role.yaml - linodemachinetemplate_viewer_role.yaml - linodeclustertemplate_editor_role.yaml diff --git a/config/samples/infrastructure_v1alpha2_linodeobjectstoragebucket.yaml b/config/samples/infrastructure_v1alpha2_linodeobjectstoragebucket.yaml new file mode 100644 index 000000000..d9d4ac248 --- /dev/null +++ b/config/samples/infrastructure_v1alpha2_linodeobjectstoragebucket.yaml @@ -0,0 +1,9 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 +kind: LinodeObjectStorageBucket +metadata: + labels: + app.kubernetes.io/name: cluster-api-provider-linode + app.kubernetes.io/managed-by: kustomize + name: linodeobjectstoragebucket-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index c1575ff40..db283218e 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -9,6 +9,7 @@ resources: - infrastructure_v1alpha2_linodecluster.yaml - infrastructure_v1alpha2_linodemachine.yaml - infrastructure_v1alpha2_linodevpc.yaml +- infrastructure_v1alpha2_linodeobjectstoragebucket.yaml - infrastructure_v1alpha2_linodeclustertemplate.yaml - infrastructure_v1alpha2_linodemachinetemplate.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 028856d37..5fa6fc077 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -46,6 +46,27 @@ webhooks: resources: - linodemachines sideEffects: None +- admissionReviewVersions: + - v1 + - v1alpha1 + - v1alpha2 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1alpha2-linodeobjectstoragebucket + failurePolicy: Fail + name: validation.linodeobjectstoragebucket.infrastructure.cluster.x-k8s.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1alpha2 + operations: + - CREATE + resources: + - linodeobjectstoragebuckets + sideEffects: None - admissionReviewVersions: - v1 - v1alpha2 diff --git a/controller/linodemachine_controller.go b/controller/linodemachine_controller.go index b8e6deaa4..32eb894f3 100644 --- a/controller/linodemachine_controller.go +++ b/controller/linodemachine_controller.go @@ -56,7 +56,6 @@ import ( const ( linodeBusyCode = 400 - linodeTooManyRequests = 429 defaultDiskFilesystem = string(linodego.FilesystemExt4) // conditions for preflight instance creation @@ -263,6 +262,16 @@ func (r *LinodeMachineReconciler) reconcile( return } +func retryIfTransient(err error) (ctrl.Result, error) { + if util.IsRetryableError(err) { + if linodego.ErrHasStatus(err, http.StatusTooManyRequests) { + return ctrl.Result{RequeueAfter: reconciler.DefaultLinodeTooManyRequestsErrorRetryDelay}, nil + } + return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerRetryDelay}, nil + } + return ctrl.Result{}, err +} + func (r *LinodeMachineReconciler) reconcileCreate( ctx context.Context, logger logr.Logger, @@ -303,16 +312,15 @@ func (r *LinodeMachineReconciler) reconcileCreate( createOpts, err := r.newCreateConfig(ctx, machineScope, tags, logger) if err != nil { logger.Error(err, "Failed to create Linode machine InstanceCreateOptions") - if util.IsTransientError(err) { - return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerRetryDelay}, nil - } - - return ctrl.Result{}, err + return retryIfTransient(err) } linodeInstance, err = machineScope.LinodeClient.CreateInstance(ctx, *createOpts) if err != nil { - if linodego.ErrHasStatus(err, linodeTooManyRequests) || linodego.ErrHasStatus(err, linodego.ErrorFromError) { + if util.IsRetryableError(err) { logger.Error(err, "Failed to create Linode instance due to API error, requeing") + if linodego.ErrHasStatus(err, http.StatusTooManyRequests) { + return ctrl.Result{RequeueAfter: reconciler.DefaultLinodeTooManyRequestsErrorRetryDelay}, nil + } return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerRetryDelay}, nil } logger.Error(err, "Failed to create Linode machine instance") @@ -363,11 +371,12 @@ func (r *LinodeMachineReconciler) reconcileInstanceCreate( instanceConfig, err := r.getDefaultInstanceConfig(ctx, machineScope, linodeInstance.ID) if err != nil { logger.Error(err, "Failed to get default instance configuration") - return ctrl.Result{}, err + return retryIfTransient(err) } if _, err := machineScope.LinodeClient.UpdateInstanceConfig(ctx, linodeInstance.ID, instanceConfig.ID, linodego.InstanceConfigUpdateOptions{Kernel: machineScope.LinodeMachine.Spec.Configuration.Kernel}); err != nil { - return ctrl.Result{}, err + logger.Error(err, "Failed to update default instance configuration") + return retryIfTransient(err) } } diff --git a/controller/linodemachine_controller_test.go b/controller/linodemachine_controller_test.go index 872544ef0..538be73cd 100644 --- a/controller/linodemachine_controller_test.go +++ b/controller/linodemachine_controller_test.go @@ -21,6 +21,7 @@ import ( "context" "errors" "net" + "net/http" "time" "github.com/go-logr/logr" @@ -853,9 +854,10 @@ var _ = Describe("machine-lifecycle", Ordered, Label("machine", "machine-lifecyc linodeMachine := &infrav1alpha2.LinodeMachine{ ObjectMeta: metadata, Spec: infrav1alpha2.LinodeMachineSpec{ - InstanceID: ptr.To(0), - Type: "g6-nanode-1", - Image: rutil.DefaultMachineControllerLinodeImage, + InstanceID: ptr.To(0), + Type: "g6-nanode-1", + Image: rutil.DefaultMachineControllerLinodeImage, + Configuration: &infrav1alpha2.InstanceConfiguration{Kernel: "test"}, }, } machineKey := client.ObjectKeyFromObject(linodeMachine) @@ -975,8 +977,113 @@ var _ = Describe("machine-lifecycle", Ordered, Label("machine", "machine-lifecyc })), ), ), + Path( + Call("machine is not created because there were too many requests", func(ctx context.Context, mck Mock) { + listInst := mck.LinodeClient.EXPECT(). + ListInstances(ctx, gomock.Any()). + Return([]linodego.Instance{}, nil) + mck.LinodeClient.EXPECT(). + GetRegion(ctx, gomock.Any()). + After(listInst). + Return(&linodego.Region{Capabilities: []string{"Metadata"}}, nil) + }), + OneOf( + Path(Result("create requeues when failing to create instance config", func(ctx context.Context, mck Mock) { + mck.LinodeClient.EXPECT(). + GetImage(ctx, gomock.Any()). + Return(nil, &linodego.Error{Code: http.StatusTooManyRequests}) + res, err := reconciler.reconcile(ctx, mck.Logger(), mScope) + Expect(err).NotTo(HaveOccurred()) + Expect(res.RequeueAfter).To(Equal(rutil.DefaultLinodeTooManyRequestsErrorRetryDelay)) + Expect(mck.Logs()).To(ContainSubstring("Failed to create Linode machine InstanceCreateOptions")) + })), + Path(Result("create requeues when failing to create instance", func(ctx context.Context, mck Mock) { + getImage := mck.LinodeClient.EXPECT(). + GetImage(ctx, gomock.Any()). + Return(&linodego.Image{Capabilities: []string{"cloud-init"}}, nil) + mck.LinodeClient.EXPECT().CreateInstance(gomock.Any(), gomock.Any()). + After(getImage). + Return(nil, &linodego.Error{Code: http.StatusTooManyRequests}) + res, err := reconciler.reconcile(ctx, mck.Logger(), mScope) + Expect(err).NotTo(HaveOccurred()) + Expect(res.RequeueAfter).To(Equal(rutil.DefaultLinodeTooManyRequestsErrorRetryDelay)) + Expect(mck.Logs()).To(ContainSubstring("Failed to create Linode instance due to API error")) + })), + Path(Result("create requeues when failing to update instance config", func(ctx context.Context, mck Mock) { + getImage := mck.LinodeClient.EXPECT(). + GetImage(ctx, gomock.Any()). + Return(&linodego.Image{Capabilities: []string{"cloud-init"}}, nil) + createInst := mck.LinodeClient.EXPECT(). + CreateInstance(ctx, gomock.Any()). + After(getImage). + Return(&linodego.Instance{ + ID: 123, + IPv4: []*net.IP{ptr.To(net.IPv4(192, 168, 0, 2))}, + IPv6: "fd00::", + Status: linodego.InstanceOffline, + }, nil) + listInstConfigs := mck.LinodeClient.EXPECT(). + ListInstanceConfigs(ctx, 123, gomock.Any()). + After(createInst). + Return([]linodego.InstanceConfig{{ + Devices: &linodego.InstanceConfigDeviceMap{ + SDA: &linodego.InstanceConfigDevice{DiskID: 100}, + }, + }}, nil) + mck.LinodeClient.EXPECT(). + UpdateInstanceConfig(ctx, 123, 0, gomock.Any()). + After(listInstConfigs). + Return(nil, &linodego.Error{Code: http.StatusTooManyRequests}) + res, err := reconciler.reconcile(ctx, mck.Logger(), mScope) + Expect(err).NotTo(HaveOccurred()) + Expect(res.RequeueAfter).To(Equal(rutil.DefaultLinodeTooManyRequestsErrorRetryDelay)) + Expect(mck.Logs()).To(ContainSubstring("Failed to update default instance configuration")) + })), + Path(Result("create requeues when failing to get instance config", func(ctx context.Context, mck Mock) { + getImage := mck.LinodeClient.EXPECT(). + GetImage(ctx, gomock.Any()). + Return(&linodego.Image{Capabilities: []string{"cloud-init"}}, nil) + createInst := mck.LinodeClient.EXPECT(). + CreateInstance(ctx, gomock.Any()). + After(getImage). + Return(&linodego.Instance{ + ID: 123, + IPv4: []*net.IP{ptr.To(net.IPv4(192, 168, 0, 2))}, + IPv6: "fd00::", + Status: linodego.InstanceOffline, + }, nil) + updateInstConfig := mck.LinodeClient.EXPECT(). + UpdateInstanceConfig(ctx, 123, 0, gomock.Any()). + After(createInst). + Return(nil, nil).AnyTimes() + getAddrs := mck.LinodeClient.EXPECT(). + GetInstanceIPAddresses(ctx, 123). + After(updateInstConfig). + Return(&linodego.InstanceIPAddressResponse{ + IPv4: &linodego.InstanceIPv4Response{ + Private: []*linodego.InstanceIP{{Address: "192.168.0.2"}}, + Public: []*linodego.InstanceIP{{Address: "172.0.0.2"}}, + }, + IPv6: &linodego.InstanceIPv6Response{ + SLAAC: &linodego.InstanceIP{ + Address: "fd00::", + }, + }, + }, nil).AnyTimes() + mck.LinodeClient.EXPECT(). + ListInstanceConfigs(ctx, 123, gomock.Any()). + After(getAddrs). + Return(nil, &linodego.Error{Code: http.StatusTooManyRequests}) + res, err := reconciler.reconcile(ctx, mck.Logger(), mScope) + Expect(err).NotTo(HaveOccurred()) + Expect(res.RequeueAfter).To(Equal(rutil.DefaultLinodeTooManyRequestsErrorRetryDelay)) + Expect(mck.Logs()).To(ContainSubstring("Failed to get default instance configuration")) + })), + ), + ), Path( Call("machine is created", func(ctx context.Context, mck Mock) { + linodeMachine.Spec.Configuration = nil }), OneOf( Path(Result("creates a worker machine without disks", func(ctx context.Context, mck Mock) { diff --git a/controller/linodeobjectstoragebucket_controller.go b/controller/linodeobjectstoragebucket_controller.go index 4041af71c..15ab1eb31 100644 --- a/controller/linodeobjectstoragebucket_controller.go +++ b/controller/linodeobjectstoragebucket_controller.go @@ -41,7 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" - 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" wrappedruntimeclient "github.com/linode/cluster-api-provider-linode/observability/wrappers/runtimeclient" @@ -82,7 +82,7 @@ func (r *LinodeObjectStorageBucketReconciler) Reconcile(ctx context.Context, req logger := r.Logger.WithValues("name", req.NamespacedName.String()) - objectStorageBucket := &infrav1alpha1.LinodeObjectStorageBucket{} + objectStorageBucket := &infrav1alpha2.LinodeObjectStorageBucket{} if err := r.TracedClient().Get(ctx, req.NamespacedName, objectStorageBucket); err != nil { if err = client.IgnoreNotFound(err); err != nil { logger.Error(err, "Failed to fetch LinodeObjectStorageBucket", "name", req.NamespacedName.String()) @@ -242,7 +242,7 @@ func (r *LinodeObjectStorageBucketReconciler) reconcileDelete(ctx context.Contex return err } - if !controllerutil.RemoveFinalizer(bScope.Bucket, infrav1alpha1.ObjectStorageBucketFinalizer) { + if !controllerutil.RemoveFinalizer(bScope.Bucket, infrav1alpha2.ObjectStorageBucketFinalizer) { err := errors.New("failed to remove finalizer from bucket; unable to delete") bScope.Logger.Error(err, "controllerutil.RemoveFinalizer") r.setFailure(bScope, err) @@ -250,8 +250,8 @@ func (r *LinodeObjectStorageBucketReconciler) reconcileDelete(ctx context.Contex return err } // TODO: remove this check and removal later - if controllerutil.ContainsFinalizer(bScope.Bucket, infrav1alpha1.GroupVersion.String()) { - controllerutil.RemoveFinalizer(bScope.Bucket, infrav1alpha1.GroupVersion.String()) + if controllerutil.ContainsFinalizer(bScope.Bucket, infrav1alpha2.GroupVersion.String()) { + controllerutil.RemoveFinalizer(bScope.Bucket, infrav1alpha2.GroupVersion.String()) } r.Recorder.Event(bScope.Bucket, clusterv1.DeletedReason, "Revoked", "Object storage keys revoked") @@ -263,15 +263,16 @@ func (r *LinodeObjectStorageBucketReconciler) reconcileDelete(ctx context.Contex func (r *LinodeObjectStorageBucketReconciler) SetupWithManager(mgr ctrl.Manager, options crcontroller.Options) error { linodeObjectStorageBucketMapper, err := kutil.ClusterToTypedObjectsMapper( r.TracedClient(), - &infrav1alpha1.LinodeObjectStorageBucketList{}, + &infrav1alpha2.LinodeObjectStorageBucketList{}, mgr.GetScheme(), ) + if err != nil { return fmt.Errorf("failed to create mapper for LinodeObjectStorageBuckets: %w", err) } err = ctrl.NewControllerManagedBy(mgr). - For(&infrav1alpha1.LinodeObjectStorageBucket{}). + For(&infrav1alpha2.LinodeObjectStorageBucket{}). WithOptions(options). Owns(&corev1.Secret{}). WithEventFilter(predicate.And( diff --git a/controller/linodeobjectstoragebucket_controller_test.go b/controller/linodeobjectstoragebucket_controller_test.go index 124eb07bd..7a649c40c 100644 --- a/controller/linodeobjectstoragebucket_controller_test.go +++ b/controller/linodeobjectstoragebucket_controller_test.go @@ -37,7 +37,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/yaml" - 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" "github.com/linode/cluster-api-provider-linode/util" @@ -68,13 +68,13 @@ type accessKeySecret struct { var _ = Describe("lifecycle", Ordered, Label("bucket", "lifecycle"), func() { suite := NewControllerSuite(GinkgoT(), mock.MockLinodeClient{}) - obj := infrav1.LinodeObjectStorageBucket{ + obj := infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "lifecycle", Namespace: "default", }, - Spec: infrav1.LinodeObjectStorageBucketSpec{ - Cluster: "cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "region", }, } @@ -106,19 +106,19 @@ var _ = Describe("lifecycle", Ordered, Label("bucket", "lifecycle"), func() { suite.Run( OneOf( Path(Call("bucket is created", func(ctx context.Context, mck Mock) { - getBucket := mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Cluster, gomock.Any()).Return(nil, nil) + getBucket := mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Region, gomock.Any()).Return(nil, nil) mck.LinodeClient.EXPECT().CreateObjectStorageBucket(gomock.Any(), gomock.Any()). After(getBucket). Return(&linodego.ObjectStorageBucket{ Label: obj.Name, - Region: obj.Spec.Cluster, + Region: obj.Spec.Region, Created: util.Pointer(time.Now()), Hostname: "hostname", }, nil) })), Path( Call("bucket is not created", func(ctx context.Context, mck Mock) { - getBucket := mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Cluster, gomock.Any()).Return(nil, nil) + getBucket := mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Region, gomock.Any()).Return(nil, nil) mck.LinodeClient.EXPECT().CreateObjectStorageBucket(gomock.Any(), gomock.Any()).After(getBucket).Return(nil, errors.New("create bucket error")) }), Result("error", func(ctx context.Context, mck Mock) { @@ -177,7 +177,7 @@ var _ = Describe("lifecycle", Ordered, Label("bucket", "lifecycle"), func() { var key accessKeySecret Expect(yaml.Unmarshal(secret.Data["bucket-details-secret.yaml"], &key)).NotTo(HaveOccurred()) Expect(key.StringData.BucketName).To(Equal("lifecycle")) - Expect(key.StringData.BucketRegion).To(Equal("cluster")) + Expect(key.StringData.BucketRegion).To(Equal("region")) Expect(key.StringData.BucketEndpoint).To(Equal("hostname")) Expect(key.StringData.AccessKeyRW).To(Equal("access-key-0")) Expect(key.StringData.SecretKeyRW).To(Equal("secret-key-0")) @@ -199,17 +199,17 @@ var _ = Describe("lifecycle", Ordered, Label("bucket", "lifecycle"), func() { }), OneOf( Path(Call("bucket is retrieved on update", func(ctx context.Context, mck Mock) { - mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Cluster, gomock.Any()). + mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Region, gomock.Any()). Return(&linodego.ObjectStorageBucket{ Label: obj.Name, - Region: obj.Spec.Cluster, + Region: obj.Spec.Region, Created: util.Pointer(time.Now()), Hostname: "hostname", }, nil) })), Path( Call("bucket is not retrieved on update", func(ctx context.Context, mck Mock) { - mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Cluster, gomock.Any()).Return(nil, errors.New("get bucket error")) + mck.LinodeClient.EXPECT().GetObjectStorageBucket(gomock.Any(), obj.Spec.Region, gomock.Any()).Return(nil, errors.New("get bucket error")) }), Result("error", func(ctx context.Context, mck Mock) { bScope.LinodeClient = mck.LinodeClient @@ -300,7 +300,7 @@ var _ = Describe("lifecycle", Ordered, Label("bucket", "lifecycle"), func() { var key accessKeySecret Expect(yaml.Unmarshal(secret.Data["bucket-details-secret.yaml"], &key)).NotTo(HaveOccurred()) Expect(key.StringData.BucketName).To(Equal("lifecycle")) - Expect(key.StringData.BucketRegion).To(Equal("cluster")) + Expect(key.StringData.BucketRegion).To(Equal("region")) Expect(key.StringData.BucketEndpoint).To(Equal("hostname")) Expect(key.StringData.AccessKeyRW).To(Equal("access-key-2")) Expect(key.StringData.SecretKeyRW).To(Equal("secret-key-2")) @@ -372,14 +372,14 @@ var _ = Describe("errors", Label("bucket", "errors"), func() { // Reset obj to base state to be modified in each test path. // We can use a consistent name since these tests are stateless. - bScope.Bucket = &infrav1.LinodeObjectStorageBucket{ + bScope.Bucket = &infrav1alpha2.LinodeObjectStorageBucket{ ObjectMeta: metav1.ObjectMeta{ Name: "mock", Namespace: "default", UID: "12345", }, - Spec: infrav1.LinodeObjectStorageBucketSpec{ - Cluster: "cluster", + Spec: infrav1alpha2.LinodeObjectStorageBucketSpec{ + Region: "region", }, } }) @@ -425,7 +425,7 @@ var _ = Describe("errors", Label("bucket", "errors"), func() { Expect(err.Error()).To(ContainSubstring("failed to create object storage bucket scope")) Expect(mck.Logs()).To(ContainSubstring("Failed to create object storage bucket scope")) }), - Call("scheme with no infrav1alpha1", func(ctx context.Context, mck Mock) { + Call("scheme with no infrav1alpha2", func(ctx context.Context, mck Mock) { prev := mck.K8sClient.EXPECT().Scheme().Return(scheme.Scheme) mck.K8sClient.EXPECT().Scheme().After(prev).Return(runtime.NewScheme()).Times(2) }), diff --git a/docs/src/topics/backups.md b/docs/src/topics/backups.md index 32953fe04..2660dcee1 100644 --- a/docs/src/topics/backups.md +++ b/docs/src/topics/backups.md @@ -31,13 +31,13 @@ Using this feature requires enabling Object Storage in the account where the res The following is the minimal required configuration needed to provision an Object Storage bucket and set of access keys. ```yaml -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: namespace: spec: - cluster: + region: secretType: Opaque ``` @@ -58,7 +58,7 @@ metadata: name: -bucket-details namespace: ownerReferences: - - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket name: controller: true @@ -80,7 +80,7 @@ The bucket-details secret is owned and managed by CAPL during the life of the `L The following configuration with `keyGeneration` set to a new value (different from `.status.lastKeyGeneration`) will instruct CAPL to rotate the access keys. ```yaml -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: @@ -98,7 +98,7 @@ spec: Upon successful provisioning of a bucket and keys, the `LinodeObjectStorageBucket` resource's status will resemble the following: ```yaml -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: diff --git a/docs/src/topics/multi-tenancy.md b/docs/src/topics/multi-tenancy.md index 6609d2b5b..c67b0b949 100644 --- a/docs/src/topics/multi-tenancy.md +++ b/docs/src/topics/multi-tenancy.md @@ -53,7 +53,7 @@ spec: ... --- # Example: LinodeObjectStorageBucket -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: test-bucket diff --git a/e2e/admission-webhooks/validating/invalid-linodeobjectstoragebucket.yaml b/e2e/admission-webhooks/validating/invalid-linodeobjectstoragebucket.yaml index 0c7772dd3..d16c36f7a 100644 --- a/e2e/admission-webhooks/validating/invalid-linodeobjectstoragebucket.yaml +++ b/e2e/admission-webhooks/validating/invalid-linodeobjectstoragebucket.yaml @@ -1,7 +1,7 @@ --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: ($name) spec: - cluster: invalid-1 + region: invalid diff --git a/e2e/linodemachine-controller/minimal-linodemachine/chainsaw-test.yaml b/e2e/linodemachine-controller/minimal-linodemachine/chainsaw-test.yaml index d3acd7226..6dc870d97 100755 --- a/e2e/linodemachine-controller/minimal-linodemachine/chainsaw-test.yaml +++ b/e2e/linodemachine-controller/minimal-linodemachine/chainsaw-test.yaml @@ -39,7 +39,7 @@ spec: file: assert-linodemachine.yaml catch: - describe: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeMachineTemplate - describe: apiVersion: controlplane.cluster.x-k8s.io/v1beta1 diff --git a/e2e/linodemachine-controller/minimal-linodemachine/create-linodemachine.yaml b/e2e/linodemachine-controller/minimal-linodemachine/create-linodemachine.yaml index af9b6d719..e68673e98 100644 --- a/e2e/linodemachine-controller/minimal-linodemachine/create-linodemachine.yaml +++ b/e2e/linodemachine-controller/minimal-linodemachine/create-linodemachine.yaml @@ -14,13 +14,13 @@ spec: cloud-provider: external machineTemplate: infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeMachineTemplate name: ($cluster) replicas: 1 version: 1.29.1 --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeMachineTemplate metadata: name: ($cluster) diff --git a/e2e/linodemachine-controller/vpc-integration/assert-vpc.yaml b/e2e/linodemachine-controller/vpc-integration/assert-vpc.yaml index 116e1ad06..7d5158e9e 100644 --- a/e2e/linodemachine-controller/vpc-integration/assert-vpc.yaml +++ b/e2e/linodemachine-controller/vpc-integration/assert-vpc.yaml @@ -1,4 +1,4 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeVPC metadata: name: ($vpc) diff --git a/e2e/linodemachine-controller/vpc-integration/chainsaw-test.yaml b/e2e/linodemachine-controller/vpc-integration/chainsaw-test.yaml index 45fea621d..f346c442f 100755 --- a/e2e/linodemachine-controller/vpc-integration/chainsaw-test.yaml +++ b/e2e/linodemachine-controller/vpc-integration/chainsaw-test.yaml @@ -38,10 +38,10 @@ spec: apiVersion: cluster.x-k8s.io/v1beta1 kind: Cluster - describe: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeCluster - describe: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeVPC - name: Create LinodeMachine resource try: @@ -113,7 +113,7 @@ spec: name: ($cluster) - delete: ref: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeVPC name: ($vpc) - error: diff --git a/e2e/linodemachine-controller/vpc-integration/check-vpc-lm-deletion.yaml b/e2e/linodemachine-controller/vpc-integration/check-vpc-lm-deletion.yaml index df95a6047..7fc64212b 100644 --- a/e2e/linodemachine-controller/vpc-integration/check-vpc-lm-deletion.yaml +++ b/e2e/linodemachine-controller/vpc-integration/check-vpc-lm-deletion.yaml @@ -4,7 +4,7 @@ kind: LinodeMachine metadata: name: ($cluster) --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeVPC metadata: name: ($vpc) diff --git a/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml b/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml index cc7871dc7..e268019fa 100644 --- a/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml +++ b/e2e/linodemachine-controller/vpc-integration/create-cluster-vpc.yaml @@ -23,7 +23,7 @@ spec: kind: LinodeVPC name: ($vpc) --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeVPC metadata: name: ($vpc) diff --git a/e2e/linodemachine-controller/vpc-integration/create-linodemachine.yaml b/e2e/linodemachine-controller/vpc-integration/create-linodemachine.yaml index 8b5c27f2b..3a3d0cdef 100644 --- a/e2e/linodemachine-controller/vpc-integration/create-linodemachine.yaml +++ b/e2e/linodemachine-controller/vpc-integration/create-linodemachine.yaml @@ -14,13 +14,13 @@ spec: cloud-provider: external machineTemplate: infrastructureRef: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeMachineTemplate name: ($cluster) replicas: 1 version: 1.29.1 --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeMachineTemplate metadata: name: ($cluster) diff --git a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-keygen.yaml b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-keygen.yaml index fc6d9ba12..002d2aee1 100644 --- a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-keygen.yaml +++ b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-keygen.yaml @@ -1,9 +1,9 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: ($bucket) spec: - cluster: us-sea-1 + region: us-sea keyGeneration: 1 status: ready: true diff --git a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-obj-and-secret.yaml b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-obj-and-secret.yaml index 081428e5f..c6532c454 100644 --- a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-obj-and-secret.yaml +++ b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/assert-obj-and-secret.yaml @@ -1,10 +1,10 @@ --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: ($bucket) spec: - cluster: us-sea-1 + region: us-sea keyGeneration: 0 status: ready: true diff --git a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/chainsaw-test.yaml b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/chainsaw-test.yaml index b5499c1d2..0b5a3e271 100755 --- a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/chainsaw-test.yaml +++ b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/chainsaw-test.yaml @@ -33,7 +33,7 @@ spec: file: assert-obj-and-secret.yaml catch: - describe: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket - describe: apiVersion: v1 @@ -62,7 +62,7 @@ spec: file: assert-keygen.yaml catch: - describe: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket - name: Delete LinodeObjectStorageBucket details Secret try: @@ -83,7 +83,7 @@ spec: try: - delete: ref: - apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket name: ($bucket) - script: diff --git a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/check-obj-and-key-deletion.yaml b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/check-obj-and-key-deletion.yaml index 57ae74579..a7a731828 100644 --- a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/check-obj-and-key-deletion.yaml +++ b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/check-obj-and-key-deletion.yaml @@ -1,4 +1,4 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: ($bucket) diff --git a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/create-linodeobjectstoragebucket.yaml b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/create-linodeobjectstoragebucket.yaml index b3d4cfdad..7292e0b23 100644 --- a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/create-linodeobjectstoragebucket.yaml +++ b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/create-linodeobjectstoragebucket.yaml @@ -1,6 +1,6 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: ($bucket) spec: - cluster: us-sea-1 + region: us-sea \ No newline at end of file diff --git a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/patch-obj.yaml b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/patch-obj.yaml index c63182bb8..67f07c126 100644 --- a/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/patch-obj.yaml +++ b/e2e/linodeobjectstoragebucket-controller/minimal-linodeobjectstoragebucket/patch-obj.yaml @@ -1,7 +1,7 @@ -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: name: ($bucket) spec: - cluster: us-sea-1 + region: us-sea keyGeneration: 1 diff --git a/templates/addons/etcd-backup-restore/linode-obj.yaml b/templates/addons/etcd-backup-restore/linode-obj.yaml index 1412b3845..a25f0a15f 100644 --- a/templates/addons/etcd-backup-restore/linode-obj.yaml +++ b/templates/addons/etcd-backup-restore/linode-obj.yaml @@ -1,5 +1,5 @@ --- -apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2 kind: LinodeObjectStorageBucket metadata: labels: @@ -11,7 +11,7 @@ metadata: cluster.x-k8s.io/cluster-name: ${CLUSTER_NAME} name: ${CLUSTER_NAME}-etcd-backup spec: - cluster: ${OBJ_BUCKET_REGION:=${LINODE_REGION}-1} + region: ${OBJ_BUCKET_REGION:=${LINODE_REGION}} --- apiVersion: addons.cluster.x-k8s.io/v1beta1 kind: ClusterResourceSet diff --git a/util/helpers.go b/util/helpers.go index ce056b919..76cf480f6 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -34,22 +34,15 @@ func UnwrapError(err error) error { return err } -// IsTransientError determines if the error is transient, meaning a controller that +// IsRetryableError determines if the error is retryable, meaning a controller that // encounters this error should requeue reconciliation to try again later -func IsTransientError(err error) bool { - if linodego.ErrHasStatus( +func IsRetryableError(err error) bool { + return linodego.ErrHasStatus( err, http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusBadGateway, http.StatusGatewayTimeout, - http.StatusServiceUnavailable) { - return true - } - - if errors.Is(err, http.ErrHandlerTimeout) || errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) { - return true - } - - return false + http.StatusServiceUnavailable, + linodego.ErrorFromError) || errors.Is(err, http.ErrHandlerTimeout) || errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) } diff --git a/util/helpers_test.go b/util/helpers_test.go index 5e65b2429..de084ffc8 100644 --- a/util/helpers_test.go +++ b/util/helpers_test.go @@ -52,16 +52,16 @@ func TestIgnoreLinodeAPIError(t *testing.T) { } } -func TestIsTransientError(t *testing.T) { +func TestIsRetryableError(t *testing.T) { t.Parallel() tests := []struct { - name string - err error - shouldRetry bool + name string + err error + want bool }{{ - name: "unexpected EOF", - err: io.ErrUnexpectedEOF, - shouldRetry: true, + name: "unexpected EOF", + err: io.ErrUnexpectedEOF, + want: true, }, { name: "not found Linode API error", err: &linodego.Error{ @@ -69,7 +69,7 @@ func TestIsTransientError(t *testing.T) { Code: http.StatusNotFound, Message: "not found", }, - shouldRetry: false, + want: false, }, { name: "Rate limiting Linode API error", err: &linodego.Error{ @@ -77,14 +77,14 @@ func TestIsTransientError(t *testing.T) { Code: http.StatusTooManyRequests, Message: "rate limited", }, - shouldRetry: true, + want: true, }} for _, tt := range tests { testcase := tt t.Run(testcase.name, func(t *testing.T) { t.Parallel() - if testcase.shouldRetry != IsTransientError(testcase.err) { - t.Errorf("wanted %v, got %v", testcase.shouldRetry, IsTransientError(testcase.err)) + if testcase.want != IsRetryableError(testcase.err) { + t.Errorf("wanted %v, got %v", testcase.want, IsRetryableError(testcase.err)) } }) } diff --git a/util/reconciler/defaults.go b/util/reconciler/defaults.go index 3d0f9d21c..19302dd41 100644 --- a/util/reconciler/defaults.go +++ b/util/reconciler/defaults.go @@ -38,6 +38,8 @@ const ( DefaultMachineControllerWaitForRunningTimeout = 20 * time.Minute // DefaultMachineControllerRetryDelay is the default requeue delay if there is an error. DefaultMachineControllerRetryDelay = 10 * time.Second + // DefaultLinodeTooManyRequestsErrorRetryDelay is the default requeue delay if there is a Linode API error. + DefaultLinodeTooManyRequestsErrorRetryDelay = time.Minute // DefaultVPCControllerReconcileDelay is the default requeue delay when a reconcile operation fails. DefaultVPCControllerReconcileDelay = 5 * time.Second