diff --git a/cmd/bbr/bbr_suite_test.go b/cli/command/command_suite_test.go similarity index 90% rename from cmd/bbr/bbr_suite_test.go rename to cli/command/command_suite_test.go index 204755134..45e132e08 100644 --- a/cmd/bbr/bbr_suite_test.go +++ b/cli/command/command_suite_test.go @@ -1,4 +1,4 @@ -package main_test +package command import ( . "github.com/onsi/ginkgo" diff --git a/cmd/bbr/bbr_test.go b/cli/command/command_test.go similarity index 93% rename from cmd/bbr/bbr_test.go rename to cli/command/command_test.go index 47fb4ea57..1af91151b 100644 --- a/cmd/bbr/bbr_test.go +++ b/cli/command/command_test.go @@ -1,9 +1,8 @@ -package main_test +package command import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - . "github.com/cloudfoundry-incubator/bosh-backup-and-restore/cmd/bbr" ) var _ = Describe("bbr", func() { diff --git a/cli/command/deployment_backup.go b/cli/command/deployment_backup.go new file mode 100644 index 000000000..3e1a08609 --- /dev/null +++ b/cli/command/deployment_backup.go @@ -0,0 +1,58 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +type DeploymentBackupCommand struct { +} + +func NewDeploymentBackupCommand() DeploymentBackupCommand { + return DeploymentBackupCommand{} +} + +func (d DeploymentBackupCommand) Cli() cli.Command { + return cli.Command{ + Name: "backup", + Aliases: []string{"b"}, + Usage: "Backup a deployment", + Action: d.Action, + Flags: []cli.Flag{cli.BoolFlag{ + Name: "with-manifest", + Usage: "Download the deployment manifest", + }}, + } +} + +func (d DeploymentBackupCommand) Action(c *cli.Context) error { + trapSigint(true) + + backuper, err := factory.BuildDeploymentBackuper(c.Parent().String("target"), + c.Parent().String("username"), + c.Parent().String("password"), + c.Parent().String("ca-cert"), + c.Bool("with-manifest"), + c.GlobalBool("debug"), + ) + + if err != nil { + return redCliError(err) + } + + deployment := c.Parent().String("deployment") + backupErr := backuper.Backup(deployment) + + errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(backupErr) + if err := writeStackTrace(errorWithStackTrace); err != nil { + return errors.Wrap(backupErr, err.Error()) + } + + if backupErr.ContainsUnlockOrCleanup() { + errorMessage = errorMessage + "\n" + backupCleanupAdvisedNotice + } + + return cli.NewExitError(errorMessage, errorCode) +} diff --git a/cli/command/deployment_backup_cleanup.go b/cli/command/deployment_backup_cleanup.go new file mode 100644 index 000000000..a98781d8a --- /dev/null +++ b/cli/command/deployment_backup_cleanup.go @@ -0,0 +1,42 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/urfave/cli" +) + +type DeploymentBackupCleanupCommand struct { +} + +func NewDeploymentBackupCleanupCommand() DeploymentBackupCleanupCommand { + return DeploymentBackupCleanupCommand{} +} + +func (d DeploymentBackupCleanupCommand) Cli() cli.Command { + return cli.Command{ + Name: "backup-cleanup", + Usage: "Cleanup a deployment after a backup was interrupted", + Action: d.Action, + } +} + +func (d DeploymentBackupCleanupCommand) Action(c *cli.Context) error { + trapSigint(true) + + cleaner, err := factory.BuildDeploymentBackupCleanuper( + c.Parent().String("target"), + c.Parent().String("username"), + c.Parent().String("password"), + c.Parent().String("ca-cert"), + c.Bool("with-manifest"), + c.GlobalBool("debug"), + ) + if err != nil { + return redCliError(err) + } + + deployment := c.Parent().String("deployment") + cleanupErr := cleaner.Cleanup(deployment) + + return processError(cleanupErr) +} diff --git a/cli/command/deployment_pre_backup_check.go b/cli/command/deployment_pre_backup_check.go new file mode 100644 index 000000000..7f623e57b --- /dev/null +++ b/cli/command/deployment_pre_backup_check.go @@ -0,0 +1,52 @@ +package command + +import ( + "fmt" + + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/mgutz/ansi" + "github.com/urfave/cli" +) + +type DeploymentPreBackupCheck struct{} + +func NewDeploymentPreBackupCheckCommand() DeploymentPreBackupCheck { + return DeploymentPreBackupCheck{} +} +func (d DeploymentPreBackupCheck) Cli() cli.Command { + return cli.Command{ + Name: "pre-backup-check", + Aliases: []string{"c"}, + Usage: "Check a deployment can be backed up", + Action: d.Action, + } +} +func (d DeploymentPreBackupCheck) Action(c *cli.Context) error { + var deployment = c.Parent().String("deployment") + backuper, err := factory.BuildDeploymentBackupChecker( + c.Parent().String("target"), + c.Parent().String("username"), + c.Parent().String("password"), + c.Parent().String("ca-cert"), + c.GlobalBool("debug"), + c.Bool("with-manifest")) + + if err != nil { + return redCliError(err) + } + + backupable, checkErr := backuper.CanBeBackedUp(deployment) + + if backupable { + fmt.Printf("Deployment '%s' can be backed up.\n", deployment) + return cli.NewExitError("", 0) + } else { + fmt.Printf("Deployment '%s' cannot be backed up.\n", deployment) + writeStackTrace(checkErr.PrettyError(true)) + return cli.NewExitError(checkErr.Error(), 1) + } +} + +func redCliError(err error) *cli.ExitError { + return cli.NewExitError(ansi.Color(err.Error(), "red"), 1) +} diff --git a/cli/command/deployment_restore.go b/cli/command/deployment_restore.go new file mode 100644 index 000000000..ba1855599 --- /dev/null +++ b/cli/command/deployment_restore.go @@ -0,0 +1,50 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/cli/flags" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/urfave/cli" +) + +type DeploymentRestoreCommand struct { +} + +func NewDeploymentRestoreCommand() DeploymentRestoreCommand { + return DeploymentRestoreCommand{} +} + +func (d DeploymentRestoreCommand) Cli() cli.Command { + return cli.Command{ + Name: "restore", + Aliases: []string{"r"}, + Usage: "Restore a deployment from backup", + Action: d.Action, + Flags: []cli.Flag{cli.StringFlag{ + Name: "artifact-path", + Usage: "Path to the artifact to restore", + }}, + } +} + +func (d DeploymentRestoreCommand) Action(c *cli.Context) error { + trapSigint(false) + + if err := flags.Validate([]string{"artifact-path"}, c); err != nil { + return redCliError(err) + } + + deployment := c.Parent().String("deployment") + artifactPath := c.String("artifact-path") + + restorer, err := factory.BuildDeploymentRestorer(c.Parent().String("target"), + c.Parent().String("username"), + c.Parent().String("password"), + c.Parent().String("ca-cert"), + c.GlobalBool("debug")) + if err != nil { + return redCliError(err) + } + + restoreErr := restorer.Restore(deployment, artifactPath) + return processError(restoreErr) +} diff --git a/cli/command/deployment_restore_cleanup.go b/cli/command/deployment_restore_cleanup.go new file mode 100644 index 000000000..f908ba9b9 --- /dev/null +++ b/cli/command/deployment_restore_cleanup.go @@ -0,0 +1,40 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/urfave/cli" +) + +type DeploymentRestoreCleanupCommand struct { +} + +func NewDeploymentRestoreCleanupCommand() DeploymentRestoreCleanupCommand { + return DeploymentRestoreCleanupCommand{} +} + +func (d DeploymentRestoreCleanupCommand) Cli() cli.Command { + return cli.Command{ + Name: "restore-cleanup", + Usage: "Cleanup a deployment after a restore was interrupted", + Action: d.Action, + } +} + +func (d DeploymentRestoreCleanupCommand) Action(c *cli.Context) error { + trapSigint(true) + + cleaner, err := factory.BuildDeploymentRestoreCleanuper(c.Parent().String("target"), + c.Parent().String("username"), + c.Parent().String("password"), + c.Parent().String("ca-cert"), + c.Bool("with-manifest"), + c.GlobalBool("debug")) + if err != nil { + return redCliError(err) + } + + deployment := c.Parent().String("deployment") + cleanupErr := cleaner.Cleanup(deployment) + + return processError(cleanupErr) +} diff --git a/cli/command/director_backup.go b/cli/command/director_backup.go new file mode 100644 index 000000000..aad0e8733 --- /dev/null +++ b/cli/command/director_backup.go @@ -0,0 +1,88 @@ +package command + +import ( + "bufio" + "fmt" + "os" + "os/signal" + "strings" + + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +type DirectorBackupCommand struct { +} + +func NewDirectorBackupCommand() DirectorBackupCommand { + return DirectorBackupCommand{} +} +func (checkCommand DirectorBackupCommand) Cli() cli.Command { + return cli.Command{ + Name: "backup", + Aliases: []string{"b"}, + Usage: "Backup a BOSH Director", + Action: checkCommand.Action, + } + +} +func (checkCommand DirectorBackupCommand) Action(c *cli.Context) error { + trapSigint(true) + + directorName := ExtractNameFromAddress(c.Parent().String("host")) + + backuper := factory.BuildDirectorBackuper( + c.Parent().String("host"), + c.Parent().String("username"), + c.Parent().String("private-key-path"), + c.GlobalBool("debug")) + + backupErr := backuper.Backup(directorName) + + errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(backupErr) + if err := writeStackTrace(errorWithStackTrace); err != nil { + return errors.Wrap(backupErr, err.Error()) + } + + if backupErr.ContainsUnlockOrCleanup() { + errorMessage = errorMessage + "\n" + backupCleanupAdvisedNotice + } + + return cli.NewExitError(errorMessage, errorCode) +} + +func trapSigint(backup bool) { + sigintChan := make(chan os.Signal, 1) + signal.Notify(sigintChan, os.Interrupt) + + var sigintQuestion, stdInErrorMessage, cleanupAdvisedNotice string + if backup { + sigintQuestion = backupSigintQuestion + stdInErrorMessage = backupStdinErrorMessage + cleanupAdvisedNotice = backupCleanupAdvisedNotice + } else { + sigintQuestion = restoreSigintQuestion + stdInErrorMessage = restoreStdinErrorMessage + cleanupAdvisedNotice = restoreCleanupAdvisedNotice + } + + go func() { + for range sigintChan { + stdinReader := bufio.NewReader(os.Stdin) + factory.ApplicationLoggerStdout.Pause() + factory.ApplicationLoggerStderr.Pause() + fmt.Fprintln(os.Stdout, "\n"+sigintQuestion) + input, err := stdinReader.ReadString('\n') + if err != nil { + fmt.Println("\n" + stdInErrorMessage) + } else if strings.ToLower(strings.TrimSpace(input)) == "yes" { + fmt.Println(cleanupAdvisedNotice) + os.Exit(1) + } + factory.ApplicationLoggerStdout.Resume() + factory.ApplicationLoggerStderr.Resume() + } + }() +} diff --git a/cli/command/director_backup_cleanup.go b/cli/command/director_backup_cleanup.go new file mode 100644 index 000000000..1f50a54a0 --- /dev/null +++ b/cli/command/director_backup_cleanup.go @@ -0,0 +1,36 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/urfave/cli" +) + +type DirectorBackupCleanupCommand struct { +} + +func NewDirectorBackupCleanupCommand() DirectorBackupCleanupCommand { + return DirectorBackupCleanupCommand{} +} +func (d DirectorBackupCleanupCommand) Cli() cli.Command { + return cli.Command{ + Name: "backup-cleanup", + Usage: "Cleanup a director after a backup was interrupted", + Action: d.Action, + } +} + +func (d DirectorBackupCleanupCommand) Action(c *cli.Context) error { + trapSigint(true) + + directorName := ExtractNameFromAddress(c.Parent().String("host")) + + cleaner := factory.BuildDirectorBackupCleaner(c.Parent().String("host"), + c.Parent().String("username"), + c.Parent().String("private-key-path"), + c.GlobalBool("debug"), + ) + + cleanupErr := cleaner.Cleanup(directorName) + + return processError(cleanupErr) +} diff --git a/cli/command/director_pre_backup_check.go b/cli/command/director_pre_backup_check.go new file mode 100644 index 000000000..aa4aa58df --- /dev/null +++ b/cli/command/director_pre_backup_check.go @@ -0,0 +1,67 @@ +package command + +import ( + "fmt" + "io/ioutil" + "net/url" + "strings" + "time" + + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/urfave/cli" +) + +type DirectorPreBackupCheckCommand struct { +} + +func (checkCommand DirectorPreBackupCheckCommand) Cli() cli.Command { + return cli.Command{ + Name: "pre-backup-check", + Aliases: []string{"c"}, + Usage: "Check a BOSH Director can be backed up", + Action: checkCommand.Action, + } +} + +func NewDirectorPreBackupCheckCommand() DirectorPreBackupCheckCommand { + return DirectorPreBackupCheckCommand{} +} + +func (checkCommand DirectorPreBackupCheckCommand) Action(c *cli.Context) error { + directorName := ExtractNameFromAddress(c.Parent().String("host")) + + backuper := factory.BuildDirectorBackupChecker( + c.Parent().String("host"), + c.Parent().String("username"), + c.Parent().String("private-key-path"), + c.GlobalBool("debug"), + ) + + backupable, checkErr := backuper.CanBeBackedUp(directorName) + + if backupable { + fmt.Printf("Director can be backed up.\n") + return cli.NewExitError("", 0) + } else { + fmt.Printf("Director cannot be backed up.\n") + writeStackTrace(checkErr.PrettyError(true)) + return cli.NewExitError(checkErr.Error(), 1) + } +} +func writeStackTrace(errorWithStackTrace string) error { + if errorWithStackTrace != "" { + err := ioutil.WriteFile(fmt.Sprintf("bbr-%s.err.log", time.Now().UTC().Format(time.RFC3339)), []byte(errorWithStackTrace), 0644) + if err != nil { + return err + } + } + return nil +} + +func ExtractNameFromAddress(address string) string { + url, err := url.Parse(address) + if err == nil && url.Hostname() != "" { + address = url.Hostname() + } + return strings.Split(address, ":")[0] +} diff --git a/cli/command/director_restore.go b/cli/command/director_restore.go new file mode 100644 index 000000000..534aef764 --- /dev/null +++ b/cli/command/director_restore.go @@ -0,0 +1,61 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/cli/flags" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +type DirectorRestoreCommand struct { +} + +func NewDirectorRestoreCommand() DirectorRestoreCommand { + return DirectorRestoreCommand{} +} + +func (cmd DirectorRestoreCommand) Cli() cli.Command { + return cli.Command{ + Name: "restore", + Aliases: []string{"r"}, + Usage: "Restore a deployment from backup", + Action: cmd.Action, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "artifact-path", + Usage: "Path to the artifact to restore", + }, + }, + } +} + +func (cmd DirectorRestoreCommand) Action(c *cli.Context) error { + trapSigint(false) + + if err := flags.Validate([]string{"artifact-path"}, c); err != nil { + return err + } + + directorName := ExtractNameFromAddress(c.Parent().String("host")) + artifactPath := c.String("artifact-path") + + restorer := factory.BuildDirectorRestorer( + c.Parent().String("host"), + c.Parent().String("username"), + c.Parent().String("private-key-path"), + c.GlobalBool("debug"), + ) + + restoreErr := restorer.Restore(directorName, artifactPath) + return processError(restoreErr) +} + +func processError(err orchestrator.Error) error { + errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(err) + if err := writeStackTrace(errorWithStackTrace); err != nil { + return errors.Wrap(err, err.Error()) + } + + return cli.NewExitError(errorMessage, errorCode) +} diff --git a/cli/command/director_restore_cleanup.go b/cli/command/director_restore_cleanup.go new file mode 100644 index 000000000..80a4e3c31 --- /dev/null +++ b/cli/command/director_restore_cleanup.go @@ -0,0 +1,37 @@ +package command + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/factory" + "github.com/urfave/cli" +) + +type DirectorRestoreCleanupCommand struct { +} + +func NewDirectorRestoreCleanupCommand() DirectorRestoreCleanupCommand { + return DirectorRestoreCleanupCommand{} +} +func (d DirectorRestoreCleanupCommand) Cli() cli.Command { + return cli.Command{ + Name: "restore-cleanup", + Usage: "Cleanup a director after a restore was interrupted", + Action: d.Action, + } +} + +func (d DirectorRestoreCleanupCommand) Action(c *cli.Context) error { + trapSigint(true) + + directorName := ExtractNameFromAddress(c.Parent().String("host")) + + cleaner := factory.BuildDirectorRestoreCleaner( + c.Parent().String("host"), + c.Parent().String("username"), + c.Parent().String("private-key-path"), + c.GlobalBool("debug"), + ) + + cleanupErr := cleaner.Cleanup(directorName) + + return processError(cleanupErr) +} diff --git a/cli/command/messages.go b/cli/command/messages.go new file mode 100644 index 000000000..0b198f9b4 --- /dev/null +++ b/cli/command/messages.go @@ -0,0 +1,9 @@ +package command + +const backupSigintQuestion = "Stopping a backup can leave the system in bad state. Are you sure you want to cancel? [yes/no]" +const backupStdinErrorMessage = "Couldn't read from Stdin, if you still want to stop the backup send SIGTERM." +const backupCleanupAdvisedNotice = "It is recommended that you run `bbr backup-cleanup` to ensure that any temp files are cleaned up and all jobs are unlocked." + +const restoreSigintQuestion = "Stopping a restore can leave the system in bad state. Are you sure you want to cancel? [yes/no]" +const restoreStdinErrorMessage = "Couldn't read from Stdin, if you still want to stop the restore send SIGTERM." +const restoreCleanupAdvisedNotice = "It is recommended that you run `bbr restore-cleanup` to ensure that any temp files are cleaned up and all jobs are unlocked." diff --git a/cli/flags/validate.go b/cli/flags/validate.go new file mode 100644 index 000000000..4ebfa04e2 --- /dev/null +++ b/cli/flags/validate.go @@ -0,0 +1,34 @@ +package flags + +import ( + "github.com/mgutz/ansi" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +func Validate(requiredFlags []string, c *cli.Context) error { + if containsHelpFlag(c) { + return nil + } + + for _, flag := range requiredFlags { + if c.String(flag) == "" { + cli.ShowCommandHelp(c, c.Parent().Command.Name) + return redCliError(errors.Errorf("--%v flag is required.", flag)) + } + } + return nil +} + +func containsHelpFlag(c *cli.Context) bool { + for _, arg := range c.Args() { + if arg == "--help" || arg == "-h" { + return true + } + } + return false +} + +func redCliError(err error) *cli.ExitError { + return cli.NewExitError(ansi.Color(err.Error(), "red"), 1) +} diff --git a/cmd/bbr/main.go b/cmd/bbr/main.go index f695bae55..d2aed603d 100644 --- a/cmd/bbr/main.go +++ b/cmd/bbr/main.go @@ -1,79 +1,18 @@ package main import ( - "fmt" "os" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/bosh" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" "github.com/urfave/cli" - "time" - - "io/ioutil" - - "strings" - - "net/url" - - "os/signal" - - "bufio" - - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/backup" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/instance" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/ssh" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/standalone" - "github.com/cloudfoundry-incubator/bosh-backup-and-restore/writer" - boshlog "github.com/cloudfoundry/bosh-utils/logger" - "github.com/mgutz/ansi" - "github.com/pkg/errors" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/cli/command" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/cli/flags" ) var version string -var stdout *writer.PausableWriter = writer.NewPausableWriter(os.Stdout) -var stderr *writer.PausableWriter = writer.NewPausableWriter(os.Stderr) - -const backupSigintQuestion = "Stopping a backup can leave the system in bad state. Are you sure you want to cancel? [yes/no]" -const backupStdinErrorMessage = "Couldn't read from Stdin, if you still want to stop the backup send SIGTERM." -const backupCleanupAdvisedNotice = "It is recommended that you run `bbr backup-cleanup` to ensure that any temp files are cleaned up and all jobs are unlocked." - -const restoreSigintQuestion = "Stopping a restore can leave the system in bad state. Are you sure you want to cancel? [yes/no]" -const restoreStdinErrorMessage = "Couldn't read from Stdin, if you still want to stop the restore send SIGTERM." -const restoreCleanupAdvisedNotice = "It is recommended that you run `bbr restore-cleanup` to ensure that any temp files are cleaned up and all jobs are unlocked." func main() { - cli.AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} - -USAGE: - bbr command [arguments...] [subcommand]{{if .Version}}{{if not .HideVersion}} - -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} - -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}} - -SUBCOMMANDS: - backup - backup-cleanup - restore - restore-cleanup - pre-backup-check{{if .Copyright}} - -COPYRIGHT: - {{.Copyright}}{{end}} -` + cli.AppHelpTemplate = helpTextTemplate app := cli.NewApp() @@ -89,42 +28,11 @@ COPYRIGHT: Flags: availableDeploymentFlags(), Before: validateDeploymentFlags, Subcommands: []cli.Command{ - { - Name: "pre-backup-check", - Aliases: []string{"c"}, - Usage: "Check a deployment can be backed up", - Action: deploymentPreBackupCheck, - }, - { - Name: "backup", - Aliases: []string{"b"}, - Usage: "Backup a deployment", - Action: deploymentBackup, - Flags: []cli.Flag{cli.BoolFlag{ - Name: "with-manifest", - Usage: "Download the deployment manifest", - }}, - }, - { - Name: "restore", - Aliases: []string{"r"}, - Usage: "Restore a deployment from backup", - Action: deploymentRestore, - Flags: []cli.Flag{cli.StringFlag{ - Name: "artifact-path", - Usage: "Path to the artifact to restore", - }}, - }, - { - Name: "backup-cleanup", - Usage: "Cleanup a deployment after a backup was interrupted", - Action: deploymentBackupCleanup, - }, - { - Name: "restore-cleanup", - Usage: "Cleanup a deployment after a restore was interrupted", - Action: deploymentRestoreCleanup, - }, + command.NewDeploymentPreBackupCheckCommand().Cli(), + command.NewDeploymentBackupCommand().Cli(), + command.NewDeploymentRestoreCommand().Cli(), + command.NewDeploymentBackupCleanupCommand().Cli(), + command.NewDeploymentRestoreCleanupCommand().Cli(), }, }, { @@ -133,48 +41,18 @@ COPYRIGHT: Flags: availableDirectorFlags(), Before: validateDirectorFlags, Subcommands: []cli.Command{ - { - Name: "pre-backup-check", - Aliases: []string{"c"}, - Usage: "Check a BOSH Director can be backed up", - Action: directorPreBackupCheck, - }, - { - Name: "backup", - Aliases: []string{"b"}, - Usage: "Backup a BOSH Director", - Action: directorBackup, - }, - { - Name: "restore", - Aliases: []string{"r"}, - Usage: "Restore a deployment from backup", - Action: directorRestore, - Flags: []cli.Flag{cli.StringFlag{ - Name: "artifact-path", - Usage: "Path to the artifact to restore", - }}, - }, - { - Name: "backup-cleanup", - Usage: "Cleanup a director after a backup was interrupted", - Action: directorBackupCleanup, - }, - { - Name: "restore-cleanup", - Usage: "Cleanup a director after a restore was interrupted", - Action: directorRestoreCleanup, - }, + command.NewDirectorPreBackupCheckCommand().Cli(), + command.NewDirectorBackupCommand().Cli(), + command.NewDirectorRestoreCommand().Cli(), + command.NewDirectorBackupCleanupCommand().Cli(), + command.NewDirectorRestoreCleanupCommand().Cli(), }, }, { Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", - Action: func(c *cli.Context) error { - cli.ShowAppHelp(c) - return nil - }, + Action: versionAction, }, { Name: "version", @@ -192,277 +70,17 @@ COPYRIGHT: } } -func trapSigint(backup bool) { - sigintChan := make(chan os.Signal, 1) - signal.Notify(sigintChan, os.Interrupt) - - var sigintQuestion, stdInErrorMessage, cleanupAdvisedNotice string - if backup { - sigintQuestion = backupSigintQuestion - stdInErrorMessage = backupStdinErrorMessage - cleanupAdvisedNotice = backupCleanupAdvisedNotice - } else { - sigintQuestion = restoreSigintQuestion - stdInErrorMessage = restoreStdinErrorMessage - cleanupAdvisedNotice = restoreCleanupAdvisedNotice - } - - go func() { - for range sigintChan { - stdinReader := bufio.NewReader(os.Stdin) - stdout.Pause() - stderr.Pause() - fmt.Fprintln(os.Stdout, "\n"+sigintQuestion) - input, err := stdinReader.ReadString('\n') - if err != nil { - fmt.Println("\n" + stdInErrorMessage) - } else if strings.ToLower(strings.TrimSpace(input)) == "yes" { - fmt.Println(cleanupAdvisedNotice) - os.Exit(1) - } - stdout.Resume() - stderr.Resume() - } - }() -} - -func deploymentPreBackupCheck(c *cli.Context) error { - var deployment = c.Parent().String("deployment") - - backuper, err := makeDeploymentBackupChecker(c) - if err != nil { - return err - } - - backupable, checkErr := backuper.CanBeBackedUp(deployment) - - if backupable { - fmt.Printf("Deployment '%s' can be backed up.\n", deployment) - return cli.NewExitError("", 0) - } else { - fmt.Printf("Deployment '%s' cannot be backed up.\n", deployment) - writeStackTrace(checkErr.PrettyError(true)) - return cli.NewExitError(checkErr.Error(), 1) - } -} - -func directorPreBackupCheck(c *cli.Context) error { - directorName := ExtractNameFromAddress(c.Parent().String("host")) - - backuper := makeDirectorBackupChecker(c) - - backupable, checkErr := backuper.CanBeBackedUp(directorName) - - if backupable { - fmt.Printf("Director can be backed up.\n") - return cli.NewExitError("", 0) - } else { - fmt.Printf("Director cannot be backed up.\n") - writeStackTrace(checkErr.PrettyError(true)) - return cli.NewExitError(checkErr.Error(), 1) - } -} - -func deploymentBackup(c *cli.Context) error { - trapSigint(true) - - backuper, err := makeDeploymentBackuper(c) - if err != nil { - return err - } - - deployment := c.Parent().String("deployment") - backupErr := backuper.Backup(deployment) - - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(backupErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(backupErr, err.Error()) - } - - if backupErr.ContainsUnlockOrCleanup() { - errorMessage = errorMessage + "\n" + backupCleanupAdvisedNotice - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func directorBackup(c *cli.Context) error { - trapSigint(true) - - directorName := ExtractNameFromAddress(c.Parent().String("host")) - - backuper := makeDirectorBackuper(c) - - backupErr := backuper.Backup(directorName) - - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(backupErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(backupErr, err.Error()) - } - - if backupErr.ContainsUnlockOrCleanup() { - errorMessage = errorMessage + "\n" + backupCleanupAdvisedNotice - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func deploymentRestore(c *cli.Context) error { - trapSigint(false) - - if err := validateFlags([]string{"artifact-path"}, c); err != nil { - return err - } - - deployment := c.Parent().String("deployment") - artifactPath := c.String("artifact-path") - - restorer, err := makeDeploymentRestorer(c) - if err != nil { - return err - } - - restoreErr := restorer.Restore(deployment, artifactPath) - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(restoreErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(restoreErr, err.Error()) - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func directorRestore(c *cli.Context) error { - trapSigint(false) - - if err := validateFlags([]string{"artifact-path"}, c); err != nil { - return err - } - - directorName := ExtractNameFromAddress(c.Parent().String("host")) - artifactPath := c.String("artifact-path") - - restorer := makeDirectorRestorer(c) - - restoreErr := restorer.Restore(directorName, artifactPath) - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(restoreErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(restoreErr, err.Error()) - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func deploymentBackupCleanup(c *cli.Context) error { - trapSigint(true) - - cleaner, err := makeDeploymentBackupCleaner(c) - if err != nil { - return err - } - - deployment := c.Parent().String("deployment") - cleanupErr := cleaner.Cleanup(deployment) - - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(cleanupErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(cleanupErr, err.Error()) - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func deploymentRestoreCleanup(c *cli.Context) error { - trapSigint(true) - - cleaner, err := makeDeploymentRestoreCleaner(c) - if err != nil { - return err - } - - deployment := c.Parent().String("deployment") - cleanupErr := cleaner.Cleanup(deployment) - - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(cleanupErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(cleanupErr, err.Error()) - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func directorBackupCleanup(c *cli.Context) error { - trapSigint(true) - - directorName := ExtractNameFromAddress(c.Parent().String("host")) - - cleaner := makeDirectorBackupCleaner(c) - - cleanupErr := cleaner.Cleanup(directorName) - - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(cleanupErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(cleanupErr, err.Error()) - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func directorRestoreCleanup(c *cli.Context) error { - trapSigint(true) - - directorName := ExtractNameFromAddress(c.Parent().String("host")) - - cleaner := makeDirectorRestoreCleaner(c) - - cleanupErr := cleaner.Cleanup(directorName) - - errorCode, errorMessage, errorWithStackTrace := orchestrator.ProcessError(cleanupErr) - if err := writeStackTrace(errorWithStackTrace); err != nil { - return errors.Wrap(cleanupErr, err.Error()) - } - - return cli.NewExitError(errorMessage, errorCode) -} - -func writeStackTrace(errorWithStackTrace string) error { - if errorWithStackTrace != "" { - err := ioutil.WriteFile(fmt.Sprintf("bbr-%s.err.log", time.Now().UTC().Format(time.RFC3339)), []byte(errorWithStackTrace), 0644) - if err != nil { - return err - } - } +func versionAction(c *cli.Context) error { + cli.ShowAppHelp(c) return nil } func validateDeploymentFlags(c *cli.Context) error { - return validateFlags([]string{"target", "username", "password", "deployment"}, c) + return flags.Validate([]string{"target", "username", "password", "deployment"}, c) } func validateDirectorFlags(c *cli.Context) error { - return validateFlags([]string{"host", "username", "private-key-path"}, c) -} - -func validateFlags(requiredFlags []string, c *cli.Context) error { - if containsHelpFlag(c) { - return nil - } - - for _, flag := range requiredFlags { - if c.String(flag) == "" { - cli.ShowCommandHelp(c, c.Parent().Command.Name) - return redCliError(errors.Errorf("--%v flag is required.", flag)) - } - } - return nil -} - -func containsHelpFlag(c *cli.Context) bool { - for _, arg := range c.Args() { - if arg == "--help" || arg == "-h" { - return true - } - } - return false + return flags.Validate([]string{"host", "username", "private-key-path"}, c) } func availableDeploymentFlags() []cli.Flag { @@ -524,190 +142,3 @@ func availableDirectorFlags() []cli.Flag { }, } } - -func ExtractNameFromAddress(address string) string { - url, err := url.Parse(address) - if err == nil && url.Hostname() != "" { - address = url.Hostname() - } - return strings.Split(address, ":")[0] -} - -func makeDeploymentBackupCleaner(c *cli.Context) (*orchestrator.BackupCleaner, error) { - logger := makeLogger(c) - deploymentManager, err := newDeploymentManager( - c.Parent().String("target"), - c.Parent().String("username"), - c.Parent().String("password"), - c.Parent().String("ca-cert"), - logger, - c.Bool("with-manifest"), - ) - - if err != nil { - return nil, redCliError(err) - } - - return orchestrator.NewBackupCleaner(logger, deploymentManager, orderer.NewKahnBackupLockOrderer()), nil -} - -func makeDeploymentRestoreCleaner(c *cli.Context) (*orchestrator.RestoreCleaner, error) { - logger := makeLogger(c) - deploymentManager, err := newDeploymentManager( - c.Parent().String("target"), - c.Parent().String("username"), - c.Parent().String("password"), - c.Parent().String("ca-cert"), - logger, - c.Bool("with-manifest"), - ) - - if err != nil { - return nil, redCliError(err) - } - - return orchestrator.NewRestoreCleaner(logger, deploymentManager, orderer.NewKahnRestoreLockOrderer()), nil -} - -func makeDirectorBackupCleaner(c *cli.Context) *orchestrator.BackupCleaner { - logger := makeLogger(c) - deploymentManager := standalone.NewDeploymentManager(logger, - c.Parent().String("host"), - c.Parent().String("username"), - c.Parent().String("private-key-path"), - instance.NewJobFinder(logger), - ssh.NewConnection, - ) - - return orchestrator.NewBackupCleaner(logger, deploymentManager, orderer.NewDirectorLockOrderer()) -} - -func makeDirectorRestoreCleaner(c *cli.Context) *orchestrator.RestoreCleaner { - logger := makeLogger(c) - deploymentManager := standalone.NewDeploymentManager(logger, - c.Parent().String("host"), - c.Parent().String("username"), - c.Parent().String("private-key-path"), - instance.NewJobFinder(logger), - ssh.NewConnection, - ) - - return orchestrator.NewRestoreCleaner(logger, deploymentManager, orderer.NewKahnRestoreLockOrderer()) -} - -func makeDeploymentBackuper(c *cli.Context) (*orchestrator.Backuper, error) { - logger := makeLogger(c) - deploymentManager, err := newDeploymentManager( - c.Parent().String("target"), - c.Parent().String("username"), - c.Parent().String("password"), - c.Parent().String("ca-cert"), - logger, - c.Bool("with-manifest"), - ) - - if err != nil { - return nil, redCliError(err) - } - - return orchestrator.NewBackuper(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewKahnBackupLockOrderer(), time.Now), nil -} - -func makeDeploymentBackupChecker(c *cli.Context) (*orchestrator.BackupChecker, error) { - logger := makeLogger(c) - deploymentManager, err := newDeploymentManager( - c.Parent().String("target"), - c.Parent().String("username"), - c.Parent().String("password"), - c.Parent().String("ca-cert"), - logger, - c.Bool("with-manifest"), - ) - - if err != nil { - return nil, redCliError(err) - } - - return orchestrator.NewBackupChecker(logger, deploymentManager, orderer.NewKahnBackupLockOrderer()), nil -} - -func makeDirectorBackuper(c *cli.Context) *orchestrator.Backuper { - logger := makeLogger(c) - deploymentManager := standalone.NewDeploymentManager(logger, - c.Parent().String("host"), - c.Parent().String("username"), - c.Parent().String("private-key-path"), - instance.NewJobFinder(logger), - ssh.NewConnection, - ) - - return orchestrator.NewBackuper(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewDirectorLockOrderer(), time.Now) -} - -func makeDirectorBackupChecker(c *cli.Context) *orchestrator.BackupChecker { - logger := makeLogger(c) - deploymentManager := standalone.NewDeploymentManager(logger, - c.Parent().String("host"), - c.Parent().String("username"), - c.Parent().String("private-key-path"), - instance.NewJobFinder(logger), - ssh.NewConnection, - ) - - return orchestrator.NewBackupChecker(logger, deploymentManager, orderer.NewDirectorLockOrderer()) -} - -func makeDeploymentRestorer(c *cli.Context) (*orchestrator.Restorer, error) { - logger := makeLogger(c) - deploymentManager, err := newDeploymentManager( - c.Parent().String("target"), - c.Parent().String("username"), - c.Parent().String("password"), - c.Parent().String("ca-cert"), - logger, - false, - ) - - if err != nil { - return nil, redCliError(err) - } - - return orchestrator.NewRestorer(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewKahnRestoreLockOrderer()), nil -} - -func makeDirectorRestorer(c *cli.Context) *orchestrator.Restorer { - logger := makeLogger(c) - deploymentManager := standalone.NewDeploymentManager(logger, - c.Parent().String("host"), - c.Parent().String("username"), - c.Parent().String("private-key-path"), - instance.NewJobFinder(logger), - ssh.NewConnection, - ) - return orchestrator.NewRestorer(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewDirectorLockOrderer()) -} - -func newDeploymentManager(targetUrl, username, password, caCert string, logger boshlog.Logger, downloadManifest bool) (orchestrator.DeploymentManager, error) { - boshClient, err := bosh.BuildClient(targetUrl, username, password, caCert, logger) - if err != nil { - return nil, redCliError(err) - } - - return bosh.NewDeploymentManager(boshClient, logger, downloadManifest), nil -} - -func makeLogger(c *cli.Context) boshlog.Logger { - var debug = c.GlobalBool("debug") - return makeBoshLogger(debug) -} - -func redCliError(err error) *cli.ExitError { - return cli.NewExitError(ansi.Color(err.Error(), "red"), 1) -} - -func makeBoshLogger(debug bool) boshlog.Logger { - if debug { - return boshlog.NewWriterLogger(boshlog.LevelDebug, stdout, stderr) - } - return boshlog.NewWriterLogger(boshlog.LevelInfo, stdout, stderr) -} diff --git a/cmd/bbr/messages.go b/cmd/bbr/messages.go new file mode 100644 index 000000000..ee26c3e53 --- /dev/null +++ b/cmd/bbr/messages.go @@ -0,0 +1,39 @@ +package main + +const helpTextTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + bbr command [arguments...] [subcommand]{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if len .Authors}} + +AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}} + +SUBCOMMANDS: + backup + backup-cleanup + restore + restore-cleanup + pre-backup-check{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` +const backupSigintQuestion = "Stopping a backup can leave the system in bad state. Are you sure you want to cancel? [yes/no]" +const backupStdinErrorMessage = "Couldn't read from Stdin, if you still want to stop the backup send SIGTERM." +const backupCleanupAdvisedNotice = "It is recommended that you run `bbr backup-cleanup` to ensure that any temp files are cleaned up and all jobs are unlocked." + +const restoreSigintQuestion = "Stopping a restore can leave the system in bad state. Are you sure you want to cancel? [yes/no]" +const restoreStdinErrorMessage = "Couldn't read from Stdin, if you still want to stop the restore send SIGTERM." +const restoreCleanupAdvisedNotice = "It is recommended that you run `bbr restore-cleanup` to ensure that any temp files are cleaned up and all jobs are unlocked." diff --git a/factory/bosh_deployment_manager.go b/factory/bosh_deployment_manager.go new file mode 100644 index 000000000..a58588ce2 --- /dev/null +++ b/factory/bosh_deployment_manager.go @@ -0,0 +1,16 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/bosh" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + boshlog "github.com/cloudfoundry/bosh-utils/logger" +) + +func BuildBoshDeploymentManager(targetUrl, username, password, caCert string, logger boshlog.Logger, downloadManifest bool) (orchestrator.DeploymentManager, error) { + boshClient, err := bosh.BuildClient(targetUrl, username, password, caCert, logger) + if err != nil { + return nil, err + } + + return bosh.NewDeploymentManager(boshClient, logger, downloadManifest), nil +} diff --git a/factory/deployment_backup_cleanuper.go b/factory/deployment_backup_cleanuper.go new file mode 100644 index 000000000..e51ddd84a --- /dev/null +++ b/factory/deployment_backup_cleanuper.go @@ -0,0 +1,30 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" +) + +func BuildDeploymentBackupCleanuper(target, + username, + password, + caCert string, + hasManifest, + hasDebug bool) (*orchestrator.BackupCleaner, error) { + + logger := BuildLogger(hasDebug) + deploymentManager, err := BuildBoshDeploymentManager( + target, + username, + password, + caCert, + logger, + hasManifest, + ) + + if err != nil { + return nil, err + } + + return orchestrator.NewBackupCleaner(logger, deploymentManager, orderer.NewKahnBackupLockOrderer()), nil +} diff --git a/factory/deployment_backuper.go b/factory/deployment_backuper.go new file mode 100644 index 000000000..f1902182b --- /dev/null +++ b/factory/deployment_backuper.go @@ -0,0 +1,27 @@ +package factory + +import ( + "time" + + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/backup" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" +) + +func BuildDeploymentBackuper(target, username, password, caCert string, withManifest, hasDebug bool) (*orchestrator.Backuper, error) { + logger := BuildLogger(hasDebug) + deploymentManager, err := BuildBoshDeploymentManager( + target, + username, + password, + caCert, + logger, + withManifest, + ) + + if err != nil { + return nil, err + } + + return orchestrator.NewBackuper(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewKahnBackupLockOrderer(), time.Now), nil +} diff --git a/factory/deployment_pre_backup_checker.go b/factory/deployment_pre_backup_checker.go new file mode 100644 index 000000000..4c1beb717 --- /dev/null +++ b/factory/deployment_pre_backup_checker.go @@ -0,0 +1,30 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" +) + +func BuildDeploymentBackupChecker(target, + username, + password, + caCert string, + withDebug, + withManifest bool) (*orchestrator.BackupChecker, error) { + logger := BuildLogger(withDebug) + + deploymentManager, err := BuildBoshDeploymentManager( + target, + username, + password, + caCert, + logger, + withManifest, + ) + + if err != nil { + return nil, err + } + + return orchestrator.NewBackupChecker(logger, deploymentManager, orderer.NewKahnBackupLockOrderer()), nil +} diff --git a/factory/deployment_restore_cleanuper.go b/factory/deployment_restore_cleanuper.go new file mode 100644 index 000000000..ca0e14154 --- /dev/null +++ b/factory/deployment_restore_cleanuper.go @@ -0,0 +1,31 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" +) + +func BuildDeploymentRestoreCleanuper(target, + usename, + password, + caCert string, + withManifest, + isDebug bool) (*orchestrator.RestoreCleaner, error) { + + logger := BuildLogger(isDebug) + + deploymentManager, err := BuildBoshDeploymentManager( + target, + usename, + password, + caCert, + logger, + withManifest, + ) + + if err != nil { + return nil, err + } + + return orchestrator.NewRestoreCleaner(logger, deploymentManager, orderer.NewKahnRestoreLockOrderer()), nil +} diff --git a/factory/deployment_restorer.go b/factory/deployment_restorer.go new file mode 100644 index 000000000..af454537a --- /dev/null +++ b/factory/deployment_restorer.go @@ -0,0 +1,30 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/backup" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" +) + +func BuildDeploymentRestorer(target, + username, + password, + caCert string, + debug bool) (*orchestrator.Restorer, error) { + + logger := BuildLogger(debug) + deploymentManager, err := BuildBoshDeploymentManager( + target, + username, + password, + caCert, + logger, + false, + ) + + if err != nil { + return nil, err + } + + return orchestrator.NewRestorer(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewKahnRestoreLockOrderer()), nil +} diff --git a/factory/director_backup_checker.go b/factory/director_backup_checker.go new file mode 100644 index 000000000..9dcc8479b --- /dev/null +++ b/factory/director_backup_checker.go @@ -0,0 +1,22 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/instance" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/ssh" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/standalone" +) + +func BuildDirectorBackupChecker(host, username, privateKeyPath string, hasDebug bool) *orchestrator.BackupChecker { + logger := BuildLogger(hasDebug) + deploymentManager := standalone.NewDeploymentManager(logger, + host, + username, + privateKeyPath, + instance.NewJobFinder(logger), + ssh.NewConnection, + ) + + return orchestrator.NewBackupChecker(logger, deploymentManager, orderer.NewDirectorLockOrderer()) +} diff --git a/factory/director_backup_cleaner.go b/factory/director_backup_cleaner.go new file mode 100644 index 000000000..bd4e8ae39 --- /dev/null +++ b/factory/director_backup_cleaner.go @@ -0,0 +1,26 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/instance" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/ssh" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/standalone" +) + +func BuildDirectorBackupCleaner(host, + username, + privateKeyPath string, + hasDebug bool) *orchestrator.BackupCleaner { + + logger := BuildLogger(hasDebug) + deploymentManager := standalone.NewDeploymentManager(logger, + host, + username, + privateKeyPath, + instance.NewJobFinder(logger), + ssh.NewConnection, + ) + + return orchestrator.NewBackupCleaner(logger, deploymentManager, orderer.NewDirectorLockOrderer()) +} diff --git a/factory/director_backuper.go b/factory/director_backuper.go new file mode 100644 index 000000000..489ba32e3 --- /dev/null +++ b/factory/director_backuper.go @@ -0,0 +1,29 @@ +package factory + +import ( + "time" + + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/backup" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/instance" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/ssh" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/standalone" +) + +func BuildDirectorBackuper(host, + username, + privateKeyPath string, + hasDebug bool) *orchestrator.Backuper { + + logger := BuildLogger(hasDebug) + deploymentManager := standalone.NewDeploymentManager(logger, + host, + username, + privateKeyPath, + instance.NewJobFinder(logger), + ssh.NewConnection, + ) + + return orchestrator.NewBackuper(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewDirectorLockOrderer(), time.Now) +} diff --git a/factory/director_restore_cleaner.go b/factory/director_restore_cleaner.go new file mode 100644 index 000000000..f8af38890 --- /dev/null +++ b/factory/director_restore_cleaner.go @@ -0,0 +1,27 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/instance" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/ssh" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/standalone" +) + +func BuildDirectorRestoreCleaner(host, + username, + privateKeyPath string, + hasDebug bool) *orchestrator.RestoreCleaner { + + logger := BuildLogger(hasDebug) + + deploymentManager := standalone.NewDeploymentManager(logger, + host, + username, + privateKeyPath, + instance.NewJobFinder(logger), + ssh.NewConnection, + ) + + return orchestrator.NewRestoreCleaner(logger, deploymentManager, orderer.NewKahnRestoreLockOrderer()) +} diff --git a/factory/director_restorer.go b/factory/director_restorer.go new file mode 100644 index 000000000..38f6852d8 --- /dev/null +++ b/factory/director_restorer.go @@ -0,0 +1,26 @@ +package factory + +import ( + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/backup" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/instance" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orchestrator" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/orderer" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/ssh" + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/standalone" +) + +func BuildDirectorRestorer( + host, + username, + privateKeyPath string, + hasDebug bool) *orchestrator.Restorer { + logger := BuildLogger(hasDebug) + deploymentManager := standalone.NewDeploymentManager(logger, + host, + username, + privateKeyPath, + instance.NewJobFinder(logger), + ssh.NewConnection, + ) + return orchestrator.NewRestorer(backup.BackupDirectoryManager{}, logger, deploymentManager, orderer.NewDirectorLockOrderer()) +} diff --git a/factory/logger.go b/factory/logger.go new file mode 100644 index 000000000..264de284e --- /dev/null +++ b/factory/logger.go @@ -0,0 +1,22 @@ +package factory + +import ( + "os" + + "github.com/cloudfoundry-incubator/bosh-backup-and-restore/writer" + boshlog "github.com/cloudfoundry/bosh-utils/logger" +) + +func BuildLogger(debug bool) boshlog.Logger { + return BuildBoshLogger(debug) +} + +var ApplicationLoggerStdout *writer.PausableWriter = writer.NewPausableWriter(os.Stdout) +var ApplicationLoggerStderr *writer.PausableWriter = writer.NewPausableWriter(os.Stderr) + +func BuildBoshLogger(debug bool) boshlog.Logger { + if debug { + return boshlog.NewWriterLogger(boshlog.LevelDebug, ApplicationLoggerStdout, ApplicationLoggerStderr) + } + return boshlog.NewWriterLogger(boshlog.LevelInfo, ApplicationLoggerStdout, ApplicationLoggerStderr) +}