1 package kubedev
2
3 import (
4 "context"
5 "fmt"
6 "path/filepath"
7 "time"
8
9 devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
10 parsercommon "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
11 corev1 "k8s.io/api/core/v1"
12
13 "github.com/redhat-developer/odo/pkg/component"
14 "github.com/redhat-developer/odo/pkg/dev/common"
15 "github.com/redhat-developer/odo/pkg/devfile/image"
16 "github.com/redhat-developer/odo/pkg/libdevfile"
17 "github.com/redhat-developer/odo/pkg/log"
18 odocontext "github.com/redhat-developer/odo/pkg/odo/context"
19 "github.com/redhat-developer/odo/pkg/port"
20 "github.com/redhat-developer/odo/pkg/sync"
21 "github.com/redhat-developer/odo/pkg/watch"
22
23 "k8s.io/klog"
24 )
25
26 func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParameters, componentStatus *watch.ComponentStatus) error {
27 var (
28 componentName = odocontext.GetComponentName(ctx)
29 devfilePath = odocontext.GetDevfilePath(ctx)
30 path = filepath.Dir(devfilePath)
31 )
32
33
34 pod, err := o.kubernetesClient.GetPodUsingComponentName(componentName)
35 if err != nil {
36 return fmt.Errorf("unable to get pod for component %s: %w", componentName, err)
37 }
38
39 podChanged := componentStatus.GetState() == watch.StateWaitDeployment
40
41 execRequired, err := o.syncFiles(ctx, parameters, pod, podChanged)
42 if err != nil {
43 componentStatus.SetState(watch.StateReady)
44 return fmt.Errorf("failed to sync to component with name %s: %w", componentName, err)
45 }
46
47 if !componentStatus.PostStartEventsDone && libdevfile.HasPostStartEvents(parameters.Devfile) {
48
49
50 handler := component.NewRunHandler(
51 ctx,
52 o.kubernetesClient,
53 o.execClient,
54 o.configAutomountClient,
55
56 nil, nil,
57 component.HandlerOptions{
58 PodName: pod.Name,
59 ContainersRunning: component.GetContainersNames(pod),
60 Msg: "Executing post-start command in container",
61 },
62 )
63 err = libdevfile.ExecPostStartEvents(ctx, parameters.Devfile, handler)
64 if err != nil {
65 return err
66 }
67 }
68 componentStatus.PostStartEventsDone = true
69
70 var hasRunOrDebugCmd bool
71 innerLoopWithCommands := !parameters.StartOptions.SkipCommands
72 if innerLoopWithCommands {
73 var (
74 cmdKind = devfilev1.RunCommandGroupKind
75 cmdName = parameters.StartOptions.RunCommand
76 )
77 if parameters.StartOptions.Debug {
78 cmdKind = devfilev1.DebugCommandGroupKind
79 cmdName = parameters.StartOptions.DebugCommand
80 }
81
82 var cmd devfilev1.Command
83 cmd, hasRunOrDebugCmd, err = libdevfile.GetCommand(parameters.Devfile, cmdName, cmdKind)
84 if err != nil {
85 return err
86 }
87
88 var running bool
89 var isComposite bool
90 var runHandler libdevfile.Handler
91 if hasRunOrDebugCmd {
92 var commandType devfilev1.CommandType
93 commandType, err = parsercommon.GetCommandType(cmd)
94 if err != nil {
95 return err
96 }
97
98 cmdHandler := component.NewRunHandler(
99 ctx,
100 o.kubernetesClient,
101 o.execClient,
102 o.configAutomountClient,
103 o.filesystem,
104 image.SelectBackend(ctx),
105 component.HandlerOptions{
106 PodName: pod.GetName(),
107 ContainersRunning: component.GetContainersNames(pod),
108 Devfile: parameters.Devfile,
109 Path: path,
110 },
111 )
112
113 if commandType == devfilev1.ExecCommandType {
114 running, err = cmdHandler.IsRemoteProcessForCommandRunning(ctx, cmd, pod.Name)
115 if err != nil {
116 return err
117 }
118 } else if commandType == devfilev1.CompositeCommandType {
119
120
121 isComposite = true
122 } else {
123 return fmt.Errorf("unsupported type %q for Devfile command %s, only exec and composite are handled",
124 commandType, cmd.Id)
125 }
126
127 cmdHandler.ComponentExists = running || isComposite
128 runHandler = cmdHandler
129 }
130
131 klog.V(4).Infof("running=%v, execRequired=%v",
132 running, execRequired)
133
134 if isComposite || !running || execRequired {
135
136
137 doExecuteBuildCommand := func() error {
138 execHandler := component.NewRunHandler(
139 ctx,
140 o.kubernetesClient,
141 o.execClient,
142 o.configAutomountClient,
143
144
145 nil, nil, component.HandlerOptions{
146 PodName: pod.Name,
147 ComponentExists: running,
148 ContainersRunning: component.GetContainersNames(pod),
149 Msg: "Building your application in container",
150 },
151 )
152 return libdevfile.Build(ctx, parameters.Devfile, parameters.StartOptions.BuildCommand, execHandler)
153 }
154 if err = doExecuteBuildCommand(); err != nil {
155 componentStatus.SetState(watch.StateReady)
156 return err
157 }
158
159 if hasRunOrDebugCmd {
160 err = libdevfile.ExecuteCommandByNameAndKind(ctx, parameters.Devfile, cmdName, cmdKind, runHandler, false)
161 if err != nil {
162 return err
163 }
164 componentStatus.RunExecuted = true
165 } else {
166 msg := fmt.Sprintf("Missing default %v command", cmdKind)
167 if cmdName != "" {
168 msg = fmt.Sprintf("Missing %v command with name %q", cmdKind, cmdName)
169 }
170 log.Warning(msg)
171 }
172 }
173 }
174
175 if podChanged || o.portsChanged {
176 o.portForwardClient.StopPortForwarding(ctx, componentName)
177 }
178
179 if innerLoopWithCommands && hasRunOrDebugCmd && len(o.portsToForward) != 0 {
180
181 appReadySpinner := log.Spinner("Waiting for the application to be ready")
182 err = o.checkAppPorts(ctx, pod.Name, o.portsToForward)
183 appReadySpinner.End(err == nil)
184 if err != nil {
185 log.Warningf("Port forwarding might not work correctly: %v", err)
186 log.Warning("Running `odo logs --follow` might help in identifying the problem.")
187 fmt.Fprintln(log.GetStdout())
188 }
189 }
190
191 err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts, parameters.StartOptions.CustomAddress)
192 if err != nil {
193 return common.NewErrPortForward(err)
194 }
195 componentStatus.EndpointsForwarded = o.portForwardClient.GetForwardedPorts()
196
197 componentStatus.SetState(watch.StateReady)
198 return nil
199 }
200
201 func (o *DevClient) syncFiles(ctx context.Context, parameters common.PushParameters, pod *corev1.Pod, podChanged bool) (bool, error) {
202 var (
203 devfileObj = odocontext.GetEffectiveDevfileObj(ctx)
204 componentName = odocontext.GetComponentName(ctx)
205 devfilePath = odocontext.GetDevfilePath(ctx)
206 path = filepath.Dir(devfilePath)
207 )
208
209 s := log.Spinner("Syncing files into the container")
210 defer s.End(false)
211
212
213 containerName, syncFolder, err := common.GetFirstContainerWithSourceVolume(pod.Spec.Containers)
214 if err != nil {
215 return false, fmt.Errorf("error while retrieving container from pod %s with a mounted project volume: %w", pod.GetName(), err)
216 }
217
218 syncFilesMap := make(map[string]string)
219 var devfileCmd devfilev1.Command
220 innerLoopWithCommands := !parameters.StartOptions.SkipCommands
221 if innerLoopWithCommands {
222 var (
223 cmdKind = devfilev1.RunCommandGroupKind
224 cmdName = parameters.StartOptions.RunCommand
225 )
226 if parameters.StartOptions.Debug {
227 cmdKind = devfilev1.DebugCommandGroupKind
228 cmdName = parameters.StartOptions.DebugCommand
229 }
230 var hasCmd bool
231 devfileCmd, hasCmd, err = libdevfile.GetCommand(*devfileObj, cmdName, cmdKind)
232 if err != nil {
233 return false, err
234 }
235 if hasCmd {
236 syncFilesMap = common.GetSyncFilesFromAttributes(devfileCmd)
237 } else {
238 klog.V(2).Infof("no command found with name %q and kind %v, syncing files without command attributes", cmdName, cmdKind)
239 }
240 }
241
242
243 compInfo := sync.ComponentInfo{
244 ComponentName: componentName,
245 ContainerName: containerName,
246 PodName: pod.GetName(),
247 SyncFolder: syncFolder,
248 }
249
250 syncParams := sync.SyncParameters{
251 Path: path,
252 WatchFiles: parameters.WatchFiles,
253 WatchDeletedFiles: parameters.WatchDeletedFiles,
254 IgnoredFiles: parameters.StartOptions.IgnorePaths,
255 DevfileScanIndexForWatch: parameters.DevfileScanIndexForWatch,
256
257 CompInfo: compInfo,
258 ForcePush: !o.deploymentExists || podChanged,
259 Files: syncFilesMap,
260 }
261
262 execRequired, err := o.syncClient.SyncFiles(ctx, syncParams)
263 if err != nil {
264 return false, err
265 }
266 s.End(true)
267 return execRequired, nil
268 }
269
270 func (o *DevClient) checkAppPorts(ctx context.Context, podName string, portsToFwd map[string][]devfilev1.Endpoint) error {
271 containerPortsMapping := make(map[string][]int)
272 for c, ports := range portsToFwd {
273 for _, p := range ports {
274 containerPortsMapping[c] = append(containerPortsMapping[c], p.TargetPort)
275 }
276 }
277 return port.CheckAppPortsListening(ctx, o.execClient, podName, containerPortsMapping, 1*time.Minute)
278 }
279
View as plain text