From 7c3dfdc1ea5de4205e292f8a09cc55f15b62782b Mon Sep 17 00:00:00 2001 From: Giuseppe Maxia Date: Sat, 5 May 2018 22:44:16 +0200 Subject: [PATCH] Code refactoring and bug fixing - Merged pull request #11 from percona-csalguero/include_version_in_dir - Fixed Issue #12 "deploying a sandbox with invalid version does not fail" - Fixed minor bugs - Removed unnecessary parameter in CreateSingleSandbox - Added instructions to call CreateSingleSandbox from other apps See ./docs/coding - Minor code refactoring for Exit calls - Added mock sandbox creation for unit tests --- Changelog | 9 + README.md | 32 ++-- cmd/admin.go | 33 ++-- cmd/defaults.go | 16 +- cmd/delete.go | 79 +-------- cmd/global.go | 13 +- cmd/replication.go | 12 +- cmd/root.go | 7 +- cmd/single.go | 58 +------ cmd/templates.go | 51 +++--- cmd/tree.go | 21 +-- cmd/unpack.go | 24 ++- cmd/versions.go | 2 - common/checks.go | 18 +- common/fileutil.go | 9 +- common/strutils.go | 9 +- common/version.go | 4 +- defaults/catalog.go | 6 +- defaults/defaults.go | 25 +-- docs/coding/dbdeployer-as-library.md | 96 +++++++++++ docs/coding/minimal-sandbox.go | 43 +++++ mkreadme/make_readme.go | 6 +- mkreadme/readme_template.md | 4 + sandbox/group_replication.go | 2 +- sandbox/mock.go | 104 ++++++++++++ sandbox/multi-source-replication.go | 19 +-- sandbox/multiple.go | 9 +- sandbox/replication.go | 27 +-- sandbox/sandbox.go | 235 ++++++++++++++++----------- sandbox/sandbox_test.go | 57 +++++++ test/sort_versions.go | 3 +- unpack/unpack.go | 10 +- 32 files changed, 614 insertions(+), 429 deletions(-) create mode 100644 docs/coding/dbdeployer-as-library.md create mode 100644 docs/coding/minimal-sandbox.go create mode 100644 sandbox/mock.go create mode 100644 sandbox/sandbox_test.go diff --git a/Changelog b/Changelog index 2e849057..e8814b05 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,12 @@ +1.4.1 05-May-2018 + - Merged pull request #11 from percona-csalguero/include_version_in_dir + - Fixed Issue #12 "deploying a sandbox with invalid version does not fail" + - Fixed minor bugs + - Removed unnecessary parameter in CreateSingleSandbox + - Added instructions to call CreateSingleSandbox from other apps + See ./docs/coding + - Minor code refactoring for Exit calls + - Added mock sandbox creation for unit tests 1.4.0 28-Apr-2018 NEW FEATURES: - Added option --enable-mysqlx (MySQL 5.7.12+) diff --git a/README.md b/README.md index 95db8eb3..67cb76a3 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [DBdeployer](https://github.com/datacharmer/dbdeployer) is a tool that deploys MySQL database servers easily. This is a port of [MySQL-Sandbox](https://github.com/datacharmer/mysql-sandbox), originally written in Perl, and re-designed from the ground up in [Go](https://golang.org). See the [features comparison](https://github.com/datacharmer/dbdeployer/blob/master/docs/features.md) for more detail. -Documentation updated for version 1.4.0 (28-Apr-2018 12:56 UTC) +Documentation updated for version 1.4.1 (05-May-2018 20:43 UTC) ## Installation @@ -13,7 +13,7 @@ Get the one for your O.S. from [dbdeployer releases](https://github.com/datachar For example: - $ VERSION=1.4.0 + $ VERSION=1.4.1 $ origin=https://github.com/datacharmer/dbdeployer/releases/download/$VERSION $ wget $origin/dbdeployer-$VERSION.linux.tar.gz $ tar -xzf dbdeployer-$VERSION.linux.tar.gz @@ -47,7 +47,7 @@ For example: The program doesn't have any dependencies. Everything is included in the binary. Calling *dbdeployer* without arguments or with ``--help`` will show the main help screen. $ dbdeployer --version - dbdeployer version 1.4.0 + dbdeployer version 1.4.1 $ dbdeployer -h @@ -459,7 +459,7 @@ Here's how: 3306, 33060 ], - "timestamp": "Sat Apr 28 14:56:12 CEST 2018" + "timestamp": "Sat May 5 22:43:32 CEST 2018" } @@ -496,7 +496,7 @@ Here's how: 3306, 33060 ], - "timestamp": "Sat Apr 28 14:56:12 CEST 2018" + "timestamp": "Sat May 5 22:43:32 CEST 2018" } @@ -674,18 +674,18 @@ Should you need to compile your own binaries for dbdeployer, follow these steps: 2. Run ``go get github.com/datacharmer/dbdeployer``. This will import all the code that is needed to build dbdeployer. 3. Change directory to ``$GOPATH/src/github.com/datacharmer/dbdeployer``. 4. From the folder ``./pflag``, copy the file ``string_slice.go`` to ``$GOPATH/src/github.com/spf13/pflag``. -5. Run ``./build.sh {linux|OSX} 1.4.0`` -6. If you need the docs enabled binaries (see the section "Generating additional documentation") run ``MKDOCS=1 ./build.sh {linux|OSX} 1.4.0`` +5. Run ``./build.sh {linux|OSX} 1.4.1`` +6. If you need the docs enabled binaries (see the section "Generating additional documentation") run ``MKDOCS=1 ./build.sh {linux|OSX} 1.4.1`` ## Generating additional documentation -Between this file and [the API API list](https://github.com/datacharmer/dbdeployer/blob/master/docs/API-1.1.md), you have all the existing documentation for dbdeployer. +Between this file and [the API API list](https://github.com/datacharmer/dbdeployer/blob/master/docs/API/API-1.1.md), you have all the existing documentation for dbdeployer. Should you need additional formats, though, dbdeployer is able to generate them on-the-fly. Tou will need the docs-enabled binaries: in the distribution list, you will find: -* dbdeployer-1.4.0-docs.linux.tar.gz -* dbdeployer-1.4.0-docs.osx.tar.gz -* dbdeployer-1.4.0.linux.tar.gz -* dbdeployer-1.4.0.osx.tar.gz +* dbdeployer-1.4.1-docs.linux.tar.gz +* dbdeployer-1.4.1-docs.osx.tar.gz +* dbdeployer-1.4.1.linux.tar.gz +* dbdeployer-1.4.1.osx.tar.gz The executables containing ``-docs`` in their name have the same capabilities of the regular ones, but in addition they can run the *hidden* command ``tree``, with alias ``docs``. @@ -740,6 +740,10 @@ Then, you can use completion as follows: $ dbdeployer deploy single --b[tab][tab] --base-port= --bind-address= +## Using dbdeployer source for other projects + +If you need to create sandboxes from other Go apps, see [dbdeployer-as-a-library.md](https://github.com/datacharmer/dbdeployer/blob/master/docs/coding/dbdeployer-as-a-library.md). + ## Semantic versioning As of version 1.0.0, dbdeployer adheres to the principles of [semantic versioning](https://semver.org/). A version number is made of Major, Minor, and Revision. When changes are applied, the following happens: @@ -748,7 +752,7 @@ As of version 1.0.0, dbdeployer adheres to the principles of [semantic versionin * Backward-compatible new features increment the **Minor** number. * Backward incompatible changes (either features or bug fixes that break compatibility with the API) increment the **Major** number. -The starting API is defined in [API-1.0.md](https://github.com/datacharmer/dbdeployer/blob/master/docs/API-1.0.md) (generated manually.) -The file [API-1.1.md](https://github.com/datacharmer/dbdeployer/blob/master/docs/API-1.1.md) contains the same API definition, but was generated automatically and can be used to better compare the initial API with further version. +The starting API is defined in [API-1.0.md](https://github.com/datacharmer/dbdeployer/blob/master/docs/API/API-1.0.md) (generated manually.) +The file [API-1.1.md](https://github.com/datacharmer/dbdeployer/blob/master/docs/API/API-1.1.md) contains the same API definition, but was generated automatically and can be used to better compare the initial API with further version. diff --git a/cmd/admin.go b/cmd/admin.go index 69d39282..e2c63f17 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -26,8 +26,7 @@ import ( func UnpreserveSandbox(sandbox_dir, sandbox_name string) { full_path := sandbox_dir + "/" + sandbox_name if !common.DirExists(full_path) { - fmt.Printf("Directory '%s' not found\n", full_path) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Directory '%s' not found", full_path)) } preserve := full_path + "/no_clear_all" if !common.ExecExists(preserve) { @@ -44,8 +43,7 @@ func UnpreserveSandbox(sandbox_dir, sandbox_name string) { is_multiple = false } if !common.ExecExists(clear) { - fmt.Printf("Executable '%s' not found\n", clear) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Executable '%s' not found", clear)) } no_clear := full_path + "/no_clear" if is_multiple { @@ -53,13 +51,11 @@ func UnpreserveSandbox(sandbox_dir, sandbox_name string) { } err := os.Remove(clear) if err != nil { - fmt.Printf("Error while removing %s \n%s\n",clear, err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Error while removing %s \n%s",clear, err)) } err = os.Rename(no_clear, clear) if err != nil { - fmt.Printf("Error while renaming script\n%s\n", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Error while renaming script\n%s", err)) } fmt.Printf("Sandbox %s unlocked\n",sandbox_name) } @@ -69,8 +65,7 @@ func UnpreserveSandbox(sandbox_dir, sandbox_name string) { func PreserveSandbox(sandbox_dir, sandbox_name string) { full_path := sandbox_dir + "/" + sandbox_name if !common.DirExists(full_path) { - fmt.Printf("Directory '%s' not found\n", full_path) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Directory '%s' not found", full_path)) } preserve := full_path + "/no_clear_all" if !common.ExecExists(preserve) { @@ -87,8 +82,7 @@ func PreserveSandbox(sandbox_dir, sandbox_name string) { is_multiple = false } if !common.ExecExists(clear) { - fmt.Printf("Executable '%s' not found\n", clear) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Executable '%s' not found", clear)) } no_clear := full_path + "/no_clear" clear_cmd := "clear" @@ -100,8 +94,7 @@ func PreserveSandbox(sandbox_dir, sandbox_name string) { } err := os.Rename(clear, no_clear) if err != nil { - fmt.Printf("Error while renaming script.\n%s\n",err) - os.Exit(1) + common.Exit(1, fmt.Sprintf( "Error while renaming script.\n%s",err)) } template := sandbox.SingleTemplates["sb_locked_template"].Contents var data = common.Smap{ @@ -121,9 +114,9 @@ func PreserveSandbox(sandbox_dir, sandbox_name string) { func LockSandbox(cmd *cobra.Command, args []string) { if len(args) < 1 { - fmt.Printf("'lock' requires the name of a sandbox (or ALL)") - fmt.Printf("Example: dbdeployer admin lock msb_5_7_21") - os.Exit(1) + common.Exit(1, + "'lock' requires the name of a sandbox (or ALL)", + "Example: dbdeployer admin lock msb_5_7_21") } flags := cmd.Flags() sandbox := args[0] @@ -143,9 +136,9 @@ func LockSandbox(cmd *cobra.Command, args []string) { func UnlockSandbox(cmd *cobra.Command, args []string) { if len(args) < 1 { - fmt.Printf("'unlock' requires the name of a sandbox (or ALL)") - fmt.Printf("Example: dbdeployer admin unlock msb_5_7_21") - os.Exit(1) + common.Exit(1, + "'unlock' requires the name of a sandbox (or ALL)", + "Example: dbdeployer admin unlock msb_5_7_21") } flags := cmd.Flags() sandbox := args[0] diff --git a/cmd/defaults.go b/cmd/defaults.go index 1ad7700e..ec6e0b58 100644 --- a/cmd/defaults.go +++ b/cmd/defaults.go @@ -20,7 +20,6 @@ import ( "github.com/datacharmer/dbdeployer/common" "github.com/datacharmer/dbdeployer/defaults" "github.com/spf13/cobra" - "os" ) func ShowDefaults(cmd *cobra.Command, args []string) { @@ -38,8 +37,7 @@ func RemoveDefaults(cmd *cobra.Command, args []string) { func LoadDefaults(cmd *cobra.Command, args []string) { if len(args) < 1 { - fmt.Printf("'load' requires a file name\n") - os.Exit(1) + common.Exit(1,"'load' requires a file name") } filename := args[0] new_defaults := defaults.ReadDefaultsFile(filename) @@ -53,13 +51,11 @@ func LoadDefaults(cmd *cobra.Command, args []string) { func ExportDefaults(cmd *cobra.Command, args []string) { if len(args) < 1 { - fmt.Printf("'export' requires a file name\n") - os.Exit(1) + common.Exit(1,"'export' requires a file name") } filename := args[0] if common.FileExists(filename) { - fmt.Printf("File %s already exists. Will not overwrite\n", filename) - os.Exit(1) + common.Exit(1, fmt.Sprintf("File %s already exists. Will not overwrite", filename)) } defaults.WriteDefaultsFile(filename, defaults.Defaults()) fmt.Printf("# Defaults exported to file %s\n", filename) @@ -67,9 +63,9 @@ func ExportDefaults(cmd *cobra.Command, args []string) { func UpdateDefaults(cmd *cobra.Command, args []string) { if len(args) < 2 { - fmt.Printf("'update' requires a label and a value\n") - fmt.Printf("Example: dbdeployer defaults update master-slave-base-port 17500\n") - os.Exit(1) + common.Exit(1, + "'update' requires a label and a value", + "Example: dbdeployer defaults update master-slave-base-port 17500") } label := args[0] value := args[1] diff --git a/cmd/delete.go b/cmd/delete.go index 2c62b737..ef870d07 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -21,82 +21,22 @@ import ( "github.com/datacharmer/dbdeployer/common" "github.com/datacharmer/dbdeployer/concurrent" "github.com/datacharmer/dbdeployer/defaults" + "github.com/datacharmer/dbdeployer/sandbox" "github.com/spf13/cobra" "os" ) -func RemoveSandbox(sandbox_dir, sandbox string, run_concurrently bool) (exec_list []concurrent.ExecutionList) { - full_path := sandbox_dir + "/" + sandbox - if !common.DirExists(full_path) { - fmt.Printf("Directory '%s' not found\n", full_path) - os.Exit(1) - } - preserve := full_path + "/no_clear_all" - if !common.ExecExists(preserve) { - preserve = full_path + "/no_clear" - } - if common.ExecExists(preserve) { - fmt.Printf("The sandbox %s is locked\n",sandbox) - fmt.Printf("You need to unlock it with \"dbdeployer admin unlock\"\n",) - return - } - stop := full_path + "/stop_all" - if !common.ExecExists(stop) { - stop = full_path + "/stop" - } - if !common.ExecExists(stop) { - fmt.Printf("Executable '%s' not found\n", stop) - os.Exit(1) - } - if run_concurrently { - var eCommand1 = concurrent.ExecCommand{ - Cmd : stop, - Args : []string{}, - } - exec_list = append(exec_list, concurrent.ExecutionList{0, eCommand1}) - } else { - fmt.Printf("Running %s\n", stop) - err, _ := common.Run_cmd(stop) - if err != nil { - fmt.Printf("Error while stopping sandbox %s\n", full_path) - os.Exit(1) - } - } - - cmd_str := "rm" - rm_args := []string{"-rf", full_path} - if run_concurrently { - var eCommand2 = concurrent.ExecCommand{ - Cmd : cmd_str, - Args : rm_args, - } - exec_list = append(exec_list, concurrent.ExecutionList{1, eCommand2}) - } else { - for _, item := range rm_args { - cmd_str += " " + item - } - fmt.Printf("Running %s\n", cmd_str) - err, _ := common.Run_cmd_with_args("rm", rm_args) - if err != nil { - fmt.Printf("Error while deleting sandbox %s\n", full_path) - os.Exit(1) - } - fmt.Printf("Sandbox %s deleted\n", full_path) - } - // fmt.Printf("%#v\n",exec_list) - return -} func DeleteSandbox(cmd *cobra.Command, args []string) { var exec_lists []concurrent.ExecutionList if len(args) < 1 { - fmt.Println("Sandbox name (or \"ALL\") required.") - fmt.Println("You can run 'dbdeployer sandboxes for a list of available deployments'") - os.Exit(1) + common.Exit(1, + "Sandbox name (or \"ALL\") required.", + "You can run 'dbdeployer sandboxes for a list of available deployments'") } flags := cmd.Flags() - sandbox := args[0] + sandbox_name := args[0] confirm, _ := flags.GetBool("confirm") run_concurrently, _ := flags.GetBool("concurrent") if os.Getenv("RUN_CONCURRENTLY") != "" { @@ -104,8 +44,8 @@ func DeleteSandbox(cmd *cobra.Command, args []string) { } skip_confirm, _ := flags.GetBool("skip-confirm") sandbox_dir, _ := flags.GetString("sandbox-home") - deletion_list := []common.SandboxInfo{common.SandboxInfo{sandbox, false}} - if sandbox == "ALL" || sandbox == "all" { + deletion_list := []common.SandboxInfo{common.SandboxInfo{sandbox_name, false}} + if sandbox_name == "ALL" || sandbox_name == "all" { confirm = true if skip_confirm { confirm = false @@ -147,8 +87,7 @@ func DeleteSandbox(cmd *cobra.Command, args []string) { if answer == "y" || answer == "Y" { fmt.Println("Proceeding with deletion") } else { - fmt.Println("Execution interrupted by user") - os.Exit(0) + common.Exit(0, "Execution interrupted by user") } } } @@ -156,7 +95,7 @@ func DeleteSandbox(cmd *cobra.Command, args []string) { if sb.Locked { fmt.Printf("Sandbox %s is locked\n",sb.SandboxName) } else { - exec_list := RemoveSandbox(sandbox_dir, sb.SandboxName, run_concurrently) + exec_list := sandbox.RemoveSandbox(sandbox_dir, sb.SandboxName, run_concurrently) for _, list := range exec_list { exec_lists = append(exec_lists, list) } diff --git a/cmd/global.go b/cmd/global.go index ff088fba..538446fc 100644 --- a/cmd/global.go +++ b/cmd/global.go @@ -19,7 +19,6 @@ import ( "fmt" "github.com/datacharmer/dbdeployer/common" "github.com/spf13/cobra" - "os" ) func GlobalRunCommand(cmd *cobra.Command, executable string, args []string, require_args bool, skip_missing bool) { @@ -27,12 +26,10 @@ func GlobalRunCommand(cmd *cobra.Command, executable string, args []string, requ sandbox_dir, _ := flags.GetString("sandbox-home") run_list := common.SandboxInfoToFileNames(common.GetInstalledSandboxes(sandbox_dir)) if len(run_list) == 0 { - fmt.Printf("No sandboxes found in %s\n", sandbox_dir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("No sandboxes found in %s", sandbox_dir)) } if require_args && len(args) < 1 { - fmt.Printf("Arguments required for command %s\n", executable) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Arguments required for command %s", executable)) } for _, sb := range run_list { single_use := true @@ -49,8 +46,7 @@ func GlobalRunCommand(cmd *cobra.Command, executable string, args []string, requ fmt.Printf("# Sandbox %s: executable %s not found\n",full_dir_path, executable) continue } - fmt.Printf("No %s or %s found in %s\n", executable, executable+"_all", full_dir_path) - os.Exit(1) + common.Exit(1, fmt.Sprintf("No %s or %s found in %s", executable, executable+"_all", full_dir_path)) } var cmd_args []string @@ -68,8 +64,7 @@ func GlobalRunCommand(cmd *cobra.Command, executable string, args []string, requ err, _ = common.Run_cmd(cmd_file) } if err != nil { - fmt.Printf("Error while running %s\n", cmd_file) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Error while running %s\n", cmd_file)) } fmt.Println("") } diff --git a/cmd/replication.go b/cmd/replication.go index 8fccff33..ff4842cf 100644 --- a/cmd/replication.go +++ b/cmd/replication.go @@ -16,9 +16,6 @@ package cmd import ( - "fmt" - "os" - "github.com/datacharmer/dbdeployer/common" "github.com/datacharmer/dbdeployer/sandbox" "github.com/spf13/cobra" @@ -44,19 +41,16 @@ func ReplicationSandbox(cmd *cobra.Command, args []string) { } if semisync { if topology != "master-slave" { - fmt.Println("--semi-sync is only available with master/slave topology") - os.Exit(1) + common.Exit(1, "--semi-sync is only available with master/slave topology") } if common.GreaterOrEqualVersion(sd.Version, []int{5, 5, 1}) { sd.SemiSyncOptions = sandbox.SingleTemplates["semisync_master_options"].Contents } else { - fmt.Println("--semi-sync requires version 5.5.1+") - os.Exit(1) + common.Exit(1, "--semi-sync requires version 5.5.1+") } } if sd.SinglePrimary && topology != "group" { - fmt.Println("Option 'single-primary' can only be used with 'group' topology ") - os.Exit(1) + common.Exit(1, "Option 'single-primary' can only be used with 'group' topology ") } sandbox.CreateReplicationSandbox(sd, args[0], topology, nodes, master_ip, master_list, slave_list) } diff --git a/cmd/root.go b/cmd/root.go index 4b55f274..67fc425e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -41,8 +41,7 @@ Runs single, multiple, and replicated sandboxes.`, // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s",err)) } } @@ -68,8 +67,7 @@ func checkDefaultsFile() { if common.FileExists(defaults.CustomConfigurationFile) { defaults.ConfigurationFile = defaults.CustomConfigurationFile } else { - fmt.Printf("*** File %s not found\n", defaults.CustomConfigurationFile) - os.Exit(1) + common.Exit(1, fmt.Sprintf("*** File %s not found", defaults.CustomConfigurationFile)) } } defaults.LoadConfiguration() @@ -84,4 +82,5 @@ func init() { set_pflag(rootCmd,"sandbox-binary", "", "SANDBOX_BINARY", defaults.Defaults().SandboxBinary, "Binary repository", false) rootCmd.InitDefaultVersionFlag() + defaults.UsingDbDeployer = true } diff --git a/cmd/single.go b/cmd/single.go index 89f98093..348019a7 100644 --- a/cmd/single.go +++ b/cmd/single.go @@ -31,14 +31,12 @@ import ( func replace_template(template_name string, file_name string) { group, _, contents := FindTemplate(template_name) if !common.FileExists(file_name) { - fmt.Printf("File %s not found\n", file_name) - os.Exit(1) + common.Exit(1, fmt.Sprintf("File %s not found\n", file_name)) } fmt.Printf("Replacing template %s.%s [%d chars] with contents of file %s\n", group, template_name, len(contents), file_name) new_contents := common.SlurpAsString(file_name) if len(new_contents) == 0 { - fmt.Printf("File %s is empty\n", file_name) - os.Exit(1) + common.Exit(1, fmt.Sprintf("File %s is empty\n", file_name)) } var new_rec sandbox.TemplateDesc = sandbox.TemplateDesc{ Description: sandbox.AllTemplates[group][template_name].Description, @@ -52,9 +50,7 @@ func check_template_change_request(request string) (template_name, file_name str re := regexp.MustCompile(`(\w+):(\S+)`) reqList := re.FindAllStringSubmatch(request, -1) if len(reqList) == 0 { - //fmt.Printf("%v\n", reqList) - fmt.Printf("request '%s' invalid. Required format is 'template_name:file_name'\n", request) - os.Exit(1) + common.Exit(1, fmt.Sprintf("request '%s' invalid. Required format is 'template_name:file_name'", request)) } template_name = reqList[0][1] file_name = reqList[0][2] @@ -82,7 +78,9 @@ func FillSdef(cmd *cobra.Command, args []string) sandbox.SandboxDef { replace_template(tname, fname) } sd.Port = common.VersionToPort(args[0]) - + if sd.Port < 0 { + common.Exit(1, fmt.Sprintf("Unsupported version format (%s)",args[0])) + } sd.UserPort, _ = flags.GetInt("port") sd.BasePort, _ = flags.GetInt("base-port") sd.DirName, _ = flags.GetString("sandbox-directory") @@ -131,8 +129,7 @@ func FillSdef(cmd *cobra.Command, args []string) sandbox.SandboxDef { sd.EnableGeneralLog, _ = flags.GetBool("enable-general-log") if sd.DisableMysqlX && sd.EnableMysqlX { - fmt.Printf("flags --enable-mysqlx and --disable-mysqlx cannot be used together\n") - os.Exit(1) + common.Exit(1, "flags --enable-mysqlx and --disable-mysqlx cannot be used together") } sd.RunConcurrently, _ = flags.GetBool("concurrent") if os.Getenv("RUN_CONCURRENTLY") != "" { @@ -156,8 +153,7 @@ func FillSdef(cmd *cobra.Command, args []string) sandbox.SandboxDef { sd.ReplOptions = sandbox.SingleTemplates["replication_options"].Contents sd.ServerId = sd.Port } else { - fmt.Println("--gtid requires version 5.6.9+") - os.Exit(1) + common.Exit(1, "--gtid requires version 5.6.9+") } } return sd @@ -169,18 +165,9 @@ func SingleSandbox(cmd *cobra.Command, args []string) { sd = FillSdef(cmd, args) // When deploying a single sandbox, we disable concurrency sd.RunConcurrently = false - sandbox.CreateSingleSandbox(sd, args[0]) + sandbox.CreateSingleSandbox(sd) } -/* -func ReplacedCmd(cmd *cobra.Command, args []string) { - invoked := cmd.Use - fmt.Printf("The command \"%s\" has been replaced.\n",invoked) - fmt.Printf("Use \"dbdeployer deploy %s\" instead.\n",invoked) - os.Exit(0) -} -*/ - var singleCmd = &cobra.Command{ Use: "single MySQL-Version", // Args: cobra.ExactArgs(1), @@ -199,34 +186,7 @@ Use the "unpack" command to get the tarball into the right directory. Run: SingleSandbox, } -/* -var ( - hiddenSingleCmd = &cobra.Command{ - Use: "single", - Short: "REMOVED: use 'deploy single' instead", - Hidden: true, - Run: ReplacedCmd, - } - hiddenReplicationCmd = &cobra.Command{ - Use: "replication", - Short: "REMOVED: use 'deploy replication' instead", - Hidden: true, - Run: ReplacedCmd, - } - - hiddenMultipleCmd = &cobra.Command{ - Use: "multiple", - Short: "REMOVED: use 'deploy multiple' instead", - Hidden: true, - Run: ReplacedCmd, - } -) -*/ - func init() { - //rootCmd.AddCommand(hiddenSingleCmd) - //rootCmd.AddCommand(hiddenReplicationCmd) - //rootCmd.AddCommand(hiddenMultipleCmd) deployCmd.AddCommand(singleCmd) singleCmd.PersistentFlags().Bool("master", false, "Make the server replication ready") diff --git a/cmd/templates.go b/cmd/templates.go index 3f547d2e..1adf0d13 100644 --- a/cmd/templates.go +++ b/cmd/templates.go @@ -43,15 +43,13 @@ func FindTemplate(requested string) (group, template_name, contents string) { } } } - fmt.Printf("template '%s' not found\n", requested) - os.Exit(1) + common.Exit(1, fmt.Sprintf("template '%s' not found", requested)) return } func ShowTemplate(cmd *cobra.Command, args []string) { if len(args) < 1 { - fmt.Println("Argument required: template name") - os.Exit(1) + common.Exit(1, "Argument required: template name") } requested := args[0] _, _, contents := FindTemplate(requested) @@ -81,8 +79,7 @@ func GetTemplatesList(wanted string) (tlist []TemplateInfo) { } } if !found { - fmt.Printf("group %s not found\n", wanted) - os.Exit(1) + common.Exit(1, fmt.Sprintf("group %s not found\n", wanted)) } return } @@ -111,8 +108,7 @@ func ListTemplates(cmd *cobra.Command, args []string) { func RunDescribeTemplate(cmd *cobra.Command, args []string) { if len(args) < 1 { - fmt.Println("Argument required: template name") - os.Exit(1) + common.Exit(1, "Argument required: template name") } requested := args[0] flags := cmd.Flags() @@ -146,9 +142,9 @@ func DescribeTemplate(requested string, complete_listing bool) { func ExportTemplates(cmd *cobra.Command, args []string) { if len(args) < 2 { - fmt.Println("The export command requires two arguments: group_name and directory_name") - fmt.Println("If group_name is 'all', it will export all groups") - os.Exit(1) + common.Exit(1, + "The export command requires two arguments: group_name and directory_name", + "If group_name is 'all', it will export all groups") } wanted := args[0] dir_name := args[1] @@ -160,8 +156,7 @@ func ExportTemplates(cmd *cobra.Command, args []string) { wanted = "" } if common.DirExists(dir_name) { - fmt.Printf("# Directory <%s> already exists\n", dir_name) - os.Exit(1) + common.Exit(1, fmt.Sprintf("# Directory <%s> already exists", dir_name)) } common.Mkdir(dir_name) common.WriteString(common.VersionDef, dir_name + "/version.txt") @@ -186,12 +181,10 @@ func ExportTemplates(cmd *cobra.Command, args []string) { } } if !found_group { - fmt.Printf("Group %s not found\n", wanted) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Group %s not found", wanted)) } if !found_template { - fmt.Printf("template %s not found\n", template_name) - os.Exit(1) + common.Exit(1, fmt.Sprintf("template %s not found", template_name)) } fmt.Printf("Exported to %s\n", dir_name) } @@ -224,9 +217,9 @@ func LoadTemplates() { func ImportTemplates(cmd *cobra.Command, args []string) { if len(args) < 2 { - fmt.Println("The import command requires two arguments: group_name and dir_name") - fmt.Println("If group_name is 'all', it will import all groups") - os.Exit(1) + common.Exit(1, + "The import command requires two arguments: group_name and dir_name", + "If group_name is 'all', it will import all groups") } wanted := args[0] if wanted == "all" || wanted == "ALL" { @@ -234,8 +227,7 @@ func ImportTemplates(cmd *cobra.Command, args []string) { } dir_name := args[1] if !common.DirExists(dir_name) { - fmt.Printf("# Directory <%s> doesn't exist\n", dir_name) - os.Exit(1) + common.Exit(1, fmt.Sprintf("# Directory <%s> doesn't exist", dir_name)) } template_name := "" if len(args) > 2 { @@ -243,20 +235,17 @@ func ImportTemplates(cmd *cobra.Command, args []string) { } version_file := dir_name + "/version.txt" if !common.FileExists(version_file) { - fmt.Printf("File %s not found. Unable to validate templates.\n",version_file) - os.Exit(1) + common.Exit(1, fmt.Sprintf("File %s not found. Unable to validate templates.",version_file)) } template_version := strings.TrimSpace(common.SlurpAsString(version_file)) version_list := common.VersionToList(template_version) // fmt.Printf("%v\n",version_list) compatible_version_list := common.VersionToList(common.CompatibleVersion) if version_list[0] < 0 { - fmt.Printf("Invalid version (%s) found in %s\n",template_version, version_file) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Invalid version (%s) found in %s",template_version, version_file)) } if !common.GreaterOrEqualVersion( template_version, compatible_version_list) { - fmt.Printf("Templates are for version %s. The minimum compatible version is %s\n", template_version, common.CompatibleVersion) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Templates are for version %s. The minimum compatible version is %s", template_version, common.CompatibleVersion)) } found_group := false found_template := false @@ -300,12 +289,10 @@ func ImportTemplates(cmd *cobra.Command, args []string) { } } if !found_group { - fmt.Printf("Group %s not found\n", wanted) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Group %s not found", wanted)) } if !found_template { - fmt.Printf("template %s not found\n", template_name) - os.Exit(1) + common.Exit(1, fmt.Sprintf("template %s not found", template_name)) } } diff --git a/cmd/tree.go b/cmd/tree.go index 5943d403..a05d0d66 100644 --- a/cmd/tree.go +++ b/cmd/tree.go @@ -47,8 +47,7 @@ func WriteBashCompletion() { func WriteManPages() { man_dir := "man_pages" if common.DirExists(man_dir) { - fmt.Printf("manual pages directory '%s' exists already.\n",man_dir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("manual pages directory '%s' exists already.",man_dir)) } common.Mkdir(man_dir) header := &doc.GenManHeader{ @@ -57,8 +56,7 @@ func WriteManPages() { } err := doc.GenManTree(rootCmd, header, man_dir) if err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s", err)) } fmt.Printf("Man pages generated in '%s'\n", man_dir) } @@ -66,14 +64,12 @@ func WriteManPages() { func WriteMarkdownPages() { md_dir := "markdown_pages" if common.DirExists(md_dir) { - fmt.Printf("Markdown pages directory '%s' exists already.\n",md_dir) - os.Exit(1) + common.Exit(fmt.Sprintf("Markdown pages directory '%s' exists already.",md_dir)) } common.Mkdir(md_dir) err := doc.GenMarkdownTree(rootCmd, md_dir) if err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s", err)) } err = doc.GenReSTTree(rootCmd, md_dir) fmt.Printf("Markdown pages generated in '%s'\n", md_dir) @@ -82,14 +78,12 @@ func WriteMarkdownPages() { func WriteRstPages() { rst_dir := "rst_pages" if common.DirExists(rst_dir) { - fmt.Printf("Restructured Text pages directory '%s' exists already.\n",rst_dir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Restructured Text pages directory '%s' exists already.",rst_dir)) } common.Mkdir(rst_dir) err := doc.GenReSTTree(rootCmd, rst_dir) if err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s", err)) } fmt.Printf("Restructured Text pages generated in '%s'\n", rst_dir) } @@ -103,8 +97,7 @@ func MakeDocumentation(cmd *cobra.Command, args []string) { md_pages, _ := flags.GetBool("markdown-pages") rst_pages, _ := flags.GetBool("rst-pages") if (man_pages && api) || (api && bash_completion) || (api && md_pages) || (api && rst_pages) { - fmt.Printf("Choose one option only\n") - os.Exit(1) + common,Exit(1, "Choose one option only") } if rst_pages { WriteRstPages() diff --git a/cmd/unpack.go b/cmd/unpack.go index 80752ea3..cb543acf 100644 --- a/cmd/unpack.go +++ b/cmd/unpack.go @@ -32,9 +32,9 @@ func UnpackTarball(cmd *cobra.Command, args []string) { Basedir, _ := flags.GetString("sandbox-binary") verbosity, _ := flags.GetInt("verbosity") if !common.DirExists(Basedir) { - fmt.Printf("Directory %s does not exist.\n", Basedir) - fmt.Println("You should create it or provide an alternate base directory using --sandbox-binary") - os.Exit(1) + common.Exit(1, + fmt.Sprintf("Directory %s does not exist.", Basedir), + "You should create it or provide an alternate base directory using --sandbox-binary") } tarball := args[0] re_version := regexp.MustCompile(`(\d+\.\d+\.\d+)`) @@ -47,9 +47,9 @@ func UnpackTarball(cmd *cobra.Command, args []string) { Version = detected_version } if Version == "" { - fmt.Println("unpack: No version was detected from tarball name. ") - fmt.Println("Flag --unpack-version becomes mandatory") - os.Exit(1) + common.Exit(1, + "unpack: No version was detected from tarball name. ", + "Flag --unpack-version becomes mandatory") } // This call used to ensure that the port provided is in the right format common.VersionToPort(Version) @@ -57,8 +57,7 @@ func UnpackTarball(cmd *cobra.Command, args []string) { destination := Basedir + "/" + Prefix + Version if common.DirExists(destination) { - fmt.Printf("Destination directory %s exists already\n", destination) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Destination directory %s exists already\n", destination)) } var extension string = ".tar.gz" extracted := path.Base(tarball) @@ -66,23 +65,20 @@ func UnpackTarball(cmd *cobra.Command, args []string) { if strings.HasSuffix(tarball, extension) { barename = extracted[0 : len(extracted)-len(extension)] } else { - fmt.Println("Tarball extension must be .tar.gz") - os.Exit(1) + common.Exit(1, "Tarball extension must be .tar.gz") } fmt.Printf("Unpacking tarball %s to %s\n", tarball, common.ReplaceLiteralHome(destination)) //verbosity_level := unpack.VERBOSE err := unpack.UnpackTar(tarball, Basedir, verbosity) if err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s", err)) } final_name := Basedir + "/" + barename if final_name != destination { err = os.Rename(final_name, destination) if err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s", err)) } } } diff --git a/cmd/versions.go b/cmd/versions.go index cb34f747..379a71f7 100644 --- a/cmd/versions.go +++ b/cmd/versions.go @@ -36,8 +36,6 @@ func ShowVersions(cmd *cobra.Command, args []string) { var dirs []string for _, f := range files { fname := f.Name() - //fmt.Printf("%#v\n", f) - //os.Exit(0) fmode := f.Mode() //fmt.Printf("%#v\n", fmode) if fmode.IsDir() { diff --git a/common/checks.go b/common/checks.go index fba1abcb..d8a34f69 100644 --- a/common/checks.go +++ b/common/checks.go @@ -42,8 +42,7 @@ func GetInstalledSandboxes(sandbox_home string) (installed_sandboxes []SandboxIn } files, err := ioutil.ReadDir(sandbox_home) if err != nil { - fmt.Printf("%s", err) - os.Exit(1) + Exit(1, fmt.Sprintf("%s", err)) } for _, f := range files { fname := f.Name() @@ -111,18 +110,16 @@ func GetInstalledPorts(sandbox_home string) []int { func CheckOrigin(args []string) { if len(args) < 1 { - fmt.Println("This command requires the MySQL version (x.xx.xx) as argument ") - os.Exit(1) + Exit(1, "This command requires the MySQL version (x.xx.xx) as argument ") } if len(args) > 1 { - fmt.Println("Extra argument detected. This command requires only the MySQL version (x.xx.xx) as argument ") - os.Exit(1) + Exit(1, "Extra argument detected. This command requires only the MySQL version (x.xx.xx) as argument ") } origin := args[0] if FileExists(origin) && strings.HasSuffix(origin, ".tar.gz") { - fmt.Println("Tarball detected. - If you want to use a tarball to create a sandbox,") - fmt.Println("you should first use the 'unpack' command") - os.Exit(1) + Exit(1, + "Tarball detected. - If you want to use a tarball to create a sandbox,", + "you should first use the 'unpack' command") } } @@ -132,8 +129,7 @@ func CheckSandboxDir(sandbox_home string) { fmt.Printf("Creating directory %s\n", sandbox_home) err := os.Mkdir(sandbox_home, 0755) if err != nil { - fmt.Println(err) - os.Exit(1) + Exit(1, fmt.Sprintf("%s", err)) } } diff --git a/common/fileutil.go b/common/fileutil.go index 4f5df3ba..252e6eed 100644 --- a/common/fileutil.go +++ b/common/fileutil.go @@ -96,8 +96,7 @@ func WriteSandboxDescription(destination string, sd SandboxDescription) { sd.Timestamp = time.Now().Format(time.UnixDate) b, err := json.MarshalIndent(sd, " ", "\t") if err != nil { - fmt.Println("error encoding sandbox description: ", err) - os.Exit(1) + Exit(1, fmt.Sprintf("error encoding sandbox description: %s", err)) } json_string := fmt.Sprintf("%s", b) filename := destination + "/sbdescription.json" @@ -110,8 +109,7 @@ func ReadSandboxDescription(sandbox_directory string) (sd SandboxDescription) { err := json.Unmarshal(sb_blob, &sd) if err != nil { - fmt.Println("error decoding sandbox description: ", err) - os.Exit(1) + Exit(1, fmt.Sprintf("error decoding sandbox description: %s", err)) } return } @@ -129,8 +127,7 @@ func SlurpAsLines(filename string) []string { lines = append(lines, scanner.Text()) } if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + Exit(1, fmt.Sprintf("%s", err)) } return lines } diff --git a/common/strutils.go b/common/strutils.go index d6035803..13cad9c0 100644 --- a/common/strutils.go +++ b/common/strutils.go @@ -130,8 +130,7 @@ func LatestVersion(versions []string ) string { func Atoi(val string) int { numvalue, err := strconv.Atoi(val) if err != nil { - fmt.Printf("Not a valid number: %s (%s)\n", val, err) - os.Exit(1) + Exit(1, fmt.Sprintf("Not a valid number: %s (%s)", val, err)) } return numvalue } @@ -145,4 +144,10 @@ func StringToIntSlice(val string) (num_list []int) { return num_list } +func Exit(exit_code int, messages ...string) { + for _, msg := range messages { + fmt.Printf("%s\n",msg) + } + os.Exit(exit_code) +} diff --git a/common/version.go b/common/version.go index c4775c10..5200382a 100644 --- a/common/version.go +++ b/common/version.go @@ -15,9 +15,9 @@ package common -var VersionDef string = "1.4.0" // 2018-04-22 +var VersionDef string = "1.4.1" // 2018-05-05 // Compatible version is the version used to mark compatible archives (templates, configuration). // It is usually major.minor.0, except when we are at version 0.x, when // every revision may bring incompatibility -var CompatibleVersion string = "1.4.0" // 2018-04-22 +var CompatibleVersion string = "1.4.0" // 2018-05-05 diff --git a/defaults/catalog.go b/defaults/catalog.go index e16cfb0d..36d010d0 100644 --- a/defaults/catalog.go +++ b/defaults/catalog.go @@ -78,8 +78,7 @@ func WriteCatalog(sc SandboxCatalog) { } b, err := json.MarshalIndent(sc, " ", "\t") if err != nil { - fmt.Println("error encoding sandbox catalog: ", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("error encoding sandbox catalog: %s", err)) } json_string := fmt.Sprintf("%s", b) filename := SandboxRegistry @@ -98,8 +97,7 @@ func ReadCatalog() (sc SandboxCatalog) { err := json.Unmarshal(sc_blob, &sc) if err != nil { - fmt.Println("error decoding sandbox catalog: ", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("error decoding sandbox catalog: %s", err)) } return } diff --git a/defaults/defaults.go b/defaults/defaults.go index 9bb0d641..ae1559a8 100644 --- a/defaults/defaults.go +++ b/defaults/defaults.go @@ -77,6 +77,7 @@ var ( StarLine string = strings.Repeat("*", LineLength) DashLine string = strings.Repeat("-", LineLength) HashLine string = strings.Repeat("#", LineLength) + UsingDbDeployer bool = false factoryDefaults = DbdeployerDefaults{ Version: common.CompatibleVersion, @@ -137,8 +138,7 @@ func ShowDefaults(defaults DbdeployerDefaults) { } b, err := json.MarshalIndent(defaults, " ", "\t") if err != nil { - fmt.Println("error encoding defaults: ", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("error encoding defaults: %s", err)) } fmt.Printf("%s\n", b) } @@ -151,8 +151,7 @@ func WriteDefaultsFile(filename string, defaults DbdeployerDefaults) { } b, err := json.MarshalIndent(defaults, " ", "\t") if err != nil { - fmt.Println("error encoding defaults: ", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("error encoding defaults: %s", err)) } json_string := fmt.Sprintf("%s", b) common.WriteString(json_string, filename) @@ -179,8 +178,7 @@ func ReadDefaultsFile(filename string) (defaults DbdeployerDefaults) { err := json.Unmarshal(defaults_blob, &defaults) if err != nil { - fmt.Println("error decoding defaults: ", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("error decoding defaults: %s", err)) } defaults = expand_environment_variables(defaults) return @@ -264,21 +262,18 @@ func RemoveDefaultsFile() { if common.FileExists(ConfigurationFile) { err := os.Remove(ConfigurationFile) if err != nil { - fmt.Printf("%s\n", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("%s", err)) } fmt.Printf("#File %s removed\n", ConfigurationFile) } else { - fmt.Printf("Configuration file %s not found\n", ConfigurationFile) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Configuration file %s not found", ConfigurationFile)) } } func a_to_i(val string) int { numvalue, err := strconv.Atoi(val) if err != nil { - fmt.Printf("Not a valid number: %s\n", val) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Not a valid number: %s", val)) } return numvalue } @@ -366,8 +361,7 @@ func UpdateDefaults(label, value string, store_defaults bool) { // case "ndb-prefix": // new_defaults.NdbPrefix = value default: - fmt.Printf("Unrecognized label %s\n", label) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Unrecognized label %s", label)) } if ValidateDefaults(new_defaults) { currentDefaults = new_defaults @@ -376,8 +370,7 @@ func UpdateDefaults(label, value string, store_defaults bool) { fmt.Printf("# Updated %s -> \"%s\"\n", label, value) } } else { - fmt.Printf("Invalid defaults data %s : %s\n", label, value) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Invalid defaults data %s : %s", label, value)) } } diff --git a/docs/coding/dbdeployer-as-library.md b/docs/coding/dbdeployer-as-library.md new file mode 100644 index 00000000..cc551ed4 --- /dev/null +++ b/docs/coding/dbdeployer-as-library.md @@ -0,0 +1,96 @@ +# Using dbdeployer code from other applications + +If you want to create a MySQL sandbox from your application, you need to fill in a structure +``sandbox.SandboxDef``, with at least the following fields: + +```go + +import "github.com/datacharmer/dbdeployer/sandbox" + +var sdef = sandbox.SandboxDef{ + Version: "5.7.22", + Basedir: os.Getenv("HOME") + "/opt/mysql/5.7.22", + SandboxDir: os.Getenv("HOME") + "/sandboxes", + DirName: "msb_5_7_22", + LoadGrants: true, + InstalledPorts: []int{1186, 3306, 33060}, + Port: 5722, + DbUser: "msandbox", + RplUser: "rsandbox", + DbPassword: "msandbox", + RplPassword: "rsandbox", + RemoteAccess: "127.%", + BindAddress: "127.0.0.1", +} +``` + +This is the full structure of SandboxDef: + +```go +type SandboxDef struct { + DirName string // name of the directory cointaining the sandbox + SBType string // Type of sandbox (single, multiple, replication-node, group-node) + Multi bool // either single or part of a multiple sandbox + NodeNum int // in multiple sandboxes, which node is this + Version string // MySQL version + Basedir string // Where to get binaries from + SandboxDir string // Target directory for sandboxes + LoadGrants bool // Should we load grants? + SkipReportHost bool // Do not add report-host to my.sandbox.cnf + SkipReportPort bool // Do not add report-port to my.sandbox.cnf + SkipStart bool // Do not start the server after deployment + InstalledPorts []int // Which ports should be skipped in port assignment for this SB + Port int // port assigned to this sandbox + MysqlXPort int // XPlugin port for thsi sandbox + UserPort int // + BasePort int // Base port for calculating more ports in multiple SB + MorePorts []int // Additional ports that belong to thos sandbox + Prompt string // Prompt to use in "mysql" client + DbUser string // Database user name + RplUser string // Replication user name + DbPassword string // Database password + RplPassword string // Replication password + RemoteAccess string // What access have the users created for this SB (127.%) + BindAddress string // Bind address for this sandbox (127.0.0.1) + CustomMysqld string // Use an alternative mysqld executable + ServerId int // Server ID (for replication) + ReplOptions string // Replication options, as string to append to my.sandbox.cnf + GtidOptions string // Options needed for GTID + SemiSyncOptions string // Options for semi-synchronous replication + InitOptions []string // Options to be added to the initialization command + MyCnfOptions []string // Options to be added to my.sandbox.cnf + PreGrantsSql []string // SQL statements to execute before grants assignment + PreGrantsSqlFile string // SQL file to load before grants assignment + PostGrantsSql []string // SQL statements to run after grants assignment + PostGrantsSqlFile string // SQL file to load after grants assignment + MyCnfFile string // options file to merge with the SB my.sandbox.cnf + InitGeneralLog bool // enable general log during server initialization + EnableGeneralLog bool // enable general log after initialization + NativeAuthPlugin bool // Use the native password plugin for MySQL 8.0.4+ + DisableMysqlX bool // Disable Xplugin (MySQL 8.0.11+) + EnableMysqlX bool // Enable Xplugin (MySQL 5.7.12+) + KeepUuid bool // Do not change UUID + SinglePrimary bool // Use single primary for group replication + Force bool // Overwrite an existing sandbox with same target + ExposeDdTables bool // Show hidden data dictionary tables (MySQL 8.0.0+) + RunConcurrently bool // Run multiple sandbox creation concurrently +} +``` + +Then you can call the function ``sandbox.CreateSingleSandbox(sdef)``. + +This will create a fully functional single sandbox that you can then use like any other created by dbdeployer. + +To remove a sandbox, you need two steps: + +``` go + sandbox.RemoveSandbox(sandbox_home, "msb_5_7_22", false) + defaults.DeleteFromCatalog(sandbox_home+"/msb_5_7_22") +``` + +See the sample source file ``minimal-sandbox.go`` for a working example. + +If you want to create multiple sandboxes, things are a bit more complicated. ``dbdeployer`` uses a concurrent execution engine that needs to be used with care. + +Look at the invocation of the replication command in ``cmd/replication.go`` for an example of how to prepare the SandboxDef structure before calling the relevant function. + diff --git a/docs/coding/minimal-sandbox.go b/docs/coding/minimal-sandbox.go new file mode 100644 index 00000000..13179645 --- /dev/null +++ b/docs/coding/minimal-sandbox.go @@ -0,0 +1,43 @@ +package main + +import ( + "os" + "github.com/datacharmer/dbdeployer/sandbox" + "github.com/datacharmer/dbdeployer/common" + "github.com/datacharmer/dbdeployer/defaults" +) + +func main() { + version := "5.7.22" + sandbox_home := os.Getenv("HOME") + "/sandboxes" + sandbox_binary := os.Getenv("HOME") + "/opt/mysql" + basedir := sandbox_binary + "/" + version + port := 5722 + user := "msandbox" + password := "psandbox" + + if !common.DirExists(sandbox_home) { + common.Mkdir(sandbox_home) + } + + var sdef = sandbox.SandboxDef{ + Version: version, + Basedir: basedir, + SandboxDir: sandbox_home, + DirName: "msb_5_7_22", + LoadGrants:true, + InstalledPorts:[]int{1186, 3306, 33060}, + Port: port, + DbUser: user, + DbPassword: password, + RplUser: "r" + user, + RplPassword: "r" + password, + RemoteAccess:"127.%", + BindAddress:"127.0.0.1", + } + + sandbox.CreateSingleSandbox(sdef) + common.Run_cmd(sandbox_home + "/msb_5_7_22/test_sb") + sandbox.RemoveSandbox(sandbox_home, "msb_5_7_22", false) + defaults.DeleteFromCatalog(sandbox_home+"/msb_5_7_22") +} diff --git a/mkreadme/make_readme.go b/mkreadme/make_readme.go index 558e06cd..2de2824c 100644 --- a/mkreadme/make_readme.go +++ b/mkreadme/make_readme.go @@ -39,8 +39,7 @@ func get_cmd_output(cmdText string) string { cmd := exec.Command(command, args...) stdout, err := cmd.StdoutPipe() if err = cmd.Start(); err != nil { - fmt.Println(err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("# ERROR: %s",err)) } slurp, _ := ioutil.ReadAll(stdout) stdout.Close() @@ -98,7 +97,6 @@ func main() { } if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "error:", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf("# ERROR: %s",err)) } } diff --git a/mkreadme/readme_template.md b/mkreadme/readme_template.md index 2deaf763..78931689 100644 --- a/mkreadme/readme_template.md +++ b/mkreadme/readme_template.md @@ -348,6 +348,10 @@ Then, you can use completion as follows: $ dbdeployer deploy single --b[tab][tab] --base-port= --bind-address= +## Using dbdeployer source for other projects + +If you need to create sandboxes from other Go apps, see [dbdeployer-as-a-library.md](https://github.com/datacharmer/dbdeployer/blob/master/docs/coding/dbdeployer-as-a-library.md). + ## Semantic versioning As of version 1.0.0, dbdeployer adheres to the principles of [semantic versioning](https://semver.org/). A version number is made of Major, Minor, and Revision. When changes are applied, the following happens: diff --git a/sandbox/group_replication.go b/sandbox/group_replication.go index 544a4eca..a4a87191 100644 --- a/sandbox/group_replication.go +++ b/sandbox/group_replication.go @@ -222,7 +222,7 @@ func CreateGroupReplication(sdef SandboxDef, origin string, nodes int, master_ip sdef.SBType = "group-node" sdef.NodeNum = i // fmt.Printf("%#v\n",sdef) - exec_list := CreateSingleSandbox(sdef, origin) + exec_list := CreateSingleSandbox(sdef) for _, list := range exec_list { exec_lists = append(exec_lists, list) } diff --git a/sandbox/mock.go b/sandbox/mock.go new file mode 100644 index 00000000..c24456fe --- /dev/null +++ b/sandbox/mock.go @@ -0,0 +1,104 @@ +// DBDeployer - The MySQL Sandbox +// Copyright © 2006-2018 Giuseppe Maxia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sandbox + +import ( + "fmt" + "os" + + "github.com/datacharmer/dbdeployer/common" + "github.com/datacharmer/dbdeployer/defaults" +) + +// Code in this module creates a fake directory structure that allows +// the testing of sandboxes without having MySQL packages. + +const default_mock_dir string = "mock_dir" +var ( + save_home string + save_sandbox_home string + save_sandbox_binary string + save_sleep_time string + sandbox_binary string + sandbox_home string +) + +func set_mock_environment(mock_upper_dir string) { + if mock_upper_dir == "" { + mock_upper_dir = default_mock_dir + } + if common.DirExists(mock_upper_dir) { + common.Exit(1, fmt.Sprintf("Mock directory %s already exists. Aborting",mock_upper_dir)) + } + PWD := os.Getenv("PWD") + home := fmt.Sprintf("%s/%s/home", PWD, mock_upper_dir) + sandbox_binary_upper := fmt.Sprintf("%s/opt", home) + sandbox_binary = fmt.Sprintf("%s/opt/mysql", home) + sandbox_home = fmt.Sprintf("%s/sandboxes", home) + common.Mkdir(mock_upper_dir) + common.Mkdir(home) + common.Mkdir(sandbox_binary_upper) + common.Mkdir(sandbox_binary) + common.Mkdir(sandbox_home) + os.Setenv("HOME", home) + os.Setenv("SANDBOX_HOME", sandbox_home) + os.Setenv("SANDBOX_BINARY", sandbox_binary) + os.Setenv("HOME", home) + os.Setenv("SLEEP_TIME", "0") + defaults.ConfigurationDir = home + ".dbdeployer" + defaults.ConfigurationFile = home + ".dbdeployer/config.json" + defaults.SandboxRegistry = home + ".dbdeployer/sandboxes.json" + defaults.SandboxRegistryLock = home + ".dbdeployer/sandboxes.lock" +} + +func remove_mock_environment(mock_upper_dir string) { + if !common.DirExists(mock_upper_dir) { + common.Exit(1, fmt.Sprintf("Mock directory %s doesn't exist. Aborting",mock_upper_dir)) + } + os.RemoveAll(mock_upper_dir) + os.Setenv("HOME", save_home) + os.Setenv("SANDBOX_HOME", save_sandbox_home) + os.Setenv("SANDBOX_BINARY", save_sandbox_binary) +} + +func create_mock_version(version string) { + if sandbox_binary == "" { + common.Exit(1, "Mock directory not set yet. - Call set_mock_environment() first") + } + version_dir := fmt.Sprintf("%s/%s", sandbox_binary, version) + common.Mkdir(version_dir) + common.Mkdir(version_dir+ "/bin") + common.Mkdir(version_dir+ "/scripts") + write_script(MockTemplates, "mysqld", "no_op_mock_template", version_dir + "/bin", common.Smap{}, true) + write_script(MockTemplates, "mysql", "no_op_mock_template", version_dir + "/bin", common.Smap{}, true) + write_script(MockTemplates, "mysql_install_db", "no_op_mock_template", version_dir + "/scripts", common.Smap{}, true) + write_script(MockTemplates, "mysqld_safe", "mysqld_safe_mock_template", version_dir + "/bin", common.Smap{}, true) +} + +func init() { + sandbox_binary = os.Getenv("SANDBOX_BINARY") + if sandbox_binary != "" { + save_sandbox_binary = sandbox_binary + } + sandbox_home = os.Getenv("SANDBOX_HOME") + if sandbox_home != "" { + save_sandbox_home = sandbox_home + } + home := os.Getenv("HOME") + if home != "" { + save_home = home + } +} diff --git a/sandbox/multi-source-replication.go b/sandbox/multi-source-replication.go index a08d69f1..08983f99 100644 --- a/sandbox/multi-source-replication.go +++ b/sandbox/multi-source-replication.go @@ -17,7 +17,6 @@ package sandbox import ( "fmt" - "os" "regexp" "strings" "strconv" @@ -28,28 +27,24 @@ import ( func check_node_lists(nodes int, mlist, slist []int) { for _, N := range mlist { if N > nodes { - fmt.Printf("Master num '%d' greater than number of nodes (%d)\n", N, nodes) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Master num '%d' greater than number of nodes (%d)", N, nodes)) } } for _, N := range slist { if N > nodes { - fmt.Printf("Slave num '%d' greater than number of nodes (%d)\n", N, nodes) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Slave num '%d' greater than number of nodes (%d)", N, nodes)) } } for _, M := range mlist { for _, S := range slist { if S == M { - fmt.Printf("Overlapping values: %d is in both master and slave list\n",M) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Overlapping values: %d is in both master and slave list",M)) } } } total_nodes := len(mlist) + len(slist) if total_nodes != nodes { - fmt.Printf("Mismatched values: masters (%d) + slaves (%d) = %d. Expected: %d \n",len(mlist), len(slist), total_nodes, nodes) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Mismatched values: masters (%d) + slaves (%d) = %d. Expected: %d",len(mlist), len(slist), total_nodes, nodes)) } } @@ -69,15 +64,13 @@ func nodes_list_to_int_slice(nodes_list string, nodes int) (int_list []int) { list := strings.Split(nodes_list, separator) // fmt.Printf("# separator: <%s> %#v\n",separator, list) if len(list) == 0 { - fmt.Printf("Empty nodes list given (%s)\n",nodes_list) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Empty nodes list given (%s)",nodes_list)) } for _, s := range list { if s != "" { num, err := strconv.Atoi(s) if err != nil { - fmt.Printf("Error converting node number '%s' to int\n",s) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Error converting node number '%s' to int",s)) } int_list = append(int_list, num) } diff --git a/sandbox/multiple.go b/sandbox/multiple.go index d3ee3a30..4e0a3795 100644 --- a/sandbox/multiple.go +++ b/sandbox/multiple.go @@ -17,7 +17,6 @@ package sandbox import ( "fmt" - "os" "time" "github.com/datacharmer/dbdeployer/common" @@ -41,8 +40,7 @@ func CreateMultipleSandbox(sdef SandboxDef, origin string, nodes int) common.Sma } Basedir := sdef.Basedir if !common.DirExists(Basedir) { - fmt.Printf("Base directory %s does not exist\n", Basedir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Base directory %s does not exist", Basedir)) } if sdef.DirName == "" { sdef.SandboxDir += "/" + defaults.Defaults().MultiplePrefix + common.VersionToName(origin) @@ -70,8 +68,7 @@ func CreateMultipleSandbox(sdef SandboxDef, origin string, nodes int) common.Sma sdef.ReplOptions = SingleTemplates["replication_options"].Contents base_server_id := 0 if nodes < 2 { - fmt.Println("For single sandbox deployment, use the 'single' command") - os.Exit(1) + common.Exit(1, "Only one node requested. For single sandbox deployment, use the 'single' command") } timestamp := time.Now() var data common.Smap = common.Smap{ @@ -132,7 +129,7 @@ func CreateMultipleSandbox(sdef SandboxDef, origin string, nodes int) common.Sma if !sdef.RunConcurrently { fmt.Printf("Installing and starting %s %d\n", node_label, i) } - exec_list := CreateSingleSandbox(sdef, origin) + exec_list := CreateSingleSandbox(sdef) for _, list := range exec_list { exec_lists = append(exec_lists, list) } diff --git a/sandbox/replication.go b/sandbox/replication.go index 1ee3fe4d..13ebe024 100644 --- a/sandbox/replication.go +++ b/sandbox/replication.go @@ -17,7 +17,6 @@ package sandbox import ( "fmt" - "os" "time" "github.com/datacharmer/dbdeployer/common" @@ -64,8 +63,7 @@ func CreateMasterSlaveReplication(sdef SandboxDef, origin string, nodes int, mas } } if nodes < 2 { - fmt.Println("Can't run replication with less than 2 nodes") - os.Exit(1) + common.Exit(1, "Can't run replication with less than 2 nodes") } slaves := nodes - 1 master_abbr := defaults.Defaults().MasterAbbr @@ -103,7 +101,7 @@ func CreateMasterSlaveReplication(sdef SandboxDef, origin string, nodes int, mas sdef.Prompt = master_label sdef.NodeNum = 1 sdef.SBType = "replication-node" - exec_list := CreateSingleSandbox(sdef, origin) + exec_list := CreateSingleSandbox(sdef) for _, list := range exec_list { exec_lists = append(exec_lists, list) } @@ -178,7 +176,7 @@ func CreateMasterSlaveReplication(sdef SandboxDef, origin string, nodes int, mas if sdef.SemiSyncOptions != "" { sdef.SemiSyncOptions = SingleTemplates["semisync_slave_options"].Contents } - exec_list_node := CreateSingleSandbox(sdef, origin) + exec_list_node := CreateSingleSandbox(sdef) for _, list := range exec_list_node { exec_lists = append(exec_lists, list) } @@ -235,8 +233,7 @@ func CreateReplicationSandbox(sdef SandboxDef, origin string, topology string, n Basedir := sdef.Basedir if !common.DirExists(Basedir) { - fmt.Printf("Base directory %s does not exist\n", Basedir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Base directory %s does not exist", Basedir)) } sandbox_dir := sdef.SandboxDir @@ -250,24 +247,20 @@ func CreateReplicationSandbox(sdef SandboxDef, origin string, topology string, n sdef.SandboxDir += "/" + defaults.Defaults().GroupPrefix + common.VersionToName(origin) } if !common.GreaterOrEqualVersion(sdef.Version, []int{5, 7, 17}) { - fmt.Println("Group replication requires MySQL 5.7.17 or greater") - os.Exit(1) + common.Exit(1, "Group replication requires MySQL 5.7.17 or greater") } case "fan-in": if !common.GreaterOrEqualVersion(sdef.Version, []int{5, 7, 9}) { - fmt.Println("multi-source replication requires MySQL 5.7.9 or greater") - os.Exit(1) + common.Exit(1, "multi-source replication requires MySQL 5.7.9 or greater") } sdef.SandboxDir += "/" + defaults.Defaults().FanInPrefix + common.VersionToName(origin) case "all-masters": if !common.GreaterOrEqualVersion(sdef.Version, []int{5, 7, 9}) { - fmt.Println("multi-source replication requires MySQL 5.7.9 or greater") - os.Exit(1) + common.Exit(1, "multi-source replication requires MySQL 5.7.9 or greater") } sdef.SandboxDir += "/" + defaults.Defaults().AllMastersPrefix + common.VersionToName(origin) default: - fmt.Println("Unrecognized topology. Accepted: 'master-slave', 'group', 'fan-in', 'all-masters'") - os.Exit(1) + common.Exit(1, "Unrecognized topology. Accepted: 'master-slave', 'group', 'fan-in', 'all-masters'") } if sdef.DirName != "" { sdef.SandboxDir = sandbox_dir + "/" + sdef.DirName @@ -284,11 +277,7 @@ func CreateReplicationSandbox(sdef SandboxDef, origin string, topology string, n CreateGroupReplication(sdef, origin, nodes, master_ip) case "fan-in": CreateFanInReplication(sdef, origin, nodes, master_ip, master_list, slave_list) - // fmt.Println("fan-in replication is not implemented yet") - // os.Exit(0) case "all-masters": CreateAllMastersReplication(sdef, origin, nodes, master_ip) - //fmt.Println("all-masters replication is not implemented yet") - //os.Exit(0) } } diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go index 056ca9d5..ca84332e 100644 --- a/sandbox/sandbox.go +++ b/sandbox/sandbox.go @@ -26,52 +26,52 @@ import ( ) type SandboxDef struct { - DirName string - SBType string - Multi bool - NodeNum int - Version string - Basedir string - SandboxDir string - LoadGrants bool - SkipReportHost bool - SkipReportPort bool - SkipStart bool - InstalledPorts []int - Port int - MysqlXPort int - UserPort int - BasePort int - MorePorts []int - Prompt string - DbUser string - RplUser string - DbPassword string - RplPassword string - RemoteAccess string - BindAddress string - CustomMysqld string - ServerId int - ReplOptions string - GtidOptions string - SemiSyncOptions string - InitOptions []string - MyCnfOptions []string - PreGrantsSql []string - PreGrantsSqlFile string - PostGrantsSql []string - PostGrantsSqlFile string - MyCnfFile string - InitGeneralLog bool - EnableGeneralLog bool - NativeAuthPlugin bool - DisableMysqlX bool - EnableMysqlX bool - KeepUuid bool - SinglePrimary bool - Force bool - ExposeDdTables bool - RunConcurrently bool + DirName string // name of the directory cointaining the sandbox + SBType string // Type of sandbox (single, multiple, replication-node, group-node) + Multi bool // either single or part of a multiple sandbox + NodeNum int // in multiple sandboxes, which node is this + Version string // MySQL version + Basedir string // Where to get binaries from + SandboxDir string // Target directory for sandboxes + LoadGrants bool // Should we load grants? + SkipReportHost bool // Do not add report-host to my.sandbox.cnf + SkipReportPort bool // Do not add report-port to my.sandbox.cnf + SkipStart bool // Do not start the server after deployment + InstalledPorts []int // Which ports should be skipped in port assignment for this SB + Port int // port assigned to this sandbox + MysqlXPort int // XPlugin port for thsi sandbox + UserPort int // + BasePort int // Base port for calculating more ports in multiple SB + MorePorts []int // Additional ports that belong to thos sandbox + Prompt string // Prompt to use in "mysql" client + DbUser string // Database user name + RplUser string // Replication user name + DbPassword string // Database password + RplPassword string // Replication password + RemoteAccess string // What access have the users created for this SB (127.%) + BindAddress string // Bind address for this sandbox (127.0.0.1) + CustomMysqld string // Use an alternative mysqld executable + ServerId int // Server ID (for replication) + ReplOptions string // Replication options, as string to append to my.sandbox.cnf + GtidOptions string // Options needed for GTID + SemiSyncOptions string // Options for semi-synchronous replication + InitOptions []string // Options to be added to the initialization command + MyCnfOptions []string // Options to be added to my.sandbox.cnf + PreGrantsSql []string // SQL statements to execute before grants assignment + PreGrantsSqlFile string // SQL file to load before grants assignment + PostGrantsSql []string // SQL statements to run after grants assignment + PostGrantsSqlFile string // SQL file to load after grants assignment + MyCnfFile string // options file to merge with the SB my.sandbox.cnf + InitGeneralLog bool // enable general log during server initialization + EnableGeneralLog bool // enable general log after initialization + NativeAuthPlugin bool // Use the native password plugin for MySQL 8.0.4+ + DisableMysqlX bool // Disable Xplugin (MySQL 8.0.11+) + EnableMysqlX bool // Enable Xplugin (MySQL 5.7.12+) + KeepUuid bool // Do not change UUID + SinglePrimary bool // Use single primary for group replication + Force bool // Overwrite an existing sandbox with same target + ExposeDdTables bool // Show hidden data dictionary tables (MySQL 8.0.0+) + RunConcurrently bool // Run multiple sandbox creation concurrently } func GetOptionsFromFile(filename string) (options []string) { @@ -121,8 +121,7 @@ func CheckDirectory(sdef SandboxDef) SandboxDef { common.Run_cmd(stop_command) err, _ := common.Run_cmd_with_args("rm", []string{"-rf", sandbox_dir}) if err != nil { - fmt.Printf("Error while deleting sandbox %s\n", sandbox_dir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Error while deleting sandbox %s", sandbox_dir)) } var new_installed_ports []int for _, port := range sdef.InstalledPorts { @@ -132,8 +131,7 @@ func CheckDirectory(sdef SandboxDef) SandboxDef { } sdef.InstalledPorts = new_installed_ports } else { - fmt.Printf("Directory %s already exists. Use --force to override.\n", sandbox_dir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Directory %s already exists. Use --force to override.", sandbox_dir)) } } return sdef @@ -161,9 +159,8 @@ func FindFreePort(base_port int, installed_ports []int, how_many int) int { } else { check_port += how_many } - if check_port > 60000 { - fmt.Printf("Could not find a free range for %d\n", base_port) - os.Exit(1) + if check_port > 64000 { + common.Exit(1, fmt.Sprintf("Could not find a free range for %d", base_port)) } } // fmt.Printf("%v, %d\n",installed_ports, check_port) @@ -183,8 +180,7 @@ func CheckPort(sandbox_type string, installed_ports []int, port int) { } } if conflict > 0 { - fmt.Printf("Port conflict detected. Port %d is already used\n", conflict) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Port conflict detected. Port %d is already used", conflict)) } } @@ -207,20 +203,7 @@ func FixServerUuid(sdef SandboxDef) (uuid_file, new_uuid string) { new_uuid = fmt.Sprintf("server-uuid=%s", common.MakeCustomizedUuid(sdef.Port, sdef.NodeNum)) operation_dir := sdef.SandboxDir + "/data" uuid_file = operation_dir + "/auto.cnf" - //if !common.DirExists(operation_dir) { - // fmt.Printf("Directory %s does not exist\n", operation_dir) - // os.Exit(1) - //} - //uuid_string = []string{"[auto]", new_uuid} return - //err := common.WriteStrings(uuid_string, uuid_file, "") - //if err != nil { - // fmt.Printf("%s\n", err) - // os.Exit(1) - //} - //check_uuid := common.SlurpAsString(uuid_file) - //fmt.Printf("UUID file (%s) updated : %s\n", uuid_file, new_uuid) - //fmt.Printf("new UUID : %s\n", check_uuid) } func slice_to_text(s_array []string) string { @@ -245,19 +228,26 @@ func set_mysqlx_properties(sdef SandboxDef, global_tmp_dir string) SandboxDef { return sdef } -func CreateSingleSandbox(sdef SandboxDef, origin string) (exec_list []concurrent.ExecutionList) { +func debug_print(sdef SandboxDef) { + if os.Getenv("SBDEBUG") == "" { + return + } + fmt.Printf("%#v\n",sdef) +} + +func CreateSingleSandbox(sdef SandboxDef) (exec_list []concurrent.ExecutionList) { var sandbox_dir string - sdef.Basedir = sdef.Basedir if !common.DirExists(sdef.Basedir) { - fmt.Printf("Base directory %s does not exist\n", sdef.Basedir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Base directory %s does not exist", sdef.Basedir)) } - //fmt.Printf("origin: %s\n", origin) - //fmt.Printf("def: %#v\n", sdef) - // port = VersionToPort(sdef.Version) + if sdef.Port <= 1024 { + common.Exit(1, fmt.Sprintf("Port for sandbox must be > 1024 (given:%d)",sdef.Port)) + } + debug_print(sdef) + version_fname := common.VersionToName(sdef.Version) if sdef.Prompt == "" { sdef.Prompt = "mysql" @@ -274,16 +264,14 @@ func CreateSingleSandbox(sdef SandboxDef, origin string) (exec_list []concurrent global_tmp_dir = "/tmp" } if !common.DirExists(global_tmp_dir) { - fmt.Printf("TMP directory %s does not exist\n", global_tmp_dir) - os.Exit(1) + common.Exit(1, fmt.Sprintf("TMP directory %s does not exist", global_tmp_dir)) } if sdef.NodeNum == 0 && !sdef.Force { sdef.Port = FindFreePort(sdef.Port, sdef.InstalledPorts, 1) } if sdef.EnableMysqlX { if !common.GreaterOrEqualVersion(sdef.Version, []int{5, 7, 12}) { - fmt.Printf("option --enable-mysqlx requires version 5.7.12+\n") - os.Exit(1) + common.Exit(1, "option --enable-mysqlx requires version 5.7.12+") } // If the version is 8.0.11 or later, MySQL X is enabled already if !common.GreaterOrEqualVersion(sdef.Version, []int{8, 0, 11}) { @@ -293,23 +281,22 @@ func CreateSingleSandbox(sdef SandboxDef, origin string) (exec_list []concurrent } if sdef.ExposeDdTables { if !common.GreaterOrEqualVersion(sdef.Version, []int{8, 0, 0}) { - fmt.Printf("--expose-dd-tables requires MySQL 8.0.0+\n") - os.Exit(1) + common.Exit(1, "--expose-dd-tables requires MySQL 8.0.0+") } sdef.PostGrantsSql = append(sdef.PostGrantsSql, SingleTemplates["expose_dd_tables"].Contents) if sdef.CustomMysqld != "" && sdef.CustomMysqld != "mysqld-debug" { - fmt.Printf("--expose-dd-tables requires mysqld-debug. A different file was indicated (--custom-mysqld=%s)\n", sdef.CustomMysqld) - fmt.Println("Either use \"mysqld-debug\" or remove --custom-mysqld") - os.Exit(1) + common.Exit(1, + fmt.Sprintf("--expose-dd-tables requires mysqld-debug. A different file was indicated (--custom-mysqld=%s)", sdef.CustomMysqld), + "Either use \"mysqld-debug\" or remove --custom-mysqld") } sdef.CustomMysqld = "mysqld-debug" } if sdef.CustomMysqld != "" { custom_mysqld := sdef.Basedir + "/bin/" + sdef.CustomMysqld if !common.ExecExists(custom_mysqld) { - fmt.Printf("File %s not found or not executable\n", custom_mysqld) - fmt.Printf("The file \"%s\" (defined with --custom-mysqld) must be in the same directory as the regular mysqld\n", sdef.CustomMysqld) - os.Exit(1) + common.Exit(1, + fmt.Sprintf("File %s not found or not executable", custom_mysqld), + fmt.Sprintf("The file \"%s\" (defined with --custom-mysqld) must be in the same directory as the regular mysqld", sdef.CustomMysqld)) } } if common.GreaterOrEqualVersion(sdef.Version, []int{5, 1, 0}) { @@ -410,8 +397,7 @@ func CreateSingleSandbox(sdef SandboxDef, origin string) (exec_list []concurrent } // fmt.Printf("Script: %s\n", script) if !common.ExecExists(script) { - fmt.Printf("Script '%s' not found\n", script) - os.Exit(1) + common.Exit(1, fmt.Sprintf("Script '%s' not found", script)) } if len(sdef.InitOptions) > 0 { for _, op := range sdef.InitOptions { @@ -446,8 +432,10 @@ func CreateSingleSandbox(sdef SandboxDef, origin string) (exec_list []concurrent err, _ := common.Run_cmd_ctrl(sandbox_dir+"/init_db", true) if err == nil { if !sdef.Multi { - fmt.Printf("Database installed in %s\n", common.ReplaceLiteralHome(sandbox_dir)) - fmt.Printf("run 'dbdeployer usage single' for basic instructions'\n") + if defaults.UsingDbDeployer { + fmt.Printf("Database installed in %s\n", common.ReplaceLiteralHome(sandbox_dir)) + fmt.Printf("run 'dbdeployer usage single' for basic instructions'\n") + } } } else { fmt.Printf("err: %s\n", err) @@ -594,3 +582,68 @@ func write_regular_file(filename, text, directory string) string { common.WriteString(text, fname) return fname } + +func RemoveSandbox(sandbox_dir, sandbox string, run_concurrently bool) (exec_list []concurrent.ExecutionList) { + full_path := sandbox_dir + "/" + sandbox + if !common.DirExists(full_path) { + common.Exit(1, fmt.Sprintf("Directory '%s' not found", full_path)) + } + preserve := full_path + "/no_clear_all" + if !common.ExecExists(preserve) { + preserve = full_path + "/no_clear" + } + if common.ExecExists(preserve) { + fmt.Printf("The sandbox %s is locked\n",sandbox) + fmt.Printf("You need to unlock it with \"dbdeployer admin unlock\"\n",) + return + } + stop := full_path + "/stop_all" + if !common.ExecExists(stop) { + stop = full_path + "/stop" + } + if !common.ExecExists(stop) { + common.Exit(1, fmt.Sprintf("Executable '%s' not found", stop)) + } + + if run_concurrently { + var eCommand1 = concurrent.ExecCommand{ + Cmd : stop, + Args : []string{}, + } + exec_list = append(exec_list, concurrent.ExecutionList{0, eCommand1}) + } else { + if defaults.UsingDbDeployer { + fmt.Printf("Running %s\n", stop) + } + err, _ := common.Run_cmd(stop) + if err != nil { + common.Exit(1, fmt.Sprintf("Error while stopping sandbox %s", full_path)) + } + } + + cmd_str := "rm" + rm_args := []string{"-rf", full_path} + if run_concurrently { + var eCommand2 = concurrent.ExecCommand{ + Cmd : cmd_str, + Args : rm_args, + } + exec_list = append(exec_list, concurrent.ExecutionList{1, eCommand2}) + } else { + for _, item := range rm_args { + cmd_str += " " + item + } + if defaults.UsingDbDeployer { + fmt.Printf("Running %s\n", cmd_str) + } + err, _ := common.Run_cmd_with_args("rm", rm_args) + if err != nil { + common.Exit(1, fmt.Sprintf("Error while deleting sandbox %s", full_path)) + } + if defaults.UsingDbDeployer { + fmt.Printf("Sandbox %s deleted\n", full_path) + } + } + // fmt.Printf("%#v\n",exec_list) + return +} diff --git a/sandbox/sandbox_test.go b/sandbox/sandbox_test.go new file mode 100644 index 00000000..ca75b5ab --- /dev/null +++ b/sandbox/sandbox_test.go @@ -0,0 +1,57 @@ +package sandbox + +import ( + "testing" + "github.com/datacharmer/dbdeployer/common" +) + +func ok_executable_exists(t *testing.T, dir, executable string) { + full_path := dir + "/" + executable + if common.ExecExists(full_path) { + t.Logf("ok - %s exists\n", full_path) + } else { + t.Logf("not ok - %s does not exist\n", full_path) + t.Fail() + } +} + +func ok_dir_exists(t *testing.T, dir string) { + if common.DirExists(dir) { + t.Logf("ok - %s exists\n", dir) + } else { + t.Logf("not ok - %s does not exist\n", dir) + t.Fail() + } +} + +func TestCreateSandbox(t *testing.T) { + set_mock_environment("mock_dir") + create_mock_version("5.7.22") + var sdef = SandboxDef{ + Version:"5.7.22", + Basedir: sandbox_binary + "/5.7.22", + SandboxDir: sandbox_home, + LoadGrants:true, + InstalledPorts:[]int{1186, 3306, 33060}, + Port:5722, + DbUser:"msandbox", + RplUser:"rsandbox", + DbPassword:"msandbox", + RplPassword:"rsandbox", + RemoteAccess:"127.%", + BindAddress:"127.0.0.1", + } + + exec_list := CreateSingleSandbox(sdef) + t.Logf("%#v", exec_list) + ok_dir_exists(t, sdef.Basedir) + sandbox_dir:= sdef.SandboxDir + "/msb_5_7_22" + ok_dir_exists(t, sandbox_dir) + ok_dir_exists(t, sandbox_dir + "/data") + ok_dir_exists(t, sandbox_dir + "/tmp") + ok_executable_exists(t, sandbox_dir, "start") + ok_executable_exists(t, sandbox_dir, "use") + ok_executable_exists(t, sandbox_dir, "stop") + + remove_mock_environment("mock_dir") +} diff --git a/test/sort_versions.go b/test/sort_versions.go index 28dd2fda..2624ffa9 100644 --- a/test/sort_versions.go +++ b/test/sort_versions.go @@ -50,8 +50,7 @@ func main() { } if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "error:", err) - os.Exit(1) + common.Exit(1, fmt.Sprintf( "error: %s", err)) } sort.Slice(vlist, func(i, j int) bool { return vlist[i].mmr[0] < vlist[j].mmr[0] || diff --git a/unpack/unpack.go b/unpack/unpack.go index 7a592934..d43c9d5b 100644 --- a/unpack/unpack.go +++ b/unpack/unpack.go @@ -43,6 +43,7 @@ import ( "path" "strconv" "strings" + "github.com/datacharmer/dbdeployer/common" ) const ( @@ -194,13 +195,12 @@ func unpackTarFiles(reader *tar.Reader) (err error) { cond_print(fmt.Sprintf ("%s -> %s",filename, header.Linkname), true, CHATTY) err := os.Symlink( header.Linkname, filename) if err != nil { - fmt.Printf("%#v\n",header) - fmt.Printf("# ERROR: %s\n",err) - os.Exit(1) + common.Exit(1, + fmt.Sprintf("%#v",header), + fmt.Sprintf("# ERROR: %s",err)) } } else { - fmt.Printf("File %s is a symlonk, but no link information was provided\n", filename) - os.Exit(1) + common.Exit(1, fmt.Sprintf("File %s is a symlonk, but no link information was provided\n", filename)) } } }