forked from UrielCohen456/argocd-executor-plugin
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdiff.go
More file actions
192 lines (177 loc) · 6.02 KB
/
diff.go
File metadata and controls
192 lines (177 loc) · 6.02 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
package argocd
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"github.com/argoproj/argo-cd/v2/controller"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/settings"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/io"
"github.com/argoproj/gitops-engine/pkg/sync/hook"
"github.com/argoproj/gitops-engine/pkg/sync/ignore"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// Most of this is copied from the Argo CD CLI code. We should refactor this to be shared.
// https://github.com/argoproj/argo-cd/blob/master/cmd/argocd/commands/app.go
type objKeyLiveTarget struct {
key kube.ResourceKey
live *unstructured.Unstructured
target *unstructured.Unstructured
}
type resourceInfoProvider struct {
namespacedByGk map[schema.GroupKind]bool
}
// Infer if obj is namespaced or not from corresponding live objects list. If corresponding live object has namespace then target object is also namespaced.
// If live object is missing then it does not matter if target is namespaced or not.
func (p *resourceInfoProvider) IsNamespaced(gk schema.GroupKind) (bool, error) {
return p.namespacedByGk[gk], nil
}
// liveObjects deserializes the list of live states into unstructured objects
func liveObjects(resources []*v1alpha1.ResourceDiff) ([]*unstructured.Unstructured, error) {
objs := make([]*unstructured.Unstructured, len(resources))
for i, resState := range resources {
obj, err := resState.LiveObject()
if err != nil {
return nil, err
}
objs[i] = obj
}
return objs, nil
}
func getRefreshType(refresh bool, hardRefresh bool) *string {
if hardRefresh {
refreshType := string(v1alpha1.RefreshTypeHard)
return &refreshType
}
if refresh {
refreshType := string(v1alpha1.RefreshTypeNormal)
return &refreshType
}
return nil
}
func groupObjsByKey(localObs []*unstructured.Unstructured, liveObjs []*unstructured.Unstructured, appNamespace string) (map[kube.ResourceKey]*unstructured.Unstructured, error) {
namespacedByGk := make(map[schema.GroupKind]bool)
for i := range liveObjs {
if liveObjs[i] != nil {
key := kube.GetResourceKey(liveObjs[i])
namespacedByGk[schema.GroupKind{Group: key.Group, Kind: key.Kind}] = key.Namespace != ""
}
}
localObs, _, err := controller.DeduplicateTargetObjects(appNamespace, localObs, &resourceInfoProvider{namespacedByGk: namespacedByGk})
if err != nil {
return nil, fmt.Errorf("failed to deduplicate target objects: %w", err)
}
objByKey := make(map[kube.ResourceKey]*unstructured.Unstructured)
for i := range localObs {
obj := localObs[i]
if !(hook.IsHook(obj) || ignore.Ignore(obj)) {
objByKey[kube.GetResourceKey(obj)] = obj
}
}
return objByKey, nil
}
func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[kube.ResourceKey]*unstructured.Unstructured, items []objKeyLiveTarget, argoSettings *settings.Settings, appName string) ([]objKeyLiveTarget, error) {
resourceTracking := argo.NewResourceTracking()
for _, res := range resources.Items {
var live = &unstructured.Unstructured{}
err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal normalized live state: %w", err)
}
key := kube.ResourceKey{Name: res.Name, Namespace: res.Namespace, Group: res.Group, Kind: res.Kind}
if key.Kind == kube.SecretKind && key.Group == "" {
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
delete(objs, key)
continue
}
if local, ok := objs[key]; ok || live != nil {
if local != nil && !kube.IsCRD(local) {
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, "", v1alpha1.TrackingMethod(argoSettings.GetTrackingMethod()))
if err != nil {
return nil, fmt.Errorf("failed to set app instance: %w", err)
}
}
items = append(items, objKeyLiveTarget{key, live, local})
delete(objs, key)
}
}
for key, local := range objs {
if key.Kind == kube.SecretKind && key.Group == "" {
// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
delete(objs, key)
continue
}
items = append(items, objKeyLiveTarget{key, nil, local})
}
return items, nil
}
// GetDiff gets a diff between two unstructured objects to stdout using an external diff utility
func GetDiff(live *unstructured.Unstructured, target *unstructured.Unstructured, diffOptions DiffOptions) (string, error) {
tempDir, err := os.MkdirTemp("", "argocd-diff")
if err != nil {
return "", err
}
defer func() {
err = os.RemoveAll(tempDir)
if err != nil {
fmt.Printf("Failed to delete temp dir %s: %v\n", tempDir, err)
}
}()
targetFile, err := os.CreateTemp(tempDir, "target")
if err != nil {
return "", err
}
defer io.Close(targetFile)
targetData := []byte("")
if target != nil {
targetData, err = yaml.Marshal(target)
if err != nil {
return "", err
}
}
_, err = targetFile.Write(targetData)
if err != nil {
return "", err
}
liveFile, err := os.CreateTemp(tempDir, "live")
if err != nil {
return "", err
}
defer io.Close(liveFile)
liveData := []byte("")
if live != nil {
liveData, err = yaml.Marshal(live)
if err != nil {
return "", err
}
}
_, err = liveFile.Write(liveData)
if err != nil {
return "", err
}
args := []string{liveFile.Name(), targetFile.Name()}
if diffOptions.CopiedContextLines != "" {
args = append(args, "--context="+diffOptions.CopiedContextLines)
}
if diffOptions.IgnoreBlankLines {
args = append(args, "--ignore-blank-lines=")
}
cmd := exec.Command("diff", args...)
out, err := cmd.Output()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if exitError.ExitCode() == 1 {
return string(out), nil
}
return "", fmt.Errorf("diff command failed with exit code %d: %v", exitError.ExitCode(), err)
}
return "", fmt.Errorf("diff command failed: %v", err)
}
return "", nil
}