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 }