Skip to content

Commit

Permalink
[feat] Adding new fields in LinodeObjectStorageBucket and decoupling …
Browse files Browse the repository at this point in the history
…ObjKey Functionality (#443)

* Adding fields and decoupling ObjBucket and ObjKey Functionality

* Modifying e2e test case for minimal-linodeobjectstoragebucket

* Adding cors in create opts and lint suggestions

* lint suggestions

* lint suggestions

* key generation pointer changes
  • Loading branch information
unnatiagg authored Aug 8, 2024
1 parent 8ab18ac commit 6ae123b
Show file tree
Hide file tree
Showing 19 changed files with 132 additions and 1,399 deletions.
15 changes: 13 additions & 2 deletions api/v1alpha1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"strings"

"k8s.io/apimachinery/pkg/conversion"
"k8s.io/utils/ptr"

infrastructurev1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2"
)
Expand Down Expand Up @@ -50,16 +51,26 @@ func Convert_v1alpha1_LinodeObjectStorageBucketSpec_To_v1alpha2_LinodeObjectStor
// 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_v1alpha1_LinodeObjectStorageBucketStatus_To_v1alpha2_LinodeObjectStorageBucketStatus(in *LinodeObjectStorageBucketStatus, out *infrastructurev1alpha2.LinodeObjectStorageBucketStatus, s conversion.Scope) error {
out.Ready = in.Ready
out.FailureMessage = in.FailureMessage
out.Conditions = in.Conditions
out.Hostname = in.Hostname
out.CreationTime = in.CreationTime
// WARNING: in.LastKeyGeneration requires manual conversion: does not exist in peer-type
// WARNING: in.KeySecretName requires manual conversion: does not exist in peer-type
// WARNING: in.AccessKeyRefs requires manual conversion: does not exist in peer-type
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.KeyGeneration = ptr.To(0)
out.SecretType = in.SecretType
return nil
}
Expand Down
10 changes: 4 additions & 6 deletions api/v1alpha1/linodeobjectstoragebucket_conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ func TestLinodeObjectStorageBucketConvertTo(t *testing.T) {
Namespace: "default",
Name: "cred-secret",
},
KeyGeneration: ptr.To(1),
SecretType: "Opaque",
SecretType: "Opaque",
},
Status: infrav1alpha2.LinodeObjectStorageBucketStatus{},
}
Expand Down Expand Up @@ -96,16 +95,15 @@ func TestLinodeObjectStorageBucketFrom(t *testing.T) {
Namespace: "default",
Name: "cred-secret",
},
KeyGeneration: ptr.To(1),
SecretType: "Opaque",
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}}`,
ConversionDataAnnotation: `{"spec":{"credentialsRef":{"name":"cred-secret","namespace":"default"},"region":"us-mia-1","secretType":"Opaque"},"status":{"ready":false}}`,
},
},
Spec: LinodeObjectStorageBucketSpec{
Expand All @@ -114,7 +112,7 @@ func TestLinodeObjectStorageBucketFrom(t *testing.T) {
Namespace: "default",
Name: "cred-secret",
},
KeyGeneration: ptr.To(1),
KeyGeneration: ptr.To(0),
SecretType: "Opaque",
},
Status: LinodeObjectStorageBucketStatus{},
Expand Down
29 changes: 11 additions & 18 deletions api/v1alpha1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 19 additions & 18 deletions api/v1alpha2/linodeobjectstoragebucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)

type ObjectStorageACL string

// ObjectStorageACL options represent the access control level of a bucket.
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"
ObjectStorageBucketFinalizer = "linodeobjectstoragebucket.infrastructure.cluster.x-k8s.io"
ACLPrivate ObjectStorageACL = "private"
ACLPublicRead ObjectStorageACL = "public-read"
ACLAuthenticatedRead ObjectStorageACL = "authenticated-read"
ACLPublicReadWrite ObjectStorageACL = "public-read-write"
)

// LinodeObjectStorageBucketSpec defines the desired state of LinodeObjectStorageBucket
Expand All @@ -37,16 +44,22 @@ type LinodeObjectStorageBucketSpec struct {
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
Region string `json:"region"`

// ACL sets The Access Control Level of the bucket using a canned ACL string
// +optional
// +kubebuilder:default=private
// +kubebuilder:validation:Enum=private;public-read;authenticated-read;public-read-write
ACL ObjectStorageACL `json:"acl,omitempty"`

// corsEnabled enables for all origins in the bucket .If set to false, CORS is disabled for all origins in the bucket
// +optional
// +kubebuilder:default=true
CorsEnabled *bool `json:"corsEnabled,omitempty"`

// 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
Expand Down Expand Up @@ -80,18 +93,6 @@ type LinodeObjectStorageBucketStatus struct {
// 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
Expand Down
25 changes: 5 additions & 20 deletions api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 1 addition & 97 deletions cloud/scope/object_storage_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,14 @@ import (
"time"

"github.com/go-logr/logr"
"github.com/linode/linodego"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2"

. "github.com/linode/cluster-api-provider-linode/clients"
)

const bucketDataSecret = `kind: Secret
apiVersion: v1
metadata:
name: etcd-backup
namespace: kube-system
stringData:
bucket_name: %s
bucket_region: %s
bucket_endpoint: %s
access_key_rw: %s
secret_key_rw: %s
access_key_ro: %s
secret_key_ro: %s`

type ObjectStorageBucketScopeParams struct {
Client K8sClient
Bucket *infrav1alpha2.LinodeObjectStorageBucket
Expand All @@ -49,9 +30,7 @@ type ObjectStorageBucketScope struct {
}

const (
AccessKeyNameTemplate = "%s-bucket-details"
NumAccessKeys = 2
clientTimeout = 20 * time.Second
clientTimeout = 20 * time.Second
)

func validateObjectStorageBucketScopeParams(params ObjectStorageBucketScopeParams) error {
Expand Down Expand Up @@ -119,78 +98,3 @@ func (s *ObjectStorageBucketScope) AddFinalizer(ctx context.Context) error {

return nil
}

// GenerateKeySecret returns a secret suitable for submission to the Kubernetes API.
// The secret is expected to contain keys for accessing the bucket, as well as owner and controller references.
func (s *ObjectStorageBucketScope) GenerateKeySecret(ctx context.Context, keys [NumAccessKeys]*linodego.ObjectStorageKey, bucket *linodego.ObjectStorageBucket) (*corev1.Secret, error) {
for _, key := range keys {
if key == nil {
return nil, errors.New("expected two non-nil object storage keys")
}
}
var secretStringData map[string]string
secretName := fmt.Sprintf(AccessKeyNameTemplate, s.Bucket.Name)
// If the secret is of ClusterResourceSet type, encapsulate real data in the outer data
if s.Bucket.Spec.SecretType == "addons.cluster.x-k8s.io/resource-set" {
secretStringData = map[string]string{
"bucket-details-secret.yaml": fmt.Sprintf(bucketDataSecret,
bucket.Label,
bucket.Region,
bucket.Hostname,
keys[0].AccessKey,
keys[0].SecretKey,
keys[1].AccessKey,
keys[1].SecretKey,
),
}
} else {
secretStringData = map[string]string{
"bucket_name": bucket.Label,
"bucket_region": bucket.Region,
"bucket_endpoint": bucket.Hostname,
"access_key_rw": keys[0].AccessKey,
"secret_key_rw": keys[0].SecretKey,
"access_key_ro": keys[1].AccessKey,
"secret_key_ro": keys[1].SecretKey,
}
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: s.Bucket.Namespace,
},
Type: corev1.SecretType(s.Bucket.Spec.SecretType),
StringData: secretStringData,
}

scheme := s.Client.Scheme()
if err := controllerutil.SetOwnerReference(s.Bucket, secret, scheme); err != nil {
return nil, fmt.Errorf("could not set owner ref on access key secret %s: %w", secretName, err)
}
if err := controllerutil.SetControllerReference(s.Bucket, secret, scheme); err != nil {
return nil, fmt.Errorf("could not set controller ref on access key secret %s: %w", secretName, err)
}

return secret, nil
}

func (s *ObjectStorageBucketScope) ShouldInitKeys() bool {
return s.Bucket.Status.LastKeyGeneration == nil
}

func (s *ObjectStorageBucketScope) ShouldRotateKeys() bool {
return s.Bucket.Status.LastKeyGeneration != nil &&
*s.Bucket.Spec.KeyGeneration != *s.Bucket.Status.LastKeyGeneration
}

func (s *ObjectStorageBucketScope) ShouldRestoreKeySecret(ctx context.Context) (bool, error) {
if s.Bucket.Status.KeySecretName == nil {
return false, nil
}

secret := &corev1.Secret{}
key := client.ObjectKey{Namespace: s.Bucket.Namespace, Name: *s.Bucket.Status.KeySecretName}
err := s.Client.Get(ctx, key, secret)

return apierrors.IsNotFound(err), client.IgnoreNotFound(err)
}
Loading

0 comments on commit 6ae123b

Please sign in to comment.