Skip to content

Commit

Permalink
CLOUDP-186825: Add deletion protection to deployments
Browse files Browse the repository at this point in the history
Signed-off-by: Jose Vazquez <jose.vazquez@mongodb.com>
  • Loading branch information
josvazg committed Jul 14, 2023
1 parent 06af725 commit d452d20
Show file tree
Hide file tree
Showing 8 changed files with 637 additions and 70 deletions.
18 changes: 10 additions & 8 deletions cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,16 @@ func main() {
}

if err = (&atlasdeployment.AtlasDeploymentReconciler{
Client: mgr.GetClient(),
Log: logger.Named("controllers").Named("AtlasDeployment").Sugar(),
Scheme: mgr.GetScheme(),
AtlasDomain: config.AtlasDomain,
GlobalAPISecret: config.GlobalAPISecret,
ResourceWatcher: watch.NewResourceWatcher(),
GlobalPredicates: globalPredicates,
EventRecorder: mgr.GetEventRecorderFor("AtlasDeployment"),
Client: mgr.GetClient(),
Log: logger.Named("controllers").Named("AtlasDeployment").Sugar(),
Scheme: mgr.GetScheme(),
AtlasDomain: config.AtlasDomain,
GlobalAPISecret: config.GlobalAPISecret,
ResourceWatcher: watch.NewResourceWatcher(),
GlobalPredicates: globalPredicates,
EventRecorder: mgr.GetEventRecorderFor("AtlasDeployment"),
ObjectDeletionProtection: config.ObjectDeletionProtection,
SubObjectDeletionProtection: config.SubObjectDeletionProtection,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "AtlasDeployment")
os.Exit(1)
Expand Down
136 changes: 81 additions & 55 deletions pkg/controller/atlasdeployment/atlasdeployment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,22 @@ import (
// AtlasDeploymentReconciler reconciles an AtlasDeployment object
type AtlasDeploymentReconciler struct {
watch.ResourceWatcher
Client client.Client
Log *zap.SugaredLogger
Scheme *runtime.Scheme
AtlasDomain string
GlobalAPISecret client.ObjectKey
GlobalPredicates []predicate.Predicate
EventRecorder record.EventRecorder
Client client.Client
Log *zap.SugaredLogger
Scheme *runtime.Scheme
AtlasDomain string
GlobalAPISecret client.ObjectKey
GlobalPredicates []predicate.Predicate
EventRecorder record.EventRecorder
ObjectDeletionProtection bool
SubObjectDeletionProtection bool
}

type reconcilerData struct {
log *zap.SugaredLogger
context context.Context
workflowCtx *workflow.Context
prevResult workflow.Result
}

// +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasdeployments,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -80,65 +89,70 @@ type AtlasDeploymentReconciler struct {
// +kubebuilder:rbac:groups="",namespace=default,resources=events,verbs=create;patch

func (r *AtlasDeploymentReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.With("atlasdeployment", req.NamespacedName)
rd := reconcilerData{}
rd.context = context
rd.log = r.Log.With("atlasdeployment", req.NamespacedName)

deployment := &mdbv1.AtlasDeployment{}
result := customresource.PrepareResource(r.Client, req, deployment, log)
if !result.IsOk() {
return result.ReconcileResult(), nil
{
result := customresource.PrepareResource(r.Client, req, deployment, rd.log)
if !result.IsOk() {
return result.ReconcileResult(), nil
}
rd.prevResult = result
}

if shouldSkip := customresource.ReconciliationShouldBeSkipped(deployment); shouldSkip {
log.Infow(fmt.Sprintf("-> Skipping AtlasDeployment reconciliation as annotation %s=%s", customresource.ReconciliationPolicyAnnotation, customresource.ReconciliationPolicySkip), "spec", deployment.Spec)
rd.log.Infow(fmt.Sprintf("-> Skipping AtlasDeployment reconciliation as annotation %s=%s", customresource.ReconciliationPolicyAnnotation, customresource.ReconciliationPolicySkip), "spec", deployment.Spec)
if !deployment.GetDeletionTimestamp().IsZero() {
err := r.removeDeletionFinalizer(context, deployment)
if err != nil {
result = workflow.Terminate(workflow.Internal, err.Error())
log.Errorw("failed to remove finalizer", "error", err)
result := workflow.Terminate(workflow.Internal, err.Error())
rd.log.Errorw("failed to remove finalizer", "error", err)
return result.ReconcileResult(), nil
}
}
return workflow.OK().ReconcileResult(), nil
}

ctx := customresource.MarkReconciliationStarted(r.Client, deployment, log)
log.Infow("-> Starting AtlasDeployment reconciliation", "spec", deployment.Spec, "status", deployment.Status)
defer statushandler.Update(ctx, r.Client, r.EventRecorder, deployment)
rd.workflowCtx = customresource.MarkReconciliationStarted(r.Client, deployment, rd.log)
rd.log.Infow("-> Starting AtlasDeployment reconciliation", "spec", deployment.Spec, "status", deployment.Status)
defer statushandler.Update(rd.workflowCtx, r.Client, r.EventRecorder, deployment)

resourceVersionIsValid := customresource.ValidateResourceVersion(ctx, deployment, r.Log)
resourceVersionIsValid := customresource.ValidateResourceVersion(rd.workflowCtx, deployment, r.Log)
if !resourceVersionIsValid.IsOk() {
r.Log.Debugf("deployment validation result: %v", resourceVersionIsValid)
return resourceVersionIsValid.ReconcileResult(), nil
}

if err := validate.DeploymentSpec(deployment.Spec); err != nil {
result := workflow.Terminate(workflow.Internal, err.Error())
ctx.SetConditionFromResult(status.ValidationSucceeded, result)
rd.workflowCtx.SetConditionFromResult(status.ValidationSucceeded, result)
return result.ReconcileResult(), nil
}
ctx.SetConditionTrue(status.ValidationSucceeded)
rd.workflowCtx.SetConditionTrue(status.ValidationSucceeded)

project := &mdbv1.AtlasProject{}
if result := r.readProjectResource(context, deployment, project); !result.IsOk() {
ctx.SetConditionFromResult(status.DeploymentReadyType, result)
rd.workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}

connection, err := atlas.ReadConnection(log, r.Client, r.GlobalAPISecret, project.ConnectionSecretObjectKey())
connection, err := atlas.ReadConnection(rd.log, r.Client, r.GlobalAPISecret, project.ConnectionSecretObjectKey())
if err != nil {
result := workflow.Terminate(workflow.AtlasCredentialsNotProvided, err.Error())
ctx.SetConditionFromResult(status.DeploymentReadyType, result)
rd.workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}
ctx.Connection = connection
rd.workflowCtx.Connection = connection

atlasClient, err := atlas.Client(r.AtlasDomain, connection, log)
atlasClient, err := atlas.Client(r.AtlasDomain, connection, rd.log)
if err != nil {
result := workflow.Terminate(workflow.Internal, err.Error())
ctx.SetConditionFromResult(status.DeploymentReadyType, result)
rd.workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}
ctx.Client = atlasClient
rd.workflowCtx.Client = atlasClient

// Allow users to specify M0/M2/M5 deployments without providing TENANT for Normal and Serverless deployments
r.verifyNonTenantCase(deployment)
Expand All @@ -147,65 +161,77 @@ func (r *AtlasDeploymentReconciler) Reconcile(context context.Context, req ctrl.
if !customresource.HaveFinalizer(deployment, customresource.FinalizerLabel) {
err = r.Client.Get(context, kube.ObjectKeyFromObject(deployment), deployment)
if err != nil {
result = workflow.Terminate(workflow.Internal, err.Error())
result := workflow.Terminate(workflow.Internal, err.Error())
return result.ReconcileResult(), nil
}
customresource.SetFinalizer(deployment, customresource.FinalizerLabel)
if err = r.Client.Update(context, deployment); err != nil {
result = workflow.Terminate(workflow.Internal, err.Error())
log.Errorw("failed to add finalizer", "error", err)
result := workflow.Terminate(workflow.Internal, err.Error())
rd.log.Errorw("failed to add finalizer", "error", err)
return result.ReconcileResult(), nil
}
}
}

if !deployment.GetDeletionTimestamp().IsZero() {
if customresource.HaveFinalizer(deployment, customresource.FinalizerLabel) {
if customresource.ResourceShouldBeLeftInAtlas(deployment) {
log.Infof("Not removing Atlas Deployment from Atlas as the '%s' annotation is set", customresource.ResourcePolicyAnnotation)
} else {
if err = r.deleteDeploymentFromAtlas(context, project, deployment, atlasClient, log); err != nil {
log.Errorf("failed to remove deployment from Atlas: %s", err)
result = workflow.Terminate(workflow.Internal, err.Error())
ctx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}
}
err = r.removeDeletionFinalizer(context, deployment)
if err != nil {
result = workflow.Terminate(workflow.Internal, err.Error())
log.Errorw("failed to remove finalizer", "error", err)
return result.ReconcileResult(), nil
}
}
return result.ReconcileResult(), nil
return r.checkAndHandleRemoval(&rd, project, deployment)
}

if deployment.IsLegacyDeployment() {
if err := ConvertLegacyDeployment(&deployment.Spec); err != nil {
result = workflow.Terminate(workflow.Internal, err.Error())
log.Errorw("failed to convert legacy deployment", "error", err)
result := workflow.Terminate(workflow.Internal, err.Error())
rd.log.Errorw("failed to convert legacy deployment", "error", err)
return result.ReconcileResult(), nil
}
deployment.Spec.DeploymentSpec = nil
}

handleDeployment := r.selectDeploymentHandler(deployment)
if result, _ := handleDeployment(ctx, project, deployment, req); !result.IsOk() {
ctx.SetConditionFromResult(status.DeploymentReadyType, result)
if result, _ := handleDeployment(rd.workflowCtx, project, deployment, req); !result.IsOk() {
rd.workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}

if !deployment.IsServerless() {
if result := r.handleAdvancedOptions(ctx, project, deployment); !result.IsOk() {
ctx.SetConditionFromResult(status.DeploymentReadyType, result)
if result := r.handleAdvancedOptions(rd.workflowCtx, project, deployment); !result.IsOk() {
rd.workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}
}

return workflow.OK().ReconcileResult(), nil
}

func (r *AtlasDeploymentReconciler) checkAndHandleRemoval(rd *reconcilerData, project *mdbv1.AtlasProject, deployment *mdbv1.AtlasDeployment) (reconcile.Result, error) {
atlasClient := rd.workflowCtx.Client
if customresource.HaveFinalizer(deployment, customresource.FinalizerLabel) {
isProtected := customresource.IsResourceProtected(deployment, r.ObjectDeletionProtection)
rd.log.Infow("RESOURCE PROTECTED",
"reconciler-object-deletion-protection", r.ObjectDeletionProtection, "protected", isProtected)
if isProtected {
rd.log.Info("Not removing Atlas deployment from Atlas as per configuration")
} else {
if customresource.ResourceShouldBeLeftInAtlas(deployment) {
rd.log.Infof("Not removing Atlas Deployment from Atlas as the '%s' annotation is set", customresource.ResourcePolicyAnnotation)
} else {
if err := r.deleteDeploymentFromAtlas(rd.context, project, deployment, atlasClient, rd.log); err != nil {
rd.log.Errorf("failed to remove deployment from Atlas: %s", err)
result := workflow.Terminate(workflow.Internal, err.Error())
rd.workflowCtx.SetConditionFromResult(status.DeploymentReadyType, result)
return result.ReconcileResult(), nil
}
}
}
err := customresource.ManageFinalizer(rd.context, r.Client, deployment, customresource.UnsetFinalizer)
if err != nil {
result := workflow.Terminate(workflow.Internal, err.Error())
rd.log.Errorw("failed to remove finalizer", "error", err)
return result.ReconcileResult(), nil
}
}
return rd.prevResult.ReconcileResult(), nil
}

func (r *AtlasDeploymentReconciler) verifyNonTenantCase(deployment *mdbv1.AtlasDeployment) {
var pSettings *mdbv1.ProviderSettingsSpec
var deploymentType string
Expand Down
Loading

0 comments on commit d452d20

Please sign in to comment.