diff --git a/README.md b/README.md index d34c0cf9..9f3bc687 100755 --- a/README.md +++ b/README.md @@ -181,6 +181,13 @@ WARNING: Unsupported key ContainerName - ignoring WARNING: Unsupported key Dockerfile - ignoring ``` +## Bash completion +Running this below command in order to benefit from bash completion + +``` +$ PROG=kompose source script/bash_autocomplete +``` + ## Building ### Building with `go` diff --git a/cli/app/app.go b/cli/app/app.go index 18bac06d..1fdd178c 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -37,6 +37,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" "github.com/fatih/structs" @@ -260,7 +261,7 @@ func createOutFile(out string) *os.File { } // Init RC object -func initRC(name string, service *project.ServiceConfig) *api.ReplicationController { +func initRC(name string, service *project.ServiceConfig, replicas int) *api.ReplicationController { rc := &api.ReplicationController{ TypeMeta: unversioned.TypeMeta{ Kind: "ReplicationController", @@ -271,7 +272,7 @@ func initRC(name string, service *project.ServiceConfig) *api.ReplicationControl //Labels: map[string]string{"service": name}, }, Spec: api.ReplicationControllerSpec{ - Replicas: 1, + Replicas: int32(replicas), Selector: map[string]string{"service": name}, Template: &api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ @@ -565,8 +566,14 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { createRS := c.BoolT("replicaset") createChart := c.BoolT("chart") fromBundles := c.BoolT("from-bundles") + replicas := c.Int("replicationcontroller") singleOutput := len(outFile) != 0 || toStdout + // Create Deployment by default if no controller has be set + if !createD && !createDS && !createRS && replicas == 0 { + createD = true + } + // Validate the flags if len(outFile) != 0 && toStdout { logrus.Fatalf("Error: --out and --stdout can't be set at the same time") @@ -585,6 +592,9 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { if createRS { count++ } + if replicas != 0 { + count++ + } if count > 1 { logrus.Fatalf("Error: only one type of Kubernetes controller can be generated when --out or --stdout is specified") } @@ -636,7 +646,7 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { checkUnsupportedKey(*service) - rc := initRC(name, service) + rc := initRC(name, service, replicas) sc := initSC(name, service) dc := initDC(name, service) ds := initDS(name, service) @@ -648,62 +658,20 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { logrus.Fatalf(err) } - rc.Spec.Template.Spec.Containers[0].Env = envs - dc.Spec.Template.Spec.Containers[0].Env = envs - ds.Spec.Template.Spec.Containers[0].Env = envs - rs.Spec.Template.Spec.Containers[0].Env = envs - // Configure the container command. var cmds []string for _, cmd := range service.Command.Slice() { cmds = append(cmds, cmd) } - rc.Spec.Template.Spec.Containers[0].Command = cmds - dc.Spec.Template.Spec.Containers[0].Command = cmds - ds.Spec.Template.Spec.Containers[0].Command = cmds - rs.Spec.Template.Spec.Containers[0].Command = cmds - - // Configure the container working dir. - rc.Spec.Template.Spec.Containers[0].WorkingDir = service.WorkingDir - dc.Spec.Template.Spec.Containers[0].WorkingDir = service.WorkingDir - ds.Spec.Template.Spec.Containers[0].WorkingDir = service.WorkingDir - rs.Spec.Template.Spec.Containers[0].WorkingDir = service.WorkingDir - // Configure the container volumes. volumesMount, volumes := configVolumes(service) - rc.Spec.Template.Spec.Containers[0].VolumeMounts = volumesMount - dc.Spec.Template.Spec.Containers[0].VolumeMounts = volumesMount - ds.Spec.Template.Spec.Containers[0].VolumeMounts = volumesMount - rs.Spec.Template.Spec.Containers[0].VolumeMounts = volumesMount - - rc.Spec.Template.Spec.Volumes = volumes - dc.Spec.Template.Spec.Volumes = volumes - ds.Spec.Template.Spec.Volumes = volumes - rs.Spec.Template.Spec.Volumes = volumes - - // Configure the container privileged mode - if service.Privileged == true { - securitycontexts := &api.SecurityContext{ - Privileged: &service.Privileged, - } - rc.Spec.Template.Spec.Containers[0].SecurityContext = securitycontexts - dc.Spec.Template.Spec.Containers[0].SecurityContext = securitycontexts - ds.Spec.Template.Spec.Containers[0].SecurityContext = securitycontexts - rs.Spec.Template.Spec.Containers[0].SecurityContext = securitycontexts - } - // Configure the container ports. ports, err := configPorts(name, service) if err != "" { logrus.Fatalf(err) } - rc.Spec.Template.Spec.Containers[0].Ports = ports - dc.Spec.Template.Spec.Containers[0].Ports = ports - ds.Spec.Template.Spec.Containers[0].Ports = ports - rs.Spec.Template.Spec.Containers[0].Ports = ports - // Configure the service ports. servicePorts, err := configServicePorts(name, service) if err != "" { @@ -717,38 +685,48 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { for key, value := range service.Labels.MapParts() { labels[key] = value } - rc.Spec.Template.ObjectMeta.Labels = labels - dc.Spec.Template.ObjectMeta.Labels = labels - ds.Spec.Template.ObjectMeta.Labels = labels - rs.Spec.Template.ObjectMeta.Labels = labels - rc.ObjectMeta.Labels = labels - dc.ObjectMeta.Labels = labels - ds.ObjectMeta.Labels = labels - rs.ObjectMeta.Labels = labels sc.ObjectMeta.Labels = labels - // Configure the container restart policy. - switch service.Restart { - case "", "always": - rc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyAlways - dc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyAlways - ds.Spec.Template.Spec.RestartPolicy = api.RestartPolicyAlways - rs.Spec.Template.Spec.RestartPolicy = api.RestartPolicyAlways - case "no": - rc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever - dc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever - ds.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever - rs.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever - case "on-failure": - rc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure - dc.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure - ds.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure - rs.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure - default: - logrus.Fatalf("Unknown restart policy %s for service %s", service.Restart, name) + // 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 + } + + // Update each supported controllers + updateController(rc, fillTemplate, fillObjectMeta) + updateController(rs, fillTemplate, fillObjectMeta) + updateController(dc, fillTemplate, fillObjectMeta) + updateController(ds, fillTemplate, fillObjectMeta) + // convert datarc to json / yaml datarc, err := transformer(rc, "replication controller", generateYaml) if err != "" { @@ -831,8 +809,7 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { } } - // We can create RC when we either don't print to --out or --stdout, or we don't create any other controllers - if !singleOutput || (!createD && !createDS && !createRS) { + if replicas != 0 { for k, v := range mReplicationControllers { print(k, "rc", v, toStdout, generateYaml, f) } @@ -843,7 +820,7 @@ func ProjectKuberConvert(p *project.Project, c *cli.Context) { } if createChart { - err := generateHelm(composeFile, svcnames, generateYaml) + err := generateHelm(composeFile, svcnames, generateYaml, createD, createDS, createRS, replicas) if err != nil { logrus.Fatalf("Failed to create Chart data: %s\n", err) } @@ -964,3 +941,24 @@ func ProjectKuberUp(p *project.Project, c *cli.Context) { } } + +// 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) + } +} diff --git a/cli/app/k8sutils.go b/cli/app/k8sutils.go index 3d77bcc7..e656e7b1 100644 --- a/cli/app/k8sutils.go +++ b/cli/app/k8sutils.go @@ -32,7 +32,7 @@ import ( /** * Generate Helm Chart configuration */ -func generateHelm(filename string, svcnames []string, generateYaml bool) error { +func generateHelm(filename string, svcnames []string, generateYaml, createD, createDS, createRS bool, replicas int) error { type ChartDetails struct { Name string } @@ -89,26 +89,35 @@ home: } /* Copy all related json/yaml files into the newly created manifests directory */ - // TODO: support copying controller files other than rc? // TODO: support copying the file specified by --out? for _, svcname := range svcnames { extension := ".json" if generateYaml { extension = ".yaml" } - infile, err := ioutil.ReadFile(svcname + "-rc" + extension) - if err != nil { - logrus.Infof("Error reading %s: %s\n", svcname+"-rc"+extension, err) - return err + if createD { + if err = cpToChart(manifestDir, svcname, "deployment", extension); err != nil { + return err + } } - - err = ioutil.WriteFile(manifestDir+string(os.PathSeparator)+svcname+"-rc"+extension, infile, 0644) - if err != nil { - return err + if createDS { + if err = cpToChart(manifestDir, svcname, "daemonset", extension); err != nil { + return err + } + } + if (replicas != 0) { + if err = cpToChart(manifestDir, svcname, "replicationcontroller", extension); err != nil { + return err + } + } + if createRS { + if err = cpToChart(manifestDir, svcname, "replicaset", extension); err != nil { + return err + } } /* The svc file is optional */ - infile, err = ioutil.ReadFile(svcname + "-svc" + extension) + infile, err := ioutil.ReadFile(svcname + "-svc" + extension) if err != nil { continue } @@ -121,3 +130,13 @@ home: fmt.Fprintf(os.Stdout, "chart created in %q\n", "."+string(os.PathSeparator)+dirName+string(os.PathSeparator)) return nil } + +func cpToChart(manifestDir, svcname, trailing, extension string) error { + infile, err := ioutil.ReadFile(svcname + "-" + trailing + extension) + if err != nil { + logrus.Infof("Error reading %s: %s\n", svcname+"-"+trailing+extension, err) + return err + } + + return ioutil.WriteFile(manifestDir+string(os.PathSeparator)+svcname+"-"+trailing+extension, infile, 0644) +} diff --git a/cli/command/command.go b/cli/command/command.go index 369653d1..7b9b20eb 100644 --- a/cli/command/command.go +++ b/cli/command/command.go @@ -40,15 +40,19 @@ func ConvertCommand(factory app.ProjectFactory) cli.Command { Usage: "Specify file name in order to save objects into", EnvVar: "OUTPUT_FILE", }, - // TODO: validate the flags and make sure only one type is specified cli.BoolFlag{ Name: "deployment,d", - Usage: "Generate a deployment resource file", + Usage: "Generate a deployment resource file (default on)", }, cli.BoolFlag{ Name: "daemonset,ds", Usage: "Generate a daemonset resource file", }, + cli.IntFlag{ + Name: "replicationcontroller,rc", + Value: 0, + Usage: "Specify replicas in order to generate a replication controller resource file", + }, cli.BoolFlag{ Name: "replicaset,rs", Usage: "Generate a replicaset resource file", diff --git a/cli/main/main.go b/cli/main/main.go index baaa19fe..d0f309f6 100644 --- a/cli/main/main.go +++ b/cli/main/main.go @@ -35,6 +35,7 @@ func main() { app.Version = version.VERSION + " (" + version.GITCOMMIT + ")" app.Author = "Skippbox Compose Contributors" app.Email = "https://github.com/skippbox/kompose" + app.EnableBashCompletion = true app.Before = cliApp.BeforeApp app.Flags = append(command.CommonFlags()) app.Commands = []cli.Command{ diff --git a/script/bash_autocomplete b/script/bash_autocomplete new file mode 100644 index 00000000..21a232f1 --- /dev/null +++ b/script/bash_autocomplete @@ -0,0 +1,14 @@ +#! /bin/bash + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_cli_bash_autocomplete() { + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + } + + complete -F _cli_bash_autocomplete $PROG