• 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

62.31
/pkg/transformer/kubernetes/kubernetes.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
        "encoding/base64"
21
        "fmt"
22
        "os"
23
        "path"
24
        "path/filepath"
25
        "reflect"
26
        "regexp"
27
        "sort"
28
        "strconv"
29
        "strings"
30

31
        "github.com/compose-spec/compose-go/types"
32
        "github.com/fatih/structs"
33
        "github.com/kubernetes/kompose/pkg/kobject"
34
        "github.com/kubernetes/kompose/pkg/loader/compose"
35
        "github.com/kubernetes/kompose/pkg/transformer"
36
        deployapi "github.com/openshift/api/apps/v1"
37
        buildapi "github.com/openshift/api/build/v1"
38
        "github.com/pkg/errors"
39
        log "github.com/sirupsen/logrus"
40
        "github.com/spf13/cast"
41
        "golang.org/x/tools/godoc/util"
42
        appsv1 "k8s.io/api/apps/v1"
43
        api "k8s.io/api/core/v1"
44
        networkingv1 "k8s.io/api/networking/v1"
45
        "k8s.io/apimachinery/pkg/api/resource"
46
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
47
        "k8s.io/apimachinery/pkg/runtime"
48
        "k8s.io/apimachinery/pkg/util/intstr"
49
)
50

51
// Kubernetes implements Transformer interface and represents Kubernetes transformer
52
type Kubernetes struct {
53
        // the user provided options from the command line
54
        Opt kobject.ConvertOptions
55
}
56

57
// PVCRequestSize (Persistent Volume Claim) has default size
58
const PVCRequestSize = "100Mi"
59

60
// ValidVolumeSet has the different types of valid volumes
61
var ValidVolumeSet = map[string]struct{}{"emptyDir": {}, "hostPath": {}, "configMap": {}, "persistentVolumeClaim": {}}
62

63
const (
64
        // DeploymentController is controller type for Deployment
65
        DeploymentController = "deployment"
66
        // DaemonSetController is controller type for DaemonSet
67
        DaemonSetController = "daemonset"
68
        // StatefulStateController is controller type for StatefulSet
69
        StatefulStateController = "statefulset"
70
)
71

72
// CheckUnsupportedKey checks if given komposeObject contains
73
// keys that are not supported by this transformer.
74
// list of all unsupported keys are stored in unsupportedKey variable
75
// returns list of TODO: ....
76
func (k *Kubernetes) CheckUnsupportedKey(komposeObject *kobject.KomposeObject, unsupportedKey map[string]bool) []string {
×
77
        // collect all keys found in project
×
78
        var keysFound []string
×
79

×
80
        for _, serviceConfig := range komposeObject.ServiceConfigs {
×
81
                // this reflection is used in check for empty arrays
×
82
                val := reflect.ValueOf(serviceConfig)
×
83
                s := structs.New(serviceConfig)
×
84

×
85
                for _, f := range s.Fields() {
×
86
                        // Check if given key is among unsupported keys, and skip it if we already saw this key
×
87
                        if alreadySaw, ok := unsupportedKey[f.Name()]; ok && !alreadySaw {
×
88
                                if f.IsExported() && !f.IsZero() {
×
89
                                        // IsZero returns false for empty array/slice ([])
×
90
                                        // this check if field is Slice, and then it checks its size
×
91
                                        if field := val.FieldByName(f.Name()); field.Kind() == reflect.Slice {
×
92
                                                if field.Len() == 0 {
×
93
                                                        // array is empty it doesn't matter if it is in unsupportedKey or not
×
94
                                                        continue
×
95
                                                }
96
                                        }
97
                                        //get tag from kobject service configure
98
                                        tag := f.Tag(komposeObject.LoadedFrom)
×
99
                                        keysFound = append(keysFound, tag)
×
100
                                        unsupportedKey[f.Name()] = true
×
101
                                }
102
                        }
103
                }
104
        }
105
        return keysFound
×
106
}
107

108
// InitPodSpec creates the pod specification
109
func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) api.PodSpec {
26✔
110
        if image == "" {
26✔
111
                image = name
×
112
        }
×
113
        pod := api.PodSpec{
26✔
114
                Containers: []api.Container{
26✔
115
                        {
26✔
116
                                Name:  name,
26✔
117
                                Image: image,
26✔
118
                        },
26✔
119
                },
26✔
120
        }
26✔
121
        if pullSecret != "" {
29✔
122
                pod.ImagePullSecrets = []api.LocalObjectReference{
3✔
123
                        {
3✔
124
                                Name: pullSecret,
3✔
125
                        },
3✔
126
                }
3✔
127
        }
3✔
128
        return pod
26✔
129
}
130

131
// InitPodSpecWithConfigMap creates the pod specification
132
func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service kobject.ServiceConfig) api.PodSpec {
8✔
133
        var volumeMounts []api.VolumeMount
8✔
134
        var volumes []api.Volume
8✔
135

8✔
136
        for _, value := range service.Configs {
16✔
137
                cmVolName := FormatFileName(value.Source)
8✔
138
                target := value.Target
8✔
139
                if target == "" {
8✔
140
                        // short syntax, = /<source>
×
141
                        target = "/" + value.Source
×
142
                }
×
143
                subPath := filepath.Base(target)
8✔
144

8✔
145
                volSource := api.ConfigMapVolumeSource{}
8✔
146
                volSource.Name = cmVolName
8✔
147
                key, err := service.GetConfigMapKeyFromMeta(value.Source)
8✔
148
                if err != nil {
8✔
149
                        log.Warnf("cannot parse config %s , %s", value.Source, err.Error())
×
150
                        // mostly it's external
×
151
                        continue
×
152
                }
153
                volSource.Items = []api.KeyToPath{{
8✔
154
                        Key:  key,
8✔
155
                        Path: subPath,
8✔
156
                }}
8✔
157

8✔
158
                if value.Mode != nil {
8✔
159
                        tmpMode := int32(*value.Mode)
×
160
                        volSource.DefaultMode = &tmpMode
×
161
                }
×
162

163
                cmVol := api.Volume{
8✔
164
                        Name:         cmVolName,
8✔
165
                        VolumeSource: api.VolumeSource{ConfigMap: &volSource},
8✔
166
                }
8✔
167

8✔
168
                volumeMounts = append(volumeMounts,
8✔
169
                        api.VolumeMount{
8✔
170
                                Name:      cmVolName,
8✔
171
                                MountPath: target,
8✔
172
                                SubPath:   subPath,
8✔
173
                        })
8✔
174
                volumes = append(volumes, cmVol)
8✔
175
        }
176

177
        pod := api.PodSpec{
8✔
178
                Containers: []api.Container{
8✔
179
                        {
8✔
180
                                Name:         name,
8✔
181
                                Image:        image,
8✔
182
                                VolumeMounts: volumeMounts,
8✔
183
                        },
8✔
184
                },
8✔
185
                Volumes: volumes,
8✔
186
        }
8✔
187

8✔
188
        if service.ImagePullSecret != "" {
16✔
189
                pod.ImagePullSecrets = []api.LocalObjectReference{
8✔
190
                        {
8✔
191
                                Name: service.ImagePullSecret,
8✔
192
                        },
8✔
193
                }
8✔
194
        }
8✔
195
        return pod
8✔
196
}
197

198
// InitSvc initializes Kubernetes Service object
199
// The created service name will = ServiceConfig.Name, but the selector may be not.
200
// If this service is grouped, the selector may be another name = name
201
func (k *Kubernetes) InitSvc(name string, service kobject.ServiceConfig) *api.Service {
27✔
202
        svc := &api.Service{
27✔
203
                TypeMeta: metav1.TypeMeta{
27✔
204
                        Kind:       "Service",
27✔
205
                        APIVersion: "v1",
27✔
206
                },
27✔
207
                ObjectMeta: metav1.ObjectMeta{
27✔
208
                        Name:   service.Name,
27✔
209
                        Labels: transformer.ConfigLabels(name),
27✔
210
                },
27✔
211
                // The selector uses the service.Name, which must be consistent with workloads label
27✔
212
                Spec: api.ServiceSpec{
27✔
213
                        Selector: transformer.ConfigLabels(name),
27✔
214
                },
27✔
215
        }
27✔
216
        return svc
27✔
217
}
27✔
218

219
// InitConfigMapForEnv initializes a ConfigMap object
220
func (k *Kubernetes) InitConfigMapForEnv(name string, opt kobject.ConvertOptions, envFile string) *api.ConfigMap {
×
221
        envs, err := GetEnvsFromFile(envFile, opt)
×
222
        if err != nil {
×
223
                log.Fatalf("Unable to retrieve env file: %s", err)
×
224
        }
×
225

226
        // Remove root pathing
227
        // replace all other slashes / periods
228
        envName := FormatEnvName(envFile)
×
229

×
230
        // In order to differentiate files, we append to the name and remove '.env' if applicable from the file name
×
231
        configMap := &api.ConfigMap{
×
232
                TypeMeta: metav1.TypeMeta{
×
233
                        Kind:       "ConfigMap",
×
234
                        APIVersion: "v1",
×
235
                },
×
236
                ObjectMeta: metav1.ObjectMeta{
×
237
                        Name:   envName,
×
238
                        Labels: transformer.ConfigLabels(name + "-" + envName),
×
239
                },
×
240
                Data: envs,
×
241
        }
×
242

×
243
        return configMap
×
244
}
245

246
// IntiConfigMapFromFileOrDir will create a configmap from dir or file
247
// usage:
248
//  1. volume
249
func (k *Kubernetes) IntiConfigMapFromFileOrDir(name, cmName, filePath string, service kobject.ServiceConfig) (*api.ConfigMap, error) {
×
250
        configMap := &api.ConfigMap{
×
251
                TypeMeta: metav1.TypeMeta{
×
252
                        Kind:       "ConfigMap",
×
253
                        APIVersion: "v1",
×
254
                },
×
255
                ObjectMeta: metav1.ObjectMeta{
×
256
                        Name:   cmName,
×
257
                        Labels: transformer.ConfigLabels(name),
×
258
                },
×
259
        }
×
260
        dataMap := make(map[string]string)
×
261

×
262
        fi, err := os.Stat(filePath)
×
263
        if err != nil {
×
264
                return nil, err
×
265
        }
×
266

267
        switch mode := fi.Mode(); {
×
268
        case mode.IsDir():
×
269
                files, err := os.ReadDir(filePath)
×
270
                if err != nil {
×
271
                        return nil, err
×
272
                }
×
273

274
                for _, file := range files {
×
275
                        if !file.IsDir() {
×
276
                                log.Debugf("Read file to ConfigMap: %s", file.Name())
×
277
                                data, err := GetContentFromFile(filePath + "/" + file.Name())
×
278
                                if err != nil {
×
279
                                        return nil, err
×
280
                                }
×
281
                                dataMap[file.Name()] = data
×
282
                        }
283
                }
284
                initConfigMapData(configMap, dataMap)
×
285

286
        case mode.IsRegular():
×
287
                // do file stuff
×
288
                configMap = k.InitConfigMapFromFile(name, service, filePath)
×
289
                configMap.Name = cmName
×
290
                configMap.Annotations = map[string]string{
×
291
                        "use-subpath": "true",
×
292
                }
×
293
        }
294

295
        return configMap, nil
×
296
}
297

298
// useSubPathMount check if a configmap should be mounted as subpath
299
// in this situation, this configmap will only contains 1 key in data
300
func useSubPathMount(cm *api.ConfigMap) bool {
×
301
        if cm.Annotations == nil {
×
302
                return false
×
303
        }
×
304
        if cm.Annotations["use-subpath"] != "true" {
×
305
                return false
×
306
        }
×
307
        return true
×
308
}
309

310
func initConfigMapData(configMap *api.ConfigMap, data map[string]string) {
12✔
311
        stringData := map[string]string{}
12✔
312
        binData := map[string][]byte{}
12✔
313

12✔
314
        for k, v := range data {
24✔
315
                isText := util.IsText([]byte(v))
12✔
316
                if isText {
24✔
317
                        stringData[k] = v
12✔
318
                } else {
12✔
319
                        binData[k] = []byte(base64.StdEncoding.EncodeToString([]byte(v)))
×
320
                }
×
321
        }
322

323
        configMap.Data = stringData
12✔
324
        configMap.BinaryData = binData
12✔
325
}
326

327
// InitConfigMapFromFile initializes a ConfigMap object
328
func (k *Kubernetes) InitConfigMapFromFile(name string, service kobject.ServiceConfig, fileName string) *api.ConfigMap {
12✔
329
        content, err := GetContentFromFile(fileName)
12✔
330
        if err != nil {
12✔
331
                log.Fatalf("Unable to retrieve file: %s", err)
×
332
        }
×
333

334
        configMapName := ""
12✔
335
        for key, tmpConfig := range service.ConfigsMetaData {
24✔
336
                if tmpConfig.File == fileName {
24✔
337
                        configMapName = key
12✔
338
                }
12✔
339
        }
340
        configMap := &api.ConfigMap{
12✔
341
                TypeMeta: metav1.TypeMeta{
12✔
342
                        Kind:       "ConfigMap",
12✔
343
                        APIVersion: "v1",
12✔
344
                },
12✔
345
                ObjectMeta: metav1.ObjectMeta{
12✔
346
                        Name:   FormatFileName(configMapName),
12✔
347
                        Labels: transformer.ConfigLabels(name),
12✔
348
                },
12✔
349
        }
12✔
350

12✔
351
        data := map[string]string{filepath.Base(fileName): content}
12✔
352
        initConfigMapData(configMap, data)
12✔
353
        return configMap
12✔
354
}
355

356
// InitD initializes Kubernetes Deployment object
357
func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int) *appsv1.Deployment {
26✔
358
        var podSpec api.PodSpec
26✔
359
        if len(service.Configs) > 0 {
32✔
360
                podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
6✔
361
        } else {
26✔
362
                podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
20✔
363
        }
20✔
364

365
        rp := int32(replicas)
26✔
366

26✔
367
        dc := &appsv1.Deployment{
26✔
368
                TypeMeta: metav1.TypeMeta{
26✔
369
                        Kind:       "Deployment",
26✔
370
                        APIVersion: "apps/v1",
26✔
371
                },
26✔
372
                ObjectMeta: metav1.ObjectMeta{
26✔
373
                        Name:   name,
26✔
374
                        Labels: transformer.ConfigAllLabels(name, &service),
26✔
375
                },
26✔
376
                Spec: appsv1.DeploymentSpec{
26✔
377
                        Replicas: &rp,
26✔
378
                        Selector: &metav1.LabelSelector{
26✔
379
                                MatchLabels: transformer.ConfigLabels(name),
26✔
380
                        },
26✔
381
                        Template: api.PodTemplateSpec{
26✔
382
                                ObjectMeta: metav1.ObjectMeta{
26✔
383
                                        //Labels: transformer.ConfigLabels(name),
26✔
384
                                        Annotations: transformer.ConfigAnnotations(service),
26✔
385
                                },
26✔
386
                                Spec: podSpec,
26✔
387
                        },
26✔
388
                },
26✔
389
        }
26✔
390
        dc.Spec.Template.Labels = transformer.ConfigLabels(name)
26✔
391

26✔
392
        update := service.GetKubernetesUpdateStrategy()
26✔
393
        if update != nil {
26✔
394
                dc.Spec.Strategy = appsv1.DeploymentStrategy{
×
395
                        Type:          appsv1.RollingUpdateDeploymentStrategyType,
×
396
                        RollingUpdate: update,
×
397
                }
×
398
                ms := ""
×
399
                if update.MaxSurge != nil {
×
400
                        ms = update.MaxSurge.String()
×
401
                }
×
402
                mu := ""
×
403
                if update.MaxUnavailable != nil {
×
404
                        mu = update.MaxUnavailable.String()
×
405
                }
×
406
                log.Debugf("Set deployment '%s' rolling update: MaxSurge: %s, MaxUnavailable: %s", name, ms, mu)
×
407
        }
408

409
        return dc
26✔
410
}
411

412
// InitDS initializes Kubernetes DaemonSet object
413
func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *appsv1.DaemonSet {
3✔
414
        ds := &appsv1.DaemonSet{
3✔
415
                TypeMeta: metav1.TypeMeta{
3✔
416
                        Kind:       "DaemonSet",
3✔
417
                        APIVersion: "apps/v1",
3✔
418
                },
3✔
419
                ObjectMeta: metav1.ObjectMeta{
3✔
420
                        Name:   name,
3✔
421
                        Labels: transformer.ConfigAllLabels(name, &service),
3✔
422
                },
3✔
423
                Spec: appsv1.DaemonSetSpec{
3✔
424
                        Selector: &metav1.LabelSelector{
3✔
425
                                MatchLabels: transformer.ConfigLabels(name),
3✔
426
                        },
3✔
427
                        Template: api.PodTemplateSpec{
3✔
428
                                Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
3✔
429
                        },
3✔
430
                },
3✔
431
        }
3✔
432
        return ds
3✔
433
}
3✔
434

435
// InitSS method initialize a stateful set
436
func (k *Kubernetes) InitSS(name string, service kobject.ServiceConfig, replicas int) *appsv1.StatefulSet {
2✔
437
        var podSpec api.PodSpec
2✔
438
        if len(service.Configs) > 0 {
4✔
439
                podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
2✔
440
        } else {
2✔
441
                podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
×
442
        }
×
443
        rp := int32(replicas)
2✔
444
        ds := &appsv1.StatefulSet{
2✔
445
                TypeMeta: metav1.TypeMeta{
2✔
446
                        Kind:       "StatefulSet",
2✔
447
                        APIVersion: "apps/v1",
2✔
448
                },
2✔
449
                ObjectMeta: metav1.ObjectMeta{
2✔
450
                        Name:   name,
2✔
451
                        Labels: transformer.ConfigAllLabels(name, &service),
2✔
452
                },
2✔
453
                Spec: appsv1.StatefulSetSpec{
2✔
454
                        Replicas: &rp,
2✔
455
                        Template: api.PodTemplateSpec{
2✔
456
                                Spec: podSpec,
2✔
457
                        },
2✔
458
                        Selector: &metav1.LabelSelector{
2✔
459
                                MatchLabels: transformer.ConfigLabels(name),
2✔
460
                        },
2✔
461
                        ServiceName: service.Name,
2✔
462
                },
2✔
463
        }
2✔
464
        return ds
2✔
465
}
466

467
func (k *Kubernetes) initIngress(name string, service kobject.ServiceConfig, port int32) *networkingv1.Ingress {
2✔
468
        hosts := regexp.MustCompile("[ ,]*,[ ,]*").Split(service.ExposeService, -1)
2✔
469

2✔
470
        ingress := &networkingv1.Ingress{
2✔
471
                TypeMeta: metav1.TypeMeta{
2✔
472
                        Kind:       "Ingress",
2✔
473
                        APIVersion: "networking.k8s.io/v1",
2✔
474
                },
2✔
475
                ObjectMeta: metav1.ObjectMeta{
2✔
476
                        Name:        name,
2✔
477
                        Labels:      transformer.ConfigLabels(name),
2✔
478
                        Annotations: transformer.ConfigAnnotations(service),
2✔
479
                },
2✔
480
                Spec: networkingv1.IngressSpec{
2✔
481
                        Rules: make([]networkingv1.IngressRule, len(hosts)),
2✔
482
                },
2✔
483
        }
2✔
484
        tlsHosts := make([]string, len(hosts))
2✔
485
        pathType := networkingv1.PathTypePrefix
2✔
486
        for i, host := range hosts {
4✔
487
                host, p := transformer.ParseIngressPath(host)
2✔
488
                if p == "" {
4✔
489
                        p = "/"
2✔
490
                }
2✔
491
                ingress.Spec.Rules[i] = networkingv1.IngressRule{
2✔
492
                        IngressRuleValue: networkingv1.IngressRuleValue{
2✔
493
                                HTTP: &networkingv1.HTTPIngressRuleValue{
2✔
494
                                        Paths: []networkingv1.HTTPIngressPath{
2✔
495
                                                {
2✔
496
                                                        Path:     p,
2✔
497
                                                        PathType: &pathType,
2✔
498
                                                        Backend: networkingv1.IngressBackend{
2✔
499
                                                                Service: &networkingv1.IngressServiceBackend{
2✔
500
                                                                        Name: name,
2✔
501
                                                                        Port: networkingv1.ServiceBackendPort{
2✔
502
                                                                                Number: port,
2✔
503
                                                                        },
2✔
504
                                                                },
2✔
505
                                                        },
2✔
506
                                                },
2✔
507
                                        },
2✔
508
                                },
2✔
509
                        },
2✔
510
                }
2✔
511
                if host != "true" {
3✔
512
                        ingress.Spec.Rules[i].Host = host
1✔
513
                        tlsHosts[i] = host
1✔
514
                }
1✔
515
        }
516
        if service.ExposeServiceTLS != "" {
2✔
517
                if service.ExposeServiceTLS != "true" {
×
518
                        ingress.Spec.TLS = []networkingv1.IngressTLS{
×
519
                                {
×
520
                                        Hosts:      tlsHosts,
×
521
                                        SecretName: service.ExposeServiceTLS,
×
522
                                },
×
523
                        }
×
524
                } else {
×
525
                        ingress.Spec.TLS = []networkingv1.IngressTLS{
×
526
                                {
×
527
                                        Hosts: tlsHosts,
×
528
                                },
×
529
                        }
×
530
                }
×
531
        }
532

533
        if service.ExposeServiceIngressClassName != "" {
2✔
534
                ingress.Spec.IngressClassName = &service.ExposeServiceIngressClassName
×
535
        }
×
536

537
        return ingress
2✔
538
}
539

540
// CreateSecrets create secrets
541
func (k *Kubernetes) CreateSecrets(komposeObject kobject.KomposeObject) ([]*api.Secret, error) {
×
542
        var objects []*api.Secret
×
543
        for name, config := range komposeObject.Secrets {
×
544
                if config.File != "" {
×
545
                        dataString, err := GetContentFromFile(config.File)
×
546
                        if err != nil {
×
547
                                log.Fatal("unable to read secret from file: ", config.File)
×
548
                                return nil, err
×
549
                        }
×
550
                        data := []byte(dataString)
×
551
                        secret := &api.Secret{
×
552
                                TypeMeta: metav1.TypeMeta{
×
553
                                        Kind:       "Secret",
×
554
                                        APIVersion: "v1",
×
555
                                },
×
556
                                ObjectMeta: metav1.ObjectMeta{
×
557
                                        Name:   FormatResourceName(name),
×
558
                                        Labels: transformer.ConfigLabels(name),
×
559
                                },
×
560
                                Type: api.SecretTypeOpaque,
×
561
                                Data: map[string][]byte{name: data},
×
562
                        }
×
563
                        objects = append(objects, secret)
×
564
                } else {
×
565
                        log.Warnf("External secrets %s is not currently supported - ignoring", name)
×
566
                }
×
567
        }
568
        return objects, nil
×
569
}
570

571
// CreatePVC initializes PersistentVolumeClaim
572
func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string) (*api.PersistentVolumeClaim, error) {
18✔
573
        volSize, err := resource.ParseQuantity(size)
18✔
574
        if err != nil {
18✔
575
                return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size")
×
576
        }
×
577

578
        pvc := &api.PersistentVolumeClaim{
18✔
579
                TypeMeta: metav1.TypeMeta{
18✔
580
                        Kind:       "PersistentVolumeClaim",
18✔
581
                        APIVersion: "v1",
18✔
582
                },
18✔
583
                ObjectMeta: metav1.ObjectMeta{
18✔
584
                        Name:   name,
18✔
585
                        Labels: transformer.ConfigLabels(name),
18✔
586
                },
18✔
587
                Spec: api.PersistentVolumeClaimSpec{
18✔
588
                        Resources: api.ResourceRequirements{
18✔
589
                                Requests: api.ResourceList{
18✔
590
                                        api.ResourceStorage: volSize,
18✔
591
                                },
18✔
592
                        },
18✔
593
                },
18✔
594
        }
18✔
595

18✔
596
        if len(selectorValue) > 0 {
18✔
597
                pvc.Spec.Selector = &metav1.LabelSelector{
×
598
                        MatchLabels: transformer.ConfigLabels(selectorValue),
×
599
                }
×
600
        }
×
601

602
        if mode == "ro" {
18✔
603
                pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany}
×
604
        } else {
18✔
605
                pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce}
18✔
606
        }
18✔
607

608
        if len(storageClassName) > 0 {
19✔
609
                pvc.Spec.StorageClassName = &storageClassName
1✔
610
        }
1✔
611

612
        return pvc, nil
18✔
613
}
614

615
// ConfigPorts configures the container ports.
616
func ConfigPorts(service kobject.ServiceConfig) []api.ContainerPort {
42✔
617
        var ports []api.ContainerPort
42✔
618
        exist := map[string]bool{}
42✔
619
        for _, port := range service.Port {
100✔
620
                if exist[port.ID()] {
58✔
621
                        continue
×
622
                }
623
                containerPort := api.ContainerPort{
58✔
624
                        ContainerPort: port.ContainerPort,
58✔
625
                        HostIP:        port.HostIP,
58✔
626
                        HostPort:      port.HostPort,
58✔
627
                        Protocol:      api.Protocol(port.Protocol),
58✔
628
                }
58✔
629
                ports = append(ports, containerPort)
58✔
630
                exist[port.ID()] = true
58✔
631
        }
632

633
        return ports
42✔
634
}
635

636
// ConfigLBServicePorts method configure the ports of the k8s Load Balancer Service
637
func (k *Kubernetes) ConfigLBServicePorts(service kobject.ServiceConfig) ([]api.ServicePort, []api.ServicePort) {
1✔
638
        var tcpPorts []api.ServicePort
1✔
639
        var udpPorts []api.ServicePort
1✔
640
        for _, port := range service.Port {
2✔
641
                if port.HostPort == 0 {
1✔
642
                        port.HostPort = port.ContainerPort
×
643
                }
×
644
                var targetPort intstr.IntOrString
1✔
645
                targetPort.IntVal = port.ContainerPort
1✔
646
                targetPort.StrVal = strconv.Itoa(int(port.ContainerPort))
1✔
647

1✔
648
                servicePort := api.ServicePort{
1✔
649
                        Name:       strconv.Itoa(int(port.HostPort)),
1✔
650
                        Port:       port.HostPort,
1✔
651
                        TargetPort: targetPort,
1✔
652
                }
1✔
653

1✔
654
                if protocol := api.Protocol(port.Protocol); protocol == api.ProtocolTCP {
1✔
655
                        // If the default is already TCP, no need to include protocol.
×
656
                        tcpPorts = append(tcpPorts, servicePort)
×
657
                } else {
1✔
658
                        servicePort.Protocol = protocol
1✔
659
                        udpPorts = append(udpPorts, servicePort)
1✔
660
                }
1✔
661
        }
662
        return tcpPorts, udpPorts
1✔
663
}
664

665
// ConfigServicePorts configure the container service ports.
666
func (k *Kubernetes) ConfigServicePorts(service kobject.ServiceConfig) []api.ServicePort {
22✔
667
        servicePorts := []api.ServicePort{}
22✔
668
        seenPorts := make(map[int]struct{}, len(service.Port))
22✔
669

22✔
670
        var servicePort api.ServicePort
22✔
671
        for _, port := range service.Port {
80✔
672
                if port.HostPort == 0 {
58✔
673
                        port.HostPort = port.ContainerPort
×
674
                }
×
675

676
                var targetPort intstr.IntOrString
58✔
677
                targetPort.IntVal = port.ContainerPort
58✔
678
                targetPort.StrVal = strconv.Itoa(int(port.ContainerPort))
58✔
679

58✔
680
                // decide the name based on whether we saw this port before
58✔
681
                name := strconv.Itoa(int(port.HostPort))
58✔
682
                if _, ok := seenPorts[int(port.HostPort)]; ok {
70✔
683
                        // https://github.com/kubernetes/kubernetes/issues/2995
12✔
684
                        if service.ServiceType == string(api.ServiceTypeLoadBalancer) {
12✔
685
                                log.Fatalf("Service %s of type LoadBalancer cannot use TCP and UDP for the same port", name)
×
686
                        }
×
687
                        name = fmt.Sprintf("%s-%s", name, strings.ToLower(port.Protocol))
12✔
688
                }
689

690
                servicePort = api.ServicePort{
58✔
691
                        Name:       name,
58✔
692
                        Port:       port.HostPort,
58✔
693
                        TargetPort: targetPort,
58✔
694
                }
58✔
695

58✔
696
                if service.ServiceType == string(api.ServiceTypeNodePort) && service.NodePortPort != 0 {
58✔
697
                        servicePort.NodePort = service.NodePortPort
×
698
                }
×
699

700
                // If the default is already TCP, no need to include protocol.
701
                if protocol := api.Protocol(port.Protocol); protocol != api.ProtocolTCP {
107✔
702
                        servicePort.Protocol = protocol
49✔
703
                }
49✔
704

705
                servicePorts = append(servicePorts, servicePort)
58✔
706
                seenPorts[int(port.HostPort)] = struct{}{}
58✔
707
        }
708
        return servicePorts
22✔
709
}
710

711
// ConfigCapabilities configure POSIX capabilities that can be added or removed to a container
712
func ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
38✔
713
        capsAdd := []api.Capability{}
38✔
714
        capsDrop := []api.Capability{}
38✔
715
        for _, capAdd := range service.CapAdd {
56✔
716
                capsAdd = append(capsAdd, api.Capability(capAdd))
18✔
717
        }
18✔
718
        for _, capDrop := range service.CapDrop {
55✔
719
                capsDrop = append(capsDrop, api.Capability(capDrop))
17✔
720
        }
17✔
721
        return &api.Capabilities{
38✔
722
                Add:  capsAdd,
38✔
723
                Drop: capsDrop,
38✔
724
        }
38✔
725
}
726

727
// ConfigTmpfs configure the tmpfs.
728
func (k *Kubernetes) ConfigTmpfs(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) {
13✔
729
        //initializing volumemounts and volumes
13✔
730
        volumeMounts := []api.VolumeMount{}
13✔
731
        volumes := []api.Volume{}
13✔
732

13✔
733
        for index, volume := range service.TmpFs {
26✔
734
                //naming volumes if multiple tmpfs are provided
13✔
735
                volumeName := fmt.Sprintf("%s-tmpfs%d", name, index)
13✔
736
                volume = strings.Split(volume, ":")[0]
13✔
737
                // create a new volume mount object and append to list
13✔
738
                volMount := api.VolumeMount{
13✔
739
                        Name:      volumeName,
13✔
740
                        MountPath: volume,
13✔
741
                }
13✔
742
                volumeMounts = append(volumeMounts, volMount)
13✔
743

13✔
744
                //create tmpfs specific empty volumes
13✔
745
                volSource := k.ConfigEmptyVolumeSource("tmpfs")
13✔
746

13✔
747
                // create a new volume object using the volsource and add to list
13✔
748
                vol := api.Volume{
13✔
749
                        Name:         volumeName,
13✔
750
                        VolumeSource: *volSource,
13✔
751
                }
13✔
752
                volumes = append(volumes, vol)
13✔
753
        }
13✔
754
        return volumeMounts, volumes
13✔
755
}
756

757
// ConfigSecretVolumes config volumes from secret.
758
// Link: https://docs.docker.com/compose/compose-file/#secrets
759
// In kubernetes' Secret resource, it has a data structure like a map[string]bytes, every key will act like the file name
760
// when mount to a container. This is the part that missing in compose. So we will create a single key secret from compose
761
// config and the key's name will be the secret's name, it's value is the file content.
762
// compose's secret can only be mounted at `/run/secrets`, so this will be hardcoded.
763
func (k *Kubernetes) ConfigSecretVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) {
36✔
764
        var volumeMounts []api.VolumeMount
36✔
765
        var volumes []api.Volume
36✔
766
        if len(service.Secrets) > 0 {
36✔
767
                for _, secretConfig := range service.Secrets {
×
768
                        if secretConfig.UID != "" {
×
769
                                log.Warnf("Ignore pid in secrets for service: %s", name)
×
770
                        }
×
771
                        if secretConfig.GID != "" {
×
772
                                log.Warnf("Ignore gid in secrets for service: %s", name)
×
773
                        }
×
774

775
                        var secretItemPath, secretMountPath, secretSubPath string
×
776
                        if k.Opt.SecretsAsFiles {
×
777
                                secretItemPath, secretMountPath, secretSubPath = k.getSecretPaths(secretConfig)
×
778
                        } else {
×
779
                                secretItemPath, secretMountPath, secretSubPath = k.getSecretPathsLegacy(secretConfig)
×
780
                        }
×
781

782
                        volSource := api.VolumeSource{
×
783
                                Secret: &api.SecretVolumeSource{
×
784
                                        SecretName: secretConfig.Source,
×
785
                                        Items: []api.KeyToPath{{
×
786
                                                Key:  secretConfig.Source,
×
787
                                                Path: secretItemPath,
×
788
                                        }},
×
789
                                },
×
790
                        }
×
791

×
792
                        if secretConfig.Mode != nil {
×
793
                                mode := cast.ToInt32(*secretConfig.Mode)
×
794
                                volSource.Secret.DefaultMode = &mode
×
795
                        }
×
796

797
                        vol := api.Volume{
×
798
                                Name:         secretConfig.Source,
×
799
                                VolumeSource: volSource,
×
800
                        }
×
801
                        volumes = append(volumes, vol)
×
802

×
803
                        volMount := api.VolumeMount{
×
804
                                Name:      vol.Name,
×
805
                                MountPath: secretMountPath,
×
806
                                SubPath:   secretSubPath,
×
807
                        }
×
808
                        volumeMounts = append(volumeMounts, volMount)
×
809
                }
810
        }
811
        return volumeMounts, volumes
36✔
812
}
813

814
func (k *Kubernetes) getSecretPaths(secretConfig types.ServiceSecretConfig) (secretItemPath, secretMountPath, secretSubPath string) {
×
815
        // Default secretConfig.Target to secretConfig.Source, just in case user was using short secret syntax or
×
816
        // otherwise did not define a specific target
×
817
        target := secretConfig.Target
×
818
        if target == "" {
×
819
                target = secretConfig.Source
×
820
        }
×
821

822
        // If target is an absolute path, set that as the MountPath
823
        if strings.HasPrefix(secretConfig.Target, "/") {
×
824
                secretMountPath = target
×
825
        } else {
×
826
                // If target is a relative path, prefix with "/run/secrets/" to replicate what docker-compose would do
×
827
                secretMountPath = "/run/secrets/" + target
×
828
        }
×
829

830
        // Set subPath to the target filename. this ensures that we end up with a file at our MountPath instead
831
        // of a directory with symlinks (see https://stackoverflow.com/a/68332231)
832
        splitPath := strings.Split(target, "/")
×
833
        secretFilename := splitPath[len(splitPath)-1]
×
834

×
835
        // `secretItemPath` and `secretSubPath` have to be the same as `secretFilename` to ensure we create a file with
×
836
        // that name at `secretMountPath`, instead of a directory containing a symlink to the actual file.
×
837
        secretItemPath = secretFilename
×
838
        secretSubPath = secretFilename
×
839

×
840
        return secretItemPath, secretMountPath, secretSubPath
×
841
}
842

843
func (k *Kubernetes) getSecretPathsLegacy(secretConfig types.ServiceSecretConfig) (secretItemPath, secretMountPath, secretSubPath string) {
×
844
        // The old way of setting secret paths. It resulted in files being placed in incorrect locations when compared to
×
845
        // docker-compose results, but some people might depend on this behavior so this is kept here for compatibility.
×
846
        // See https://github.com/kubernetes/kompose/issues/1280 for more details.
×
847

×
848
        var itemPath string // should be the filename
×
849
        var mountPath = ""  // should be the directory
×
850
        // if is used the short-syntax
×
851
        if secretConfig.Target == "" {
×
852
                // the secret path (mountPath) should be inside the default directory /run/secrets
×
853
                mountPath = "/run/secrets/" + secretConfig.Source
×
854
                // the itemPath should be the source itself
×
855
                itemPath = secretConfig.Source
×
856
        } else {
×
857
                // if is the long-syntax, i should get the last part of path and consider it the filename
×
858
                pathSplitted := strings.Split(secretConfig.Target, "/")
×
859
                lastPart := pathSplitted[len(pathSplitted)-1]
×
860

×
861
                // if the filename (lastPart) and the target is the same
×
862
                if lastPart == secretConfig.Target {
×
863
                        // the secret path should be the source (it need to be inside a directory and only the filename was given)
×
864
                        mountPath = secretConfig.Source
×
865
                } else {
×
866
                        // should then get the target without the filename (lastPart)
×
867
                        mountPath = mountPath + strings.TrimSuffix(secretConfig.Target, "/"+lastPart) // menos ultima parte
×
868
                }
×
869

870
                // if the target isn't absolute path
871
                if !strings.HasPrefix(secretConfig.Target, "/") {
×
872
                        // concat the default secret directory
×
873
                        mountPath = "/run/secrets/" + mountPath
×
874
                }
×
875

876
                itemPath = lastPart
×
877
        }
878

879
        secretSubPath = "" // We didn't set a SubPath in legacy behavior
×
880
        return itemPath, mountPath, ""
×
881
}
882

883
// ConfigVolumes configure the container volumes.
884
func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, []*api.ConfigMap, error) {
36✔
885
        volumeMounts := []api.VolumeMount{}
36✔
886
        volumes := []api.Volume{}
36✔
887
        var PVCs []*api.PersistentVolumeClaim
36✔
888
        var cms []*api.ConfigMap
36✔
889
        var volumeName string
36✔
890

36✔
891
        // Set a var based on if the user wants to use empty volumes
36✔
892
        // as opposed to persistent volumes and volume claims
36✔
893
        useEmptyVolumes := k.Opt.EmptyVols
36✔
894
        useHostPath := k.Opt.Volumes == "hostPath"
36✔
895
        useConfigMap := k.Opt.Volumes == "configMap"
36✔
896
        if k.Opt.Volumes == "emptyDir" {
36✔
897
                useEmptyVolumes = true
×
898
        }
×
899

900
        // Override volume type if specified in service labels.
901
        if vt, ok := service.Labels["kompose.volume.type"]; ok {
36✔
902
                if _, okk := ValidVolumeSet[vt]; !okk {
×
903
                        return nil, nil, nil, nil, fmt.Errorf("invalid volume type %s specified in label 'kompose.volume.type' in service %s", vt, service.Name)
×
904
                }
×
905
                useEmptyVolumes = vt == "emptyDir"
×
906
                useHostPath = vt == "hostPath"
×
907
                useConfigMap = vt == "configMap"
×
908
        }
909

910
        // config volumes from secret if present
911
        secretsVolumeMounts, secretsVolumes := k.ConfigSecretVolumes(name, service)
36✔
912
        volumeMounts = append(volumeMounts, secretsVolumeMounts...)
36✔
913
        volumes = append(volumes, secretsVolumes...)
36✔
914

36✔
915
        var count int
36✔
916
        //iterating over array of `Vols` struct as it contains all necessary information about volumes
36✔
917
        for _, volume := range service.Volumes {
53✔
918
                // check if ro/rw mode is defined, default rw
17✔
919
                readonly := len(volume.Mode) > 0 && volume.Mode == "ro"
17✔
920
                if volume.VolumeName == "" {
30✔
921
                        if useEmptyVolumes {
13✔
922
                                volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1)
×
923
                        } else if useHostPath {
13✔
924
                                volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1)
×
925
                        } else if useConfigMap {
13✔
926
                                volumeName = strings.Replace(volume.PVCName, "claim", "cm", 1)
×
927
                        } else {
13✔
928
                                volumeName = volume.PVCName
13✔
929
                        }
13✔
930
                        // to support service group bases on volume, we need use the new group name to replace the origin service name
931
                        // in volume name. For normal service, this should have no effect
932
                        volumeName = strings.Replace(volumeName, service.Name, name, 1)
13✔
933
                        count++
13✔
934
                } else {
4✔
935
                        volumeName = volume.VolumeName
4✔
936
                }
4✔
937
                volMount := api.VolumeMount{
17✔
938
                        Name:      volumeName,
17✔
939
                        ReadOnly:  readonly,
17✔
940
                        MountPath: volume.Container,
17✔
941
                }
17✔
942

17✔
943
                // Get a volume source based on the type of volume we are using
17✔
944
                // For PVC we will also create a PVC object and add to list
17✔
945
                var volsource *api.VolumeSource
17✔
946

17✔
947
                if useEmptyVolumes {
17✔
948
                        volsource = k.ConfigEmptyVolumeSource("volume")
×
949
                } else if useHostPath {
17✔
950
                        source, err := k.ConfigHostPathVolumeSource(volume.Host)
×
951
                        if err != nil {
×
952
                                return nil, nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed")
×
953
                        }
×
954
                        volsource = source
×
955
                } else if useConfigMap {
17✔
956
                        log.Debugf("Use configmap volume")
×
957
                        cm, err := k.IntiConfigMapFromFileOrDir(name, volumeName, volume.Host, service)
×
958
                        if err != nil {
×
959
                                return nil, nil, nil, nil, err
×
960
                        }
×
961
                        cms = append(cms, cm)
×
962
                        volsource = k.ConfigConfigMapVolumeSource(volumeName, volume.Container, cm)
×
963

×
964
                        if useSubPathMount(cm) {
×
965
                                volMount.SubPath = volsource.ConfigMap.Items[0].Path
×
966
                        }
×
967
                } else {
17✔
968
                        volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
17✔
969
                        if volume.VFrom == "" {
34✔
970
                                var storageClassName string
17✔
971
                                defaultSize := PVCRequestSize
17✔
972
                                if k.Opt.PVCRequestSize != "" {
17✔
973
                                        defaultSize = k.Opt.PVCRequestSize
×
974
                                }
×
975
                                if len(volume.PVCSize) > 0 {
17✔
976
                                        defaultSize = volume.PVCSize
×
977
                                } else {
17✔
978
                                        for key, value := range service.Labels {
23✔
979
                                                if key == "kompose.volume.size" {
6✔
980
                                                        defaultSize = value
×
981
                                                } else if key == "kompose.volume.storage-class-name" {
6✔
982
                                                        storageClassName = value
×
983
                                                }
×
984
                                        }
985
                                }
986

987
                                createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName)
17✔
988

17✔
989
                                if err != nil {
17✔
990
                                        return nil, nil, nil, nil, errors.Wrap(err, "k.CreatePVC failed")
×
991
                                }
×
992

993
                                PVCs = append(PVCs, createdPVC)
17✔
994
                        }
995
                }
996
                volumeMounts = append(volumeMounts, volMount)
17✔
997

17✔
998
                // create a new volume object using the volsource and add to list
17✔
999
                vol := api.Volume{
17✔
1000
                        Name:         volumeName,
17✔
1001
                        VolumeSource: *volsource,
17✔
1002
                }
17✔
1003
                volumes = append(volumes, vol)
17✔
1004

17✔
1005
                if len(volume.Host) > 0 && (!useHostPath && !useConfigMap) {
17✔
1006
                        log.Warningf("Volume mount on the host %q isn't supported - ignoring path on the host", volume.Host)
×
1007
                }
×
1008
        }
1009

1010
        return volumeMounts, volumes, PVCs, cms, nil
36✔
1011
}
1012

1013
// ConfigEmptyVolumeSource is helper function to create an EmptyDir api.VolumeSource
1014
// either for Tmpfs or for emptyvolumes
1015
func (k *Kubernetes) ConfigEmptyVolumeSource(key string) *api.VolumeSource {
13✔
1016
        //if key is tmpfs
13✔
1017
        if key == "tmpfs" {
26✔
1018
                return &api.VolumeSource{
13✔
1019
                        EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageMediumMemory},
13✔
1020
                }
13✔
1021
        }
13✔
1022

1023
        //if key is volume
1024
        return &api.VolumeSource{
×
1025
                EmptyDir: &api.EmptyDirVolumeSource{},
×
1026
        }
×
1027
}
1028

1029
// ConfigConfigMapVolumeSource config a configmap to use as volume source
1030
func (k *Kubernetes) ConfigConfigMapVolumeSource(cmName string, targetPath string, cm *api.ConfigMap) *api.VolumeSource {
×
1031
        s := api.ConfigMapVolumeSource{}
×
1032
        s.Name = cmName
×
1033
        if useSubPathMount(cm) {
×
1034
                var keys []string
×
1035
                for k := range cm.Data {
×
1036
                        keys = append(keys, k)
×
1037
                }
×
1038
                for k := range cm.BinaryData {
×
1039
                        keys = append(keys, k)
×
1040
                }
×
1041
                key := keys[0]
×
1042
                _, p := path.Split(targetPath)
×
1043
                s.Items = []api.KeyToPath{
×
1044
                        {
×
1045
                                Key:  key,
×
1046
                                Path: p,
×
1047
                        },
×
1048
                }
×
1049
        }
1050
        return &api.VolumeSource{
×
1051
                ConfigMap: &s,
×
1052
        }
×
1053
}
1054

1055
// ConfigHostPathVolumeSource is a helper function to create a HostPath api.VolumeSource
1056
func (k *Kubernetes) ConfigHostPathVolumeSource(path string) (*api.VolumeSource, error) {
×
1057
        dir, err := transformer.GetComposeFileDir(k.Opt.InputFiles)
×
1058
        if err != nil {
×
1059
                return nil, err
×
1060
        }
×
1061
        absPath := path
×
1062
        if !filepath.IsAbs(path) {
×
1063
                absPath = filepath.Join(dir, path)
×
1064
        }
×
1065

1066
        return &api.VolumeSource{
×
1067
                HostPath: &api.HostPathVolumeSource{Path: absPath},
×
1068
        }, nil
×
1069
}
1070

1071
// ConfigPVCVolumeSource is helper function to create an api.VolumeSource with a PVC
1072
func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.VolumeSource {
17✔
1073
        return &api.VolumeSource{
17✔
1074
                PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
17✔
1075
                        ClaimName: name,
17✔
1076
                        ReadOnly:  readonly,
17✔
1077
                },
17✔
1078
        }
17✔
1079
}
17✔
1080

1081
// ConfigEnvs configures the environment variables.
1082
func ConfigEnvs(service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
36✔
1083
        envs := transformer.EnvSort{}
36✔
1084

36✔
1085
        keysFromEnvFile := make(map[string]bool)
36✔
1086

36✔
1087
        // If there is an env_file, use ConfigMaps and ignore the environment variables
36✔
1088
        // already specified
36✔
1089

36✔
1090
        if len(service.EnvFile) > 0 {
36✔
1091
                // Load each env_file
×
1092

×
1093
                for _, file := range service.EnvFile {
×
1094
                        envName := FormatEnvName(file)
×
1095

×
1096
                        // Load environment variables from file
×
1097
                        envLoad, err := GetEnvsFromFile(file, opt)
×
1098
                        if err != nil {
×
1099
                                return envs, errors.Wrap(err, "Unable to read env_file")
×
1100
                        }
×
1101

1102
                        // Add configMapKeyRef to each environment variable
1103
                        for k := range envLoad {
×
1104
                                envs = append(envs, api.EnvVar{
×
1105
                                        Name: k,
×
1106
                                        ValueFrom: &api.EnvVarSource{
×
1107
                                                ConfigMapKeyRef: &api.ConfigMapKeySelector{
×
1108
                                                        LocalObjectReference: api.LocalObjectReference{
×
1109
                                                                Name: envName,
×
1110
                                                        },
×
1111
                                                        Key: k,
×
1112
                                                }},
×
1113
                                })
×
1114
                                keysFromEnvFile[k] = true
×
1115
                        }
×
1116
                }
1117
        }
1118

1119
        // Load up the environment variables
1120
        for _, v := range service.Environment {
56✔
1121
                if !keysFromEnvFile[v.Name] {
40✔
1122
                        envs = append(envs, api.EnvVar{
20✔
1123
                                Name:  v.Name,
20✔
1124
                                Value: v.Value,
20✔
1125
                        })
20✔
1126
                }
20✔
1127
        }
1128

1129
        // Stable sorts data while keeping the original order of equal elements
1130
        // we need this because envs are not populated in any random order
1131
        // this sorting ensures they are populated in a particular order
1132
        sort.Stable(envs)
36✔
1133
        return envs, nil
36✔
1134
}
1135

1136
// ConfigAffinity configures the Affinity.
1137
func ConfigAffinity(service kobject.ServiceConfig) *api.Affinity {
35✔
1138
        var affinity *api.Affinity
35✔
1139
        // Config constraints
35✔
1140
        // Convert constraints to requiredDuringSchedulingIgnoredDuringExecution
35✔
1141
        positiveConstraints := configConstrains(service.Placement.PositiveConstraints, api.NodeSelectorOpIn)
35✔
1142
        negativeConstraints := configConstrains(service.Placement.NegativeConstraints, api.NodeSelectorOpNotIn)
35✔
1143
        if len(positiveConstraints) != 0 || len(negativeConstraints) != 0 {
36✔
1144
                affinity = &api.Affinity{
1✔
1145
                        NodeAffinity: &api.NodeAffinity{
1✔
1146
                                RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
1✔
1147
                                        NodeSelectorTerms: []api.NodeSelectorTerm{
1✔
1148
                                                {
1✔
1149
                                                        MatchExpressions: append(positiveConstraints, negativeConstraints...),
1✔
1150
                                                },
1✔
1151
                                        },
1✔
1152
                                },
1✔
1153
                        },
1✔
1154
                }
1✔
1155
        }
1✔
1156
        return affinity
35✔
1157
}
1158

1159
// ConfigTopologySpreadConstraints configures the TopologySpreadConstraints.
1160
func ConfigTopologySpreadConstraints(service kobject.ServiceConfig) []api.TopologySpreadConstraint {
34✔
1161
        preferencesLen := len(service.Placement.Preferences)
34✔
1162
        constraints := make([]api.TopologySpreadConstraint, 0, preferencesLen)
34✔
1163

34✔
1164
        // Placement preferences are ignored for global services
34✔
1165
        if service.DeployMode == "global" {
34✔
1166
                log.Warnf("Ignore placement preferences for global service %s", service.Name)
×
1167
                return constraints
×
1168
        }
×
1169

1170
        for i, p := range service.Placement.Preferences {
36✔
1171
                constraints = append(constraints, api.TopologySpreadConstraint{
2✔
1172
                        // According to the order of preferences, the MaxSkew decreases in order
2✔
1173
                        // The minimum value is 1
2✔
1174
                        MaxSkew:           int32(preferencesLen - i),
2✔
1175
                        TopologyKey:       p,
2✔
1176
                        WhenUnsatisfiable: api.ScheduleAnyway,
2✔
1177
                        LabelSelector: &metav1.LabelSelector{
2✔
1178
                                MatchLabels: transformer.ConfigLabels(service.Name),
2✔
1179
                        },
2✔
1180
                })
2✔
1181
        }
2✔
1182

1183
        return constraints
34✔
1184
}
1185

1186
func configConstrains(constrains map[string]string, operator api.NodeSelectorOperator) []api.NodeSelectorRequirement {
70✔
1187
        constraintsLen := len(constrains)
70✔
1188
        rs := make([]api.NodeSelectorRequirement, 0, constraintsLen)
70✔
1189
        if constraintsLen == 0 {
138✔
1190
                return rs
68✔
1191
        }
68✔
1192
        for k, v := range constrains {
4✔
1193
                r := api.NodeSelectorRequirement{
2✔
1194
                        Key:      k,
2✔
1195
                        Operator: operator,
2✔
1196
                        Values:   []string{v},
2✔
1197
                }
2✔
1198
                rs = append(rs, r)
2✔
1199
        }
2✔
1200
        return rs
2✔
1201
}
1202

1203
// CreateWorkloadAndConfigMapObjects generates a Kubernetes artifact for each input type service
1204
func (k *Kubernetes) CreateWorkloadAndConfigMapObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) []runtime.Object {
34✔
1205
        var objects []runtime.Object
34✔
1206
        var replica int
34✔
1207

34✔
1208
        if opt.IsReplicaSetFlag || service.Replicas == 0 {
59✔
1209
                replica = opt.Replicas
25✔
1210
        } else {
34✔
1211
                replica = service.Replicas
9✔
1212
        }
9✔
1213

1214
        // Check to see if Docker Compose v3 Deploy.Mode has been set to "global"
1215
        if service.DeployMode == "global" {
34✔
1216
                //default use daemonset
×
1217
                if opt.Controller == "" {
×
1218
                        opt.CreateD = false
×
1219
                        opt.CreateDS = true
×
1220
                } else if opt.Controller != "daemonset" {
×
1221
                        log.Warnf("Global deploy mode service is best converted to daemonset, now it convert to %s", opt.Controller)
×
1222
                }
×
1223
        }
1224

1225
        //Resolve labels first
1226
        if val, ok := service.Labels[compose.LabelControllerType]; ok {
34✔
1227
                opt.CreateD = false
×
1228
                opt.CreateDS = false
×
1229
                opt.CreateRC = false
×
1230
                if opt.Controller != "" {
×
1231
                        log.Warnf("Use label %s type %s for service %s, ignore %s flags", compose.LabelControllerType, val, name, opt.Controller)
×
1232
                }
×
1233
                opt.Controller = val
×
1234
        }
1235

1236
        if len(service.Configs) > 0 {
46✔
1237
                objects = k.createConfigMapFromComposeConfig(name, service, objects)
12✔
1238
        }
12✔
1239

1240
        if opt.CreateD || opt.Controller == DeploymentController {
60✔
1241
                objects = append(objects, k.InitD(name, service, replica))
26✔
1242
        }
26✔
1243

1244
        if opt.CreateDS || opt.Controller == DaemonSetController {
37✔
1245
                objects = append(objects, k.InitDS(name, service))
3✔
1246
        }
3✔
1247

1248
        if opt.Controller == StatefulStateController {
36✔
1249
                objects = append(objects, k.InitSS(name, service, replica))
2✔
1250
        }
2✔
1251

1252
        if len(service.EnvFile) > 0 {
34✔
1253
                for _, envFile := range service.EnvFile {
×
1254
                        configMap := k.InitConfigMapForEnv(name, opt, envFile)
×
1255
                        objects = append(objects, configMap)
×
1256
                }
×
1257
        }
1258

1259
        return objects
34✔
1260
}
1261

1262
func (k *Kubernetes) createConfigMapFromComposeConfig(name string, service kobject.ServiceConfig, objects []runtime.Object) []runtime.Object {
12✔
1263
        for _, config := range service.Configs {
24✔
1264
                currentConfigName := config.Source
12✔
1265
                currentConfigObj := service.ConfigsMetaData[currentConfigName]
12✔
1266
                if currentConfigObj.External.External {
12✔
1267
                        continue
×
1268
                }
1269
                currentFileName := currentConfigObj.File
12✔
1270
                configMap := k.InitConfigMapFromFile(name, service, currentFileName)
12✔
1271
                objects = append(objects, configMap)
12✔
1272
        }
1273
        return objects
12✔
1274
}
1275

1276
// InitPod initializes Kubernetes Pod object
1277
func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig) *api.Pod {
2✔
1278
        pod := api.Pod{
2✔
1279
                TypeMeta: metav1.TypeMeta{
2✔
1280
                        Kind:       "Pod",
2✔
1281
                        APIVersion: "v1",
2✔
1282
                },
2✔
1283
                ObjectMeta: metav1.ObjectMeta{
2✔
1284
                        Name:        name,
2✔
1285
                        Labels:      transformer.ConfigLabels(name),
2✔
1286
                        Annotations: transformer.ConfigAnnotations(service),
2✔
1287
                },
2✔
1288
                Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
2✔
1289
        }
2✔
1290
        return &pod
2✔
1291
}
2✔
1292

1293
// CreateNetworkPolicy initializes Network policy
1294
func (k *Kubernetes) CreateNetworkPolicy(networkName string) (*networkingv1.NetworkPolicy, error) {
36✔
1295
        str := "true"
36✔
1296
        np := &networkingv1.NetworkPolicy{
36✔
1297
                TypeMeta: metav1.TypeMeta{
36✔
1298
                        Kind:       "NetworkPolicy",
36✔
1299
                        APIVersion: "networking.k8s.io/v1",
36✔
1300
                },
36✔
1301
                ObjectMeta: metav1.ObjectMeta{
36✔
1302
                        Name: networkName,
36✔
1303
                        //Labels: transformer.ConfigLabels(name)(name),
36✔
1304
                },
36✔
1305
                Spec: networkingv1.NetworkPolicySpec{
36✔
1306
                        PodSelector: metav1.LabelSelector{
36✔
1307
                                MatchLabels: map[string]string{"io.kompose.network/" + networkName: str},
36✔
1308
                        },
36✔
1309
                        Ingress: []networkingv1.NetworkPolicyIngressRule{{
36✔
1310
                                From: []networkingv1.NetworkPolicyPeer{{
36✔
1311
                                        PodSelector: &metav1.LabelSelector{
36✔
1312
                                                MatchLabels: map[string]string{"io.kompose.network/" + networkName: str},
36✔
1313
                                        },
36✔
1314
                                }},
36✔
1315
                        }},
36✔
1316
                },
36✔
1317
        }
36✔
1318

36✔
1319
        return np, nil
36✔
1320
}
36✔
1321

1322
func buildServiceImage(opt kobject.ConvertOptions, service kobject.ServiceConfig, name string) error {
36✔
1323
        // Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
36✔
1324
        // Check that --build is set to true
36✔
1325
        // Check to see if there is an InputFile (required!) before we build the container
36✔
1326
        // Check that there's actually a Build key
36✔
1327
        // Lastly, we must have an Image name to continue
36✔
1328
        if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
36✔
1329
                // If there's no "image" key, use the name of the container that's built
×
1330
                if service.Image == "" {
×
1331
                        service.Image = name
×
1332
                }
×
1333

1334
                if service.Image == "" {
×
1335
                        return fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
×
1336
                }
×
1337

1338
                log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
×
1339

×
1340
                // Build the image!
×
1341
                err := transformer.BuildDockerImage(service, name)
×
1342
                if err != nil {
×
1343
                        return errors.Wrapf(err, "Unable to build Docker image for service %v", name)
×
1344
                }
×
1345

1346
                // Push the built image to the repo!
1347
                err = transformer.PushDockerImageWithOpt(service, name, opt)
×
1348
                if err != nil {
×
1349
                        return errors.Wrapf(err, "Unable to push Docker image for service %v", name)
×
1350
                }
×
1351
        }
1352
        return nil
36✔
1353
}
1354

1355
func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) {
36✔
1356
        if k.PortsExist(service) {
58✔
1357
                if service.ServiceType == "LoadBalancer" {
23✔
1358
                        svcs := k.CreateLBService(name, service)
1✔
1359
                        for _, svc := range svcs {
2✔
1360
                                svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy)
1✔
1361
                                *objects = append(*objects, svc)
1✔
1362
                        }
1✔
1363
                        if len(svcs) > 1 {
1✔
1364
                                log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalancer type")
×
1365
                        }
×
1366
                } else {
21✔
1367
                        svc := k.CreateService(name, service)
21✔
1368
                        *objects = append(*objects, svc)
21✔
1369
                        if service.ExposeService != "" {
23✔
1370
                                *objects = append(*objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
2✔
1371
                        }
2✔
1372
                        if service.ServiceExternalTrafficPolicy != "" && svc.Spec.Type != api.ServiceTypeNodePort {
21✔
1373
                                log.Warningf("External Traffic Policy is ignored for the service %v of type %v", name, service.ServiceType)
×
1374
                        }
×
1375
                }
1376
        } else {
14✔
1377
                if service.ServiceType == "Headless" {
18✔
1378
                        svc := k.CreateHeadlessService(name, service)
4✔
1379
                        *objects = append(*objects, svc)
4✔
1380
                        if service.ServiceExternalTrafficPolicy != "" {
4✔
1381
                                log.Warningf("External Traffic Policy is ignored for the service %v of type Headless", name)
×
1382
                        }
×
1383
                } else {
10✔
1384
                        log.Warnf("Service %q won't be created because 'ports' is not specified", service.Name)
10✔
1385
                }
10✔
1386
        }
1387
}
1388

1389
func (k *Kubernetes) configNetworkPolicyForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) error {
36✔
1390
        if len(service.Network) > 0 {
54✔
1391
                for _, net := range service.Network {
54✔
1392
                        log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
36✔
1393
                        np, err := k.CreateNetworkPolicy(net)
36✔
1394

36✔
1395
                        if err != nil {
36✔
1396
                                return errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
×
1397
                        }
×
1398
                        *objects = append(*objects, np)
36✔
1399
                }
1400
        }
1401
        return nil
36✔
1402
}
1403

1404
// Transform maps komposeObject to k8s objects
1405
// returns object that are already sorted in the way that Services are first
1406
func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) ([]runtime.Object, error) {
33✔
1407
        // this will hold all the converted data
33✔
1408
        var allobjects []runtime.Object
33✔
1409

33✔
1410
        if komposeObject.Secrets != nil {
33✔
1411
                secrets, err := k.CreateSecrets(komposeObject)
×
1412
                if err != nil {
×
1413
                        return nil, errors.Wrapf(err, "Unable to create Secret resource")
×
1414
                }
×
1415
                for _, item := range secrets {
×
1416
                        allobjects = append(allobjects, item)
×
1417
                }
×
1418
        }
1419
        if opt.ServiceGroupMode != "" {
38✔
1420
                log.Debugf("Service group mode is: %s", opt.ServiceGroupMode)
5✔
1421
                komposeObjectToServiceConfigGroupMapping := KomposeObjectToServiceConfigGroupMapping(&komposeObject, opt)
5✔
1422
                for name, group := range komposeObjectToServiceConfigGroupMapping {
8✔
1423
                        var objects []runtime.Object
3✔
1424
                        podSpec := PodSpec{}
3✔
1425

3✔
1426
                        // if using volume group, the name here will be a volume config string. reset to the first service name
3✔
1427
                        if opt.ServiceGroupMode == "volume" {
3✔
1428
                                if opt.ServiceGroupName != "" {
×
1429
                                        name = opt.ServiceGroupName
×
1430
                                } else {
×
1431
                                        var names []string
×
1432
                                        for _, svc := range group {
×
1433
                                                names = append(names, svc.Name)
×
1434
                                        }
×
1435
                                        name = strings.Join(names, "-")
×
1436
                                }
1437
                        }
1438

1439
                        // added a container
1440
                        // ports conflict check between services
1441
                        portsUses := map[string]bool{}
3✔
1442

3✔
1443
                        for _, service := range group {
9✔
1444
                                // first do ports check
6✔
1445
                                ports := ConfigPorts(service)
6✔
1446
                                for _, port := range ports {
6✔
1447
                                        key := string(port.ContainerPort) + string(port.Protocol)
×
1448
                                        if portsUses[key] {
×
1449
                                                return nil, fmt.Errorf("detect ports conflict when group services, service: %s, port: %d", service.Name, port.ContainerPort)
×
1450
                                        }
×
1451
                                        portsUses[key] = true
×
1452
                                }
1453

1454
                                log.Infof("Group Service %s to [%s]", service.Name, name)
6✔
1455
                                service.WithKomposeAnnotation = opt.WithKomposeAnnotation
6✔
1456
                                podSpec.Append(AddContainer(service, opt))
6✔
1457

6✔
1458
                                if err := buildServiceImage(opt, service, service.Name); err != nil {
6✔
1459
                                        return nil, err
×
1460
                                }
×
1461
                                // override..
1462
                                objects = append(objects, k.CreateWorkloadAndConfigMapObjects(name, service, opt)...)
6✔
1463
                                k.configKubeServiceAndIngressForService(service, name, &objects)
6✔
1464

6✔
1465
                                // Configure the container volumes.
6✔
1466
                                volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service)
6✔
1467
                                if err != nil {
6✔
1468
                                        return nil, errors.Wrap(err, "k.ConfigVolumes failed")
×
1469
                                }
×
1470
                                // Configure Tmpfs
1471
                                if len(service.TmpFs) > 0 {
6✔
1472
                                        TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service)
×
1473
                                        volumes = append(volumes, TmpVolumes...)
×
1474
                                        volumesMount = append(volumesMount, TmpVolumesMount...)
×
1475
                                }
×
1476
                                podSpec.Append(
6✔
1477
                                        SetVolumeMounts(volumesMount),
6✔
1478
                                        SetVolumes(volumes),
6✔
1479
                                )
6✔
1480

6✔
1481
                                // Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
6✔
1482
                                // because the type of objects and pvc is different, but when doing append
6✔
1483
                                // one element at a time it gets converted to runtime.Object for objects slice
6✔
1484
                                for _, p := range pvc {
10✔
1485
                                        objects = append(objects, p)
4✔
1486
                                }
4✔
1487

1488
                                for _, c := range cms {
6✔
1489
                                        objects = append(objects, c)
×
1490
                                }
×
1491

1492
                                podSpec.Append(
6✔
1493
                                        SetPorts(service),
6✔
1494
                                        ImagePullPolicy(name, service),
6✔
1495
                                        RestartPolicy(name, service),
6✔
1496
                                        SecurityContext(name, service),
6✔
1497
                                        HostName(service),
6✔
1498
                                        DomainName(service),
6✔
1499
                                        ResourcesLimits(service),
6✔
1500
                                        ResourcesRequests(service),
6✔
1501
                                        TerminationGracePeriodSeconds(name, service),
6✔
1502
                                        TopologySpreadConstraints(service),
6✔
1503
                                )
6✔
1504

6✔
1505
                                if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
8✔
1506
                                        podSpec.Append(ServiceAccountName(serviceAccountName))
2✔
1507
                                }
2✔
1508

1509
                                err = k.UpdateKubernetesObjectsMultipleContainers(name, service, &objects, podSpec)
6✔
1510
                                if err != nil {
6✔
1511
                                        return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
×
1512
                                }
×
1513

1514
                                if err = k.configNetworkPolicyForService(service, service.Name, &objects); err != nil {
6✔
1515
                                        return nil, err
×
1516
                                }
×
1517
                        }
1518

1519
                        allobjects = append(allobjects, objects...)
3✔
1520
                }
1521
        }
1522
        sortedKeys := SortedKeys(komposeObject)
33✔
1523
        for _, name := range sortedKeys {
69✔
1524
                service := komposeObject.ServiceConfigs[name]
36✔
1525

36✔
1526
                // if service belongs to a group, we already processed it
36✔
1527
                if service.InGroup {
42✔
1528
                        continue
6✔
1529
                }
1530

1531
                var objects []runtime.Object
30✔
1532

30✔
1533
                service.WithKomposeAnnotation = opt.WithKomposeAnnotation
30✔
1534

30✔
1535
                if err := buildServiceImage(opt, service, name); err != nil {
30✔
1536
                        return nil, err
×
1537
                }
×
1538

1539
                // Generate pod only and nothing more
1540
                if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
32✔
1541
                        log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
2✔
1542
                        pod := k.InitPod(name, service)
2✔
1543
                        objects = append(objects, pod)
2✔
1544
                } else {
30✔
1545
                        objects = k.CreateWorkloadAndConfigMapObjects(name, service, opt)
28✔
1546
                }
28✔
1547
                if opt.Controller == StatefulStateController {
32✔
1548
                        service.ServiceType = "Headless"
2✔
1549
                }
2✔
1550
                k.configKubeServiceAndIngressForService(service, name, &objects)
30✔
1551
                err := k.UpdateKubernetesObjects(name, service, opt, &objects)
30✔
1552
                if err != nil {
30✔
1553
                        return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
×
1554
                }
×
1555
                if err := k.configNetworkPolicyForService(service, name, &objects); err != nil {
30✔
1556
                        return nil, err
×
1557
                }
×
1558
                allobjects = append(allobjects, objects...)
30✔
1559
        }
1560

1561
        // sort all object so Services are first
1562
        k.SortServicesFirst(&allobjects)
33✔
1563
        k.RemoveDupObjects(&allobjects)
33✔
1564
        // k.FixWorkloadVersion(&allobjects)
33✔
1565
        return allobjects, nil
33✔
1566
}
1567

1568
// UpdateController updates the given object with the given pod template update function and ObjectMeta update function
1569
func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec) error, updateMeta func(meta *metav1.ObjectMeta)) (err error) {
93✔
1570
        switch t := obj.(type) {
93✔
1571
        case *appsv1.Deployment:
29✔
1572
                err = updateTemplate(&t.Spec.Template)
29✔
1573
                if err != nil {
29✔
1574
                        return errors.Wrap(err, "updateTemplate failed")
×
1575
                }
×
1576
                updateMeta(&t.ObjectMeta)
29✔
1577
        case *appsv1.DaemonSet:
3✔
1578
                err = updateTemplate(&t.Spec.Template)
3✔
1579
                if err != nil {
3✔
1580
                        return errors.Wrap(err, "updateTemplate failed")
×
1581
                }
×
1582
                updateMeta(&t.ObjectMeta)
3✔
1583
        case *appsv1.StatefulSet:
2✔
1584
                err = updateTemplate(&t.Spec.Template)
2✔
1585
                if err != nil {
2✔
1586
                        return errors.Wrap(err, "updateTemplate failed")
×
1587
                }
×
1588
                updateMeta(&t.ObjectMeta)
2✔
1589
        case *deployapi.DeploymentConfig:
×
1590
                err = updateTemplate(t.Spec.Template)
×
1591
                if err != nil {
×
1592
                        return errors.Wrap(err, "updateTemplate failed")
×
1593
                }
×
1594
                updateMeta(&t.ObjectMeta)
×
1595
        case *api.Pod:
2✔
1596
                p := api.PodTemplateSpec{
2✔
1597
                        ObjectMeta: t.ObjectMeta,
2✔
1598
                        Spec:       t.Spec,
2✔
1599
                }
2✔
1600
                err = updateTemplate(&p)
2✔
1601
                if err != nil {
2✔
1602
                        return errors.Wrap(err, "updateTemplate failed")
×
1603
                }
×
1604
                t.Spec = p.Spec
2✔
1605
                t.ObjectMeta = p.ObjectMeta
2✔
1606
        case *buildapi.BuildConfig:
×
1607
                updateMeta(&t.ObjectMeta)
×
1608
        }
1609
        return nil
93✔
1610
}
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