1 package podmandev
2
3 import (
4 "context"
5 "fmt"
6 "math/rand"
7 "sort"
8 "time"
9
10 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
11 "github.com/devfile/library/v2/pkg/devfile/generator"
12 "github.com/devfile/library/v2/pkg/devfile/parser"
13 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
14
15 "github.com/redhat-developer/odo/pkg/api"
16 "github.com/redhat-developer/odo/pkg/component"
17 "github.com/redhat-developer/odo/pkg/dev/kubedev/utils"
18 "github.com/redhat-developer/odo/pkg/labels"
19 "github.com/redhat-developer/odo/pkg/libdevfile"
20 "github.com/redhat-developer/odo/pkg/odo/commonflags"
21 odocontext "github.com/redhat-developer/odo/pkg/odo/context"
22 "github.com/redhat-developer/odo/pkg/storage"
23 "github.com/redhat-developer/odo/pkg/util"
24
25 corev1 "k8s.io/api/core/v1"
26 "k8s.io/klog"
27 )
28
29
30 const (
31 portForwardingHelperContainerName = "odo-helper-port-forwarding"
32 portForwardingHelperImage = "quay.io/devfile/base-developer-image@sha256:27d5ce66a259decb84770ea0d1ce8058a806f39dfcfeed8387f9cf2f29e76480"
33 )
34
35 func (o *DevClient) createPodFromComponent(
36 ctx context.Context,
37 debug bool,
38 buildCommand string,
39 runCommand string,
40 debugCommand string,
41 withHelperContainer bool,
42 randomPorts bool,
43 customForwardedPorts []api.ForwardedPort,
44 usedPorts []int,
45 customAddress string,
46 devfileObj parser.DevfileObj,
47 ) (*corev1.Pod, []api.ForwardedPort, error) {
48 var (
49 appName = odocontext.GetApplication(ctx)
50 componentName = odocontext.GetComponentName(ctx)
51 workingDir = odocontext.GetWorkingDirectory(ctx)
52 )
53
54 podTemplate, err := generator.GetPodTemplateSpec(devfileObj, generator.PodTemplateParams{})
55 if err != nil {
56 return nil, nil, err
57 }
58
59 podmanCaps, err := o.podmanClient.GetCapabilities()
60 if err != nil {
61 return nil, nil, err
62 }
63
64 if !podmanCaps.Cgroupv2 {
65 for i := range podTemplate.Spec.Containers {
66 delete(podTemplate.Spec.Containers[i].Resources.Limits, corev1.ResourceMemory)
67 }
68 }
69
70 containers := podTemplate.Spec.Containers
71 if len(containers) == 0 {
72 return nil, nil, fmt.Errorf("no valid components found in the devfile")
73 }
74
75 var fwPorts []api.ForwardedPort
76 fwPorts, err = getPortMapping(devfileObj, debug, randomPorts, usedPorts, customForwardedPorts, customAddress)
77 if err != nil {
78 return nil, nil, err
79 }
80
81 utils.AddOdoProjectVolume(containers)
82 utils.AddOdoMandatoryVolume(containers)
83
84 volumes := []corev1.Volume{
85 {
86 Name: storage.OdoSourceVolume,
87 VolumeSource: corev1.VolumeSource{
88 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
89 ClaimName: getVolumeName(storage.OdoSourceVolume, componentName, appName),
90 },
91 },
92 },
93 {
94 Name: storage.SharedDataVolumeName,
95 VolumeSource: corev1.VolumeSource{
96 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
97 ClaimName: getVolumeName(storage.SharedDataVolumeName, componentName, appName),
98 },
99 },
100 },
101 }
102
103 devfileVolumes, err := storage.ListStorage(devfileObj)
104 if err != nil {
105 return nil, nil, err
106 }
107
108 for _, devfileVolume := range devfileVolumes {
109 volumes = append(volumes, corev1.Volume{
110 Name: devfileVolume.Name,
111 VolumeSource: corev1.VolumeSource{
112 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
113 ClaimName: getVolumeName(devfileVolume.Name, componentName, appName),
114 },
115 },
116 })
117 err = addVolumeMountToContainer(containers, devfileVolume)
118 if err != nil {
119 return nil, nil, err
120 }
121 }
122
123 containers, err = utils.UpdateContainersEntrypointsIfNeeded(devfileObj, containers, buildCommand, runCommand, debugCommand)
124 if err != nil {
125 return nil, nil, err
126 }
127
128 containers = addHostPorts(withHelperContainer, containers, fwPorts, customAddress)
129
130 pod := corev1.Pod{
131 Spec: corev1.PodSpec{
132 Containers: containers,
133 Volumes: volumes,
134 },
135 }
136
137 pod.APIVersion, pod.Kind = corev1.SchemeGroupVersion.WithKind("Pod").ToAPIVersionAndKind()
138 name, err := util.NamespaceKubernetesObject(componentName, appName)
139 if err != nil {
140 return nil, nil, err
141 }
142 pod.SetName(name)
143
144 runtime := component.GetComponentRuntimeFromDevfileMetadata(devfileObj.Data.GetMetadata())
145 pod.SetLabels(labels.GetLabels(componentName, appName, runtime, labels.ComponentDevMode, true))
146 labels.SetProjectType(pod.GetLabels(), component.GetComponentTypeFromDevfileMetadata(devfileObj.Data.GetMetadata()))
147
148 if pod.Annotations == nil {
149 pod.Annotations = make(map[string]string)
150 }
151 if vcsUri := util.GetGitOriginPath(workingDir); vcsUri != "" {
152 pod.Annotations["app.openshift.io/vcs-uri"] = vcsUri
153 }
154
155 return &pod, fwPorts, nil
156 }
157
158 func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPorts []api.ForwardedPort, customAddress string) []corev1.Container {
159 if customAddress == "" {
160 customAddress = "127.0.0.1"
161 }
162 if withHelperContainer {
163
164
165 for i := range containers {
166 containers[i].Ports = nil
167 }
168
169 pfHelperContainer := corev1.Container{
170 Name: portForwardingHelperContainerName,
171 Image: portForwardingHelperImage,
172 Command: []string{"tail"},
173 Args: []string{"-f", "/dev/null"},
174 }
175 for _, fwPort := range fwPorts {
176 pfHelperContainer.Ports = append(pfHelperContainer.Ports, corev1.ContainerPort{
177
178
179
180 Name: fwPort.PortName,
181 ContainerPort: int32(fwPort.LocalPort),
182 HostPort: int32(fwPort.LocalPort),
183 HostIP: customAddress,
184 })
185 }
186 containers = append(containers, pfHelperContainer)
187 } else {
188
189
190 for i := range containers {
191 var containerPorts []corev1.ContainerPort
192 for _, p := range containers[i].Ports {
193 for _, fwPort := range fwPorts {
194 if containers[i].Name == fwPort.ContainerName && int(p.ContainerPort) == fwPort.ContainerPort {
195 p.HostPort = int32(fwPort.LocalPort)
196 p.HostIP = customAddress
197 containerPorts = append(containerPorts, p)
198 break
199 }
200 }
201 }
202 containers[i].Ports = containerPorts
203 }
204 }
205 return containers
206 }
207
208 func getVolumeName(volume string, componentName string, appName string) string {
209 return volume + "-" + componentName + "-" + appName
210 }
211
212 func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool, usedPorts []int, definedPorts []api.ForwardedPort, address string) ([]api.ForwardedPort, error) {
213 if address == "" {
214 address = "127.0.0.1"
215 }
216 containerComponents, err := devfileObj.Data.GetComponents(common.DevfileOptions{
217 ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
218 })
219 if err != nil {
220 return nil, err
221 }
222 ceMapping := libdevfile.GetContainerEndpointMapping(containerComponents, debug)
223
224 var existingContainerPorts []int
225 for _, endpoints := range ceMapping {
226 for _, ep := range endpoints {
227 existingContainerPorts = append(existingContainerPorts, ep.TargetPort)
228 }
229 }
230
231
232 customLocalPorts := make(map[int]struct{})
233 for _, dPort := range definedPorts {
234 customLocalPorts[dPort.LocalPort] = struct{}{}
235 }
236
237 isPortUsedInContainer := func(p int) bool {
238 for _, port := range existingContainerPorts {
239 if p == port {
240 return true
241 }
242 }
243 return false
244 }
245
246
247 getCustomLocalPort := func(containerPort int, container string) int {
248 for _, dp := range definedPorts {
249 if dp.ContainerPort == containerPort {
250 if dp.ContainerName != "" {
251 if dp.ContainerName == container {
252 return dp.LocalPort
253 }
254 } else {
255 return dp.LocalPort
256 }
257 }
258 }
259 return 0
260 }
261
262 var result []api.ForwardedPort
263 startPort := 20001
264 endPort := startPort + 10000
265 usedPortsCopy := make([]int, len(usedPorts))
266 copy(usedPortsCopy, usedPorts)
267
268
269
270 var containers []string
271 for container := range ceMapping {
272 containers = append(containers, container)
273 }
274 sort.Strings(containers)
275
276 for _, containerName := range containers {
277 endpoints := ceMapping[containerName]
278 epLoop:
279 for _, ep := range endpoints {
280 portName := ep.Name
281 isDebugPort := libdevfile.IsDebugPort(portName)
282 if !debug && isDebugPort {
283 klog.V(4).Infof("not running in Debug mode, so skipping Debug endpoint %s (%d) for container %q",
284 portName, ep.TargetPort, containerName)
285 continue
286 }
287 var freePort int
288 if len(definedPorts) != 0 {
289 freePort = getCustomLocalPort(ep.TargetPort, containerName)
290 if freePort == 0 {
291 for {
292 freePort, err = util.NextFreePort(startPort, endPort, usedPorts, address)
293 if err != nil {
294 klog.Infof("%s", err)
295 continue
296 }
297
298 if _, isPortUsed := customLocalPorts[freePort]; isPortUsed {
299 startPort = freePort + 1
300 continue
301 }
302 break
303 }
304 startPort = freePort + 1
305 }
306 } else if randomPorts {
307 if len(usedPortsCopy) != 0 {
308 freePort = usedPortsCopy[0]
309 usedPortsCopy = usedPortsCopy[1:]
310 } else {
311 rand.Seed(time.Now().UnixNano())
312 for {
313 freePort = rand.Intn(endPort-startPort+1) + startPort
314 if !isPortUsedInContainer(freePort) && util.IsPortFree(freePort, address) {
315 break
316 }
317 time.Sleep(100 * time.Millisecond)
318 }
319 }
320 } else {
321 for {
322 freePort, err = util.NextFreePort(startPort, endPort, usedPorts, address)
323 if err != nil {
324 klog.Infof("%s", err)
325 continue epLoop
326 }
327 if !isPortUsedInContainer(freePort) {
328 break
329 }
330 startPort = freePort + 1
331 time.Sleep(100 * time.Millisecond)
332 }
333 startPort = freePort + 1
334 }
335 fp := api.ForwardedPort{
336 Platform: commonflags.PlatformPodman,
337 PortName: portName,
338 IsDebug: isDebugPort,
339 ContainerName: containerName,
340 LocalAddress: address,
341 LocalPort: freePort,
342 ContainerPort: ep.TargetPort,
343 Exposure: string(ep.Exposure),
344 Protocol: string(ep.Protocol),
345 }
346 result = append(result, fp)
347 }
348 }
349 return result, nil
350 }
351
352 func addVolumeMountToContainer(containers []corev1.Container, devfileVolume storage.LocalStorage) error {
353 for i := range containers {
354 if containers[i].Name == devfileVolume.Container {
355 containers[i].VolumeMounts = append(containers[i].VolumeMounts, corev1.VolumeMount{
356 Name: devfileVolume.Name,
357 MountPath: devfileVolume.Path,
358 })
359 return nil
360 }
361 }
362 return fmt.Errorf("container %q not found", devfileVolume.Container)
363 }
364
365 func getUsedPorts(ports []api.ForwardedPort) []int {
366 res := make([]int, 0, len(ports))
367 for _, port := range ports {
368 res = append(res, port.LocalPort)
369 }
370 return res
371 }
372
View as plain text