From e867d35e399293b3441221255c2a915b4950205f Mon Sep 17 00:00:00 2001 From: Tuna Date: Mon, 1 Aug 2016 15:21:38 +0700 Subject: [PATCH] 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