1 package snapshot
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7
8 crdAll "github.com/datawire/ambassador/v2/pkg/api/getambassador.io"
9 crdCurrent "github.com/datawire/ambassador/v2/pkg/api/getambassador.io/v3alpha1"
10 "github.com/datawire/ambassador/v2/pkg/kates"
11 "github.com/datawire/dlib/derror"
12 )
13
14 func annotationKey(obj kates.Object) string {
15 return fmt.Sprintf("%s/%s.%s",
16 obj.GetObjectKind().GroupVersionKind().Kind,
17 obj.GetName(),
18 obj.GetNamespace())
19 }
20
21 var (
22 scheme = crdAll.BuildScheme()
23 validator = crdAll.NewValidator()
24 )
25
26 func (s *KubernetesSnapshot) PopulateAnnotations(ctx context.Context) error {
27 var annotatable []kates.Object
28 for _, svc := range s.Services {
29 annotatable = append(annotatable, svc)
30 }
31 for _, ing := range s.Ingresses {
32 annotatable = append(annotatable, ing)
33 }
34
35 s.Annotations = make(map[string]AnnotationList)
36 var errs derror.MultiError
37 for _, r := range annotatable {
38 key := annotationKey(r)
39 objs, err := ParseAnnotationResources(r)
40 if err != nil {
41 errs = append(errs, fmt.Errorf("%s: %w", key, err))
42 continue
43 }
44 annotations := make(AnnotationList, len(objs))
45 for i, untypedObj := range objs {
46 typedObj, err := ValidateAndConvertObject(ctx, untypedObj)
47 if err != nil {
48 untypedObj.Object["errors"] = err.Error()
49 annotations[i] = untypedObj
50 } else {
51 annotations[i] = typedObj
52 }
53 }
54 if len(annotations) > 0 {
55 s.Annotations[key] = annotations
56 }
57 }
58
59 if len(errs) > 0 {
60 return errs
61 }
62 return nil
63 }
64
65
66
67
68
69
70 func ValidateAndConvertObject(
71 ctx context.Context,
72 in *kates.Unstructured,
73 ) (out kates.Object, err error) {
74
75 gvk := in.GetObjectKind().GroupVersionKind()
76 if !scheme.Recognizes(gvk) {
77 return nil, fmt.Errorf("unsupported GroupVersionKind %q, ignoring", gvk)
78 }
79 if err := validator.Validate(ctx, in); err != nil {
80 return nil, err
81 }
82
83
84 out, err = convertAnnotationObject(in)
85 if err != nil {
86 return nil, err
87 }
88
89
90 if err := validator.Validate(ctx, out); err != nil {
91 return nil, err
92 }
93
94 return out, nil
95 }
96
97
98 func convertAnnotationObject(srcUnstruct *kates.Unstructured) (kates.Object, error) {
99
100
101 srcGVK := srcUnstruct.GetObjectKind().GroupVersionKind()
102 _src, err := scheme.ConvertToVersion(srcUnstruct, srcGVK.GroupVersion())
103 if err != nil {
104 return nil, fmt.Errorf("1: %w", err)
105 }
106 src, ok := _src.(kates.Object)
107 if !ok {
108 return nil, fmt.Errorf("type %T doesn't implement kates.Object", _src)
109 }
110
111
112 dstGVK := crdCurrent.GroupVersion.WithKind(srcGVK.Kind)
113 if dstGVK == srcGVK {
114
115 return src, nil
116 }
117 _dst, err := scheme.New(dstGVK)
118 if err != nil {
119 return nil, fmt.Errorf("2: %w", err)
120 }
121 dst, ok := _dst.(kates.Object)
122 if !ok {
123 return nil, fmt.Errorf("type %T doesn't implement kates.Object", _dst)
124 }
125 dst.GetObjectKind().SetGroupVersionKind(dstGVK)
126
127
128
129 if err := kates.ConvertObject(scheme, src, dst); err != nil {
130 return nil, fmt.Errorf("3: %w", err)
131 }
132 return dst, nil
133 }
134
135
136
137
138
139
140 func ParseAnnotationResources(resource kates.Object) ([]*kates.Unstructured, error) {
141 annotationStr, annotationStrOK := resource.GetAnnotations()["getambassador.io/config"]
142 if !annotationStrOK {
143 return nil, nil
144 }
145
146
147 _annotationResources, err := kates.ParseManifestsToUnstructured(annotationStr)
148 if err != nil {
149 return nil, fmt.Errorf("annotation getambassador.io/config: could not parse YAML: %w", err)
150 }
151 annotationResources := make([]*kates.Unstructured, 0, len(_annotationResources))
152 for _, _annotationResource := range _annotationResources {
153 annotationResource := _annotationResource.(*kates.Unstructured).Object
154
155 if _, ok := annotationResource["apiVersion"].(string); !ok {
156 annotationResource["apiVersion"] = ""
157 }
158 if dat, ok := annotationResource["metadata"].(map[string]interface{}); !ok || dat == nil {
159 annotationResource["metadata"] = map[string]interface{}{}
160 }
161 if dat, ok := annotationResource["spec"].(map[string]interface{}); !ok || dat == nil {
162 annotationResource["spec"] = map[string]interface{}{}
163 }
164 for k, v := range annotationResource {
165 switch k {
166 case "apiVersion", "kind", "metadata", "spec", "status":
167
168 case "name", "namespace", "generation":
169 annotationResource["metadata"].(map[string]interface{})[k] = v
170 delete(annotationResource, k)
171 case "metadata_labels":
172 annotationResource["metadata"].(map[string]interface{})["labels"] = v
173 delete(annotationResource, k)
174 default:
175 annotationResource["spec"].(map[string]interface{})[k] = v
176 delete(annotationResource, k)
177 }
178 }
179
180
181 if ns, ok := annotationResource["metadata"].(map[string]interface{})["namespace"].(string); !ok || ns == "" {
182 annotationResource["metadata"].(map[string]interface{})["namespace"] = resource.GetNamespace()
183 }
184 if annotationResource["metadata"].(map[string]interface{})["labels"] == nil && resource.GetLabels() != nil {
185 annotationResource["metadata"].(map[string]interface{})["labels"] = resource.GetLabels()
186 }
187
188
189
190
191 if apiVersion := annotationResource["apiVersion"].(string); strings.HasPrefix(apiVersion, "ambassador/") {
192 annotationResource["apiVersion"] = "getambassador.io/" + strings.TrimPrefix(apiVersion, "ambassador/")
193 }
194
195
196 annotationResources = append(annotationResources, &kates.Unstructured{Object: annotationResource})
197 }
198 return annotationResources, nil
199 }
200
View as plain text