• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes / kompose / 4217081086

19 Feb 2023 04:20PM UTC coverage: 53.126%. Remained the same
4217081086

push

github

GitHub
Bump golang.org/x/net from 0.5.0 to 0.7.0 (#1592)

1963 of 3695 relevant lines covered (53.13%)

7.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.66
/pkg/transformer/kubernetes/k8sutils.go
1
/*
2
Copyright 2017 The Kubernetes Authors All rights reserved.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package kubernetes
18

19
import (
20
        "bytes"
21
        "encoding/json"
22
        "fmt"
23
        "os"
24
        "path"
25
        "path/filepath"
26
        "reflect"
27
        "regexp"
28
        "sort"
29
        "strconv"
30
        "strings"
31
        "text/template"
32
        "time"
33

34
        "github.com/joho/godotenv"
35
        "github.com/kubernetes/kompose/pkg/kobject"
36
        "github.com/kubernetes/kompose/pkg/loader/compose"
37
        "github.com/kubernetes/kompose/pkg/transformer"
38
        deployapi "github.com/openshift/api/apps/v1"
39
        "github.com/pkg/errors"
40
        log "github.com/sirupsen/logrus"
41
        "gopkg.in/yaml.v3"
42
        appsv1 "k8s.io/api/apps/v1"
43
        api "k8s.io/api/core/v1"
44
        "k8s.io/apimachinery/pkg/api/resource"
45
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
46
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
47
        "k8s.io/apimachinery/pkg/runtime"
48
)
49

50
/**
51
 * Generate Helm Chart configuration
52
 */
53
func generateHelm(dirName string) error {
×
54
        type ChartDetails struct {
×
55
                Name string
×
56
        }
×
57

×
58
        details := ChartDetails{dirName}
×
59
        manifestDir := dirName + string(os.PathSeparator) + "templates"
×
60
        dir, err := os.Open(dirName)
×
61

×
62
        /* Setup the initial directories/files */
×
63
        if err == nil {
×
64
                _ = dir.Close()
×
65
        }
×
66

67
        if err != nil {
×
68
                err = os.Mkdir(dirName, 0755)
×
69
                if err != nil {
×
70
                        return err
×
71
                }
×
72

73
                err = os.Mkdir(manifestDir, 0755)
×
74
                if err != nil {
×
75
                        return err
×
76
                }
×
77
        }
78

79
        /* Create the readme file */
80
        readme := "This chart was created by Kompose\n"
×
81
        err = os.WriteFile(dirName+string(os.PathSeparator)+"README.md", []byte(readme), 0644)
×
82
        if err != nil {
×
83
                return err
×
84
        }
×
85

86
        /* Create the Chart.yaml file */
87
        chart := `name: {{.Name}}
×
88
description: A generated Helm Chart for {{.Name}} from Skippbox Kompose
×
89
version: 0.0.1
×
90
apiVersion: v2
×
91
keywords:
×
92
  - {{.Name}}
×
93
sources:
×
94
home:
×
95
`
×
96

×
97
        t, err := template.New("ChartTmpl").Parse(chart)
×
98
        if err != nil {
×
99
                return errors.Wrap(err, "Failed to generate Chart.yaml template, template.New failed")
×
100
        }
×
101
        var chartData bytes.Buffer
×
102
        _ = t.Execute(&chartData, details)
×
103

×
104
        err = os.WriteFile(dirName+string(os.PathSeparator)+"Chart.yaml", chartData.Bytes(), 0644)
×
105
        if err != nil {
×
106
                return err
×
107
        }
×
108

109
        log.Infof("chart created in %q\n", dirName+string(os.PathSeparator))
×
110
        return nil
×
111
}
112

113
// Check if given path is a directory
114
func isDir(name string) (bool, error) {
3✔
115
        // Open file to get stat later
3✔
116
        f, err := os.Open(name)
3✔
117
        if err != nil {
4✔
118
                return false, nil
1✔
119
        }
1✔
120
        defer f.Close()
2✔
121

2✔
122
        // Get file attributes and information
2✔
123
        fileStat, err := f.Stat()
2✔
124
        if err != nil {
2✔
125
                return false, errors.Wrap(err, "error retrieving file information, f.Stat failed")
×
126
        }
×
127

128
        // Check if given path is a directory
129
        if fileStat.IsDir() {
3✔
130
                return true, nil
1✔
131
        }
1✔
132
        return false, nil
1✔
133
}
134

135
func getDirName(opt kobject.ConvertOptions) string {
×
136
        dirName := opt.OutFile
×
137
        if dirName == "" {
×
138
                // Let assume all the docker-compose files are in the same directory
×
139
                if opt.CreateChart {
×
140
                        filename := opt.InputFiles[0]
×
141
                        extension := filepath.Ext(filename)
×
142
                        dirName = filename[0 : len(filename)-len(extension)]
×
143
                } else {
×
144
                        dirName = "."
×
145
                }
×
146
        }
147
        return dirName
×
148
}
149

150
// PrintList will take the data converted and decide on the commandline attributes given
151
func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error {
×
152
        var f *os.File
×
153
        dirName := getDirName(opt)
×
154
        log.Debugf("Target Dir: %s", dirName)
×
155

×
156
        // Create a directory if "out" ends with "/" and does not exist.
×
157
        if !transformer.Exists(opt.OutFile) && strings.HasSuffix(opt.OutFile, "/") {
×
158
                if err := os.MkdirAll(opt.OutFile, os.ModePerm); err != nil {
×
159
                        return errors.Wrap(err, "failed to create a directory")
×
160
                }
×
161
        }
162

163
        // Check if output file is a directory
164
        isDirVal, err := isDir(opt.OutFile)
×
165
        if err != nil {
×
166
                return errors.Wrap(err, "isDir failed")
×
167
        }
×
168
        if opt.CreateChart {
×
169
                isDirVal = true
×
170
        }
×
171
        if !isDirVal {
×
172
                f, err = transformer.CreateOutFile(opt.OutFile)
×
173
                if err != nil {
×
174
                        return errors.Wrap(err, "transformer.CreateOutFile failed")
×
175
                }
×
176
                if len(opt.OutFile) != 0 {
×
177
                        log.Printf("Kubernetes file %q created", opt.OutFile)
×
178
                }
×
179
                defer f.Close()
×
180
        }
181

182
        var files []string
×
183
        // if asked to print to stdout or to put in single file
×
184
        // we will create a list
×
185
        if opt.ToStdout || f != nil {
×
186
                // convert objects to versioned and add them to list
×
187
                if opt.GenerateJSON {
×
188
                        return fmt.Errorf("cannot convert to one file while specifying a json output file or stdout option")
×
189
                }
×
190
                for _, object := range objects {
×
191
                        versionedObject, err := convertToVersion(object)
×
192
                        if err != nil {
×
193
                                return err
×
194
                        }
×
195

196
                        data, err := marshal(versionedObject, opt.GenerateJSON, opt.YAMLIndent)
×
197
                        if err != nil {
×
198
                                return fmt.Errorf("error in marshalling the List: %v", err)
×
199
                        }
×
200
                        // this part add --- which unifies the file
201
                        data = []byte(fmt.Sprintf("---\n%s", data))
×
202
                        printVal, err := transformer.Print("", dirName, "", data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
×
203
                        if err != nil {
×
204
                                return errors.Wrap(err, "transformer to print to one single file failed")
×
205
                        }
×
206
                        files = append(files, printVal)
×
207
                }
208
        } else {
×
209
                finalDirName := dirName
×
210
                if opt.CreateChart {
×
211
                        finalDirName = dirName + string(os.PathSeparator) + "templates"
×
212
                }
×
213

214
                if err := os.MkdirAll(finalDirName, 0755); err != nil {
×
215
                        return err
×
216
                }
×
217

218
                var file string
×
219
                // create a separate file for each provider
×
220
                for _, v := range objects {
×
221
                        versionedObject, err := convertToVersion(v)
×
222
                        if err != nil {
×
223
                                return err
×
224
                        }
×
225
                        data, err := marshal(versionedObject, opt.GenerateJSON, opt.YAMLIndent)
×
226
                        if err != nil {
×
227
                                return err
×
228
                        }
×
229

230
                        var typeMeta metav1.TypeMeta
×
231
                        var objectMeta metav1.ObjectMeta
×
232

×
233
                        if us, ok := v.(*unstructured.Unstructured); ok {
×
234
                                typeMeta = metav1.TypeMeta{
×
235
                                        Kind:       us.GetKind(),
×
236
                                        APIVersion: us.GetAPIVersion(),
×
237
                                }
×
238
                                objectMeta = metav1.ObjectMeta{
×
239
                                        Name: us.GetName(),
×
240
                                }
×
241
                        } else {
×
242
                                val := reflect.ValueOf(v).Elem()
×
243
                                // Use reflect to access TypeMeta struct inside runtime.Object.
×
244
                                // cast it to correct type - metav1.TypeMeta
×
245
                                typeMeta = val.FieldByName("TypeMeta").Interface().(metav1.TypeMeta)
×
246

×
247
                                // Use reflect to access ObjectMeta struct inside runtime.Object.
×
248
                                // cast it to correct type - api.ObjectMeta
×
249
                                objectMeta = val.FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
×
250
                        }
×
251

252
                        file, err = transformer.Print(objectMeta.Name, finalDirName, strings.ToLower(typeMeta.Kind), data, opt.ToStdout, opt.GenerateJSON, f, opt.Provider)
×
253
                        if err != nil {
×
254
                                return errors.Wrap(err, "transformer.Print failed")
×
255
                        }
×
256

257
                        files = append(files, file)
×
258
                }
259
        }
260
        if opt.CreateChart {
×
261
                err = generateHelm(dirName)
×
262
                if err != nil {
×
263
                        return errors.Wrap(err, "generateHelm failed")
×
264
                }
×
265
        }
266
        return nil
×
267
}
268

269
// marshal object runtime.Object and return byte array
270
func marshal(obj runtime.Object, jsonFormat bool, indent int) (data []byte, err error) {
×
271
        // convert data to yaml or json
×
272
        if jsonFormat {
×
273
                data, err = json.MarshalIndent(obj, "", "  ")
×
274
        } else {
×
275
                data, err = marshalWithIndent(obj, indent)
×
276
        }
×
277
        if err != nil {
×
278
                data = nil
×
279
        }
×
280
        return
×
281
}
282

283
// Convert JSON to YAML.
284
func jsonToYaml(j []byte, spaces int) ([]byte, error) {
×
285
        // Convert the JSON to an object.
×
286
        var jsonObj interface{}
×
287
        // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
×
288
        // Go JSON library doesn't try to pick the right number type (int, float,
×
289
        // etc.) when unmarshling to interface{}, it just picks float64
×
290
        // universally. go-yaml does go through the effort of picking the right
×
291
        // number type, so we can preserve number type throughout this process.
×
292
        err := yaml.Unmarshal(j, &jsonObj)
×
293
        if err != nil {
×
294
                return nil, err
×
295
        }
×
296

297
        var b bytes.Buffer
×
298
        encoder := yaml.NewEncoder(&b)
×
299
        encoder.SetIndent(spaces)
×
300
        if err := encoder.Encode(jsonObj); err != nil {
×
301
                return nil, err
×
302
        }
×
303
        return b.Bytes(), nil
×
304

305
        // Marshal this object into YAML.
306
        // return yaml.Marshal(jsonObj)
307
}
308

309
func marshalWithIndent(o interface{}, indent int) ([]byte, error) {
×
310
        j, err := json.Marshal(o)
×
311
        if err != nil {
×
312
                return nil, fmt.Errorf("error marshaling into JSON: %s", err.Error())
×
313
        }
×
314

315
        y, err := jsonToYaml(j, indent)
×
316
        if err != nil {
×
317
                return nil, fmt.Errorf("error converting JSON to YAML: %s", err.Error())
×
318
        }
×
319

320
        return y, nil
×
321
}
322

323
// Convert object to versioned object
324
// if groupVersion is  empty (metav1.GroupVersion{}), use version from original object (obj)
325
func convertToVersion(obj runtime.Object) (runtime.Object, error) {
×
326
        // ignore unstruct object
×
327
        if _, ok := obj.(*unstructured.Unstructured); ok {
×
328
                return obj, nil
×
329
        }
×
330

331
        return obj, nil
×
332

333
        //var version metav1.GroupVersion
334
        //
335
        //if groupVersion.Empty() {
336
        //        objectVersion := obj.GetObjectKind().GroupVersionKind()
337
        //        version = metav1.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version}
338
        //} else {
339
        //        version = groupVersion
340
        //}
341
        //convertedObject, err := api.Scheme.ConvertToVersion(obj, version)
342
        //if err != nil {
343
        //        return nil, err
344
        //}
345
        //return convertedObject, nil
346
}
347

348
// PortsExist checks if service has ports defined
349
func (k *Kubernetes) PortsExist(service kobject.ServiceConfig) bool {
36✔
350
        return len(service.Port) != 0
36✔
351
}
36✔
352

353
func (k *Kubernetes) initSvcObject(name string, service kobject.ServiceConfig, ports []api.ServicePort) *api.Service {
1✔
354
        svc := k.InitSvc(name, service)
1✔
355
        // special case, only for loaderbalancer type
1✔
356
        svc.Name = name
1✔
357
        svc.Spec.Selector = transformer.ConfigLabels(service.Name)
1✔
358

1✔
359
        svc.Spec.Ports = ports
1✔
360
        svc.Spec.Type = api.ServiceType(service.ServiceType)
1✔
361

1✔
362
        // Configure annotations
1✔
363
        annotations := transformer.ConfigAnnotations(service)
1✔
364
        svc.ObjectMeta.Annotations = annotations
1✔
365

1✔
366
        return svc
1✔
367
}
1✔
368

369
// CreateLBService creates a k8s Load Balancer Service
370
func (k *Kubernetes) CreateLBService(name string, service kobject.ServiceConfig) []*api.Service {
1✔
371
        var svcs []*api.Service
1✔
372
        tcpPorts, udpPorts := k.ConfigLBServicePorts(service)
1✔
373
        if tcpPorts != nil {
1✔
374
                svc := k.initSvcObject(name+"-tcp", service, tcpPorts)
×
375
                svcs = append(svcs, svc)
×
376
        }
×
377
        if udpPorts != nil {
2✔
378
                svc := k.initSvcObject(name+"-udp", service, udpPorts)
1✔
379
                svcs = append(svcs, svc)
1✔
380
        }
1✔
381
        return svcs
1✔
382
}
383

384
// CreateService creates a k8s service
385
func (k *Kubernetes) CreateService(name string, service kobject.ServiceConfig) *api.Service {
22✔
386
        svc := k.InitSvc(name, service)
22✔
387

22✔
388
        // Configure the service ports.
22✔
389
        servicePorts := k.ConfigServicePorts(service)
22✔
390
        svc.Spec.Ports = servicePorts
22✔
391

22✔
392
        if service.ServiceType == "Headless" {
24✔
393
                svc.Spec.Type = api.ServiceTypeClusterIP
2✔
394
                svc.Spec.ClusterIP = "None"
2✔
395
        } else {
22✔
396
                svc.Spec.Type = api.ServiceType(service.ServiceType)
20✔
397
        }
20✔
398

399
        // Configure annotations
400
        annotations := transformer.ConfigAnnotations(service)
22✔
401
        svc.ObjectMeta.Annotations = annotations
22✔
402

22✔
403
        return svc
22✔
404
}
405

406
// CreateHeadlessService creates a k8s headless service.
407
// This is used for docker-compose services without ports. For such services we can't create regular Kubernetes Service.
408
// and without Service Pods can't find each other using DNS names.
409
// Instead of regular Kubernetes Service we create Headless Service. DNS of such service points directly to Pod IP address.
410
// You can find more about Headless Services in Kubernetes documentation https://kubernetes.io/docs/user-guide/services/#headless-services
411
func (k *Kubernetes) CreateHeadlessService(name string, service kobject.ServiceConfig) *api.Service {
4✔
412
        svc := k.InitSvc(name, service)
4✔
413

4✔
414
        var servicePorts []api.ServicePort
4✔
415
        // Configure a dummy port: https://github.com/kubernetes/kubernetes/issues/32766.
4✔
416
        servicePorts = append(servicePorts, api.ServicePort{
4✔
417
                Name: "headless",
4✔
418
                Port: 55555,
4✔
419
        })
4✔
420

4✔
421
        svc.Spec.Ports = servicePorts
4✔
422
        svc.Spec.ClusterIP = "None"
4✔
423

4✔
424
        // Configure annotations
4✔
425
        annotations := transformer.ConfigAnnotations(service)
4✔
426
        svc.ObjectMeta.Annotations = annotations
4✔
427

4✔
428
        return svc
4✔
429
}
4✔
430

431
// UpdateKubernetesObjectsMultipleContainers method updates the kubernetes objects with the necessary data
432
func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, service kobject.ServiceConfig, objects *[]runtime.Object, podSpec PodSpec) error {
6✔
433
        // Configure annotations
6✔
434
        annotations := transformer.ConfigAnnotations(service)
6✔
435

6✔
436
        // fillTemplate fills the pod template with the value calculated from config
6✔
437
        fillTemplate := func(template *api.PodTemplateSpec) error {
15✔
438
                template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
9✔
439
                template.Spec = podSpec.Get()
9✔
440
                return nil
9✔
441
        }
9✔
442

443
        // fillObjectMeta fills the metadata with the value calculated from config
444
        fillObjectMeta := func(meta *metav1.ObjectMeta) {
15✔
445
                meta.Annotations = annotations
9✔
446
        }
9✔
447

448
        // update supported controller
449
        for _, obj := range *objects {
21✔
450
                err := k.UpdateController(obj, fillTemplate, fillObjectMeta)
15✔
451
                if err != nil {
15✔
452
                        return errors.Wrap(err, "k.UpdateController failed")
×
453
                }
×
454
                if len(service.Volumes) > 0 {
27✔
455
                        switch objType := obj.(type) {
12✔
456
                        case *appsv1.Deployment:
6✔
457
                                objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
6✔
458
                        case *deployapi.DeploymentConfig:
×
459
                                objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate
×
460
                        }
461
                }
462
        }
463
        return nil
6✔
464
}
465

466
// UpdateKubernetesObjects loads configurations to k8s objects
467
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error {
30✔
468
        // Configure the environment variables.
30✔
469
        envs, err := ConfigEnvs(service, opt)
30✔
470
        if err != nil {
30✔
471
                return errors.Wrap(err, "Unable to load env variables")
×
472
        }
×
473

474
        // Configure the container volumes.
475
        volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service)
30✔
476
        if err != nil {
30✔
477
                return errors.Wrap(err, "k.ConfigVolumes failed")
×
478
        }
×
479
        // Configure Tmpfs
480
        if len(service.TmpFs) > 0 {
42✔
481
                TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service)
12✔
482
                volumes = append(volumes, TmpVolumes...)
12✔
483
                volumesMount = append(volumesMount, TmpVolumesMount...)
12✔
484
        }
12✔
485

486
        if pvc != nil && opt.Controller != StatefulStateController {
41✔
487
                // Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
11✔
488
                // because the type of objects and pvc is different, but when doing append
11✔
489
                // one element at a time it gets converted to runtime.Object for objects slice
11✔
490
                for _, p := range pvc {
22✔
491
                        *objects = append(*objects, p)
11✔
492
                }
11✔
493
        }
494

495
        for _, c := range cms {
30✔
496
                *objects = append(*objects, c)
×
497
        }
×
498

499
        // Configure the container ports.
500
        ports := ConfigPorts(service)
30✔
501
        // Configure capabilities
30✔
502
        capabilities := ConfigCapabilities(service)
30✔
503

30✔
504
        // Configure annotations
30✔
505
        annotations := transformer.ConfigAnnotations(service)
30✔
506

30✔
507
        // fillTemplate fills the pod template with the value calculated from config
30✔
508
        fillTemplate := func(template *api.PodTemplateSpec) error {
57✔
509
                template.Spec.Containers[0].Name = GetContainerName(service)
27✔
510
                template.Spec.Containers[0].Env = envs
27✔
511
                template.Spec.Containers[0].Command = service.Command
27✔
512
                template.Spec.Containers[0].Args = GetContainerArgs(service)
27✔
513
                template.Spec.Containers[0].WorkingDir = service.WorkingDir
27✔
514
                template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts, volumesMount...)
27✔
515
                template.Spec.Containers[0].Stdin = service.Stdin
27✔
516
                template.Spec.Containers[0].TTY = service.Tty
27✔
517
                if opt.Controller != StatefulStateController || opt.Volumes == "configMap" {
52✔
518
                        template.Spec.Volumes = append(template.Spec.Volumes, volumes...)
25✔
519
                }
25✔
520
                template.Spec.Affinity = ConfigAffinity(service)
27✔
521
                template.Spec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service)
27✔
522
                // Configure the HealthCheck
27✔
523
                template.Spec.Containers[0].LivenessProbe = configProbe(service.HealthChecks.Liveness)
27✔
524
                template.Spec.Containers[0].ReadinessProbe = configProbe(service.HealthChecks.Readiness)
27✔
525

27✔
526
                if service.StopGracePeriod != "" {
27✔
527
                        template.Spec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod)
×
528
                        if err != nil {
×
529
                                log.Warningf("Failed to parse duration \"%v\" for service \"%v\"", service.StopGracePeriod, name)
×
530
                        }
×
531
                }
532

533
                TranslatePodResource(&service, template)
27✔
534

27✔
535
                // Configure resource reservations
27✔
536
                podSecurityContext := &api.PodSecurityContext{}
27✔
537

27✔
538
                //set pid namespace mode
27✔
539
                if service.Pid != "" {
29✔
540
                        if service.Pid == "host" {
3✔
541
                                // podSecurityContext.HostPID = true
1✔
542
                        } else {
2✔
543
                                log.Warningf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, service.Pid)
1✔
544
                        }
1✔
545
                }
546

547
                //set supplementalGroups
548
                if service.GroupAdd != nil {
38✔
549
                        podSecurityContext.SupplementalGroups = service.GroupAdd
11✔
550
                }
11✔
551

552
                // Setup security context
553
                securityContext := &api.SecurityContext{}
27✔
554
                if service.Privileged {
42✔
555
                        securityContext.Privileged = &service.Privileged
15✔
556
                }
15✔
557
                if service.User != "" {
28✔
558
                        uid, err := strconv.ParseInt(service.User, 10, 64)
1✔
559
                        if err != nil {
1✔
560
                                log.Warn("Ignoring user directive. User to be specified as a UID (numeric).")
×
561
                        } else {
1✔
562
                                securityContext.RunAsUser = &uid
1✔
563
                        }
1✔
564
                }
565

566
                //set capabilities if it is not empty
567
                if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 {
42✔
568
                        securityContext.Capabilities = capabilities
15✔
569
                }
15✔
570

571
                // update template only if securityContext is not empty
572
                if *securityContext != (api.SecurityContext{}) {
42✔
573
                        template.Spec.Containers[0].SecurityContext = securityContext
15✔
574
                }
15✔
575
                if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) {
38✔
576
                        template.Spec.SecurityContext = podSecurityContext
11✔
577
                }
11✔
578
                template.Spec.Containers[0].Ports = ports
27✔
579
                template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
27✔
580

27✔
581
                // Configure the image pull policy
27✔
582
                policy, err := GetImagePullPolicy(name, service.ImagePullPolicy)
27✔
583
                if err != nil {
27✔
584
                        return err
×
585
                }
×
586
                template.Spec.Containers[0].ImagePullPolicy = policy
27✔
587

27✔
588
                // Configure the container restart policy.
27✔
589
                restart, err := GetRestartPolicy(name, service.Restart)
27✔
590
                if err != nil {
27✔
591
                        return err
×
592
                }
×
593
                template.Spec.RestartPolicy = restart
27✔
594

27✔
595
                // Configure hostname/domain_name settings
27✔
596
                if service.HostName != "" {
27✔
597
                        template.Spec.Hostname = service.HostName
×
598
                }
×
599
                if service.DomainName != "" {
27✔
600
                        template.Spec.Subdomain = service.DomainName
×
601
                }
×
602

603
                if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
28✔
604
                        template.Spec.ServiceAccountName = serviceAccountName
1✔
605
                }
1✔
606

607
                return nil
27✔
608
        }
609

610
        // fillObjectMeta fills the metadata with the value calculated from config
611
        fillObjectMeta := func(meta *metav1.ObjectMeta) {
55✔
612
                meta.Annotations = annotations
25✔
613
        }
25✔
614

615
        // update supported controller
616
        for _, obj := range *objects {
108✔
617
                err = k.UpdateController(obj, fillTemplate, fillObjectMeta)
78✔
618
                if err != nil {
78✔
619
                        return errors.Wrap(err, "k.UpdateController failed")
×
620
                }
×
621
                if len(service.Volumes) > 0 {
127✔
622
                        switch objType := obj.(type) {
49✔
623
                        case *appsv1.Deployment:
7✔
624
                                objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
7✔
625
                        case *deployapi.DeploymentConfig:
×
626
                                objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate
×
627
                        case *appsv1.StatefulSet:
2✔
628
                                // embed all PVCs inside the StatefulSet object
2✔
629
                                if opt.Volumes == "configMap" {
2✔
630
                                        break
×
631
                                }
632
                                persistentVolumeClaims := make([]api.PersistentVolumeClaim, len(pvc))
2✔
633
                                for i, persistentVolumeClaim := range pvc {
4✔
634
                                        persistentVolumeClaims[i] = *persistentVolumeClaim
2✔
635
                                        persistentVolumeClaims[i].APIVersion = ""
2✔
636
                                        persistentVolumeClaims[i].Kind = ""
2✔
637
                                }
2✔
638
                                objType.Spec.VolumeClaimTemplates = persistentVolumeClaims
2✔
639
                        }
640
                }
641
        }
642
        return nil
30✔
643
}
644

645
// getServiceVolumesID create a unique id for the service's volume mounts
646
func getServiceVolumesID(service kobject.ServiceConfig) string {
×
647
        id := ""
×
648
        for _, v := range service.VolList {
×
649
                id += v
×
650
        }
×
651
        return id
×
652
}
653

654
// getServiceGroupID ...
655
// return empty string should mean this service should go alone
656
func getServiceGroupID(service kobject.ServiceConfig, mode string) string {
8✔
657
        if mode == "label" {
14✔
658
                return service.Labels[compose.LabelServiceGroup]
6✔
659
        }
6✔
660
        if mode == "volume" {
2✔
661
                return getServiceVolumesID(service)
×
662
        }
×
663
        return ""
2✔
664
}
665

666
// KomposeObjectToServiceConfigGroupMapping returns the service config group by name or by volume
667
// This group function works as following
668
//  1. Support two mode
669
//     (1): label: use a custom label, the service that contains it will be merged to one workload.
670
//     (2): volume: the service that share to exactly same volume config will be merged to one workload. If use pvc, only
671
//     create one for this group.
672
//  2. If service containers restart policy and no workload argument provide and it's restart policy looks like a pod, then
673
//     this service should generate a pod. If group mode specified, it should be grouped and ignore the restart policy.
674
//  3. If group mode specified, port conflict between services in one group will be ignored, and multiple service should be created.
675
//  4. If `volume` group mode specified, we don't have an appropriate name for this combined service, use the first one for now.
676
//     A warn/info message should be printed to let the user know.
677
func KomposeObjectToServiceConfigGroupMapping(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) map[string]kobject.ServiceConfigGroup {
5✔
678
        serviceConfigGroup := make(map[string]kobject.ServiceConfigGroup)
5✔
679

5✔
680
        for name, service := range komposeObject.ServiceConfigs {
13✔
681
                groupID := getServiceGroupID(service, opt.ServiceGroupMode)
8✔
682
                if groupID != "" {
14✔
683
                        service.Name = name
6✔
684
                        service.InGroup = true
6✔
685
                        serviceConfigGroup[groupID] = append(serviceConfigGroup[groupID], service)
6✔
686
                        komposeObject.ServiceConfigs[name] = service
6✔
687
                }
6✔
688
        }
689

690
        return serviceConfigGroup
5✔
691
}
692

693
// TranslatePodResource config pod resources
694
func TranslatePodResource(service *kobject.ServiceConfig, template *api.PodTemplateSpec) {
27✔
695
        // Configure the resource limits
27✔
696
        if service.MemLimit != 0 || service.CPULimit != 0 {
29✔
697
                resourceLimit := api.ResourceList{}
2✔
698

2✔
699
                if service.MemLimit != 0 {
3✔
700
                        resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
1✔
701
                }
1✔
702

703
                if service.CPULimit != 0 {
3✔
704
                        resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI)
1✔
705
                }
1✔
706

707
                template.Spec.Containers[0].Resources.Limits = resourceLimit
2✔
708
        }
709

710
        // Configure the resource requests
711
        if service.MemReservation != 0 || service.CPUReservation != 0 {
29✔
712
                resourceRequests := api.ResourceList{}
2✔
713

2✔
714
                if service.MemReservation != 0 {
3✔
715
                        resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat")
1✔
716
                }
1✔
717

718
                if service.CPUReservation != 0 {
3✔
719
                        resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI)
1✔
720
                }
1✔
721

722
                template.Spec.Containers[0].Resources.Requests = resourceRequests
2✔
723
        }
724
}
725

726
// GetImagePullPolicy get image pull settings
727
func GetImagePullPolicy(name, policy string) (api.PullPolicy, error) {
33✔
728
        switch policy {
33✔
729
        case "":
33✔
730
        case "Always":
×
731
                return api.PullAlways, nil
×
732
        case "Never":
×
733
                return api.PullNever, nil
×
734
        case "IfNotPresent":
×
735
                return api.PullIfNotPresent, nil
×
736
        default:
×
737
                return "", errors.New("Unknown image-pull-policy " + policy + " for service " + name)
×
738
        }
739
        return "", nil
33✔
740
}
741

742
// GetRestartPolicy ...
743
func GetRestartPolicy(name, restart string) (api.RestartPolicy, error) {
33✔
744
        switch restart {
33✔
745
        case "", "always", "any":
31✔
746
                return api.RestartPolicyAlways, nil
31✔
747
        case "no", "none":
1✔
748
                return api.RestartPolicyNever, nil
1✔
749
        case "on-failure":
1✔
750
                return api.RestartPolicyOnFailure, nil
1✔
751
        default:
×
752
                return "", errors.New("Unknown restart policy " + restart + " for service " + name)
×
753
        }
754
}
755

756
// SortServicesFirst - the objects that we get can be in any order this keeps services first
757
// according to best practice kubernetes services should be created first
758
// http://kubernetes.io/docs/user-guide/config-best-practices/
759
func (k *Kubernetes) SortServicesFirst(objs *[]runtime.Object) {
33✔
760
        var svc, others, ret []runtime.Object
33✔
761

33✔
762
        for _, obj := range *objs {
157✔
763
                if obj.GetObjectKind().GroupVersionKind().Kind == "Service" {
150✔
764
                        svc = append(svc, obj)
26✔
765
                } else {
124✔
766
                        others = append(others, obj)
98✔
767
                }
98✔
768
        }
769
        ret = append(ret, svc...)
33✔
770
        ret = append(ret, others...)
33✔
771
        *objs = ret
33✔
772
}
773

774
// RemoveDupObjects remove objects that are dups...eg. configmaps from env.
775
// since we know for sure that the duplication can only happen on ConfigMap, so
776
// this code will looks like this for now.
777
// + NetworkPolicy
778
func (k *Kubernetes) RemoveDupObjects(objs *[]runtime.Object) {
33✔
779
        var result []runtime.Object
33✔
780
        exist := map[string]bool{}
33✔
781
        for _, obj := range *objs {
157✔
782
                if us, ok := obj.(metav1.Object); ok {
248✔
783
                        k := obj.GetObjectKind().GroupVersionKind().String() + us.GetNamespace() + us.GetName()
124✔
784
                        if exist[k] {
129✔
785
                                log.Debugf("Remove duplicate resource: %s/%s", obj.GetObjectKind().GroupVersionKind().Kind, us.GetName())
5✔
786
                                continue
5✔
787
                        } else {
119✔
788
                                result = append(result, obj)
119✔
789
                                exist[k] = true
119✔
790
                        }
119✔
791
                } else {
×
792
                        result = append(result, obj)
×
793
                }
×
794
        }
795
        *objs = result
33✔
796
}
797

798
// SortedKeys Ensure the kubernetes objects are in a consistent order
799
func SortedKeys(komposeObject kobject.KomposeObject) []string {
34✔
800
        var sortedKeys []string
34✔
801
        for name := range komposeObject.ServiceConfigs {
72✔
802
                sortedKeys = append(sortedKeys, name)
38✔
803
        }
38✔
804
        sort.Strings(sortedKeys)
34✔
805
        return sortedKeys
34✔
806
}
807

808
// DurationStrToSecondsInt converts duration string to *int64 in seconds
809
func DurationStrToSecondsInt(s string) (*int64, error) {
5✔
810
        if s == "" {
6✔
811
                return nil, nil
1✔
812
        }
1✔
813
        duration, err := time.ParseDuration(s)
4✔
814
        if err != nil {
6✔
815
                return nil, err
2✔
816
        }
2✔
817
        r := (int64)(duration.Seconds())
2✔
818
        return &r, nil
2✔
819
}
820

821
// GetEnvsFromFile get env vars from env_file
822
func GetEnvsFromFile(file string, opt kobject.ConvertOptions) (map[string]string, error) {
×
823
        // Get the correct file context / directory
×
824
        composeDir, err := transformer.GetComposeFileDir(opt.InputFiles)
×
825
        if err != nil {
×
826
                return nil, errors.Wrap(err, "Unable to load file context")
×
827
        }
×
828
        fileLocation := path.Join(composeDir, file)
×
829

×
830
        // Load environment variables from file
×
831
        envLoad, err := godotenv.Read(fileLocation)
×
832
        if err != nil {
×
833
                return nil, errors.Wrap(err, "Unable to read env_file")
×
834
        }
×
835

836
        return envLoad, nil
×
837
}
838

839
// GetContentFromFile gets the content from the file..
840
func GetContentFromFile(file string) (string, error) {
12✔
841
        fileBytes, err := os.ReadFile(file)
12✔
842
        if err != nil {
12✔
843
                return "", errors.Wrap(err, "Unable to read file")
×
844
        }
×
845
        return string(fileBytes), nil
12✔
846
}
847

848
// FormatEnvName format env name
849
func FormatEnvName(name string) string {
×
850
        envName := strings.Trim(name, "./")
×
851
        envName = strings.Replace(envName, ".", "-", -1)
×
852
        envName = strings.Replace(envName, "/", "-", -1)
×
853
        return envName
×
854
}
×
855

856
// FormatFileName format file name
857
func FormatFileName(name string) string {
20✔
858
        // Split the filepath name so that we use the
20✔
859
        // file name (after the base) for ConfigMap,
20✔
860
        // it shouldn't matter whether it has special characters or not
20✔
861
        _, file := path.Split(name)
20✔
862

20✔
863
        // Make it DNS-1123 compliant for Kubernetes
20✔
864
        return strings.Replace(file, "_", "-", -1)
20✔
865
}
20✔
866

867
// FormatContainerName format Container name
868
func FormatContainerName(name string) string {
42✔
869
        name = strings.Replace(name, "_", "-", -1)
42✔
870
        return name
42✔
871
}
42✔
872

873
// GetContainerName returns the name of the container, from the service config object
874
func GetContainerName(service kobject.ServiceConfig) string {
42✔
875
        name := service.Name
42✔
876
        if len(service.ContainerName) > 0 {
77✔
877
                name = service.ContainerName
35✔
878
        }
35✔
879
        return FormatContainerName(name)
42✔
880
}
881

882
// FormatResourceName generate a valid k8s resource name
883
func FormatResourceName(name string) string {
×
884
        return strings.ToLower(strings.Replace(name, "_", "-", -1))
×
885
}
×
886

887
// GetContainerArgs update the interpolation of env variables if exists.
888
// example: [curl, $PROTOCOL://$DOMAIN] => [curl, $(PROTOCOL)://$(DOMAIN)]
889
func GetContainerArgs(service kobject.ServiceConfig) []string {
27✔
890
        var args []string
27✔
891
        re := regexp.MustCompile(`\$([a-zA-Z0-9]*)`)
27✔
892
        for _, arg := range service.Args {
62✔
893
                arg = re.ReplaceAllString(arg, `$($1)`)
35✔
894
                args = append(args, arg)
35✔
895
        }
35✔
896
        return args
27✔
897
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc