1 package libdevfile
2
3 import (
4 "context"
5 "fmt"
6 "reflect"
7 "regexp"
8 "strings"
9
10 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
11 "github.com/devfile/api/v2/pkg/validation/variables"
12 "github.com/devfile/library/v2/pkg/devfile/parser"
13 "github.com/devfile/library/v2/pkg/devfile/parser/data"
14 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
15 devfilefs "github.com/devfile/library/v2/pkg/testingutil/filesystem"
16 "k8s.io/klog"
17
18 "github.com/redhat-developer/odo/pkg/util"
19 )
20
21 const DebugEndpointNamePrefix = "debug"
22
23 type Handler interface {
24 ApplyImage(image v1alpha2.Component) error
25 ApplyKubernetes(kubernetes v1alpha2.Component, kind v1alpha2.CommandGroupKind) error
26 ApplyOpenShift(openshift v1alpha2.Component, kind v1alpha2.CommandGroupKind) error
27 ExecuteNonTerminatingCommand(ctx context.Context, command v1alpha2.Command) error
28 ExecuteTerminatingCommand(ctx context.Context, command v1alpha2.Command) error
29 }
30
31
32 func Deploy(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
33 return ExecuteCommandByNameAndKind(ctx, devfileObj, "", v1alpha2.DeployCommandGroupKind, handler, false)
34 }
35
36
37
38
39
40 func Build(ctx context.Context, devfileObj parser.DevfileObj, buildCmd string, handler Handler) error {
41 return ExecuteCommandByNameAndKind(ctx, devfileObj, buildCmd, v1alpha2.BuildCommandGroupKind, handler, buildCmd == "")
42 }
43
44
45
46
47 func ExecuteCommandByNameAndKind(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, kind v1alpha2.CommandGroupKind, handler Handler, ignoreCommandNotFound bool) error {
48 cmd, hasDefaultCmd, err := GetCommand(devfileObj, cmdName, kind)
49 if err != nil {
50 if _, isNotFound := err.(NoCommandFoundError); isNotFound {
51 if ignoreCommandNotFound {
52 klog.V(3).Infof("ignoring command not found: %v", cmdName)
53 return nil
54 }
55 }
56 return err
57 }
58 if !hasDefaultCmd {
59 if ignoreCommandNotFound {
60 klog.V(3).Infof("ignoring default %v command not found", kind)
61 return nil
62 }
63 return NewNoDefaultCommandFoundError(kind)
64 }
65
66 return executeCommand(ctx, devfileObj, cmd, handler)
67 }
68
69
70
71 func ExecuteCommandByName(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, handler Handler, ignoreCommandNotFound bool) error {
72 commands, err := devfileObj.Data.GetCommands(
73 common.DevfileOptions{
74 FilterByName: cmdName,
75 },
76 )
77 if err != nil {
78 return err
79 }
80 if len(commands) != 1 {
81 return NewNoCommandFoundError("", cmdName)
82 }
83
84 cmd := commands[0]
85 return executeCommand(ctx, devfileObj, cmd, handler)
86 }
87
88
89 func executeCommand(ctx context.Context, devfileObj parser.DevfileObj, command v1alpha2.Command, handler Handler) error {
90 cmd, err := newCommand(devfileObj, command)
91 if err != nil {
92 return err
93 }
94 return cmd.Execute(ctx, handler, nil)
95 }
96
97
98
99
100
101 func GetCommand(
102 devfileObj parser.DevfileObj,
103 commandName string,
104 groupType v1alpha2.CommandGroupKind,
105 ) (v1alpha2.Command, bool, error) {
106 if commandName == "" {
107 return getDefaultCommand(devfileObj, groupType)
108 }
109 cmdByName, err := getCommandByName(devfileObj, groupType, commandName)
110 if err != nil {
111 return v1alpha2.Command{}, false, err
112 }
113 return cmdByName, true, nil
114 }
115
116
117
118 func getDefaultCommand(devfileObj parser.DevfileObj, groupType v1alpha2.CommandGroupKind) (v1alpha2.Command, bool, error) {
119 commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{CommandOptions: common.CommandOptions{CommandGroupKind: groupType}})
120 if err != nil {
121 return v1alpha2.Command{}, false, err
122 }
123
124
125 if len(commands) == 1 {
126 return commands[0], true, nil
127 }
128
129 defaultCmds := make([]v1alpha2.Command, 0)
130
131 for _, cmd := range commands {
132 cmdGroup := common.GetGroup(cmd)
133 if cmdGroup != nil {
134 if cmdGroup.IsDefault != nil && *cmdGroup.IsDefault {
135 defaultCmds = append(defaultCmds, cmd)
136 }
137 } else {
138 klog.V(2).Infof("command %s has no group", cmd.Id)
139 }
140 }
141
142 if len(defaultCmds) == 0 {
143 return v1alpha2.Command{}, false, nil
144 }
145 if len(defaultCmds) > 1 {
146 return v1alpha2.Command{}, false, NewMoreThanOneDefaultCommandFoundError(groupType)
147 }
148
149
150 return defaultCmds[0], true, nil
151 }
152
153
154
155 func getCommandByName(devfileObj parser.DevfileObj, groupType v1alpha2.CommandGroupKind, commandName string) (v1alpha2.Command, error) {
156 commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{CommandOptions: common.CommandOptions{CommandGroupKind: groupType}})
157 if err != nil {
158 return v1alpha2.Command{}, err
159 }
160
161 for _, cmd := range commands {
162 if cmd.Id == commandName {
163 return cmd, nil
164 }
165 }
166
167 return v1alpha2.Command{}, NewNoCommandFoundError(groupType, commandName)
168 }
169
170
171
172
173
174
175
176
177 func ValidateAndGetCommand(devfileObj parser.DevfileObj, commandName string, groupType v1alpha2.CommandGroupKind) (v1alpha2.Command, error) {
178 cmd, ok, err := GetCommand(devfileObj, commandName, groupType)
179 if err != nil {
180 return v1alpha2.Command{}, err
181 }
182 if !ok {
183 return v1alpha2.Command{}, NewNoCommandFoundError(groupType, commandName)
184 }
185 return cmd, nil
186 }
187
188
189
190 func ValidateAndGetPushCommands(
191 devfileObj parser.DevfileObj,
192 devfileBuildCmd,
193 devfileRunCmd string,
194 ) (map[v1alpha2.CommandGroupKind]v1alpha2.Command, error) {
195 var buildCmd v1alpha2.Command
196 var present bool
197 var err error
198
199 if devfileBuildCmd != "" {
200 buildCmd, err = ValidateAndGetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
201 present = true
202 } else {
203 buildCmd, present, err = GetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
204 }
205 if err != nil {
206 return nil, err
207 }
208
209 commandMap := make(map[v1alpha2.CommandGroupKind]v1alpha2.Command)
210 if present {
211 klog.V(2).Infof("Build command: %v", buildCmd.Id)
212 commandMap[v1alpha2.BuildCommandGroupKind] = buildCmd
213 } else {
214
215 klog.V(2).Infof("No build command was provided")
216 }
217
218 var runCmd v1alpha2.Command
219 runCmd, err = ValidateAndGetCommand(devfileObj, devfileRunCmd, v1alpha2.RunCommandGroupKind)
220 if err != nil {
221 return nil, err
222 }
223 klog.V(2).Infof("Run command: %v", runCmd.Id)
224 commandMap[v1alpha2.RunCommandGroupKind] = runCmd
225
226 return commandMap, nil
227 }
228
229 func HasPostStartEvents(devfileObj parser.DevfileObj) bool {
230 postStartEvents := devfileObj.Data.GetEvents().PostStart
231 return len(postStartEvents) > 0
232 }
233
234 func HasPreStopEvents(devfileObj parser.DevfileObj) bool {
235 preStopEvents := devfileObj.Data.GetEvents().PreStop
236 return len(preStopEvents) > 0
237 }
238
239 func ExecPostStartEvents(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
240 postStartEvents := devfileObj.Data.GetEvents().PostStart
241 return execDevfileEvent(ctx, devfileObj, postStartEvents, handler)
242 }
243
244 func ExecPreStopEvents(ctx context.Context, devfileObj parser.DevfileObj, handler Handler) error {
245 preStopEvents := devfileObj.Data.GetEvents().PreStop
246 return execDevfileEvent(ctx, devfileObj, preStopEvents, handler)
247 }
248
249 func hasCommand(devfileData data.DevfileData, kind v1alpha2.CommandGroupKind) bool {
250 commands, err := devfileData.GetCommands(common.DevfileOptions{
251 CommandOptions: common.CommandOptions{
252 CommandGroupKind: kind,
253 },
254 })
255 return err == nil && len(commands) > 0
256 }
257
258 func HasRunCommand(devfileData data.DevfileData) bool {
259 return hasCommand(devfileData, v1alpha2.RunCommandGroupKind)
260 }
261
262 func HasDeployCommand(devfileData data.DevfileData) bool {
263 return hasCommand(devfileData, v1alpha2.DeployCommandGroupKind)
264 }
265
266 func HasDebugCommand(devfileData data.DevfileData) bool {
267 return hasCommand(devfileData, v1alpha2.DebugCommandGroupKind)
268 }
269
270
271
272
273 func execDevfileEvent(ctx context.Context, devfileObj parser.DevfileObj, events []string, handler Handler) error {
274 if len(events) > 0 {
275 commandMap, err := allCommandsMap(devfileObj)
276 if err != nil {
277 return err
278 }
279 for _, commandName := range events {
280 command, ok := commandMap[commandName]
281 if !ok {
282 return fmt.Errorf("unable to find devfile command %q", commandName)
283 }
284
285 c, err := newCommand(devfileObj, command)
286 if err != nil {
287 return err
288 }
289
290 err = c.Execute(ctx, handler, nil)
291 if err != nil {
292 return fmt.Errorf("unable to execute devfile command %q: %w", commandName, err)
293 }
294 }
295 }
296 return nil
297 }
298
299
300
301
302 func GetDevfileContainerEndpointMapping(devFileObj parser.DevfileObj, includeDebug bool) (map[string][]v1alpha2.Endpoint, error) {
303
304 containers, err := devFileObj.Data.GetComponents(common.DevfileOptions{
305 ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
306 })
307 if err != nil {
308 return nil, err
309 }
310 return GetContainerEndpointMapping(containers, includeDebug), nil
311 }
312
313
314
315 func GetContainerEndpointMapping(containers []v1alpha2.Component, includeDebug bool) map[string][]v1alpha2.Endpoint {
316 ceMapping := make(map[string][]v1alpha2.Endpoint)
317 for _, container := range containers {
318 if container.ComponentUnion.Container == nil {
319
320 continue
321 }
322
323 var ports []v1alpha2.Endpoint
324 for _, e := range container.Container.Endpoints {
325 if !includeDebug && IsDebugEndpoint(e) {
326 klog.V(4).Infof("not running in Debug mode, so ignored Debug port for container %v:%v:%v",
327 container.Name, e.Name, e.TargetPort)
328 continue
329 }
330 ports = append(ports, e)
331 }
332 if len(ports) != 0 {
333 ceMapping[container.Name] = ports
334 }
335 }
336 return ceMapping
337 }
338
339
340 func GetEndpointsFromDevfile(devfileObj parser.DevfileObj, ignoreExposures []v1alpha2.EndpointExposure) ([]v1alpha2.Endpoint, error) {
341 containers, err := devfileObj.Data.GetComponents(common.DevfileOptions{
342 ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
343 })
344 if err != nil {
345 return nil, err
346 }
347
348 var allEndpoints []v1alpha2.Endpoint
349 for _, c := range containers {
350 allEndpoints = append(allEndpoints, c.Container.Endpoints...)
351 }
352
353 var endpoints []v1alpha2.Endpoint
354 for _, e := range allEndpoints {
355 ignore := false
356 for _, i := range ignoreExposures {
357 if e.Exposure == i {
358 ignore = true
359 }
360 }
361 if !ignore {
362 endpoints = append(endpoints, e)
363 }
364 }
365 return endpoints, nil
366 }
367
368
369
370 func GetDebugEndpointsForComponent(cmp v1alpha2.Component) ([]v1alpha2.Endpoint, error) {
371 if cmp.Container == nil {
372 return nil, fmt.Errorf("component %q is not a container component", cmp.Name)
373 }
374
375 var result []v1alpha2.Endpoint
376 for _, ep := range cmp.Container.Endpoints {
377 if IsDebugEndpoint(ep) {
378 result = append(result, ep)
379 }
380 }
381 return result, nil
382 }
383
384
385
386 func IsDebugEndpoint(ep v1alpha2.Endpoint) bool {
387 return IsDebugPort(ep.Name)
388 }
389
390
391
392 func IsDebugPort(name string) bool {
393 return name == DebugEndpointNamePrefix || strings.HasPrefix(name, DebugEndpointNamePrefix+"-")
394 }
395
396
397 func GetContainerComponentsForCommand(devfileObj parser.DevfileObj, cmd v1alpha2.Command) ([]string, error) {
398
399 if reflect.DeepEqual(cmd, v1alpha2.Command{}) {
400 return nil, nil
401 }
402
403 commandType, err := common.GetCommandType(cmd)
404 if err != nil {
405 return nil, err
406 }
407
408 hasComponent := func(n string) bool {
409 _, ok, _ := findComponentByNameAndType(devfileObj, n, v1alpha2.ContainerComponentType)
410 return ok
411 }
412
413 switch commandType {
414 case v1alpha2.ExecCommandType:
415 if hasComponent(cmd.Exec.Component) {
416 return []string{cmd.Exec.Component}, nil
417 }
418 return nil, nil
419 case v1alpha2.ApplyCommandType:
420 if hasComponent(cmd.Apply.Component) {
421 return []string{cmd.Apply.Component}, nil
422 }
423 return nil, nil
424 case v1alpha2.CompositeCommandType:
425 var commandsMap map[string]v1alpha2.Command
426 commandsMap, err = allCommandsMap(devfileObj)
427 if err != nil {
428 return nil, err
429 }
430
431 var res []string
432 set := make(map[string]bool)
433 var componentsForCommand []string
434 for _, c := range cmd.Composite.Commands {
435 fromCommandMap, present := commandsMap[strings.ToLower(c)]
436 if !present {
437 return nil, fmt.Errorf("command %q not found in all commands map", c)
438 }
439 componentsForCommand, err = GetContainerComponentsForCommand(devfileObj, fromCommandMap)
440 if err != nil {
441 return nil, err
442 }
443 for _, s := range componentsForCommand {
444 if _, ok := set[s]; !ok && hasComponent(s) {
445 set[s] = true
446 res = append(res, s)
447 }
448 }
449 }
450
451 return res, nil
452
453 default:
454 return nil, fmt.Errorf("type not handled for command %q: %v", cmd.Id, commandType)
455 }
456 }
457
458
459 func FindComponentByName(d data.DevfileData, n string) (v1alpha2.Component, bool, error) {
460 comps, err := d.GetComponents(common.DevfileOptions{})
461 if err != nil {
462 return v1alpha2.Component{}, false, err
463 }
464 for _, c := range comps {
465 if c.Name == n {
466 return c, true, nil
467 }
468 }
469 return v1alpha2.Component{}, false, nil
470 }
471
472
473
474
475
476
477 func GetK8sManifestsWithVariablesSubstituted(devfileObj parser.DevfileObj, devfileCmpName string,
478 context string, fs devfilefs.Filesystem) (string, error) {
479
480 components, err := devfileObj.Data.GetComponents(common.DevfileOptions{FilterByName: devfileCmpName})
481 if err != nil {
482 return "", err
483 }
484
485 if len(components) == 0 {
486 return "", NewComponentNotExistError(devfileCmpName)
487 }
488
489 if len(components) != 1 {
490 return "", NewComponentsWithSameNameError(devfileCmpName)
491 }
492
493 devfileCmp := components[0]
494 componentType, err := common.GetComponentType(devfileCmp)
495 if err != nil {
496 return "", err
497 }
498
499 var content, uri string
500 switch componentType {
501 case v1alpha2.KubernetesComponentType:
502 content = devfileCmp.Kubernetes.Inlined
503 if devfileCmp.Kubernetes.Uri != "" {
504 uri = devfileCmp.Kubernetes.Uri
505 }
506
507 case v1alpha2.OpenshiftComponentType:
508 content = devfileCmp.Openshift.Inlined
509 if devfileCmp.Openshift.Uri != "" {
510 uri = devfileCmp.Openshift.Uri
511 }
512
513 default:
514 return "", fmt.Errorf("unexpected component type %s", componentType)
515 }
516
517 if uri != "" {
518 return loadResourceManifestFromUriAndResolveVariables(devfileObj, uri, context, fs)
519 }
520 return substituteVariables(devfileObj.Data.GetDevfileWorkspaceSpec().Variables, content)
521 }
522
523 func loadResourceManifestFromUriAndResolveVariables(devfileObj parser.DevfileObj, uri string,
524 context string, fs devfilefs.Filesystem) (string, error) {
525 content, err := util.GetDataFromURI(uri, context, fs)
526 if err != nil {
527 return content, err
528 }
529 return substituteVariables(devfileObj.Data.GetDevfileWorkspaceSpec().Variables, content)
530 }
531
532
533
534
535
536 func substituteVariables(devfileVars map[string]string, val string) (string, error) {
537
538 matches := regexp.MustCompile(`\{\{\s*(.*?)\s*\}\}`).FindAllStringSubmatch(val, -1)
539 var invalidKeys []string
540 for _, match := range matches {
541 varValue, ok := devfileVars[match[1]]
542 if !ok {
543 invalidKeys = append(invalidKeys, match[1])
544 } else {
545 val = strings.Replace(val, match[0], varValue, -1)
546 }
547 }
548
549 if len(invalidKeys) > 0 {
550 return val, &variables.InvalidKeysError{Keys: invalidKeys}
551 }
552
553 return val, nil
554 }
555
556
557 func findComponentByNameAndType(d parser.DevfileObj, n string, t v1alpha2.ComponentType) (v1alpha2.Component, bool, error) {
558 comps, err := d.Data.GetComponents(common.DevfileOptions{ComponentOptions: common.ComponentOptions{ComponentType: t}})
559 if err != nil {
560 return v1alpha2.Component{}, false, err
561 }
562 for _, c := range comps {
563 if c.Name == n {
564 return c, true, nil
565 }
566 }
567 return v1alpha2.Component{}, false, nil
568 }
569
View as plain text