diff --git a/cli/app/app.go b/cli/app/app.go index dbfc2017e..6d1b612b0 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -18,6 +18,7 @@ package app import ( "fmt" + "strings" "github.com/Sirupsen/logrus" "github.com/urfave/cli" @@ -39,118 +40,105 @@ import ( const ( DefaultComposeFile = "docker-compose.yml" + DefaultProvider = "kubernetes" ) var inputFormat = "compose" -// Hook for erroring and exit out on warning -type errorOnWarningHook struct{} +func validateFlags(c *cli.Context, opt *kobject.ConvertOptions) { -func (errorOnWarningHook) Levels() []logrus.Level { - return []logrus.Level{logrus.WarnLevel} -} - -func (errorOnWarningHook) Fire(entry *logrus.Entry) error { - logrus.Fatalln(entry.Message) - return nil -} - -// BeforeApp is an action that is executed before any cli command. -func BeforeApp(c *cli.Context) error { - - if c.GlobalBool("verbose") { - logrus.SetLevel(logrus.DebugLevel) - } else if c.GlobalBool("suppress-warnings") { - logrus.SetLevel(logrus.ErrorLevel) - } else if c.GlobalBool("error-on-warning") { - hook := errorOnWarningHook{} - logrus.AddHook(hook) + if opt.OutFile == "-" { + opt.ToStdout = true + opt.OutFile = "" } - return 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") } + if opt.CreateChart && opt.ToStdout { logrus.Fatalf("Error: chart cannot be generated when --stdout is specified") } + if opt.Replicas < 0 { logrus.Fatalf("Error: --replicas cannot be negative") } - if singleOutput { - count := 0 - if opt.CreateD { - count++ - } - if opt.CreateDS { - count++ - } - if opt.CreateRC { - count++ - } - if opt.CreateDeploymentConfig { - count++ - } - if count > 1 { - logrus.Fatalf("Error: only one kind of Kubernetes resource can be generated when --out or --stdout is specified") - } - } - if len(dabFile) > 0 && len(inputFile) > 0 && inputFile != DefaultComposeFile { - logrus.Fatalf("Error: compose file and dab file cannot be specified at the same time") - } -} -// Convert transforms docker compose or dab file to k8s objects -func Convert(c *cli.Context) { - inputFile := c.GlobalString("file") dabFile := c.GlobalString("bundle") - outFile := c.String("out") - generateYaml := c.BoolT("yaml") - toStdout := c.BoolT("stdout") - createD := c.BoolT("deployment") - createDS := c.BoolT("daemonset") - createRC := c.BoolT("replicationcontroller") - createChart := c.BoolT("chart") - replicas := c.Int("replicas") - singleOutput := len(outFile) != 0 || outFile == "-" || toStdout - createDeploymentConfig := c.BoolT("deploymentconfig") - - if outFile == "-" { - toStdout = true - outFile = "" - } - // Create Deployment by default if no controller has be set - if !createD && !createDS && !createRC && !createDeploymentConfig { - createD = true + if len(dabFile) > 0 { + inputFormat = "bundle" + opt.InputFile = dabFile } - komposeObject := kobject.KomposeObject{ - ServiceConfigs: make(map[string]kobject.ServiceConfig), + if len(dabFile) > 0 && len(opt.InputFile) > 0 && opt.InputFile != DefaultComposeFile { + logrus.Fatalf("Error: 'compose' file and 'dab' file cannot be specified at the same time") } +} - file := inputFile - if len(dabFile) > 0 { - inputFormat = "bundle" - file = dabFile - } +func validateControllers(opt *kobject.ConvertOptions) { - opt := kobject.ConvertOptions{ - ToStdout: toStdout, - CreateD: createD, - CreateRC: createRC, - CreateDS: createDS, - CreateDeploymentConfig: createDeploymentConfig, - CreateChart: createChart, - GenerateYaml: generateYaml, - Replicas: replicas, - InputFile: file, - OutFile: outFile, + singleOutput := len(opt.OutFile) != 0 || opt.OutFile == "-" || opt.ToStdout + + if opt.Provider == "kubernetes" { + // create deployment by default if no controller has been set + if !opt.CreateD && !opt.CreateDS && !opt.CreateRC { + opt.CreateD = true + } + if singleOutput { + count := 0 + if opt.CreateD { + count++ + } + if opt.CreateDS { + count++ + } + if opt.CreateRC { + count++ + } + if count > 1 { + logrus.Fatalf("Error: only one kind of Kubernetes resource can be generated when --out or --stdout is specified") + } + } + + } else if opt.Provider == "openshift" { + // create deploymentconfig by default if no controller has been set + if !opt.CreateDeploymentConfig { + opt.CreateDeploymentConfig = true + } + if singleOutput { + count := 0 + if opt.CreateDeploymentConfig { + count++ + } + // Add more controllers here once they are available in OpenShift + // if opt.foo {count++} + + if count > 1 { + logrus.Fatalf("Error: only one kind of OpenShift resource can be generated when --out or --stdout is specified") + } + } } +} - validateFlags(opt, singleOutput, dabFile, inputFile) +// Convert transforms docker compose or dab file to k8s objects +func Convert(c *cli.Context) { + opt := kobject.ConvertOptions{ + ToStdout: c.BoolT("stdout"), + CreateChart: c.BoolT("chart"), + GenerateYaml: c.BoolT("yaml"), + Replicas: c.Int("replicas"), + InputFile: c.GlobalString("file"), + OutFile: c.String("out"), + Provider: strings.ToLower(c.GlobalString("provider")), + CreateD: c.BoolT("deployment"), + CreateDS: c.BoolT("daemonset"), + CreateRC: c.BoolT("replicationcontroller"), + CreateDeploymentConfig: c.BoolT("deploymentconfig"), + } + + validateFlags(c, &opt) + validateControllers(&opt) // loader parses input from file into komposeObject. l, err := loader.GetLoader(inputFormat) @@ -158,11 +146,14 @@ func Convert(c *cli.Context) { logrus.Fatal(err) } - komposeObject = l.LoadFile(file) + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } + komposeObject = l.LoadFile(opt.InputFile) // transformer maps komposeObject to provider's primitives var t transformer.Transformer - if !createDeploymentConfig { + if opt.Provider == "kubernetes" { t = new(kubernetes.Kubernetes) } else { t = new(openshift.OpenShift) @@ -176,25 +167,13 @@ func Convert(c *cli.Context) { // Up brings up deployment, svc. func Up(c *cli.Context) { - inputFile := c.GlobalString("file") - dabFile := c.GlobalString("bundle") - - 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, + InputFile: c.GlobalString("file"), + Replicas: 1, + Provider: strings.ToLower(c.GlobalString("provider")), } - - validateFlags(opt, false, dabFile, inputFile) + validateFlags(c, &opt) + validateControllers(&opt) // loader parses input from file into komposeObject. l, err := loader.GetLoader(inputFormat) @@ -202,39 +181,35 @@ func Up(c *cli.Context) { logrus.Fatal(err) } - komposeObject = l.LoadFile(file) + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } + komposeObject = l.LoadFile(opt.InputFile) //get transfomer - t := new(kubernetes.Kubernetes) + var t transformer.Transformer + if opt.Provider == "kubernetes" { + t = new(kubernetes.Kubernetes) + } else { + t = new(openshift.OpenShift) + } //Submit objects provider errDeploy := t.Deploy(komposeObject, opt) if errDeploy != nil { - logrus.Fatalf("Error while deploying application: %s", err) + logrus.Fatalf("Error while deploying application: %s", errDeploy) } } // Down deletes all deployment, svc. func Down(c *cli.Context) { - inputFile := c.GlobalString("file") - dabFile := c.GlobalString("bundle") - - 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, + InputFile: c.GlobalString("file"), + Replicas: 1, + Provider: strings.ToLower(c.GlobalString("provider")), } - - validateFlags(opt, false, dabFile, inputFile) + validateFlags(c, &opt) + validateControllers(&opt) // loader parses input from file into komposeObject. l, err := loader.GetLoader(inputFormat) @@ -242,15 +217,23 @@ func Down(c *cli.Context) { logrus.Fatal(err) } - komposeObject = l.LoadFile(file) + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + } + komposeObject = l.LoadFile(opt.InputFile) - // get transformer - t := new(kubernetes.Kubernetes) + //get transfomer + var t transformer.Transformer + if opt.Provider == "kubernetes" { + t = new(kubernetes.Kubernetes) + } else { + t = new(openshift.OpenShift) + } //Remove deployed application errUndeploy := t.Undeploy(komposeObject, opt) if errUndeploy != nil { - logrus.Fatalf("Error while deleting application: %s", err) + logrus.Fatalf("Error while deleting application: %s", errUndeploy) } } diff --git a/cli/command/command.go b/cli/command/command.go index c24d93422..706b5290b 100644 --- a/cli/command/command.go +++ b/cli/command/command.go @@ -18,60 +18,134 @@ package command import ( "fmt" + "strings" + "github.com/Sirupsen/logrus" "github.com/skippbox/kompose/cli/app" "github.com/urfave/cli" ) -// ConvertCommand defines the kompose convert subcommand. -func ConvertCommand() cli.Command { - return cli.Command{ +// Hook for erroring and exit out on warning +type errorOnWarningHook struct{} + +func (errorOnWarningHook) Levels() []logrus.Level { + return []logrus.Level{logrus.WarnLevel} +} + +func (errorOnWarningHook) Fire(entry *logrus.Entry) error { + logrus.Fatalln(entry.Message) + return nil +} + +// BeforeApp is an action that is executed before any cli command. +func BeforeApp(c *cli.Context) error { + + if c.GlobalBool("verbose") { + logrus.SetLevel(logrus.DebugLevel) + } else if c.GlobalBool("suppress-warnings") { + logrus.SetLevel(logrus.ErrorLevel) + } else if c.GlobalBool("error-on-warning") { + hook := errorOnWarningHook{} + logrus.AddHook(hook) + } + + // First command added was dummy convert command so removing it + c.App.Commands = c.App.Commands[1:] + provider := strings.ToLower(c.GlobalString("provider")) + switch provider { + case "kubernetes": + c.App.Commands = append(c.App.Commands, ConvertKubernetesCommand()) + case "openshift": + c.App.Commands = append(c.App.Commands, ConvertOpenShiftCommand()) + default: + logrus.Fatalf("Error: Unknown provider name. Providers supported are - 'kubernetes', 'openshift'.") + } + + return nil +} + +// When user tries out `kompose -h`, the convert option should be visible +// so adding a dummy `convert` command, real convert commands depending on Providers +// mentioned are added in `BeforeApp` function +func ConvertCommandDummy() cli.Command { + command := cli.Command{ + Name: "convert", + Usage: fmt.Sprintf("Convert Docker Compose file (e.g. %s) to Kubernetes/OpenShift objects", app.DefaultComposeFile), + } + return command +} + +// ConvertKubernetesCommand defines the kompose convert subcommand for Kubernetes provider +func ConvertKubernetesCommand() cli.Command { + command := cli.Command{ Name: "convert", Usage: fmt.Sprintf("Convert Docker Compose file (e.g. %s) to Kubernetes objects", app.DefaultComposeFile), Action: func(c *cli.Context) { app.Convert(c) }, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "out,o", - Usage: "Specify file name in order to save objects into", - EnvVar: "OUTPUT_FILE", + cli.BoolFlag{ + Name: "chart,c", + Usage: "Create a Helm chart for converted objects", }, cli.BoolFlag{ Name: "deployment,d", - Usage: "Generate a deployment resource file (default on)", + Usage: "Generate a Kubernetes deployment object (default on)", }, cli.BoolFlag{ Name: "daemonset,ds", - Usage: "Generate a daemonset resource file", - }, - cli.BoolFlag{ - Name: "deploymentconfig,dc", - Usage: "Generate a DeploymentConfig for OpenShift", + Usage: "Generate a Kubernetes daemonset object", }, cli.BoolFlag{ Name: "replicationcontroller,rc", - Usage: "Generate a replication controller resource file", - }, - cli.IntFlag{ - Name: "replicas", - Value: 1, - Usage: "Specify the number of replicas in the generated resource spec (default 1)", - }, - cli.BoolFlag{ - Name: "chart,c", - Usage: "Create a chart deployment", - }, - cli.BoolFlag{ - Name: "yaml, y", - Usage: "Generate resource file in yaml format", + Usage: "Generate a Kubernetes replication controller object", }, + }, + } + command.Flags = append(command.Flags, commonConvertFlags()...) + return command +} + +// ConvertOpenShiftCommand defines the kompose convert subcommand for OpenShift provider +func ConvertOpenShiftCommand() cli.Command { + command := cli.Command{ + Name: "convert", + Usage: fmt.Sprintf("Convert Docker Compose file (e.g. %s) to OpenShift objects", app.DefaultComposeFile), + Action: func(c *cli.Context) { + app.Convert(c) + }, + Flags: []cli.Flag{ cli.BoolFlag{ - Name: "stdout", - Usage: "Print Kubernetes objects to stdout", + Name: "deploymentconfig,dc", + Usage: "Generate a OpenShift DeploymentConfig object", }, }, } + command.Flags = append(command.Flags, commonConvertFlags()...) + return command +} + +func commonConvertFlags() []cli.Flag { + return []cli.Flag{ + cli.StringFlag{ + Name: "out,o", + Usage: "Specify file name in order to save objects into", + EnvVar: "OUTPUT_FILE", + }, + cli.IntFlag{ + Name: "replicas", + Value: 1, + Usage: "Specify the number of replicas in the generated resource spec (default 1)", + }, + cli.BoolFlag{ + Name: "yaml, y", + Usage: "Generate resource file in yaml format", + }, + cli.BoolFlag{ + Name: "stdout", + Usage: "Print converted objects to stdout", + }, + } } // UpCommand defines the kompose up subcommand. @@ -126,5 +200,12 @@ func CommonFlags() []cli.Flag { Name: "error-on-warning", Usage: "Treat any warning as error", }, + // mention the end provider + cli.StringFlag{ + Name: "provider", + Usage: "Generate artifacts for this provider", + Value: app.DefaultProvider, + EnvVar: "PROVIDER", + }, } } diff --git a/cli/main/main.go b/cli/main/main.go index decad4cb9..dc30ce0b0 100644 --- a/cli/main/main.go +++ b/cli/main/main.go @@ -19,7 +19,6 @@ package main import ( "os" - cliApp "github.com/skippbox/kompose/cli/app" "github.com/skippbox/kompose/cli/command" "github.com/skippbox/kompose/version" "github.com/urfave/cli" @@ -33,10 +32,15 @@ func main() { app.Author = "Skippbox Kompose Contributors" app.Email = "https://github.com/skippbox/kompose" app.EnableBashCompletion = true - app.Before = cliApp.BeforeApp + app.Before = command.BeforeApp app.Flags = append(command.CommonFlags()) app.Commands = []cli.Command{ - command.ConvertCommand(), + // NOTE: Always add this in first, because this dummy command will be removed later + // in command.BeforeApp function and provider specific command will be added + command.ConvertCommandDummy(), + // command.ConvertKubernetesCommand or command.ConvertOpenShiftCommand + // is added depending on provider mentioned. + command.UpCommand(), command.DownCommand(), // TODO: enable these commands and update docs once we fix them diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 17c003c37..228df9ed5 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -125,6 +125,7 @@ type ConvertOptions struct { Replicas int InputFile string OutFile string + Provider string } // ServiceConfig holds the basic struct of a container diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 9723a83c9..e461a0b92 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -14,7 +14,7 @@ export $(cat $KOMPOSE_ROOT/script/test/fixtures/etherpad/envs) # kubernetes test convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/etherpad/output-k8s.json" "Unsupported key depends_on - ignoring" # openshift test -convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/etherpad/output-os.json" "Unsupported key depends_on - ignoring" +convert::expect_success_and_warning "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/etherpad/output-os.json" "Unsupported key depends_on - ignoring" unset $(cat $KOMPOSE_ROOT/script/test/fixtures/etherpad/envs | cut -d'=' -f1) ###### @@ -24,7 +24,7 @@ export $(cat $KOMPOSE_ROOT/script/test/fixtures/gitlab/envs) # kubernetes test convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/gitlab/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/gitlab/output-k8s.json" # openshift test -convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/gitlab/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/gitlab/output-os.json" +convert::expect_success "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/gitlab/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/gitlab/output-os.json" unset $(cat $KOMPOSE_ROOT/script/test/fixtures/gitlab/envs | cut -d'=' -f1) ###### @@ -32,7 +32,7 @@ unset $(cat $KOMPOSE_ROOT/script/test/fixtures/gitlab/envs | cut -d'=' -f1) # kubernetes test convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ngnix-node-redis/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/ngnix-node-redis/output-k8s.json" "Unsupported key build - ignoring" # openshift test -convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ngnix-node-redis/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/ngnix-node-redis/output-os.json" "Unsupported key build - ignoring" +convert::expect_success_and_warning "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/ngnix-node-redis/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/ngnix-node-redis/output-os.json" "Unsupported key build - ignoring" ###### @@ -40,7 +40,7 @@ convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtur # kubernetes test convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/output-k8s.json" "Service cannot be created because of missing port." # openshift test -convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/output-os.json" "Service cannot be created because of missing port." +convert::expect_success_and_warning "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/output-os.json" "Service cannot be created because of missing port." ###### @@ -48,7 +48,7 @@ convert::expect_success_and_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtur # kubernetes test convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/output-k8s.json" # openshift test -convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/docker-compose.yml convert --stdout --dc" "$KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/output-os.json" +convert::expect_success "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/docker-compose.yml convert --stdout" "$KOMPOSE_ROOT/script/test/fixtures/ports-with-proto/output-os.json" exit $EXIT_STATUS