Skip to content

Commit

Permalink
feat(tfrun): wip: create a cleanup routine in layer ctrl
Browse files Browse the repository at this point in the history
  • Loading branch information
corrieriluca committed Sep 21, 2023
1 parent 20ca3cb commit 9ced629
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 0 deletions.
2 changes: 2 additions & 0 deletions internal/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const (
LastCommentedCommit string = "pullrequest.terraform.padok.cloud/last-commented-commit"

AdditionnalTriggerPaths string = "config.terraform.padok.cloud/additionnal-trigger-paths"
PlanRunRetention string = "config.terraform.padok.cloud/plan-run-retention"
ApplyRunRetention string = "config.terraform.padok.cloud/apply-run-retention"
)

func Add(ctx context.Context, c client.Client, obj client.Object, annotations map[string]string) error {
Expand Down
4 changes: 4 additions & 0 deletions internal/controllers/terraformlayer/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
log.Errorf("failed to get TerraformRepository linked to layer %s: %s", layer.Name, err)
return ctrl.Result{RequeueAfter: r.Config.Controller.Timers.OnError}, err
}
err = r.cleanupRuns(ctx, layer, repository)
if err != nil {
log.Warningf("failed to cleanup runs for layer %s: %s", layer.Name, err)
}
state, conditions := r.GetState(ctx, layer)
lastResult, err := r.Storage.Get(storage.GenerateKey(storage.LastPlanResult, layer))
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions internal/controllers/terraformlayer/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ var _ = Describe("Layer", func() {
})
})
})
// TODO: test cleanup of runs
Describe("Cleanup case", func() {

})
})

var _ = AfterSuite(func() {
Expand Down
150 changes: 150 additions & 0 deletions internal/controllers/terraformlayer/run.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package terraformlayer

import (
"context"
"fmt"
"sync"
"time"

configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1"
"github.com/padok-team/burrito/internal/annotations"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type Action string
Expand Down Expand Up @@ -44,3 +52,145 @@ func (r *Reconciler) getRun(layer *configv1alpha1.TerraformLayer, repository *co
},
}
}

func (r *Reconciler) getAllFinishedRuns(ctx context.Context, layer *configv1alpha1.TerraformLayer, repository *configv1alpha1.TerraformRepository) ([]*configv1alpha1.TerraformRun, error) {
list := &configv1alpha1.TerraformRunList{}
labelSelector := labels.NewSelector()
for key, value := range GetDefaultLabels(layer) {
requirement, err := labels.NewRequirement(key, selection.Equals, []string{value})
if err != nil {
return []*configv1alpha1.TerraformRun{}, err
}
labelSelector = labelSelector.Add(*requirement)
}
err := r.Client.List(
ctx,
list,
client.MatchingLabelsSelector{Selector: labelSelector},
&client.ListOptions{Namespace: layer.Namespace},
)
if err != nil {
return []*configv1alpha1.TerraformRun{}, err
}

// Keep only runs with state Succeeded or Failed
var runs []*configv1alpha1.TerraformRun
for _, run := range list.Items {
if run.Status.State == "Succeeded" || run.Status.State == "Failed" {
runs = append(runs, &run)
}
}
return runs, nil
}

type runRetention struct {
plan time.Duration
apply time.Duration
}

func getRetentions(layer *configv1alpha1.TerraformLayer, repository *configv1alpha1.TerraformRepository) (runRetention, error) {
var planRet time.Duration
var applyRet time.Duration
var err error

if ann, ok := layer.Annotations[annotations.PlanRunRetention]; ok {
planRet, err = time.ParseDuration(ann)
} else if ann, ok := repository.Annotations[annotations.PlanRunRetention]; ok {
planRet, err = time.ParseDuration(ann)
} else {
planRet = 0 * time.Second
}

if ann, ok := layer.Annotations[annotations.ApplyRunRetention]; ok {
applyRet, err = time.ParseDuration(ann)
} else if ann, ok := repository.Annotations[annotations.ApplyRunRetention]; ok {
applyRet, err = time.ParseDuration(ann)
} else {
applyRet = 0 * time.Second
}

if err != nil {
return runRetention{}, err
}

return runRetention{
plan: planRet,
apply: applyRet,
}, nil
}

func deleteAll(ctx context.Context, c client.Client, objs []*configv1alpha1.TerraformRun) error {
var wg sync.WaitGroup
errorCh := make(chan error, len(objs))

deleteObject := func(obj *configv1alpha1.TerraformRun) {
defer wg.Done()
err := c.Delete(ctx, obj)
if err != nil {
errorCh <- fmt.Errorf("error deleting %s: %v", obj.Name, err)
} else {
log.Infof("deleted run %s", obj.Name)
}
}

for _, obj := range objs {
wg.Add(1)
go deleteObject(obj)
}

go func() {
wg.Wait()
close(errorCh)
}()

var ret error = nil
for err := range errorCh {
if err != nil {
ret = err
}
}

return ret
}

func (r *Reconciler) cleanupRuns(ctx context.Context, layer *configv1alpha1.TerraformLayer, repository *configv1alpha1.TerraformRepository) error {
retentions, err := getRetentions(layer, repository)
if err != nil {
log.Errorf("could not get run retentions for layer %s: %s", layer.Name, err)
return err
}
if retentions.plan == 0 && retentions.apply == 0 {
// nothing to do
log.Infof("no run retention set for layer %s, skipping cleanup", layer.Name)
return nil
}

runs, err := r.getAllFinishedRuns(ctx, layer, repository)
if err != nil {
log.Errorf("could not get runs for layer %s: %s", layer.Name, err)
return err
}

toDelete := []*configv1alpha1.TerraformRun{}
for _, run := range runs {
if run.Spec.Action == string(PlanAction) &&
retentions.plan != 0 &&
r.Clock.Now().Sub(run.CreationTimestamp.Time) > retentions.plan {
toDelete = append(toDelete, run)
continue
}
if run.Spec.Action == string(ApplyAction) &&
retentions.apply != 0 &&
r.Clock.Now().Sub(run.CreationTimestamp.Time) > retentions.apply {
toDelete = append(toDelete, run)
continue
}
}

err = deleteAll(ctx, r.Client, toDelete)
if err != nil {
return err
}

return nil
}
25 changes: 25 additions & 0 deletions internal/controllers/terraformlayer/testdata/cleanup-case.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: config.terraform.padok.cloud/v1alpha1
kind: TerraformRepository
metadata:
name: cleanup-repo-1
namespace: default
spec:
repository:
url: git@github.com:padok-team/burrito-examples.git
---
apiVersion: config.terraform.padok.cloud/v1alpha1
kind: TerraformLayer
metadata:
name: cleanup-case-1
namespace: default
annotations:
config.terraform.padok.cloud/plan-run-retention: "1h"
config.terraform.padok.cloud/apply-run-retention: "24h"
spec:
branch: main
path: cleanup-case-1/
repository:
name: cleanup-repo-1
namespace: default
terraform:
version: 1.3.1

0 comments on commit 9ced629

Please sign in to comment.