-
Notifications
You must be signed in to change notification settings - Fork 155
/
Copy pathtf_controller_drift_detect.go
146 lines (122 loc) · 4.6 KB
/
tf_controller_drift_detect.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
package controllers
import (
"context"
"fmt"
"strings"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
infrav1 "github.com/flux-iac/tofu-controller/api/v1alpha2"
"github.com/flux-iac/tofu-controller/runner"
"google.golang.org/grpc/status"
ctrl "sigs.k8s.io/controller-runtime"
)
func (r *TerraformReconciler) shouldDetectDrift(terraform infrav1.Terraform, revision string) bool {
// Please do not optimize this logic, as we'd like others to easily understand the logics behind this behaviour.
// return false when drift detection is disabled
if terraform.Spec.DisableDriftDetection == true {
return false
}
// not support when Destroy == true
if terraform.Spec.Destroy == true {
return false
}
// new object
if terraform.Status.LastAppliedRevision == "" &&
terraform.Status.LastPlannedRevision == "" &&
terraform.Status.LastAttemptedRevision == "" {
return false
}
if terraform.Spec.ApprovePlan == infrav1.ApprovePlanDisableValue {
return true
}
// thing worked normally, no change pending
// then, we do drift detection
if terraform.Status.LastAttemptedRevision == terraform.Status.LastAppliedRevision &&
terraform.Status.LastAttemptedRevision == terraform.Status.LastPlannedRevision &&
terraform.Status.LastAttemptedRevision == revision &&
terraform.Status.Plan.Pending == "" {
return true
}
// last time source changed with non-TF file, so we planned but no changes
// this time, it needs drift detection
if terraform.Status.LastAttemptedRevision == terraform.Status.LastPlannedRevision &&
terraform.Status.LastAttemptedRevision == revision &&
terraform.Status.Plan.Pending == "" {
return true
}
return false
}
func (r *TerraformReconciler) detectDrift(ctx context.Context, terraform infrav1.Terraform, tfInstance string, runnerClient runner.RunnerClient, revision string, sourceRefRootDir string) (infrav1.Terraform, error) {
log := ctrl.LoggerFrom(ctx)
log.Info("calling detectDrift ...")
const (
driftFilename = "tfdrift"
)
planRequest := &runner.PlanRequest{
TfInstance: tfInstance,
Out: driftFilename,
Refresh: true,
Targets: terraform.Spec.Targets,
SourceRefRootDir: sourceRefRootDir,
}
if r.backendCompletelyDisable(terraform) {
planRequest.Out = ""
planRequest.Refresh = true
}
eventSent := false
planReply, err := runnerClient.Plan(ctx, planRequest)
if err != nil {
if st, ok := status.FromError(err); ok {
for _, detail := range st.Details() {
if reply, ok := detail.(*runner.PlanReply); ok {
msg := fmt.Sprintf("Drift detection error: State locked with Lock Identifier %s", reply.StateLockIdentifier)
r.event(ctx, terraform, revision, eventv1.EventSeverityError, msg, nil)
eventSent = true
terraform = infrav1.TerraformStateLocked(terraform, reply.StateLockIdentifier, fmt.Sprintf("Terraform Locked with Lock Identifier: %s", reply.StateLockIdentifier))
}
}
}
if eventSent == false {
msg := fmt.Sprintf("Drift detection error: %s", err.Error())
r.event(ctx, terraform, revision, eventv1.EventSeverityError, msg, nil)
}
err = fmt.Errorf("error running Plan: %s", err)
return infrav1.TerraformNotReady(
terraform,
revision,
infrav1.DriftDetectionFailedReason,
err.Error(),
), err
}
drifted := planReply.Drifted
log.Info(fmt.Sprintf("plan for drift: %s found drift: %v", planReply.Message, planReply.Drifted))
if drifted {
var rawOutput string
if r.backendCompletelyDisable(terraform) {
rawOutput = "not available"
} else {
showPlanFileRawReply, err := runnerClient.ShowPlanFileRaw(ctx, &runner.ShowPlanFileRawRequest{
TfInstance: tfInstance,
Filename: driftFilename,
})
if err != nil {
return infrav1.TerraformNotReady(
terraform,
revision,
infrav1.DriftDetectionFailedReason,
err.Error(),
), err
}
rawOutput = showPlanFileRawReply.RawOutput
log.Info(fmt.Sprintf("show plan: %s", showPlanFileRawReply.RawOutput))
}
// Clean up the message for Terraform v1.1.9.
rawOutput = strings.Replace(rawOutput, "You can apply this plan to save these new output values to the Terraform\nstate, without changing any real infrastructure.", "", 1)
msg := fmt.Sprintf("Drift detected.\n%s", rawOutput)
r.event(ctx, terraform, revision, eventv1.EventSeverityError, msg, nil)
// If drift detected & we use the auto mode, then we continue
terraform = infrav1.TerraformDriftDetected(terraform, revision, infrav1.DriftDetectedReason, rawOutput)
return terraform, fmt.Errorf(infrav1.DriftDetectedReason)
}
terraform = infrav1.TerraformNoDrift(terraform, revision, infrav1.NoDriftReason, "No drift")
return terraform, nil
}