1 package devstate
2
3 import (
4 "errors"
5 "fmt"
6
7 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
8 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
9 . "github.com/redhat-developer/odo/pkg/apiserver-gen/go"
10 "k8s.io/utils/pointer"
11 )
12
13 func (o *DevfileState) AddContainer(
14 name string,
15 image string,
16 command []string,
17 args []string,
18 envs []Env,
19 memRequest string,
20 memLimit string,
21 cpuRequest string,
22 cpuLimit string,
23 volumeMounts []VolumeMount,
24 configureSources bool,
25 mountSources bool,
26 sourceMapping string,
27 annotation Annotation,
28 endpoints []Endpoint,
29 ) (DevfileContent, error) {
30
31 container := v1alpha2.Component{
32 Name: name,
33 ComponentUnion: v1alpha2.ComponentUnion{
34 Container: &v1alpha2.ContainerComponent{
35 Container: v1alpha2.Container{
36 Image: image,
37 Command: command,
38 Args: args,
39 Env: tov1alpha2EnvVars(envs),
40 MemoryRequest: memRequest,
41 MemoryLimit: memLimit,
42 CpuRequest: cpuRequest,
43 CpuLimit: cpuLimit,
44 VolumeMounts: tov1alpha2VolumeMounts(volumeMounts),
45 Annotation: tov1alpha2Annotation(annotation),
46 },
47 Endpoints: tov1alpha2Endpoints(endpoints),
48 },
49 },
50 }
51 if configureSources {
52 container.Container.MountSources = &mountSources
53 container.Container.SourceMapping = sourceMapping
54 }
55 err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container})
56 if err != nil {
57 return DevfileContent{}, err
58 }
59 return o.GetContent()
60 }
61
62 func (o *DevfileState) PatchContainer(
63 name string,
64 image string,
65 command []string,
66 args []string,
67 envs []Env,
68 memRequest string,
69 memLimit string,
70 cpuRequest string,
71 cpuLimit string,
72 volumeMounts []VolumeMount,
73 configureSources bool,
74 mountSources bool,
75 sourceMapping string,
76 annotation Annotation,
77 endpoints []Endpoint,
78 ) (DevfileContent, error) {
79 found, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
80 ComponentOptions: common.ComponentOptions{
81 ComponentType: v1alpha2.ContainerComponentType,
82 },
83 FilterByName: name,
84 })
85 if err != nil {
86 return DevfileContent{}, err
87 }
88 if len(found) != 1 {
89 return DevfileContent{}, fmt.Errorf("%d Container found with name %q", len(found), name)
90 }
91
92 container := found[0]
93 container.Container.Image = image
94 container.Container.Command = command
95 container.Container.Args = args
96 container.Container.Env = tov1alpha2EnvVars(envs)
97 container.Container.MemoryRequest = memRequest
98 container.Container.MemoryLimit = memLimit
99 container.Container.CpuRequest = cpuRequest
100 container.Container.CpuLimit = cpuLimit
101 container.Container.VolumeMounts = tov1alpha2VolumeMounts(volumeMounts)
102
103 container.Container.MountSources = nil
104 container.Container.SourceMapping = ""
105 if configureSources {
106 container.Container.MountSources = &mountSources
107 container.Container.SourceMapping = sourceMapping
108 }
109 container.Container.Annotation = tov1alpha2Annotation(annotation)
110 container.Container.Endpoints = tov1alpha2Endpoints(endpoints)
111
112 err = o.Devfile.Data.UpdateComponent(container)
113 if err != nil {
114 return DevfileContent{}, err
115 }
116 return o.GetContent()
117 }
118
119 func tov1alpha2EnvVars(envs []Env) []v1alpha2.EnvVar {
120 result := make([]v1alpha2.EnvVar, 0, len(envs))
121 for _, env := range envs {
122 result = append(result, v1alpha2.EnvVar{
123 Name: env.Name,
124 Value: env.Value,
125 })
126 }
127 return result
128 }
129
130 func tov1alpha2VolumeMounts(volumeMounts []VolumeMount) []v1alpha2.VolumeMount {
131 result := make([]v1alpha2.VolumeMount, 0, len(volumeMounts))
132 for _, vm := range volumeMounts {
133 result = append(result, v1alpha2.VolumeMount{
134 Name: vm.Name,
135 Path: vm.Path,
136 })
137 }
138 return result
139 }
140
141 func tov1alpha2Annotation(annotation Annotation) *v1alpha2.Annotation {
142 var result *v1alpha2.Annotation
143 if len(annotation.Deployment) > 0 || len(annotation.Service) > 0 {
144 result = &v1alpha2.Annotation{}
145 if len(annotation.Deployment) > 0 {
146 result.Deployment = annotation.Deployment
147 }
148 if len(annotation.Service) > 0 {
149 result.Service = annotation.Service
150 }
151 }
152 return result
153 }
154
155 func tov1alpha2Endpoints(endpoints []Endpoint) []v1alpha2.Endpoint {
156 result := make([]v1alpha2.Endpoint, 0, len(endpoints))
157 for _, endpoint := range endpoints {
158 endpoint := endpoint
159 result = append(result, v1alpha2.Endpoint{
160 Name: endpoint.Name,
161 TargetPort: int(endpoint.TargetPort),
162 Exposure: v1alpha2.EndpointExposure(endpoint.Exposure),
163 Protocol: v1alpha2.EndpointProtocol(endpoint.Protocol),
164 Secure: &endpoint.Secure,
165 Path: endpoint.Path,
166 })
167 }
168 return result
169 }
170
171 func (o *DevfileState) DeleteContainer(name string) (DevfileContent, error) {
172
173 err := o.checkContainerUsed(name)
174 if err != nil {
175 return DevfileContent{}, fmt.Errorf("error deleting container %q: %w", name, err)
176 }
177
178
179
180 err = o.Devfile.Data.DeleteComponent(name)
181 if err != nil {
182 return DevfileContent{}, err
183 }
184 return o.GetContent()
185 }
186
187 func (o *DevfileState) checkContainerUsed(name string) error {
188 commands, err := o.Devfile.Data.GetCommands(common.DevfileOptions{
189 CommandOptions: common.CommandOptions{
190 CommandType: v1alpha2.ExecCommandType,
191 },
192 })
193 if err != nil {
194 return err
195 }
196 for _, command := range commands {
197 if command.Exec.Component == name {
198 return fmt.Errorf("container %q is used by exec command %q", name, command.Id)
199 }
200 }
201 return nil
202 }
203
204 func (o *DevfileState) AddImage(name string, imageName string, args []string, buildContext string, rootRequired bool, uri string, autoBuild string) (DevfileContent, error) {
205 container := v1alpha2.Component{
206 Name: name,
207 ComponentUnion: v1alpha2.ComponentUnion{
208 Image: &v1alpha2.ImageComponent{
209 Image: v1alpha2.Image{
210 ImageName: imageName,
211 ImageUnion: v1alpha2.ImageUnion{
212 Dockerfile: &v1alpha2.DockerfileImage{
213 Dockerfile: v1alpha2.Dockerfile{
214 Args: args,
215 BuildContext: buildContext,
216 RootRequired: &rootRequired,
217 },
218 DockerfileSrc: v1alpha2.DockerfileSrc{
219 Uri: uri,
220 },
221 },
222 },
223 },
224 },
225 },
226 }
227 if autoBuild == "never" {
228 container.Image.AutoBuild = pointer.Bool(false)
229 } else if autoBuild == "always" {
230 container.Image.AutoBuild = pointer.Bool(true)
231 }
232 err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container})
233 if err != nil {
234 return DevfileContent{}, err
235 }
236 return o.GetContent()
237 }
238
239 func (o *DevfileState) PatchImage(name string, imageName string, args []string, buildContext string, rootRequired bool, uri string, autoBuild string) (DevfileContent, error) {
240 found, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
241 ComponentOptions: common.ComponentOptions{
242 ComponentType: v1alpha2.ImageComponentType,
243 },
244 FilterByName: name,
245 })
246 if err != nil {
247 return DevfileContent{}, err
248 }
249 if len(found) != 1 {
250 return DevfileContent{}, fmt.Errorf("%d Image found with name %q", len(found), name)
251 }
252
253 image := found[0]
254 if image.Image == nil {
255 image.Image = &v1alpha2.ImageComponent{}
256 }
257 image.Image.ImageName = imageName
258 if image.Image.Dockerfile == nil {
259 image.Image.Dockerfile = &v1alpha2.DockerfileImage{}
260 }
261 image.Image.Dockerfile.Args = args
262 image.Image.Dockerfile.BuildContext = buildContext
263 image.Image.Dockerfile.RootRequired = &rootRequired
264 image.Image.Dockerfile.DockerfileSrc.Uri = uri
265 image.Image.AutoBuild = nil
266 if autoBuild == "never" {
267 image.Image.AutoBuild = pointer.Bool(false)
268 } else if autoBuild == "always" {
269 image.Image.AutoBuild = pointer.Bool(true)
270 }
271 err = o.Devfile.Data.UpdateComponent(image)
272 if err != nil {
273 return DevfileContent{}, err
274 }
275 return o.GetContent()
276 }
277
278 func (o *DevfileState) DeleteImage(name string) (DevfileContent, error) {
279
280 err := o.checkImageUsed(name)
281 if err != nil {
282 return DevfileContent{}, fmt.Errorf("error deleting image %q: %w", name, err)
283 }
284
285
286
287 err = o.Devfile.Data.DeleteComponent(name)
288 if err != nil {
289 return DevfileContent{}, err
290 }
291 return o.GetContent()
292 }
293
294 func (o *DevfileState) checkImageUsed(name string) error {
295 commands, err := o.Devfile.Data.GetCommands(common.DevfileOptions{
296 CommandOptions: common.CommandOptions{
297 CommandType: v1alpha2.ApplyCommandType,
298 },
299 })
300 if err != nil {
301 return err
302 }
303 for _, command := range commands {
304 if command.Apply.Component == name {
305 return fmt.Errorf("image %q is used by Image Command %q", name, command.Id)
306 }
307 }
308 return nil
309 }
310
311 func (o *DevfileState) AddResource(name string, inlined string, uri string, deployByDefault string) (DevfileContent, error) {
312 if inlined != "" && uri != "" {
313 return DevfileContent{}, errors.New("both inlined and uri cannot be set at the same time")
314 }
315 container := v1alpha2.Component{
316 Name: name,
317 ComponentUnion: v1alpha2.ComponentUnion{
318 Kubernetes: &v1alpha2.KubernetesComponent{
319 K8sLikeComponent: v1alpha2.K8sLikeComponent{
320 K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{
321 Inlined: inlined,
322 Uri: uri,
323 },
324 },
325 },
326 },
327 }
328 if deployByDefault == "never" {
329 container.Kubernetes.DeployByDefault = pointer.Bool(false)
330 } else if deployByDefault == "always" {
331 container.Kubernetes.DeployByDefault = pointer.Bool(true)
332 }
333
334 err := o.Devfile.Data.AddComponents([]v1alpha2.Component{container})
335 if err != nil {
336 return DevfileContent{}, err
337 }
338 return o.GetContent()
339 }
340
341 func (o *DevfileState) PatchResource(name string, inlined string, uri string, deployByDefault string) (DevfileContent, error) {
342 if inlined != "" && uri != "" {
343 return DevfileContent{}, errors.New("both inlined and uri cannot be set at the same time")
344 }
345 found, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
346 ComponentOptions: common.ComponentOptions{
347 ComponentType: v1alpha2.KubernetesComponentType,
348 },
349 FilterByName: name,
350 })
351 if err != nil {
352 return DevfileContent{}, err
353 }
354 if len(found) != 1 {
355 return DevfileContent{}, fmt.Errorf("%d Resource found with name %q", len(found), name)
356 }
357
358 resource := found[0]
359 resource.Kubernetes.Inlined = inlined
360 resource.Kubernetes.Uri = uri
361 resource.Kubernetes.DeployByDefault = nil
362 if deployByDefault == "never" {
363 resource.Kubernetes.DeployByDefault = pointer.Bool(false)
364 } else if deployByDefault == "always" {
365 resource.Kubernetes.DeployByDefault = pointer.Bool(true)
366 }
367
368 err = o.Devfile.Data.UpdateComponent(resource)
369 if err != nil {
370 return DevfileContent{}, err
371 }
372
373 return o.GetContent()
374 }
375
376 func (o *DevfileState) DeleteResource(name string) (DevfileContent, error) {
377
378 err := o.checkResourceUsed(name)
379 if err != nil {
380 return DevfileContent{}, fmt.Errorf("error deleting resource %q: %w", name, err)
381 }
382
383
384 err = o.Devfile.Data.DeleteComponent(name)
385 if err != nil {
386 return DevfileContent{}, err
387 }
388 return o.GetContent()
389 }
390
391 func (o *DevfileState) checkResourceUsed(name string) error {
392 commands, err := o.Devfile.Data.GetCommands(common.DevfileOptions{
393 CommandOptions: common.CommandOptions{
394 CommandType: v1alpha2.ApplyCommandType,
395 },
396 })
397 if err != nil {
398 return err
399 }
400 for _, command := range commands {
401 if command.Apply.Component == name {
402 return fmt.Errorf("resource %q is used by Apply Command %q", name, command.Id)
403 }
404 }
405 return nil
406 }
407
408 func (o *DevfileState) AddVolume(name string, ephemeral bool, size string) (DevfileContent, error) {
409 volume := v1alpha2.Component{
410 Name: name,
411 ComponentUnion: v1alpha2.ComponentUnion{
412 Volume: &v1alpha2.VolumeComponent{
413 Volume: v1alpha2.Volume{
414 Ephemeral: &ephemeral,
415 Size: size,
416 },
417 },
418 },
419 }
420 err := o.Devfile.Data.AddComponents([]v1alpha2.Component{volume})
421 if err != nil {
422 return DevfileContent{}, err
423 }
424 return o.GetContent()
425 }
426
427 func (o *DevfileState) PatchVolume(name string, ephemeral bool, size string) (DevfileContent, error) {
428 found, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
429 ComponentOptions: common.ComponentOptions{
430 ComponentType: v1alpha2.VolumeComponentType,
431 },
432 FilterByName: name,
433 })
434 if err != nil {
435 return DevfileContent{}, err
436 }
437 if len(found) != 1 {
438 return DevfileContent{}, fmt.Errorf("%d Volume found with name %q", len(found), name)
439 }
440
441 volume := found[0]
442 volume.Volume.Ephemeral = &ephemeral
443 volume.Volume.Size = size
444
445 err = o.Devfile.Data.UpdateComponent(volume)
446 if err != nil {
447 return DevfileContent{}, err
448 }
449 return o.GetContent()
450 }
451
452 func (o *DevfileState) DeleteVolume(name string) (DevfileContent, error) {
453 err := o.checkVolumeUsed(name)
454 if err != nil {
455 return DevfileContent{}, fmt.Errorf("error deleting volume %q: %w", name, err)
456 }
457
458
459 err = o.Devfile.Data.DeleteComponent(name)
460 if err != nil {
461 return DevfileContent{}, err
462 }
463 return o.GetContent()
464 }
465
466 func (o *DevfileState) checkVolumeUsed(name string) error {
467 containers, err := o.Devfile.Data.GetComponents(common.DevfileOptions{
468 ComponentOptions: common.ComponentOptions{
469 ComponentType: v1alpha2.ContainerComponentType,
470 },
471 })
472 if err != nil {
473 return err
474 }
475 for _, container := range containers {
476 for _, mount := range container.Container.VolumeMounts {
477 if mount.Name == name {
478 return fmt.Errorf("volume %q is mounted by Container %q", name, container.Name)
479 }
480 }
481 }
482 return nil
483 }
484
View as plain text