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

kubernetes / kompose / 6696194161

30 Oct 2023 05:02PM UTC coverage: 54.96%. Remained the same
6696194161

push

github

web-flow
chore(deps)(deps): bump golang.org/x/tools from 0.13.0 to 0.14.0 (#1723)

Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

2205 of 4012 relevant lines covered (54.96%)

7.91 hits per line

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

55.4
/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 {
42✔
350
        return len(service.Port) != 0
42✔
351
}
42✔
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 {
26✔
386
        svc := k.InitSvc(name, service)
26✔
387

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

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

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

26✔
403
        return svc
26✔
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 {
36✔
468
        // Configure the environment variables.
36✔
469
        envs, err := ConfigEnvs(service, opt)
36✔
470
        if err != nil {
36✔
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)
36✔
476
        if err != nil {
36✔
477
                return errors.Wrap(err, "k.ConfigVolumes failed")
×
478
        }
×
479
        // Configure Tmpfs
480
        if len(service.TmpFs) > 0 {
52✔
481
                TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service)
16✔
482
                volumes = append(volumes, TmpVolumes...)
16✔
483
                volumesMount = append(volumesMount, TmpVolumesMount...)
16✔
484
        }
16✔
485

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

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

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

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

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

28✔
526
                if service.StopGracePeriod != "" {
28✔
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)
28✔
534

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

28✔
538
                //set pid namespace mode
28✔
539
                if service.Pid != "" {
30✔
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 {
39✔
549
                        podSecurityContext.SupplementalGroups = service.GroupAdd
11✔
550
                }
11✔
551

552
                //set Security Context FsGroup
553
                if service.FsGroup != 0 {
39✔
554
                        podSecurityContext.FSGroup = &service.FsGroup
11✔
555
                }
11✔
556

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

571
                //set capabilities if it is not empty
572
                if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 {
43✔
573
                        securityContext.Capabilities = capabilities
15✔
574
                }
15✔
575

576
                //set readOnlyRootFilesystem if it is enabled
577
                if service.ReadOnly {
29✔
578
                        securityContext.ReadOnlyRootFilesystem = &service.ReadOnly
1✔
579
                }
1✔
580

581
                // update template only if securityContext is not empty
582
                if *securityContext != (api.SecurityContext{}) {
44✔
583
                        template.Spec.Containers[0].SecurityContext = securityContext
16✔
584
                }
16✔
585
                if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) {
39✔
586
                        template.Spec.SecurityContext = podSecurityContext
11✔
587
                }
11✔
588
                template.Spec.Containers[0].Ports = ports
28✔
589
                template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
28✔
590

28✔
591
                // Configure the image pull policy
28✔
592
                policy, err := GetImagePullPolicy(name, service.ImagePullPolicy)
28✔
593
                if err != nil {
28✔
594
                        return err
×
595
                }
×
596
                template.Spec.Containers[0].ImagePullPolicy = policy
28✔
597

28✔
598
                // Configure the container restart policy.
28✔
599
                restart, err := GetRestartPolicy(name, service.Restart)
28✔
600
                if err != nil {
28✔
601
                        return err
×
602
                }
×
603
                template.Spec.RestartPolicy = restart
28✔
604

28✔
605
                // Configure hostname/domain_name settings
28✔
606
                if service.HostName != "" {
28✔
607
                        template.Spec.Hostname = service.HostName
×
608
                }
×
609
                if service.DomainName != "" {
28✔
610
                        template.Spec.Subdomain = service.DomainName
×
611
                }
×
612

613
                if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
29✔
614
                        template.Spec.ServiceAccountName = serviceAccountName
1✔
615
                }
1✔
616

617
                return nil
28✔
618
        }
619

620
        // fillObjectMeta fills the metadata with the value calculated from config
621
        fillObjectMeta := func(meta *metav1.ObjectMeta) {
62✔
622
                meta.Annotations = annotations
26✔
623
        }
26✔
624

625
        // update supported controller
626
        for _, obj := range *objects {
127✔
627
                err = k.UpdateController(obj, fillTemplate, fillObjectMeta)
91✔
628
                if err != nil {
91✔
629
                        return errors.Wrap(err, "k.UpdateController failed")
×
630
                }
×
631
                if len(service.Volumes) > 0 {
152✔
632
                        switch objType := obj.(type) {
61✔
633
                        case *appsv1.Deployment:
7✔
634
                                objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
7✔
635
                        case *deployapi.DeploymentConfig:
×
636
                                objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate
×
637
                        case *appsv1.StatefulSet:
2✔
638
                                // embed all PVCs inside the StatefulSet object
2✔
639
                                if opt.Volumes == "configMap" {
2✔
640
                                        break
×
641
                                }
642
                                persistentVolumeClaims := make([]api.PersistentVolumeClaim, len(pvc))
2✔
643
                                for i, persistentVolumeClaim := range pvc {
4✔
644
                                        persistentVolumeClaims[i] = *persistentVolumeClaim
2✔
645
                                        persistentVolumeClaims[i].APIVersion = ""
2✔
646
                                        persistentVolumeClaims[i].Kind = ""
2✔
647
                                }
2✔
648
                                objType.Spec.VolumeClaimTemplates = persistentVolumeClaims
2✔
649
                        }
650
                }
651
        }
652
        return nil
36✔
653
}
654

655
// getServiceVolumesID create a unique id for the service's volume mounts
656
func getServiceVolumesID(service kobject.ServiceConfig) string {
×
657
        id := ""
×
658
        for _, v := range service.VolList {
×
659
                id += v
×
660
        }
×
661
        return id
×
662
}
663

664
// getServiceGroupID ...
665
// return empty string should mean this service should go alone
666
func getServiceGroupID(service kobject.ServiceConfig, mode string) string {
11✔
667
        if mode == "label" {
17✔
668
                return service.Labels[compose.LabelServiceGroup]
6✔
669
        }
6✔
670
        if mode == "volume" {
5✔
671
                return getServiceVolumesID(service)
×
672
        }
×
673
        return ""
5✔
674
}
675

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

8✔
691
        for _, service := range sortedServiceConfigs {
19✔
692
                serviceConfig := komposeObject.ServiceConfigs[service]
11✔
693
                groupID := getServiceGroupID(serviceConfig, opt.ServiceGroupMode)
11✔
694
                if groupID != "" {
17✔
695
                        serviceConfig.Name = service
6✔
696
                        serviceConfig.InGroup = true
6✔
697
                        serviceConfigGroup[groupID] = append(serviceConfigGroup[groupID], serviceConfig)
6✔
698
                        komposeObject.ServiceConfigs[service] = serviceConfig
6✔
699
                }
6✔
700
        }
701

702
        return serviceConfigGroup
8✔
703
}
704

705
// TranslatePodResource config pod resources
706
func TranslatePodResource(service *kobject.ServiceConfig, template *api.PodTemplateSpec) {
28✔
707
        // Configure the resource limits
28✔
708
        if service.MemLimit != 0 || service.CPULimit != 0 {
30✔
709
                resourceLimit := api.ResourceList{}
2✔
710

2✔
711
                if service.MemLimit != 0 {
3✔
712
                        resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
1✔
713
                }
1✔
714

715
                if service.CPULimit != 0 {
3✔
716
                        resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI)
1✔
717
                }
1✔
718

719
                template.Spec.Containers[0].Resources.Limits = resourceLimit
2✔
720
        }
721

722
        // Configure the resource requests
723
        if service.MemReservation != 0 || service.CPUReservation != 0 {
30✔
724
                resourceRequests := api.ResourceList{}
2✔
725

2✔
726
                if service.MemReservation != 0 {
3✔
727
                        resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat")
1✔
728
                }
1✔
729

730
                if service.CPUReservation != 0 {
3✔
731
                        resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI)
1✔
732
                }
1✔
733

734
                template.Spec.Containers[0].Resources.Requests = resourceRequests
2✔
735
        }
736
}
737

738
// GetImagePullPolicy get image pull settings
739
func GetImagePullPolicy(name, policy string) (api.PullPolicy, error) {
34✔
740
        switch policy {
34✔
741
        case "":
34✔
742
        case "Always":
×
743
                return api.PullAlways, nil
×
744
        case "Never":
×
745
                return api.PullNever, nil
×
746
        case "IfNotPresent":
×
747
                return api.PullIfNotPresent, nil
×
748
        default:
×
749
                return "", errors.New("Unknown image-pull-policy " + policy + " for service " + name)
×
750
        }
751
        return "", nil
34✔
752
}
753

754
// GetRestartPolicy ...
755
func GetRestartPolicy(name, restart string) (api.RestartPolicy, error) {
34✔
756
        switch restart {
34✔
757
        case "", "always", "any":
32✔
758
                return api.RestartPolicyAlways, nil
32✔
759
        case "no", "none":
1✔
760
                return api.RestartPolicyNever, nil
1✔
761
        case "on-failure":
1✔
762
                return api.RestartPolicyOnFailure, nil
1✔
763
        default:
×
764
                return "", errors.New("Unknown restart policy " + restart + " for service " + name)
×
765
        }
766
}
767

768
// SortServicesFirst - the objects that we get can be in any order this keeps services first
769
// according to best practice kubernetes services should be created first
770
// http://kubernetes.io/docs/user-guide/config-best-practices/
771
func (k *Kubernetes) SortServicesFirst(objs *[]runtime.Object) {
39✔
772
        var svc, others, ret []runtime.Object
39✔
773

39✔
774
        for _, obj := range *objs {
145✔
775
                if obj.GetObjectKind().GroupVersionKind().Kind == "Service" {
136✔
776
                        svc = append(svc, obj)
30✔
777
                } else {
106✔
778
                        others = append(others, obj)
76✔
779
                }
76✔
780
        }
781
        ret = append(ret, svc...)
39✔
782
        ret = append(ret, others...)
39✔
783
        *objs = ret
39✔
784
}
785

786
// RemoveDupObjects remove objects that are dups...eg. configmaps from env.
787
// since we know for sure that the duplication can only happen on ConfigMap, so
788
// this code will looks like this for now.
789
// + NetworkPolicy
790
func (k *Kubernetes) RemoveDupObjects(objs *[]runtime.Object) {
39✔
791
        var result []runtime.Object
39✔
792
        exist := map[string]bool{}
39✔
793
        for _, obj := range *objs {
145✔
794
                if us, ok := obj.(metav1.Object); ok {
212✔
795
                        k := obj.GetObjectKind().GroupVersionKind().String() + us.GetNamespace() + us.GetName()
106✔
796
                        if exist[k] {
111✔
797
                                log.Debugf("Remove duplicate resource: %s/%s", obj.GetObjectKind().GroupVersionKind().Kind, us.GetName())
5✔
798
                                continue
5✔
799
                        } else {
101✔
800
                                result = append(result, obj)
101✔
801
                                exist[k] = true
101✔
802
                        }
101✔
803
                } else {
×
804
                        result = append(result, obj)
×
805
                }
×
806
        }
807
        *objs = result
39✔
808
}
809

810
// SortedKeys Ensure the kubernetes objects are in a consistent order
811
func SortedKeys[V kobject.ServiceConfig | kobject.ServiceConfigGroup](serviceConfig map[string]V) []string {
56✔
812
        var sortedKeys []string
56✔
813
        for name := range serviceConfig {
114✔
814
                sortedKeys = append(sortedKeys, name)
58✔
815
        }
58✔
816
        sort.Strings(sortedKeys)
56✔
817
        return sortedKeys
56✔
818
}
819

820
// DurationStrToSecondsInt converts duration string to *int64 in seconds
821
func DurationStrToSecondsInt(s string) (*int64, error) {
5✔
822
        if s == "" {
6✔
823
                return nil, nil
1✔
824
        }
1✔
825
        duration, err := time.ParseDuration(s)
4✔
826
        if err != nil {
6✔
827
                return nil, err
2✔
828
        }
2✔
829
        r := (int64)(duration.Seconds())
2✔
830
        return &r, nil
2✔
831
}
832

833
// GetEnvsFromFile get env vars from env_file
834
func GetEnvsFromFile(file string) (map[string]string, error) {
×
835

×
836
        envLoad, err := godotenv.Read(file)
×
837
        if err != nil {
×
838
                return nil, errors.Wrap(err, "Unable to read env_file")
×
839
        }
×
840

841
        return envLoad, nil
×
842
}
843

844
// GetContentFromFile gets the content from the file..
845
func GetContentFromFile(file string) (string, error) {
16✔
846
        fileBytes, err := os.ReadFile(file)
16✔
847
        if err != nil {
16✔
848
                return "", errors.Wrap(err, "Unable to read file")
×
849
        }
×
850
        return string(fileBytes), nil
16✔
851
}
852

853
// FormatEnvName format env name
854
func FormatEnvName(name string) string {
×
855
        envName := strings.Trim(name, "./")
×
856
        // only take string after the last slash only if the string contains a slash
×
857
        if strings.Contains(envName, "/") {
×
858
                envName = envName[strings.LastIndex(envName, "/")+1:]
×
859
        }
×
860
        // take only last 63 chars
861
        if len(envName) > 63 {
×
862
                envName = envName[len(envName)-63:]
×
863
        }
×
864
        envName = strings.Replace(envName, ".", "-", -1)
×
865
        return envName
×
866
}
867

868
// FormatFileName format file name
869
func FormatFileName(name string) string {
24✔
870
        // Split the filepath name so that we use the
24✔
871
        // file name (after the base) for ConfigMap,
24✔
872
        // it shouldn't matter whether it has special characters or not
24✔
873
        _, file := path.Split(name)
24✔
874

24✔
875
        // Make it DNS-1123 compliant for Kubernetes
24✔
876
        return strings.Replace(file, "_", "-", -1)
24✔
877
}
24✔
878

879
// FormatContainerName format Container name
880
func FormatContainerName(name string) string {
43✔
881
        name = strings.Replace(name, "_", "-", -1)
43✔
882
        return name
43✔
883
}
43✔
884

885
// GetContainerName returns the name of the container, from the service config object
886
func GetContainerName(service kobject.ServiceConfig) string {
43✔
887
        name := service.Name
43✔
888
        if len(service.ContainerName) > 0 {
79✔
889
                name = service.ContainerName
36✔
890
        }
36✔
891
        return FormatContainerName(name)
43✔
892
}
893

894
// FormatResourceName generate a valid k8s resource name
895
func FormatResourceName(name string) string {
×
896
        return strings.ToLower(strings.Replace(name, "_", "-", -1))
×
897
}
×
898

899
// GetContainerArgs update the interpolation of env variables if exists.
900
// example: [curl, $PROTOCOL://$DOMAIN] => [curl, $(PROTOCOL)://$(DOMAIN)]
901
func GetContainerArgs(service kobject.ServiceConfig) []string {
28✔
902
        var args []string
28✔
903
        re := regexp.MustCompile(`\$([a-zA-Z0-9]*)`)
28✔
904
        for _, arg := range service.Args {
63✔
905
                arg = re.ReplaceAllString(arg, `$($1)`)
35✔
906
                args = append(args, arg)
35✔
907
        }
35✔
908
        return args
28✔
909
}
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