From 0e485c6a5f1bf72ae20818a712f33b49997cb742 Mon Sep 17 00:00:00 2001 From: Tuna Date: Thu, 28 Jul 2016 11:50:19 +0700 Subject: [PATCH 1/7] init loader, transformer pkg --- pkg/loader/bundle.go | 1 + pkg/loader/compose.go | 1 + pkg/transformer/kubernetes.go | 1 + pkg/transformer/openshift.go | 1 + 4 files changed, 4 insertions(+) create mode 100644 pkg/loader/bundle.go create mode 100644 pkg/loader/compose.go create mode 100644 pkg/transformer/kubernetes.go create mode 100644 pkg/transformer/openshift.go diff --git a/pkg/loader/bundle.go b/pkg/loader/bundle.go new file mode 100644 index 00000000..529648a2 --- /dev/null +++ b/pkg/loader/bundle.go @@ -0,0 +1 @@ +package loader diff --git a/pkg/loader/compose.go b/pkg/loader/compose.go new file mode 100644 index 00000000..529648a2 --- /dev/null +++ b/pkg/loader/compose.go @@ -0,0 +1 @@ +package loader diff --git a/pkg/transformer/kubernetes.go b/pkg/transformer/kubernetes.go new file mode 100644 index 00000000..38ef5e16 --- /dev/null +++ b/pkg/transformer/kubernetes.go @@ -0,0 +1 @@ +package transformer diff --git a/pkg/transformer/openshift.go b/pkg/transformer/openshift.go new file mode 100644 index 00000000..38ef5e16 --- /dev/null +++ b/pkg/transformer/openshift.go @@ -0,0 +1 @@ +package transformer From 85b0b6a2b858ec27053d2f18622160c536f56cae Mon Sep 17 00:00:00 2001 From: Tuna Date: Thu, 28 Jul 2016 19:31:12 +0700 Subject: [PATCH 2/7] breaking app.go into loader and transformer, to be continue --- cli/app/app.go | 438 +++++----------------------------- cli/app/types.go | 70 ------ pkg/kobject/kobject.go | 167 +++++++++++++ pkg/loader/bundle.go | 139 +++++++++++ pkg/loader/compose.go | 169 +++++++++++++ pkg/transformer/kubernetes.go | 188 +++++++++++++++ version/version.go | 4 +- 7 files changed, 719 insertions(+), 456 deletions(-) delete mode 100644 cli/app/types.go create mode 100644 pkg/kobject/kobject.go diff --git a/cli/app/app.go b/cli/app/app.go index 1ab9132d..1adb05e7 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -27,12 +27,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/urfave/cli" - "github.com/docker/docker/api/client/bundlefile" - "github.com/docker/libcompose/config" - "github.com/docker/libcompose/docker" - "github.com/docker/libcompose/lookup" - "github.com/docker/libcompose/project" - "encoding/json" "io/ioutil" @@ -45,6 +39,7 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/intstr" @@ -52,8 +47,12 @@ import ( // install kubernetes api _ "github.com/openshift/origin/pkg/deploy/api/install" - "github.com/fatih/structs" "github.com/ghodss/yaml" + "github.com/skippbox/kompose/pkg/kobject" + "github.com/skippbox/kompose/pkg/transformer" + "github.com/docker/libcompose/lookup" + "github.com/docker/libcompose/config" + "github.com/docker/libcompose/project" ) const ( @@ -61,49 +60,7 @@ const ( DefaultComposeFile = "docker-compose.yml" ) -var unsupportedKey = map[string]int{ - "Build": 0, - "CapAdd": 0, - "CapDrop": 0, - "CPUSet": 0, - "CPUShares": 0, - "CPUQuota": 0, - "CgroupParent": 0, - "Devices": 0, - "DependsOn": 0, - "DNS": 0, - "DNSSearch": 0, - "DomainName": 0, - "Entrypoint": 0, - "EnvFile": 0, - "Expose": 0, - "Extends": 0, - "ExternalLinks": 0, - "ExtraHosts": 0, - "Hostname": 0, - "Ipc": 0, - "Logging": 0, - "MacAddress": 0, - "MemLimit": 0, - "MemSwapLimit": 0, - "NetworkMode": 0, - "Networks": 0, - "Pid": 0, - "SecurityOpt": 0, - "ShmSize": 0, - "StopSignal": 0, - "VolumeDriver": 0, - "VolumesFrom": 0, - "Uts": 0, - "ReadOnly": 0, - "StdinOpen": 0, - "Tty": 0, - "User": 0, - "Ulimits": 0, - "Dockerfile": 0, - "Net": 0, - "Args": 0, -} +var inputFormat = "compose" var composeOptions = map[string]string{ "Build": "build", @@ -315,7 +272,7 @@ func createOutFile(out string) *os.File { } // Init RC object -func initRC(name string, service ServiceConfig, replicas int) *api.ReplicationController { +func initRC(name string, service kobject.ServiceConfig, replicas int) *api.ReplicationController { rc := &api.ReplicationController{ TypeMeta: unversioned.TypeMeta{ Kind: "ReplicationController", @@ -347,7 +304,7 @@ func initRC(name string, service ServiceConfig, replicas int) *api.ReplicationCo } // Init SC object -func initSC(name string, service ServiceConfig) *api.Service { +func initSC(name string, service kobject.ServiceConfig) *api.Service { sc := &api.Service{ TypeMeta: unversioned.TypeMeta{ Kind: "Service", @@ -365,7 +322,7 @@ func initSC(name string, service ServiceConfig) *api.Service { } // Init DC object -func initDC(name string, service ServiceConfig, replicas int) *extensions.Deployment { +func initDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { dc := &extensions.Deployment{ TypeMeta: unversioned.TypeMeta{ Kind: "Deployment", @@ -400,7 +357,7 @@ func initDC(name string, service ServiceConfig, replicas int) *extensions.Deploy } // Init DS object -func initDS(name string, service ServiceConfig) *extensions.DaemonSet { +func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { ds := &extensions.DaemonSet{ TypeMeta: unversioned.TypeMeta{ Kind: "DaemonSet", @@ -429,7 +386,7 @@ func initDS(name string, service ServiceConfig) *extensions.DaemonSet { } // Init RS object -func initRS(name string, service ServiceConfig, replicas int) *extensions.ReplicaSet { +func initRS(name string, service kobject.ServiceConfig, replicas int) *extensions.ReplicaSet { rs := &extensions.ReplicaSet{ TypeMeta: unversioned.TypeMeta{ Kind: "ReplicaSet", @@ -460,7 +417,7 @@ func initRS(name string, service ServiceConfig, replicas int) *extensions.Replic } // initDeploymentConfig initialize OpenShifts DeploymentConfig object -func initDeploymentConfig(name string, service ServiceConfig, replicas int) *deployapi.DeploymentConfig { +func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { dc := &deployapi.DeploymentConfig{ TypeMeta: unversioned.TypeMeta{ Kind: "DeploymentConfig", @@ -493,7 +450,7 @@ func initDeploymentConfig(name string, service ServiceConfig, replicas int) *dep } // Configure the environment variables. -func configEnvs(name string, service ServiceConfig) []api.EnvVar { +func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { envs := []api.EnvVar{} for _, v := range service.Environment { envs = append(envs, api.EnvVar{ @@ -506,7 +463,7 @@ func configEnvs(name string, service ServiceConfig) []api.EnvVar { } // Configure the container volumes. -func configVolumes(service ServiceConfig) ([]api.VolumeMount, []api.Volume) { +func configVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { volumesMount := []api.VolumeMount{} volumes := []api.Volume{} volumeSource := api.VolumeSource{} @@ -574,16 +531,16 @@ func isPath(substring string) bool { } // Configure the container ports. -func configPorts(name string, service ServiceConfig) []api.ContainerPort { +func configPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { ports := []api.ContainerPort{} for _, port := range service.Port { var p api.Protocol switch port.Protocol { default: p = api.ProtocolTCP - case ProtocolTCP: + case kobject.ProtocolTCP: p = api.ProtocolTCP - case ProtocolUDP: + case kobject.ProtocolUDP: p = api.ProtocolUDP } ports = append(ports, api.ContainerPort{ @@ -596,7 +553,7 @@ func configPorts(name string, service ServiceConfig) []api.ContainerPort { } // Configure the container service ports. -func configServicePorts(name string, service ServiceConfig) []api.ServicePort { +func configServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { servicePorts := []api.ServicePort{} for _, port := range service.Port { if port.HostPort == 0 { @@ -606,9 +563,9 @@ func configServicePorts(name string, service ServiceConfig) []api.ServicePort { switch port.Protocol { default: p = api.ProtocolTCP - case ProtocolTCP: + case kobject.ProtocolTCP: p = api.ProtocolTCP - case ProtocolUDP: + case kobject.ProtocolUDP: p = api.ProtocolUDP } var targetPort intstr.IntOrString @@ -625,7 +582,7 @@ func configServicePorts(name string, service ServiceConfig) []api.ServicePort { } // Transform data to json/yaml -func transformer(obj runtime.Object, generateYaml bool) ([]byte, error) { +func transformData(obj runtime.Object, generateYaml bool) ([]byte, error) { // Convert to versioned object objectVersion := obj.GetObjectKind().GroupVersionKind() version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} @@ -646,284 +603,6 @@ func transformer(obj runtime.Object, generateYaml bool) ([]byte, error) { return data, nil } -// load Environment Variable from bundles file -func loadEnvVars(service bundlefile.Service) ([]EnvVar, string) { - envs := []EnvVar{} - for _, env := range service.Env { - character := "=" - if strings.Contains(env, character) { - value := env[strings.Index(env, character)+1:] - name := env[0:strings.Index(env, character)] - name = strings.TrimSpace(name) - value = strings.TrimSpace(value) - envs = append(envs, EnvVar{ - Name: name, - Value: value, - }) - } else { - character = ":" - if strings.Contains(env, character) { - charQuote := "'" - value := env[strings.Index(env, character)+1:] - name := env[0:strings.Index(env, character)] - name = strings.TrimSpace(name) - value = strings.TrimSpace(value) - if strings.Contains(value, charQuote) { - value = strings.Trim(value, "'") - } - envs = append(envs, EnvVar{ - Name: name, - Value: value, - }) - } else { - return envs, "Invalid container env " + env - } - } - } - return envs, "" -} - -// load Environment Variable from compose file -func loadEnvVarsFromCompose(e map[string]string) []EnvVar { - envs := []EnvVar{} - for k, v := range e { - envs = append(envs, EnvVar{ - Name: k, - Value: v, - }) - } - return envs -} - -// load Ports from bundles file -func loadPorts(service bundlefile.Service) ([]Ports, string) { - ports := []Ports{} - for _, port := range service.Ports { - var p Protocol - switch port.Protocol { - default: - p = ProtocolTCP - case "TCP": - p = ProtocolTCP - case "UDP": - p = ProtocolUDP - } - ports = append(ports, Ports{ - HostPort: int32(port.Port), - ContainerPort: int32(port.Port), - Protocol: p, - }) - } - return ports, "" -} - -// Load Ports from compose file -func loadPortsFromCompose(composePorts []string) ([]Ports, string) { - ports := []Ports{} - character := ":" - for _, port := range composePorts { - p := ProtocolTCP - if strings.Contains(port, character) { - hostPort := port[0:strings.Index(port, character)] - hostPort = strings.TrimSpace(hostPort) - hostPortInt, err := strconv.Atoi(hostPort) - if err != nil { - return nil, "Invalid host port of " + port - } - containerPort := port[strings.Index(port, character)+1:] - containerPort = strings.TrimSpace(containerPort) - containerPortInt, err := strconv.Atoi(containerPort) - if err != nil { - return nil, "Invalid container port of " + port - } - ports = append(ports, Ports{ - HostPort: int32(hostPortInt), - ContainerPort: int32(containerPortInt), - Protocol: p, - }) - } else { - containerPortInt, err := strconv.Atoi(port) - if err != nil { - return nil, "Invalid container port of " + port - } - ports = append(ports, Ports{ - ContainerPort: int32(containerPortInt), - Protocol: p, - }) - } - - } - return ports, "" -} - -// load Image from bundles file -func loadImage(service bundlefile.Service) (string, string) { - character := "@" - if strings.Contains(service.Image, character) { - return service.Image[0:strings.Index(service.Image, character)], "" - } - return "", "Invalid image format" -} - -// Load DAB file into KomposeObject -func loadBundlesFile(file string) KomposeObject { - komposeObject := KomposeObject{ - ServiceConfigs: make(map[string]ServiceConfig), - } - buf, err := ioutil.ReadFile(file) - if err != nil { - logrus.Fatalf("Failed to read bundles file: %v", err) - } - reader := strings.NewReader(string(buf)) - bundle, err := bundlefile.LoadFile(reader) - if err != nil { - logrus.Fatalf("Failed to parse bundles file: %v", err) - } - - for name, service := range bundle.Services { - checkUnsupportedKey(service) - serviceConfig := ServiceConfig{} - serviceConfig.Command = service.Command - serviceConfig.Args = service.Args - // convert bundle labels to annotations - serviceConfig.Annotations = service.Labels - - image, err := loadImage(service) - if err != "" { - logrus.Fatalf("Failed to load image from bundles file: %v", err) - } - serviceConfig.Image = image - - envs, err := loadEnvVars(service) - if err != "" { - logrus.Fatalf("Failed to load envvar from bundles file: %v", err) - } - serviceConfig.Environment = envs - - ports, err := loadPorts(service) - if err != "" { - logrus.Fatalf("Failed to load ports from bundles file: %v", err) - } - serviceConfig.Port = ports - - if service.WorkingDir != nil { - serviceConfig.WorkingDir = *service.WorkingDir - } - - komposeObject.ServiceConfigs[name] = serviceConfig - } - return komposeObject -} - -// Load compose file into KomposeObject -func loadComposeFile(file string) KomposeObject { - komposeObject := KomposeObject{ - ServiceConfigs: make(map[string]ServiceConfig), - } - context := &docker.Context{} - if file == "" { - file = "docker-compose.yml" - } - context.ComposeFiles = []string{file} - - if context.ResourceLookup == nil { - context.ResourceLookup = &lookup.FileResourceLookup{} - } - - if context.EnvironmentLookup == nil { - cwd, err := os.Getwd() - if err != nil { - return KomposeObject{} - } - context.EnvironmentLookup = &lookup.ComposableEnvLookup{ - Lookups: []config.EnvironmentLookup{ - &lookup.EnvfileLookup{ - Path: filepath.Join(cwd, ".env"), - }, - &lookup.OsEnvLookup{}, - }, - } - } - - // load compose file into composeObject - composeObject := project.NewProject(&context.Context, nil, nil) - err := composeObject.Parse() - if err != nil { - logrus.Fatalf("Failed to load compose file: %v", err) - } - - // transform composeObject into komposeObject - composeServiceNames := composeObject.ServiceConfigs.Keys() - - // volume config and network config are not supported - if len(composeObject.NetworkConfigs) > 0 { - logrus.Warningf("Unsupported network configuration of compose v2 - ignoring") - } - if len(composeObject.VolumeConfigs) > 0 { - logrus.Warningf("Unsupported volume configuration of compose v2 - ignoring") - } - - networksWarningFound := false - for _, name := range composeServiceNames { - if composeServiceConfig, ok := composeObject.ServiceConfigs.Get(name); ok { - //FIXME: networks always contains one default element, even it isn't declared in compose v2. - if len(composeServiceConfig.Networks.Networks) > 0 && - composeServiceConfig.Networks.Networks[0].Name != "default" && - !networksWarningFound { - logrus.Warningf("Unsupported key networks - ignoring") - networksWarningFound = true - } - checkUnsupportedKey(composeServiceConfig) - serviceConfig := ServiceConfig{} - serviceConfig.Image = composeServiceConfig.Image - serviceConfig.ContainerName = composeServiceConfig.ContainerName - - // load environment variables - envs := loadEnvVarsFromCompose(composeServiceConfig.Environment.ToMap()) - serviceConfig.Environment = envs - - // load ports - ports, err := loadPortsFromCompose(composeServiceConfig.Ports) - if err != "" { - logrus.Fatalf("Failed to load ports from compose file: %v", err) - } - serviceConfig.Port = ports - - serviceConfig.WorkingDir = composeServiceConfig.WorkingDir - serviceConfig.Volumes = composeServiceConfig.Volumes - - // convert compose labels to annotations - serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) - - serviceConfig.CPUSet = composeServiceConfig.CPUSet - serviceConfig.CPUShares = composeServiceConfig.CPUShares - serviceConfig.CPUQuota = composeServiceConfig.CPUQuota - serviceConfig.CapAdd = composeServiceConfig.CapAdd - serviceConfig.CapDrop = composeServiceConfig.CapDrop - serviceConfig.Expose = composeServiceConfig.Expose - serviceConfig.Privileged = composeServiceConfig.Privileged - serviceConfig.Restart = composeServiceConfig.Restart - serviceConfig.User = composeServiceConfig.User - - komposeObject.ServiceConfigs[name] = serviceConfig - } - } - return komposeObject -} - -type convertOptions struct { - toStdout bool - createD bool - createRC bool - createDS bool - createDeploymentConfig bool - createChart bool - generateYaml bool - replicas int - inputFile string - outFile string -} - // Convert komposeObject to K8S controllers func komposeConvert(komposeObject KomposeObject, opt convertOptions) []runtime.Object { var svcnames []string @@ -1119,28 +798,28 @@ func ConvertToVersion(objs []runtime.Object) ([]runtime.Object, error) { return ret, nil } -func validateFlags(opt convertOptions, singleOutput bool, dabFile, inputFile string) { - if len(opt.outFile) != 0 && opt.toStdout { +func validateFlags(opt kobject.ConvertOptions, singleOutput bool, dabFile, inputFile string) { + if len(opt.OutFile) != 0 && opt.ToStdout { logrus.Fatalf("Error: --out and --stdout can't be set at the same time") } - if opt.createChart && opt.toStdout { + if opt.CreateChart && opt.ToStdout { logrus.Fatalf("Error: chart cannot be generated when --stdout is specified") } - if opt.replicas < 0 { + if opt.Replicas < 0 { logrus.Fatalf("Error: --replicas cannot be negative") } if singleOutput { count := 0 - if opt.createD { + if opt.CreateD { count++ } - if opt.createDS { + if opt.CreateDS { count++ } - if opt.createRC { + if opt.CreateRC { count++ } - if opt.createDeploymentConfig { + if opt.CreateDeploymentConfig { count++ } if count > 1 { @@ -1172,51 +851,42 @@ func Convert(c *cli.Context) { createD = true } + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } + file := inputFile if len(dabFile) > 0 { + inputFormat = "bundle" file = dabFile } - opt := convertOptions{ - toStdout: toStdout, - createD: createD, - createRC: createRC, - createDS: createDS, - createDeploymentConfig: createDeploymentConfig, - createChart: createChart, - generateYaml: generateYaml, - replicas: replicas, - inputFile: file, - outFile: outFile, + opt := kobject.ConvertOptions{ + ToStdout: toStdout, + CreateD: createD, + CreateRC: createRC, + CreateDS: createDS, + CreateDeploymentConfig: createDeploymentConfig, + CreateChart: createChart, + GenerateYaml: generateYaml, + Replicas: replicas, + InputFile: file, + OutFile: outFile, } + f := createOutFile(opt.OutFile) + defer f.Close() + validateFlags(opt, singleOutput, dabFile, inputFile) - komposeObject := KomposeObject{} - - if len(dabFile) > 0 { - komposeObject = loadBundlesFile(dabFile) - } else { - komposeObject = loadComposeFile(inputFile) - } + komposeObject.Loader(file, inputFormat) + //komposeObject.Transformer(opt) // Convert komposeObject to K8S controllers - objects := komposeConvert(komposeObject, opt) + mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := transformer.Transform(komposeObject, opt) - // print output to places as needed - PrintList(objects, opt) -} - -func checkUnsupportedKey(service interface{}) { - s := structs.New(service) - for _, f := range s.Fields() { - if f.IsExported() && !f.IsZero() && f.Name() != "Networks" { - if count, ok := unsupportedKey[f.Name()]; ok && count == 0 { - logrus.Warningf("Unsupported key %s - ignoring", composeOptions[f.Name()]) - unsupportedKey[f.Name()]++ - } - } - } + // Print output + transformer.PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames, opt, f) } // Either print to stdout or to file/s diff --git a/cli/app/types.go b/cli/app/types.go deleted file mode 100644 index b5fe4176..00000000 --- a/cli/app/types.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2016 Skippbox, Ltd 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 app - -// KomposeObject holds the generic struct of Kompose transformation -type KomposeObject struct { - ServiceConfigs map[string]ServiceConfig -} - -// ServiceConfig holds the basic struct of a container -type ServiceConfig struct { - ContainerName string - Image string - Environment []EnvVar - Port []Ports - Command []string - WorkingDir string - Args []string - Volumes []string - Network []string - Labels map[string]string - Annotations map[string]string - CPUSet string - CPUShares int64 - CPUQuota int64 - CapAdd []string - CapDrop []string - Entrypoint []string - Expose []string - Privileged bool - Restart string - User string -} - -// EnvVar holds the environment variable struct of a container -type EnvVar struct { - Name string - Value string -} - -// Ports holds the ports struct of a container -type Ports struct { - HostPort int32 - ContainerPort int32 - Protocol Protocol -} - -// Protocol defines network protocols supported for things like container ports. -type Protocol string - -const ( - // ProtocolTCP is the TCP protocol. - ProtocolTCP Protocol = "TCP" - // ProtocolUDP is the UDP protocol. - ProtocolUDP Protocol = "UDP" -) diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go new file mode 100644 index 00000000..8121fedd --- /dev/null +++ b/pkg/kobject/kobject.go @@ -0,0 +1,167 @@ +/* +Copyright 2016 Skippbox, Ltd 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 kobject + +import ( + "github.com/Sirupsen/logrus" + + "fmt" + "github.com/skippbox/kompose/pkg/loader" + "github.com/fatih/structs" + "github.com/skippbox/kompose/pkg/transformer" +) + +var unsupportedKey = map[string]int{ + "Build": 0, + "CapAdd": 0, + "CapDrop": 0, + "CPUSet": 0, + "CPUShares": 0, + "CPUQuota": 0, + "CgroupParent": 0, + "Devices": 0, + "DependsOn": 0, + "DNS": 0, + "DNSSearch": 0, + "DomainName": 0, + "Entrypoint": 0, + "EnvFile": 0, + "Expose": 0, + "Extends": 0, + "ExternalLinks": 0, + "ExtraHosts": 0, + "Hostname": 0, + "Ipc": 0, + "Logging": 0, + "MacAddress": 0, + "MemLimit": 0, + "MemSwapLimit": 0, + "NetworkMode": 0, + "Networks": 0, + "Pid": 0, + "SecurityOpt": 0, + "ShmSize": 0, + "StopSignal": 0, + "VolumeDriver": 0, + "VolumesFrom": 0, + "Uts": 0, + "ReadOnly": 0, + "StdinOpen": 0, + "Tty": 0, + "User": 0, + "Ulimits": 0, + "Dockerfile": 0, + "Net": 0, + "Args": 0, +} + +// KomposeObject holds the generic struct of Kompose transformation +type KomposeObject struct { + ServiceConfigs map[string]ServiceConfig +} + +type ConvertOptions struct { + ToStdout bool + CreateD bool + CreateRC bool + CreateDS bool + CreateDeploymentConfig bool + CreateChart bool + GenerateYaml bool + Replicas int + InputFile string + OutFile string +} + +// ServiceConfig holds the basic struct of a container +type ServiceConfig struct { + ContainerName string + Image string + Environment []EnvVar + Port []Ports + Command []string + WorkingDir string + Args []string + Volumes []string + Network []string + Labels map[string]string + CPUSet string + CPUShares int64 + CPUQuota int64 + CapAdd []string + CapDrop []string + Entrypoint []string + Expose []string + Privileged bool + Restart string + User string +} + +// EnvVar holds the environment variable struct of a container +type EnvVar struct { + Name string + Value string +} + +// Ports holds the ports struct of a container +type Ports struct { + HostPort int32 + ContainerPort int32 + Protocol Protocol +} + +// Protocol defines network protocols supported for things like container ports. +type Protocol string + +const ( + // ProtocolTCP is the TCP protocol. + ProtocolTCP Protocol = "TCP" + // ProtocolUDP is the UDP protocol. + ProtocolUDP Protocol = "UDP" +) + +// loader takes input and converts to KomposeObject +func (k *KomposeObject) Loader(file string, inp string) { + switch inp { + case "bundle": + //k.loadBundleFile(file) + loader.LoadBundle(k, file) + case "compose": + //k.loadComposeFile(file) + loader.LoadCompose(k, file) + default: + logrus.Fatalf("Input file format is not supported") + + } +} + +// transformer takes KomposeObject and converts to K8S / OpenShift primitives +func (k *KomposeObject) Transformer(opt ConvertOptions) { + transformer.Transform(k, opt) +} + +func CheckUnsupportedKey(service interface{}) { + s := structs.New(service) + for _, f := range s.Fields() { + if f.IsExported() && !f.IsZero() { + if count, ok := unsupportedKey[f.Name()]; ok && count == 0 { + fmt.Println("WARNING: Unsupported key " + f.Name() + " - ignoring") + unsupportedKey[f.Name()]++ + } + } + } +} diff --git a/pkg/loader/bundle.go b/pkg/loader/bundle.go index 529648a2..666cc3ba 100644 --- a/pkg/loader/bundle.go +++ b/pkg/loader/bundle.go @@ -1 +1,140 @@ +/* +Copyright 2016 Skippbox, Ltd 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 loader + +import ( + "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/client/bundlefile" + "io/ioutil" + "strings" + "github.com/skippbox/kompose/pkg/kobject" +) + +// load Image from bundles file +func loadImage(service bundlefile.Service) (string, string) { + character := "@" + if strings.Contains(service.Image, character) { + return service.Image[0:strings.Index(service.Image, character)], "" + } + return "", "Invalid image format" +} + +// load Environment Variable from bundles file +func loadEnvVarsfromBundle(service bundlefile.Service) ([]kobject.EnvVar, string) { + envs := []kobject.EnvVar{} + for _, env := range service.Env { + character := "=" + if strings.Contains(env, character) { + value := env[strings.Index(env, character)+1:] + name := env[0:strings.Index(env, character)] + name = strings.TrimSpace(name) + value = strings.TrimSpace(value) + envs = append(envs, kobject.EnvVar{ + Name: name, + Value: value, + }) + } else { + character = ":" + if strings.Contains(env, character) { + charQuote := "'" + value := env[strings.Index(env, character)+1:] + name := env[0:strings.Index(env, character)] + name = strings.TrimSpace(name) + value = strings.TrimSpace(value) + if strings.Contains(value, charQuote) { + value = strings.Trim(value, "'") + } + envs = append(envs, kobject.EnvVar{ + Name: name, + Value: value, + }) + } else { + return envs, "Invalid container env " + env + } + } + } + return envs, "" +} + +// load Ports from bundles file +func loadPortsfromBundle(service bundlefile.Service) ([]kobject.Ports, string) { + ports := []kobject.Ports{} + for _, port := range service.Ports { + var p kobject.Protocol + switch port.Protocol { + default: + p = kobject.ProtocolTCP + case "TCP": + p = kobject.ProtocolTCP + case "UDP": + p = kobject.ProtocolUDP + } + ports = append(ports, kobject.Ports{ + HostPort: int32(port.Port), + ContainerPort: int32(port.Port), + Protocol: p, + }) + } + return ports, "" +} + +// load Bundlefile into KomposeObject +func LoadBundle(komposeObject *kobject.KomposeObject, file string) { + buf, err := ioutil.ReadFile(file) + if err != nil { + logrus.Fatalf("Failed to read bundles file: ", err) + } + reader := strings.NewReader(string(buf)) + bundle, err := bundlefile.LoadFile(reader) + if err != nil { + logrus.Fatalf("Failed to parse bundles file: ", err) + } + + for name, service := range bundle.Services { + kobject.CheckUnsupportedKey(service) + + serviceConfig := kobject.ServiceConfig{} + serviceConfig.Command = service.Command + serviceConfig.Args = service.Args + // convert bundle labels to annotations + serviceConfig.Annotations = service.Labels + + image, err := loadImage(service) + if err != "" { + logrus.Fatalf("Failed to load image from bundles file: " + err) + } + serviceConfig.Image = image + + envs, err := loadEnvVarsfromBundle(service) + if err != "" { + logrus.Fatalf("Failed to load envvar from bundles file: " + err) + } + serviceConfig.Environment = envs + + ports, err := loadPortsfromBundle(service) + if err != "" { + logrus.Fatalf("Failed to load ports from bundles file: " + err) + } + serviceConfig.Port = ports + + if service.WorkingDir != nil { + serviceConfig.WorkingDir = *service.WorkingDir + } + + komposeObject.ServiceConfigs[name] = serviceConfig + } +} diff --git a/pkg/loader/compose.go b/pkg/loader/compose.go index 529648a2..aee17070 100644 --- a/pkg/loader/compose.go +++ b/pkg/loader/compose.go @@ -1 +1,170 @@ +/* +Copyright 2016 Skippbox, Ltd 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 loader + +import ( + "github.com/Sirupsen/logrus" + "github.com/docker/libcompose/docker" + "github.com/docker/libcompose/project" + "github.com/skippbox/kompose/pkg/kobject" + "strconv" + "strings" +) + +// load Environment Variable from compose file +func loadEnvVarsfromCompose(e map[string]string) []kobject.EnvVar { + envs := []kobject.EnvVar{} + for k, v := range e { + envs = append(envs, kobject.EnvVar{ + Name: k, + Value: v, + }) + } + return envs +} + +// Load Ports from compose file +func loadPortsFromCompose(composePorts []string) ([]kobject.Ports, string) { + ports := []kobject.Ports{} + character := ":" + for _, port := range composePorts { + p := kobject.ProtocolTCP + if strings.Contains(port, character) { + hostPort := port[0:strings.Index(port, character)] + hostPort = strings.TrimSpace(hostPort) + hostPortInt, err := strconv.Atoi(hostPort) + if err != nil { + return nil, "Invalid host port of " + port + } + containerPort := port[strings.Index(port, character)+1:] + containerPort = strings.TrimSpace(containerPort) + containerPortInt, err := strconv.Atoi(containerPort) + if err != nil { + return nil, "Invalid container port of " + port + } + ports = append(ports, kobject.Ports{ + HostPort: int32(hostPortInt), + ContainerPort: int32(containerPortInt), + Protocol: p, + }) + } else { + containerPortInt, err := strconv.Atoi(port) + if err != nil { + return nil, "Invalid container port of " + port + } + ports = append(ports, kobject.Ports{ + ContainerPort: int32(containerPortInt), + Protocol: p, + }) + } + + } + return ports, "" +} + +// load Docker Compose file into KomposeObject +func LoadCompose(komposeObject *kobject.KomposeObject, file string) { + context := &docker.Context{} + if file == "" { + file = "docker-compose.yml" + } + context.ComposeFiles = []string{file} + + if context.ResourceLookup == nil { + context.ResourceLookup = &lookup.FileResourceLookup{} + } + + if context.EnvironmentLookup == nil { + cwd, err := os.Getwd() + if err != nil { + return KomposeObject{} + } + context.EnvironmentLookup = &lookup.ComposableEnvLookup{ + Lookups: []config.EnvironmentLookup{ + &lookup.EnvfileLookup{ + Path: filepath.Join(cwd, ".env"), + }, + &lookup.OsEnvLookup{}, + }, + } + } + + // load compose file into composeObject + composeObject := project.NewProject(&context.Context, nil, nil) + err := composeObject.Parse() + if err != nil { + logrus.Fatalf("Failed to load compose file", err) + } + + // transform composeObject into komposeObject + composeServiceNames := composeObject.ServiceConfigs.Keys() + + // volume config and network config are not supported + if len(composeObject.NetworkConfigs) > 0 { + logrus.Warningf("Unsupported network configuration of compose v2 - ignoring") + } + if len(composeObject.VolumeConfigs) > 0 { + logrus.Warningf("Unsupported volume configuration of compose v2 - ignoring") + } + + networksWarningFound := false + + for _, name := range composeServiceNames { + if composeServiceConfig, ok := composeObject.ServiceConfigs.Get(name); ok { + //FIXME: networks always contains one default element, even it isn't declared in compose v2. + if len(composeServiceConfig.Networks.Networks) > 0 && + composeServiceConfig.Networks.Networks[0].Name != "default" && + !networksWarningFound { + logrus.Warningf("Unsupported key networks - ignoring") + networksWarningFound = true + } + kobject.CheckUnsupportedKey(composeServiceConfig) + serviceConfig := kobject.ServiceConfig{} + serviceConfig.Image = composeServiceConfig.Image + serviceConfig.ContainerName = composeServiceConfig.ContainerName + + // load environment variables + envs := loadEnvVarsfromCompose(composeServiceConfig.Environment.ToMap()) + serviceConfig.Environment = envs + + // load ports + ports, err := loadPortsFromCompose(composeServiceConfig.Ports) + if err != "" { + logrus.Fatalf("Failed to load ports from compose file: " + err) + } + serviceConfig.Port = ports + + serviceConfig.WorkingDir = composeServiceConfig.WorkingDir + serviceConfig.Volumes = composeServiceConfig.Volumes + + // convert compose labels to annotations + serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) + + serviceConfig.CPUSet = composeServiceConfig.CPUSet + serviceConfig.CPUShares = composeServiceConfig.CPUShares + serviceConfig.CPUQuota = composeServiceConfig.CPUQuota + serviceConfig.CapAdd = composeServiceConfig.CapAdd + serviceConfig.CapDrop = composeServiceConfig.CapDrop + serviceConfig.Expose = composeServiceConfig.Expose + serviceConfig.Privileged = composeServiceConfig.Privileged + serviceConfig.Restart = composeServiceConfig.Restart + serviceConfig.User = composeServiceConfig.User + + komposeObject.ServiceConfigs[name] = serviceConfig + } + } +} diff --git a/pkg/transformer/kubernetes.go b/pkg/transformer/kubernetes.go index 38ef5e16..884a001f 100644 --- a/pkg/transformer/kubernetes.go +++ b/pkg/transformer/kubernetes.go @@ -1 +1,189 @@ package transformer + +import ( + "github.com/skippbox/kompose/pkg/kobject" + "fmt" + "os" +) + +func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string) { + mServices := make(map[string][]byte) + mReplicationControllers := make(map[string][]byte) + mDeployments := make(map[string][]byte) + mDaemonSets := make(map[string][]byte) + mReplicaSets := make(map[string][]byte) + // OpenShift DeploymentConfigs + mDeploymentConfigs := make(map[string][]byte) + + f := createOutFile(opt.outFile) + defer f.Close() + + var svcnames []string + + for name, service := range komposeObject.ServiceConfigs { + svcnames = append(svcnames, name) + + rc := initRC(name, service, opt.replicas) + sc := initSC(name, service) + dc := initDC(name, service, opt.replicas) + ds := initDS(name, service) + osDC := initDeploymentConfig(name, service, opt.replicas) // OpenShift DeploymentConfigs + + // Configure the environment variables. + envs := configEnvs(name, service) + + // Configure the container command. + var cmds []string + for _, cmd := range service.Command { + cmds = append(cmds, cmd) + } + // Configure the container volumes. + volumesMount, volumes := configVolumes(service) + + // Configure the container ports. + ports := configPorts(name, service) + + // Configure the service ports. + servicePorts := configServicePorts(name, service) + sc.Spec.Ports = servicePorts + + // Configure label + labels := map[string]string{"service": name} + sc.ObjectMeta.Labels = labels + // Configure annotations + annotations := map[string]string{} + for key, value := range service.Annotations { + annotations[key] = value + } + sc.ObjectMeta.Annotations = annotations + + // fillTemplate fills the pod template with the value calculated from config + fillTemplate := func(template *api.PodTemplateSpec) { + template.Spec.Containers[0].Env = envs + template.Spec.Containers[0].Command = cmds + template.Spec.Containers[0].WorkingDir = service.WorkingDir + template.Spec.Containers[0].VolumeMounts = volumesMount + template.Spec.Volumes = volumes + // Configure the container privileged mode + if service.Privileged == true { + template.Spec.Containers[0].SecurityContext = &api.SecurityContext{ + Privileged: &service.Privileged, + } + } + template.Spec.Containers[0].Ports = ports + template.ObjectMeta.Labels = labels + // Configure the container restart policy. + switch service.Restart { + case "", "always": + template.Spec.RestartPolicy = api.RestartPolicyAlways + case "no": + template.Spec.RestartPolicy = api.RestartPolicyNever + case "on-failure": + template.Spec.RestartPolicy = api.RestartPolicyOnFailure + default: + logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name) + } + } + + // fillObjectMeta fills the metadata with the value calculated from config + fillObjectMeta := func(meta *api.ObjectMeta) { + meta.Labels = labels + meta.Annotations = annotations + } + + // Update each supported controllers + updateController(rc, fillTemplate, fillObjectMeta) + updateController(dc, fillTemplate, fillObjectMeta) + updateController(ds, fillTemplate, fillObjectMeta) + // OpenShift DeploymentConfigs + updateController(osDC, fillTemplate, fillObjectMeta) + + // convert datarc to json / yaml + datarc, err := transformer(rc, opt.generateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + // convert datadc to json / yaml + datadc, err := transformer(dc, opt.generateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + // convert datads to json / yaml + datads, err := transformer(ds, opt.generateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + var datasvc []byte + // If ports not provided in configuration we will not make service + if len(ports) == 0 { + logrus.Warningf("[%s] Service cannot be created because of missing port.", name) + } else if len(ports) != 0 { + // convert datasvc to json / yaml + datasvc, err = transformer(sc, opt.generateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + } + + // convert OpenShift DeploymentConfig to json / yaml + dataDeploymentConfig, err := transformer(osDC, opt.generateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + mServices[name] = datasvc + mReplicationControllers[name] = datarc + mDeployments[name] = datadc + mDaemonSets[name] = datads + mDeploymentConfigs[name] = dataDeploymentConfig + } + + return mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames +} + +func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt convertOptions, f *os.File) { + for k, v := range mServices { + if v != nil { + print(k, "svc", v, opt.toStdout, opt.generateYaml, f) + } + } + + // If --out or --stdout is set, the validation should already prevent multiple controllers being generated + if opt.createD { + for k, v := range mDeployments { + print(k, "deployment", v, opt.toStdout, opt.generateYaml, f) + } + } + + if opt.createDS { + for k, v := range mDaemonSets { + print(k, "daemonset", v, opt.toStdout, opt.generateYaml, f) + } + } + + if opt.createRC { + for k, v := range mReplicationControllers { + print(k, "rc", v, opt.toStdout, opt.generateYaml, f) + } + } + + if f != nil { + fmt.Fprintf(os.Stdout, "file %q created\n", opt.outFile) + } + + if opt.createChart { + err := generateHelm(opt.inputFile, svcnames, opt.generateYaml, opt.createD, opt.createDS, opt.createRC, opt.outFile) + if err != nil { + logrus.Fatalf("Failed to create Chart data: %v", err) + } + } + + if opt.createDeploymentConfig { + for k, v := range mDeploymentConfigs { + print(k, "deploymentconfig", v, opt.toStdout, opt.generateYaml, f) + } + } +} diff --git a/version/version.go b/version/version.go index c22807ea..4dcae1d7 100644 --- a/version/version.go +++ b/version/version.go @@ -17,9 +17,9 @@ limitations under the License. package version var ( -// VERSION should be updated by hand at each release + // VERSION should be updated by hand at each release VERSION = "0.0.1-beta" -// GITCOMMIT will be overwritten automatically by the build system + // GITCOMMIT will be overwritten automatically by the build system GITCOMMIT = "HEAD" ) From e867d35e399293b3441221255c2a915b4950205f Mon Sep 17 00:00:00 2001 From: Tuna Date: Mon, 1 Aug 2016 15:21:38 +0700 Subject: [PATCH 3/7] creat kobject package, make loader and transformer refering to it --- cli/app/app.go | 487 +---------------------- pkg/kobject/kobject.go | 82 +++- pkg/loader/bundle.go | 10 +- pkg/loader/compose.go | 13 +- {cli/app => pkg/transformer}/k8sutils.go | 2 +- pkg/transformer/kubernetes.go | 453 +++++++++++++++++++-- pkg/transformer/openshift.go | 16 + 7 files changed, 541 insertions(+), 522 deletions(-) rename {cli/app => pkg/transformer}/k8sutils.go (99%) diff --git a/cli/app/app.go b/cli/app/app.go index 1adb05e7..cc77979d 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -17,104 +17,37 @@ limitations under the License. package app import ( - "fmt" - "math/rand" - "os" - "path/filepath" - "strconv" - "strings" - "github.com/Sirupsen/logrus" "github.com/urfave/cli" - "encoding/json" - "io/ioutil" - // 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/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util/intstr" - - deployapi "github.com/openshift/origin/pkg/deploy/api" // install kubernetes api _ "github.com/openshift/origin/pkg/deploy/api/install" - "github.com/ghodss/yaml" "github.com/skippbox/kompose/pkg/kobject" + "github.com/skippbox/kompose/pkg/loader" "github.com/skippbox/kompose/pkg/transformer" "github.com/docker/libcompose/lookup" "github.com/docker/libcompose/config" "github.com/docker/libcompose/project" + "fmt" + "strings" ) const ( - letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" DefaultComposeFile = "docker-compose.yml" ) var inputFormat = "compose" -var composeOptions = map[string]string{ - "Build": "build", - "CapAdd": "cap_add", - "CapDrop": "cap_drop", - "CPUSet": "cpuset", - "CPUShares": "cpu_shares", - "CPUQuota": "cpu_quota", - "CgroupParent": "cgroup_parent", - "Devices": "devices", - "DependsOn": "depends_on", - "DNS": "dns", - "DNSSearch": "dns_search", - "DomainName": "domainname", - "Entrypoint": "entrypoint", - "EnvFile": "env_file", - "Expose": "expose", - "Extends": "extends", - "ExternalLinks": "external_links", - "ExtraHosts": "extra_hosts", - "Hostname": "hostname", - "Ipc": "ipc", - "Logging": "logging", - "MacAddress": "mac_address", - "MemLimit": "mem_limit", - "MemSwapLimit": "memswap_limit", - "NetworkMode": "network_mode", - "Networks": "networks", - "Pid": "pid", - "SecurityOpt": "security_opt", - "ShmSize": "shm_size", - "StopSignal": "stop_signal", - "VolumeDriver": "volume_driver", - "VolumesFrom": "volumes_from", - "Uts": "uts", - "ReadOnly": "read_only", - "StdinOpen": "stdin_open", - "Tty": "tty", - "User": "user", - "Ulimits": "ulimits", - "Dockerfile": "dockerfile", - "Net": "net", - "Args": "args", -} - -// RandStringBytes generates randomly n-character string -func RandStringBytes(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = letterBytes[rand.Intn(len(letterBytes))] - } - return string(b) -} - // BeforeApp is an action that is executed before any cli command. func BeforeApp(c *cli.Context) error { if c.GlobalBool("verbose") { @@ -258,351 +191,6 @@ func Scale(c *cli.Context) { //} } -// Create the file to write to if --out is specified -func createOutFile(out string) *os.File { - var f *os.File - var err error - if len(out) != 0 { - f, err = os.Create(out) - if err != nil { - logrus.Fatalf("error opening file: %v", err) - } - } - return f -} - -// Init RC object -func 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, - //Labels: map[string]string{"service": name}, - }, - Spec: api.ReplicationControllerSpec{ - Selector: map[string]string{"service": name}, - Replicas: int32(replicas), - Template: &api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - //Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return rc -} - -// Init SC object -func initSC(name string, service kobject.ServiceConfig) *api.Service { - sc := &api.Service{ - TypeMeta: unversioned.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - //Labels: map[string]string{"service": name}, - }, - Spec: api.ServiceSpec{ - Selector: map[string]string{"service": name}, - }, - } - return sc -} - -// Init DC object -func initDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { - dc := &extensions.Deployment{ - TypeMeta: unversioned.TypeMeta{ - Kind: "Deployment", - APIVersion: "extensions/v1beta1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - Labels: map[string]string{"service": name}, - }, - Spec: extensions.DeploymentSpec{ - Replicas: int32(replicas), - Selector: &unversioned.LabelSelector{ - MatchLabels: map[string]string{"service": name}, - }, - //UniqueLabelKey: p.Name, - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return dc -} - -// Init DS object -func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { - ds := &extensions.DaemonSet{ - TypeMeta: unversioned.TypeMeta{ - Kind: "DaemonSet", - APIVersion: "extensions/v1beta1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - }, - Spec: extensions.DaemonSetSpec{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Name: name, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return ds -} - -// Init RS object -func initRS(name string, service kobject.ServiceConfig, replicas int) *extensions.ReplicaSet { - rs := &extensions.ReplicaSet{ - TypeMeta: unversioned.TypeMeta{ - Kind: "ReplicaSet", - APIVersion: "extensions/v1beta1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - }, - Spec: extensions.ReplicaSetSpec{ - Replicas: int32(replicas), - Selector: &unversioned.LabelSelector{ - MatchLabels: map[string]string{"service": name}, - }, - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{}, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return rs -} - -// initDeploymentConfig initialize OpenShifts DeploymentConfig object -func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { - dc := &deployapi.DeploymentConfig{ - TypeMeta: unversioned.TypeMeta{ - Kind: "DeploymentConfig", - APIVersion: "v1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - Labels: map[string]string{"service": name}, - }, - Spec: deployapi.DeploymentConfigSpec{ - Replicas: int32(replicas), - Selector: map[string]string{"service": name}, - //UniqueLabelKey: p.Name, - Template: &api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return dc -} - -// Configure the environment variables. -func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { - envs := []api.EnvVar{} - for _, v := range service.Environment { - envs = append(envs, api.EnvVar{ - Name: v.Name, - Value: v.Value, - }) - } - - return envs -} - -// Configure the container volumes. -func configVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { - volumesMount := []api.VolumeMount{} - volumes := []api.Volume{} - volumeSource := api.VolumeSource{} - for _, volume := range service.Volumes { - name, host, container, mode, err := parseVolume(volume) - if err != nil { - logrus.Warningf("Failed to configure container volume: %v", err) - continue - } - - // if volume name isn't specified, set it to a random string of 20 chars - if len(name) == 0 { - name = RandStringBytes(20) - } - // check if ro/rw mode is defined, default rw - readonly := len(mode) > 0 && mode == "ro" - - volumesMount = append(volumesMount, api.VolumeMount{Name: name, ReadOnly: readonly, MountPath: container}) - - if len(host) > 0 { - volumeSource = api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: host}} - } else { - volumeSource = api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}} - } - - volumes = append(volumes, api.Volume{Name: name, VolumeSource: volumeSource}) - } - return volumesMount, volumes -} - -// parseVolume parse a given volume, which might be [name:][host:]container[:access_mode] -func parseVolume(volume string) (name, host, container, mode string, err error) { - separator := ":" - volumeStrings := strings.Split(volume, separator) - if len(volumeStrings) == 0 { - return - } - // Set name if existed - if !isPath(volumeStrings[0]) { - name = volumeStrings[0] - volumeStrings = volumeStrings[1:] - } - if len(volumeStrings) == 0 { - err = fmt.Errorf("invalid volume format: %s", volume) - return - } - if volumeStrings[len(volumeStrings)-1] == "rw" || volumeStrings[len(volumeStrings)-1] == "ro" { - mode = volumeStrings[len(volumeStrings)-1] - volumeStrings = volumeStrings[:len(volumeStrings)-1] - } - container = volumeStrings[len(volumeStrings)-1] - volumeStrings = volumeStrings[:len(volumeStrings)-1] - if len(volumeStrings) == 1 { - host = volumeStrings[0] - } - if !isPath(container) || (len(host) > 0 && !isPath(host)) || len(volumeStrings) > 1 { - err = fmt.Errorf("invalid volume format: %s", volume) - return - } - return -} - -func isPath(substring string) bool { - return strings.Contains(substring, "/") -} - -// Configure the container ports. -func configPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { - ports := []api.ContainerPort{} - for _, port := range service.Port { - var p api.Protocol - switch port.Protocol { - default: - p = api.ProtocolTCP - case kobject.ProtocolTCP: - p = api.ProtocolTCP - case kobject.ProtocolUDP: - p = api.ProtocolUDP - } - ports = append(ports, api.ContainerPort{ - ContainerPort: port.ContainerPort, - Protocol: p, - }) - } - - return ports -} - -// Configure the container service ports. -func configServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { - servicePorts := []api.ServicePort{} - for _, port := range service.Port { - if port.HostPort == 0 { - port.HostPort = port.ContainerPort - } - var p api.Protocol - switch port.Protocol { - default: - p = api.ProtocolTCP - case kobject.ProtocolTCP: - p = api.ProtocolTCP - case kobject.ProtocolUDP: - p = api.ProtocolUDP - } - var targetPort intstr.IntOrString - targetPort.IntVal = port.ContainerPort - targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) - servicePorts = append(servicePorts, api.ServicePort{ - Name: strconv.Itoa(int(port.HostPort)), - Protocol: p, - Port: port.HostPort, - TargetPort: targetPort, - }) - } - return servicePorts -} - -// Transform data to json/yaml -func transformData(obj runtime.Object, generateYaml bool) ([]byte, error) { - // Convert to versioned object - objectVersion := obj.GetObjectKind().GroupVersionKind() - version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} - versionedObj, err := api.Scheme.ConvertToVersion(obj, version) - if err != nil { - return nil, err - } - - // convert data to json / yaml - data, err := json.MarshalIndent(versionedObj, "", " ") - if generateYaml == true { - data, err = yaml.Marshal(versionedObj) - } - if err != nil { - return nil, err - } - logrus.Debugf("%s\n", data) - return data, nil -} - // Convert komposeObject to K8S controllers func komposeConvert(komposeObject KomposeObject, opt convertOptions) []runtime.Object { var svcnames []string @@ -861,6 +449,17 @@ func Convert(c *cli.Context) { file = dabFile } + //komposeObject.Loader(file, inputFormat) + switch inputFormat { + case "bundle": + komposeObject = loader.LoadBundle(file) + case "compose": + komposeObject = loader.LoadCompose(file) + default: + logrus.Fatalf("Input file format is not supported") + + } + opt := kobject.ConvertOptions{ ToStdout: toStdout, CreateD: createD, @@ -874,14 +473,8 @@ func Convert(c *cli.Context) { OutFile: outFile, } - f := createOutFile(opt.OutFile) - defer f.Close() - validateFlags(opt, singleOutput, dabFile, inputFile) - komposeObject.Loader(file, inputFormat) - - //komposeObject.Transformer(opt) // Convert komposeObject to K8S controllers mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := transformer.Transform(komposeObject, opt) @@ -889,34 +482,6 @@ func Convert(c *cli.Context) { transformer.PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames, opt, f) } -// Either print to stdout or to file/s -func print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) string { - - file := "" - if generateYaml { - file = fmt.Sprintf("%s-%s.yaml", name, trailing) - } else { - file = fmt.Sprintf("%s-%s.json", name, trailing) - } - if toStdout { - fmt.Fprintf(os.Stdout, "%s\n", string(data)) - return "" - } else if f != nil { - // Write all content to a single file f - if _, err := f.WriteString(fmt.Sprintf("%s\n", string(data))); err != nil { - logrus.Fatalf("Failed to write %s to file: %v", trailing, err) - } - f.Sync() - } else { - // Write content separately to each file - if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil { - logrus.Fatalf("Failed to write %s: %v", trailing, err) - } - logrus.Printf("file %q created", file) - } - return file -} - // Up brings up deployment, svc. func Up(c *cli.Context) { fmt.Println("We are going to create Kubernetes deployment and service for your dockerized application. \n" + @@ -988,27 +553,3 @@ func sortServicesFirst(objs []runtime.Object) []runtime.Object { ret = append(ret, others...) return ret } - -// updateController updates the given object with the given pod template update function and ObjectMeta update function -func updateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec), updateMeta func(meta *api.ObjectMeta)) { - switch t := obj.(type) { - case *api.ReplicationController: - if t.Spec.Template == nil { - t.Spec.Template = &api.PodTemplateSpec{} - } - updateTemplate(t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *extensions.Deployment: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *extensions.ReplicaSet: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *extensions.DaemonSet: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *deployapi.DeploymentConfig: - updateTemplate(t.Spec.Template) - updateMeta(&t.ObjectMeta) - } -} diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 8121fedd..f6ee3701 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -17,12 +17,9 @@ limitations under the License. package kobject import ( - "github.com/Sirupsen/logrus" - "fmt" - "github.com/skippbox/kompose/pkg/loader" "github.com/fatih/structs" - "github.com/skippbox/kompose/pkg/transformer" + "math/rand" ) var unsupportedKey = map[string]int{ @@ -69,6 +66,50 @@ var unsupportedKey = map[string]int{ "Args": 0, } +var composeOptions = map[string]string{ + "Build": "build", + "CapAdd": "cap_add", + "CapDrop": "cap_drop", + "CPUSet": "cpuset", + "CPUShares": "cpu_shares", + "CPUQuota": "cpu_quota", + "CgroupParent": "cgroup_parent", + "Devices": "devices", + "DependsOn": "depends_on", + "DNS": "dns", + "DNSSearch": "dns_search", + "DomainName": "domainname", + "Entrypoint": "entrypoint", + "EnvFile": "env_file", + "Expose": "expose", + "Extends": "extends", + "ExternalLinks": "external_links", + "ExtraHosts": "extra_hosts", + "Hostname": "hostname", + "Ipc": "ipc", + "Logging": "logging", + "MacAddress": "mac_address", + "MemLimit": "mem_limit", + "MemSwapLimit": "memswap_limit", + "NetworkMode": "network_mode", + "Networks": "networks", + "Pid": "pid", + "SecurityOpt": "security_opt", + "ShmSize": "shm_size", + "StopSignal": "stop_signal", + "VolumeDriver": "volume_driver", + "VolumesFrom": "volumes_from", + "Uts": "uts", + "ReadOnly": "read_only", + "StdinOpen": "stdin_open", + "Tty": "tty", + "User": "user", + "Ulimits": "ulimits", + "Dockerfile": "dockerfile", + "Net": "net", + "Args": "args", +} + // KomposeObject holds the generic struct of Kompose transformation type KomposeObject struct { ServiceConfigs map[string]ServiceConfig @@ -99,6 +140,7 @@ type ServiceConfig struct { Volumes []string Network []string Labels map[string]string + Annotations map[string]string CPUSet string CPUShares int64 CPUQuota int64 @@ -135,24 +177,24 @@ const ( ) // loader takes input and converts to KomposeObject -func (k *KomposeObject) Loader(file string, inp string) { - switch inp { - case "bundle": - //k.loadBundleFile(file) - loader.LoadBundle(k, file) - case "compose": - //k.loadComposeFile(file) - loader.LoadCompose(k, file) - default: - logrus.Fatalf("Input file format is not supported") - - } -} +//func (k *KomposeObject) Loader(file string, inp string) { +// switch inp { +// case "bundle": +// //k.loadBundleFile(file) +// loader.LoadBundle(k, file) +// case "compose": +// //k.loadComposeFile(file) +// loader.LoadCompose(k, file) +// default: +// logrus.Fatalf("Input file format is not supported") +// +// } +//} // transformer takes KomposeObject and converts to K8S / OpenShift primitives -func (k *KomposeObject) Transformer(opt ConvertOptions) { - transformer.Transform(k, opt) -} +//func (k *KomposeObject) Transformer(opt ConvertOptions) { +// transformer.Transform(k, opt) +//} func CheckUnsupportedKey(service interface{}) { s := structs.New(service) diff --git a/pkg/loader/bundle.go b/pkg/loader/bundle.go index 666cc3ba..fcd566cc 100644 --- a/pkg/loader/bundle.go +++ b/pkg/loader/bundle.go @@ -19,9 +19,9 @@ package loader import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client/bundlefile" + "github.com/skippbox/kompose/pkg/kobject" "io/ioutil" "strings" - "github.com/skippbox/kompose/pkg/kobject" ) // load Image from bundles file @@ -93,7 +93,11 @@ func loadPortsfromBundle(service bundlefile.Service) ([]kobject.Ports, string) { } // load Bundlefile into KomposeObject -func LoadBundle(komposeObject *kobject.KomposeObject, file string) { +func LoadBundle(file string) (kobject.KomposeObject) { + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } + buf, err := ioutil.ReadFile(file) if err != nil { logrus.Fatalf("Failed to read bundles file: ", err) @@ -137,4 +141,6 @@ func LoadBundle(komposeObject *kobject.KomposeObject, file string) { komposeObject.ServiceConfigs[name] = serviceConfig } + + return komposeObject } diff --git a/pkg/loader/compose.go b/pkg/loader/compose.go index aee17070..3d1bc3b6 100644 --- a/pkg/loader/compose.go +++ b/pkg/loader/compose.go @@ -23,6 +23,10 @@ import ( "github.com/skippbox/kompose/pkg/kobject" "strconv" "strings" + "github.com/docker/libcompose/lookup" + "os" + "github.com/docker/libcompose/config" + "path/filepath" ) // load Environment Variable from compose file @@ -77,7 +81,10 @@ func loadPortsFromCompose(composePorts []string) ([]kobject.Ports, string) { } // load Docker Compose file into KomposeObject -func LoadCompose(komposeObject *kobject.KomposeObject, file string) { +func LoadCompose(file string) (kobject.KomposeObject) { + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } context := &docker.Context{} if file == "" { file = "docker-compose.yml" @@ -91,7 +98,7 @@ func LoadCompose(komposeObject *kobject.KomposeObject, file string) { if context.EnvironmentLookup == nil { cwd, err := os.Getwd() if err != nil { - return KomposeObject{} + return kobject.KomposeObject{} } context.EnvironmentLookup = &lookup.ComposableEnvLookup{ Lookups: []config.EnvironmentLookup{ @@ -167,4 +174,6 @@ func LoadCompose(komposeObject *kobject.KomposeObject, file string) { komposeObject.ServiceConfigs[name] = serviceConfig } } + + return komposeObject } diff --git a/cli/app/k8sutils.go b/pkg/transformer/k8sutils.go similarity index 99% rename from cli/app/k8sutils.go rename to pkg/transformer/k8sutils.go index 30fc604b..f75738e8 100644 --- a/cli/app/k8sutils.go +++ b/pkg/transformer/k8sutils.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package app +package transformer import ( "bytes" diff --git a/pkg/transformer/kubernetes.go b/pkg/transformer/kubernetes.go index 884a001f..d86c6689 100644 --- a/pkg/transformer/kubernetes.go +++ b/pkg/transformer/kubernetes.go @@ -1,21 +1,376 @@ +/* +Copyright 2016 Skippbox, Ltd 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 transformer import ( - "github.com/skippbox/kompose/pkg/kobject" + "encoding/json" "fmt" + "github.com/Sirupsen/logrus" + "github.com/ghodss/yaml" + "github.com/skippbox/kompose/pkg/kobject" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util/intstr" + "math/rand" "os" + "strconv" + "strings" + + deployapi "github.com/openshift/origin/pkg/deploy/api" + "io/ioutil" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/runtime" ) +const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" + +// RandStringBytes generates randomly n-character string +func RandStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} + +// Create the file to write to if --out is specified +func createOutFile(out string) *os.File { + var f *os.File + var err error + if len(out) != 0 { + f, err = os.Create(out) + if err != nil { + logrus.Fatalf("error opening file: %v", err) + } + } + return f +} + +// Init RC object +func 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, + //Labels: map[string]string{"service": name}, + }, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"service": name}, + Replicas: int32(replicas), + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + //Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return rc +} + +// Init SC object +func initSC(name string, service kobject.ServiceConfig) *api.Service { + sc := &api.Service{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + //Labels: map[string]string{"service": name}, + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{"service": name}, + }, + } + return sc +} + +// Init DC object +func initDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { + dc := &extensions.Deployment{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: map[string]string{"service": name}, + }, + Spec: extensions.DeploymentSpec{ + Replicas: int32(replicas), + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"service": name}, + }, + //UniqueLabelKey: p.Name, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return dc +} + +// Init DS object +func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { + ds := &extensions.DaemonSet{ + TypeMeta: unversioned.TypeMeta{ + Kind: "DaemonSet", + APIVersion: "extensions/v1beta1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: extensions.DaemonSetSpec{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return ds +} + +// initDeploymentConfig initialize OpenShifts DeploymentConfig object +func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { + dc := &deployapi.DeploymentConfig{ + TypeMeta: unversioned.TypeMeta{ + Kind: "DeploymentConfig", + APIVersion: "v1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: map[string]string{"service": name}, + }, + Spec: deployapi.DeploymentConfigSpec{ + Replicas: int32(replicas), + Selector: map[string]string{"service": name}, + //UniqueLabelKey: p.Name, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return dc +} + +// Configure the environment variables. +func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { + envs := []api.EnvVar{} + for _, v := range service.Environment { + envs = append(envs, api.EnvVar{ + Name: v.Name, + Value: v.Value, + }) + } + + return envs +} + +// Configure the container volumes. +func configVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { + volumesMount := []api.VolumeMount{} + volumes := []api.Volume{} + volumeSource := api.VolumeSource{} + for _, volume := range service.Volumes { + name, host, container, mode, err := parseVolume(volume) + if err != nil { + logrus.Warningf("Failed to configure container volume: %v", err) + continue + } + + // if volume name isn't specified, set it to a random string of 20 chars + if len(name) == 0 { + name = RandStringBytes(20) + } + // check if ro/rw mode is defined, default rw + readonly := len(mode) > 0 && mode == "ro" + + volumesMount = append(volumesMount, api.VolumeMount{Name: name, ReadOnly: readonly, MountPath: container}) + + if len(host) > 0 { + volumeSource = api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: host}} + } else { + volumeSource = api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}} + } + + volumes = append(volumes, api.Volume{Name: name, VolumeSource: volumeSource}) + } + return volumesMount, volumes +} + +// parseVolume parse a given volume, which might be [name:][host:]container[:access_mode] +func parseVolume(volume string) (name, host, container, mode string, err error) { + separator := ":" + volumeStrings := strings.Split(volume, separator) + if len(volumeStrings) == 0 { + return + } + // Set name if existed + if !isPath(volumeStrings[0]) { + name = volumeStrings[0] + volumeStrings = volumeStrings[1:] + } + if len(volumeStrings) == 0 { + err = fmt.Errorf("invalid volume format: %s", volume) + return + } + if volumeStrings[len(volumeStrings)-1] == "rw" || volumeStrings[len(volumeStrings)-1] == "ro" { + mode = volumeStrings[len(volumeStrings)-1] + volumeStrings = volumeStrings[:len(volumeStrings)-1] + } + container = volumeStrings[len(volumeStrings)-1] + volumeStrings = volumeStrings[:len(volumeStrings)-1] + if len(volumeStrings) == 1 { + host = volumeStrings[0] + } + if !isPath(container) || (len(host) > 0 && !isPath(host)) || len(volumeStrings) > 1 { + err = fmt.Errorf("invalid volume format: %s", volume) + return + } + return +} + +func isPath(substring string) bool { + return strings.Contains(substring, "/") +} + +// Configure the container ports. +func configPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { + ports := []api.ContainerPort{} + for _, port := range service.Port { + var p api.Protocol + switch port.Protocol { + default: + p = api.ProtocolTCP + case kobject.ProtocolTCP: + p = api.ProtocolTCP + case kobject.ProtocolUDP: + p = api.ProtocolUDP + } + ports = append(ports, api.ContainerPort{ + ContainerPort: port.ContainerPort, + Protocol: p, + }) + } + + return ports +} + +// Configure the container service ports. +func configServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { + servicePorts := []api.ServicePort{} + for _, port := range service.Port { + if port.HostPort == 0 { + port.HostPort = port.ContainerPort + } + var p api.Protocol + switch port.Protocol { + default: + p = api.ProtocolTCP + case kobject.ProtocolTCP: + p = api.ProtocolTCP + case kobject.ProtocolUDP: + p = api.ProtocolUDP + } + var targetPort intstr.IntOrString + targetPort.IntVal = port.ContainerPort + targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) + servicePorts = append(servicePorts, api.ServicePort{ + Name: strconv.Itoa(int(port.HostPort)), + Protocol: p, + Port: port.HostPort, + TargetPort: targetPort, + }) + } + return servicePorts +} + +// Transform data to json/yaml +func transformer(obj runtime.Object, GenerateYaml bool) ([]byte, error) { + // Convert to versioned object + objectVersion := obj.GetObjectKind().GroupVersionKind() + version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} + versionedObj, err := api.Scheme.ConvertToVersion(obj, version) + if err != nil { + return nil, err + } + + // convert data to json / yaml + data, err := json.MarshalIndent(versionedObj, "", " ") + if GenerateYaml == true { + data, err = yaml.Marshal(versionedObj) + } + if err != nil { + return nil, err + } + logrus.Debugf("%s\n", data) + return data, nil +} + func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string) { mServices := make(map[string][]byte) mReplicationControllers := make(map[string][]byte) mDeployments := make(map[string][]byte) mDaemonSets := make(map[string][]byte) - mReplicaSets := make(map[string][]byte) + // OpenShift DeploymentConfigs mDeploymentConfigs := make(map[string][]byte) - f := createOutFile(opt.outFile) + f := createOutFile(opt.OutFile) defer f.Close() var svcnames []string @@ -23,11 +378,11 @@ func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) for name, service := range komposeObject.ServiceConfigs { svcnames = append(svcnames, name) - rc := initRC(name, service, opt.replicas) + rc := initRC(name, service, opt.Replicas) sc := initSC(name, service) - dc := initDC(name, service, opt.replicas) + dc := initDC(name, service, opt.Replicas) ds := initDS(name, service) - osDC := initDeploymentConfig(name, service, opt.replicas) // OpenShift DeploymentConfigs + osDC := initDeploymentConfig(name, service, opt.Replicas) // OpenShift DeploymentConfigs // Configure the environment variables. envs := configEnvs(name, service) @@ -99,19 +454,19 @@ func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) updateController(osDC, fillTemplate, fillObjectMeta) // convert datarc to json / yaml - datarc, err := transformer(rc, opt.generateYaml) + datarc, err := transformer(rc, opt.GenerateYaml) if err != nil { logrus.Fatalf(err.Error()) } // convert datadc to json / yaml - datadc, err := transformer(dc, opt.generateYaml) + datadc, err := transformer(dc, opt.GenerateYaml) if err != nil { logrus.Fatalf(err.Error()) } // convert datads to json / yaml - datads, err := transformer(ds, opt.generateYaml) + datads, err := transformer(ds, opt.GenerateYaml) if err != nil { logrus.Fatalf(err.Error()) } @@ -122,14 +477,14 @@ func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) logrus.Warningf("[%s] Service cannot be created because of missing port.", name) } else if len(ports) != 0 { // convert datasvc to json / yaml - datasvc, err = transformer(sc, opt.generateYaml) + datasvc, err = transformer(sc, opt.GenerateYaml) if err != nil { logrus.Fatalf(err.Error()) } } // convert OpenShift DeploymentConfig to json / yaml - dataDeploymentConfig, err := transformer(osDC, opt.generateYaml) + dataDeploymentConfig, err := transformer(osDC, opt.GenerateYaml) if err != nil { logrus.Fatalf(err.Error()) } @@ -144,46 +499,96 @@ func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) return mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames } -func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt convertOptions, f *os.File) { +func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt kobject.ConvertOptions, f *os.File) { for k, v := range mServices { if v != nil { - print(k, "svc", v, opt.toStdout, opt.generateYaml, f) + print(k, "svc", v, opt.ToStdout, opt.GenerateYaml, f) } } // If --out or --stdout is set, the validation should already prevent multiple controllers being generated - if opt.createD { + if opt.CreateD { for k, v := range mDeployments { - print(k, "deployment", v, opt.toStdout, opt.generateYaml, f) + print(k, "deployment", v, opt.ToStdout, opt.GenerateYaml, f) } } - if opt.createDS { + if opt.CreateDS { for k, v := range mDaemonSets { - print(k, "daemonset", v, opt.toStdout, opt.generateYaml, f) + print(k, "daemonset", v, opt.ToStdout, opt.GenerateYaml, f) } } - if opt.createRC { + if opt.CreateRC { for k, v := range mReplicationControllers { - print(k, "rc", v, opt.toStdout, opt.generateYaml, f) + print(k, "rc", v, opt.ToStdout, opt.GenerateYaml, f) } } if f != nil { - fmt.Fprintf(os.Stdout, "file %q created\n", opt.outFile) + fmt.Fprintf(os.Stdout, "file %q created\n", opt.OutFile) } - if opt.createChart { - err := generateHelm(opt.inputFile, svcnames, opt.generateYaml, opt.createD, opt.createDS, opt.createRC, opt.outFile) + if opt.CreateChart { + err := generateHelm(opt.InputFile, svcnames, opt.GenerateYaml, opt.CreateD, opt.CreateDS, opt.CreateRC, opt.OutFile) if err != nil { logrus.Fatalf("Failed to create Chart data: %v", err) } } - if opt.createDeploymentConfig { + if opt.CreateDeploymentConfig { for k, v := range mDeploymentConfigs { - print(k, "deploymentconfig", v, opt.toStdout, opt.generateYaml, f) + print(k, "deploymentconfig", v, opt.ToStdout, opt.GenerateYaml, f) } } } + +// updateController updates the given object with the given pod template update function and ObjectMeta update function +func updateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec), updateMeta func(meta *api.ObjectMeta)) { + switch t := obj.(type) { + case *api.ReplicationController: + if t.Spec.Template == nil { + t.Spec.Template = &api.PodTemplateSpec{} + } + updateTemplate(t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *extensions.Deployment: + updateTemplate(&t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *extensions.ReplicaSet: + updateTemplate(&t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *extensions.DaemonSet: + updateTemplate(&t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *deployapi.DeploymentConfig: + updateTemplate(t.Spec.Template) + updateMeta(&t.ObjectMeta) + } +} + +func print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) { + file := fmt.Sprintf("%s-%s.json", name, trailing) + if generateYaml { + file = fmt.Sprintf("%s-%s.yaml", name, trailing) + } + separator := "" + if generateYaml { + separator = "---" + } + if toStdout { + fmt.Fprintf(os.Stdout, "%s%s\n", string(data), separator) + } else if f != nil { + // Write all content to a single file f + if _, err := f.WriteString(fmt.Sprintf("%s%s\n", string(data), separator)); err != nil { + logrus.Fatalf("Failed to write %s to file: %v", trailing, err) + } + f.Sync() + } else { + // Write content separately to each file + if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil { + logrus.Fatalf("Failed to write %s: %v", trailing, err) + } + fmt.Fprintf(os.Stdout, "file %q created\n", file) + } +} diff --git a/pkg/transformer/openshift.go b/pkg/transformer/openshift.go index 38ef5e16..51a750ac 100644 --- a/pkg/transformer/openshift.go +++ b/pkg/transformer/openshift.go @@ -1 +1,17 @@ +/* +Copyright 2016 Skippbox, Ltd 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 transformer From d532b29d959e7e4f05e1841d0f41a75175394666 Mon Sep 17 00:00:00 2001 From: Tuna Date: Mon, 1 Aug 2016 16:47:35 +0700 Subject: [PATCH 4/7] clean code --- cli/app/app.go | 4 ++-- pkg/kobject/kobject.go | 23 +------------------- pkg/loader/bundle.go | 2 +- pkg/loader/compose.go | 2 +- pkg/transformer/kubernetes.go | 33 ---------------------------- pkg/transformer/openshift.go | 41 +++++++++++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 59 deletions(-) diff --git a/cli/app/app.go b/cli/app/app.go index cc77979d..3633a851 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -449,7 +449,7 @@ func Convert(c *cli.Context) { file = dabFile } - //komposeObject.Loader(file, inputFormat) + // loader parses input from file into komposeObject. switch inputFormat { case "bundle": komposeObject = loader.LoadBundle(file) @@ -475,7 +475,7 @@ func Convert(c *cli.Context) { validateFlags(opt, singleOutput, dabFile, inputFile) - // Convert komposeObject to K8S controllers + // transformer maps komposeObject to provider(K8S, OpenShift) primitives mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := transformer.Transform(komposeObject, opt) // Print output diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index f6ee3701..eb39e07b 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -19,7 +19,6 @@ package kobject import ( "fmt" "github.com/fatih/structs" - "math/rand" ) var unsupportedKey = map[string]int{ @@ -176,32 +175,12 @@ const ( ProtocolUDP Protocol = "UDP" ) -// loader takes input and converts to KomposeObject -//func (k *KomposeObject) Loader(file string, inp string) { -// switch inp { -// case "bundle": -// //k.loadBundleFile(file) -// loader.LoadBundle(k, file) -// case "compose": -// //k.loadComposeFile(file) -// loader.LoadCompose(k, file) -// default: -// logrus.Fatalf("Input file format is not supported") -// -// } -//} - -// transformer takes KomposeObject and converts to K8S / OpenShift primitives -//func (k *KomposeObject) Transformer(opt ConvertOptions) { -// transformer.Transform(k, opt) -//} - func CheckUnsupportedKey(service interface{}) { s := structs.New(service) for _, f := range s.Fields() { if f.IsExported() && !f.IsZero() { if count, ok := unsupportedKey[f.Name()]; ok && count == 0 { - fmt.Println("WARNING: Unsupported key " + f.Name() + " - ignoring") + fmt.Println("WARNING: Unsupported key " + composeOptions[f.Name()] + " - ignoring") unsupportedKey[f.Name()]++ } } diff --git a/pkg/loader/bundle.go b/pkg/loader/bundle.go index fcd566cc..77d459c9 100644 --- a/pkg/loader/bundle.go +++ b/pkg/loader/bundle.go @@ -93,7 +93,7 @@ func loadPortsfromBundle(service bundlefile.Service) ([]kobject.Ports, string) { } // load Bundlefile into KomposeObject -func LoadBundle(file string) (kobject.KomposeObject) { +func LoadBundle(file string) kobject.KomposeObject { komposeObject := kobject.KomposeObject{ ServiceConfigs: make(map[string]kobject.ServiceConfig), } diff --git a/pkg/loader/compose.go b/pkg/loader/compose.go index 3d1bc3b6..743038cf 100644 --- a/pkg/loader/compose.go +++ b/pkg/loader/compose.go @@ -81,7 +81,7 @@ func loadPortsFromCompose(composePorts []string) ([]kobject.Ports, string) { } // load Docker Compose file into KomposeObject -func LoadCompose(file string) (kobject.KomposeObject) { +func LoadCompose(file string) kobject.KomposeObject { komposeObject := kobject.KomposeObject{ ServiceConfigs: make(map[string]kobject.ServiceConfig), } diff --git a/pkg/transformer/kubernetes.go b/pkg/transformer/kubernetes.go index d86c6689..1b995a4f 100644 --- a/pkg/transformer/kubernetes.go +++ b/pkg/transformer/kubernetes.go @@ -174,39 +174,6 @@ func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { return ds } -// initDeploymentConfig initialize OpenShifts DeploymentConfig object -func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { - dc := &deployapi.DeploymentConfig{ - TypeMeta: unversioned.TypeMeta{ - Kind: "DeploymentConfig", - APIVersion: "v1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - Labels: map[string]string{"service": name}, - }, - Spec: deployapi.DeploymentConfigSpec{ - Replicas: int32(replicas), - Selector: map[string]string{"service": name}, - //UniqueLabelKey: p.Name, - Template: &api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return dc -} - // Configure the environment variables. func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { envs := []api.EnvVar{} diff --git a/pkg/transformer/openshift.go b/pkg/transformer/openshift.go index 51a750ac..10d8bac4 100644 --- a/pkg/transformer/openshift.go +++ b/pkg/transformer/openshift.go @@ -15,3 +15,44 @@ limitations under the License. */ package transformer + +import ( + "github.com/skippbox/kompose/pkg/kobject" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + + deployapi "github.com/openshift/origin/pkg/deploy/api" +) + +// initDeploymentConfig initialize OpenShifts DeploymentConfig object +func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { + dc := &deployapi.DeploymentConfig{ + TypeMeta: unversioned.TypeMeta{ + Kind: "DeploymentConfig", + APIVersion: "v1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: map[string]string{"service": name}, + }, + Spec: deployapi.DeploymentConfigSpec{ + Replicas: int32(replicas), + Selector: map[string]string{"service": name}, + //UniqueLabelKey: p.Name, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return dc +} From f10d6afecf82f168b7772ef64e38dad0fda1008f Mon Sep 17 00:00:00 2001 From: Tuna Date: Mon, 8 Aug 2016 00:00:55 +0700 Subject: [PATCH 5/7] make loader, transformer as interfaces --- cli/app/app.go | 24 +- pkg/loader/{ => bundle}/bundle.go | 12 +- pkg/loader/{ => compose}/compose.go | 52 ++-- pkg/loader/loader.go | 23 ++ pkg/transformer/{ => kubernetes}/k8sutils.go | 4 +- pkg/transformer/kubernetes/kubernetes.go | 176 +++++++++++ pkg/transformer/openshift.go | 58 ---- pkg/transformer/openshift/openshift.go | 195 ++++++++++++ pkg/transformer/transformer.go | 24 ++ pkg/transformer/{kubernetes.go => utils.go} | 300 +++++-------------- 10 files changed, 548 insertions(+), 320 deletions(-) rename pkg/loader/{ => bundle}/bundle.go (97%) rename pkg/loader/{ => compose}/compose.go (91%) create mode 100644 pkg/loader/loader.go rename pkg/transformer/{ => kubernetes}/k8sutils.go (94%) create mode 100644 pkg/transformer/kubernetes/kubernetes.go delete mode 100644 pkg/transformer/openshift.go create mode 100644 pkg/transformer/openshift/openshift.go create mode 100644 pkg/transformer/transformer.go rename pkg/transformer/{kubernetes.go => utils.go} (56%) diff --git a/cli/app/app.go b/cli/app/app.go index 3633a851..b1fdc599 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -34,12 +34,16 @@ import ( "github.com/skippbox/kompose/pkg/kobject" "github.com/skippbox/kompose/pkg/loader" + "github.com/skippbox/kompose/pkg/loader/bundle" + "github.com/skippbox/kompose/pkg/loader/compose" "github.com/skippbox/kompose/pkg/transformer" "github.com/docker/libcompose/lookup" "github.com/docker/libcompose/config" "github.com/docker/libcompose/project" "fmt" "strings" + "github.com/skippbox/kompose/pkg/transformer/kubernetes" + "github.com/skippbox/kompose/pkg/transformer/openshift" ) const ( @@ -450,16 +454,18 @@ func Convert(c *cli.Context) { } // loader parses input from file into komposeObject. + var l loader.Loader switch inputFormat { case "bundle": - komposeObject = loader.LoadBundle(file) + l = new(bundle.Bundle) case "compose": - komposeObject = loader.LoadCompose(file) + l = new(compose.Compose) default: logrus.Fatalf("Input file format is not supported") - } + komposeObject = l.LoadFile(file) + opt := kobject.ConvertOptions{ ToStdout: toStdout, CreateD: createD, @@ -476,10 +482,18 @@ func Convert(c *cli.Context) { validateFlags(opt, singleOutput, dabFile, inputFile) // transformer maps komposeObject to provider(K8S, OpenShift) primitives - mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := transformer.Transform(komposeObject, opt) + var t transformer.Transformer + if !createDeploymentConfig { + t = new(kubernetes.Kubernetes) + } else { + t = new(openshift.OpenShift) + } + + mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := t.Transform(komposeObject, opt) // Print output - transformer.PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames, opt, f) + transformer.PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames, opt) + } // Up brings up deployment, svc. diff --git a/pkg/loader/bundle.go b/pkg/loader/bundle/bundle.go similarity index 97% rename from pkg/loader/bundle.go rename to pkg/loader/bundle/bundle.go index 77d459c9..4a4c684e 100644 --- a/pkg/loader/bundle.go +++ b/pkg/loader/bundle/bundle.go @@ -14,16 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -package loader +package bundle import ( + "io/ioutil" + "strings" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client/bundlefile" "github.com/skippbox/kompose/pkg/kobject" - "io/ioutil" - "strings" ) +type Bundle struct { +} + // load Image from bundles file func loadImage(service bundlefile.Service) (string, string) { character := "@" @@ -93,7 +97,7 @@ func loadPortsfromBundle(service bundlefile.Service) ([]kobject.Ports, string) { } // load Bundlefile into KomposeObject -func LoadBundle(file string) kobject.KomposeObject { +func (b *Bundle) LoadFile(file string) kobject.KomposeObject { komposeObject := kobject.KomposeObject{ ServiceConfigs: make(map[string]kobject.ServiceConfig), } diff --git a/pkg/loader/compose.go b/pkg/loader/compose/compose.go similarity index 91% rename from pkg/loader/compose.go rename to pkg/loader/compose/compose.go index 743038cf..b3ca6c7a 100644 --- a/pkg/loader/compose.go +++ b/pkg/loader/compose/compose.go @@ -14,21 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -package loader +package compose import ( - "github.com/Sirupsen/logrus" - "github.com/docker/libcompose/docker" - "github.com/docker/libcompose/project" - "github.com/skippbox/kompose/pkg/kobject" + "os" + "path/filepath" "strconv" "strings" - "github.com/docker/libcompose/lookup" - "os" + + "github.com/Sirupsen/logrus" "github.com/docker/libcompose/config" - "path/filepath" + "github.com/docker/libcompose/docker" + "github.com/docker/libcompose/lookup" + "github.com/docker/libcompose/project" + "github.com/skippbox/kompose/pkg/kobject" ) +type Compose struct { +} + // load Environment Variable from compose file func loadEnvVarsfromCompose(e map[string]string) []kobject.EnvVar { envs := []kobject.EnvVar{} @@ -81,7 +85,7 @@ func loadPortsFromCompose(composePorts []string) ([]kobject.Ports, string) { } // load Docker Compose file into KomposeObject -func LoadCompose(file string) kobject.KomposeObject { +func (c *Compose) LoadFile(file string) kobject.KomposeObject { komposeObject := kobject.KomposeObject{ ServiceConfigs: make(map[string]kobject.ServiceConfig), } @@ -92,23 +96,23 @@ func LoadCompose(file string) kobject.KomposeObject { context.ComposeFiles = []string{file} if context.ResourceLookup == nil { - context.ResourceLookup = &lookup.FileResourceLookup{} - } + context.ResourceLookup = &lookup.FileResourceLookup{} + } - if context.EnvironmentLookup == nil { - cwd, err := os.Getwd() - if err != nil { - return kobject.KomposeObject{} - } - context.EnvironmentLookup = &lookup.ComposableEnvLookup{ - Lookups: []config.EnvironmentLookup{ - &lookup.EnvfileLookup{ - Path: filepath.Join(cwd, ".env"), - }, - &lookup.OsEnvLookup{}, - }, - } + if context.EnvironmentLookup == nil { + cwd, err := os.Getwd() + if err != nil { + return kobject.KomposeObject{} } + context.EnvironmentLookup = &lookup.ComposableEnvLookup{ + Lookups: []config.EnvironmentLookup{ + &lookup.EnvfileLookup{ + Path: filepath.Join(cwd, ".env"), + }, + &lookup.OsEnvLookup{}, + }, + } + } // load compose file into composeObject composeObject := project.NewProject(&context.Context, nil, nil) diff --git a/pkg/loader/loader.go b/pkg/loader/loader.go new file mode 100644 index 00000000..d4f07e19 --- /dev/null +++ b/pkg/loader/loader.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 Skippbox, Ltd 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 loader + +import "github.com/skippbox/kompose/pkg/kobject" + +type Loader interface { + LoadFile(file string) kobject.KomposeObject +} diff --git a/pkg/transformer/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go similarity index 94% rename from pkg/transformer/k8sutils.go rename to pkg/transformer/kubernetes/k8sutils.go index f75738e8..5137ec9a 100644 --- a/pkg/transformer/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package transformer +package kubernetes import ( "bytes" @@ -29,7 +29,7 @@ import ( /** * Generate Helm Chart configuration */ -func generateHelm(filename string, outFiles []string) error { +func GenerateHelm(filename string, svcnames []string, generateYaml, createD, createDS, createRC bool, outFile string) error { type ChartDetails struct { Name string } diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go new file mode 100644 index 00000000..ea2e09fb --- /dev/null +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -0,0 +1,176 @@ +/* +Copyright 2016 Skippbox, Ltd 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" + "os" + + "github.com/Sirupsen/logrus" + deployapi "github.com/openshift/origin/pkg/deploy/api" + "github.com/skippbox/kompose/pkg/kobject" + "github.com/skippbox/kompose/pkg/transformer" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/runtime" +) + +type Kubernetes struct { +} + +func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string){ + mServices := make(map[string][]byte) + mReplicationControllers := make(map[string][]byte) + mDeployments := make(map[string][]byte) + mDaemonSets := make(map[string][]byte) + + var svcnames []string + + for name, service := range komposeObject.ServiceConfigs { + svcnames = append(svcnames, name) + + rc := transformer.InitRC(name, service, opt.Replicas) + sc := transformer.InitSC(name, service) + dc := transformer.InitDC(name, service, opt.Replicas) + ds := transformer.InitDS(name, service) + + // Configure the environment variables. + envs := transformer.ConfigEnvs(name, service) + + // Configure the container command. + cmds := transformer.ConfigCommands(service) + + // Configure the container volumes. + volumesMount, volumes := transformer.ConfigVolumes(service) + + // Configure the container ports. + ports := transformer.ConfigPorts(name, service) + + // Configure the service ports. + servicePorts := transformer.ConfigServicePorts(name, service) + sc.Spec.Ports = servicePorts + + // Configure label + labels := transformer.ConfigLabels(name) + sc.ObjectMeta.Labels = labels + + // Configure annotations + annotations := transformer.ConfigAnnotations(service) + sc.ObjectMeta.Annotations = annotations + + // fillTemplate fills the pod template with the value calculated from config + fillTemplate := func(template *api.PodTemplateSpec) { + template.Spec.Containers[0].Env = envs + template.Spec.Containers[0].Command = cmds + template.Spec.Containers[0].WorkingDir = service.WorkingDir + template.Spec.Containers[0].VolumeMounts = volumesMount + template.Spec.Volumes = volumes + // Configure the container privileged mode + if service.Privileged == true { + template.Spec.Containers[0].SecurityContext = &api.SecurityContext{ + Privileged: &service.Privileged, + } + } + template.Spec.Containers[0].Ports = ports + template.ObjectMeta.Labels = labels + // Configure the container restart policy. + switch service.Restart { + case "", "always": + template.Spec.RestartPolicy = api.RestartPolicyAlways + case "no": + template.Spec.RestartPolicy = api.RestartPolicyNever + case "on-failure": + template.Spec.RestartPolicy = api.RestartPolicyOnFailure + default: + logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name) + } + } + + // fillObjectMeta fills the metadata with the value calculated from config + fillObjectMeta := func(meta *api.ObjectMeta) { + meta.Labels = labels + meta.Annotations = annotations + } + + // Update each supported controllers + UpdateController(rc, fillTemplate, fillObjectMeta) + UpdateController(dc, fillTemplate, fillObjectMeta) + UpdateController(ds, fillTemplate, fillObjectMeta) + + // convert datarc to json / yaml + datarc, err := transformer.TransformData(rc, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + // convert datadc to json / yaml + datadc, err := transformer.TransformData(dc, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + // convert datads to json / yaml + datads, err := transformer.TransformData(ds, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + var datasvc []byte + // If ports not provided in configuration we will not make service + if len(ports) == 0 { + logrus.Warningf("[%s] Service cannot be created because of missing port.", name) + } else if len(ports) != 0 { + // convert datasvc to json / yaml + datasvc, err = transformer.TransformData(sc, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + } + + mServices[name] = datasvc + mReplicationControllers[name] = datarc + mDeployments[name] = datadc + mDaemonSets[name] = datads + } + + return mServices, mDeployments, mDaemonSets, mReplicationControllers, nil, svcnames +} + +// updateController updates the given object with the given pod template update function and ObjectMeta update function +func UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec), updateMeta func(meta *api.ObjectMeta)) { + switch t := obj.(type) { + case *api.ReplicationController: + if t.Spec.Template == nil { + t.Spec.Template = &api.PodTemplateSpec{} + } + updateTemplate(t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *extensions.Deployment: + updateTemplate(&t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *extensions.ReplicaSet: + updateTemplate(&t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *extensions.DaemonSet: + updateTemplate(&t.Spec.Template) + updateMeta(&t.ObjectMeta) + case *deployapi.DeploymentConfig: + updateTemplate(t.Spec.Template) + updateMeta(&t.ObjectMeta) + } +} diff --git a/pkg/transformer/openshift.go b/pkg/transformer/openshift.go deleted file mode 100644 index 10d8bac4..00000000 --- a/pkg/transformer/openshift.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2016 Skippbox, Ltd 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 transformer - -import ( - "github.com/skippbox/kompose/pkg/kobject" - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - - deployapi "github.com/openshift/origin/pkg/deploy/api" -) - -// initDeploymentConfig initialize OpenShifts DeploymentConfig object -func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { - dc := &deployapi.DeploymentConfig{ - TypeMeta: unversioned.TypeMeta{ - Kind: "DeploymentConfig", - APIVersion: "v1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - Labels: map[string]string{"service": name}, - }, - Spec: deployapi.DeploymentConfigSpec{ - Replicas: int32(replicas), - Selector: map[string]string{"service": name}, - //UniqueLabelKey: p.Name, - Template: &api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return dc -} diff --git a/pkg/transformer/openshift/openshift.go b/pkg/transformer/openshift/openshift.go new file mode 100644 index 00000000..d2334718 --- /dev/null +++ b/pkg/transformer/openshift/openshift.go @@ -0,0 +1,195 @@ +/* +Copyright 2016 Skippbox, Ltd 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 openshift + +import ( + "github.com/Sirupsen/logrus" + deployapi "github.com/openshift/origin/pkg/deploy/api" + "github.com/skippbox/kompose/pkg/kobject" + "github.com/skippbox/kompose/pkg/transformer" + "github.com/skippbox/kompose/pkg/transformer/kubernetes" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +type OpenShift struct { +} + +// initDeploymentConfig initialize OpenShifts DeploymentConfig object +func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas int) *deployapi.DeploymentConfig { + dc := &deployapi.DeploymentConfig{ + TypeMeta: unversioned.TypeMeta{ + Kind: "DeploymentConfig", + APIVersion: "v1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: map[string]string{"service": name}, + }, + Spec: deployapi.DeploymentConfigSpec{ + Replicas: int32(replicas), + Selector: map[string]string{"service": name}, + //UniqueLabelKey: p.Name, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return dc +} + +func (k *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string){ + mServices := make(map[string][]byte) + mReplicationControllers := make(map[string][]byte) + mDeployments := make(map[string][]byte) + mDaemonSets := make(map[string][]byte) + + // OpenShift DeploymentConfigs + mDeploymentConfigs := make(map[string][]byte) + + var svcnames []string + + for name, service := range komposeObject.ServiceConfigs { + svcnames = append(svcnames, name) + + rc := transformer.InitRC(name, service, opt.Replicas) + sc := transformer.InitSC(name, service) + dc := transformer.InitDC(name, service, opt.Replicas) + ds := transformer.InitDS(name, service) + osDC := initDeploymentConfig(name, service, opt.Replicas) // OpenShift DeploymentConfigs + + // Configure the environment variables. + envs := transformer.ConfigEnvs(name, service) + + // Configure the container command. + cmds := transformer.ConfigCommands(service) + + // Configure the container volumes. + volumesMount, volumes := transformer.ConfigVolumes(service) + + // Configure the container ports. + ports := transformer.ConfigPorts(name, service) + + // Configure the service ports. + servicePorts := transformer.ConfigServicePorts(name, service) + sc.Spec.Ports = servicePorts + + // Configure label + labels := transformer.ConfigLabels(name) + sc.ObjectMeta.Labels = labels + + // Configure annotations + annotations := transformer.ConfigAnnotations(service) + sc.ObjectMeta.Annotations = annotations + + // fillTemplate fills the pod template with the value calculated from config + fillTemplate := func(template *api.PodTemplateSpec) { + template.Spec.Containers[0].Env = envs + template.Spec.Containers[0].Command = cmds + template.Spec.Containers[0].WorkingDir = service.WorkingDir + template.Spec.Containers[0].VolumeMounts = volumesMount + template.Spec.Volumes = volumes + // Configure the container privileged mode + if service.Privileged == true { + template.Spec.Containers[0].SecurityContext = &api.SecurityContext{ + Privileged: &service.Privileged, + } + } + template.Spec.Containers[0].Ports = ports + template.ObjectMeta.Labels = labels + // Configure the container restart policy. + switch service.Restart { + case "", "always": + template.Spec.RestartPolicy = api.RestartPolicyAlways + case "no": + template.Spec.RestartPolicy = api.RestartPolicyNever + case "on-failure": + template.Spec.RestartPolicy = api.RestartPolicyOnFailure + default: + logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name) + } + } + + // fillObjectMeta fills the metadata with the value calculated from config + fillObjectMeta := func(meta *api.ObjectMeta) { + meta.Labels = labels + meta.Annotations = annotations + } + + // Update each supported controllers + kubernetes.UpdateController(rc, fillTemplate, fillObjectMeta) + kubernetes.UpdateController(dc, fillTemplate, fillObjectMeta) + kubernetes.UpdateController(ds, fillTemplate, fillObjectMeta) + // OpenShift DeploymentConfigs + kubernetes.UpdateController(osDC, fillTemplate, fillObjectMeta) + + // convert datarc to json / yaml + datarc, err := transformer.TransformData(rc, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + // convert datadc to json / yaml + datadc, err := transformer.TransformData(dc, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + // convert datads to json / yaml + datads, err := transformer.TransformData(ds, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + var datasvc []byte + // If ports not provided in configuration we will not make service + if len(ports) == 0 { + logrus.Warningf("[%s] Service cannot be created because of missing port.", name) + } else if len(ports) != 0 { + // convert datasvc to json / yaml + datasvc, err = transformer.TransformData(sc, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + } + + // convert OpenShift DeploymentConfig to json / yaml + dataDeploymentConfig, err := transformer.TransformData(osDC, opt.GenerateYaml) + if err != nil { + logrus.Fatalf(err.Error()) + } + + mServices[name] = datasvc + mReplicationControllers[name] = datarc + mDeployments[name] = datadc + mDaemonSets[name] = datads + mDeploymentConfigs[name] = dataDeploymentConfig + } + + return mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames +} diff --git a/pkg/transformer/transformer.go b/pkg/transformer/transformer.go new file mode 100644 index 00000000..f0deacb5 --- /dev/null +++ b/pkg/transformer/transformer.go @@ -0,0 +1,24 @@ +/* +Copyright 2016 Skippbox, Ltd 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 transformer + +import "github.com/skippbox/kompose/pkg/kobject" + +type Transformer interface { + Transform(kobject.KomposeObject, kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string) +} + diff --git a/pkg/transformer/kubernetes.go b/pkg/transformer/utils.go similarity index 56% rename from pkg/transformer/kubernetes.go rename to pkg/transformer/utils.go index 1b995a4f..a96f7a26 100644 --- a/pkg/transformer/kubernetes.go +++ b/pkg/transformer/utils.go @@ -1,45 +1,30 @@ -/* -Copyright 2016 Skippbox, Ltd 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 transformer import ( "encoding/json" "fmt" - "github.com/Sirupsen/logrus" - "github.com/ghodss/yaml" - "github.com/skippbox/kompose/pkg/kobject" - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/util/intstr" + "io/ioutil" "math/rand" "os" "strconv" "strings" - deployapi "github.com/openshift/origin/pkg/deploy/api" - "io/ioutil" + "github.com/Sirupsen/logrus" + "github.com/ghodss/yaml" + "github.com/skippbox/kompose/pkg/kobject" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" + "github.com/skippbox/kompose/pkg/transformer/kubernetes" ) const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" // RandStringBytes generates randomly n-character string -func RandStringBytes(n int) string { +func randStringBytes(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] @@ -61,7 +46,7 @@ func createOutFile(out string) *os.File { } // Init RC object -func initRC(name string, service kobject.ServiceConfig, replicas int) *api.ReplicationController { +func InitRC(name string, service kobject.ServiceConfig, replicas int) *api.ReplicationController { rc := &api.ReplicationController{ TypeMeta: unversioned.TypeMeta{ Kind: "ReplicationController", @@ -93,7 +78,7 @@ func initRC(name string, service kobject.ServiceConfig, replicas int) *api.Repli } // Init SC object -func initSC(name string, service kobject.ServiceConfig) *api.Service { +func InitSC(name string, service kobject.ServiceConfig) *api.Service { sc := &api.Service{ TypeMeta: unversioned.TypeMeta{ Kind: "Service", @@ -111,7 +96,7 @@ func initSC(name string, service kobject.ServiceConfig) *api.Service { } // Init DC object -func initDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { +func InitDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { dc := &extensions.Deployment{ TypeMeta: unversioned.TypeMeta{ Kind: "Deployment", @@ -146,7 +131,7 @@ func initDC(name string, service kobject.ServiceConfig, replicas int) *extension } // Init DS object -func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { +func InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { ds := &extensions.DaemonSet{ TypeMeta: unversioned.TypeMeta{ Kind: "DaemonSet", @@ -175,7 +160,7 @@ func initDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { } // Configure the environment variables. -func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { +func ConfigEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { envs := []api.EnvVar{} for _, v := range service.Environment { envs = append(envs, api.EnvVar{ @@ -187,8 +172,18 @@ func configEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { return envs } +// Configure the container commands +func ConfigCommands(service kobject.ServiceConfig) []string { + var cmds []string + for _, cmd := range service.Command { + cmds = append(cmds, cmd) + } + + return cmds +} + // Configure the container volumes. -func configVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { +func ConfigVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { volumesMount := []api.VolumeMount{} volumes := []api.Volume{} volumeSource := api.VolumeSource{} @@ -201,7 +196,7 @@ func configVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volu // if volume name isn't specified, set it to a random string of 20 chars if len(name) == 0 { - name = RandStringBytes(20) + name = randStringBytes(20) } // check if ro/rw mode is defined, default rw readonly := len(mode) > 0 && mode == "ro" @@ -256,7 +251,7 @@ func isPath(substring string) bool { } // Configure the container ports. -func configPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { +func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { ports := []api.ContainerPort{} for _, port := range service.Port { var p api.Protocol @@ -278,7 +273,7 @@ func configPorts(name string, service kobject.ServiceConfig) []api.ContainerPort } // Configure the container service ports. -func configServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { +func ConfigServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { servicePorts := []api.ServicePort{} for _, port := range service.Port { if port.HostPort == 0 { @@ -306,8 +301,23 @@ func configServicePorts(name string, service kobject.ServiceConfig) []api.Servic return servicePorts } +// Configure label +func ConfigLabels(name string) map[string]string { + return map[string]string{"service": name} +} + +// Configure annotations +func ConfigAnnotations(service kobject.ServiceConfig) map[string]string { + annotations := map[string]string{} + for key, value := range service.Annotations { + annotations[key] = value + } + + return annotations +} + // Transform data to json/yaml -func transformer(obj runtime.Object, GenerateYaml bool) ([]byte, error) { +func TransformData(obj runtime.Object, GenerateYaml bool) ([]byte, error) { // Convert to versioned object objectVersion := obj.GetObjectKind().GroupVersionKind() version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} @@ -328,145 +338,37 @@ func transformer(obj runtime.Object, GenerateYaml bool) ([]byte, error) { return data, nil } -func Transform(komposeObject *kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string) { - mServices := make(map[string][]byte) - mReplicationControllers := make(map[string][]byte) - mDeployments := make(map[string][]byte) - mDaemonSets := make(map[string][]byte) - // OpenShift DeploymentConfigs - mDeploymentConfigs := make(map[string][]byte) +func print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) { + file := fmt.Sprintf("%s-%s.json", name, trailing) + if generateYaml { + file = fmt.Sprintf("%s-%s.yaml", name, trailing) + } + separator := "" + if generateYaml { + separator = "---" + } + if toStdout { + fmt.Fprintf(os.Stdout, "%s%s\n", string(data), separator) + } else if f != nil { + // Write all content to a single file f + if _, err := f.WriteString(fmt.Sprintf("%s%s\n", string(data), separator)); err != nil { + logrus.Fatalf("Failed to write %s to file: %v", trailing, err) + } + f.Sync() + } else { + // Write content separately to each file + if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil { + logrus.Fatalf("Failed to write %s: %v", trailing, err) + } + fmt.Fprintf(os.Stdout, "file %q created\n", file) + } +} +func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt kobject.ConvertOptions) { f := createOutFile(opt.OutFile) defer f.Close() - var svcnames []string - - for name, service := range komposeObject.ServiceConfigs { - svcnames = append(svcnames, name) - - rc := initRC(name, service, opt.Replicas) - sc := initSC(name, service) - dc := initDC(name, service, opt.Replicas) - ds := initDS(name, service) - osDC := initDeploymentConfig(name, service, opt.Replicas) // OpenShift DeploymentConfigs - - // Configure the environment variables. - envs := configEnvs(name, service) - - // Configure the container command. - var cmds []string - for _, cmd := range service.Command { - cmds = append(cmds, cmd) - } - // Configure the container volumes. - volumesMount, volumes := configVolumes(service) - - // Configure the container ports. - ports := configPorts(name, service) - - // Configure the service ports. - servicePorts := configServicePorts(name, service) - sc.Spec.Ports = servicePorts - - // Configure label - labels := map[string]string{"service": name} - sc.ObjectMeta.Labels = labels - // Configure annotations - annotations := map[string]string{} - for key, value := range service.Annotations { - annotations[key] = value - } - sc.ObjectMeta.Annotations = annotations - - // fillTemplate fills the pod template with the value calculated from config - fillTemplate := func(template *api.PodTemplateSpec) { - template.Spec.Containers[0].Env = envs - template.Spec.Containers[0].Command = cmds - template.Spec.Containers[0].WorkingDir = service.WorkingDir - template.Spec.Containers[0].VolumeMounts = volumesMount - template.Spec.Volumes = volumes - // Configure the container privileged mode - if service.Privileged == true { - template.Spec.Containers[0].SecurityContext = &api.SecurityContext{ - Privileged: &service.Privileged, - } - } - template.Spec.Containers[0].Ports = ports - template.ObjectMeta.Labels = labels - // Configure the container restart policy. - switch service.Restart { - case "", "always": - template.Spec.RestartPolicy = api.RestartPolicyAlways - case "no": - template.Spec.RestartPolicy = api.RestartPolicyNever - case "on-failure": - template.Spec.RestartPolicy = api.RestartPolicyOnFailure - default: - logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name) - } - } - - // fillObjectMeta fills the metadata with the value calculated from config - fillObjectMeta := func(meta *api.ObjectMeta) { - meta.Labels = labels - meta.Annotations = annotations - } - - // Update each supported controllers - updateController(rc, fillTemplate, fillObjectMeta) - updateController(dc, fillTemplate, fillObjectMeta) - updateController(ds, fillTemplate, fillObjectMeta) - // OpenShift DeploymentConfigs - updateController(osDC, fillTemplate, fillObjectMeta) - - // convert datarc to json / yaml - datarc, err := transformer(rc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - // convert datadc to json / yaml - datadc, err := transformer(dc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - // convert datads to json / yaml - datads, err := transformer(ds, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - var datasvc []byte - // If ports not provided in configuration we will not make service - if len(ports) == 0 { - logrus.Warningf("[%s] Service cannot be created because of missing port.", name) - } else if len(ports) != 0 { - // convert datasvc to json / yaml - datasvc, err = transformer(sc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - } - - // convert OpenShift DeploymentConfig to json / yaml - dataDeploymentConfig, err := transformer(osDC, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - mServices[name] = datasvc - mReplicationControllers[name] = datarc - mDeployments[name] = datadc - mDaemonSets[name] = datads - mDeploymentConfigs[name] = dataDeploymentConfig - } - - return mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames -} - -func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt kobject.ConvertOptions, f *os.File) { for k, v := range mServices { if v != nil { print(k, "svc", v, opt.ToStdout, opt.GenerateYaml, f) @@ -497,65 +399,9 @@ func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControll } if opt.CreateChart { - err := generateHelm(opt.InputFile, svcnames, opt.GenerateYaml, opt.CreateD, opt.CreateDS, opt.CreateRC, opt.OutFile) + err := kubernetes.GenerateHelm(opt.InputFile, svcnames, opt.GenerateYaml, opt.CreateD, opt.CreateDS, opt.CreateRC, opt.OutFile) if err != nil { - logrus.Fatalf("Failed to create Chart data: %v", err) - } - } - - if opt.CreateDeploymentConfig { - for k, v := range mDeploymentConfigs { - print(k, "deploymentconfig", v, opt.ToStdout, opt.GenerateYaml, f) + logrus.Fatalf("Failed to create Chart data: %s\n", err) } } } - -// updateController updates the given object with the given pod template update function and ObjectMeta update function -func updateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSpec), updateMeta func(meta *api.ObjectMeta)) { - switch t := obj.(type) { - case *api.ReplicationController: - if t.Spec.Template == nil { - t.Spec.Template = &api.PodTemplateSpec{} - } - updateTemplate(t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *extensions.Deployment: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *extensions.ReplicaSet: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *extensions.DaemonSet: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) - case *deployapi.DeploymentConfig: - updateTemplate(t.Spec.Template) - updateMeta(&t.ObjectMeta) - } -} - -func print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) { - file := fmt.Sprintf("%s-%s.json", name, trailing) - if generateYaml { - file = fmt.Sprintf("%s-%s.yaml", name, trailing) - } - separator := "" - if generateYaml { - separator = "---" - } - if toStdout { - fmt.Fprintf(os.Stdout, "%s%s\n", string(data), separator) - } else if f != nil { - // Write all content to a single file f - if _, err := f.WriteString(fmt.Sprintf("%s%s\n", string(data), separator)); err != nil { - logrus.Fatalf("Failed to write %s to file: %v", trailing, err) - } - f.Sync() - } else { - // Write content separately to each file - if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil { - logrus.Fatalf("Failed to write %s: %v", trailing, err) - } - fmt.Fprintf(os.Stdout, "file %q created\n", file) - } -} From baedd92036aa811fd8618c697afd35256c597d4a Mon Sep 17 00:00:00 2001 From: Tuna Date: Tue, 9 Aug 2016 22:55:42 +0700 Subject: [PATCH 6/7] move k8s base functions into k8s package --- cli/app/app.go | 306 ++++------------------ cli/app/app_test.go | 3 +- pkg/kobject/kobject.go | 2 +- pkg/transformer/kubernetes/k8sutils.go | 107 +++++++- pkg/transformer/kubernetes/kubernetes.go | 311 +++++++++++++++++++---- pkg/transformer/openshift/openshift.go | 92 +++---- pkg/transformer/transformer.go | 8 +- pkg/transformer/utils.go | 280 +------------------- 8 files changed, 470 insertions(+), 639 deletions(-) diff --git a/cli/app/app.go b/cli/app/app.go index b1fdc599..9ec0c94e 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -17,31 +17,25 @@ limitations under the License. package app import ( + "fmt" + "github.com/Sirupsen/logrus" "github.com/urfave/cli" // 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/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/runtime" // install kubernetes api _ "github.com/openshift/origin/pkg/deploy/api/install" - "github.com/skippbox/kompose/pkg/kobject" "github.com/skippbox/kompose/pkg/loader" "github.com/skippbox/kompose/pkg/loader/bundle" "github.com/skippbox/kompose/pkg/loader/compose" "github.com/skippbox/kompose/pkg/transformer" - "github.com/docker/libcompose/lookup" - "github.com/docker/libcompose/config" - "github.com/docker/libcompose/project" - "fmt" - "strings" "github.com/skippbox/kompose/pkg/transformer/kubernetes" "github.com/skippbox/kompose/pkg/transformer/openshift" ) @@ -195,201 +189,6 @@ func Scale(c *cli.Context) { //} } -// Convert komposeObject to K8S controllers -func komposeConvert(komposeObject KomposeObject, opt convertOptions) []runtime.Object { - var svcnames []string - - // this will hold all the converted data - var allobjects []runtime.Object - for name, service := range komposeObject.ServiceConfigs { - var objects []runtime.Object - svcnames = append(svcnames, name) - sc := initSC(name, service) - - if opt.createD { - objects = append(objects, initDC(name, service, opt.replicas)) - } - if opt.createDS { - objects = append(objects, initDS(name, service)) - } - if opt.createRC { - objects = append(objects, initRC(name, service, opt.replicas)) - } - if opt.createDeploymentConfig { - objects = append(objects, initDeploymentConfig(name, service, opt.replicas)) // OpenShift DeploymentConfigs - } - - // Configure the environment variables. - envs := configEnvs(name, service) - - // Configure the container command. - var cmds []string - for _, cmd := range service.Command { - cmds = append(cmds, cmd) - } - // Configure the container volumes. - volumesMount, volumes := configVolumes(service) - - // Configure the container ports. - ports := configPorts(name, service) - - // Configure the service ports. - servicePorts := configServicePorts(name, service) - sc.Spec.Ports = servicePorts - - // Configure labels - labels := map[string]string{"service": name} - sc.ObjectMeta.Labels = labels - // Configure annotations - annotations := map[string]string{} - for key, value := range service.Annotations { - annotations[key] = value - } - sc.ObjectMeta.Annotations = annotations - - // fillTemplate fills the pod template with the value calculated from config - fillTemplate := func(template *api.PodTemplateSpec) { - template.Spec.Containers[0].Env = envs - template.Spec.Containers[0].Command = cmds - template.Spec.Containers[0].WorkingDir = service.WorkingDir - template.Spec.Containers[0].VolumeMounts = volumesMount - template.Spec.Volumes = volumes - // Configure the container privileged mode - if service.Privileged == true { - template.Spec.Containers[0].SecurityContext = &api.SecurityContext{ - Privileged: &service.Privileged, - } - } - template.Spec.Containers[0].Ports = ports - template.ObjectMeta.Labels = labels - // Configure the container restart policy. - switch service.Restart { - case "", "always": - template.Spec.RestartPolicy = api.RestartPolicyAlways - case "no": - template.Spec.RestartPolicy = api.RestartPolicyNever - case "on-failure": - template.Spec.RestartPolicy = api.RestartPolicyOnFailure - default: - logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name) - } - } - - // fillObjectMeta fills the metadata with the value calculated from config - fillObjectMeta := func(meta *api.ObjectMeta) { - meta.Labels = labels - meta.Annotations = annotations - } - - // update supported controller - for _, obj := range objects { - updateController(obj, fillTemplate, fillObjectMeta) - } - - // If ports not provided in configuration we will not make service - if len(ports) == 0 { - logrus.Warningf("[%s] Service cannot be created because of missing port.", name) - } else { - objects = append(objects, sc) - } - allobjects = append(allobjects, objects...) - } - return allobjects -} - -// PrintList will take the data converted and decide on the commandline attributes given -func PrintList(objects []runtime.Object, opt convertOptions) error { - f := createOutFile(opt.outFile) - defer f.Close() - - var err error - var files []string - - // if asked to print to stdout or to put in single file - // we will create a list - if opt.toStdout || f != nil { - list := &api.List{} - list.Items = objects - - // version each object in the list - list.Items, err = ConvertToVersion(list.Items) - if err != nil { - return err - } - - // version list itself - listVersion := unversioned.GroupVersion{Group: "", Version: "v1"} - convertedList, err := api.Scheme.ConvertToVersion(list, listVersion) - if err != nil { - return err - } - data, err := marshal(convertedList, opt.generateYaml) - if err != nil { - return fmt.Errorf("Error in marshalling the List: %v", err) - } - files = append(files, print("", "", data, opt.toStdout, opt.generateYaml, f)) - } else { - var file string - // create a separate file for each provider - for _, v := range objects { - data, err := marshal(v, opt.generateYaml) - if err != nil { - return err - } - switch t := v.(type) { - case *api.ReplicationController: - file = print(t.Name, strings.ToLower(t.Kind), data, opt.toStdout, opt.generateYaml, f) - case *extensions.Deployment: - file = print(t.Name, strings.ToLower(t.Kind), data, opt.toStdout, opt.generateYaml, f) - case *extensions.DaemonSet: - file = print(t.Name, strings.ToLower(t.Kind), data, opt.toStdout, opt.generateYaml, f) - case *deployapi.DeploymentConfig: - file = print(t.Name, strings.ToLower(t.Kind), data, opt.toStdout, opt.generateYaml, f) - case *api.Service: - file = print(t.Name, strings.ToLower(t.Kind), data, opt.toStdout, opt.generateYaml, f) - } - files = append(files, file) - - } - } - if opt.createChart { - generateHelm(opt.inputFile, files) - } - return nil -} - -// marshal object runtime.Object and return byte array -func marshal(obj runtime.Object, yamlFormat bool) (data []byte, err error) { - // convert data to yaml or json - if yamlFormat { - data, err = yaml.Marshal(obj) - } else { - data, err = json.MarshalIndent(obj, "", " ") - } - if err != nil { - data = nil - } - return -} - -// Convert all objects in objs to versioned objects -func ConvertToVersion(objs []runtime.Object) ([]runtime.Object, error) { - ret := []runtime.Object{} - - for _, obj := range objs { - - objectVersion := obj.GetObjectKind().GroupVersionKind() - version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} - convertedObject, err := api.Scheme.ConvertToVersion(obj, version) - if err != nil { - return nil, err - } - ret = append(ret, convertedObject) - } - - return ret, nil -} - func validateFlags(opt kobject.ConvertOptions, singleOutput bool, dabFile, inputFile string) { if len(opt.OutFile) != 0 && opt.ToStdout { logrus.Fatalf("Error: --out and --stdout can't be set at the same time") @@ -453,19 +252,6 @@ func Convert(c *cli.Context) { file = dabFile } - // loader parses input from file into komposeObject. - var l loader.Loader - switch inputFormat { - case "bundle": - l = new(bundle.Bundle) - case "compose": - l = new(compose.Compose) - default: - logrus.Fatalf("Input file format is not supported") - } - - komposeObject = l.LoadFile(file) - opt := kobject.ConvertOptions{ ToStdout: toStdout, CreateD: createD, @@ -481,7 +267,20 @@ func Convert(c *cli.Context) { validateFlags(opt, singleOutput, dabFile, inputFile) - // transformer maps komposeObject to provider(K8S, OpenShift) primitives + // loader parses input from file into komposeObject. + var l loader.Loader + switch inputFormat { + case "bundle": + l = new(bundle.Bundle) + case "compose": + l = new(compose.Compose) + default: + logrus.Fatalf("Input file format is not supported") + } + + komposeObject = l.LoadFile(file) + + // transformer maps komposeObject to provider's primitives var t transformer.Transformer if !createDeploymentConfig { t = new(kubernetes.Kubernetes) @@ -489,11 +288,10 @@ func Convert(c *cli.Context) { t = new(openshift.OpenShift) } - mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames := t.Transform(komposeObject, opt) + objects := t.Transform(komposeObject, opt) // Print output - transformer.PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames, opt) - + kubernetes.PrintList(objects, opt) } // Up brings up deployment, svc. @@ -511,52 +309,52 @@ func Up(c *cli.Context) { inputFile := c.String("file") dabFile := c.String("bundle") - komposeObject := KomposeObject{} - opt := convertOptions{ - replicas: 1, - createD: true, + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } + + file := inputFile + if len(dabFile) > 0 { + inputFormat = "bundle" + file = dabFile + } + + opt := kobject.ConvertOptions{ + Replicas: 1, + CreateD: true, } validateFlags(opt, false, dabFile, inputFile) - if len(dabFile) > 0 { - komposeObject = loadBundlesFile(dabFile) - } else { - komposeObject = loadComposeFile(inputFile) + // loader parses input from file into komposeObject. + var l loader.Loader + switch inputFormat { + case "bundle": + l = new(bundle.Bundle) + case "compose": + l = new(compose.Compose) + default: + logrus.Fatalf("Input file format is not supported") } + komposeObject = l.LoadFile(file) + + t := new(kubernetes.Kubernetes) //Convert komposeObject to K8S controllers - objects := komposeConvert(komposeObject, opt) - objects = sortServicesFirst(objects) + objects := t.Transform(komposeObject, opt) + sortServicesFirst(&objects) - for _, v := range objects { - switch t := v.(type) { - case *extensions.Deployment: - _, err := client.Deployments(api.NamespaceDefault).Create(t) - if err != nil { - logrus.Fatalf("Error: '%v' while creating deployment: %s", err, t.Name) - } - logrus.Infof("Successfully created deployment: %s", t.Name) - case *api.Service: - _, err := client.Services(api.NamespaceDefault).Create(t) - if err != nil { - logrus.Fatalf("Error: '%v' while creating service: %s", err, t.Name) - } - logrus.Infof("Successfully created service: %s", t.Name) - } - } - fmt.Println("\nApplication has been deployed to Kubernetes. You can run 'kubectl get deployment,svc' for details.") + //Submit objects to K8s endpoint + kubernetes.CreateObjects(client, objects) } // the objects that we get can be in any order this keeps services first // according to best practice kubernetes services should be created first // http://kubernetes.io/docs/user-guide/config-best-practices/ -func sortServicesFirst(objs []runtime.Object) []runtime.Object { - var svc []runtime.Object - var others []runtime.Object - var ret []runtime.Object +func sortServicesFirst(objs *[]runtime.Object) { + var svc, others, ret []runtime.Object - for _, obj := range objs { + for _, obj := range *objs { if obj.GetObjectKind().GroupVersionKind().Kind == "Service" { svc = append(svc, obj) } else { @@ -565,5 +363,5 @@ func sortServicesFirst(objs []runtime.Object) []runtime.Object { } ret = append(ret, svc...) ret = append(ret, others...) - return ret + *objs = ret } diff --git a/cli/app/app_test.go b/cli/app/app_test.go index b02d9b81..b19aed54 100644 --- a/cli/app/app_test.go +++ b/cli/app/app_test.go @@ -19,6 +19,7 @@ package app import ( "fmt" "testing" + "github.com/skippbox/kompose/pkg/transformer" ) func TestParseVolume(t *testing.T) { @@ -99,7 +100,7 @@ func TestParseVolume(t *testing.T) { } for _, test := range tests { - name, host, container, mode, err := parseVolume(test.volume) + name, host, container, mode, err := transformer.ParseVolume(test.volume) if err != nil { t.Errorf("In test case %q, returned unexpected error %v", test.test, err) } diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index eb39e07b..5d44cef2 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -178,7 +178,7 @@ const ( func CheckUnsupportedKey(service interface{}) { s := structs.New(service) for _, f := range s.Fields() { - if f.IsExported() && !f.IsZero() { + if f.IsExported() && !f.IsZero() && f.Name() != "Networks" { if count, ok := unsupportedKey[f.Name()]; ok && count == 0 { fmt.Println("WARNING: Unsupported key " + composeOptions[f.Name()] + " - ignoring") unsupportedKey[f.Name()]++ diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 5137ec9a..ba421f5d 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -18,18 +18,31 @@ package kubernetes import ( "bytes" + "encoding/json" + "fmt" "io/ioutil" "os" "path/filepath" + "strings" "text/template" "github.com/Sirupsen/logrus" + "github.com/ghodss/yaml" + "github.com/skippbox/kompose/pkg/kobject" + "github.com/skippbox/kompose/pkg/transformer" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/runtime" + + deployapi "github.com/openshift/origin/pkg/deploy/api" ) /** * Generate Helm Chart configuration */ -func GenerateHelm(filename string, svcnames []string, generateYaml, createD, createDS, createRC bool, outFile string) error { +func generateHelm(filename string, outFiles []string) error { type ChartDetails struct { Name string } @@ -108,3 +121,95 @@ func cpFileToChart(manifestDir, filename string) error { return ioutil.WriteFile(manifestDir+string(os.PathSeparator)+filename, infile, 0644) } + +// PrintList will take the data converted and decide on the commandline attributes given +func PrintList(objects []runtime.Object, opt kobject.ConvertOptions) error { + f := transformer.CreateOutFile(opt.OutFile) + defer f.Close() + + var err error + var files []string + + // if asked to print to stdout or to put in single file + // we will create a list + if opt.ToStdout || f != nil { + list := &api.List{} + list.Items = objects + + // version each object in the list + list.Items, err = convertToVersion(list.Items) + if err != nil { + return err + } + + // version list itself + listVersion := unversioned.GroupVersion{Group: "", Version: "v1"} + convertedList, err := api.Scheme.ConvertToVersion(list, listVersion) + if err != nil { + return err + } + data, err := marshal(convertedList, opt.GenerateYaml) + if err != nil { + return fmt.Errorf("Error in marshalling the List: %v", err) + } + files = append(files, transformer.Print("", "", data, opt.ToStdout, opt.GenerateYaml, f)) + } else { + var file string + // create a separate file for each provider + for _, v := range objects { + data, err := marshal(v, opt.GenerateYaml) + if err != nil { + return err + } + switch t := v.(type) { + case *api.ReplicationController: + file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) + case *extensions.Deployment: + file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) + case *extensions.DaemonSet: + file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) + case *deployapi.DeploymentConfig: + file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) + case *api.Service: + file = transformer.Print(t.Name, strings.ToLower(t.Kind), data, opt.ToStdout, opt.GenerateYaml, f) + } + files = append(files, file) + } + } + if opt.CreateChart { + generateHelm(opt.InputFile, files) + } + return nil +} + +// marshal object runtime.Object and return byte array +func marshal(obj runtime.Object, yamlFormat bool) (data []byte, err error) { + // convert data to yaml or json + if yamlFormat { + data, err = yaml.Marshal(obj) + } else { + data, err = json.MarshalIndent(obj, "", " ") + } + if err != nil { + data = nil + } + return +} + +// Convert all objects in objs to versioned objects +func convertToVersion(objs []runtime.Object) ([]runtime.Object, error) { + ret := []runtime.Object{} + + for _, obj := range objs { + + objectVersion := obj.GetObjectKind().GroupVersionKind() + version := unversioned.GroupVersion{Group: objectVersion.Group, Version: objectVersion.Version} + convertedObject, err := api.Scheme.ConvertToVersion(obj, version) + if err != nil { + return nil, err + } + ret = append(ret, convertedObject) + } + + return ret, nil +} diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index ea2e09fb..a42b1cd4 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -18,7 +18,7 @@ package kubernetes import ( "fmt" - "os" + "strconv" "github.com/Sirupsen/logrus" deployapi "github.com/openshift/origin/pkg/deploy/api" @@ -26,43 +26,262 @@ import ( "github.com/skippbox/kompose/pkg/transformer" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" + client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" ) type Kubernetes struct { } -func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string){ - mServices := make(map[string][]byte) - mReplicationControllers := make(map[string][]byte) - mDeployments := make(map[string][]byte) - mDaemonSets := make(map[string][]byte) +// Init RC object +func 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, + //Labels: map[string]string{"service": name}, + }, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"service": name}, + Replicas: int32(replicas), + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + //Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return rc +} +// Init SC object +func InitSC(name string, service kobject.ServiceConfig) *api.Service { + sc := &api.Service{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + //Labels: map[string]string{"service": name}, + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{"service": name}, + }, + } + return sc +} + +// Init DC object +func InitDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { + dc := &extensions.Deployment{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Deployment", + APIVersion: "extensions/v1beta1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: map[string]string{"service": name}, + }, + Spec: extensions.DeploymentSpec{ + Replicas: int32(replicas), + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"service": name}, + }, + //UniqueLabelKey: p.Name, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"service": name}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return dc +} + +// Init DS object +func InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { + ds := &extensions.DaemonSet{ + TypeMeta: unversioned.TypeMeta{ + Kind: "DaemonSet", + APIVersion: "extensions/v1beta1", + }, + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: extensions.DaemonSetSpec{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: name, + Image: service.Image, + }, + }, + }, + }, + }, + } + return ds +} + +// Configure the container ports. +func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { + ports := []api.ContainerPort{} + for _, port := range service.Port { + var p api.Protocol + switch port.Protocol { + default: + p = api.ProtocolTCP + case kobject.ProtocolTCP: + p = api.ProtocolTCP + case kobject.ProtocolUDP: + p = api.ProtocolUDP + } + ports = append(ports, api.ContainerPort{ + ContainerPort: port.ContainerPort, + Protocol: p, + }) + } + + return ports +} + +// Configure the container service ports. +func ConfigServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { + servicePorts := []api.ServicePort{} + for _, port := range service.Port { + if port.HostPort == 0 { + port.HostPort = port.ContainerPort + } + var p api.Protocol + switch port.Protocol { + default: + p = api.ProtocolTCP + case kobject.ProtocolTCP: + p = api.ProtocolTCP + case kobject.ProtocolUDP: + p = api.ProtocolUDP + } + var targetPort intstr.IntOrString + targetPort.IntVal = port.ContainerPort + targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) + servicePorts = append(servicePorts, api.ServicePort{ + Name: strconv.Itoa(int(port.HostPort)), + Protocol: p, + Port: port.HostPort, + TargetPort: targetPort, + }) + } + return servicePorts +} + +// Configure the container volumes. +func ConfigVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { + volumesMount := []api.VolumeMount{} + volumes := []api.Volume{} + volumeSource := api.VolumeSource{} + for _, volume := range service.Volumes { + name, host, container, mode, err := transformer.ParseVolume(volume) + if err != nil { + logrus.Warningf("Failed to configure container volume: %v", err) + continue + } + + // if volume name isn't specified, set it to a random string of 20 chars + if len(name) == 0 { + name = transformer.RandStringBytes(20) + } + // check if ro/rw mode is defined, default rw + readonly := len(mode) > 0 && mode == "ro" + + volumesMount = append(volumesMount, api.VolumeMount{Name: name, ReadOnly: readonly, MountPath: container}) + + if len(host) > 0 { + volumeSource = api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: host}} + } else { + volumeSource = api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}} + } + + volumes = append(volumes, api.Volume{Name: name, VolumeSource: volumeSource}) + } + return volumesMount, volumes +} + +// Configure the environment variables. +func ConfigEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { + envs := []api.EnvVar{} + for _, v := range service.Environment { + envs = append(envs, api.EnvVar{ + Name: v.Name, + Value: v.Value, + }) + } + + return envs +} + +func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) []runtime.Object { var svcnames []string + // this will hold all the converted data + var allobjects []runtime.Object + for name, service := range komposeObject.ServiceConfigs { + var objects []runtime.Object svcnames = append(svcnames, name) - rc := transformer.InitRC(name, service, opt.Replicas) - sc := transformer.InitSC(name, service) - dc := transformer.InitDC(name, service, opt.Replicas) - ds := transformer.InitDS(name, service) + sc := InitSC(name, service) + + if opt.CreateD { + objects = append(objects, InitDC(name, service, opt.Replicas)) + } + if opt.CreateDS { + objects = append(objects, InitDS(name, service)) + } + if opt.CreateRC { + objects = append(objects, InitRC(name, service, opt.Replicas)) + } // Configure the environment variables. - envs := transformer.ConfigEnvs(name, service) + envs := ConfigEnvs(name, service) // Configure the container command. cmds := transformer.ConfigCommands(service) // Configure the container volumes. - volumesMount, volumes := transformer.ConfigVolumes(service) + volumesMount, volumes := ConfigVolumes(service) // Configure the container ports. - ports := transformer.ConfigPorts(name, service) + ports := ConfigPorts(name, service) // Configure the service ports. - servicePorts := transformer.ConfigServicePorts(name, service) + servicePorts := ConfigServicePorts(name, service) sc.Spec.Ports = servicePorts // Configure label @@ -107,48 +326,21 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. meta.Annotations = annotations } - // Update each supported controllers - UpdateController(rc, fillTemplate, fillObjectMeta) - UpdateController(dc, fillTemplate, fillObjectMeta) - UpdateController(ds, fillTemplate, fillObjectMeta) - - // convert datarc to json / yaml - datarc, err := transformer.TransformData(rc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) + // update supported controller + for _, obj := range objects { + UpdateController(obj, fillTemplate, fillObjectMeta) } - // convert datadc to json / yaml - datadc, err := transformer.TransformData(dc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - // convert datads to json / yaml - datads, err := transformer.TransformData(ds, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - var datasvc []byte // If ports not provided in configuration we will not make service if len(ports) == 0 { logrus.Warningf("[%s] Service cannot be created because of missing port.", name) - } else if len(ports) != 0 { - // convert datasvc to json / yaml - datasvc, err = transformer.TransformData(sc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } + } else { + objects = append(objects, sc) } - - mServices[name] = datasvc - mReplicationControllers[name] = datarc - mDeployments[name] = datadc - mDaemonSets[name] = datads + allobjects = append(allobjects, objects...) } - return mServices, mDeployments, mDaemonSets, mReplicationControllers, nil, svcnames + return allobjects } // updateController updates the given object with the given pod template update function and ObjectMeta update function @@ -163,9 +355,6 @@ func UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSp case *extensions.Deployment: updateTemplate(&t.Spec.Template) updateMeta(&t.ObjectMeta) - case *extensions.ReplicaSet: - updateTemplate(&t.Spec.Template) - updateMeta(&t.ObjectMeta) case *extensions.DaemonSet: updateTemplate(&t.Spec.Template) updateMeta(&t.ObjectMeta) @@ -174,3 +363,23 @@ func UpdateController(obj runtime.Object, updateTemplate func(*api.PodTemplateSp updateMeta(&t.ObjectMeta) } } + +func CreateObjects(client *client.Client, objects []runtime.Object) { + for _, v := range objects { + switch t := v.(type) { + case *extensions.Deployment: + _, err := client.Deployments(api.NamespaceDefault).Create(t) + if err != nil { + logrus.Fatalf("Error: '%v' while creating deployment: %s", err, t.Name) + } + logrus.Infof("Successfully created deployment: %s", t.Name) + case *api.Service: + _, err := client.Services(api.NamespaceDefault).Create(t) + if err != nil { + logrus.Fatalf("Error: '%v' while creating service: %s", err, t.Name) + } + logrus.Infof("Successfully created service: %s", t.Name) + } + } + fmt.Println("\nApplication has been deployed to Kubernetes. You can run 'kubectl get deployment,svc' for details.") +} diff --git a/pkg/transformer/openshift/openshift.go b/pkg/transformer/openshift/openshift.go index d2334718..aa21fa09 100644 --- a/pkg/transformer/openshift/openshift.go +++ b/pkg/transformer/openshift/openshift.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" ) type OpenShift struct { @@ -63,40 +64,45 @@ func initDeploymentConfig(name string, service kobject.ServiceConfig, replicas i return dc } -func (k *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string){ - mServices := make(map[string][]byte) - mReplicationControllers := make(map[string][]byte) - mDeployments := make(map[string][]byte) - mDaemonSets := make(map[string][]byte) - - // OpenShift DeploymentConfigs - mDeploymentConfigs := make(map[string][]byte) - +func (k *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.ConvertOptions) []runtime.Object { var svcnames []string + // this will hold all the converted data + var allobjects []runtime.Object + for name, service := range komposeObject.ServiceConfigs { + var objects []runtime.Object svcnames = append(svcnames, name) - rc := transformer.InitRC(name, service, opt.Replicas) - sc := transformer.InitSC(name, service) - dc := transformer.InitDC(name, service, opt.Replicas) - ds := transformer.InitDS(name, service) - osDC := initDeploymentConfig(name, service, opt.Replicas) // OpenShift DeploymentConfigs + sc := kubernetes.InitSC(name, service) + + if opt.CreateD { + objects = append(objects, kubernetes.InitDC(name, service, opt.Replicas)) + } + if opt.CreateDS { + objects = append(objects, kubernetes.InitDS(name, service)) + } + if opt.CreateRC { + objects = append(objects, kubernetes.InitRC(name, service, opt.Replicas)) + } + if opt.CreateDeploymentConfig { + objects = append(objects, initDeploymentConfig(name, service, opt.Replicas)) // OpenShift DeploymentConfigs + } // Configure the environment variables. - envs := transformer.ConfigEnvs(name, service) + envs := kubernetes.ConfigEnvs(name, service) // Configure the container command. cmds := transformer.ConfigCommands(service) // Configure the container volumes. - volumesMount, volumes := transformer.ConfigVolumes(service) + volumesMount, volumes := kubernetes.ConfigVolumes(service) // Configure the container ports. - ports := transformer.ConfigPorts(name, service) + ports := kubernetes.ConfigPorts(name, service) // Configure the service ports. - servicePorts := transformer.ConfigServicePorts(name, service) + servicePorts := kubernetes.ConfigServicePorts(name, service) sc.Spec.Ports = servicePorts // Configure label @@ -141,55 +147,19 @@ func (k *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C meta.Annotations = annotations } - // Update each supported controllers - kubernetes.UpdateController(rc, fillTemplate, fillObjectMeta) - kubernetes.UpdateController(dc, fillTemplate, fillObjectMeta) - kubernetes.UpdateController(ds, fillTemplate, fillObjectMeta) - // OpenShift DeploymentConfigs - kubernetes.UpdateController(osDC, fillTemplate, fillObjectMeta) - - // convert datarc to json / yaml - datarc, err := transformer.TransformData(rc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) + // update supported controller + for _, obj := range objects { + kubernetes.UpdateController(obj, fillTemplate, fillObjectMeta) } - // convert datadc to json / yaml - datadc, err := transformer.TransformData(dc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - // convert datads to json / yaml - datads, err := transformer.TransformData(ds, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - var datasvc []byte // If ports not provided in configuration we will not make service if len(ports) == 0 { logrus.Warningf("[%s] Service cannot be created because of missing port.", name) - } else if len(ports) != 0 { - // convert datasvc to json / yaml - datasvc, err = transformer.TransformData(sc, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } + } else { + objects = append(objects, sc) } - - // convert OpenShift DeploymentConfig to json / yaml - dataDeploymentConfig, err := transformer.TransformData(osDC, opt.GenerateYaml) - if err != nil { - logrus.Fatalf(err.Error()) - } - - mServices[name] = datasvc - mReplicationControllers[name] = datarc - mDeployments[name] = datadc - mDaemonSets[name] = datads - mDeploymentConfigs[name] = dataDeploymentConfig + allobjects = append(allobjects, objects...) } - return mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs, svcnames + return allobjects } diff --git a/pkg/transformer/transformer.go b/pkg/transformer/transformer.go index f0deacb5..bf332c56 100644 --- a/pkg/transformer/transformer.go +++ b/pkg/transformer/transformer.go @@ -16,9 +16,11 @@ limitations under the License. package transformer -import "github.com/skippbox/kompose/pkg/kobject" +import ( + "github.com/skippbox/kompose/pkg/kobject" + "k8s.io/kubernetes/pkg/runtime" +) type Transformer interface { - Transform(kobject.KomposeObject, kobject.ConvertOptions) (map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, map[string][]byte, []string) + Transform(kobject.KomposeObject, kobject.ConvertOptions) []runtime.Object } - diff --git a/pkg/transformer/utils.go b/pkg/transformer/utils.go index a96f7a26..301b4d3b 100644 --- a/pkg/transformer/utils.go +++ b/pkg/transformer/utils.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "math/rand" "os" - "strconv" "strings" "github.com/Sirupsen/logrus" @@ -15,16 +14,13 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util/intstr" - "github.com/skippbox/kompose/pkg/transformer/kubernetes" ) const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" // RandStringBytes generates randomly n-character string -func randStringBytes(n int) string { +func RandStringBytes(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] @@ -33,7 +29,7 @@ func randStringBytes(n int) string { } // Create the file to write to if --out is specified -func createOutFile(out string) *os.File { +func CreateOutFile(out string) *os.File { var f *os.File var err error if len(out) != 0 { @@ -45,133 +41,6 @@ func createOutFile(out string) *os.File { return f } -// Init RC object -func 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, - //Labels: map[string]string{"service": name}, - }, - Spec: api.ReplicationControllerSpec{ - Selector: map[string]string{"service": name}, - Replicas: int32(replicas), - Template: &api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - //Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return rc -} - -// Init SC object -func InitSC(name string, service kobject.ServiceConfig) *api.Service { - sc := &api.Service{ - TypeMeta: unversioned.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - //Labels: map[string]string{"service": name}, - }, - Spec: api.ServiceSpec{ - Selector: map[string]string{"service": name}, - }, - } - return sc -} - -// Init DC object -func InitDC(name string, service kobject.ServiceConfig, replicas int) *extensions.Deployment { - dc := &extensions.Deployment{ - TypeMeta: unversioned.TypeMeta{ - Kind: "Deployment", - APIVersion: "extensions/v1beta1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - Labels: map[string]string{"service": name}, - }, - Spec: extensions.DeploymentSpec{ - Replicas: int32(replicas), - Selector: &unversioned.LabelSelector{ - MatchLabels: map[string]string{"service": name}, - }, - //UniqueLabelKey: p.Name, - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Labels: map[string]string{"service": name}, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return dc -} - -// Init DS object -func InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { - ds := &extensions.DaemonSet{ - TypeMeta: unversioned.TypeMeta{ - Kind: "DaemonSet", - APIVersion: "extensions/v1beta1", - }, - ObjectMeta: api.ObjectMeta{ - Name: name, - }, - Spec: extensions.DaemonSetSpec{ - Template: api.PodTemplateSpec{ - ObjectMeta: api.ObjectMeta{ - Name: name, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: name, - Image: service.Image, - }, - }, - }, - }, - }, - } - return ds -} - -// Configure the environment variables. -func ConfigEnvs(name string, service kobject.ServiceConfig) []api.EnvVar { - envs := []api.EnvVar{} - for _, v := range service.Environment { - envs = append(envs, api.EnvVar{ - Name: v.Name, - Value: v.Value, - }) - } - - return envs -} - // Configure the container commands func ConfigCommands(service kobject.ServiceConfig) []string { var cmds []string @@ -182,40 +51,8 @@ func ConfigCommands(service kobject.ServiceConfig) []string { return cmds } -// Configure the container volumes. -func ConfigVolumes(service kobject.ServiceConfig) ([]api.VolumeMount, []api.Volume) { - volumesMount := []api.VolumeMount{} - volumes := []api.Volume{} - volumeSource := api.VolumeSource{} - for _, volume := range service.Volumes { - name, host, container, mode, err := parseVolume(volume) - if err != nil { - logrus.Warningf("Failed to configure container volume: %v", err) - continue - } - - // if volume name isn't specified, set it to a random string of 20 chars - if len(name) == 0 { - name = randStringBytes(20) - } - // check if ro/rw mode is defined, default rw - readonly := len(mode) > 0 && mode == "ro" - - volumesMount = append(volumesMount, api.VolumeMount{Name: name, ReadOnly: readonly, MountPath: container}) - - if len(host) > 0 { - volumeSource = api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: host}} - } else { - volumeSource = api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}} - } - - volumes = append(volumes, api.Volume{Name: name, VolumeSource: volumeSource}) - } - return volumesMount, volumes -} - // parseVolume parse a given volume, which might be [name:][host:]container[:access_mode] -func parseVolume(volume string) (name, host, container, mode string, err error) { +func ParseVolume(volume string) (name, host, container, mode string, err error) { separator := ":" volumeStrings := strings.Split(volume, separator) if len(volumeStrings) == 0 { @@ -250,57 +87,6 @@ func isPath(substring string) bool { return strings.Contains(substring, "/") } -// Configure the container ports. -func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { - ports := []api.ContainerPort{} - for _, port := range service.Port { - var p api.Protocol - switch port.Protocol { - default: - p = api.ProtocolTCP - case kobject.ProtocolTCP: - p = api.ProtocolTCP - case kobject.ProtocolUDP: - p = api.ProtocolUDP - } - ports = append(ports, api.ContainerPort{ - ContainerPort: port.ContainerPort, - Protocol: p, - }) - } - - return ports -} - -// Configure the container service ports. -func ConfigServicePorts(name string, service kobject.ServiceConfig) []api.ServicePort { - servicePorts := []api.ServicePort{} - for _, port := range service.Port { - if port.HostPort == 0 { - port.HostPort = port.ContainerPort - } - var p api.Protocol - switch port.Protocol { - default: - p = api.ProtocolTCP - case kobject.ProtocolTCP: - p = api.ProtocolTCP - case kobject.ProtocolUDP: - p = api.ProtocolUDP - } - var targetPort intstr.IntOrString - targetPort.IntVal = port.ContainerPort - targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) - servicePorts = append(servicePorts, api.ServicePort{ - Name: strconv.Itoa(int(port.HostPort)), - Protocol: p, - Port: port.HostPort, - TargetPort: targetPort, - }) - } - return servicePorts -} - // Configure label func ConfigLabels(name string) map[string]string { return map[string]string{"service": name} @@ -338,21 +124,21 @@ func TransformData(obj runtime.Object, GenerateYaml bool) ([]byte, error) { return data, nil } +// Either print to stdout or to file/s +func Print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) string { -func print(name, trailing string, data []byte, toStdout, generateYaml bool, f *os.File) { - file := fmt.Sprintf("%s-%s.json", name, trailing) + file := "" if generateYaml { file = fmt.Sprintf("%s-%s.yaml", name, trailing) - } - separator := "" - if generateYaml { - separator = "---" + } else { + file = fmt.Sprintf("%s-%s.json", name, trailing) } if toStdout { - fmt.Fprintf(os.Stdout, "%s%s\n", string(data), separator) + fmt.Fprintf(os.Stdout, "%s\n", string(data)) + return "" } else if f != nil { // Write all content to a single file f - if _, err := f.WriteString(fmt.Sprintf("%s%s\n", string(data), separator)); err != nil { + if _, err := f.WriteString(fmt.Sprintf("%s\n", string(data))); err != nil { logrus.Fatalf("Failed to write %s to file: %v", trailing, err) } f.Sync() @@ -361,47 +147,7 @@ func print(name, trailing string, data []byte, toStdout, generateYaml bool, f *o if err := ioutil.WriteFile(file, []byte(data), 0644); err != nil { logrus.Fatalf("Failed to write %s: %v", trailing, err) } - fmt.Fprintf(os.Stdout, "file %q created\n", file) - } -} - -func PrintControllers(mServices, mDeployments, mDaemonSets, mReplicationControllers, mDeploymentConfigs map[string][]byte, svcnames []string, opt kobject.ConvertOptions) { - f := createOutFile(opt.OutFile) - defer f.Close() - - for k, v := range mServices { - if v != nil { - print(k, "svc", v, opt.ToStdout, opt.GenerateYaml, f) - } - } - - // If --out or --stdout is set, the validation should already prevent multiple controllers being generated - if opt.CreateD { - for k, v := range mDeployments { - print(k, "deployment", v, opt.ToStdout, opt.GenerateYaml, f) - } - } - - if opt.CreateDS { - for k, v := range mDaemonSets { - print(k, "daemonset", v, opt.ToStdout, opt.GenerateYaml, f) - } - } - - if opt.CreateRC { - for k, v := range mReplicationControllers { - print(k, "rc", v, opt.ToStdout, opt.GenerateYaml, f) - } - } - - if f != nil { - fmt.Fprintf(os.Stdout, "file %q created\n", opt.OutFile) - } - - if opt.CreateChart { - err := kubernetes.GenerateHelm(opt.InputFile, svcnames, opt.GenerateYaml, opt.CreateD, opt.CreateDS, opt.CreateRC, opt.OutFile) - if err != nil { - logrus.Fatalf("Failed to create Chart data: %s\n", err) - } + logrus.Printf("file %q created", file) } + return file } From 469b50c33d824aec50c6fb7c9b0536bef4a6946f Mon Sep 17 00:00:00 2001 From: Tuna Date: Fri, 12 Aug 2016 10:38:33 +0700 Subject: [PATCH 7/7] rebase master --- pkg/kobject/kobject.go | 4 ++-- pkg/loader/compose/compose.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 5d44cef2..b3ae3e31 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -17,7 +17,7 @@ limitations under the License. package kobject import ( - "fmt" + "github.com/Sirupsen/logrus" "github.com/fatih/structs" ) @@ -180,7 +180,7 @@ func CheckUnsupportedKey(service interface{}) { for _, f := range s.Fields() { if f.IsExported() && !f.IsZero() && f.Name() != "Networks" { if count, ok := unsupportedKey[f.Name()]; ok && count == 0 { - fmt.Println("WARNING: Unsupported key " + composeOptions[f.Name()] + " - ignoring") + logrus.Warningf("Unsupported key %s - ignoring", composeOptions[f.Name()]) unsupportedKey[f.Name()]++ } } diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index b3ca6c7a..d7d45eda 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -137,7 +137,7 @@ func (c *Compose) LoadFile(file string) kobject.KomposeObject { for _, name := range composeServiceNames { if composeServiceConfig, ok := composeObject.ServiceConfigs.Get(name); ok { //FIXME: networks always contains one default element, even it isn't declared in compose v2. - if len(composeServiceConfig.Networks.Networks) > 0 && + if composeServiceConfig.Networks != nil && len(composeServiceConfig.Networks.Networks) > 0 && composeServiceConfig.Networks.Networks[0].Name != "default" && !networksWarningFound { logrus.Warningf("Unsupported key networks - ignoring")