Skip to content
This repository has been archived by the owner on Aug 22, 2023. It is now read-only.

Commit

Permalink
Merge pull request #68 from vshn/delete-operation
Browse files Browse the repository at this point in the history
Add instance deletion
  • Loading branch information
zugao authored Jun 3, 2022
2 parents e23a3f0 + e2f9a69 commit e0c201c
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 23 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ install-samples: export KUBECONFIG = $(KIND_KUBECONFIG)
install-samples: generate-go install-crd ## Install samples into cluster
yq package/samples/*.yaml | kubectl apply -f -

.PHONY: delete-instance
delete-instance: export KUBECONFIG = $(KIND_KUBECONFIG)
delete-instance: ## Deletes sample instance if it exists
kubectl delete -f package/samples/postgresql.appcat.vshn.io_postgresqlstandalone.yaml --ignore-not-found=true

.PHONY: run-operator
run-operator: ## Run in Operator mode against your current kube context
go run . -v 1 operator
Expand Down
4 changes: 2 additions & 2 deletions operator/operatortest/envtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ func (ts *Suite) SetupSuite() {

info, err := os.Stat(envtestAssets)
absEnvtestAssets, _ := filepath.Abs(envtestAssets)
ts.Require().NoErrorf(err, "'%s' does not seem to exist. Check KUBEBUILDER_ASSETS and make sure you run `make integration-test` before you run this test in your IDE.", absEnvtestAssets)
ts.Require().Truef(info.IsDir(), "'%s' does not seem to be a directory. Check KUBEBUILDER_ASSETS and make sure you run `make integration-test` before you run this test in your IDE.", absEnvtestAssets)
ts.Require().NoErrorf(err, "'%s' does not seem to exist. Check KUBEBUILDER_ASSETS and make sure you run `make test-integration` before you run this test in your IDE.", absEnvtestAssets)
ts.Require().Truef(info.IsDir(), "'%s' does not seem to be a directory. Check KUBEBUILDER_ASSETS and make sure you run `make test-integration` before you run this test in your IDE.", absEnvtestAssets)

absCrds, _ := filepath.Abs(crdDir)
info, err = os.Stat(crdDir)
Expand Down
16 changes: 7 additions & 9 deletions operator/standalone/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (r *PostgresStandaloneReconciler) Reconcile(ctx context.Context, request re
return reconcile.Result{}, err
}
if !obj.DeletionTimestamp.IsZero() {
return r.Delete(ctx, obj)
return r.DeleteDeployment(ctx, obj)
}
if readyCondition := meta.FindStatusCondition(obj.Status.Conditions, conditions.TypeReady); readyCondition == nil {
// Ready condition is not present, it's a fresh object
Expand Down Expand Up @@ -89,15 +89,13 @@ func (r *PostgresStandaloneReconciler) CreateDeployment(ctx context.Context, ins
return reconcile.Result{RequeueAfter: 10 * time.Second}, err
}

// Delete prepares the given instance for deletion.
func (r *PostgresStandaloneReconciler) Delete(ctx context.Context, instance *v1alpha1.PostgresqlStandalone) (reconcile.Result, error) {
// we don't need to delete it by ourselves, since the deletion timestamp is already set.
// Just remove all finalizers
// DeleteDeployment prepares the given instance for deletion.
func (r *PostgresStandaloneReconciler) DeleteDeployment(ctx context.Context, instance *v1alpha1.PostgresqlStandalone) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)
controllerutil.RemoveFinalizer(instance, finalizer)
log.Info("Deleting")
err := r.client.Update(ctx, instance)
return reconcile.Result{}, err
d := NewDeleteStandalonePipeline(r.client, instance)
log.Info("Deleting instance")
err := d.RunPipeline(ctx)
return reconcile.Result{RequeueAfter: 1 * time.Second}, err
}

// Update saves the given spec in Kubernetes.
Expand Down
16 changes: 8 additions & 8 deletions operator/standalone/create_it_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (ts *CreateStandalonePipelineSuite) Test_FetchOperatorConfig() {
p := &CreateStandalonePipeline{
operatorNamespace: tc.givenNamespace,
client: ts.Client,
instance: newInstance("instance"),
instance: newInstance("instance", "my-app"),
}
tc.prepare()
err := p.fetchOperatorConfig(ts.Context)
Expand All @@ -93,7 +93,7 @@ func (ts *CreateStandalonePipelineSuite) Test_FetchOperatorConfig() {
func (ts *CreateStandalonePipelineSuite) Test_EnsureDeploymentNamespace() {
// Arrange
p := &CreateStandalonePipeline{
instance: newInstance("test-ensure-namespace"),
instance: newInstance("test-ensure-namespace", "my-app"),
client: ts.Client,
}
currentRand := namegeneratorRNG
Expand All @@ -117,7 +117,7 @@ func (ts *CreateStandalonePipelineSuite) Test_EnsureCredentialSecret() {
ns := ServiceNamespacePrefix + "my-app-instance"
ts.EnsureNS(ns)
p := &CreateStandalonePipeline{
instance: newInstance("instance"),
instance: newInstance("instance", "my-app"),
client: ts.Client,
deploymentNamespace: &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}},
}
Expand All @@ -137,7 +137,7 @@ func (ts *CreateStandalonePipelineSuite) Test_EnsureCredentialSecret() {
func (ts *CreateStandalonePipelineSuite) Test_EnsureHelmRelease() {
// Arrange
p := &CreateStandalonePipeline{
instance: newInstance("instance"),
instance: newInstance("instance", "my-app"),
client: ts.Client,
helmChart: &v1alpha1.ChartMeta{Repository: "https://host/path", Version: "version", Name: "postgres"},
helmValues: helmvalues.V{"key": "value"},
Expand All @@ -160,7 +160,7 @@ func (ts *CreateStandalonePipelineSuite) Test_EnsureHelmRelease() {
func (ts *CreateStandalonePipelineSuite) Test_EnrichStatus() {
// Arrange
p := &CreateStandalonePipeline{
instance: newInstance("enrich-status"),
instance: newInstance("enrich-status", "my-app"),
client: ts.Client,
helmChart: &v1alpha1.ChartMeta{Repository: "https://host/path", Version: "version", Name: "postgres"},
deploymentNamespace: &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: generateClusterScopedNameForInstance()}},
Expand All @@ -186,7 +186,7 @@ func (ts *CreateStandalonePipelineSuite) Test_EnrichStatus() {
func (ts *CreateStandalonePipelineSuite) Test_FetchHelmRelease() {
// Arrange
p := &CreateStandalonePipeline{
instance: newInstance("fetch-release"),
instance: newInstance("fetch-release", "my-app"),
client: ts.Client,
}
p.instance.Status.HelmChart = &v1alpha1.ChartMetaStatus{
Expand All @@ -208,7 +208,7 @@ func (ts *CreateStandalonePipelineSuite) Test_FetchHelmRelease() {
func (ts *CreateStandalonePipelineSuite) Test_FetchCredentialSecret() {
// Arrange
p := CreateStandalonePipeline{
instance: newInstance("fetch-credentials"),
instance: newInstance("fetch-credentials", "my-app"),
}
credentialSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -241,7 +241,7 @@ func (ts *CreateStandalonePipelineSuite) Test_FetchCredentialSecret() {
func (ts *CreateStandalonePipelineSuite) Test_FetchService() {
// Arrange
p := CreateStandalonePipeline{
instance: newInstance("fetch-service"),
instance: newInstance("fetch-service", "my-app"),
}
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down
8 changes: 4 additions & 4 deletions operator/standalone/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func TestCreateStandalonePipeline_OverrideTemplateValues(t *testing.T) {
func TestCreateStandalonePipeline_ApplyValuesFromInstance(t *testing.T) {
p := CreateStandalonePipeline{
config: newPostgresqlStandaloneOperatorConfig("cfg", "postgresql-system"),
instance: newInstance("instance"),
instance: newInstance("instance", "my-app"),
}
err := p.applyValuesFromInstance(nil)
require.NoError(t, err)
Expand Down Expand Up @@ -153,7 +153,7 @@ func TestCreateStandalonePipeline_ApplyValuesFromInstance(t *testing.T) {

func TestCreateStandalonePipeline_IsHelmReleaseReady(t *testing.T) {
p := CreateStandalonePipeline{
instance: newInstance("release-ready"),
instance: newInstance("release-ready", "my-app"),
}
p.instance.Status.HelmChart = &v1alpha1.ChartMetaStatus{}

Expand Down Expand Up @@ -207,9 +207,9 @@ func newPostgresqlStandaloneOperatorConfig(name string, namespace string) *v1alp
},
}
}
func newInstance(name string) *v1alpha1.PostgresqlStandalone {
func newInstance(name string, namespace string) *v1alpha1.PostgresqlStandalone {
return &v1alpha1.PostgresqlStandalone{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "my-app"},
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: v1alpha1.PostgresqlStandaloneSpec{
Parameters: v1alpha1.PostgresqlStandaloneParameters{
MajorVersion: v1alpha1.PostgresqlVersion14,
Expand Down
100 changes: 100 additions & 0 deletions operator/standalone/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package standalone

import (
"context"
pipeline "github.com/ccremer/go-command-pipeline"
helmv1beta1 "github.com/crossplane-contrib/provider-helm/apis/release/v1beta1"
"github.com/vshn/appcat-service-postgresql/apis/postgresql/v1alpha1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

// DeleteStandalonePipeline is a pipeline that deletes an instance from the target deployment namespace.
type DeleteStandalonePipeline struct {
client client.Client
instance *v1alpha1.PostgresqlStandalone
helmReleaseDeleted bool
}

// NewDeleteStandalonePipeline creates a new delete pipeline with the required dependencies.
func NewDeleteStandalonePipeline(client client.Client, instance *v1alpha1.PostgresqlStandalone) *DeleteStandalonePipeline {
return &DeleteStandalonePipeline{
instance: instance,
client: client,
}
}

// RunPipeline executes the pipeline with configured business logic steps.
// The pipeline requires multiple reconciliations due to asynchronous deletion of resources in background
// The Helm Release step requires a complete removal of its resources before moving to the next step
func (d *DeleteStandalonePipeline) RunPipeline(ctx context.Context) error {
return pipeline.NewPipeline().
WithSteps(
pipeline.NewStepFromFunc("delete connection secret", d.deleteConnectionSecret),
pipeline.NewStepFromFunc("delete helm release", d.deleteHelmRelease),
pipeline.If(d.isHelmReleaseDeleted,
pipeline.NewPipeline().WithNestedSteps("finalize",
pipeline.NewStepFromFunc("delete namespace", d.deleteNamespace),
pipeline.NewStepFromFunc("remove finalizer", d.removeFinalizer),
),
),
).
RunWithContext(ctx).Err()
}

// deleteHelmRelease removes the Helm Release from the cluster
// We may reconcile multiple times until Release is completely deleted hence we use helmReleaseDeleted variable
func (d *DeleteStandalonePipeline) deleteHelmRelease(ctx context.Context) error {
helmRelease := &helmv1beta1.Release{
ObjectMeta: metav1.ObjectMeta{
Name: d.instance.Status.HelmChart.DeploymentNamespace,
},
}
err := d.client.Delete(ctx, helmRelease)
if err != nil && apierrors.IsNotFound(err) {
d.helmReleaseDeleted = true
}
return client.IgnoreNotFound(err)
}

// deleteNamespace removes the namespace of the PostgreSQL instance
// We delete the namespace only if the Helm Release has been deleted
func (d *DeleteStandalonePipeline) deleteNamespace(ctx context.Context) error {
deploymentNamespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: d.instance.Status.HelmChart.DeploymentNamespace,
},
}
propagation := metav1.DeletePropagationBackground
deleteOptions := &client.DeleteOptions{
PropagationPolicy: &propagation,
}
return client.IgnoreNotFound(d.client.Delete(ctx, deploymentNamespace, deleteOptions))
}

// deleteConnectionSecret removes the connection secret of the PostgreSQL instance
func (d *DeleteStandalonePipeline) deleteConnectionSecret(ctx context.Context) error {
connectionSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: d.instance.Spec.WriteConnectionSecretToRef.Name,
Namespace: d.instance.Namespace,
},
}
return client.IgnoreNotFound(d.client.Delete(ctx, connectionSecret))
}

// removeFinalizer removes the finalizer from the PostgreSQL CRD
func (d *DeleteStandalonePipeline) removeFinalizer(ctx context.Context) error {
if controllerutil.RemoveFinalizer(d.instance, finalizer) {
return d.client.Update(ctx, d.instance)
}
return nil
}

// isHelmReleaseDeleted checks whether the Release was completely deleted
func (d *DeleteStandalonePipeline) isHelmReleaseDeleted(_ context.Context) bool {
return d.helmReleaseDeleted
}
Loading

0 comments on commit e0c201c

Please sign in to comment.