Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add label field to OBJ bucket #164

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/v1alpha1/linodeobjectstoragebucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ type LinodeObjectStorageBucketSpec struct {
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
Cluster string `json:"cluster"`

// Label specifies the name of the Object Storage Bucket.
// If not supplied then the name of the LinodeObjectStorageBucket resource will be used.
// +kubebuilder:validation:MinLength=3
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// +optional
Label *string `json:"label,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
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

30 changes: 15 additions & 15 deletions cloud/scope/object_storage_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import (

type ObjectStorageBucketScopeParams struct {
Client client.Client
Object *infrav1alpha1.LinodeObjectStorageBucket
Bucket *infrav1alpha1.LinodeObjectStorageBucket
Logger *logr.Logger
}

type ObjectStorageBucketScope struct {
client client.Client
Object *infrav1alpha1.LinodeObjectStorageBucket
Bucket *infrav1alpha1.LinodeObjectStorageBucket
Logger logr.Logger
LinodeClient *linodego.Client
BucketPatchHelper *patch.Helper
Expand All @@ -35,7 +35,7 @@ const AccessKeyNameTemplate = "%s-access-keys"
const NumAccessKeys = 2

func validateObjectStorageBucketScopeParams(params ObjectStorageBucketScopeParams) error {
if params.Object == nil {
if params.Bucket == nil {
return errors.New("object storage bucket is required when creating an ObjectStorageBucketScope")
}
if params.Logger == nil {
Expand All @@ -51,23 +51,23 @@ func NewObjectStorageBucketScope(ctx context.Context, apiKey string, params Obje
}

// Override the controller credentials with ones from the Cluster's Secret reference (if supplied).
if params.Object.Spec.CredentialsRef != nil {
data, err := getCredentialDataFromRef(ctx, params.Client, *params.Object.Spec.CredentialsRef, params.Object.GetNamespace())
if params.Bucket.Spec.CredentialsRef != nil {
data, err := getCredentialDataFromRef(ctx, params.Client, *params.Bucket.Spec.CredentialsRef, params.Bucket.GetNamespace())
if err != nil {
return nil, fmt.Errorf("credentials from cluster secret ref: %w", err)
}
apiKey = string(data)
}
linodeClient := createLinodeClient(apiKey)

bucketPatchHelper, err := patch.NewHelper(params.Object, params.Client)
bucketPatchHelper, err := patch.NewHelper(params.Bucket, params.Client)
if err != nil {
return nil, fmt.Errorf("failed to init patch helper: %w", err)
}

return &ObjectStorageBucketScope{
client: params.Client,
Object: params.Object,
Bucket: params.Bucket,
Logger: *params.Logger,
LinodeClient: linodeClient,
BucketPatchHelper: bucketPatchHelper,
Expand All @@ -76,7 +76,7 @@ func NewObjectStorageBucketScope(ctx context.Context, apiKey string, params Obje

// PatchObject persists the object storage bucket configuration and status.
func (s *ObjectStorageBucketScope) PatchObject(ctx context.Context) error {
return s.BucketPatchHelper.Patch(ctx, s.Object)
return s.BucketPatchHelper.Patch(ctx, s.Bucket)
}

// Close closes the current scope persisting the object storage bucket configuration and status.
Expand All @@ -87,7 +87,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.Object, infrav1alpha1.GroupVersion.String()) {
if controllerutil.AddFinalizer(s.Bucket, infrav1alpha1.GroupVersion.String()) {
return s.Close(ctx)
}

Expand All @@ -109,22 +109,22 @@ func (s *ObjectStorageBucketScope) ApplyAccessKeySecret(ctx context.Context, key
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: s.Object.Namespace,
Namespace: s.Bucket.Namespace,
},
StringData: map[string]string{
"read_write": string(accessKeys[0]),
"read_only": string(accessKeys[1]),
},
}

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

// Add finalizer to secret so it isn't deleted when bucket deletion is triggered
controllerutil.AddFinalizer(secret, infrav1alpha1.GroupVersion.String())

if s.Object.Status.KeySecretName == nil {
if s.Bucket.Status.KeySecretName == nil {
if err := s.client.Create(ctx, secret); err != nil {
return fmt.Errorf("could not create access key secret %s: %w", secretName, err)
}
Expand All @@ -140,10 +140,10 @@ func (s *ObjectStorageBucketScope) ApplyAccessKeySecret(ctx context.Context, key
}

func (s *ObjectStorageBucketScope) GetAccessKeySecret(ctx context.Context) (*corev1.Secret, error) {
secretName := fmt.Sprintf(AccessKeyNameTemplate, s.Object.Name)
secretName := fmt.Sprintf(AccessKeyNameTemplate, *s.Bucket.Spec.Label)

objKey := client.ObjectKey{
Namespace: s.Object.Namespace,
Namespace: s.Bucket.Namespace,
Name: secretName,
}
var secret corev1.Secret
Expand Down Expand Up @@ -177,5 +177,5 @@ func (s *ObjectStorageBucketScope) GetAccessKeysFromSecret(ctx context.Context,
}

func (s *ObjectStorageBucketScope) ShouldRotateKeys() bool {
return *s.Object.Spec.KeyGeneration != *s.Object.Status.LastKeyGeneration
return *s.Bucket.Spec.KeyGeneration != *s.Bucket.Status.LastKeyGeneration
}
35 changes: 13 additions & 22 deletions cloud/services/object_storage_buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package services

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand All @@ -13,25 +12,17 @@ import (
"github.com/linode/linodego"

"github.com/linode/cluster-api-provider-linode/cloud/scope"
"github.com/linode/cluster-api-provider-linode/util"
)

func EnsureObjectStorageBucket(ctx context.Context, bScope *scope.ObjectStorageBucketScope) (*linodego.ObjectStorageBucket, error) {
filter := map[string]string{
"label": bScope.Object.Name,
}

rawFilter, err := json.Marshal(filter)
if err != nil {
return nil, fmt.Errorf("failed to list buckets in cluster %s: %w", bScope.Object.Spec.Cluster, err)
}

var buckets []linodego.ObjectStorageBucket
if buckets, err = bScope.LinodeClient.ListObjectStorageBucketsInCluster(
buckets, err := bScope.LinodeClient.ListObjectStorageBucketsInCluster(
ctx,
linodego.NewListOptions(1, string(rawFilter)),
bScope.Object.Spec.Cluster,
); err != nil {
return nil, fmt.Errorf("failed to list buckets in cluster %s: %w", bScope.Object.Spec.Cluster, err)
linodego.NewListOptions(1, util.CreateLinodeAPIFilter(*bScope.Bucket.Spec.Label, nil)),
bScope.Bucket.Spec.Cluster,
)
if err != nil {
return nil, fmt.Errorf("failed to list buckets in cluster %s: %w", bScope.Bucket.Spec.Cluster, err)
}
if len(buckets) == 1 {
bScope.Logger.Info("Bucket exists")
Expand All @@ -40,8 +31,8 @@ func EnsureObjectStorageBucket(ctx context.Context, bScope *scope.ObjectStorageB
}

opts := linodego.ObjectStorageBucketCreateOptions{
Cluster: bScope.Object.Spec.Cluster,
Label: bScope.Object.Name,
Cluster: bScope.Bucket.Spec.Cluster,
Label: *bScope.Bucket.Spec.Label,
ACL: linodego.ACLPrivate,
}

Expand All @@ -65,7 +56,7 @@ func RotateObjectStorageKeys(ctx context.Context, bScope *scope.ObjectStorageBuc
{"read_write", "rw"},
{"read_only", "ro"},
} {
keyLabel := fmt.Sprintf("%s-%s", bScope.Object.Name, permission.suffix)
keyLabel := fmt.Sprintf("%s-%s", *bScope.Bucket.Spec.Label, permission.suffix)
key, err := createObjectStorageKey(ctx, bScope, keyLabel, permission.name)
if err != nil {
return newKeys, err
Expand All @@ -75,7 +66,7 @@ func RotateObjectStorageKeys(ctx context.Context, bScope *scope.ObjectStorageBuc
}

// If key revocation fails here, just log the errors since new keys have been created
if bScope.Object.Status.LastKeyGeneration != nil && bScope.ShouldRotateKeys() {
if bScope.Bucket.Status.LastKeyGeneration != nil && bScope.ShouldRotateKeys() {
secret, err := bScope.GetAccessKeySecret(ctx)
if err != nil {
bScope.Logger.Error(err, "Failed to read secret with access keys to revoke; keys must be manually revoked")
Expand All @@ -94,8 +85,8 @@ func createObjectStorageKey(ctx context.Context, bScope *scope.ObjectStorageBuck
Label: label,
BucketAccess: &[]linodego.ObjectStorageKeyBucketAccess{
{
BucketName: bScope.Object.Name,
Cluster: bScope.Object.Spec.Cluster,
BucketName: *bScope.Bucket.Spec.Label,
Cluster: bScope.Bucket.Spec.Cluster,
Permissions: permission,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ spec:
description: KeyGeneration may be modified to trigger rotations of
access keys created for the bucket.
type: integer
label:
description: |-
Label specifies the name of the Object Storage Bucket.
If not supplied then the name of the LinodeObjectStorageBucket resource will be used.
maxLength: 63
minLength: 3
type: string
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
required:
- cluster
type: object
Expand Down
36 changes: 20 additions & 16 deletions controller/linodeobjectstoragebucket_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (r *LinodeObjectStorageBucketReconciler) Reconcile(ctx context.Context, req
r.LinodeApiKey,
scope.ObjectStorageBucketScopeParams{
Client: r.Client,
Object: objectStorageBucket,
Bucket: objectStorageBucket,
Logger: &logger,
},
)
Expand All @@ -115,7 +115,7 @@ func (r *LinodeObjectStorageBucketReconciler) reconcile(ctx context.Context, bSc
}()

// Delete
if !bScope.Object.DeletionTimestamp.IsZero() {
if !bScope.Bucket.DeletionTimestamp.IsZero() {
return res, r.reconcileDelete(ctx, bScope)
}

Expand All @@ -128,31 +128,35 @@ func (r *LinodeObjectStorageBucketReconciler) reconcile(ctx context.Context, bSc
}

func (r *LinodeObjectStorageBucketReconciler) setFailure(bScope *scope.ObjectStorageBucketScope, err error) {
bScope.Object.Status.FailureMessage = util.Pointer(err.Error())
r.Recorder.Event(bScope.Object, corev1.EventTypeWarning, "Failed", err.Error())
conditions.MarkFalse(bScope.Object, clusterv1.ReadyCondition, "Failed", clusterv1.ConditionSeverityError, "%s", err.Error())
bScope.Bucket.Status.FailureMessage = util.Pointer(err.Error())
r.Recorder.Event(bScope.Bucket, corev1.EventTypeWarning, "Failed", err.Error())
conditions.MarkFalse(bScope.Bucket, clusterv1.ReadyCondition, "Failed", clusterv1.ConditionSeverityError, "%s", err.Error())
}

func (r *LinodeObjectStorageBucketReconciler) reconcileApply(ctx context.Context, bScope *scope.ObjectStorageBucketScope) error {
bScope.Logger.Info("Reconciling apply")

bScope.Object.Status.Ready = false
bScope.Bucket.Status.Ready = false

if err := bScope.AddFinalizer(ctx); err != nil {
return err
}

if bScope.Bucket.Spec.Label == nil {
bScope.Bucket.Spec.Label = util.Pointer(bScope.Bucket.Name)
}

bucket, err := services.EnsureObjectStorageBucket(ctx, bScope)
if err != nil {
bScope.Logger.Error(err, "Failed to ensure bucket exists")
r.setFailure(bScope, err)

return err
}
bScope.Object.Status.Hostname = util.Pointer(bucket.Hostname)
bScope.Object.Status.CreationTime = &metav1.Time{Time: *bucket.Created}
bScope.Bucket.Status.Hostname = util.Pointer(bucket.Hostname)
bScope.Bucket.Status.CreationTime = &metav1.Time{Time: *bucket.Created}

if bScope.Object.Status.LastKeyGeneration == nil || bScope.ShouldRotateKeys() {
if bScope.Bucket.Status.LastKeyGeneration == nil || bScope.ShouldRotateKeys() {
keys, err := services.RotateObjectStorageKeys(ctx, bScope)
if err != nil {
bScope.Logger.Error(err, "Failed to provision new access keys")
Expand All @@ -161,21 +165,21 @@ func (r *LinodeObjectStorageBucketReconciler) reconcileApply(ctx context.Context
return err
}

secretName := fmt.Sprintf(scope.AccessKeyNameTemplate, bScope.Object.Name)
secretName := fmt.Sprintf(scope.AccessKeyNameTemplate, *bScope.Bucket.Spec.Label)
if err := bScope.ApplyAccessKeySecret(ctx, keys, secretName); err != nil {
bScope.Logger.Error(err, "Failed to apply access key secret")
r.setFailure(bScope, err)

return err
}
bScope.Object.Status.KeySecretName = util.Pointer(secretName)
bScope.Object.Status.LastKeyGeneration = bScope.Object.Spec.KeyGeneration
bScope.Bucket.Status.KeySecretName = util.Pointer(secretName)
bScope.Bucket.Status.LastKeyGeneration = bScope.Bucket.Spec.KeyGeneration
}

r.Recorder.Event(bScope.Object, corev1.EventTypeNormal, "Ready", "Object storage bucket configuration applied")
r.Recorder.Event(bScope.Bucket, corev1.EventTypeNormal, "Ready", "Object storage bucket configuration applied")

bScope.Object.Status.Ready = true
conditions.MarkTrue(bScope.Object, clusterv1.ReadyCondition)
bScope.Bucket.Status.Ready = true
conditions.MarkTrue(bScope.Bucket, clusterv1.ReadyCondition)

return nil
}
Expand Down Expand Up @@ -213,7 +217,7 @@ func (r *LinodeObjectStorageBucketReconciler) reconcileDelete(ctx context.Contex
return err
}

if !controllerutil.RemoveFinalizer(bScope.Object, infrav1alpha1.GroupVersion.String()) {
if !controllerutil.RemoveFinalizer(bScope.Bucket, infrav1alpha1.GroupVersion.String()) {
bScope.Logger.Error(err, "Failed to remove finalizer from bucket; will not be deleted")
r.setFailure(bScope, err)

Expand Down
Loading