forked from jcrossley3/manifestival
/
manifestival.go
177 lines (162 loc) · 5.34 KB
/
manifestival.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
package manifestival
import (
"fmt"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/klog"
)
type Manifest interface {
// Either updates or creates all resources in the manifest
ApplyAll() error
// Updates or creates a particular resource
Apply(*unstructured.Unstructured) error
// Deletes all resources in the manifest
DeleteAll() error
// Deletes a particular resource
Delete(spec *unstructured.Unstructured) error
// Retains every resource for which all FilterFn's return true
Filter(fns ...FilterFn) Manifest
// Returns a deep copy of the matching resource read from the file
Find(apiVersion string, kind string, name string) *unstructured.Unstructured
// Returns the resource fetched from the api server, nil if not found
Get(spec *unstructured.Unstructured) (*unstructured.Unstructured, error)
// Returns a deep copy of all resources in the manifest
DeepCopyResources() []unstructured.Unstructured
// Convenient list of all the resource names in the manifest
ResourceNames() []string
}
type YamlManifest struct {
client dynamic.Interface
resources []unstructured.Unstructured
}
var _ Manifest = &YamlManifest{}
func NewYamlManifest(pathname string, recursive bool, client dynamic.Interface) (Manifest, error) {
klog.Info("Reading YAML file", "name", pathname)
resources, err := Parse(pathname, recursive)
if err != nil {
return nil, err
}
return &YamlManifest{resources: resources, client: client}, nil
}
func (f *YamlManifest) ApplyAll() error {
for _, spec := range f.resources {
if err := f.Apply(&spec); err != nil {
return err
}
}
return nil
}
func (f *YamlManifest) Apply(spec *unstructured.Unstructured) error {
current, err := f.Get(spec)
if err != nil {
return err
}
if current == nil {
klog.Info("Creating", "type", spec.GroupVersionKind(), "name", spec.GetName())
gvr, _ := meta.UnsafeGuessKindToResource(spec.GroupVersionKind())
if _, err := f.client.Resource(gvr).Namespace(spec.GetNamespace()).Create(spec, v1.CreateOptions{}); err != nil {
return err
}
} else {
// Update existing one
if UpdateChanged(spec.UnstructuredContent(), current.UnstructuredContent()) {
klog.Info("Updating", "type", spec.GroupVersionKind(), "name", spec.GetName())
gvr, _ := meta.UnsafeGuessKindToResource(spec.GroupVersionKind())
if _, err = f.client.Resource(gvr).Namespace(current.GetNamespace()).Update(current, v1.UpdateOptions{}); err != nil {
return err
}
}
}
return nil
}
func (f *YamlManifest) DeleteAll() error {
a := make([]unstructured.Unstructured, len(f.resources))
copy(a, f.resources)
// we want to delete in reverse order
for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {
a[left], a[right] = a[right], a[left]
}
for _, spec := range a {
if err := f.Delete(&spec); err != nil {
return err
}
}
return nil
}
func (f *YamlManifest) Delete(spec *unstructured.Unstructured) error {
current, err := f.Get(spec)
if current == nil && err == nil {
return nil
}
klog.Info("Deleting", "type", spec.GroupVersionKind(), "name", spec.GetName())
gvr, _ := meta.UnsafeGuessKindToResource(spec.GroupVersionKind())
if err := f.client.Resource(gvr).Namespace(spec.GetNamespace()).Delete(spec.GetName(), &v1.DeleteOptions{}); err != nil {
// ignore GC race conditions triggered by owner references
if !errors.IsNotFound(err) {
return err
}
}
return nil
}
func (f *YamlManifest) Get(spec *unstructured.Unstructured) (*unstructured.Unstructured, error) {
gvr, _ := meta.UnsafeGuessKindToResource(spec.GroupVersionKind())
result, err := f.client.Resource(gvr).Namespace(spec.GetNamespace()).Get(spec.GetName(), v1.GetOptions{})
if err != nil {
result = nil
if errors.IsNotFound(err) {
err = nil
}
}
return result, err
}
func (f *YamlManifest) Find(apiVersion string, kind string, name string) *unstructured.Unstructured {
for _, spec := range f.resources {
if spec.GetAPIVersion() == apiVersion &&
spec.GetKind() == kind &&
spec.GetName() == name {
return spec.DeepCopy()
}
}
return nil
}
func (f *YamlManifest) DeepCopyResources() []unstructured.Unstructured {
result := make([]unstructured.Unstructured, len(f.resources))
for i, spec := range f.resources {
result[i] = *spec.DeepCopy()
}
return result
}
func (f *YamlManifest) ResourceNames() []string {
var names []string
for _, spec := range f.resources {
names = append(names, fmt.Sprintf("%s/%s (%s)", spec.GetNamespace(), spec.GetName(), spec.GroupVersionKind()))
}
return names
}
// We need to preserve the top-level target keys, specifically
// 'metadata.resourceVersion', 'spec.clusterIP', and any existing
// entries in a ConfigMap's 'data' field. So we only overwrite fields
// set in our src resource.
func UpdateChanged(src, tgt map[string]interface{}) bool {
changed := false
for k, v := range src {
if v, ok := v.(map[string]interface{}); ok {
if tgt[k] == nil {
tgt[k], changed = v, true
} else if UpdateChanged(v, tgt[k].(map[string]interface{})) {
// This could be an issue if a field in a nested src
// map doesn't overwrite its corresponding tgt
changed = true
}
continue
}
if !equality.Semantic.DeepEqual(v, tgt[k]) {
tgt[k], changed = v, true
}
}
return changed
}