1 package podmandev
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7
8 devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
9 "k8s.io/klog"
10
11 "github.com/redhat-developer/odo/pkg/dev"
12 "github.com/redhat-developer/odo/pkg/dev/common"
13 "github.com/redhat-developer/odo/pkg/devfile"
14 "github.com/redhat-developer/odo/pkg/devfile/location"
15 "github.com/redhat-developer/odo/pkg/exec"
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/podman"
20 "github.com/redhat-developer/odo/pkg/portForward"
21 "github.com/redhat-developer/odo/pkg/preference"
22 "github.com/redhat-developer/odo/pkg/state"
23 "github.com/redhat-developer/odo/pkg/sync"
24 "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
25 "github.com/redhat-developer/odo/pkg/watch"
26
27 corev1 "k8s.io/api/core/v1"
28 )
29
30 type DevClient struct {
31 fs filesystem.Filesystem
32
33 podmanClient podman.Client
34 prefClient preference.Client
35 portForwardClient portForward.Client
36 syncClient sync.Client
37 execClient exec.Client
38 stateClient state.Client
39 watchClient watch.Client
40
41 deployedPod *corev1.Pod
42 usedPorts []int
43 }
44
45 var _ dev.Client = (*DevClient)(nil)
46
47 func NewDevClient(
48 fs filesystem.Filesystem,
49 podmanClient podman.Client,
50 prefClient preference.Client,
51 portForwardClient portForward.Client,
52 syncClient sync.Client,
53 execClient exec.Client,
54 stateClient state.Client,
55 watchClient watch.Client,
56 ) *DevClient {
57 return &DevClient{
58 fs: fs,
59 podmanClient: podmanClient,
60 prefClient: prefClient,
61 portForwardClient: portForwardClient,
62 syncClient: syncClient,
63 execClient: execClient,
64 stateClient: stateClient,
65 watchClient: watchClient,
66 }
67 }
68
69 func (o *DevClient) Start(
70 ctx context.Context,
71 options dev.StartOptions,
72 ) error {
73 klog.V(4).Infoln("Creating new adapter")
74
75 var (
76 componentStatus = watch.ComponentStatus{
77 ImageComponentsAutoApplied: make(map[string]devfilev1.ImageComponent),
78 }
79 )
80
81 klog.V(4).Infoln("Creating inner-loop resources for the component")
82
83 watchParameters := watch.WatchParameters{
84 StartOptions: options,
85 DevfileWatchHandler: o.watchHandler,
86 WatchCluster: false,
87 }
88
89 return o.watchClient.WatchAndPush(ctx, watchParameters, componentStatus)
90 }
91
92
93 func (o *DevClient) syncFiles(ctx context.Context, options dev.StartOptions, pod *corev1.Pod, path string) (bool, error) {
94 var (
95 devfileObj = odocontext.GetEffectiveDevfileObj(ctx)
96 componentName = odocontext.GetComponentName(ctx)
97 )
98
99 containerName, syncFolder, err := common.GetFirstContainerWithSourceVolume(pod.Spec.Containers)
100 if err != nil {
101 return false, fmt.Errorf("error while retrieving container from pod %s with a mounted project volume: %w", pod.GetName(), err)
102 }
103
104 compInfo := sync.ComponentInfo{
105 ComponentName: componentName,
106 ContainerName: containerName,
107 PodName: pod.GetName(),
108 SyncFolder: syncFolder,
109 }
110 s := log.Spinner("Syncing files into the container")
111 defer s.End(false)
112
113 syncFilesMap := make(map[string]string)
114 var devfileCmd devfilev1.Command
115 innerLoopWithCommands := !options.SkipCommands
116 if innerLoopWithCommands {
117 var (
118 cmdKind = devfilev1.RunCommandGroupKind
119 cmdName = options.RunCommand
120 )
121 if options.Debug {
122 cmdKind = devfilev1.DebugCommandGroupKind
123 cmdName = options.DebugCommand
124 }
125 var hasCmd bool
126 devfileCmd, hasCmd, err = libdevfile.GetCommand(*devfileObj, cmdName, cmdKind)
127 if err != nil {
128 return false, err
129 }
130 if hasCmd {
131 syncFilesMap = common.GetSyncFilesFromAttributes(devfileCmd)
132 } else {
133 klog.V(2).Infof("no command found with name %q and kind %v, syncing files without command attributes", cmdName, cmdKind)
134 }
135 }
136
137 syncParams := sync.SyncParameters{
138 Path: path,
139 WatchFiles: nil,
140 WatchDeletedFiles: nil,
141 IgnoredFiles: options.IgnorePaths,
142 DevfileScanIndexForWatch: true,
143
144 CompInfo: compInfo,
145 ForcePush: true,
146 Files: syncFilesMap,
147 }
148 execRequired, err := o.syncClient.SyncFiles(ctx, syncParams)
149 if err != nil {
150 return false, err
151 }
152 s.End(true)
153 return execRequired, nil
154 }
155
156
157
158 func (o *DevClient) checkVolumesFree(pod *corev1.Pod) error {
159 existingVolumesSet, err := o.podmanClient.VolumeLs()
160 if err != nil {
161 return err
162 }
163 var problematicVolumes []string
164 for _, volume := range pod.Spec.Volumes {
165 if volume.PersistentVolumeClaim != nil && existingVolumesSet[volume.PersistentVolumeClaim.ClaimName] {
166 problematicVolumes = append(problematicVolumes, volume.PersistentVolumeClaim.ClaimName)
167 }
168 }
169 if len(problematicVolumes) > 0 {
170 return fmt.Errorf("volumes already exist, please remove them before to run odo dev: %s", strings.Join(problematicVolumes, ", "))
171 }
172 return nil
173 }
174
175 func (o *DevClient) watchHandler(ctx context.Context, pushParams common.PushParameters, componentStatus *watch.ComponentStatus) error {
176
177 devObj, err := devfile.ParseAndValidateFromFileWithVariables(location.DevfileLocation(o.fs, ""), pushParams.StartOptions.Variables, o.prefClient.GetImageRegistry(), true)
178 if err != nil {
179 return fmt.Errorf("unable to read devfile: %w", err)
180 }
181 pushParams.Devfile = devObj
182
183 return o.reconcile(ctx, pushParams, componentStatus)
184 }
185
View as plain text