Skip to content

Commit

Permalink
Merge pull request #31 from vshn/deletion-policy
Browse files Browse the repository at this point in the history
Add deletion policy to remove all existing objects in a bucket
  • Loading branch information
ccremer authored Aug 5, 2022
2 parents e607cae + 0b1cb2d commit 6b29166
Show file tree
Hide file tree
Showing 20 changed files with 127 additions and 1,164 deletions.
1 change: 1 addition & 0 deletions Makefile.vars.mk
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ PROJECT_OWNER ?= vshn

## BUILD:go
BIN_FILENAME ?= provider-cloudscale
GOPATH ?= $(shell go env GOPATH)

## BUILD:docker
DOCKER_CMD ?= docker
Expand Down
19 changes: 19 additions & 0 deletions apis/cloudscale/v1/bucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ const (
SecretAccessKeyName = "AWS_SECRET_ACCESS_KEY"
)

const (
// DeleteIfEmpty only deletes the bucket if the bucket is empty.
DeleteIfEmpty BucketDeletionPolicy = "DeleteIfEmpty"
// DeleteAll recursively deletes all objects in the bucket and then removes it.
DeleteAll BucketDeletionPolicy = "DeleteAll"
)

// BucketDeletionPolicy determines how buckets should be deleted when a Bucket is deleted.
type BucketDeletionPolicy string

// BucketParameters are the configurable fields of a Bucket.
type BucketParameters struct {
// +kubebuilder:validation:Required
Expand Down Expand Up @@ -44,6 +54,15 @@ type BucketParameters struct {
// The region must be available in the S3 endpoint.
// Cannot be changed after bucket is created.
Region string `json:"region"`

// +kubebuilder:validation:Enum=DeleteIfEmpty;DeleteAll
// +kubebuilder:default="DeleteIfEmpty"

// BucketDeletionPolicy determines how buckets should be deleted when Bucket is deleted.
// `DeleteIfEmpty` only deletes the bucket if the bucket is empty.
// `DeleteAll` recursively deletes all objects in the bucket and then removes it.
// To skip deletion of the bucket (orphan it) set `spec.deletionPolicy=Orphan`.
BucketDeletionPolicy BucketDeletionPolicy `json:"bucketDeletionPolicy,omitempty"`
}

// BucketSpec defines the desired state of a Bucket.
Expand Down
7 changes: 4 additions & 3 deletions generate_sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ func newBucketSample() *cloudscalev1.Bucket {
Name: "my-cloudscale-user-credentials",
Namespace: "default",
},
EndpointURL: "objects.rma.cloudscale.ch",
BucketName: "my-provider-test-bucket",
Region: "rma",
EndpointURL: "objects.rma.cloudscale.ch",
BucketName: "my-provider-test-bucket",
Region: "rma",
BucketDeletionPolicy: cloudscalev1.DeleteAll,
},
},
}
Expand Down
37 changes: 37 additions & 0 deletions operator/bucketcontroller/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package bucketcontroller

import (
"context"
"fmt"

pipeline "github.com/ccremer/go-command-pipeline"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/event"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/minio/minio-go/v7"
cloudscalev1 "github.com/vshn/provider-cloudscale/apis/cloudscale/v1"
"github.com/vshn/provider-cloudscale/operator/steps"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -21,13 +23,48 @@ func (p *ProvisioningPipeline) Delete(ctx context.Context, mg resource.Managed)
bucket := fromManaged(mg)
pipe := pipeline.NewPipeline().WithBeforeHooks(steps.DebugLogger(ctx)).
WithSteps(
pipeline.If(hasDeleteAllPolicy(bucket),
pipeline.NewStepFromFunc("delete all objects", p.deleteAllObjectsFn(bucket)),
),
pipeline.NewStepFromFunc("delete bucket", p.deleteS3BucketFn(bucket)),
pipeline.NewStepFromFunc("emit event", p.emitDeletionEventFn(bucket)),
)
result := pipe.RunWithContext(ctx)
return errors.Wrap(result.Err(), "cannot deprovision bucket")
}

func hasDeleteAllPolicy(bucket *cloudscalev1.Bucket) pipeline.Predicate {
return func(ctx context.Context) bool {
return bucket.Spec.ForProvider.BucketDeletionPolicy == cloudscalev1.DeleteAll
}
}

func (p *ProvisioningPipeline) deleteAllObjectsFn(bucket *cloudscalev1.Bucket) func(ctx context.Context) error {
return func(ctx context.Context) error {
log := controllerruntime.LoggerFrom(ctx)
bucketName := bucket.Status.AtProvider.BucketName

objectsCh := make(chan minio.ObjectInfo)

// Send object names that are needed to be removed to objectsCh
go func() {
defer close(objectsCh)
for object := range p.minio.ListObjects(ctx, bucketName, minio.ListObjectsOptions{Recursive: true}) {
if object.Err != nil {
log.V(1).Info("warning: cannot list object", "key", object.Key, "error", object.Err)
continue
}
objectsCh <- object
}
}()

for obj := range p.minio.RemoveObjects(ctx, bucketName, objectsCh, minio.RemoveObjectsOptions{GovernanceBypass: true}) {
return fmt.Errorf("object %q cannot be removed: %w", obj.ObjectName, obj.Err)
}
return nil
}
}

// deleteS3BucketFn deletes the bucket.
// NOTE: The removal fails if there are still objects in the bucket.
// This func does not recursively delete all objects beforehand.
Expand Down
2 changes: 1 addition & 1 deletion operator/bucketcontroller/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
// Update implements managed.ExternalClient.
func (p *ProvisioningPipeline) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) {
log := controllerruntime.LoggerFrom(ctx)
log.Info("Updating resource", "res", mg.GetName())
log.V(1).Info("Updating resource", "res", mg.GetName())

return managed.ExternalUpdate{}, nil
}
11 changes: 11 additions & 0 deletions package/crds/cloudscale.crossplane.io_buckets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ spec:
forProvider:
description: BucketParameters are the configurable fields of a Bucket.
properties:
bucketDeletionPolicy:
default: DeleteIfEmpty
description: BucketDeletionPolicy determines how buckets should
be deleted when Bucket is deleted. `DeleteIfEmpty` only deletes
the bucket if the bucket is empty. `DeleteAll` recursively deletes
all objects in the bucket and then removes it. To skip deletion
of the bucket (orphan it) set `spec.deletionPolicy=Orphan`.
enum:
- DeleteIfEmpty
- DeleteAll
type: string
bucketName:
description: BucketName is the name of the bucket to create. Defaults
to `metadata.name` if unset. Cannot be changed after bucket
Expand Down
6 changes: 4 additions & 2 deletions samples/admission.k8s.io_admissionreview.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
},
"endpointURL": "objects.rma.cloudscale.ch",
"bucketName": "another",
"region": "rma"
"region": "rma",
"bucketDeletionPolicy": "DeleteAll"
}
},
"status": {
Expand All @@ -68,7 +69,8 @@
},
"endpointURL": "objects.rma.cloudscale.ch",
"bucketName": "my-provider-test-bucket",
"region": "rma"
"region": "rma",
"bucketDeletionPolicy": "DeleteAll"
}
},
"status": {
Expand Down
1 change: 1 addition & 0 deletions samples/cloudscale.crossplane.io_bucket.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ metadata:
name: bucket
spec:
forProvider:
bucketDeletionPolicy: DeleteAll
bucketName: my-provider-test-bucket
credentialsSecretRef:
name: my-cloudscale-user-credentials
Expand Down
79 changes: 0 additions & 79 deletions test/e2e/go.mod

This file was deleted.

Loading

0 comments on commit 6b29166

Please sign in to comment.