/
kubernetes.go
1393 lines (1229 loc) · 39.4 KB
/
kubernetes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright 2017 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubernetes
import (
"fmt"
"reflect"
"regexp"
"strconv"
"time"
"github.com/fatih/structs"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/transformer"
buildapi "github.com/openshift/origin/pkg/build/api"
deployapi "github.com/openshift/origin/pkg/deploy/api"
log "github.com/sirupsen/logrus"
// install kubernetes api
_ "k8s.io/kubernetes/pkg/api/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
//"k8s.io/kubernetes/pkg/controller/daemon"
"sort"
"strings"
"path/filepath"
"github.com/kubernetes/kompose/pkg/loader/compose"
"github.com/pkg/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/labels"
)
// Kubernetes implements Transformer interface and represents Kubernetes transformer
type Kubernetes struct {
// the user provided options from the command line
Opt kobject.ConvertOptions
}
// TIMEOUT is how long we'll wait for the termination of kubernetes resource to be successful
// used when undeploying resources from kubernetes
const TIMEOUT = 300
// PVCRequestSize (Persistent Volume Claim) has default size
const PVCRequestSize = "100Mi"
const (
// DeploymentController is controller type for Deployment
DeploymentController = "deployment"
// DaemonSetController is controller type for DaemonSet
DaemonSetController = "daemonset"
// ReplicationController is controller type for ReplicationController
ReplicationController = "replicationcontroller"
)
// CheckUnsupportedKey checks if given komposeObject contains
// keys that are not supported by this transformer.
// list of all unsupported keys are stored in unsupportedKey variable
// returns list of TODO: ....
func (k *Kubernetes) CheckUnsupportedKey(komposeObject *kobject.KomposeObject, unsupportedKey map[string]bool) []string {
// collect all keys found in project
var keysFound []string
for _, serviceConfig := range komposeObject.ServiceConfigs {
// this reflection is used in check for empty arrays
val := reflect.ValueOf(serviceConfig)
s := structs.New(serviceConfig)
for _, f := range s.Fields() {
// Check if given key is among unsupported keys, and skip it if we already saw this key
if alreadySaw, ok := unsupportedKey[f.Name()]; ok && !alreadySaw {
if f.IsExported() && !f.IsZero() {
// IsZero returns false for empty array/slice ([])
// this check if field is Slice, and then it checks its size
if field := val.FieldByName(f.Name()); field.Kind() == reflect.Slice {
if field.Len() == 0 {
// array is empty it doesn't matter if it is in unsupportedKey or not
continue
}
}
//get tag from kobject service configure
tag := f.Tag(komposeObject.LoadedFrom)
keysFound = append(keysFound, tag)
unsupportedKey[f.Name()] = true
}
}
}
}
return keysFound
}
// InitPodSpec creates the pod specification
func (k *Kubernetes) InitPodSpec(name string, image string, pullSecret string) api.PodSpec {
pod := api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: image,
},
},
}
if pullSecret != "" {
pod.ImagePullSecrets = []api.LocalObjectReference{
{
Name: pullSecret,
},
}
}
return pod
}
//InitPodSpecWithConfigMap creates the pod specification
func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service kobject.ServiceConfig) api.PodSpec {
var volumeMounts []api.VolumeMount
var volumes []api.Volume
if len(service.Configs) > 0 && service.Configs[0].Mode != nil {
//This is for LONG SYNTAX
for _, value := range service.Configs {
if value.Target == "/" {
log.Warnf("Long syntax config, target path can not be /")
continue
}
tmpKey := FormatFileName(value.Source)
volumeMounts = append(volumeMounts,
api.VolumeMount{
Name: tmpKey,
MountPath: "/" + FormatFileName(value.Target),
})
tmpVolume := api.Volume{
Name: tmpKey,
}
tmpVolume.ConfigMap = &api.ConfigMapVolumeSource{}
tmpVolume.ConfigMap.Name = tmpKey
var tmpMode int32
tmpMode = int32(*value.Mode)
tmpVolume.ConfigMap.DefaultMode = &tmpMode
volumes = append(volumes, tmpVolume)
}
} else {
//This is for SHORT SYNTAX, unsupported
}
pod := api.PodSpec{
Containers: []api.Container{
{
Name: name,
Image: image,
VolumeMounts: volumeMounts,
},
},
Volumes: volumes,
}
return pod
}
// InitRC initializes Kubernetes ReplicationController object
func (k *Kubernetes) InitRC(name string, service kobject.ServiceConfig, replicas int) *api.ReplicationController {
rc := &api.ReplicationController{
TypeMeta: unversioned.TypeMeta{
Kind: "ReplicationController",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Spec: api.ReplicationControllerSpec{
Replicas: int32(replicas),
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: transformer.ConfigLabels(name),
},
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
},
},
}
return rc
}
// InitSvc initializes Kubernetes Service object
func (k *Kubernetes) InitSvc(name string, service kobject.ServiceConfig) *api.Service {
svc := &api.Service{
TypeMeta: unversioned.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Spec: api.ServiceSpec{
Selector: transformer.ConfigLabels(name),
},
}
return svc
}
// InitConfigMapForEnv initializes a ConfigMap object
func (k *Kubernetes) InitConfigMapForEnv(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, envFile string) *api.ConfigMap {
envs, err := GetEnvsFromFile(envFile, opt)
if err != nil {
log.Fatalf("Unable to retrieve env file: %s", err)
}
// Remove root pathing
// replace all other slashes / periods
envName := FormatEnvName(envFile)
// In order to differentiate files, we append to the name and remove '.env' if applicable from the file name
configMap := &api.ConfigMap{
TypeMeta: unversioned.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name + "-" + envName,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name + "-" + envName),
},
Data: envs,
}
return configMap
}
//InitConfigMapFromFile initializes a ConfigMap object
func (k *Kubernetes) InitConfigMapFromFile(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, fileName string) *api.ConfigMap {
content, err := GetContentFromFile(fileName, opt)
if err != nil {
log.Fatalf("Unable to retrieve file: %s", err)
}
originFileName := FormatFileName(fileName)
dataMap := make(map[string]string)
dataMap[originFileName] = content
configMapName := ""
for key, tmpConfig := range service.ConfigsMetaData {
if tmpConfig.File == fileName {
configMapName = key
}
}
configMap := &api.ConfigMap{
TypeMeta: unversioned.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: FormatFileName(configMapName),
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Data: dataMap,
}
return configMap
}
// InitD initializes Kubernetes Deployment object
func (k *Kubernetes) InitD(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment {
var podSpec api.PodSpec
if len(service.Configs) > 0 {
podSpec = k.InitPodSpecWithConfigMap(name, service.Image, service)
} else {
podSpec = k.InitPodSpec(name, service.Image, service.ImagePullSecret)
}
dc := &extensions.Deployment{
TypeMeta: unversioned.TypeMeta{
Kind: "Deployment",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Spec: extensions.DeploymentSpec{
Replicas: int32(replicas),
Template: api.PodTemplateSpec{
Spec: podSpec,
},
},
}
return dc
}
// InitDS initializes Kubernetes DaemonSet object
func (k *Kubernetes) InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet {
ds := &extensions.DaemonSet{
TypeMeta: unversioned.TypeMeta{
Kind: "DaemonSet",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Spec: extensions.DaemonSetSpec{
Template: api.PodTemplateSpec{
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
},
},
}
return ds
}
func (k *Kubernetes) initIngress(name string, service kobject.ServiceConfig, port int32) *extensions.Ingress {
hosts := regexp.MustCompile("[ ,]*,[ ,]*").Split(service.ExposeService, -1)
ingress := &extensions.Ingress{
TypeMeta: unversioned.TypeMeta{
Kind: "Ingress",
APIVersion: "extensions/v1beta1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Spec: extensions.IngressSpec{
Rules: make([]extensions.IngressRule, len(hosts)),
},
}
for i, host := range hosts {
ingress.Spec.Rules[i] = extensions.IngressRule{
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Backend: extensions.IngressBackend{
ServiceName: name,
ServicePort: intstr.IntOrString{
IntVal: port,
},
},
},
},
},
},
}
if host != "true" {
ingress.Spec.Rules[i].Host = host
}
}
if service.ExposeServiceTLS != "" {
ingress.Spec.TLS = []extensions.IngressTLS{
{
Hosts: hosts,
SecretName: service.ExposeServiceTLS,
},
}
}
return ingress
}
// CreatePVC initializes PersistentVolumeClaim
func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, namespace string) (*api.PersistentVolumeClaim, error) {
volSize, err := resource.ParseQuantity(size)
if err != nil {
return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size")
}
pvc := &api.PersistentVolumeClaim{
TypeMeta: unversioned.TypeMeta{
Kind: "PersistentVolumeClaim",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: transformer.ConfigLabels(name),
},
Spec: api.PersistentVolumeClaimSpec{
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceStorage: volSize,
},
},
},
}
if len(selectorValue) > 0 {
pvc.Spec.Selector = &unversioned.LabelSelector{
MatchLabels: transformer.ConfigLabels(selectorValue),
}
}
if mode == "ro" {
pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany}
} else {
pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce}
}
return pvc, nil
}
// ConfigPorts configures the container ports.
func (k *Kubernetes) ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
ports := []api.ContainerPort{}
for _, port := range service.Port {
// If the default is already TCP, no need to include it.
if port.Protocol == api.ProtocolTCP {
ports = append(ports, api.ContainerPort{
ContainerPort: port.ContainerPort,
HostIP: port.HostIP,
})
} else {
ports = append(ports, api.ContainerPort{
ContainerPort: port.ContainerPort,
Protocol: port.Protocol,
HostIP: port.HostIP,
})
}
}
return ports
}
// ConfigServicePorts configure the container service ports.
func (k *Kubernetes) ConfigServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort {
servicePorts := []api.ServicePort{}
seenPorts := make(map[int]struct{}, len(service.Port))
var servicePort api.ServicePort
for _, port := range service.Port {
if port.HostPort == 0 {
port.HostPort = port.ContainerPort
}
var targetPort intstr.IntOrString
targetPort.IntVal = port.ContainerPort
targetPort.StrVal = strconv.Itoa(int(port.ContainerPort))
// decide the name based on whether we saw this port before
name := strconv.Itoa(int(port.HostPort))
if _, ok := seenPorts[int(port.HostPort)]; ok {
// https://github.com/kubernetes/kubernetes/issues/2995
if service.ServiceType == string(api.ServiceTypeLoadBalancer) {
log.Fatalf("Service %s of type LoadBalancer cannot use TCP and UDP for the same port", name)
}
name = fmt.Sprintf("%s-%s", name, strings.ToLower(string(port.Protocol)))
}
servicePort = api.ServicePort{
Name: name,
Port: port.HostPort,
TargetPort: targetPort,
}
// If the default is already TCP, no need to include it.
if port.Protocol != api.ProtocolTCP {
servicePort.Protocol = port.Protocol
}
servicePorts = append(servicePorts, servicePort)
seenPorts[int(port.HostPort)] = struct{}{}
}
return servicePorts
}
//ConfigCapabilities configure POSIX capabilities that can be added or removed to a container
func (k *Kubernetes) ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
capsAdd := []api.Capability{}
capsDrop := []api.Capability{}
for _, capAdd := range service.CapAdd {
capsAdd = append(capsAdd, api.Capability(capAdd))
}
for _, capDrop := range service.CapDrop {
capsDrop = append(capsDrop, api.Capability(capDrop))
}
return &api.Capabilities{
Add: capsAdd,
Drop: capsDrop,
}
}
// ConfigTmpfs configure the tmpfs.
func (k *Kubernetes) ConfigTmpfs(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) {
//initializing volumemounts and volumes
volumeMounts := []api.VolumeMount{}
volumes := []api.Volume{}
for index, volume := range service.TmpFs {
//naming volumes if multiple tmpfs are provided
volumeName := fmt.Sprintf("%s-tmpfs%d", name, index)
volume = strings.Split(volume, ":")[0]
// create a new volume mount object and append to list
volMount := api.VolumeMount{
Name: volumeName,
MountPath: volume,
}
volumeMounts = append(volumeMounts, volMount)
//create tmpfs specific empty volumes
volSource := k.ConfigEmptyVolumeSource("tmpfs")
// create a new volume object using the volsource and add to list
vol := api.Volume{
Name: volumeName,
VolumeSource: *volSource,
}
volumes = append(volumes, vol)
}
return volumeMounts, volumes
}
// ConfigVolumes configure the container volumes.
func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume, []*api.PersistentVolumeClaim, error) {
volumeMounts := []api.VolumeMount{}
volumes := []api.Volume{}
var PVCs []*api.PersistentVolumeClaim
var volumeName string
// Set a var based on if the user wants to use empty volumes
// as opposed to persistent volumes and volume claims
useEmptyVolumes := k.Opt.EmptyVols
useHostPath := false
if k.Opt.Volumes == "emptyDir" {
useEmptyVolumes = true
}
if k.Opt.Volumes == "hostPath" {
useHostPath = true
}
var count int
//iterating over array of `Vols` struct as it contains all necessary information about volumes
for _, volume := range service.Volumes {
// check if ro/rw mode is defined, default rw
readonly := len(volume.Mode) > 0 && volume.Mode == "ro"
if volume.VolumeName == "" {
if useEmptyVolumes {
volumeName = strings.Replace(volume.PVCName, "claim", "empty", 1)
} else if useHostPath {
volumeName = strings.Replace(volume.PVCName, "claim", "hostpath", 1)
} else {
volumeName = volume.PVCName
}
count++
} else {
volumeName = volume.VolumeName
}
volMount := api.VolumeMount{
Name: volumeName,
ReadOnly: readonly,
MountPath: volume.Container,
}
volumeMounts = append(volumeMounts, volMount)
// Get a volume source based on the type of volume we are using
// For PVC we will also create a PVC object and add to list
var volsource *api.VolumeSource
if useEmptyVolumes {
volsource = k.ConfigEmptyVolumeSource("volume")
} else if useHostPath {
source, err := k.ConfigHostPathVolumeSource(volume.Host)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "k.ConfigHostPathVolumeSource failed")
}
volsource = source
} else {
volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
if volume.VFrom == "" {
defaultSize := PVCRequestSize
if len(volume.PVCSize) > 0 {
defaultSize = volume.PVCSize
} else {
for key, value := range service.Labels {
if key == "kompose.volume.size" {
defaultSize = value
}
}
}
createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, service.Namespace.Name)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "k.CreatePVC failed")
}
PVCs = append(PVCs, createdPVC)
}
}
// create a new volume object using the volsource and add to list
vol := api.Volume{
Name: volumeName,
VolumeSource: *volsource,
}
volumes = append(volumes, vol)
if len(volume.Host) > 0 && !useHostPath {
log.Warningf("Volume mount on the host %q isn't supported - ignoring path on the host", volume.Host)
}
}
return volumeMounts, volumes, PVCs, nil
}
// ConfigEmptyVolumeSource is helper function to create an EmptyDir api.VolumeSource
//either for Tmpfs or for emptyvolumes
func (k *Kubernetes) ConfigEmptyVolumeSource(key string) *api.VolumeSource {
//if key is tmpfs
if key == "tmpfs" {
return &api.VolumeSource{
EmptyDir: &api.EmptyDirVolumeSource{Medium: api.StorageMediumMemory},
}
}
//if key is volume
return &api.VolumeSource{
EmptyDir: &api.EmptyDirVolumeSource{},
}
}
// ConfigHostPathVolumeSource is a helper function to create a HostPath api.VolumeSource
func (k *Kubernetes) ConfigHostPathVolumeSource(path string) (*api.VolumeSource, error) {
dir, err := transformer.GetComposeFileDir(k.Opt.InputFiles)
if err != nil {
return nil, err
}
absPath := filepath.Join(dir, path)
return &api.VolumeSource{
HostPath: &api.HostPathVolumeSource{Path: absPath},
}, nil
}
// ConfigPVCVolumeSource is helper function to create an api.VolumeSource with a PVC
func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.VolumeSource {
return &api.VolumeSource{
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
ClaimName: name,
ReadOnly: readonly,
},
}
}
// ConfigEnvs configures the environment variables.
func (k *Kubernetes) ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
envs := transformer.EnvSort{}
keysFromEnvFile := make(map[string]bool)
// If there is an env_file, use ConfigMaps and ignore the environment variables
// already specified
if len(service.EnvFile) > 0 {
// Load each env_file
for _, file := range service.EnvFile {
envName := FormatEnvName(file)
// Load environment variables from file
envLoad, err := GetEnvsFromFile(file, opt)
if err != nil {
return envs, errors.Wrap(err, "Unable to read env_file")
}
// Add configMapKeyRef to each environment variable
for k := range envLoad {
envs = append(envs, api.EnvVar{
Name: k,
ValueFrom: &api.EnvVarSource{
ConfigMapKeyRef: &api.ConfigMapKeySelector{
LocalObjectReference: api.LocalObjectReference{
Name: name + "-" + envName,
},
Key: k,
}},
})
keysFromEnvFile[k] = true
}
}
}
// Load up the environment variables
for _, v := range service.Environment {
if !keysFromEnvFile[v.Name] {
envs = append(envs, api.EnvVar{
Name: v.Name,
Value: v.Value,
})
}
}
// Stable sorts data while keeping the original order of equal elements
// we need this because envs are not populated in any random order
// this sorting ensures they are populated in a particular order
sort.Stable(envs)
return envs, nil
}
// CreateKubernetesObjects generates a Kubernetes artifact for each input type service
func (k *Kubernetes) CreateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) []runtime.Object {
var objects []runtime.Object
var replica int
if opt.IsReplicaSetFlag || service.Replicas == 0 {
replica = opt.Replicas
} else {
replica = service.Replicas
}
// Check to see if Docker Compose v3 Deploy.Mode has been set to "global"
if service.DeployMode == "global" {
//default use daemonset
if opt.Controller == "" {
opt.CreateD = false
opt.CreateDS = true
} else if opt.Controller != "daemonset" {
log.Warnf("Global deploy mode service is best converted to daemonset, now it convert to %s", opt.Controller)
}
}
//Resolve labels first
if val, ok := service.Labels[compose.LabelControllerType]; ok {
opt.CreateD = false
opt.CreateDS = false
opt.CreateRC = false
if opt.Controller != "" {
log.Warnf("Use label %s type %s for service %s, ignore %s flags", compose.LabelControllerType, val, name, opt.Controller)
}
opt.Controller = val
}
if len(service.Configs) > 0 {
objects = k.createConfigMapFromComposeConfig(name, opt, service, objects)
}
if opt.CreateD || opt.Controller == DeploymentController {
objects = append(objects, k.InitD(name, service, replica))
}
if opt.CreateDS || opt.Controller == DaemonSetController {
objects = append(objects, k.InitDS(name, service))
}
if opt.CreateRC || opt.Controller == ReplicationController {
objects = append(objects, k.InitRC(name, service, replica))
}
if len(service.EnvFile) > 0 {
for _, envFile := range service.EnvFile {
configMap := k.InitConfigMapForEnv(name, service, opt, envFile)
objects = append(objects, configMap)
}
}
return objects
}
func (k *Kubernetes) createConfigMapFromComposeConfig(name string, opt kobject.ConvertOptions, service kobject.ServiceConfig, objects []runtime.Object) []runtime.Object {
for _, config := range service.Configs {
currentConfigName := config.Source
currentConfigObj := service.ConfigsMetaData[currentConfigName]
if currentConfigObj.External.External {
continue
}
currentFileName := currentConfigObj.File
configMap := k.InitConfigMapFromFile(name, service, opt, currentFileName)
objects = append(objects, configMap)
}
return objects
}
// InitPod initializes Kubernetes Pod object
func (k *Kubernetes) InitPod(name string, service kobject.ServiceConfig) *api.Pod {
pod := api.Pod{
TypeMeta: unversioned.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: service.Namespace.Name,
Labels: transformer.ConfigLabels(name),
},
Spec: k.InitPodSpec(name, service.Image, service.ImagePullSecret),
}
return &pod
}
// Transform maps komposeObject to k8s objects
// returns object that are already sorted in the way that Services are first
func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) ([]runtime.Object, error) {
// this will hold all the converted data
var allobjects []runtime.Object
sortedKeys := SortedKeys(komposeObject)
for _, name := range sortedKeys {
service := komposeObject.ServiceConfigs[name]
var objects []runtime.Object
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
// Check that --build is set to true
// Check to see if there is an InputFile (required!) before we build the container
// Check that there's actually a Build key
// Lastly, we must have an Image name to continue
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
if service.Image == "" {
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
}
log.Infof("Build key detected. Attempting to build and push image '%s'", service.Image)
// Get the directory where the compose file is
composeFileDir, err := transformer.GetComposeFileDir(opt.InputFiles)
if err != nil {
return nil, err
}
// Build the image!
err = transformer.BuildDockerImage(service, name, composeFileDir)
if err != nil {
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
}
// Push the built image to the repo!
err = transformer.PushDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
}
// If there's no "image" key, use the name of the container that's built
if service.Image == "" {
service.Image = name
}
// Generate pod only and nothing more
if service.Restart == "no" || service.Restart == "on-failure" {
// Error out if Controller Object is specified with restart: 'on-failure'
if opt.IsDeploymentFlag || opt.IsDaemonSetFlag || opt.IsReplicationControllerFlag {
return nil, errors.New("Controller object cannot be specified with restart: 'on-failure'")
}
pod := k.InitPod(name, service)
objects = append(objects, pod)
} else {
objects = k.CreateKubernetesObjects(name, service, opt)
}
if k.PortsExist(service) {
svc := k.CreateService(name, service, objects)
objects = append(objects, svc)
if service.ExposeService != "" {
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
}
if service.Namespace.Create && service.Namespace.Name != "" {
var obj = k.InitNamespace(service.Namespace.Name)
objects = append(objects, obj)
}
} else {
if service.ServiceType == "Headless" {
svc := k.CreateHeadlessService(name, service, objects)
objects = append(objects, svc)
}
}
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
}
allobjects = append(allobjects, objects...)
}
// sort all object so Services are first
k.SortServicesFirst(&allobjects)
return allobjects, nil
}
// UpdateController updates the given object with the given pod template update function and ObjectMeta update function
func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec) error, updateMeta func(meta *api.ObjectMeta)) (err error) {
switch t := obj.(type) {
case *api.ReplicationController:
if t.Spec.Template == nil {
t.Spec.Template = &api.PodTemplateSpec{}
}
err = updateTemplate(t.Spec.Template)
if err != nil {
return errors.Wrap(err, "updateTemplate failed")
}
updateMeta(&t.ObjectMeta)
case *extensions.Deployment:
err = updateTemplate(&t.Spec.Template)
if err != nil {
return errors.Wrap(err, "updateTemplate failed")
}
updateMeta(&t.ObjectMeta)
case *extensions.DaemonSet:
err = updateTemplate(&t.Spec.Template)
if err != nil {
return errors.Wrap(err, "updateTemplate failed")
}
updateMeta(&t.ObjectMeta)
case *deployapi.DeploymentConfig:
err = updateTemplate(t.Spec.Template)
if err != nil {
return errors.Wrap(err, "updateTemplate failed")
}
updateMeta(&t.ObjectMeta)
case *api.Pod:
p := api.PodTemplateSpec{
ObjectMeta: t.ObjectMeta,
Spec: t.Spec,
}
err = updateTemplate(&p)
if err != nil {
return errors.Wrap(err, "updateTemplate failed")
}
t.Spec = p.Spec
t.ObjectMeta = p.ObjectMeta
case *buildapi.BuildConfig:
updateMeta(&t.ObjectMeta)
}
return nil
}
// GetKubernetesClient creates the k8s Client, returns k8s client and namespace
func (k *Kubernetes) GetKubernetesClient() (*client.Client, string, error) {
// initialize Kubernetes client
factory := cmdutil.NewFactory(nil)
clientConfig, err := factory.ClientConfig()
if err != nil {
return nil, "", err
}
client := client.NewOrDie(clientConfig)
// get namespace from config
namespace, _, err := factory.DefaultNamespace()
if err != nil {
return nil, "", err
}
return client, namespace, nil
}
// InitNamespace initializes Kubernetes Namespace object
func (k *Kubernetes) InitNamespace(name string) *api.Namespace {
namespace := api.Namespace{
TypeMeta: unversioned.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: api.ObjectMeta{
Name: name,