From b3ba402f738c17c13095a18a3c79435207875c42 Mon Sep 17 00:00:00 2001 From: Martin Nirtl Date: Thu, 6 Feb 2020 23:02:13 +0100 Subject: [PATCH] features and fixes --- internal/commands/configcmd/cat.go | 4 +- internal/commands/downcmd/root.go | 9 +- internal/commands/environmentscmd/rename.go | 1 - .../{environmentscmd => envscmd}/init.go | 34 +++---- .../{environmentscmd => envscmd}/list.go | 8 +- .../{environmentscmd => envscmd}/remove.go | 20 ++-- internal/commands/envscmd/rename.go | 1 + .../{environmentscmd => envscmd}/root.go | 6 +- .../{environmentscmd => envscmd}/set.go | 4 +- internal/commands/initcmd/root.go | 33 ++----- internal/commands/inspectcmd/root.go | 5 +- internal/commands/logscmd/root.go | 2 +- internal/commands/profilecmd/create.go | 66 +++++++++++++ internal/commands/profilecmd/root.go | 10 ++ internal/commands/pscmd/root.go | 2 +- internal/commands/pullcmd/root.go | 65 +++++++++++++ internal/commands/root.go | 10 +- internal/commands/upcmd/root.go | 71 +++++++++++--- internal/config/config.go | 93 ++++++++++++++++++- internal/envvars/envvars.go | 36 +++++++ internal/envvars/envvars_test.go | 40 ++++++++ internal/survey/survey.go | 76 +++++++++++++++ internal/utils/utils.go | 52 ++++------- pkg/dockercompose/dockercompose.go | 8 +- pkg/dockercompose/helpers.go | 3 +- .../spinnertimebridger/spinner.go | 29 +++--- 26 files changed, 535 insertions(+), 153 deletions(-) delete mode 100644 internal/commands/environmentscmd/rename.go rename internal/commands/{environmentscmd => envscmd}/init.go (66%) rename internal/commands/{environmentscmd => envscmd}/list.go (83%) rename internal/commands/{environmentscmd => envscmd}/remove.go (74%) create mode 100644 internal/commands/envscmd/rename.go rename internal/commands/{environmentscmd => envscmd}/root.go (51%) rename internal/commands/{environmentscmd => envscmd}/set.go (94%) create mode 100644 internal/commands/profilecmd/create.go create mode 100644 internal/commands/profilecmd/root.go create mode 100644 internal/commands/pullcmd/root.go create mode 100644 internal/envvars/envvars.go create mode 100644 internal/envvars/envvars_test.go create mode 100644 internal/survey/survey.go diff --git a/internal/commands/configcmd/cat.go b/internal/commands/configcmd/cat.go index d09bc1b..e2493ef 100644 --- a/internal/commands/configcmd/cat.go +++ b/internal/commands/configcmd/cat.go @@ -4,7 +4,7 @@ import ( "fmt" "io/ioutil" - "github.com/martinnirtl/dockma/internal/utils" + "github.com/martinnirtl/dockma/internal/config" "github.com/spf13/cobra" "github.com/ttacon/chalk" ) @@ -15,7 +15,7 @@ var catCmd = &cobra.Command{ Long: `-`, Example: "dockma config cat", Run: func(cmd *cobra.Command, args []string) { - filepath := utils.GetFullLogfilePath("config.json") + filepath := config.GetDockmaFilepath(("config.json")) content, err := ioutil.ReadFile(filepath) diff --git a/internal/commands/downcmd/root.go b/internal/commands/downcmd/root.go index c16e1e1..33b6533 100644 --- a/internal/commands/downcmd/root.go +++ b/internal/commands/downcmd/root.go @@ -4,11 +4,13 @@ import ( "fmt" "os" + "github.com/martinnirtl/dockma/internal/config" "github.com/martinnirtl/dockma/internal/utils" "github.com/martinnirtl/dockma/pkg/externalcommand" "github.com/martinnirtl/dockma/pkg/externalcommand/spinnertimebridger" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/ttacon/chalk" ) var DownCommand = &cobra.Command{ @@ -16,8 +18,7 @@ var DownCommand = &cobra.Command{ Short: "Stops active environment.", Long: "-", Run: func(cmd *cobra.Command, args []string) { - logfileName := viper.GetString("logfile") - filepath := utils.GetFullLogfilePath(logfileName) + filepath := config.GetLogfile() activeEnv := viper.GetString("active") @@ -25,7 +26,7 @@ var DownCommand = &cobra.Command{ utils.NoEnvs() } - envHomeDir := viper.GetString(fmt.Sprintf("environments.%s.home", activeEnv)) + envHomeDir := viper.GetString(fmt.Sprintf("envs.%s.home", activeEnv)) err := os.Chdir(envHomeDir) @@ -35,7 +36,7 @@ var DownCommand = &cobra.Command{ var timebridger externalcommand.Timebridger if hideCmdOutput := viper.GetBool("hidesubcommandoutput"); !hideCmdOutput { - timebridger = spinnertimebridger.New("Running command '%s'", "", 14, "cyan") + timebridger = spinnertimebridger.New("Running 'docker-compose down'", fmt.Sprintf("%sSuccessfully executed 'docker-compose down'%s", chalk.Green, chalk.ResetColor), 14, "cyan") } _, err = externalcommand.Execute("docker-compose down", timebridger, filepath) diff --git a/internal/commands/environmentscmd/rename.go b/internal/commands/environmentscmd/rename.go deleted file mode 100644 index 075b4eb..0000000 --- a/internal/commands/environmentscmd/rename.go +++ /dev/null @@ -1 +0,0 @@ -package environmentscmd diff --git a/internal/commands/environmentscmd/init.go b/internal/commands/envscmd/init.go similarity index 66% rename from internal/commands/environmentscmd/init.go rename to internal/commands/envscmd/init.go index 898173f..ff7363a 100644 --- a/internal/commands/environmentscmd/init.go +++ b/internal/commands/envscmd/init.go @@ -1,13 +1,12 @@ -package environmentscmd +package envscmd import ( "errors" "fmt" "os" - "github.com/AlecAivazis/survey/v2" + "github.com/martinnirtl/dockma/internal/survey" "github.com/martinnirtl/dockma/internal/utils" - "github.com/martinnirtl/dockma/pkg/dockercompose" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/ttacon/chalk" @@ -40,14 +39,18 @@ var initCmd = &cobra.Command{ } } - err := survey.AskOne(&survey.Input{ - Message: "Enter a name for the new environment (has to be unique)", - }, &env) + env, err := survey.Input("Enter a name for the new environment (has to be unique)", "") if err != nil { utils.Abort() } + if env == "" { + utils.Error(errors.New("Got empty string for environment name")) + } else if env == "-" { + utils.Error(errors.New("Invalid environment name '-'")) + } + workingDir, err := os.Getwd() if err != nil { @@ -56,20 +59,13 @@ var initCmd = &cobra.Command{ os.Exit(0) } - // TODO read docker-compose.yaml - services, err := dockercompose.GetServices(workingDir) + autoPull, err := survey.Confirm(fmt.Sprintf("Run %sgit pull%s before %sdockma up%s", chalk.Cyan, chalk.Reset, chalk.Cyan, chalk.ResetColor), false) if err != nil { - fmt.Print(err) - - os.Exit(1) + utils.Abort() } - proceed := false - err = survey.AskOne(&survey.Confirm{ - Message: fmt.Sprintf("Add new environment %s%s%s (location: %s)", chalk.Cyan, env, chalk.ResetColor, workingDir), - Default: true, - }, &proceed) + proceed, err := survey.Confirm(fmt.Sprintf("Add new environment %s%s%s (location: %s)", chalk.Cyan, env, chalk.ResetColor, workingDir), true) if !proceed { utils.Abort() @@ -79,8 +75,8 @@ var initCmd = &cobra.Command{ os.Exit(0) } - viper.Set(fmt.Sprintf("environments.%s.home", env), workingDir) - viper.Set(fmt.Sprintf("environments.%s.services", env), services.All) + viper.Set(fmt.Sprintf("envs.%s.home", env), workingDir) + viper.Set(fmt.Sprintf("envs.%s.autopull", env), autoPull) oldEnv := viper.GetString("active") @@ -97,5 +93,5 @@ var initCmd = &cobra.Command{ } func init() { - EnvironmentsCommand.AddCommand(initCmd) + EnvsCommand.AddCommand(initCmd) } diff --git a/internal/commands/environmentscmd/list.go b/internal/commands/envscmd/list.go similarity index 83% rename from internal/commands/environmentscmd/list.go rename to internal/commands/envscmd/list.go index 6683aad..606ab9e 100644 --- a/internal/commands/environmentscmd/list.go +++ b/internal/commands/envscmd/list.go @@ -1,9 +1,9 @@ -package environmentscmd +package envscmd import ( "fmt" - "github.com/martinnirtl/dockma/internal/utils" + "github.com/martinnirtl/dockma/internal/config" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/ttacon/chalk" @@ -16,7 +16,7 @@ var listCmd = &cobra.Command{ Long: `-`, Example: "dockma envs list", Run: func(cmd *cobra.Command, args []string) { - envs := utils.GetEnvironments() + envs := config.GetEnvs() activeEnv := viper.GetString("active") @@ -35,5 +35,5 @@ var listCmd = &cobra.Command{ } func init() { - EnvironmentsCommand.AddCommand(listCmd) + EnvsCommand.AddCommand(listCmd) } diff --git a/internal/commands/environmentscmd/remove.go b/internal/commands/envscmd/remove.go similarity index 74% rename from internal/commands/environmentscmd/remove.go rename to internal/commands/envscmd/remove.go index 38f655b..30cc5dc 100644 --- a/internal/commands/environmentscmd/remove.go +++ b/internal/commands/envscmd/remove.go @@ -1,11 +1,10 @@ -package environmentscmd +package envscmd import ( "errors" "fmt" - "os" - "github.com/AlecAivazis/survey/v2" + "github.com/martinnirtl/dockma/internal/survey" "github.com/martinnirtl/dockma/internal/utils" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -33,13 +32,10 @@ var removeCmd = &cobra.Command{ env = utils.GetEnvironment(args[0]) } - sure := false - if err := survey.AskOne(&survey.Confirm{ - Message: fmt.Sprintf("Are you sure to remove '%s'", env), - }, &sure); err != nil || !sure { - fmt.Printf("%sAborted.%s\n", chalk.Cyan, chalk.ResetColor) + sure, err := survey.Confirm(fmt.Sprintf("Are you sure to remove '%s'", env), false) - os.Exit(0) + if err != nil || !sure { + utils.Abort() } activeEnv := viper.GetString("active") @@ -54,11 +50,11 @@ var removeCmd = &cobra.Command{ fmt.Printf("%sRemoved environment: %s%s\n", chalk.Cyan, env, chalk.ResetColor) } - envs := viper.GetStringMap("environments") + envs := viper.GetStringMap("envs") delete(envs, env) - viper.Set("environments", envs) + viper.Set("envs", envs) if err := viper.WriteConfig(); err != nil { fmt.Printf("%sError removing environment: %s%s\n", chalk.Red, env, chalk.ResetColor) @@ -67,5 +63,5 @@ var removeCmd = &cobra.Command{ } func init() { - EnvironmentsCommand.AddCommand(removeCmd) + EnvsCommand.AddCommand(removeCmd) } diff --git a/internal/commands/envscmd/rename.go b/internal/commands/envscmd/rename.go new file mode 100644 index 0000000..3be8faf --- /dev/null +++ b/internal/commands/envscmd/rename.go @@ -0,0 +1 @@ +package envscmd diff --git a/internal/commands/environmentscmd/root.go b/internal/commands/envscmd/root.go similarity index 51% rename from internal/commands/environmentscmd/root.go rename to internal/commands/envscmd/root.go index b44c42d..77d7555 100644 --- a/internal/commands/environmentscmd/root.go +++ b/internal/commands/envscmd/root.go @@ -1,11 +1,11 @@ -package environmentscmd +package envscmd import ( "github.com/spf13/cobra" ) -// EnvironmentsCommand is the top level environments command -var EnvironmentsCommand = &cobra.Command{ +// EnvsCommand is the top level Envs command +var EnvsCommand = &cobra.Command{ Use: "envs", Short: "Environments reflect docker-compose based projects.", Long: "-", diff --git a/internal/commands/environmentscmd/set.go b/internal/commands/envscmd/set.go similarity index 94% rename from internal/commands/environmentscmd/set.go rename to internal/commands/envscmd/set.go index 22dc9d3..f620b0f 100644 --- a/internal/commands/environmentscmd/set.go +++ b/internal/commands/envscmd/set.go @@ -1,4 +1,4 @@ -package environmentscmd +package envscmd import ( "errors" @@ -49,5 +49,5 @@ var setCmd = &cobra.Command{ } func init() { - EnvironmentsCommand.AddCommand(setCmd) + EnvsCommand.AddCommand(setCmd) } diff --git a/internal/commands/initcmd/root.go b/internal/commands/initcmd/root.go index a58ee69..1061ff6 100644 --- a/internal/commands/initcmd/root.go +++ b/internal/commands/initcmd/root.go @@ -1,7 +1,6 @@ package initcmd import ( - "errors" "fmt" "os" "os/user" @@ -9,7 +8,7 @@ import ( "strings" "time" - "github.com/AlecAivazis/survey/v2" + "github.com/martinnirtl/dockma/internal/survey" "github.com/martinnirtl/dockma/internal/utils" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -26,21 +25,13 @@ var InitCommand = &cobra.Command{ func initPreRunHook(cmd *cobra.Command, args []string) { if init := viper.GetTime("init"); !init.IsZero() { - proceed := false - err := survey.AskOne(&survey.Confirm{ - Message: fmt.Sprintf("%sDockma CLI has already been initialized!%s Do you want to proceed", chalk.Yellow, chalk.ResetColor), - Default: false, - }, &proceed, survey.WithValidator(survey.Required)) + proceed, err := survey.Confirm(fmt.Sprintf("%sDockma CLI has already been initialized!%s Do you want to proceed", chalk.Yellow, chalk.ResetColor), false) if err != nil || !proceed { utils.Abort() } } else { - accept := false - err := survey.AskOne(&survey.Confirm{ - Message: fmt.Sprintf("Dockma CLI config will be stored at: %s", viper.GetString("home")), - Default: true, - }, &accept, survey.WithValidator(survey.Required)) + accept, err := survey.Confirm(fmt.Sprintf("Dockma CLI config will be stored at: %s", viper.GetString("home")), true) if err != nil { utils.Abort() @@ -58,21 +49,11 @@ func initCommandHandler(cmd *cobra.Command, args []string) { username = sysUser.Username } - survey.AskOne(&survey.Input{ - Message: "What is your name", - Default: strings.Title(username), - }, &username, survey.WithValidator(func(val interface{}) error { - switch text := val.(type) { - case string: - if len(text) > 0 { - return nil - } - default: - return errors.New("invalid input for username") - } + username, err := survey.Input("What is your name", strings.Title(username)) - return nil - })) + if err != nil { + utils.Abort() + } viper.Set("username", username) viper.Set("init", time.Now()) diff --git a/internal/commands/inspectcmd/root.go b/internal/commands/inspectcmd/root.go index 6fd644a..2feaabc 100644 --- a/internal/commands/inspectcmd/root.go +++ b/internal/commands/inspectcmd/root.go @@ -6,7 +6,7 @@ import ( "io/ioutil" "os" - "github.com/martinnirtl/dockma/internal/utils" + "github.com/martinnirtl/dockma/internal/config" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/ttacon/chalk" @@ -17,8 +17,7 @@ var InspectCommand = &cobra.Command{ Short: "Print detailed output of previously executed command [up|down].", Long: "-", Run: func(cmd *cobra.Command, args []string) { - filename := viper.GetString("logfile") - filepath := utils.GetFullLogfilePath(filename) + filepath := config.GetLogfile() content, err := ioutil.ReadFile(filepath) diff --git a/internal/commands/logscmd/root.go b/internal/commands/logscmd/root.go index d24988c..b838705 100644 --- a/internal/commands/logscmd/root.go +++ b/internal/commands/logscmd/root.go @@ -26,7 +26,7 @@ var LogsCommand = &cobra.Command{ utils.NoEnvs() } - envHomeDir := viper.GetString(fmt.Sprintf("environments.%s.home", activeEnv)) + envHomeDir := viper.GetString(fmt.Sprintf("envs.%s.home", activeEnv)) err := os.Chdir(envHomeDir) diff --git a/internal/commands/profilecmd/create.go b/internal/commands/profilecmd/create.go new file mode 100644 index 0000000..1067c7c --- /dev/null +++ b/internal/commands/profilecmd/create.go @@ -0,0 +1,66 @@ +package profilecmd + +import ( + "errors" + "fmt" + + "github.com/martinnirtl/dockma/internal/config" + "github.com/martinnirtl/dockma/internal/survey" + "github.com/martinnirtl/dockma/internal/utils" + "github.com/martinnirtl/dockma/pkg/dockercompose" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/ttacon/chalk" +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Create named service selection.", + Long: `-`, + Example: "dockma profile create [name]", + Run: func(cmd *cobra.Command, args []string) { + activeEnv := config.GetActiveEnv() + envHomeDir := config.GetEnvHomeDir(activeEnv) + + profileName, err := survey.Input("Enter name for profile", "") + + if err != nil { + utils.Abort() + } + + if config.HasProfileName(activeEnv, profileName) { + utils.Error(errors.New("Profile name already taken. Use 'update' to reselect services")) + } + + // FIXME use regex + if profileName == "" || profileName == "-" { + utils.Error(errors.New("Invalid profile name")) + } + + services, err := dockercompose.GetServices(envHomeDir) + + if err != nil { + utils.Error(errors.New("Could not read services")) + } + + selected, err := survey.MultiSelect(fmt.Sprintf("Select services for profile %s%s%s", chalk.Cyan, profileName, chalk.ResetColor), services.All, nil) + + if err != nil { + utils.Abort() + } + + if len(selected) == 0 { + fmt.Printf("%sNo services selected%s\n\n", chalk.Yellow, chalk.ResetColor) + + utils.Abort() + } + + viper.Set(fmt.Sprintf("envs.%s.profiles.%s", activeEnv, profileName), selected) + + config.Save() + }, +} + +func init() { + ProfileCommand.AddCommand(createCmd) +} diff --git a/internal/commands/profilecmd/root.go b/internal/commands/profilecmd/root.go new file mode 100644 index 0000000..0981fb9 --- /dev/null +++ b/internal/commands/profilecmd/root.go @@ -0,0 +1,10 @@ +package profilecmd + +import "github.com/spf13/cobra" + +// ProfileCommand is the top level config command +var ProfileCommand = &cobra.Command{ + Use: "profile", + Short: "Manage profiles (predefined service selections).", + Long: "-", +} diff --git a/internal/commands/pscmd/root.go b/internal/commands/pscmd/root.go index 29ea377..d5e811b 100644 --- a/internal/commands/pscmd/root.go +++ b/internal/commands/pscmd/root.go @@ -21,7 +21,7 @@ var PSCommand = &cobra.Command{ utils.NoEnvs() } - envHomeDir := viper.GetString(fmt.Sprintf("environments.%s.home", activeEnv)) + envHomeDir := viper.GetString(fmt.Sprintf("envs.%s.home", activeEnv)) err := os.Chdir(envHomeDir) diff --git a/internal/commands/pullcmd/root.go b/internal/commands/pullcmd/root.go new file mode 100644 index 0000000..7a6ce07 --- /dev/null +++ b/internal/commands/pullcmd/root.go @@ -0,0 +1,65 @@ +package pullcmd + +import ( + "errors" + "fmt" + "os" + + "github.com/martinnirtl/dockma/internal/config" + "github.com/martinnirtl/dockma/internal/utils" + "github.com/martinnirtl/dockma/pkg/externalcommand" + "github.com/martinnirtl/dockma/pkg/externalcommand/spinnertimebridger" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/ttacon/chalk" +) + +// Pull runs git pull in given path and optionally logs output +func Pull(path string, log bool) error { + err := os.Chdir(path) + + if err != nil { + return errors.New("could not change working dir") + } + + var timebridger externalcommand.Timebridger + if hideCmdOutput := viper.GetBool("hidesubcommandoutput"); !hideCmdOutput { + timebridger = spinnertimebridger.New(fmt.Sprintf("Running %sgit pull%s", chalk.Cyan, chalk.ResetColor), "", 14, "cyan") + } + + var logfile string + if log { + logfile = config.GetLogfile() + } + + _, err = externalcommand.Execute("git pull", timebridger, logfile) + + if err != nil { + return errors.New("Could not execute 'git pull' in active environment home dir") + } + + return nil +} + +// PullCommand is a top level dockma command +var PullCommand = &cobra.Command{ + Use: "pull", + Short: "Run 'git pull' in active environment home dir.", + Long: "-", + Run: func(cmd *cobra.Command, args []string) { + + activeEnv := config.GetActiveEnv() + + if activeEnv == "-" { + utils.NoEnvs() + } + + envHomeDir := config.GetEnvHomeDir(activeEnv) + + err := Pull(envHomeDir, true) + + if err != nil { + utils.Error(err) + } + }, +} diff --git a/internal/commands/root.go b/internal/commands/root.go index d24a10c..cdff3b1 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -9,11 +9,13 @@ import ( "github.com/martinnirtl/dockma/internal/commands/configcmd" "github.com/martinnirtl/dockma/internal/commands/downcmd" - "github.com/martinnirtl/dockma/internal/commands/environmentscmd" + "github.com/martinnirtl/dockma/internal/commands/envscmd" "github.com/martinnirtl/dockma/internal/commands/initcmd" "github.com/martinnirtl/dockma/internal/commands/inspectcmd" "github.com/martinnirtl/dockma/internal/commands/logscmd" + "github.com/martinnirtl/dockma/internal/commands/profilecmd" "github.com/martinnirtl/dockma/internal/commands/pscmd" + "github.com/martinnirtl/dockma/internal/commands/pullcmd" "github.com/martinnirtl/dockma/internal/commands/upcmd" "github.com/martinnirtl/dockma/internal/commands/versioncmd" "github.com/mitchellh/go-homedir" @@ -33,11 +35,13 @@ var RootCommand = &cobra.Command{ func init() { RootCommand.AddCommand(configcmd.ConfigCommand) RootCommand.AddCommand(downcmd.DownCommand) - RootCommand.AddCommand(environmentscmd.EnvironmentsCommand) + RootCommand.AddCommand(envscmd.EnvsCommand) RootCommand.AddCommand(initcmd.InitCommand) RootCommand.AddCommand(inspectcmd.InspectCommand) RootCommand.AddCommand(logscmd.LogsCommand) + RootCommand.AddCommand(profilecmd.ProfileCommand) RootCommand.AddCommand(pscmd.PSCommand) + RootCommand.AddCommand(pullcmd.PullCommand) RootCommand.AddCommand(upcmd.UpCommand) RootCommand.AddCommand(versioncmd.VersionCommand) @@ -62,7 +66,7 @@ func init() { viper.SetDefault("hidesubcommandoutput", false) viper.SetDefault("logfile", "log.txt") viper.SetDefault("active", "-") - viper.SetDefault("environments", map[string]interface{}{}) + viper.SetDefault("envs", map[string]interface{}{}) } func initConfig() { diff --git a/internal/commands/upcmd/root.go b/internal/commands/upcmd/root.go index f69e556..c68a0d3 100644 --- a/internal/commands/upcmd/root.go +++ b/internal/commands/upcmd/root.go @@ -3,31 +3,48 @@ package upcmd import ( "fmt" "os" + "strings" - "github.com/AlecAivazis/survey/v2" + "github.com/martinnirtl/dockma/internal/commands/pullcmd" + "github.com/martinnirtl/dockma/internal/config" + "github.com/martinnirtl/dockma/internal/envvars" + "github.com/martinnirtl/dockma/internal/survey" "github.com/martinnirtl/dockma/internal/utils" "github.com/martinnirtl/dockma/pkg/dockercompose" "github.com/martinnirtl/dockma/pkg/externalcommand" "github.com/martinnirtl/dockma/pkg/externalcommand/spinnertimebridger" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/ttacon/chalk" ) +// UpCommand implements the top level dockma command up var UpCommand = &cobra.Command{ Use: "up", Short: "Runs active environment with service selection.", Long: "-", Run: func(cmd *cobra.Command, args []string) { - logfileName := viper.GetString("logfile") - filepath := utils.GetFullLogfilePath(logfileName) + filepath := config.GetLogfile() - activeEnv := viper.GetString("active") + activeEnv := config.GetActiveEnv() if activeEnv == "-" { utils.NoEnvs() } - envHomeDir := viper.GetString(fmt.Sprintf("environments.%s.home", activeEnv)) + envHomeDir := viper.GetString(fmt.Sprintf("envs.%s.home", activeEnv)) + + autoPull := config.IsAutoPullSet(activeEnv) + + if autoPull { + err := pullcmd.Pull(envHomeDir, false) + + if err != nil { + fmt.Printf("%sCould not execute git pull.%s\n", chalk.Yellow, chalk.ResetColor) + } + } else { + fmt.Println("no auto pull") + } services, err := dockercompose.GetServices(envHomeDir) @@ -35,13 +52,37 @@ var UpCommand = &cobra.Command{ utils.Error(err) } - var selection []string - survey.AskOne(&survey.MultiSelect{ - Message: "What days do you prefer:", - Options: services.All, - Default: services.All, - PageSize: len(services.All), - }, &selection) + if len(services.Override) > 0 { + fmt.Printf("%sFound %d services in docker-compose.override.y(a)ml: %s%s\n\n", chalk.Yellow, len(services.Override), strings.Join(services.Override, ", "), chalk.ResetColor) + } + + selectedServices, err := survey.MultiSelect("Select services to start", services.All, services.All) + + if err != nil { + utils.Abort() + } + + saveProfile, err := survey.Confirm("Save as profile", false) + + if err != nil { + utils.Abort() + } + + if saveProfile { + profileName, err := survey.Input("Enter profile name", "") + + if err != nil { + utils.Abort() + } + + viper.Set(fmt.Sprintf("envs.%s.profiles.%s", activeEnv, profileName), selectedServices) + } + + err = envvars.SetEnvVars(services.All, selectedServices) + + if err != nil { + utils.Error(err) + } err = os.Chdir(envHomeDir) @@ -50,11 +91,11 @@ var UpCommand = &cobra.Command{ } var timebridger externalcommand.Timebridger - if hideCmdOutput := viper.GetBool("hidesubcommandoutput"); hideCmdOutput { - timebridger = spinnertimebridger.New("Running command '%s'", "", 14, "cyan") + if hideCmdOutput := viper.GetBool("hidesubcommandoutput"); !hideCmdOutput { + timebridger = spinnertimebridger.New("Running 'docker-compose up'", fmt.Sprintf("%sSuccessfully executed 'docker-compose up'%s", chalk.Green, chalk.ResetColor), 14, "cyan") } - command := externalcommand.JoinCommandSlices("docker-compose up -d", selection...) + command := externalcommand.JoinCommandSlices("docker-compose up -d", selectedServices...) _, err = externalcommand.Execute(command, timebridger, filepath) diff --git a/internal/config/config.go b/internal/config/config.go index cbc8193..c2052b9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,10 +1,97 @@ package config -import "github.com/spf13/viper" +import ( + "errors" + "fmt" + "path/filepath" + "sort" -var homeDir = viper.GetString("home") + "github.com/spf13/viper" +) -// GetActiveEnv returns id/name of the currently active environment +// NOTE viper gets initialized in commands/root.go and is + +// Save saves the config +func Save() error { + err := viper.WriteConfig() + + if err != nil { + return errors.New("Could not save changes to dockma config") + } + + return nil +} + +// GetUsername returns the user's name +func GetUsername() string { + return viper.GetString("username") +} + +// GetHomeDir returns the full path to dockma home dir +func GetHomeDir() string { + return viper.GetString("home") +} + +// GetActiveEnv returns the name of the active environment func GetActiveEnv() string { return viper.GetString("active") } + +// GetDockmaFilepath returns full path of the given filename joined with dockma home dir +func GetDockmaFilepath(filename string) string { + path := viper.GetString("home") + + return filepath.Join(path, filename) +} + +// GetLogfile returns full path to std dockma logfile +func GetLogfile() string { + filename := viper.GetString("logfile") + + return GetDockmaFilepath(filename) +} + +// GetEnvs returns configured envs +func GetEnvs() (envs []string) { + envsMap := viper.GetStringMap("envs") + + envs = make([]string, 0, len(envsMap)) + + for env := range envsMap { + envs = append(envs, env) + } + + sort.Strings(envs) + + return +} + +// GetEnvHomeDir returns the full path to dockma home dir +func GetEnvHomeDir(envName string) string { + return viper.GetString(fmt.Sprintf("envs.%s.home", envName)) +} + +// IsAutoPullSet returns wether to run git pull or not +func IsAutoPullSet(envName string) bool { + return viper.GetBool(fmt.Sprintf("envs.%s.autopull", envName)) +} + +// GetProfilesNames returns profile names for given env +func GetProfilesNames(env string) (profiles []string) { + for profile := range viper.GetStringMap(fmt.Sprintf("envs.%s.profiles", env)) { + profiles = append(profiles, profile) + } + + return +} + +// HasProfileName tells if profile with name exists in env +func HasProfileName(env string, name string) bool { + profile := viper.GetStringSlice(fmt.Sprintf("envs.%s.profiles.%s", env, name)) + + if profile != nil { + return true + } + + return false +} diff --git a/internal/envvars/envvars.go b/internal/envvars/envvars.go new file mode 100644 index 0000000..3f93313 --- /dev/null +++ b/internal/envvars/envvars.go @@ -0,0 +1,36 @@ +package envvars + +import ( + "errors" + "fmt" + "os" + "strings" +) + +// SetEnvVars sets env vars for given env +func SetEnvVars(services []string, selected []string) error { + for _, service := range services { + key := fmt.Sprintf("%s_HOST", strings.ToUpper(strings.ReplaceAll(service, "-", "_"))) + + var val string + for _, selectedService := range selected { + if service == selectedService { + val = service + + break + } + } + + if val == "" { + val = "docker.host.internal" + } + + err := os.Setenv(key, val) + + if err != nil { + return errors.New("setting environment variables for docker dns failed") + } + } + + return nil +} diff --git a/internal/envvars/envvars_test.go b/internal/envvars/envvars_test.go new file mode 100644 index 0000000..6dc15af --- /dev/null +++ b/internal/envvars/envvars_test.go @@ -0,0 +1,40 @@ +package envvars + +import ( + "fmt" + "os" + "strings" + "testing" +) + +func includes(slice []string, s string) bool { + for _, val := range slice { + if val == s { + return true + } + } + + return false +} + +func TestSetEnvVars(t *testing.T) { + services := []string{"database", "backend", "frontend"} + selected := []string{"database", "frontend"} + + SetEnvVars(services, selected) + + for _, service := range services { + envName := fmt.Sprintf("%s_HOST", strings.ToUpper(strings.ReplaceAll(service, "-", "_"))) + envVal := os.Getenv(envName) + + if includes(selected, service) { + if envVal != service { + t.Errorf("%s set to %s instead of %s", envName, envVal, service) + } + } else { + if envVal != "docker.host.internal" { + t.Errorf("%s set to %s instead of %s", envName, envVal, "docker.host.internal") + } + } + } +} diff --git a/internal/survey/survey.go b/internal/survey/survey.go new file mode 100644 index 0000000..c25e87e --- /dev/null +++ b/internal/survey/survey.go @@ -0,0 +1,76 @@ +package survey + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" +) + +// Confirm abstracts survey's confirm and adds styling +func Confirm(message string, preselected bool) (confirm bool, err error) { + err = survey.AskOne(&survey.Confirm{ + Message: message, + Default: preselected, + }, &confirm) + + if err != nil { + return false, fmt.Errorf("confirm got interrupted") + } + + return +} + +// Input abstracts survey's input and adds styling +func Input(message string, suggestion string) (response string, err error) { + err = survey.AskOne(&survey.Input{ + Message: message, + Default: suggestion, + }, &response) + + if err != nil { + return "", fmt.Errorf("input got interrupted") + } + + return +} + +// Select abstracts survey's select and adds styling +func Select(message string, options []string) (selection string, err error) { + err = survey.AskOne(&survey.Select{ + Message: message, + Options: options, + }, &selection, survey.WithIcons(func(icons *survey.IconSet) { + icons.SelectFocus.Text = "❯" + + icons.SelectFocus.Format = "cyan+b" + })) + + if err != nil { + return "", fmt.Errorf("select got interrupted") + } + + return +} + +// MultiSelect abstracts survey's multiselect and adds styling +func MultiSelect(message string, options []string, preselected []string) (selection []string, err error) { + err = survey.AskOne(&survey.MultiSelect{ + Message: message, + Options: options, + Default: preselected, + PageSize: len(options), + }, &selection, survey.WithIcons(func(icons *survey.IconSet) { + icons.UnmarkedOption.Text = "◯" + icons.MarkedOption.Text = "◉" + icons.SelectFocus.Text = "❯" + + icons.MarkedOption.Format = "green+b" + icons.SelectFocus.Format = "cyan+b" + })) + + if err != nil { + return nil, fmt.Errorf("multiselect got interrupted") + } + + return +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index c66dfde..a873f52 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -3,11 +3,9 @@ package utils import ( "fmt" "os" - "path/filepath" - "sort" - "github.com/AlecAivazis/survey/v2" - "github.com/spf13/viper" + "github.com/martinnirtl/dockma/internal/config" + "github.com/martinnirtl/dockma/internal/survey" "github.com/ttacon/chalk" ) @@ -37,31 +35,9 @@ func NoEnvs() { os.Exit(0) } -// GetFullLogfilePath returns the absolute path of the logfile location in dockma home dir -func GetFullLogfilePath(filename string) string { - path := viper.GetString("home") - - return filepath.Join(path, filename) -} - -// GetEnvironments returns configured environments -func GetEnvironments() (envs []string) { - envsMap := viper.GetStringMap("environments") - - envs = make([]string, 0, len(envsMap)) - - for env := range envsMap { - envs = append(envs, env) - } - - sort.Strings(envs) - - return -} - // GetEnvironment returns one environment func GetEnvironment(env string) string { - envs := GetEnvironments() + envs := config.GetEnvs() for _, envName := range envs { if env == envName { @@ -69,15 +45,12 @@ func GetEnvironment(env string) string { } } - survey.AskOne(&survey.Select{ - Message: "Choose an environment:", - Options: envs, - }, &env) + fmt.Printf("%sNo such environment: %s%s\n", chalk.Yellow, env, chalk.ResetColor) - if env == "" { - fmt.Printf("%sAborted.%s\n", chalk.Cyan, chalk.ResetColor) + env, err := survey.Select("Choose an environment", envs) - os.Exit(0) + if err != nil || env == "" { + Abort() } return env @@ -91,3 +64,14 @@ func Fallback(val string, fallback string) string { return val } + +// Includes checks if string slice includes string +func Includes(slice []string, s string) bool { + for _, val := range slice { + if val == s { + return true + } + } + + return false +} diff --git a/pkg/dockercompose/dockercompose.go b/pkg/dockercompose/dockercompose.go index 7cfd9a4..4bc511b 100644 --- a/pkg/dockercompose/dockercompose.go +++ b/pkg/dockercompose/dockercompose.go @@ -13,20 +13,20 @@ type Services struct { } func GetDockerCompose(filepath string, override bool) (*viper.Viper, error) { - configName := "docker-compose" + fileName := "docker-compose" if override { - configName = "docker-compose.override" + fileName = "docker-compose.override" } temp := viper.New() - temp.SetConfigName(configName) + temp.SetConfigName(fileName) temp.SetConfigType("yaml") temp.AddConfigPath(filepath) readError := temp.ReadInConfig() if readError != nil { - return nil, fmt.Errorf("could not read docker-compose?.override file") + return nil, fmt.Errorf("Could not read %s file", fileName) } return temp, nil diff --git a/pkg/dockercompose/helpers.go b/pkg/dockercompose/helpers.go index c16779c..a6fb71c 100644 --- a/pkg/dockercompose/helpers.go +++ b/pkg/dockercompose/helpers.go @@ -24,7 +24,6 @@ func mergeServiceSlices(primary []string, secondary []string) []string { for _, priService := range primary { if priService == secService { add = false - break } } @@ -34,5 +33,7 @@ func mergeServiceSlices(primary []string, secondary []string) []string { } } + sort.Strings(services) + return services } diff --git a/pkg/externalcommand/spinnertimebridger/spinner.go b/pkg/externalcommand/spinnertimebridger/spinner.go index e480cb7..c1368c9 100644 --- a/pkg/externalcommand/spinnertimebridger/spinner.go +++ b/pkg/externalcommand/spinnertimebridger/spinner.go @@ -1,7 +1,6 @@ package spinnertimebridger import ( - "fmt" "time" spinnerlib "github.com/briandowns/spinner" @@ -9,18 +8,18 @@ import ( // SpinnerTimebridger implements timebridger interface type SpinnerTimebridger struct { - command string - templateRunning string - templateFinished string - spinner *spinnerlib.Spinner + command string + messageRunning string + messageFinished string + spinner *spinnerlib.Spinner } // Start should get called after external command execution starts func (otb *SpinnerTimebridger) Start(command string) error { otb.command = command - if otb.templateRunning != "" { - otb.spinner.Suffix = " " + fmt.Sprintf(otb.templateRunning, command) + if otb.messageRunning != "" { + otb.spinner.Suffix = " " + otb.messageRunning } else { otb.spinner.Suffix = " " + command } @@ -34,8 +33,8 @@ func (otb *SpinnerTimebridger) Start(command string) error { func (otb *SpinnerTimebridger) Update(update string) error { otb.command = update - if otb.templateRunning != "" { - otb.spinner.Suffix = " " + fmt.Sprintf(otb.templateRunning, update) + if otb.messageRunning != "" { + otb.spinner.Suffix = " " + otb.messageRunning } else { otb.spinner.Suffix = " " + update } @@ -45,8 +44,8 @@ func (otb *SpinnerTimebridger) Update(update string) error { // Stop should get called after external command execution finishes func (otb *SpinnerTimebridger) Stop() error { - if otb.templateFinished != "" { - otb.spinner.FinalMSG = fmt.Sprintf(otb.templateFinished+"\n", otb.command) + if otb.messageFinished != "" { + otb.spinner.FinalMSG = otb.messageFinished + "\n" } otb.spinner.Stop() @@ -55,13 +54,13 @@ func (otb *SpinnerTimebridger) Stop() error { } // New creates a new OutputTimebidger -func New(templateRunning string, templateFinished string, spinner int, color string) *SpinnerTimebridger { +func New(messageRunning string, messageFinished string, spinner int, color string) *SpinnerTimebridger { s := spinnerlib.New(spinnerlib.CharSets[spinner], 100*time.Millisecond) s.Color(color, "bold") return &SpinnerTimebridger{ - templateRunning: templateRunning, - templateFinished: templateFinished, - spinner: s, + messageRunning: messageRunning, + messageFinished: messageFinished, + spinner: s, } }