1 package component
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "time"
8
9 devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
10 "k8s.io/klog"
11
12 "github.com/redhat-developer/odo/pkg/exec"
13 "github.com/redhat-developer/odo/pkg/log"
14 "github.com/redhat-developer/odo/pkg/platform"
15 "github.com/redhat-developer/odo/pkg/remotecmd"
16 "github.com/redhat-developer/odo/pkg/task"
17 "github.com/redhat-developer/odo/pkg/util"
18 )
19
20 const numberOfLinesToOutputLog = 100
21
22
23
24 func ExecuteRunCommand(ctx context.Context, execClient exec.Client, platformClient platform.Client, devfileCmd devfilev1.Command, componentExists bool, podName string, appName string, componentName string) error {
25 remoteProcessHandler := remotecmd.NewKubeExecProcessHandler(execClient)
26
27 statusHandlerFunc := func(s *log.Status) remotecmd.CommandOutputHandler {
28 return func(status remotecmd.RemoteProcessStatus, stdout []string, stderr []string, err error) {
29 switch status {
30 case remotecmd.Starting:
31
32 s.Start(fmt.Sprintf("Executing the application (command: %s)", devfileCmd.Id), true)
33 case remotecmd.Stopped, remotecmd.Errored:
34 s.EndWithStatus(fmt.Sprintf("Finished executing the application (command: %s)", devfileCmd.Id), status == remotecmd.Stopped)
35 if err != nil {
36 klog.V(2).Infof("error while running background command: %v", err)
37 }
38 }
39 }
40 }
41
42
43
44 spinner := log.NewStatus(log.GetStdout())
45
46
47
48 if componentExists {
49 if devfileCmd.Exec == nil || !util.SafeGetBool(devfileCmd.Exec.HotReloadCapable) {
50 klog.V(2).Infof("restart required for command %s", devfileCmd.Id)
51
52 cmdDef, err := devfileCommandToRemoteCmdDefinition(devfileCmd)
53 if err != nil {
54 return err
55 }
56
57 err = remoteProcessHandler.StopProcessForCommand(ctx, cmdDef, podName, devfileCmd.Exec.Component)
58 if err != nil {
59 return err
60 }
61
62 if err = remoteProcessHandler.StartProcessForCommand(ctx, cmdDef, podName, devfileCmd.Exec.Component, statusHandlerFunc(spinner)); err != nil {
63 return err
64 }
65 } else {
66 klog.V(2).Infof("command is hot-reload capable, not restarting %s", devfileCmd.Id)
67 }
68 } else {
69 cmdDef, err := devfileCommandToRemoteCmdDefinition(devfileCmd)
70 if err != nil {
71 return err
72 }
73
74 if err := remoteProcessHandler.StartProcessForCommand(ctx, cmdDef, podName, devfileCmd.Exec.Component, statusHandlerFunc(spinner)); err != nil {
75 return err
76 }
77 }
78
79 retrySchedule := []time.Duration{
80 5 * time.Second,
81 6 * time.Second,
82 9 * time.Second,
83 }
84 var totalWaitTime float64
85 for _, s := range retrySchedule {
86 totalWaitTime += s.Seconds()
87 }
88
89 _, err := task.NewRetryable(fmt.Sprintf("process for command %q", devfileCmd.Id), func() (bool, interface{}, error) {
90 klog.V(4).Infof("checking if process for command %q is running", devfileCmd.Id)
91 remoteProcess, err := remoteProcessHandler.GetProcessInfoForCommand(ctx, remotecmd.CommandDefinition{Id: devfileCmd.Id}, podName, devfileCmd.Exec.Component)
92 if err != nil {
93 return false, nil, err
94 }
95 isRunningOrDone := remoteProcess.Status == remotecmd.Running ||
96 remoteProcess.Status == remotecmd.Stopped ||
97 remoteProcess.Status == remotecmd.Errored
98 return isRunningOrDone, nil, err
99 }).RetryWithSchedule(retrySchedule, false)
100 if err != nil {
101 return err
102 }
103
104 return checkRemoteCommandStatus(ctx, execClient, platformClient, devfileCmd, podName, appName, componentName, fmt.Sprintf("Devfile command %q exited with an error status in %.0f second(s)", devfileCmd.Id, totalWaitTime))
105 }
106
107
108
109 func devfileCommandToRemoteCmdDefinition(devfileCmd devfilev1.Command) (remotecmd.CommandDefinition, error) {
110 if devfileCmd.Exec == nil {
111 return remotecmd.CommandDefinition{}, errors.New(" only Exec commands are supported")
112 }
113
114 envVars := make([]remotecmd.CommandEnvVar, 0, len(devfileCmd.Exec.Env))
115 for _, e := range devfileCmd.Exec.Env {
116 envVars = append(envVars, remotecmd.CommandEnvVar{Key: e.Name, Value: e.Value})
117 }
118
119 return remotecmd.CommandDefinition{
120 Id: devfileCmd.Id,
121 WorkingDir: devfileCmd.Exec.WorkingDir,
122 EnvVars: envVars,
123 CmdLine: devfileCmd.Exec.CommandLine,
124 }, nil
125 }
126
127
128
129 func checkRemoteCommandStatus(ctx context.Context, execClient exec.Client, platformClient platform.Client, command devfilev1.Command, podName string, appName string, componentName string, notRunningMessage string) error {
130 remoteProcessHandler := remotecmd.NewKubeExecProcessHandler(execClient)
131 remoteProcess, err := remoteProcessHandler.GetProcessInfoForCommand(ctx, remotecmd.CommandDefinition{Id: command.Id}, podName, command.Exec.Component)
132 if err != nil {
133 return err
134 }
135
136 if remoteProcess.Status != remotecmd.Running && remoteProcess.Status != remotecmd.Stopped {
137 log.Warningf(notRunningMessage)
138 log.Warningf("Last %d lines of log:", numberOfLinesToOutputLog)
139
140 rd, err := Log(platformClient, componentName, appName, false, command)
141 if err != nil {
142 return err
143 }
144
145
146
147 err = util.DisplayLog(false, rd, log.GetStderr(), componentName, numberOfLinesToOutputLog)
148 if err != nil {
149 return err
150 }
151 }
152 return nil
153 }
154
View as plain text