-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgitops_status.go
More file actions
229 lines (200 loc) · 6.83 KB
/
Copy pathgitops_status.go
File metadata and controls
229 lines (200 loc) · 6.83 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package main
import (
"fmt"
"time"
log "github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// updateGitOpsState updates the state of a GitOps resource
func updateGitOpsState(key string, hasError bool, errorMessage, repositoryName, resourceKind, resourceName, namespace, mismatchType, expectedHash, actualHash string) {
gitOpsStatesLock.Lock()
defer gitOpsStatesLock.Unlock()
now := time.Now()
prevState, exists := gitOpsStates[key]
newState := gitOpsState{
unitState: unitState{
hasError: hasError,
lastSeen: now,
lastMessage: errorMessage,
},
repositoryName: repositoryName,
resourceKind: resourceKind,
resourceName: resourceName,
namespace: namespace,
mismatchType: mismatchType,
expectedHash: expectedHash,
actualHash: actualHash,
}
// If this is a new error or the error has changed, reset the alert state
if !exists || (!prevState.hasError && hasError) || (prevState.hasError && prevState.lastMessage != errorMessage) {
newState.firstError = now
newState.alertSent = false
} else if exists && prevState.hasError {
// Keep the original error time and alert state
newState.firstError = prevState.firstError
newState.alertSent = prevState.alertSent
}
gitOpsStates[key] = newState
log.Debug().
Str("key", key).
Bool("hasError", hasError).
Str("message", errorMessage).
Msg("Updated GitOps state")
}
// shouldSendGitOpsAlert checks if we should send an alert for a GitOps resource
func shouldSendGitOpsAlert(key string) bool {
gitOpsStatesLock.RLock()
state, exists := gitOpsStates[key]
gitOpsStatesLock.RUnlock()
if !exists || !state.hasError || state.alertSent {
return false
}
// Check if alerting is enabled globally and for this repository
if !config.GitOps.AlertOnMismatch {
return false
}
// Check repository-specific alert setting
for _, repo := range config.GitOps.Repositories {
if repo.Name == state.repositoryName && !repo.AlertOnMismatch {
return false
}
}
// If interval is 0, send alert immediately
if config.Interval == 0 {
return true
}
// Check if enough time has passed since the error was first seen
intervalDuration := time.Duration(config.Interval) * time.Minute
return time.Since(state.firstError) >= intervalDuration
}
// markGitOpsAlertSent marks an alert as sent for a GitOps resource
func markGitOpsAlertSent(key string) {
gitOpsStatesLock.Lock()
defer gitOpsStatesLock.Unlock()
if state, exists := gitOpsStates[key]; exists {
state.alertSent = true
gitOpsStates[key] = state
}
}
// sendGitOpsMismatchAlert sends an alert for a GitOps mismatch
func sendGitOpsMismatchAlert(repositoryName string, expected, actual *unstructured.Unstructured, mismatchType string) {
var title, description string
var resourceName, resourceKind, namespace string
if expected != nil {
resourceName = expected.GetName()
resourceKind = expected.GetKind()
namespace = expected.GetNamespace()
} else if actual != nil {
resourceName = actual.GetName()
resourceKind = actual.GetKind()
namespace = actual.GetNamespace()
}
switch mismatchType {
case "missing":
title = fmt.Sprintf("GitOps Alert: Missing Resource in %s", repositoryName)
description = fmt.Sprintf("Resource %s/%s is defined in Git but missing from cluster", resourceKind, resourceName)
case "different":
title = fmt.Sprintf("GitOps Alert: Resource Drift in %s", repositoryName)
description = fmt.Sprintf("Resource %s/%s differs between Git and cluster", resourceKind, resourceName)
case "extra":
title = fmt.Sprintf("GitOps Alert: Extra Resource in %s", repositoryName)
description = fmt.Sprintf("Resource %s/%s exists in cluster but not in Git", resourceKind, resourceName)
default:
title = fmt.Sprintf("GitOps Alert: Unknown Issue in %s", repositoryName)
description = fmt.Sprintf("Unknown mismatch type %s for resource %s/%s", mismatchType, resourceKind, resourceName)
}
alert := Alert{
Title: title,
Description: description,
Fields: []struct {
Name string
Value string
Inline bool
}{
{Name: "Repository", Value: repositoryName, Inline: true},
{Name: "Resource Kind", Value: resourceKind, Inline: true},
{Name: "Resource Name", Value: resourceName, Inline: true},
},
}
if namespace != "" {
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Namespace", Value: namespace, Inline: true})
}
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Mismatch Type", Value: mismatchType, Inline: true})
// Add additional context based on mismatch type
switch mismatchType {
case "missing":
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Action Required", Value: "Apply the resource to the cluster or remove from Git", Inline: false})
case "different":
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Action Required", Value: "Review differences and either update Git or apply changes to cluster", Inline: false})
case "extra":
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Action Required", Value: "Remove resource from cluster or add to Git repository", Inline: false})
}
sendWebhookMessage(alert)
log.Error().
Str("repository", repositoryName).
Str("kind", resourceKind).
Str("name", resourceName).
Str("namespace", namespace).
Str("mismatchType", mismatchType).
Msg("GitOps mismatch alert sent")
}
// checkGitOpsRecovery checks if a GitOps resource has recovered and sends a recovery alert
func checkGitOpsRecovery(key, repositoryName, resourceKind, resourceName, namespace string) {
gitOpsStatesLock.RLock()
prevState, exists := gitOpsStates[key]
gitOpsStatesLock.RUnlock()
if exists && prevState.hasError && prevState.alertSent {
alert := Alert{
Title: fmt.Sprintf("GitOps Recovery: %s", repositoryName),
Description: fmt.Sprintf("Resource %s/%s is now in sync between Git and cluster", resourceKind, resourceName),
Fields: []struct {
Name string
Value string
Inline bool
}{
{Name: "Repository", Value: repositoryName, Inline: true},
{Name: "Resource Kind", Value: resourceKind, Inline: true},
{Name: "Resource Name", Value: resourceName, Inline: true},
},
}
if namespace != "" {
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Namespace", Value: namespace, Inline: true})
}
alert.Fields = append(alert.Fields, struct {
Name string
Value string
Inline bool
}{Name: "Status", Value: "✅ In Sync", Inline: true})
sendWebhookMessage(alert)
log.Info().
Str("repository", repositoryName).
Str("kind", resourceKind).
Str("name", resourceName).
Str("namespace", namespace).
Msg("GitOps resource has recovered")
}
}