1
2
3
4
5
6
7
8
9 package edgerelease
10
11 import (
12 "fmt"
13 "strings"
14
15 "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
16 "sigs.k8s.io/kustomize/kyaml/kio/kioutil"
17 "sigs.k8s.io/kustomize/kyaml/yaml"
18
19 "edge-infra.dev/pkg/edge/component/build"
20 "edge-infra.dev/pkg/edge/component/build/image"
21 "edge-infra.dev/pkg/edge/constants"
22 fnv1alpha1 "edge-infra.dev/pkg/edge/gitops/fn/v1alpha1"
23 "edge-infra.dev/pkg/k8s/meta"
24 )
25
26
27 var containerPaths = []string{"containers", "initContainers"}
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 func (f *EdgeRelease) Run(input []*yaml.RNode) ([]*yaml.RNode, error) {
44 if err := f.Spec.Validate(); err != nil {
45 return nil, err
46 }
47
48 f.init()
49
50 input, err := kioutil.MapMeta(input, f.componentMapper)
51 if err != nil {
52 return nil, err
53 }
54
55 input, err = kioutil.MapMeta(input, f.kptFnMapper)
56 if err != nil {
57 return nil, err
58 }
59
60 return input, nil
61 }
62
63 func (f *EdgeRelease) componentMapper(i *yaml.RNode, m yaml.ResourceMeta) (*yaml.RNode, error) {
64
65 cname, ok := m.Labels[constants.PlatformComponent]
66 if !ok {
67 return i, nil
68 }
69
70
71 componentImgs, ok := f.componentMap[cname]
72
73 if !ok || !meta.IsWorkload(m.Kind) {
74 return i, nil
75 }
76
77
78 if f.Spec.AddBuildLabels {
79 if err := f.addBuildLabels(i, m.Labels); err != nil {
80 return nil, wrapErr("edgerelease.componentMapper: failed to add build labels", m, err)
81 }
82 }
83
84
85 podSpecPath, err := getPodSpecPath(m.Kind)
86 if err != nil {
87 return nil, wrapErr("edgerelease.componentMapper: failed to determine PodSpec path from workload", m, err)
88 }
89
90
91
92 if isDefaultImg(componentImgs) {
93 if err := patchContainers(i, componentImgs, append(podSpecPath, "containers")); err != nil {
94 return nil, wrapErr("edgerelease.componentMapper: failed to patch containers", m, err)
95 }
96 return i, nil
97 }
98
99 for _, p := range containerPaths {
100 if err := patchContainers(i, componentImgs, append(podSpecPath, p)); err != nil {
101 return nil, wrapErr("edgerelease.componentMapper: failed to patch containers", m, err)
102 }
103 }
104
105 return i, nil
106 }
107
108 func patchContainers(i *yaml.RNode, imgs map[string]string, path []string) error {
109
110 containerList, err := i.Pipe(yaml.Lookup(path...))
111 if err != nil {
112 return fmt.Errorf("failed to get list of containers from workload: %w", err)
113 }
114
115
116 containers, err := containerList.Elements()
117 if err != nil {
118 return fmt.Errorf("failed to get list of containers from workload: %w", err)
119 }
120
121
122
123
124
125
126
127 if isDefaultImg(imgs) && len(containers) == 1 {
128 newImg := yaml.NewStringRNode(imgs[defaultImgName])
129 return containers[0].PipeE(yaml.SetField("image", newImg))
130 }
131
132 for i := range containers {
133 c := containers[i]
134 name, err := c.Pipe(yaml.Lookup("name"))
135 if err != nil {
136 return err
137 }
138
139 if ref, ok := imgs[strings.TrimSpace(name.MustString())]; ok {
140 if err := c.PipeE(yaml.SetField("image", yaml.NewStringRNode(ref))); err != nil {
141 return err
142 }
143 }
144 }
145
146 return nil
147 }
148
149 func (f *EdgeRelease) kptFnMapper(i *yaml.RNode, m yaml.ResourceMeta) (*yaml.RNode, error) {
150
151 if m.APIVersion != fnv1alpha1.GroupVersion.String() {
152 return i, nil
153 }
154
155 fnAnno, ok := m.Annotations[runtimeutil.FunctionAnnotationKey]
156
157 if !ok {
158 return i, nil
159 }
160
161 fnConfig, err := yaml.Parse(fnAnno)
162 if err != nil {
163 return nil, wrapErr("edgerelease.kptFnMapper: failed to parse function annotation", m, err)
164 }
165
166 imgNode, err := fnConfig.Pipe(yaml.Lookup("container", "image"))
167 if err != nil {
168 return nil, wrapErr("edgerelease.kptFnMapper: failed to look up image in function annotation", m, err)
169 }
170
171
172
173 if imgNode == nil {
174 return i, nil
175 }
176
177 img, err := imgNode.String()
178 if err != nil {
179 return nil, wrapErr("edgerelease.kptFnMapper: failed to get string for function image", m, err)
180 }
181
182 kptFn, ok := f.kptFnMap[image.NameFromRef(img)]
183 if !ok {
184 return i, nil
185 }
186
187 if f.Spec.AddBuildLabels {
188 if err := f.addBuildLabels(i, m.Labels); err != nil {
189 return nil, wrapErr("edgerelease.kptFnMapper: failed to add build labels", m, err)
190 }
191 }
192
193 newImg := map[string]string{"image": kptFn.Reference()}
194 if err := fnConfig.PipeE(yaml.SetField("container", yaml.NewMapRNode(&newImg))); err != nil {
195 return nil, wrapErr("edgerelease.kptFnMapper: failed to update function annotation", m, err)
196 }
197
198 fnConfigStr, err := fnConfig.String()
199 if err != nil {
200 return nil, wrapErr("edgerelease.kptFnMapper: failed to convert new function annotation to string", m, err)
201 }
202
203 if err := i.PipeE(yaml.SetAnnotation(runtimeutil.FunctionAnnotationKey, fnConfigStr)); err != nil {
204 return nil, wrapErr("edgerelease.kptFnMapper: failed to set function annotation", m, err)
205 }
206
207 return i, nil
208 }
209
210
211
212 func (f *EdgeRelease) addBuildLabels(i *yaml.RNode, labels map[string]string) error {
213 if labels == nil {
214 labels = map[string]string{}
215 }
216 labels[build.CommitLabel] = f.Spec.Metadata.Commit
217 labels[build.SemVerLabel] = f.Spec.Metadata.SemVer
218 labels[build.TimestampLabel] = f.Spec.Metadata.Timestamp
219 labels[build.BuildIDLabel] = f.Spec.Metadata.ID
220 labels[build.RepoLabel] = f.Spec.Metadata.Repo
221 labels[build.OrgLabel] = f.Spec.Metadata.Org
222
223 return i.SetLabels(labels)
224 }
225
226 func getPodSpecPath(kind string) ([]string, error) {
227 switch kind {
228
229 case "Deployment", "DaemonSet", "StatefulSet", "Job":
230 return []string{"spec", "template", "spec"}, nil
231 case "CronJob":
232 return []string{"spec", "jobTemplate", "spec", "template", "spec"}, nil
233 default:
234 return []string{}, fmt.Errorf("could not match %s when looking up PodSpec path", kind)
235 }
236 }
237
238 func wrapErr(msg string, m yaml.ResourceMeta, err error) error {
239 if path, ok := m.Annotations[kioutil.PathAnnotation]; ok {
240 return fmt.Errorf("%s: %s/%s from %s. error: %w", msg, m.Kind, m.Name, path, err)
241 }
242 return fmt.Errorf("%s: %s/%s. error: %w", msg, m.Kind, m.Name, err)
243 }
244
245
246
247 func isDefaultImg(imgs map[string]string) bool {
248 _, ok := imgs[defaultImgName]
249 return ok && len(imgs) == 1
250 }
251
View as plain text