/
tf_controller_finalizer.go
205 lines (180 loc) · 7.41 KB
/
tf_controller_finalizer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package controllers
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
"github.com/fluxcd/pkg/runtime/logger"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
infrav1 "github.com/weaveworks/tf-controller/api/v1alpha2"
"github.com/weaveworks/tf-controller/runner"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/types"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
func (r *TerraformReconciler) finalize(ctx context.Context, terraform infrav1.Terraform, runnerClient runner.RunnerClient, sourceObj sourcev1.Source, reconciliationLoopID string) (infrav1.Terraform, controllerruntime.Result, error) {
log := controllerruntime.LoggerFrom(ctx)
traceLog := log.V(logger.TraceLevel).WithValues("function", "TerraformReconciler.finalize")
objectKey := types.NamespacedName{Namespace: terraform.Namespace, Name: terraform.Name}
// TODO how to completely delete without planning?
traceLog.Info("Check if we need to Destroy on Delete")
if terraform.Spec.DestroyResourcesOnDeletion {
for _, finalizer := range terraform.GetFinalizers() {
if strings.HasPrefix(finalizer, infrav1.TFDependencyOfPrefix) {
log.Info("waiting for a dependant to be deleted", "dependant", finalizer)
msg := fmt.Sprintf("waiting for a dependant to be deleted: %s", strings.TrimPrefix(finalizer, infrav1.TFDependencyOfPrefix))
terraform = infrav1.TerraformNotReady(terraform, "", infrav1.DeletionBlockedByDependants, msg)
if err := r.patchStatus(ctx, objectKey, terraform.Status); err != nil {
log.Error(err, "unable to update status for source not found")
return terraform, controllerruntime.Result{Requeue: true}, nil
}
return terraform, controllerruntime.Result{Requeue: true}, nil
}
}
// TODO There's a case of sourceObj got deleted before finalize is called.
revision := sourceObj.GetArtifact().Revision
traceLog.Info("Setup the terraform instance")
terraform, tfInstance, tmpDir, err := r.setupTerraform(ctx, runnerClient, terraform, sourceObj, revision, objectKey, reconciliationLoopID)
traceLog.Info("Defer function for cleanup")
defer func() {
traceLog.Info("Run CleanupDir")
cleanupDirReply, err := runnerClient.CleanupDir(ctx, &runner.CleanupDirRequest{TmpDir: tmpDir})
traceLog.Info("Check for error")
if err != nil {
log.Error(err, "clean up error")
}
traceLog.Info("Check for cleanupDirReply")
if cleanupDirReply != nil {
log.Info(fmt.Sprintf("clean up dir: %s", cleanupDirReply.Message))
}
}()
traceLog.Info("Check for error")
if err != nil {
traceLog.Error(err, "Error, requeue job")
return terraform, controllerruntime.Result{Requeue: true}, err
}
// This will create the "destroy" plan because deletion timestamp is set.
traceLog.Info("Create a new plan to destroy")
terraform, err = r.plan(ctx, terraform, tfInstance, runnerClient, revision)
traceLog.Info("Check for error")
if err != nil {
traceLog.Error(err, "Error, requeue job")
return terraform, controllerruntime.Result{Requeue: true}, err
}
traceLog.Info("Patch status of the Terraform resource")
if err := r.patchStatus(ctx, objectKey, terraform.Status); err != nil {
log.Error(err, "unable to update status after planing")
return terraform, controllerruntime.Result{Requeue: true}, err
}
if thereIsNothingToDestroy(terraform) == false {
traceLog.Info("Apply the destroy plan")
terraform, err = r.apply(ctx, terraform, tfInstance, runnerClient, revision)
traceLog.Info("Check for error")
if err != nil {
traceLog.Error(err, "Error, requeue job")
return terraform, controllerruntime.Result{Requeue: true}, err
}
traceLog.Info("Patch status of the Terraform resource")
if err := r.patchStatus(ctx, objectKey, terraform.Status); err != nil {
log.Error(err, "unable to update status after applying")
return terraform, controllerruntime.Result{Requeue: true}, err
}
traceLog.Info("Check for a nil error")
if err == nil {
log.Info("finalizing destroyResourcesOnDeletion: ok")
}
}
}
traceLog.Info("Check if we are writing output to secrets")
outputSecretName := ""
hasSpecifiedOutputSecret := terraform.Spec.WriteOutputsToSecret != nil && terraform.Spec.WriteOutputsToSecret.Name != ""
if hasSpecifiedOutputSecret {
traceLog.Info("Get the name of the output secret")
outputSecretName = terraform.Spec.WriteOutputsToSecret.Name
}
traceLog.Info("Finalize the secrets")
finalizeSecretsReply, err := runnerClient.FinalizeSecrets(ctx, &runner.FinalizeSecretsRequest{
Namespace: terraform.Namespace,
Name: terraform.Name,
Workspace: terraform.WorkspaceName(),
HasSpecifiedOutputSecret: hasSpecifiedOutputSecret,
OutputSecretName: outputSecretName,
})
traceLog.Info("Check for an error")
if err != nil {
traceLog.Info("Try getting a status from the error")
if e, ok := status.FromError(err); ok {
switch e.Code() {
case codes.Internal:
// transient error
traceLog.Info("Internal error, transient, requeue")
return terraform, controllerruntime.Result{Requeue: true}, err
case codes.NotFound:
// do nothing, fall through
traceLog.Info("Not found, do nothing, fall through")
}
}
}
traceLog.Info("Check for an error")
if err == nil {
log.Info(fmt.Sprintf("finalizing secrets: %s", finalizeSecretsReply.Message))
}
// Record deleted status
traceLog.Info("Record the deleted status")
r.recordReadinessMetric(ctx, terraform)
traceLog.Info("Get the Terraform resource")
if err := r.Get(ctx, objectKey, &terraform); err != nil {
traceLog.Error(err, "Hit an error, return")
return terraform, controllerruntime.Result{}, err
}
// Remove our finalizer from the list and update it
traceLog.Info("Remove the finalizer")
controllerutil.RemoveFinalizer(&terraform, infrav1.TerraformFinalizer)
traceLog.Info("Check for an error")
if err := r.Update(ctx, &terraform); err != nil {
traceLog.Error(err, "Hit an error, return")
return terraform, controllerruntime.Result{}, err
}
// Remove the dependant finalizer from every dependency
dependantFinalizer := infrav1.TFDependencyOfPrefix + terraform.GetName()
for _, d := range terraform.Spec.DependsOn {
if d.Namespace == "" {
d.Namespace = terraform.GetNamespace()
}
dName := types.NamespacedName{
Namespace: d.Namespace,
Name: d.Name,
}
var tf infrav1.Terraform
err := r.Get(context.Background(), dName, &tf)
if err != nil {
return terraform, controllerruntime.Result{}, err
}
// add finalizer to the dependency
if controllerutil.ContainsFinalizer(&tf, dependantFinalizer) {
controllerutil.RemoveFinalizer(&tf, dependantFinalizer)
if err := r.Update(context.Background(), &tf, client.FieldOwner(r.statusManager)); err != nil {
return terraform, controllerruntime.Result{}, err
}
}
}
// Stop reconciliation as the object is being deleted
traceLog.Info("Return success")
return terraform, controllerruntime.Result{}, nil
}
func thereIsNothingToDestroy(terraform infrav1.Terraform) bool {
// find condition with type "Plan"
for _, c := range terraform.Status.Conditions {
if c.Type == infrav1.ConditionTypePlan {
if c.Status == metav1.ConditionFalse &&
c.Reason == infrav1.PlannedNoChangesReason &&
c.Message == "No objects need to be destroyed" {
return true
}
}
}
return false
}