diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f01475..e9c000f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.1.0 (April 7, 2017) + +FEATURES: + - Make a major change to the way nanobox runs. When running nanobox now creates a nanobox server + This change fixes the quality of life of most users because it should only ask for passwords once + - VPN now runs under the server + - All administrative commands (dns add, sharing etc.) now run through the server + + ## 2.0.4 (March 6, 2017) BUG FIXES: diff --git a/boxfile.yml b/boxfile.yml index d4ce7217..cba537fd 100644 --- a/boxfile.yml +++ b/boxfile.yml @@ -2,6 +2,9 @@ run.config: image: nanobox/build engine: golang engine.config: + # runtime set to the older version until + # this issue is fixed https://github.com/Microsoft/go-winio/issues/41 + runtime: go-1.7 package: github.com/nanobox-io/nanobox dev_packages: - py27-awscli diff --git a/commands/commands.go b/commands/commands.go index ba52ab31..0de7570a 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -9,8 +9,9 @@ import ( "github.com/spf13/cobra" "github.com/nanobox-io/nanobox/commands/registry" - "github.com/nanobox-io/nanobox/util/config" + "github.com/nanobox-io/nanobox/commands/server" "github.com/nanobox-io/nanobox/models" + "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/mixpanel" "github.com/nanobox-io/nanobox/util/update" @@ -39,6 +40,7 @@ var ( Short: "", Long: ``, PersistentPreRun: func(ccmd *cobra.Command, args []string) { + // report the command usage to mixpanel mixpanel.Report(strings.Replace(ccmd.CommandPath(), "nanobox ", "", 1)) @@ -77,7 +79,7 @@ var ( if config.CIMode { lumber.Level(lumber.INFO) display.Summary = false - display.Level = "info" + display.Level = "info" } }, @@ -126,6 +128,7 @@ func init() { NanoboxCmd.AddCommand(DnsCmd) NanoboxCmd.AddCommand(LogCmd) NanoboxCmd.AddCommand(VersionCmd) + NanoboxCmd.AddCommand(server.ServerCmd) // hidden subcommands NanoboxCmd.AddCommand(EnvCmd) diff --git a/commands/destroy.go b/commands/destroy.go index d1a17f6f..6bac7b59 100644 --- a/commands/destroy.go +++ b/commands/destroy.go @@ -38,6 +38,7 @@ func destroyFunc(ccmd *cobra.Command, args []string) { if len(args) == 0 { display.CommandErr(env.Destroy(envModel)) + return } _, _, name := helpers.Endpoint(envModel, args, 2) diff --git a/commands/dns/add.go b/commands/dns/add.go index df7bddd2..d34827a1 100644 --- a/commands/dns/add.go +++ b/commands/dns/add.go @@ -5,7 +5,6 @@ import ( "github.com/spf13/cobra" - // "github.com/nanobox-io/nanobox/commands/steps" "github.com/nanobox-io/nanobox/helpers" "github.com/nanobox-io/nanobox/models" app_dns "github.com/nanobox-io/nanobox/processors/app/dns" diff --git a/commands/dns/remove.go b/commands/dns/remove.go index c06f583b..520b7d59 100644 --- a/commands/dns/remove.go +++ b/commands/dns/remove.go @@ -5,7 +5,6 @@ import ( "github.com/spf13/cobra" - // "github.com/nanobox-io/nanobox/commands/steps" "github.com/nanobox-io/nanobox/helpers" "github.com/nanobox-io/nanobox/models" app_dns "github.com/nanobox-io/nanobox/processors/app/dns" diff --git a/commands/dns/remove_all.go b/commands/dns/remove_all.go index f54615f1..e17a81aa 100644 --- a/commands/dns/remove_all.go +++ b/commands/dns/remove_all.go @@ -5,7 +5,6 @@ import ( "github.com/spf13/cobra" - // "github.com/nanobox-io/nanobox/commands/steps" "github.com/nanobox-io/nanobox/helpers" "github.com/nanobox-io/nanobox/models" app_dns "github.com/nanobox-io/nanobox/processors/app/dns" diff --git a/commands/env.go b/commands/env.go index 98e31579..86419da6 100644 --- a/commands/env.go +++ b/commands/env.go @@ -22,6 +22,5 @@ A namespaced collection of hidded subcommands used primarily as share provisioni // func init() { // hidden subcommands - EnvCmd.AddCommand(env.ShareCmd) - EnvCmd.AddCommand(env.BridgeCmd) + EnvCmd.AddCommand(env.ServerCmd) } diff --git a/commands/env/bridge.go b/commands/env/bridge.go deleted file mode 100644 index a93515c8..00000000 --- a/commands/env/bridge.go +++ /dev/null @@ -1,67 +0,0 @@ -package env - -import ( - "github.com/spf13/cobra" - - "github.com/nanobox-io/nanobox/processors/provider/bridge" - "github.com/nanobox-io/nanobox/util/display" -) - -var ( - - // BridgeCmd ... - BridgeCmd = &cobra.Command{ - Hidden: true, - Use: "bridge", - Short: "Bridge control", - Long: ``, - } - - // BridgeStartCmd ... - BridgeStartCmd = &cobra.Command{ - Hidden: true, - Use: "start", - Short: "Start the bridge", - Long: ``, - Run: bridgeStartFn, - } - - // BridgeStopCmd ... - BridgeStopCmd = &cobra.Command{ - Hidden: true, - Use: "stop", - Short: "Stop the bridge", - Long: ``, - Run: bridgeStopFn, - } - - // BridgeTeadownCmd ... - BridgeTeadownCmd = &cobra.Command{ - Hidden: true, - Use: "teardown", - Short: "Teardown the bridge", - Long: ``, - Run: bridgeTeardownFn, - } -) - -// -func init() { - BridgeCmd.AddCommand(BridgeStartCmd) - BridgeCmd.AddCommand(BridgeStopCmd) - BridgeCmd.AddCommand(BridgeTeadownCmd) -} - -func bridgeStartFn(ccmd *cobra.Command, args []string) { - display.CommandErr(bridge.Start()) -} - -func bridgeStopFn(ccmd *cobra.Command, args []string) { - - display.CommandErr(bridge.Stop()) -} - -func bridgeTeardownFn(ccmd *cobra.Command, args []string) { - - display.CommandErr(bridge.Teardown()) -} diff --git a/commands/env/server.go b/commands/env/server.go new file mode 100644 index 00000000..c074b258 --- /dev/null +++ b/commands/env/server.go @@ -0,0 +1,67 @@ +package env + +import ( + "github.com/spf13/cobra" + + "github.com/nanobox-io/nanobox/processors/server" + "github.com/nanobox-io/nanobox/util/display" +) + +var ( + + // ServerCmd ... + ServerCmd = &cobra.Command{ + Hidden: true, + Use: "server", + Short: "Server control", + Long: ``, + } + + // ServerStartCmd ... + ServerStartCmd = &cobra.Command{ + Hidden: true, + Use: "start", + Short: "Start the server", + Long: ``, + Run: serverStartFn, + } + + // ServerStopCmd ... + ServerStopCmd = &cobra.Command{ + Hidden: true, + Use: "stop", + Short: "Stop the server", + Long: ``, + Run: serverStopFn, + } + + // ServerTeadownCmd ... + ServerTeadownCmd = &cobra.Command{ + Hidden: true, + Use: "teardown", + Short: "Teardown the server", + Long: ``, + Run: serverTeardownFn, + } +) + +// +func init() { + ServerCmd.AddCommand(ServerStartCmd) + ServerCmd.AddCommand(ServerStopCmd) + ServerCmd.AddCommand(ServerTeadownCmd) +} + +func serverStartFn(ccmd *cobra.Command, args []string) { + display.CommandErr(server.Setup()) +} + +func serverStopFn(ccmd *cobra.Command, args []string) { + + display.CommandErr(server.Stop()) +} + +func serverTeardownFn(ccmd *cobra.Command, args []string) { + + display.CommandErr(server.Teardown()) +} diff --git a/commands/env/share.go b/commands/env/share.go deleted file mode 100644 index 9d4d9d4f..00000000 --- a/commands/env/share.go +++ /dev/null @@ -1,85 +0,0 @@ -package env - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/nanobox-io/nanobox/processors/env/share" - "github.com/nanobox-io/nanobox/util/display" -) - -var ( - - // ShareCmd ... - ShareCmd = &cobra.Command{ - Hidden: true, - Use: "share", - Short: "Add or remove share directories.", - Long: ``, - } - - // ShareAddCmd ... - ShareAddCmd = &cobra.Command{ - Hidden: true, - Use: "add", - Short: "Add a share export.", - Long: ``, - Run: shareAddFn, - } - - // ShareRmCmd ... - ShareRmCmd = &cobra.Command{ - Hidden: true, - Use: "rm", - Short: "Remove a share export.", - Long: ``, - Run: shareRmFn, - } -) - -// -func init() { - ShareCmd.AddCommand(ShareAddCmd) - ShareCmd.AddCommand(ShareRmCmd) -} - -// shareAddFn will run the share processor for adding a share export -func shareAddFn(ccmd *cobra.Command, args []string) { - - // validate we have args required to set the meta we'll need; if we don't have - // the required args this will return with instructions - if len(args) != 1 { - fmt.Printf(` -Wrong number of arguments (expecting 1 got %v). Run the command again with the -path of the exports entry you would like to add: - -ex: nanobox env share add - -`, len(args)) - - return - } - - display.CommandErr(share.Add(args[0])) -} - -// shareRmFn will run the share processor for removing a share export -func shareRmFn(ccmd *cobra.Command, args []string) { - - // validate we have args required to set the meta we'll need; if we don't have - // the required args this will return with instructions - if len(args) != 1 { - fmt.Printf(` -Wrong number of arguments (expecting 1 got %v). Run the command again with the -path of the exports entry you would like to remove: - -ex: nanobox env share rm - -`, len(args)) - - return - } - - display.CommandErr(share.Remove(args[0])) -} diff --git a/commands/server/registry.go b/commands/server/registry.go new file mode 100644 index 00000000..7b460c2c --- /dev/null +++ b/commands/server/registry.go @@ -0,0 +1,7 @@ +package server + +var registeredRPCs = []interface{}{} + +func Register(i interface{}) { + registeredRPCs = append(registeredRPCs, i) +} diff --git a/commands/server/server.go b/commands/server/server.go new file mode 100644 index 00000000..673b8eb5 --- /dev/null +++ b/commands/server/server.go @@ -0,0 +1,93 @@ +package server + +import ( + "log" + "net" + "net/rpc" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" + + "github.com/nanobox-io/nanobox/commands/registry" + "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/util/update" +) + +var ServerCmd = &cobra.Command{ + Use: "server", + Short: "Start a dedicated nanobox server", + Long: ``, + Run: serverFnc, +} + +const name = "nanobox-server" + +func serverFnc(ccmd *cobra.Command, args []string) { + + if !util.IsPrivileged() { + log.Fatal("server needs to run as privilaged user") + return + } + // make sure things know im the server + registry.Set("server", true) + + // fire up the service manager (only required on windows) + go svcStart() + + go updateUpdater() + + // add any registered rpc classes + for _, controller := range registeredRPCs { + rpc.Register(controller) + } + + // only listen for rpc calls on localhost + listener, e := net.Listen("tcp", "127.0.0.1:23456") + if e != nil { + log.Fatal("listen error:", e) + return + } + + // listen for new connections forever + for { + if conn, err := listener.Accept(); err != nil { + log.Fatal("accept error: " + err.Error()) + } else { + log.Printf("new connection established\n") + go rpc.ServeConn(conn) + } + } +} + +// TEMP: this only ever needs to be run once. +// but it wont hurt to run it once everytime nanobox server starts +// this can be removed once everyone is >= 2.1.0 +func updateUpdater() { + // update.Server = true + update.Name = strings.Replace(update.Name, "nanobox", "nanobox-update", 1) + update.TmpName = strings.Replace(update.TmpName, "nanobox", "nanobox-update", 1) + path, err := exec.LookPath(os.Args[0]) + path = strings.Replace(path, "nanobox", "nanobox-update", 1) + if err != nil { + return + } + log.Println(update.Run(path)) +} + +// run a client request to the rpc server +func ClientRun(funcName string, args interface{}, response interface{}) error { + // log.Printf("clientcall: %s %#v %#v\n", funcName, args, response) + client, err := rpc.Dial("tcp", "127.0.0.1:23456") + if err != nil { + return err + } + + err = client.Call(funcName, args, response) + if err != nil { + return err + } + + return nil +} diff --git a/commands/server/svc_unix.go b/commands/server/svc_unix.go new file mode 100644 index 00000000..ebe55aa6 --- /dev/null +++ b/commands/server/svc_unix.go @@ -0,0 +1,9 @@ +// +build !windows + +package server + +import () + +func svcStart() { + +} diff --git a/commands/server/svc_windows.go b/commands/server/svc_windows.go new file mode 100644 index 00000000..f86268d3 --- /dev/null +++ b/commands/server/svc_windows.go @@ -0,0 +1,63 @@ +package server + +import ( + "fmt" + "os" + "time" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/debug" + "golang.org/x/sys/windows/svc/eventlog" +) + +var elog debug.Log + +type nanoboxServer struct{} + +func (ns *nanoboxServer) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { + elog.Info(1, fmt.Sprintf("execute called")) + const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + elog.Info(1, fmt.Sprintf("running")) + +loop: + for { + select { + case c := <-r: + switch c.Cmd { + case svc.Interrogate: + changes <- c.CurrentStatus + // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4 + time.Sleep(100 * time.Millisecond) + changes <- c.CurrentStatus + case svc.Stop, svc.Shutdown: + break loop + default: + elog.Error(1, fmt.Sprintf("unexpected control request #%#v", c)) + } + } + } + changes <- svc.Status{State: svc.StopPending} + return +} + +func svcStart() { + var err error + elog, err = eventlog.Open(name) + if err != nil { + return + } + + defer elog.Close() + + elog.Info(1, fmt.Sprintf("starting %s service", name)) + err = svc.Run(name, &nanoboxServer{}) + if err != nil { + elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err)) + return + } + elog.Info(1, fmt.Sprintf("%s service stopped", name)) + + // on windows when i get here the service has been stopped so we exit + os.Exit(0) +} diff --git a/commands/start.go b/commands/start.go index 081731d2..ee025eaa 100644 --- a/commands/start.go +++ b/commands/start.go @@ -5,9 +5,10 @@ import ( "github.com/nanobox-io/nanobox/commands/steps" "github.com/nanobox-io/nanobox/processors" + "github.com/nanobox-io/nanobox/processors/provider/bridge" "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/provider" - "github.com/nanobox-io/nanobox/util/provider/bridge" + "github.com/nanobox-io/nanobox/util/service" ) var ( @@ -31,5 +32,9 @@ func startFn(ccmd *cobra.Command, args []string) { } func startCheck() bool { - return provider.IsReady() && bridge.Connected() + bridgeReady := true + if provider.BridgeRequired() { + bridgeReady = bridge.Connected() + } + return provider.IsReady() && service.Running("nanobox-server") && bridgeReady } diff --git a/commands/version.go b/commands/version.go index 83ff8345..fe8b30b8 100644 --- a/commands/version.go +++ b/commands/version.go @@ -22,7 +22,7 @@ var ( // versionFn ... func versionFn(ccmd *cobra.Command, args []string) { - v := "2.0.4" + v := "2.1.0" update, _ := models.LoadUpdate() md5Parts := strings.Fields(update.CurrentVersion) md5 := "" diff --git a/main.go b/main.go index 35cdbbbb..28344dd7 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/nanobox-io/nanobox/commands" "github.com/nanobox-io/nanobox/models" "github.com/nanobox-io/nanobox/processors" + proc_provider "github.com/nanobox-io/nanobox/processors/provider" "github.com/nanobox-io/nanobox/util" "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/display" @@ -34,6 +35,28 @@ func (bugLog) Printf(fmt string, v ...interface{}) { // main func main() { + // setup a file logger, this will be replaced in verbose mode. + fileLogger, err := lumber.NewTruncateLogger(filepath.ToSlash(filepath.Join(config.GlobalDir(), "nanobox.log"))) + if err != nil { + fmt.Println("logging error:", err) + } + + // + lumber.SetLogger(fileLogger) + lumber.Level(lumber.INFO) + defer lumber.Close() + + // if it is running the server just run it + // skip the tratiotional messaging + if len(os.Args) == 2 && os.Args[1] == "server" { + err := commands.NanoboxCmd.Execute() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + return + } + // verify that we support the prompt they are using if badTerminal() { display.BadTerminal() @@ -42,7 +65,7 @@ func main() { // do the commands configure check here because we need it to happen before setupBugsnag creates the config command := strings.Join(os.Args, " ") - if _, err := models.LoadConfig(); err != nil && !strings.Contains(command, " config") { + if _, err := models.LoadConfig(); err != nil && !strings.Contains(command, " config") && !strings.Contains(command, "env server") { processors.Configure() } @@ -57,25 +80,15 @@ func main() { providerName := configModel.Provider // make sure nanobox has all the necessry parts - if !strings.Contains(command, " config") { + if !strings.Contains(command, " config") && !strings.Contains(command, " server") { valid, missingParts := provider.Valid() if !valid { + display.MissingDependencies(providerName, missingParts) os.Exit(1) } } - // setup a file logger, this will be replaced in verbose mode. - fileLogger, err := lumber.NewAppendLogger(filepath.ToSlash(filepath.Join(config.GlobalDir(), "nanobox.log"))) - if err != nil { - fmt.Println("logging error:", err) - } - - // - lumber.SetLogger(fileLogger) - lumber.Level(lumber.INFO) - defer lumber.Close() - // global panic handler; this is done to avoid showing any panic output if // something happens to fail. The output is logged and "pretty" message is // shown @@ -116,7 +129,7 @@ func setupBugsnag() { APIKey: bugsnagToken, Logger: bugLog{}, Synchronous: true, - AppVersion: "2.0.4", + AppVersion: "2.1.0", PanicHandler: func() {}, // the built in panic handler reexicutes our code }) @@ -200,9 +213,21 @@ func migrationCheck() { // on implode success // adjust the provider to the new one and save the provider model + config.Provider = newProviderName config.Save() providerModel.Name = newProviderName providerModel.Save() + + // unset all the docer variables and re init the docker client + os.Unsetenv("DOCKER_TLS_VERIFY") + os.Unsetenv("DOCKER_MACHINE_NAME") + os.Unsetenv("DOCKER_HOST") + os.Unsetenv("DOCKER_CERT_PATH") + + if err := proc_provider.Init(); err != nil { + os.Exit(0) + } + } diff --git a/models/config.go b/models/config.go index 39dfff4a..d96c454d 100644 --- a/models/config.go +++ b/models/config.go @@ -8,7 +8,7 @@ import ( // Config ... type Config struct { Provider string `json:"provider"` - CIMode bool `json:"ci-mode"` + CIMode bool `json:"ci-mode"` // required for docker-machine MountType string `json:"mount-type"` diff --git a/processors/app/dns/add.go b/processors/app/dns/add.go index 21301433..a85977ea 100644 --- a/processors/app/dns/add.go +++ b/processors/app/dns/add.go @@ -1,13 +1,13 @@ package dns import ( - "fmt" + // "fmt" "github.com/jcelliott/lumber" "github.com/nanobox-io/nanobox/models" + "github.com/nanobox-io/nanobox/processors/server" "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/dns" ) @@ -34,9 +34,9 @@ func Add(envModel *models.Env, appModel *models.App, name string) error { return nil } - // ensure we're running as the administrator for this - if !util.IsPrivileged() { - return reExecPrivilegedAdd(appModel, name) + // make sure the server is running since it will do the dns addition + if err := server.Setup(); err != nil { + return util.ErrorAppend(err, "failed to setup server") } // add the entry @@ -45,26 +45,7 @@ func Add(envModel *models.Env, appModel *models.App, name string) error { return util.ErrorAppend(err, "unable to add dns entry") } - fmt.Printf("\n%s %s added\n\n", display.TaskComplete, name) - - return nil -} - -// reExecPrivilegedAdd re-execs the current process with a privileged user -func reExecPrivilegedAdd(appModel *models.App, name string) error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to modify host dns entries") - - // call 'dev dns add' with the original path and args - cmd := fmt.Sprintf("%s dns add %s %s", config.NanoboxPath(), appModel.DisplayName(), name) - - // if the sudo'ed subprocess fails, we need to return error to stop the process - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("dns:reExecPrivilegedAdd:util.PrivilegeExec(%s): %s", cmd, err) - return err - } + display.Info("\n%s %s added\n", display.TaskComplete, name) return nil } diff --git a/processors/app/dns/remove.go b/processors/app/dns/remove.go index b84afc55..05dd639e 100644 --- a/processors/app/dns/remove.go +++ b/processors/app/dns/remove.go @@ -1,13 +1,13 @@ package dns import ( - "fmt" + // "fmt" "github.com/jcelliott/lumber" "github.com/nanobox-io/nanobox/models" + "github.com/nanobox-io/nanobox/processors/server" "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/dns" ) @@ -27,9 +27,9 @@ func Remove(a *models.App, name string) error { return nil } - // ensure we're running as the administrator for this - if !util.IsPrivileged() { - return reExecPrivilegedRemove(a, name) + // make sure the server is running since it will do the dns work + if err := server.Setup(); err != nil { + return util.ErrorAppend(err, "failed to setup server") } // remove the entry @@ -38,26 +38,7 @@ func Remove(a *models.App, name string) error { return util.ErrorAppend(err, "unable to add dns entry: %s") } - fmt.Printf("\n%s %s removed\n\n", display.TaskComplete, name) - - return nil -} - -// reExecPrivilegedRemove re-execs the current process with a privileged user -func reExecPrivilegedRemove(a *models.App, name string) error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to modify host dns entries") - - // call 'dev dns add' with the original path and args - cmd := fmt.Sprintf("%s dns rm %s %s", config.NanoboxPath(), a.DisplayName(), name) - - // if the sudo'ed subprocess fails, we need to return error to stop the process - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("dns:reExecPrivilegedRemove:util.PrivilegeExec(%s): %s", cmd, err) - return err - } + display.Info("\n%s %s removed\n", display.TaskComplete, name) return nil } diff --git a/processors/app/dns/remove_all.go b/processors/app/dns/remove_all.go index 7277ffb3..9fe6bb95 100644 --- a/processors/app/dns/remove_all.go +++ b/processors/app/dns/remove_all.go @@ -1,13 +1,13 @@ package dns import ( - "fmt" + // "fmt" "github.com/jcelliott/lumber" "github.com/nanobox-io/nanobox/models" + "github.com/nanobox-io/nanobox/processors/server" "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/dns" ) @@ -17,13 +17,12 @@ func RemoveAll(a *models.App) error { // shortcut if we dont have any entries for this app if len(dns.List(a.ID)) == 0 { - return nil } - // ensure we're running as the administrator for this - if !util.IsPrivileged() { - return reExecPrivilegedRemoveAll(a) + // make sure the server is running since it will do the dns work + if err := server.Setup(); err != nil { + return util.ErrorAppend(err, "failed to setup server") } if err := dns.Remove(a.ID); err != nil { @@ -31,24 +30,6 @@ func RemoveAll(a *models.App) error { return util.ErrorAppend(err, "failed to remove all dns entries") } - return nil -} - -// reExecPrivilegedRemoveAll re-execs the current process with a privileged user -func reExecPrivilegedRemoveAll(a *models.App) error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to modify host dns entries") - - // call 'dev dns add' with the original path and args - cmd := fmt.Sprintf("%s dns rm-all %s", config.NanoboxPath(), a.DisplayName()) - - // if the sudo'ed subprocess fails, we need to return error to stop the process - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("dns:reExecPrivilegedRemoveAll:util.PrivilegeExec(%s): %s", cmd, err) - return err - } - + display.Info("\n%s removed all\n", display.TaskComplete) return nil } diff --git a/processors/env/share/add.go b/processors/env/share/add.go index 656e14ab..54a43ccc 100644 --- a/processors/env/share/add.go +++ b/processors/env/share/add.go @@ -1,29 +1,20 @@ package share import ( - "fmt" - "github.com/jcelliott/lumber" "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/provider/share" ) // Add adds a share share to the workstation func Add(path string) error { - // short-circuit if the entry already exist - if share.Exists(path) { - return nil - } - - // if we're not running as the privileged user, we need to re-exec with privilege - if !util.IsPrivileged() { - - return reExecPrivilegedAdd(path) - } + // since we dont + // // short-circuit if the entry already exist + // if share.Exists(path) { + // return nil + // } // add the share entry if err := share.Add(path); err != nil { @@ -33,24 +24,3 @@ func Add(path string) error { return nil } - -// reExecPrivilegedAdd re-execs the current process with a privileged user -func reExecPrivilegedAdd(path string) error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to modify network shares") - - // call 'dev share add' with the original path and args; config.NanoboxPath() will be the - // currently executing program, so this command will ultimately lead right back - // here - cmd := fmt.Sprintf("%s env share add \"%s\"", config.NanoboxPath(), path) - - // if the escalated subprocess fails, we need to return error to stop the process - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("share:reExecPrivilegedAdd:util.PrivilegeExec(%s): %s", cmd, err) - return err - } - - return nil -} diff --git a/processors/env/share/remove.go b/processors/env/share/remove.go index 75242601..83b0115e 100644 --- a/processors/env/share/remove.go +++ b/processors/env/share/remove.go @@ -1,13 +1,9 @@ package share import ( - "fmt" - "github.com/jcelliott/lumber" "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/provider/share" ) @@ -19,36 +15,10 @@ func Remove(path string) error { return nil } - // if we're not running as the privileged user, we need to re-exec with privilege - if !util.IsPrivileged() { - return reExecPrivilegedRemove(path) - } - // rm the share entry if err := share.Remove(path); err != nil { lumber.Error("share:Add:share.Remove(%s): %s", path, err.Error()) - return util.ErrorAppend(err, "failed to remove share share: %s") - } - - return nil -} - -// reExecPrivilegedRemove re-execs the current process with a privileged user -func reExecPrivilegedRemove(path string) error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to modify network shares") - - // call 'dev share add' with the original path and args; config.NanoboxPath() will be the - // currently executing program, so this command will ultimately lead right back - // here - cmd := fmt.Sprintf("%s env share rm %s", config.NanoboxPath(), path) - - // if the escalated subprocess fails, we need to return error to stop the process - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("share:reExecPrivilegedRemove:util.PrivilegeExec(%s): %s", cmd, err) - return err + return util.ErrorAppend(err, "failed to remove share share") } return nil diff --git a/processors/implode.go b/processors/implode.go index 33045cc1..100430fa 100644 --- a/processors/implode.go +++ b/processors/implode.go @@ -8,6 +8,7 @@ import ( "github.com/nanobox-io/nanobox/models" "github.com/nanobox-io/nanobox/processors/env" "github.com/nanobox-io/nanobox/processors/provider" + "github.com/nanobox-io/nanobox/processors/server" "github.com/nanobox-io/nanobox/util" "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/display" @@ -39,8 +40,15 @@ func Implode() error { return util.ErrorAppend(err, "failed to implode the provider") } - // purge the installation + // check to see if we need to uninstall nanobox + // or just remove apps if registry.GetBool("full-implode") { + + // teardown the server + if err := server.Teardown(); err != nil { + return util.ErrorAppend(err, "failed to remove server") + } + purgeConfiguration() } diff --git a/processors/provider/bridge/bridge.go b/processors/provider/bridge/bridge.go new file mode 100644 index 00000000..8c007224 --- /dev/null +++ b/processors/provider/bridge/bridge.go @@ -0,0 +1,96 @@ +package bridge + +import ( + "fmt" + "path/filepath" + // "runtime" + "net" + + "github.com/nanobox-io/nanobox/util/config" + "github.com/nanobox-io/nanobox/util/dhcp" + "github.com/nanobox-io/nanobox/util/provider" +) + +func BridgeConfig() string { + // node := "" + // if runtime.GOOS == "windows" { + // node = "dev-node MyTap" + // } + + ip, _ := provider.HostIP() + return fmt.Sprintf(`client + +dev tap +proto udp +remote %s 1194 +resolv-retry infinite +nobind +persist-key +persist-tun + +ca "%s" +cert "%s" +key "%s" + +cipher none +auth none +verb 3 +`, ip, CaCrt(), ClientCrt(), ClientKey()) +} + +func ConfigFile() string { + return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "openvpn.conf")) +} + +func CaCrt() string { + return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "ca.crt")) +} + +func ClientKey() string { + return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "client.key")) +} + +func ClientCrt() string { + return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "client.crt")) +} + +// check to see if the bridge is connected +func Connected() bool { + network, err := dhcp.LocalNet() + if err != nil { + return false + } + interfaces, err := net.Interfaces() + if err != nil { + return false + } + + // look throught the interfaces on the system + for _, i := range interfaces { + addrs, err := i.Addrs() + if err != nil { + continue + } + + // find all the addresses assigned to the interface + for _, addr := range addrs { + ip, _, err := net.ParseCIDR(addr.String()) + if err != nil { + continue + } + + // check to see if the ip address is in our network + if network.Contains(ip) { + + // now check to see if that interface is up + if i.Flags&net.FlagUp != net.FlagUp { + return false + } + + return true + } + } + } + + return false +} diff --git a/processors/provider/bridge/setup.go b/processors/provider/bridge/setup.go index e8cded62..24cd6730 100644 --- a/processors/provider/bridge/setup.go +++ b/processors/provider/bridge/setup.go @@ -18,7 +18,6 @@ import ( "github.com/nanobox-io/nanobox/util/config" "github.com/nanobox-io/nanobox/util/hookit" "github.com/nanobox-io/nanobox/util/locker" - "github.com/nanobox-io/nanobox/util/provider/bridge" ) var keys map[string]string @@ -29,7 +28,7 @@ func Setup() error { defer display.CloseContext() // if the container exists and openvpn is running - if bridge.Connected() { + if Connected() { return nil } @@ -159,7 +158,7 @@ func configureBridge() error { } // create config file - if err := ioutil.WriteFile(bridge.ConfigFile(), []byte(bridge.BridgeConfig()), 0644); err != nil { + if err := ioutil.WriteFile(ConfigFile(), []byte(BridgeConfig()), 0644); err != nil { return err } @@ -169,16 +168,16 @@ func configureBridge() error { } // create ca.crt - if err := ioutil.WriteFile(bridge.CaCrt(), []byte(keys["ca.crt"]), 0644); err != nil { + if err := ioutil.WriteFile(CaCrt(), []byte(keys["ca.crt"]), 0644); err != nil { return err } // create client.key - if err := ioutil.WriteFile(bridge.ClientKey(), []byte(keys["client.key"]), 0644); err != nil { + if err := ioutil.WriteFile(ClientKey(), []byte(keys["client.key"]), 0644); err != nil { return err } // create client.crt - if err := ioutil.WriteFile(bridge.ClientCrt(), []byte(keys["client.crt"]), 0644); err != nil { + if err := ioutil.WriteFile(ClientCrt(), []byte(keys["client.crt"]), 0644); err != nil { return err } diff --git a/processors/provider/bridge/start.go b/processors/provider/bridge/start.go index 837796f8..99a36e73 100644 --- a/processors/provider/bridge/start.go +++ b/processors/provider/bridge/start.go @@ -1,53 +1,10 @@ package bridge import ( - "fmt" - - "github.com/jcelliott/lumber" - - "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/provider/bridge" ) +// ask the server to start the bridge func Start() error { - - if util.IsPrivileged() { - - // create service - if err := bridge.CreateService(); err != nil { - return util.ErrorAppend(err, "failed to create service") - } - - // start service - if err := bridge.StartService(); err != nil { - return util.ErrorAppend(err, "failed to start service") - } - - } else { - - // escalate - return reExecPrivilageStart() - - } - - return nil -} - -// reExecPrivilageStart re-execs the current process with a privileged user -func reExecPrivilageStart() error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to start the vpn") - - cmd := fmt.Sprintf("%s env bridge start", config.NanoboxPath()) - - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("bridge:reExecPrivilageStart:util.PrivilegeExec(%s): %s", cmd, err) - return err - } - - return nil + return bridge.Start(ConfigFile()) } diff --git a/processors/provider/bridge/stop.go b/processors/provider/bridge/stop.go index 90cddf27..4dd1a9c6 100644 --- a/processors/provider/bridge/stop.go +++ b/processors/provider/bridge/stop.go @@ -1,46 +1,10 @@ package bridge import ( - "fmt" - - "github.com/jcelliott/lumber" - - "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/display" "github.com/nanobox-io/nanobox/util/provider/bridge" ) +// ask the server to stop the bridge func Stop() error { - - if util.IsPrivileged() { - - // start service - return bridge.StopService() - - } else { - - // escalate - return reExecPrivilageStop() - - } - - return nil -} - -// reExecPrivilageStart re-execs the current process with a privileged user -func reExecPrivilageStop() error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("to stop the vpn") - - cmd := fmt.Sprintf("%s env bridge stop", config.NanoboxPath()) - - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("bridge:reExecPrivilageStart:util.PrivilegeExec(%s): %s", cmd, err) - return err - } - - return nil + return bridge.Stop() } diff --git a/processors/provider/bridge/teardown.go b/processors/provider/bridge/teardown.go index dcc859b5..724f4541 100644 --- a/processors/provider/bridge/teardown.go +++ b/processors/provider/bridge/teardown.go @@ -1,36 +1,26 @@ package bridge import ( - "fmt" - "github.com/jcelliott/lumber" "github.com/nanobox-io/golang-docker-client" container_generator "github.com/nanobox-io/nanobox/generators/containers" "github.com/nanobox-io/nanobox/util" - "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/display" - "github.com/nanobox-io/nanobox/util/provider/bridge" ) func Teardown() error { + if !Connected() { + return nil + } - if util.IsPrivileged() { - // remove bridge client - if err := Stop(); err != nil { - return err - } - - if err := bridge.Remove(); err != nil { - return err - } + // remove bridge client + if err := Stop(); err != nil { + return err + } - } else { - // remove component - if err := removeComponent(); err != nil { - return err - } - return reExecTeardown() + // remove component + if err := removeComponent(); err != nil { + return err } return nil @@ -54,20 +44,3 @@ func removeComponent() error { return nil } - -// reExecTeardown re-execs the current process with a privileged user -func reExecTeardown() error { - display.PauseTask() - defer display.ResumeTask() - - display.PrintRequiresPrivilege("remove the vpn") - - cmd := fmt.Sprintf("%s env bridge teardown", config.NanoboxPath()) - - if err := util.PrivilegeExec(cmd); err != nil { - lumber.Error("bridge:reExecTeardown:util.PrivilegeExec(%s): %s", cmd, err) - return err - } - - return nil -} diff --git a/processors/provider/stop.go b/processors/provider/stop.go index 98406309..12302ee3 100644 --- a/processors/provider/stop.go +++ b/processors/provider/stop.go @@ -20,7 +20,9 @@ func Stop() error { // stop the vpn if err := bridge.Stop(); err != nil { - return util.ErrorAppend(err, "failed to stop vpn") + // do nothing about the error since provider stop happens + // then we shut down the server (killing the bridge) + // return util.ErrorAppend(err, "failed to stop vpn") } // stop the provider (VM) diff --git a/processors/run.go b/processors/run.go index 9f19cbc4..49d3cdab 100644 --- a/processors/run.go +++ b/processors/run.go @@ -1,6 +1,7 @@ package processors import ( + "runtime" "time" "github.com/jcelliott/lumber" @@ -167,12 +168,20 @@ func downloadImage(image string) error { func watchFiles(envModel *models.Env, appModel *models.App) { boxfile := boxfile.New([]byte(appModel.DeployedBoxfile)) - if boxfile.Node("run.config").BoolValue("fs_watch") && provider.RequiresMount() { + if boxfile.Node("run.config").BoolValue("fs_watch") && (provider.RequiresMount() || specialException()) { lumber.Info("watcher starting") go watch.Watch(container_generator.DevName(), envModel.Directory) } } +// TEMP: this is added because osxfs on native doesnt propigate file changes +// this will be removed when osxfs gets better or we switch native on osx to +// use nfs +func specialException() bool { + config, _ := models.LoadConfig() + return runtime.GOOS == "darwin" && config.Provider == "native" +} + // cwd sets the cwd from the boxfile or provides a sensible default func cwd(appModel *models.App) string { boxfile := boxfile.New([]byte(appModel.DeployedBoxfile)) diff --git a/processors/server/setup.go b/processors/server/setup.go new file mode 100644 index 00000000..ef860210 --- /dev/null +++ b/processors/server/setup.go @@ -0,0 +1,55 @@ +package server + +import ( + "fmt" + + "github.com/jcelliott/lumber" + + "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/util/config" + "github.com/nanobox-io/nanobox/util/display" + "github.com/nanobox-io/nanobox/util/service" +) + +func Setup() error { + if service.Running("nanobox-server") { + return nil + } + + // run as admin + if !util.IsPrivileged() { + return reExecPrivilageStart() + } + + // TEMP: we need to remove the old nanobox-vpn just incase it is left over + // we will not catch errors here because if it doesnt exist or it breaks it + // should not stop us from creating the new nanobox-server + service.Stop("nanobox-vpn") + service.Remove("nanobox-vpn") + + // create the service this call is idempotent so we shouldnt need to check + if err := service.Create("nanobox-server", []string{config.NanoboxPath(), "server"}); err != nil { + return err + } + + // start the service + return Start() + +} + +// reExecPrivilageStart re-execs the current process with a privileged user +func reExecPrivilageStart() error { + display.PauseTask() + defer display.ResumeTask() + + display.PrintRequiresPrivilege("to start the server") + + cmd := fmt.Sprintf("%s env server start", config.NanoboxPath()) + + if err := util.PrivilegeExec(cmd); err != nil { + lumber.Error("server:reExecPrivilageStart:util.PrivilegeExec(%s): %s", cmd, err) + return err + } + + return nil +} diff --git a/processors/server/start.go b/processors/server/start.go new file mode 100644 index 00000000..8653fba1 --- /dev/null +++ b/processors/server/start.go @@ -0,0 +1,24 @@ +package server + +import ( + // "time" + + "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/util/service" +) + +func Start() error { + // run as admin + // the reExecPrivilageStart function is defined in the setup + // since the service create is idempotent it is fine to only have one + // start command for the server + if !util.IsPrivileged() { + return reExecPrivilageStart() + } + + // fn := func() error { + // return service.Start("nanobox-server") + // } + + return service.Start("nanobox-server") +} diff --git a/processors/server/stop.go b/processors/server/stop.go new file mode 100644 index 00000000..b57d2af0 --- /dev/null +++ b/processors/server/stop.go @@ -0,0 +1,38 @@ +package server + +import ( + "fmt" + + "github.com/jcelliott/lumber" + + "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/util/config" + "github.com/nanobox-io/nanobox/util/display" + "github.com/nanobox-io/nanobox/util/service" +) + +func Stop() error { + // run as admin + if !util.IsPrivileged() { + return reExecPrivilageStop() + } + + return service.Stop("nanobox-server") +} + +// reExecPrivilageStop re-execs the current process with a privileged user +func reExecPrivilageStop() error { + display.PauseTask() + defer display.ResumeTask() + + display.PrintRequiresPrivilege("to stop the server") + + cmd := fmt.Sprintf("%s env server stop", config.NanoboxPath()) + + if err := util.PrivilegeExec(cmd); err != nil { + lumber.Error("server:reExecPrivilageStop:util.PrivilegeExec(%s): %s", cmd, err) + return err + } + + return nil +} diff --git a/processors/server/teardown.go b/processors/server/teardown.go new file mode 100644 index 00000000..0ce5ebb5 --- /dev/null +++ b/processors/server/teardown.go @@ -0,0 +1,43 @@ +package server + +import ( + "fmt" + + "github.com/jcelliott/lumber" + + "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/util/config" + "github.com/nanobox-io/nanobox/util/display" + "github.com/nanobox-io/nanobox/util/service" +) + +func Teardown() error { + // run as admin + if !util.IsPrivileged() { + return reExecPrivilageRemove() + } + + // make sure its stopped first + if err := Stop(); err != nil { + return err + } + + return service.Remove("nanobox-server") +} + +// reExecPrivilageRemove re-execs the current process with a privileged user +func reExecPrivilageRemove() error { + display.PauseTask() + defer display.ResumeTask() + + display.PrintRequiresPrivilege("to remove the server") + + cmd := fmt.Sprintf("%s env server teardown", config.NanoboxPath()) + + if err := util.PrivilegeExec(cmd); err != nil { + lumber.Error("server:reExecPrivilageRemove:util.PrivilegeExec(%s): %s", cmd, err) + return err + } + + return nil +} diff --git a/processors/start.go b/processors/start.go index 800c9458..c07e9727 100644 --- a/processors/start.go +++ b/processors/start.go @@ -2,10 +2,16 @@ package processors import ( "github.com/nanobox-io/nanobox/processors/provider" + "github.com/nanobox-io/nanobox/processors/server" ) // Start starts the provider (VM) func Start() error { + // start the nanobox server + if err := server.Setup(); err != nil { + return err + } + // run a provider setup return provider.Setup() } diff --git a/processors/stop.go b/processors/stop.go index e3f001bb..aff20d27 100644 --- a/processors/stop.go +++ b/processors/stop.go @@ -7,6 +7,7 @@ import ( "github.com/nanobox-io/nanobox/processors/app" "github.com/nanobox-io/nanobox/processors/env" "github.com/nanobox-io/nanobox/processors/provider" + "github.com/nanobox-io/nanobox/processors/server" "github.com/nanobox-io/nanobox/util" "github.com/nanobox-io/nanobox/util/display" util_provider "github.com/nanobox-io/nanobox/util/provider" @@ -40,6 +41,11 @@ func Stop() error { return util.ErrorAppend(err, "failed to stop the provider") } + // stop the server + if err := server.Stop(); err != nil { + return util.ErrorAppend(err, "failed to stop server") + } + return nil } diff --git a/updater/main.go b/updater/main.go index 58d642e9..71420092 100644 --- a/updater/main.go +++ b/updater/main.go @@ -9,6 +9,7 @@ import ( "github.com/nanobox-io/nanobox/models" "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/util/service" "github.com/nanobox-io/nanobox/util/update" ) @@ -55,6 +56,9 @@ func main() { return } + // stop the nanobox service so we can replace the nanobox binary + service.Stop("nanobox-server") + // run the update err = update.Run(path) if err != nil { diff --git a/util/config/vars.go b/util/config/vars.go index 48cb297a..0b7a826a 100644 --- a/util/config/vars.go +++ b/util/config/vars.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "os/exec" + "path/filepath" + "runtime" "github.com/nanobox-io/nanobox-boxfile" @@ -53,3 +55,22 @@ func NanoboxPath() string { // unable to find the full path, just return what was called return programName } + +// the path where the vpn is located +func VpnPath() string { + bridgeClient := "nanobox-vpn" + + // lookup the full path to nanobox + path, err := exec.LookPath(bridgeClient) + if err == nil { + return path + } + + cmd := filepath.Join(BinDir(), bridgeClient) + + if runtime.GOOS == "windows" { + cmd = fmt.Sprintf(`%s\%s.exe`, BinDir(), bridgeClient) + } + + return cmd +} diff --git a/util/display/display.go b/util/display/display.go index 08f889da..fc1de5ee 100644 --- a/util/display/display.go +++ b/util/display/display.go @@ -35,7 +35,7 @@ var ( Mode = "text" // Out - writer to send output to - Out = os.Stdout + Out io.Writer = os.Stdout // internal logFile *os.File // open file descriptor of the log file diff --git a/util/dns/dns.go b/util/dns/dns.go index 6ab5cfb8..e09684d4 100644 --- a/util/dns/dns.go +++ b/util/dns/dns.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "os" "strings" + + "github.com/nanobox-io/nanobox/commands/server" ) type DomainName struct { @@ -14,11 +16,26 @@ type DomainName struct { Domain string } +type DomainRPC struct{} + +type Request struct { + Entry string +} + +type Response struct { + Message string + Success bool +} + var ( hostsFile = detectHostsFile() newline = detectNewlineChar() ) +func init() { + server.Register(&DomainRPC{}) +} + // Entry generate the DNS entry to be added func Entry(ip, name, env string) string { return fmt.Sprintf("%s %s # dns added for '%s' by nanobox", ip, name, env) @@ -80,6 +97,20 @@ func Add(entry string) error { return nil } + req := Request{entry} + resp := &Response{} + + err := server.ClientRun("DomainRPC.Add", req, resp) + if !resp.Success { + err = fmt.Errorf("failed to add domain: %v %v", err, resp.Message) + } + + return err +} + +// the rpc function run from the server +func (drpc *DomainRPC) Add(req Request, resp *Response) error { + // open hosts file f, err := os.OpenFile(hostsFile, os.O_RDWR|os.O_APPEND, 0644) if err != nil { @@ -88,10 +119,11 @@ func Add(entry string) error { defer f.Close() // write the DNS entry to the file - if _, err := f.WriteString(fmt.Sprintf("%s%s", entry, newline)); err != nil { + if _, err := f.WriteString(fmt.Sprintf("%s%s", req.Entry, newline)); err != nil { return err } + resp.Success = true return nil } @@ -100,6 +132,18 @@ func Remove(entry string) error { if entry == "" { return nil } + req := Request{entry} + resp := &Response{} + + err := server.ClientRun("DomainRPC.Remove", req, resp) + if !resp.Success { + err = fmt.Errorf("failed to remove domain: %v %v", err, resp.Message) + } + return nil +} + +// the rpc function run from the server +func (drpc *DomainRPC) Remove(req Request, resp *Response) error { // "contents" will end up storing the entire contents of the file excluding the // entry that is trying to be removed @@ -121,7 +165,7 @@ func Remove(entry string) error { // if its exactly the entry then remove it. // if it contains the same environment // also remove it - if strings.Contains(scanner.Text(), entry) { + if strings.Contains(scanner.Text(), req.Entry) { continue } @@ -140,7 +184,9 @@ func Remove(entry string) error { return err } + resp.Success = true return nil + } // RemoveAll removes all dns entries added by nanobox diff --git a/util/provider/bridge/bridge.go b/util/provider/bridge/bridge.go index ae84a056..8177eeae 100644 --- a/util/provider/bridge/bridge.go +++ b/util/provider/bridge/bridge.go @@ -1,98 +1,20 @@ package bridge import ( - "fmt" - "path/filepath" - // "runtime" - "net" - - "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/dhcp" - "github.com/nanobox-io/nanobox/util/provider" + "github.com/nanobox-io/nanobox/commands/server" + "os/exec" ) -var BridgeClient = "nanobox-vpn" - -func BridgeConfig() string { - // node := "" - // if runtime.GOOS == "windows" { - // node = "dev-node MyTap" - // } - - ip, _ := provider.HostIP() - return fmt.Sprintf(`client - -dev tap -proto udp -remote %s 1194 -resolv-retry infinite -nobind -persist-key -persist-tun - -ca "%s" -cert "%s" -key "%s" - -cipher none -auth none -verb 3 -`, ip, CaCrt(), ClientCrt(), ClientKey()) -} - -func ConfigFile() string { - return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "openvpn.conf")) -} - -func CaCrt() string { - return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "ca.crt")) -} +type Bridge struct{} -func ClientKey() string { - return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "client.key")) +// not being used yet. but could be +type Response struct { + Output string + ExitCode int } -func ClientCrt() string { - return filepath.ToSlash(filepath.Join(config.EtcDir(), "openvpn", "client.crt")) -} - -// check to see if the bridge is connected -func Connected() bool { - network, err := dhcp.LocalNet() - if err != nil { - return false - } - interfaces, err := net.Interfaces() - if err != nil { - return false - } - - // look throught the interfaces on the system - for _, i := range interfaces { - addrs, err := i.Addrs() - if err != nil { - continue - } - - // find all the addresses assigned to the interface - for _, addr := range addrs { - ip, _, err := net.ParseCIDR(addr.String()) - if err != nil { - continue - } - - // check to see if the ip address is in our network - if network.Contains(ip) { - - // now check to see if that interface is up - if i.Flags&net.FlagUp != net.FlagUp { - return false - } - - return true - } - } - } +var runningBridge *exec.Cmd - return false +func init() { + server.Register(&Bridge{}) } diff --git a/util/provider/bridge/start.go b/util/provider/bridge/start.go index 74331a4b..e634ba92 100644 --- a/util/provider/bridge/start.go +++ b/util/provider/bridge/start.go @@ -2,28 +2,36 @@ package bridge import ( "fmt" - "path/filepath" - "runtime" - "time" + "os" + "os/exec" - "github.com/nanobox-io/nanobox/util" + "github.com/nanobox-io/nanobox/commands/server" "github.com/nanobox-io/nanobox/util/config" - "github.com/nanobox-io/nanobox/util/service" ) -func CreateService() error { - cmd := []string{filepath.Join(config.BinDir(), BridgeClient), "--config", ConfigFile()} +func Start(conf string) error { + resp := &Response{} - if runtime.GOOS == "windows" { - cmd = []string{fmt.Sprintf(`"%s\%s.exe"`, config.BinDir(), BridgeClient), "--config", fmt.Sprintf(`"%s"`, ConfigFile())} + return server.ClientRun("Bridge.Start", conf, resp) +} + +func (br *Bridge) Start(conf string, resp *Response) error { + + if runningBridge != nil { + // if asked to start but we are already running + // lets teardown the old and recreate with the new conf + br.Stop(conf, resp) + // return nil } - return service.Create("nanobox-vpn", cmd) -} + runningBridge = exec.Command(config.VpnPath(), "--config", conf) + runningBridge.Stdout = os.Stdout + runningBridge.Stderr = os.Stderr -func StartService() error { - fn := func() error { - return service.Start("nanobox-vpn") + err := runningBridge.Start() + if err != nil { + runningBridge = nil + err = fmt.Errorf("failed to start cmd(%s --config %s): %s", config.VpnPath(), conf, err) } - return util.Retry(fn, 2, 10*time.Second) + return err } diff --git a/util/provider/bridge/stop.go b/util/provider/bridge/stop.go index 203d977b..7de3a4bd 100644 --- a/util/provider/bridge/stop.go +++ b/util/provider/bridge/stop.go @@ -1,13 +1,32 @@ package bridge import ( - "github.com/nanobox-io/nanobox/util/service" + "github.com/nanobox-io/nanobox/commands/server" ) -func StopService() error { - return service.Stop("nanobox-vpn") +func Stop() error { + resp := &Response{} + + return server.ClientRun("Bridge.Stop", "", resp) } -func Remove() error { - return service.Remove("nanobox-vpn") +func (br *Bridge) Stop(config string, resp *Response) error { + if runningBridge == nil { + return nil + } + + if err := runningBridge.Process.Kill(); err != nil { + return err + } + + if err := runningBridge.Wait(); err != nil { + // it gets a signal but it shows up as an error + // we dont want that + return nil + } + + // if we killed it and released the resources + // remove running bridge + runningBridge = nil + return nil } diff --git a/util/provider/dockermachine_mount_windows.go b/util/provider/dockermachine_mount_windows.go index 81b18d97..1d1be5af 100644 --- a/util/provider/dockermachine_mount_windows.go +++ b/util/provider/dockermachine_mount_windows.go @@ -52,7 +52,7 @@ func (machine DockerMachine) addNetfsMount(local, host string) error { } // mount! - // mount -t cifs -o username=USER,password=PASSWORD //192.168.99.1/APP /PATH + // mount -t cifs -o sec=ntlmssp,username=USER,password=PASSWORD,uid=1000,gid=1000 //192.168.99.1/ / source := fmt.Sprintf("//192.168.99.1/nanobox-%s", appID) // mfsymlinks, config, _ := models.LoadConfig() diff --git a/util/provider/share/rpc.go b/util/provider/share/rpc.go new file mode 100644 index 00000000..fe161977 --- /dev/null +++ b/util/provider/share/rpc.go @@ -0,0 +1,16 @@ +package share + +import ( + "github.com/nanobox-io/nanobox/commands/server" +) + +type ShareRPC struct{} + +type Response struct { + Message string + Success bool +} + +func init() { + server.Register(&ShareRPC{}) +} diff --git a/util/provider/share/share_darwin.go b/util/provider/share/share_darwin.go index 58899f63..e5f978cf 100644 --- a/util/provider/share/share_darwin.go +++ b/util/provider/share/share_darwin.go @@ -11,10 +11,18 @@ import ( "github.com/jcelliott/lumber" + "github.com/nanobox-io/nanobox/commands/server" "github.com/nanobox-io/nanobox/models" "github.com/nanobox-io/nanobox/util" ) +type Request struct { + Path string + UID int + GID int + MountIP string +} + // EXPORTSFILE ... var EXPORTSFILE = "/etc/exports" @@ -53,6 +61,38 @@ func Add(path string) error { return err } + // create a rpc request + req := Request{ + Path: path, + UID: uid(), + GID: gid(), + MountIP: provider.MountIP, + } + + resp := &Response{} + + // in testing we will call the rpc function directly + if flag.Lookup("test.v") != nil { + shareRPC := &ShareRPC{} + err := shareRPC.Add(req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + } + + // have the server run the share command + err = server.ClientRun("ShareRPC.Add", req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err +} + +// the rpc function run from the server +func (sh *ShareRPC) Add(req Request, resp *Response) error { + fmt.Printf("req: %#v\n", req) + // read exports file existingFile, err := ioutil.ReadFile(EXPORTSFILE) if err != nil { @@ -60,7 +100,7 @@ func Add(path string) error { existingFile = []byte("") } - lineCheck := fmt.Sprintf("%s -alldirs -mapall=%v:%v", provider.MountIP, uid(), gid()) + lineCheck := fmt.Sprintf("%s -alldirs -mapall=%v:%v", req.MountIP, req.UID, req.GID) lines := strings.Split(string(existingFile), "\n") @@ -69,32 +109,74 @@ func Add(path string) error { // get existing line if strings.Contains(line, lineCheck) { // add our path to the line - lines[i] = fmt.Sprintf("\"%s\" %s", path, line) + // check to see if this path has already been added + if !(strings.Contains(line, req.Path+" ") || strings.Contains(line, req.Path+"\" ")) { + lines[i] = fmt.Sprintf("\"%s\" %s", req.Path, line) + } + lines[i] = cleanLine(lines[i], lineCheck) found = true break } } if !found { - lines = append(lines, fmt.Sprintf("%s %s", path, lineCheck)) + lines = append(lines, fmt.Sprintf("\"%s\" %s", req.Path, lineCheck)) } // save if err := ioutil.WriteFile(EXPORTSFILE, []byte(strings.Join(lines, "\n")), 0644); err != nil { return err } - return reloadServer() + + if err := reloadServer(); err != nil { + return err + } + resp.Success = true + return nil } func Remove(path string) error { - quotedPath := fmt.Sprintf("\"%s\"", path) // get the provider because i need the mount ip provider, err := models.LoadProvider() if err != nil { return err } + // create a rpc request + req := Request{ + Path: path, + UID: uid(), + GID: gid(), + MountIP: provider.MountIP, + } + + resp := &Response{} + + // in testing we will call the rpc function directly + if flag.Lookup("test.v") != nil { + shareRPC := &ShareRPC{} + err := shareRPC.Remove(req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + } + + // have the server run the share command + err = server.ClientRun("ShareRPC.Remove", req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + +} + +// the rpc function run from the server +func (sh *ShareRPC) Remove(req Request, resp *Response) error { + + quotedPath := fmt.Sprintf("\"%s\"", req.Path) + // read exports file existingFile, err := ioutil.ReadFile(EXPORTSFILE) if err != nil { @@ -103,7 +185,7 @@ func Remove(path string) error { return nil } - lineCheck := fmt.Sprintf("%s -alldirs -mapall=%v:%v", provider.MountIP, uid(), gid()) + lineCheck := fmt.Sprintf("%s -alldirs -mapall=%v:%v", req.MountIP, req.UID, req.GID) existingLines := strings.Split(string(existingFile), "\n") newLines := []string{} @@ -116,7 +198,7 @@ func Remove(path string) error { } // recreate the line without our path or quoted path - line = strings.Replace(line, fmt.Sprintf("%s ", path), "", 1) + line = strings.Replace(line, fmt.Sprintf("%s ", req.Path), "", 1) line = strings.Replace(line, fmt.Sprintf("%s ", quotedPath), "", 1) if line != lineCheck { // if there is still any paths left in our line @@ -130,7 +212,11 @@ func Remove(path string) error { return err } - return reloadServer() + err = reloadServer() + if err == nil { + resp.Success = true + } + return err } // reloadServer will reload the nfs server with the new export configuration diff --git a/util/provider/share/share_linux.go b/util/provider/share/share_linux.go index 75e763a6..05889d48 100644 --- a/util/provider/share/share_linux.go +++ b/util/provider/share/share_linux.go @@ -11,12 +11,17 @@ import ( "github.com/jcelliott/lumber" + "github.com/nanobox-io/nanobox/commands/server" "github.com/nanobox-io/nanobox/models" ) // EXPORTSFILE ... var EXPORTSFILE = "/etc/exports" +type Request struct { + Entry string +} + // Exists checks to see if the mount already exists func Exists(path string) bool { @@ -55,8 +60,32 @@ func Add(path string) error { return err } + req := Request{Entry: entry} + resp := &Response{} + + // in testing we will call the rpc function directly + if flag.Lookup("test.v") != nil { + shareRPC := &ShareRPC{} + err := shareRPC.Add(req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + } + + // have the server run the share command + err = server.ClientRun("ShareRPC.Add", req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err +} + +// the rpc function run from the server +func (sh *ShareRPC) Add(req Request, resp *Response) error { + // add entry into the /etc/exports file - if err := addEntry(entry); err != nil { + if err := addEntry(req.Entry); err != nil { return err } @@ -69,6 +98,7 @@ func Add(path string) error { return err } + resp.Success = true return nil } @@ -81,7 +111,30 @@ func Remove(path string) error { return err } - if err := removeEntry(entry); err != nil { + req := Request{Entry: entry} + resp := &Response{} + + // in testing we will call the rpc function directly + if flag.Lookup("test.v") != nil { + shareRPC := &ShareRPC{} + err := shareRPC.Remove(req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + } + + // have the server run the share command + err = server.ClientRun("ShareRPC.Remove", req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err +} + +// the rpc function run from the server +func (sh *ShareRPC) Remove(req Request, resp *Response) error { + if err := removeEntry(req.Entry); err != nil { return err } @@ -90,6 +143,7 @@ func Remove(path string) error { return err } + resp.Success = true return nil } diff --git a/util/provider/share/share_windows.go b/util/provider/share/share_windows.go index 8d91fad0..6ac2f6bc 100644 --- a/util/provider/share/share_windows.go +++ b/util/provider/share/share_windows.go @@ -2,16 +2,23 @@ package share import ( "bytes" - "errors" + "flag" "fmt" "os" "os/exec" "github.com/jcelliott/lumber" + "github.com/nanobox-io/nanobox/commands/server" "github.com/nanobox-io/nanobox/util/config" ) +type Request struct { + Path string + AppID string + User string +} + // Exists checks to see if the share already exists func Exists(path string) bool { @@ -38,18 +45,47 @@ func Exists(path string) bool { // Add exports a cifs share func Add(path string) error { - appID := config.EnvID() - user := os.Getenv("USERNAME") + // on windows we dont want to try adding more then once + if Exists(path) { + return nil + } + + req := Request{ + Path: path, + AppID: config.EnvID(), + User: os.Getenv("USERNAME"), + } + resp := &Response{} + + // in testing we will call the rpc function directly + if flag.Lookup("test.v") != nil { + shareRPC := &ShareRPC{} + err := shareRPC.Add(req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + } + + // have the server run the share command + err := server.ClientRun("ShareRPC.Add", req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + +} - // net share APPNAME=path /unlimited /GRANT:Everyone,FULL +// the rpc function run from the server +func (sh *ShareRPC) Add(req Request, resp *Response) error { // net share APPNAME=path /unlimited /GRANT:%username%,FULL cmd := []string{ "net", "share", - fmt.Sprintf("nanobox-%s=%s", appID, path), + fmt.Sprintf("nanobox-%s=%s", req.AppID, req.Path), "/unlimited", - fmt.Sprintf("/GRANT:%s,FULL", user), + fmt.Sprintf("/GRANT:%s,FULL", req.User), } lumber.Debug("share add: %v", cmd) @@ -63,32 +99,59 @@ func Add(path string) error { // return nil (success) if the command was successful if bytes.Contains(output, []byte("was shared successfully.")) { + resp.Success = true return nil } // if we are here, it might have failed. Lets just check one more time // to see if the share already exists. If so, let's return success (nil) - if Exists(path) { + if Exists(req.Path) { + resp.Success = true return nil } - return errors.New("Failed to create cifs share") + return fmt.Errorf("Failed to create cifs share") } // Remove removes a cifs share func Remove(path string) error { + req := Request{ + Path: path, + AppID: config.EnvID(), + } + resp := &Response{} + + // in testing we will call the rpc function directly + if flag.Lookup("test.v") != nil { + shareRPC := &ShareRPC{} + err := shareRPC.Remove(req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + } - appID := config.EnvID() + // have the server run the share command + err := server.ClientRun("ShareRPC.Remove", req, resp) + if err != nil || !resp.Success { + err = fmt.Errorf("failed to add share %v %v", err, resp.Message) + } + return err + +} +// the rpc function run from the server +func (sh *ShareRPC) Remove(req Request, resp *Response) error { // net share APPNAME /delete /y cmd := []string{ "net", "share", - fmt.Sprintf("nanobox-%s", appID), + fmt.Sprintf("nanobox-%s", req.AppID), "/delete", "/y", } + lumber.Debug("share remove: %v", cmd) process := exec.Command(cmd[0], cmd[1:]...) output, err := process.CombinedOutput() @@ -100,14 +163,17 @@ func Remove(path string) error { // return nil (success) if the command was successful if bytes.Contains(output, []byte("was deleted successfully.")) { + resp.Success = true return nil } // if we are here, it might have failed. Lets just check one more time // to see if the share is already gone. If so, let's return success (nil) - if !Exists(path) { + if !Exists(req.Path) { + resp.Success = true return nil } - return errors.New("Failed to delete cifs share") + return fmt.Errorf("Failed to delete cifs share") + } diff --git a/util/service/create_windows.go b/util/service/create_windows.go index 03d053f5..ae75c83a 100644 --- a/util/service/create_windows.go +++ b/util/service/create_windows.go @@ -1,43 +1,46 @@ package service import ( - "fmt" - "io/ioutil" - "os/exec" - "strings" + // "fmt" + // // "io/ioutil" + // "os/exec" + // "strings" - "github.com/nanobox-io/nanobox/util/config" + "golang.org/x/sys/windows/svc/eventlog" + "golang.org/x/sys/windows/svc/mgr" ) func Create(name string, command []string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() - // make sure we actually have to do this part - if out, _ := exec.Command("sc.exe", "query", name).CombinedOutput(); !strings.Contains(string(out), "service does not exist") { + // check to see if we need to create at all + s, err := m.OpenService(name) + if err == nil { + s.Close() + // jobs done return nil } - // the service may have been created this should clean out any old version - // we arent catching errors just incase they dont exist - Stop(name) - Remove(name) - - // setup config file - if err := ioutil.WriteFile(serviceConfigFile(name), []byte(serviceConfig(name, command)), 0644); err != nil { + // create the service + args := []string{} + if len(command) > 1 { + args = command[1:] + } + s, err = m.CreateService(name, command[0], mgr.Config{DisplayName: name}, args...) + if err != nil { return err } + defer s.Close() - out, err := exec.Command("sc.exe", "create", name, "binpath=", fmt.Sprintf(`%s\srvstart.exe %s -c "%s"`, config.BinDir(), name, serviceConfigFile(name))).CombinedOutput() + err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { - return fmt.Errorf("%s: %s", out, err) + // s.Delete() + // eventlog.Remove(name) + // return fmt.Errorf("SetupEventLogSource() failed: %s", err) } - fmt.Printf("\n\nout: %s\n\n", out) - - return err -} - -func serviceConfig(name string, command []string) string { - return fmt.Sprintf(`[%s] -startup=%s -shutdown_method=winmessage -`, name, strings.Join(command, " ")) + return nil } diff --git a/util/service/remove.go b/util/service/remove_unix.go similarity index 94% rename from util/service/remove.go rename to util/service/remove_unix.go index 8d365994..e3aeb76c 100644 --- a/util/service/remove.go +++ b/util/service/remove_unix.go @@ -1,3 +1,5 @@ +// +build !windows + package service import ( diff --git a/util/service/remove_windows.go b/util/service/remove_windows.go new file mode 100644 index 00000000..26ca4eb8 --- /dev/null +++ b/util/service/remove_windows.go @@ -0,0 +1,34 @@ +package service + +import ( + "fmt" + + "golang.org/x/sys/windows/svc/eventlog" + "golang.org/x/sys/windows/svc/mgr" +) + +func Remove(name string) error { + // connect to the service manager + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err != nil { + // no service exists.. job done + return nil + } + defer s.Close() + + err = s.Delete() + if err != nil { + return err + } + + err = eventlog.Remove(name) + if err != nil { + return fmt.Errorf("RemoveEventLogSource() failed: %s", err) + } + return nil +} diff --git a/util/service/service_darwin.go b/util/service/service_darwin.go index 7805d7ab..96a7ffcb 100644 --- a/util/service/service_darwin.go +++ b/util/service/service_darwin.go @@ -2,6 +2,8 @@ package service import ( "fmt" + "net" + "time" ) func serviceConfigFile(name string) string { @@ -12,8 +14,13 @@ func startCmd(name string) []string { return []string{"launchctl", "start", fmt.Sprintf("io.%s", name)} } -func running(name string) bool { - // there is currently no query mechanism built into launchctl +func Running(name string) bool { + <-time.After(500 * time.Millisecond) + conn, err := net.DialTimeout("tcp", "127.0.0.1:23456", 100*time.Millisecond) + if err != nil { + return false + } + conn.Close() return true } diff --git a/util/service/service_linux.go b/util/service/service_linux.go index 847b0b23..e0834c7f 100644 --- a/util/service/service_linux.go +++ b/util/service/service_linux.go @@ -44,7 +44,7 @@ func startCmd(name string) []string { return nil } -func running(name string) bool { +func Running(name string) bool { switch launchSystem() { case "systemd": out, err := exec.Command("systemctl", "--no-pager", "status", name).CombinedOutput() diff --git a/util/service/service_windows.go b/util/service/service_windows.go index d55f561c..7b97ac36 100644 --- a/util/service/service_windows.go +++ b/util/service/service_windows.go @@ -2,22 +2,12 @@ package service import ( "bytes" - "fmt" "os/exec" - "path/filepath" - - "github.com/nanobox-io/nanobox/util/config" + // "golang.org/x/sys/windows/svc" + // "golang.org/x/sys/windows/svc/mgr" ) -func serviceConfigFile(name string) string { - return filepath.Join(config.BinDir(), fmt.Sprintf("%s-config.ini", name)) -} - -func startCmd(name string) []string { - return []string{"sc.exe", "start", name} -} - -func running(name string) bool { +func Running(name string) bool { out, err := exec.Command("sc.exe", "query", name).CombinedOutput() if err != nil { return false @@ -27,12 +17,24 @@ func running(name string) bool { return false } return true -} - -func stopCmd(name string) []string { - return []string{"sc.exe", "stop", name} -} - -func removeCmd(name string) []string { - return []string{"sc.exe", "delete", name} + // m, err := mgr.Connect() + // if err != nil { + // return false + // } + // defer m.Disconnect() + + // // check to see if we need to create at all + // s, err := m.OpenService(name) + // if err != nil { + // // jobs done + // return false + // } + // defer s.Close() + + // status, err := s.Query() + // if err != nil { + // return false + // } + + // return status.State == svc.Running } diff --git a/util/service/start.go b/util/service/start_unix.go similarity index 64% rename from util/service/start.go rename to util/service/start_unix.go index 7f00637d..ef2ad686 100644 --- a/util/service/start.go +++ b/util/service/start_unix.go @@ -1,3 +1,5 @@ +// +build !windows + package service import ( @@ -12,8 +14,8 @@ func Start(name string) error { fmt.Errorf("out: %s, err: %s", out, err) } - if !running(name) { - return fmt.Errorf("service start was successful but the service is not running") + if !Running(name) { + return fmt.Errorf("%s service start was successful but the service is not running", name) } return nil } diff --git a/util/service/start_windows.go b/util/service/start_windows.go new file mode 100644 index 00000000..5b854675 --- /dev/null +++ b/util/service/start_windows.go @@ -0,0 +1,32 @@ +package service + +import ( + "fmt" + "strings" + + "golang.org/x/sys/windows/svc/mgr" +) + +func Start(name string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + + s, err := m.OpenService(name) + if err != nil { + return fmt.Errorf("could not access service: %v", err) + } + defer s.Close() + + err = s.Start() + if err != nil { + if strings.Contains(err.Error(), "already running") { + return nil + } + return fmt.Errorf("could not start service: %v", err) + } + + return nil +} diff --git a/util/service/stop.go b/util/service/stop_unix.go similarity index 92% rename from util/service/stop.go rename to util/service/stop_unix.go index 9442aafd..f90e523a 100644 --- a/util/service/stop.go +++ b/util/service/stop_unix.go @@ -1,3 +1,5 @@ +// +build !windows + package service import ( diff --git a/util/service/stop_windows.go b/util/service/stop_windows.go new file mode 100644 index 00000000..b41acb9a --- /dev/null +++ b/util/service/stop_windows.go @@ -0,0 +1,42 @@ +package service + +import ( + "fmt" + "time" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" +) + +func Stop(name string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + + s, err := m.OpenService(name) + if err != nil { + return fmt.Errorf("could not access service: %v", err) + } + defer s.Close() + + status, err := s.Control(svc.Stop) + if err != nil { + return fmt.Errorf("could not send control=%d: %v", svc.Stop, err) + } + + timeout := time.Now().Add(10 * time.Second) + + for status.State != svc.Stopped { + if timeout.Before(time.Now()) { + return fmt.Errorf("timeout waiting for service to go to state=%d", svc.Stopped) + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not retrieve service status: %v", err) + } + } + return nil +} diff --git a/util/update/name.go b/util/update/name.go index 53f16403..e134ec82 100644 --- a/util/update/name.go +++ b/util/update/name.go @@ -2,5 +2,5 @@ package update -const Name = "nanobox" -const tmpName = "nanobox.tmp" +var Name = "nanobox" +var TmpName = "nanobox.tmp" diff --git a/util/update/name_windows.go b/util/update/name_windows.go index ec06f3e3..c13c4ab7 100644 --- a/util/update/name_windows.go +++ b/util/update/name_windows.go @@ -1,4 +1,4 @@ package update -const Name = "nanobox.exe" -const tmpName = "nanobox-tmp.exe" +var Name = "nanobox.exe" +var TmpName = "nanobox-tmp.exe" diff --git a/util/update/run.go b/util/update/run.go index a3dea028..e621c55c 100644 --- a/util/update/run.go +++ b/util/update/run.go @@ -2,6 +2,7 @@ package update import ( "fmt" + "io/ioutil" "net/http" "os" "path/filepath" @@ -11,13 +12,15 @@ import ( "github.com/nanobox-io/nanobox/util/display" ) +var Server bool + func Run(path string) error { if path == "" { fmt.Errorf("invalid path") } // create a temporary file - tmpFileName := filepath.Join(filepath.Dir(path), tmpName) + tmpFileName := filepath.Join(filepath.Dir(path), TmpName) tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return err @@ -31,6 +34,10 @@ func Run(path string) error { defer resp.Body.Close() dp := display.DownloadPercent{Total: resp.ContentLength} + if Server { + // on the Server we dont really care to see this + dp.Output = ioutil.Discard + } dp.Copy(tmpFile, resp.Body) // close the tmp file diff --git a/vendor/github.com/Microsoft/go-winio/README.md b/vendor/github.com/Microsoft/go-winio/README.md index 478862a8..56800105 100644 --- a/vendor/github.com/Microsoft/go-winio/README.md +++ b/vendor/github.com/Microsoft/go-winio/README.md @@ -1,15 +1,22 @@ # go-winio -This repository contains utilities for efficiently performing Win32 IO operations in +This repository contains utilities for efficiently performing Win32 IO operations in Go. Currently, this is focused on accessing named pipes and other file handles, and for using named pipes as a net transport. -This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go -to reuse the thread to schedule another goroutine. This limits support to Windows Vista and +This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go +to reuse the thread to schedule another goroutine. This limits support to Windows Vista and newer operating systems. This is similar to the implementation of network sockets in Go's net package. Please see the LICENSE file for licensing information. -Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe +This project has adopted the [Microsoft Open Source Code of +Conduct](https://opensource.microsoft.com/codeofconduct/). For more information +see the [Code of Conduct +FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional +questions or comments. + +Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe for another named pipe implementation. diff --git a/vendor/github.com/Microsoft/go-winio/backup.go b/vendor/github.com/Microsoft/go-winio/backup.go index 86493517..ceedee1f 100644 --- a/vendor/github.com/Microsoft/go-winio/backup.go +++ b/vendor/github.com/Microsoft/go-winio/backup.go @@ -1,3 +1,5 @@ +// +build windows + package winio import ( @@ -257,7 +259,7 @@ func OpenForBackup(path string, access uint32, share uint32, createmode uint32) if err != nil { return nil, err } - h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0) if err != nil { err = &os.PathError{Op: "open", Path: path, Err: err} return nil, err diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go index fd16f007..8c15e412 100644 --- a/vendor/github.com/Microsoft/go-winio/file.go +++ b/vendor/github.com/Microsoft/go-winio/file.go @@ -1,3 +1,5 @@ +// +build windows + package winio import ( diff --git a/vendor/github.com/Microsoft/go-winio/fileinfo.go b/vendor/github.com/Microsoft/go-winio/fileinfo.go index d5acb72d..a822e431 100644 --- a/vendor/github.com/Microsoft/go-winio/fileinfo.go +++ b/vendor/github.com/Microsoft/go-winio/fileinfo.go @@ -1,3 +1,5 @@ +// +build windows + package winio import ( diff --git a/vendor/github.com/Microsoft/go-winio/pipe.go b/vendor/github.com/Microsoft/go-winio/pipe.go index 82db2830..b85b2eef 100644 --- a/vendor/github.com/Microsoft/go-winio/pipe.go +++ b/vendor/github.com/Microsoft/go-winio/pipe.go @@ -1,3 +1,5 @@ +// +build windows + package winio import ( diff --git a/vendor/github.com/Microsoft/go-winio/privilege.go b/vendor/github.com/Microsoft/go-winio/privilege.go index 81f9af7b..9c83d36f 100644 --- a/vendor/github.com/Microsoft/go-winio/privilege.go +++ b/vendor/github.com/Microsoft/go-winio/privilege.go @@ -1,3 +1,5 @@ +// +build windows + package winio import ( @@ -5,14 +7,17 @@ import ( "encoding/binary" "fmt" "runtime" + "sync" "syscall" "unicode/utf16" + + "golang.org/x/sys/windows" ) -//sys adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges +//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges //sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf //sys revertToSelf() (err error) = advapi32.RevertToSelf -//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) = advapi32.OpenThreadToken +//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken //sys getCurrentThread() (h syscall.Handle) = GetCurrentThread //sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW //sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW @@ -34,6 +39,12 @@ const ( securityDelegation ) +var ( + privNames = make(map[string]uint64) + privNameMutex sync.Mutex +) + +// PrivilegeError represents an error enabling privileges. type PrivilegeError struct { privileges []uint64 } @@ -56,19 +67,16 @@ func (e *PrivilegeError) Error() string { return s } +// RunWithPrivilege enables a single privilege for a function call. func RunWithPrivilege(name string, fn func() error) error { return RunWithPrivileges([]string{name}, fn) } +// RunWithPrivileges enables privileges for a function call. func RunWithPrivileges(names []string, fn func() error) error { - var privileges []uint64 - for _, name := range names { - p := uint64(0) - err := lookupPrivilegeValue("", name, &p) - if err != nil { - return err - } - privileges = append(privileges, p) + privileges, err := mapPrivileges(names) + if err != nil { + return err } runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -77,19 +85,64 @@ func RunWithPrivileges(names []string, fn func() error) error { return err } defer releaseThreadToken(token) - err = adjustPrivileges(token, privileges) + err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED) if err != nil { return err } return fn() } -func adjustPrivileges(token syscall.Handle, privileges []uint64) error { +func mapPrivileges(names []string) ([]uint64, error) { + var privileges []uint64 + privNameMutex.Lock() + defer privNameMutex.Unlock() + for _, name := range names { + p, ok := privNames[name] + if !ok { + err := lookupPrivilegeValue("", name, &p) + if err != nil { + return nil, err + } + privNames[name] = p + } + privileges = append(privileges, p) + } + return privileges, nil +} + +// EnableProcessPrivileges enables privileges globally for the process. +func EnableProcessPrivileges(names []string) error { + return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED) +} + +// DisableProcessPrivileges disables privileges globally for the process. +func DisableProcessPrivileges(names []string) error { + return enableDisableProcessPrivilege(names, 0) +} + +func enableDisableProcessPrivilege(names []string, action uint32) error { + privileges, err := mapPrivileges(names) + if err != nil { + return err + } + + p, _ := windows.GetCurrentProcess() + var token windows.Token + err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) + if err != nil { + return err + } + + defer token.Close() + return adjustPrivileges(token, privileges, action) +} + +func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { var b bytes.Buffer binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) for _, p := range privileges { binary.Write(&b, binary.LittleEndian, p) - binary.Write(&b, binary.LittleEndian, uint32(SE_PRIVILEGE_ENABLED)) + binary.Write(&b, binary.LittleEndian, action) } prevState := make([]byte, b.Len()) reqSize := uint32(0) @@ -113,23 +166,22 @@ func getPrivilegeName(luid uint64) string { var displayNameBuffer [256]uint16 displayBufSize := uint32(len(displayNameBuffer)) - var langId uint32 - err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langId) + var langID uint32 + err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID) if err != nil { - return fmt.Sprintf("", utf16.Decode(nameBuffer[:bufSize])) + return fmt.Sprintf("", string(utf16.Decode(nameBuffer[:bufSize]))) } return string(utf16.Decode(displayNameBuffer[:displayBufSize])) } -func newThreadToken() (syscall.Handle, error) { +func newThreadToken() (windows.Token, error) { err := impersonateSelf(securityImpersonation) if err != nil { - panic(err) return 0, err } - var token syscall.Handle + var token windows.Token err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token) if err != nil { rerr := revertToSelf() @@ -141,10 +193,10 @@ func newThreadToken() (syscall.Handle, error) { return token, nil } -func releaseThreadToken(h syscall.Handle) { +func releaseThreadToken(h windows.Token) { err := revertToSelf() if err != nil { panic(err) } - syscall.Close(h) + h.Close() } diff --git a/vendor/github.com/Microsoft/go-winio/sd.go b/vendor/github.com/Microsoft/go-winio/sd.go index 60ab56ce..db1b370a 100644 --- a/vendor/github.com/Microsoft/go-winio/sd.go +++ b/vendor/github.com/Microsoft/go-winio/sd.go @@ -1,3 +1,5 @@ +// +build windows + package winio import ( diff --git a/vendor/github.com/Microsoft/go-winio/syscall.go b/vendor/github.com/Microsoft/go-winio/syscall.go index 96fdff7b..20d64cf4 100644 --- a/vendor/github.com/Microsoft/go-winio/syscall.go +++ b/vendor/github.com/Microsoft/go-winio/syscall.go @@ -1,3 +1,3 @@ package winio -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go diff --git a/vendor/github.com/Microsoft/go-winio/zsyscall.go b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go similarity index 97% rename from vendor/github.com/Microsoft/go-winio/zsyscall.go rename to vendor/github.com/Microsoft/go-winio/zsyscall_windows.go index 74b6e97a..c5e369ba 100644 --- a/vendor/github.com/Microsoft/go-winio/zsyscall.go +++ b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go @@ -2,15 +2,19 @@ package winio -import "unsafe" -import "syscall" +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) var _ unsafe.Pointer var ( - modkernel32 = syscall.NewLazyDLL("kernel32.dll") - modwinmm = syscall.NewLazyDLL("winmm.dll") - modadvapi32 = syscall.NewLazyDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + modwinmm = windows.NewLazySystemDLL("winmm.dll") + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") @@ -300,7 +304,7 @@ func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, si return } -func adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { +func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { var _p0 uint32 if releaseAll { _p0 = 1 @@ -343,7 +347,7 @@ func revertToSelf() (err error) { return } -func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) { +func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) { var _p0 uint32 if openAsSelf { _p0 = 1 diff --git a/vendor/github.com/docker/engine-api/CHANGELOG.md b/vendor/github.com/docker/engine-api/CHANGELOG.md new file mode 100644 index 00000000..3c94dcc8 --- /dev/null +++ b/vendor/github.com/docker/engine-api/CHANGELOG.md @@ -0,0 +1,141 @@ +# Changelog + +Items starting with DEPRECATE are important deprecation notices. For more information on the list of deprecated APIs please have a look at https://docs.docker.com/misc/deprecated/ where target removal dates can also be found. + +## 0.3.2 (2016-03-30) + +## Client + +- Revert setting the ServerName in the TLS configuration at client init. See https://github.com/docker/swarm/issues/2027. + +## 0.3.1 (2016-03-23) + +### Client + +- Ensure that API paths are properly escaped. + +## 0.3.0 (2016-03-22) + +### Client + +- Add context to every function. +- Fix issue loading a default TLS CA. +- Allow to configure the client with a given http.Client. +- Add support for Windows named pipes. +- Set default host for Solaris. +- Add quiet flag for image load. +- Add ability to hijack connections through a proxy. +- Correctly set content type for image load. +- Add support for getting token for login. + +### Types + +- Add struct for update restart policy. +- Add human friendly State for container. +- Use OS specific host when DOCKER_HOST is not set. +- Rename Status in info to SystemStatus. +- Add internal flag to network inspect. +- Add disk quota field to container. +- Add EnableIPv6 fields. +- Add Mounts to container. +- Add cgroup driver to info. +- Add userns to host config. +- Remove email from AuthConfig. +- Make AuthConfig fields optional. +- Add IO resource settings for Windows. +- Add storage driver to host config. +- Update NetworkName to return proper user defined network names. +- Support joining cgroups by container id. +- Add KernelMemory to info. +- Add UsernsMode to container config. +- Add CPU resource control for Windows. +- Add AutoRemove to host config. +- Add Status field to Volume. +- Add Label to Image, Network and Volume. +- Add RootFS to container. + +## 0.2.3 (2016-02-02) + +### Types + +- Add missing status field. + +## 0.2.2 (2016-01-13) + +### Client + +- Fix issue configuring response hijacking with TLS enabled. + + +## 0.2.1 (2016-01-12) + +### Client + +- Fix issue detecting missing images on container creation. + +### Types + +- Remove invalid json tag in endpoint configuration. +- Add missing fields in info structure. + +## 0.2.0 (2016-01-11) + +### Client + +- Allow to force network disconnection. (docker 1.10) + +### Types + +- Add global and local alias configuration to network endpoint. +- Add network ID to network endpoint. +- Add IPAM options. +- Add Seccomp options. +- Fix issue referencing OOMKillDisable. + + +## 0.1.3 (2016-01-07) + +### Client + +- Fix issue sending all network configurations for a per network request. + + +## 0.1.2 (2016-01-07) + +### Client + +- Add interface to represent the API client. +- Restrict the fields send to the update endpoint to only those that are used. +- Send network settings as part of the container create request. (docker 1.10) +- Send network settings as part of the network connect request. (docker 1.10) + +### Types + +- Add PidsLimit as part of the host configuration. +- Add PidsStats to show PID stats. +- Add graph storage options to host configuration. +- Add NetworkConfig and EndpointIPAMConfig structs. (docker 1.10) + + +## 0.1.1 (2016-01-06) + +### Client + +- Delegate shmSize units conversion to the consumer. + +### Types + +- Add warnings to the volume list response. +- Fix image build options: + * use 0 as default value for shmSize. + + +## 0.1.0 (2016-01-04) + +### Client + +- Initial API client implementation. + +### Types + +- Initial API types implementation. diff --git a/vendor/github.com/docker/engine-api/CONTRIBUTING.md b/vendor/github.com/docker/engine-api/CONTRIBUTING.md new file mode 100644 index 00000000..926dcc93 --- /dev/null +++ b/vendor/github.com/docker/engine-api/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing to Docker + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. diff --git a/vendor/github.com/docker/engine-api/MAINTAINERS b/vendor/github.com/docker/engine-api/MAINTAINERS new file mode 100644 index 00000000..f86ffe0b --- /dev/null +++ b/vendor/github.com/docker/engine-api/MAINTAINERS @@ -0,0 +1,160 @@ +# engine-api maintainers file +# +# This file describes who runs the docker/engine-api project and how. +# This is a living document - if you see something out of date or missing, speak up! +# +# It is structured to be consumable by both humans and programs. +# To extract its contents programmatically, use any TOML-compliant parser. +# +# This file is compiled into the MAINTAINERS file in docker/opensource. +# +[Org] + [Org."Core maintainers"] + people = [ + "aaronlehmann", + "calavera", + "coolljt0725", + "cpuguy83", + "crosbymichael", + "dnephin", + "dongluochen", + "duglin", + "estesp", + "icecrime", + "jhowardmsft", + "lk4d4", + "mavenugo", + "mhbauer", + "runcom", + "stevvooe", + "thajeztah", + "tianon", + "tibor", + "tonistiigi", + "unclejack", + "vdemeester", + "vieux" + ] + +[people] + +# A reference list of all people associated with the project. +# All other sections should refer to people by their canonical key +# in the people section. + + # ADD YOURSELF HERE IN ALPHABETICAL ORDER + + [people.aaronlehmann] + Name = "Aaron Lehmann" + Email = "aaron.lehmann@docker.com" + GitHub = "aaronlehmann" + + [people.calavera] + Name = "David Calavera" + Email = "david.calavera@gmail.com" + GitHub = "calavera" + + [people.coolljt0725] + Name = "Lei Jitang" + Email = "leijitang@huawei.com" + GitHub = "coolljt0725" + + [people.cpuguy83] + Name = "Brian Goff" + Email = "cpuguy83@gmail.com" + Github = "cpuguy83" + + [people.crosbymichael] + Name = "Michael Crosby" + Email = "crosbymichael@gmail.com" + GitHub = "crosbymichael" + + [people.dnephin] + Name = "Daniel Nephin" + Email = "dnephin@gmail.com" + GitHub = "dnephin" + + [people.dongluochen] + Name = "Dongluo Chen" + Email = "dongluo.chen@docker.com" + GitHub = "dongluochen" + + [people.duglin] + Name = "Doug Davis" + Email = "dug@us.ibm.com" + GitHub = "duglin" + + [people.estesp] + Name = "Phil Estes" + Email = "estesp@linux.vnet.ibm.com" + GitHub = "estesp" + + [people.icecrime] + Name = "Arnaud Porterie" + Email = "arnaud@docker.com" + GitHub = "icecrime" + + [people.jhowardmsft] + Name = "John Howard" + Email = "jhoward@microsoft.com" + GitHub = "jhowardmsft" + + [people.lk4d4] + Name = "Alexander Morozov" + Email = "lk4d4@docker.com" + GitHub = "lk4d4" + + [people.mavenugo] + Name = "Madhu Venugopal" + Email = "madhu@docker.com" + GitHub = "mavenugo" + + [people.mhbauer] + Name = "Morgan Bauer" + Email = "mbauer@us.ibm.com" + GitHub = "mhbauer" + + [people.runcom] + Name = "Antonio Murdaca" + Email = "runcom@redhat.com" + GitHub = "runcom" + + [people.stevvooe] + Name = "Stephen Day" + Email = "stephen.day@docker.com" + GitHub = "stevvooe" + + [people.thajeztah] + Name = "Sebastiaan van Stijn" + Email = "github@gone.nl" + GitHub = "thaJeztah" + + [people.tianon] + Name = "Tianon Gravi" + Email = "admwiggin@gmail.com" + GitHub = "tianon" + + [people.tibor] + Name = "Tibor Vass" + Email = "tibor@docker.com" + GitHub = "tiborvass" + + [people.tonistiigi] + Name = "Tõnis Tiigi" + Email = "tonis@docker.com" + GitHub = "tonistiigi" + + [people.unclejack] + Name = "Cristian Staretu" + Email = "cristian.staretu@gmail.com" + GitHub = "unclejack" + + [people.vdemeester] + Name = "Vincent Demeester" + Email = "vincent@sbr.pm" + GitHub = "vdemeester" + + [people.vieux] + Name = "Victor Vieux" + Email = "vieux@docker.com" + GitHub = "vieux" diff --git a/vendor/github.com/docker/engine-api/Makefile b/vendor/github.com/docker/engine-api/Makefile new file mode 100644 index 00000000..61e9ef28 --- /dev/null +++ b/vendor/github.com/docker/engine-api/Makefile @@ -0,0 +1,21 @@ +.PHONY: all deps test validate lint + +all: deps test validate + +deps: + go get -t ./... + go get -u github.com/golang/lint/golint + +test: + go test -tags experimental -race -cover ./... + +validate: lint + go vet ./... + test -z "$(gofmt -s -l . | tee /dev/stderr)" + +lint: + out="$$(golint ./...)"; \ + if [ -n "$$(golint ./...)" ]; then \ + echo "$$out"; \ + exit 1; \ + fi diff --git a/vendor/github.com/docker/engine-api/README.md b/vendor/github.com/docker/engine-api/README.md new file mode 100644 index 00000000..99a0ef3c --- /dev/null +++ b/vendor/github.com/docker/engine-api/README.md @@ -0,0 +1,17 @@ +[![GoDoc](https://godoc.org/github.com/docker/engine-api?status.svg)](https://godoc.org/github.com/docker/engine-api) + +# Deprecated + +For new projects please do not use this project anymore. The Docker API client and types have been moved +to the main docker repo under the following import paths: + +* https://github.com/docker/docker/tree/master/client +* https://github.com/docker/docker/tree/master/api/types + +All pull requests and issues should be filed under the docker/docker repository. +This repo will not receive any future updates and you should update your existing import +paths to the new package paths. + +## License + +engine-api is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. diff --git a/vendor/github.com/docker/engine-api/appveyor.yml b/vendor/github.com/docker/engine-api/appveyor.yml new file mode 100644 index 00000000..b31137f3 --- /dev/null +++ b/vendor/github.com/docker/engine-api/appveyor.yml @@ -0,0 +1,37 @@ +version: "{build}" + +# Source Config +clone_folder: c:\gopath\src\github.com\docker\engine-api + +# Build host + +environment: + GOPATH: c:\gopath + GOVERSION: 1.6 + +init: + - git config --global core.autocrlf input + +# Build + +install: + # Install Go 1.6. + - rmdir c:\go /s /q + - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi + - msiexec /i go%GOVERSION%.windows-amd64.msi /q + - set Path=c:\go\bin;c:\gopath\bin;%Path% + - go version + - go env + +build: false +deploy: false + +before_test: + - go get -t ./... + - go get github.com/golang/lint/golint + +test_script: + - go vet ./... + - golint ./... + - gofmt -s -l . + - go test -race -cover -v -tags=test ./... diff --git a/vendor/github.com/docker/engine-api/doc.go b/vendor/github.com/docker/engine-api/doc.go new file mode 100644 index 00000000..8e2da06a --- /dev/null +++ b/vendor/github.com/docker/engine-api/doc.go @@ -0,0 +1,21 @@ +/* +Package engineapi provides libraries to implement client and server components compatible with the Docker engine. + +The client package in github.com/docker/engine-api/client implements all necessary requests to implement the official Docker engine cli. + +Create a new client, then use it to send and receive messages to the Docker engine API: + + defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"} + cli, err := client.NewClient("unix:///var/run/docker.sock", "v1.22", nil, defaultHeaders) + +Other programs, like Docker Machine, can set the default Docker engine environment for you. There is a shortcut to use its variables to configure the client: + + cli, err := client.NewEnvClient() + +All request arguments are defined as typed structures in the types package. For instance, this is how to get all containers running in the host: + + options := types.ContainerListOptions{All: true} + containers, err := cli.ContainerList(context.Background(), options) + +*/ +package engineapi diff --git a/vendor/golang.org/x/sys/windows/svc/debug/log.go b/vendor/golang.org/x/sys/windows/svc/debug/log.go new file mode 100644 index 00000000..e51ab42a --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/debug/log.go @@ -0,0 +1,56 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package debug + +import ( + "os" + "strconv" +) + +// Log interface allows different log implementations to be used. +type Log interface { + Close() error + Info(eid uint32, msg string) error + Warning(eid uint32, msg string) error + Error(eid uint32, msg string) error +} + +// ConsoleLog provides access to the console. +type ConsoleLog struct { + Name string +} + +// New creates new ConsoleLog. +func New(source string) *ConsoleLog { + return &ConsoleLog{Name: source} +} + +// Close closes console log l. +func (l *ConsoleLog) Close() error { + return nil +} + +func (l *ConsoleLog) report(kind string, eid uint32, msg string) error { + s := l.Name + "." + kind + "(" + strconv.Itoa(int(eid)) + "): " + msg + "\n" + _, err := os.Stdout.Write([]byte(s)) + return err +} + +// Info writes an information event msg with event id eid to the console l. +func (l *ConsoleLog) Info(eid uint32, msg string) error { + return l.report("info", eid, msg) +} + +// Warning writes an warning event msg with event id eid to the console l. +func (l *ConsoleLog) Warning(eid uint32, msg string) error { + return l.report("warn", eid, msg) +} + +// Error writes an error event msg with event id eid to the console l. +func (l *ConsoleLog) Error(eid uint32, msg string) error { + return l.report("error", eid, msg) +} diff --git a/vendor/golang.org/x/sys/windows/svc/debug/service.go b/vendor/golang.org/x/sys/windows/svc/debug/service.go new file mode 100644 index 00000000..d5ab94b2 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/debug/service.go @@ -0,0 +1,45 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package debug provides facilities to execute svc.Handler on console. +// +package debug + +import ( + "os" + "os/signal" + "syscall" + + "golang.org/x/sys/windows/svc" +) + +// Run executes service name by calling appropriate handler function. +// The process is running on console, unlike real service. Use Ctrl+C to +// send "Stop" command to your service. +func Run(name string, handler svc.Handler) error { + cmds := make(chan svc.ChangeRequest) + changes := make(chan svc.Status) + + sig := make(chan os.Signal) + signal.Notify(sig) + + go func() { + status := svc.Status{State: svc.Stopped} + for { + select { + case <-sig: + cmds <- svc.ChangeRequest{svc.Stop, status} + case status = <-changes: + } + } + }() + + _, errno := handler.Execute([]string{name}, cmds, changes) + if errno != 0 { + return syscall.Errno(errno) + } + return nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/event.go b/vendor/golang.org/x/sys/windows/svc/event.go new file mode 100644 index 00000000..0508e228 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/event.go @@ -0,0 +1,48 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package svc + +import ( + "errors" + + "golang.org/x/sys/windows" +) + +// event represents auto-reset, initially non-signaled Windows event. +// It is used to communicate between go and asm parts of this package. +type event struct { + h windows.Handle +} + +func newEvent() (*event, error) { + h, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + return nil, err + } + return &event{h: h}, nil +} + +func (e *event) Close() error { + return windows.CloseHandle(e.h) +} + +func (e *event) Set() error { + return windows.SetEvent(e.h) +} + +func (e *event) Wait() error { + s, err := windows.WaitForSingleObject(e.h, windows.INFINITE) + switch s { + case windows.WAIT_OBJECT_0: + break + case windows.WAIT_FAILED: + return err + default: + return errors.New("unexpected result from WaitForSingleObject") + } + return nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/eventlog/install.go b/vendor/golang.org/x/sys/windows/svc/eventlog/install.go new file mode 100644 index 00000000..c76a3760 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/eventlog/install.go @@ -0,0 +1,80 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package eventlog + +import ( + "errors" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +const ( + // Log levels. + Info = windows.EVENTLOG_INFORMATION_TYPE + Warning = windows.EVENTLOG_WARNING_TYPE + Error = windows.EVENTLOG_ERROR_TYPE +) + +const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` + +// Install modifies PC registry to allow logging with an event source src. +// It adds all required keys and values to the event log registry key. +// Install uses msgFile as the event message file. If useExpandKey is true, +// the event message file is installed as REG_EXPAND_SZ value, +// otherwise as REG_SZ. Use bitwise of log.Error, log.Warning and +// log.Info to specify events supported by the new event source. +func Install(src, msgFile string, useExpandKey bool, eventsSupported uint32) error { + appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.CREATE_SUB_KEY) + if err != nil { + return err + } + defer appkey.Close() + + sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.SET_VALUE) + if err != nil { + return err + } + defer sk.Close() + if alreadyExist { + return errors.New(addKeyName + `\` + src + " registry key already exists") + } + + err = sk.SetDWordValue("CustomSource", 1) + if err != nil { + return err + } + if useExpandKey { + err = sk.SetExpandStringValue("EventMessageFile", msgFile) + } else { + err = sk.SetStringValue("EventMessageFile", msgFile) + } + if err != nil { + return err + } + err = sk.SetDWordValue("TypesSupported", eventsSupported) + if err != nil { + return err + } + return nil +} + +// InstallAsEventCreate is the same as Install, but uses +// %SystemRoot%\System32\EventCreate.exe as the event message file. +func InstallAsEventCreate(src string, eventsSupported uint32) error { + return Install(src, "%SystemRoot%\\System32\\EventCreate.exe", true, eventsSupported) +} + +// Remove deletes all registry elements installed by the correspondent Install. +func Remove(src string) error { + appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.SET_VALUE) + if err != nil { + return err + } + defer appkey.Close() + return registry.DeleteKey(appkey, src) +} diff --git a/vendor/golang.org/x/sys/windows/svc/eventlog/log.go b/vendor/golang.org/x/sys/windows/svc/eventlog/log.go new file mode 100644 index 00000000..46e5153d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/eventlog/log.go @@ -0,0 +1,70 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package eventlog implements access to Windows event log. +// +package eventlog + +import ( + "errors" + "syscall" + + "golang.org/x/sys/windows" +) + +// Log provides access to the system log. +type Log struct { + Handle windows.Handle +} + +// Open retrieves a handle to the specified event log. +func Open(source string) (*Log, error) { + return OpenRemote("", source) +} + +// OpenRemote does the same as Open, but on different computer host. +func OpenRemote(host, source string) (*Log, error) { + if source == "" { + return nil, errors.New("Specify event log source") + } + var s *uint16 + if host != "" { + s = syscall.StringToUTF16Ptr(host) + } + h, err := windows.RegisterEventSource(s, syscall.StringToUTF16Ptr(source)) + if err != nil { + return nil, err + } + return &Log{Handle: h}, nil +} + +// Close closes event log l. +func (l *Log) Close() error { + return windows.DeregisterEventSource(l.Handle) +} + +func (l *Log) report(etype uint16, eid uint32, msg string) error { + ss := []*uint16{syscall.StringToUTF16Ptr(msg)} + return windows.ReportEvent(l.Handle, etype, 0, eid, 0, 1, 0, &ss[0], nil) +} + +// Info writes an information event msg with event id eid to the end of event log l. +// When EventCreate.exe is used, eid must be between 1 and 1000. +func (l *Log) Info(eid uint32, msg string) error { + return l.report(windows.EVENTLOG_INFORMATION_TYPE, eid, msg) +} + +// Warning writes an warning event msg with event id eid to the end of event log l. +// When EventCreate.exe is used, eid must be between 1 and 1000. +func (l *Log) Warning(eid uint32, msg string) error { + return l.report(windows.EVENTLOG_WARNING_TYPE, eid, msg) +} + +// Error writes an error event msg with event id eid to the end of event log l. +// When EventCreate.exe is used, eid must be between 1 and 1000. +func (l *Log) Error(eid uint32, msg string) error { + return l.report(windows.EVENTLOG_ERROR_TYPE, eid, msg) +} diff --git a/vendor/golang.org/x/sys/windows/svc/go12.c b/vendor/golang.org/x/sys/windows/svc/go12.c new file mode 100644 index 00000000..6f1be1fa --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/go12.c @@ -0,0 +1,24 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows +// +build !go1.3 + +// copied from pkg/runtime +typedef unsigned int uint32; +typedef unsigned long long int uint64; +#ifdef _64BIT +typedef uint64 uintptr; +#else +typedef uint32 uintptr; +#endif + +// from sys_386.s or sys_amd64.s +void ·servicemain(void); + +void +·getServiceMain(uintptr *r) +{ + *r = (uintptr)·servicemain; +} diff --git a/vendor/golang.org/x/sys/windows/svc/go12.go b/vendor/golang.org/x/sys/windows/svc/go12.go new file mode 100644 index 00000000..6f0a924e --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/go12.go @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows +// +build !go1.3 + +package svc + +// from go12.c +func getServiceMain(r *uintptr) diff --git a/vendor/golang.org/x/sys/windows/svc/go13.go b/vendor/golang.org/x/sys/windows/svc/go13.go new file mode 100644 index 00000000..432a9e79 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/go13.go @@ -0,0 +1,31 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows +// +build go1.3 + +package svc + +import "unsafe" + +const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const + +// Should be a built-in for unsafe.Pointer? +func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(p) + x) +} + +// funcPC returns the entry PC of the function f. +// It assumes that f is a func value. Otherwise the behavior is undefined. +func funcPC(f interface{}) uintptr { + return **(**uintptr)(add(unsafe.Pointer(&f), ptrSize)) +} + +// from sys_386.s and sys_amd64.s +func servicectlhandler(ctl uint32) uintptr +func servicemain(argc uint32, argv **uint16) + +func getServiceMain(r *uintptr) { + *r = funcPC(servicemain) +} diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/config.go b/vendor/golang.org/x/sys/windows/svc/mgr/config.go new file mode 100644 index 00000000..0a6edba4 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/config.go @@ -0,0 +1,139 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package mgr + +import ( + "syscall" + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + // Service start types. + StartManual = windows.SERVICE_DEMAND_START // the service must be started manually + StartAutomatic = windows.SERVICE_AUTO_START // the service will start by itself whenever the computer reboots + StartDisabled = windows.SERVICE_DISABLED // the service cannot be started + + // The severity of the error, and action taken, + // if this service fails to start. + ErrorCritical = windows.SERVICE_ERROR_CRITICAL + ErrorIgnore = windows.SERVICE_ERROR_IGNORE + ErrorNormal = windows.SERVICE_ERROR_NORMAL + ErrorSevere = windows.SERVICE_ERROR_SEVERE +) + +// TODO(brainman): Password is not returned by windows.QueryServiceConfig, not sure how to get it. + +type Config struct { + ServiceType uint32 + StartType uint32 + ErrorControl uint32 + BinaryPathName string // fully qualified path to the service binary file, can also include arguments for an auto-start service + LoadOrderGroup string + TagId uint32 + Dependencies []string + ServiceStartName string // name of the account under which the service should run + DisplayName string + Password string + Description string +} + +func toString(p *uint16) string { + if p == nil { + return "" + } + return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:]) +} + +func toStringSlice(ps *uint16) []string { + if ps == nil { + return nil + } + r := make([]string, 0) + for from, i, p := 0, 0, (*[1 << 24]uint16)(unsafe.Pointer(ps)); true; i++ { + if p[i] == 0 { + // empty string marks the end + if i <= from { + break + } + r = append(r, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return r +} + +// Config retrieves service s configuration paramteres. +func (s *Service) Config() (Config, error) { + var p *windows.QUERY_SERVICE_CONFIG + n := uint32(1024) + for { + b := make([]byte, n) + p = (*windows.QUERY_SERVICE_CONFIG)(unsafe.Pointer(&b[0])) + err := windows.QueryServiceConfig(s.Handle, p, n, &n) + if err == nil { + break + } + if err.(syscall.Errno) != syscall.ERROR_INSUFFICIENT_BUFFER { + return Config{}, err + } + if n <= uint32(len(b)) { + return Config{}, err + } + } + + var p2 *windows.SERVICE_DESCRIPTION + n = uint32(1024) + for { + b := make([]byte, n) + p2 = (*windows.SERVICE_DESCRIPTION)(unsafe.Pointer(&b[0])) + err := windows.QueryServiceConfig2(s.Handle, + windows.SERVICE_CONFIG_DESCRIPTION, &b[0], n, &n) + if err == nil { + break + } + if err.(syscall.Errno) != syscall.ERROR_INSUFFICIENT_BUFFER { + return Config{}, err + } + if n <= uint32(len(b)) { + return Config{}, err + } + } + + return Config{ + ServiceType: p.ServiceType, + StartType: p.StartType, + ErrorControl: p.ErrorControl, + BinaryPathName: toString(p.BinaryPathName), + LoadOrderGroup: toString(p.LoadOrderGroup), + TagId: p.TagId, + Dependencies: toStringSlice(p.Dependencies), + ServiceStartName: toString(p.ServiceStartName), + DisplayName: toString(p.DisplayName), + Description: toString(p2.Description), + }, nil +} + +func updateDescription(handle windows.Handle, desc string) error { + d := windows.SERVICE_DESCRIPTION{toPtr(desc)} + return windows.ChangeServiceConfig2(handle, + windows.SERVICE_CONFIG_DESCRIPTION, (*byte)(unsafe.Pointer(&d))) +} + +// UpdateConfig updates service s configuration parameters. +func (s *Service) UpdateConfig(c Config) error { + err := windows.ChangeServiceConfig(s.Handle, c.ServiceType, c.StartType, + c.ErrorControl, toPtr(c.BinaryPathName), toPtr(c.LoadOrderGroup), + nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), + toPtr(c.Password), toPtr(c.DisplayName)) + if err != nil { + return err + } + return updateDescription(s.Handle, c.Description) +} diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/mgr.go b/vendor/golang.org/x/sys/windows/svc/mgr/mgr.go new file mode 100644 index 00000000..da8ceb6e --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/mgr.go @@ -0,0 +1,119 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package mgr can be used to manage Windows service programs. +// It can be used to install and remove them. It can also start, +// stop and pause them. The package can query / change current +// service state and config parameters. +// +package mgr + +import ( + "syscall" + "unicode/utf16" + + "golang.org/x/sys/windows" +) + +// Mgr is used to manage Windows service. +type Mgr struct { + Handle windows.Handle +} + +// Connect establishes a connection to the service control manager. +func Connect() (*Mgr, error) { + return ConnectRemote("") +} + +// ConnectRemote establishes a connection to the +// service control manager on computer named host. +func ConnectRemote(host string) (*Mgr, error) { + var s *uint16 + if host != "" { + s = syscall.StringToUTF16Ptr(host) + } + h, err := windows.OpenSCManager(s, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return nil, err + } + return &Mgr{Handle: h}, nil +} + +// Disconnect closes connection to the service control manager m. +func (m *Mgr) Disconnect() error { + return windows.CloseServiceHandle(m.Handle) +} + +func toPtr(s string) *uint16 { + if len(s) == 0 { + return nil + } + return syscall.StringToUTF16Ptr(s) +} + +// toStringBlock terminates strings in ss with 0, and then +// concatenates them together. It also adds extra 0 at the end. +func toStringBlock(ss []string) *uint16 { + if len(ss) == 0 { + return nil + } + t := "" + for _, s := range ss { + if s != "" { + t += s + "\x00" + } + } + if t == "" { + return nil + } + t += "\x00" + return &utf16.Encode([]rune(t))[0] +} + +// CreateService installs new service name on the system. +// The service will be executed by running exepath binary. +// Use config c to specify service parameters. +// If service StartType is set to StartAutomatic, +// args will be passed to svc.Handle.Execute. +func (m *Mgr) CreateService(name, exepath string, c Config, args ...string) (*Service, error) { + if c.StartType == 0 { + c.StartType = StartManual + } + if c.ErrorControl == 0 { + c.ErrorControl = ErrorNormal + } + if c.ServiceType == 0 { + c.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS + } + s := syscall.EscapeArg(exepath) + for _, v := range args { + s += " " + syscall.EscapeArg(v) + } + h, err := windows.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName), + windows.SERVICE_ALL_ACCESS, c.ServiceType, + c.StartType, c.ErrorControl, toPtr(s), toPtr(c.LoadOrderGroup), + nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password)) + if err != nil { + return nil, err + } + if c.Description != "" { + err = updateDescription(h, c.Description) + if err != nil { + return nil, err + } + } + return &Service{Name: name, Handle: h}, nil +} + +// OpenService retrieves access to service name, so it can +// be interrogated and controlled. +func (m *Mgr) OpenService(name string) (*Service, error) { + h, err := windows.OpenService(m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_ALL_ACCESS) + if err != nil { + return nil, err + } + return &Service{Name: name, Handle: h}, nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/service.go b/vendor/golang.org/x/sys/windows/svc/mgr/service.go new file mode 100644 index 00000000..465f3c3d --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/service.go @@ -0,0 +1,74 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package mgr + +import ( + "syscall" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" +) + +// TODO(brainman): Use EnumDependentServices to enumerate dependent services. + +// TODO(brainman): Use EnumServicesStatus to enumerate services in the specified service control manager database. + +// Service is used to access Windows service. +type Service struct { + Name string + Handle windows.Handle +} + +// Delete marks service s for deletion from the service control manager database. +func (s *Service) Delete() error { + return windows.DeleteService(s.Handle) +} + +// Close relinquish access to the service s. +func (s *Service) Close() error { + return windows.CloseServiceHandle(s.Handle) +} + +// Start starts service s. +// args will be passed to svc.Handler.Execute. +func (s *Service) Start(args ...string) error { + var p **uint16 + if len(args) > 0 { + vs := make([]*uint16, len(args)) + for i, _ := range vs { + vs[i] = syscall.StringToUTF16Ptr(args[i]) + } + p = &vs[0] + } + return windows.StartService(s.Handle, uint32(len(args)), p) +} + +// Control sends state change request c to the servce s. +func (s *Service) Control(c svc.Cmd) (svc.Status, error) { + var t windows.SERVICE_STATUS + err := windows.ControlService(s.Handle, uint32(c), &t) + if err != nil { + return svc.Status{}, err + } + return svc.Status{ + State: svc.State(t.CurrentState), + Accepts: svc.Accepted(t.ControlsAccepted), + }, nil +} + +// Query returns current status of service s. +func (s *Service) Query() (svc.Status, error) { + var t windows.SERVICE_STATUS + err := windows.QueryServiceStatus(s.Handle, &t) + if err != nil { + return svc.Status{}, err + } + return svc.Status{ + State: svc.State(t.CurrentState), + Accepts: svc.Accepted(t.ControlsAccepted), + }, nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/security.go b/vendor/golang.org/x/sys/windows/svc/security.go new file mode 100644 index 00000000..6fbc9236 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/security.go @@ -0,0 +1,62 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package svc + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +func allocSid(subAuth0 uint32) (*windows.SID, error) { + var sid *windows.SID + err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, + 1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid) + if err != nil { + return nil, err + } + return sid, nil +} + +// IsAnInteractiveSession determines if calling process is running interactively. +// It queries the process token for membership in the Interactive group. +// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s +func IsAnInteractiveSession() (bool, error) { + interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID) + if err != nil { + return false, err + } + defer windows.FreeSid(interSid) + + serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID) + if err != nil { + return false, err + } + defer windows.FreeSid(serviceSid) + + t, err := windows.OpenCurrentProcessToken() + if err != nil { + return false, err + } + defer t.Close() + + gs, err := t.GetTokenGroups() + if err != nil { + return false, err + } + p := unsafe.Pointer(&gs.Groups[0]) + groups := (*[2 << 20]windows.SIDAndAttributes)(p)[:gs.GroupCount] + for _, g := range groups { + if windows.EqualSid(g.Sid, interSid) { + return true, nil + } + if windows.EqualSid(g.Sid, serviceSid) { + return false, nil + } + } + return false, nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/service.go b/vendor/golang.org/x/sys/windows/svc/service.go new file mode 100644 index 00000000..9864f7a7 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/service.go @@ -0,0 +1,316 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package svc provides everything required to build Windows service. +// +package svc + +import ( + "errors" + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +// State describes service execution state (Stopped, Running and so on). +type State uint32 + +const ( + Stopped = State(windows.SERVICE_STOPPED) + StartPending = State(windows.SERVICE_START_PENDING) + StopPending = State(windows.SERVICE_STOP_PENDING) + Running = State(windows.SERVICE_RUNNING) + ContinuePending = State(windows.SERVICE_CONTINUE_PENDING) + PausePending = State(windows.SERVICE_PAUSE_PENDING) + Paused = State(windows.SERVICE_PAUSED) +) + +// Cmd represents service state change request. It is sent to a service +// by the service manager, and should be actioned upon by the service. +type Cmd uint32 + +const ( + Stop = Cmd(windows.SERVICE_CONTROL_STOP) + Pause = Cmd(windows.SERVICE_CONTROL_PAUSE) + Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE) + Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE) + Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN) +) + +// Accepted is used to describe commands accepted by the service. +// Note that Interrogate is always accepted. +type Accepted uint32 + +const ( + AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP) + AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN) + AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE) +) + +// Status combines State and Accepted commands to fully describe running service. +type Status struct { + State State + Accepts Accepted + CheckPoint uint32 // used to report progress during a lengthy operation + WaitHint uint32 // estimated time required for a pending operation, in milliseconds +} + +// ChangeRequest is sent to the service Handler to request service status change. +type ChangeRequest struct { + Cmd Cmd + CurrentStatus Status +} + +// Handler is the interface that must be implemented to build Windows service. +type Handler interface { + + // Execute will be called by the package code at the start of + // the service, and the service will exit once Execute completes. + // Inside Execute you must read service change requests from r and + // act accordingly. You must keep service control manager up to date + // about state of your service by writing into s as required. + // args contains service name followed by argument strings passed + // to the service. + // You can provide service exit code in exitCode return parameter, + // with 0 being "no error". You can also indicate if exit code, + // if any, is service specific or not by using svcSpecificEC + // parameter. + Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) +} + +var ( + // These are used by asm code. + goWaitsH uintptr + cWaitsH uintptr + ssHandle uintptr + sName *uint16 + sArgc uintptr + sArgv **uint16 + ctlHandlerProc uintptr + cSetEvent uintptr + cWaitForSingleObject uintptr + cRegisterServiceCtrlHandlerW uintptr +) + +func init() { + k := syscall.MustLoadDLL("kernel32.dll") + cSetEvent = k.MustFindProc("SetEvent").Addr() + cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr() + a := syscall.MustLoadDLL("advapi32.dll") + cRegisterServiceCtrlHandlerW = a.MustFindProc("RegisterServiceCtrlHandlerW").Addr() +} + +type ctlEvent struct { + cmd Cmd + errno uint32 +} + +// service provides access to windows service api. +type service struct { + name string + h windows.Handle + cWaits *event + goWaits *event + c chan ctlEvent + handler Handler +} + +func newService(name string, handler Handler) (*service, error) { + var s service + var err error + s.name = name + s.c = make(chan ctlEvent) + s.handler = handler + s.cWaits, err = newEvent() + if err != nil { + return nil, err + } + s.goWaits, err = newEvent() + if err != nil { + s.cWaits.Close() + return nil, err + } + return &s, nil +} + +func (s *service) close() error { + s.cWaits.Close() + s.goWaits.Close() + return nil +} + +type exitCode struct { + isSvcSpecific bool + errno uint32 +} + +func (s *service) updateStatus(status *Status, ec *exitCode) error { + if s.h == 0 { + return errors.New("updateStatus with no service status handle") + } + var t windows.SERVICE_STATUS + t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS + t.CurrentState = uint32(status.State) + if status.Accepts&AcceptStop != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP + } + if status.Accepts&AcceptShutdown != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN + } + if status.Accepts&AcceptPauseAndContinue != 0 { + t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE + } + if ec.errno == 0 { + t.Win32ExitCode = windows.NO_ERROR + t.ServiceSpecificExitCode = windows.NO_ERROR + } else if ec.isSvcSpecific { + t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) + t.ServiceSpecificExitCode = ec.errno + } else { + t.Win32ExitCode = ec.errno + t.ServiceSpecificExitCode = windows.NO_ERROR + } + t.CheckPoint = status.CheckPoint + t.WaitHint = status.WaitHint + return windows.SetServiceStatus(s.h, &t) +} + +const ( + sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota + sysErrNewThreadInCallback +) + +func (s *service) run() { + s.goWaits.Wait() + s.h = windows.Handle(ssHandle) + argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc] + args := make([]string, len(argv)) + for i, a := range argv { + args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:]) + } + + cmdsToHandler := make(chan ChangeRequest) + changesFromHandler := make(chan Status) + exitFromHandler := make(chan exitCode) + + go func() { + ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler) + exitFromHandler <- exitCode{ss, errno} + }() + + status := Status{State: Stopped} + ec := exitCode{isSvcSpecific: true, errno: 0} + var outch chan ChangeRequest + inch := s.c + var cmd Cmd +loop: + for { + select { + case r := <-inch: + if r.errno != 0 { + ec.errno = r.errno + break loop + } + inch = nil + outch = cmdsToHandler + cmd = r.cmd + case outch <- ChangeRequest{cmd, status}: + inch = s.c + outch = nil + case c := <-changesFromHandler: + err := s.updateStatus(&c, &ec) + if err != nil { + // best suitable error number + ec.errno = sysErrSetServiceStatusFailed + if err2, ok := err.(syscall.Errno); ok { + ec.errno = uint32(err2) + } + break loop + } + status = c + case ec = <-exitFromHandler: + break loop + } + } + + s.updateStatus(&Status{State: Stopped}, &ec) + s.cWaits.Set() +} + +func newCallback(fn interface{}) (cb uintptr, err error) { + defer func() { + r := recover() + if r == nil { + return + } + cb = 0 + switch v := r.(type) { + case string: + err = errors.New(v) + case error: + err = v + default: + err = errors.New("unexpected panic in syscall.NewCallback") + } + }() + return syscall.NewCallback(fn), nil +} + +// BUG(brainman): There is no mechanism to run multiple services +// inside one single executable. Perhaps, it can be overcome by +// using RegisterServiceCtrlHandlerEx Windows api. + +// Run executes service name by calling appropriate handler function. +func Run(name string, handler Handler) error { + runtime.LockOSThread() + + tid := windows.GetCurrentThreadId() + + s, err := newService(name, handler) + if err != nil { + return err + } + + ctlHandler := func(ctl uint32) uintptr { + e := ctlEvent{cmd: Cmd(ctl)} + // We assume that this callback function is running on + // the same thread as Run. Nowhere in MS documentation + // I could find statement to guarantee that. So putting + // check here to verify, otherwise things will go bad + // quickly, if ignored. + i := windows.GetCurrentThreadId() + if i != tid { + e.errno = sysErrNewThreadInCallback + } + s.c <- e + return 0 + } + + var svcmain uintptr + getServiceMain(&svcmain) + t := []windows.SERVICE_TABLE_ENTRY{ + {syscall.StringToUTF16Ptr(s.name), svcmain}, + {nil, 0}, + } + + goWaitsH = uintptr(s.goWaits.h) + cWaitsH = uintptr(s.cWaits.h) + sName = t[0].ServiceName + ctlHandlerProc, err = newCallback(ctlHandler) + if err != nil { + return err + } + + go s.run() + + err = windows.StartServiceCtrlDispatcher(&t[0]) + if err != nil { + return err + } + return nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/sys_386.s b/vendor/golang.org/x/sys/windows/svc/sys_386.s new file mode 100644 index 00000000..5e11bfad --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/sys_386.s @@ -0,0 +1,67 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// func servicemain(argc uint32, argv **uint16) +TEXT ·servicemain(SB),7,$0 + MOVL argc+0(FP), AX + MOVL AX, ·sArgc(SB) + MOVL argv+4(FP), AX + MOVL AX, ·sArgv(SB) + + PUSHL BP + PUSHL BX + PUSHL SI + PUSHL DI + + SUBL $12, SP + + MOVL ·sName(SB), AX + MOVL AX, (SP) + MOVL $·servicectlhandler(SB), AX + MOVL AX, 4(SP) + MOVL ·cRegisterServiceCtrlHandlerW(SB), AX + MOVL SP, BP + CALL AX + MOVL BP, SP + CMPL AX, $0 + JE exit + MOVL AX, ·ssHandle(SB) + + MOVL ·goWaitsH(SB), AX + MOVL AX, (SP) + MOVL ·cSetEvent(SB), AX + MOVL SP, BP + CALL AX + MOVL BP, SP + + MOVL ·cWaitsH(SB), AX + MOVL AX, (SP) + MOVL $-1, AX + MOVL AX, 4(SP) + MOVL ·cWaitForSingleObject(SB), AX + MOVL SP, BP + CALL AX + MOVL BP, SP + +exit: + ADDL $12, SP + + POPL DI + POPL SI + POPL BX + POPL BP + + MOVL 0(SP), CX + ADDL $12, SP + JMP CX + +// I do not know why, but this seems to be the only way to call +// ctlHandlerProc on Windows 7. + +// func servicectlhandler(ctl uint32) uintptr +TEXT ·servicectlhandler(SB),7,$0 + MOVL ·ctlHandlerProc(SB), CX + JMP CX diff --git a/vendor/golang.org/x/sys/windows/svc/sys_amd64.s b/vendor/golang.org/x/sys/windows/svc/sys_amd64.s new file mode 100644 index 00000000..87dbec83 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/sys_amd64.s @@ -0,0 +1,41 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// func servicemain(argc uint32, argv **uint16) +TEXT ·servicemain(SB),7,$0 + MOVL CX, ·sArgc(SB) + MOVL DX, ·sArgv(SB) + + SUBQ $32, SP // stack for the first 4 syscall params + + MOVQ ·sName(SB), CX + MOVQ $·servicectlhandler(SB), DX + MOVQ ·cRegisterServiceCtrlHandlerW(SB), AX + CALL AX + CMPQ AX, $0 + JE exit + MOVQ AX, ·ssHandle(SB) + + MOVQ ·goWaitsH(SB), CX + MOVQ ·cSetEvent(SB), AX + CALL AX + + MOVQ ·cWaitsH(SB), CX + MOVQ $4294967295, DX + MOVQ ·cWaitForSingleObject(SB), AX + CALL AX + +exit: + ADDQ $32, SP + RET + +// I do not know why, but this seems to be the only way to call +// ctlHandlerProc on Windows 7. + +// func servicectlhandler(ctl uint32) uintptr +TEXT ·servicectlhandler(SB),7,$0 + MOVQ ·ctlHandlerProc(SB), AX + JMP AX diff --git a/vendor/vendor.json b/vendor/vendor.json index c7d5edf0..792d38e6 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -21,10 +21,10 @@ "revisionTime": "2016-01-02T14:47:48Z" }, { - "checksumSHA1": "RF/PNVBK2xurtGDZlut5whaoItE=", + "checksumSHA1": "L9njXCkN30+qHaLayiiA2Q9jDlY=", "path": "github.com/Microsoft/go-winio", - "revision": "3b8b3c98b207f95fe0cd6c7c311a9ac497ba7c0f", - "revisionTime": "2016-04-18T22:16:11Z" + "revision": "fff283ad5116362ca252298cfc9b95828956d85d", + "revisionTime": "2017-02-01T00:43:30Z" }, { "checksumSHA1": "okEyrP/wQJTUiNl3in0GqJdc9jI=", @@ -98,6 +98,12 @@ "revision": "09b929083d34836a78ce8ee79b22aea082712072", "revisionTime": "2016-08-19T20:09:57Z" }, + { + "checksumSHA1": "dxJfrizyMV1LAZMqkqaasz+T0kU=", + "path": "github.com/docker/engine-api", + "revision": "4290f40c056686fcaa5c9caf02eac1dde9315adf", + "revisionTime": "2016-09-08T23:21:04Z" + }, { "checksumSHA1": "PDXSPZlb43hdy26n6hZi2UJxi5I=", "path": "github.com/docker/engine-api/client", @@ -764,6 +770,30 @@ "revision": "b776ec39b3e54652e09028aaaaac9757f4f8211a", "revisionTime": "2016-04-21T02:29:30Z" }, + { + "checksumSHA1": "IRqLaXM/VQRzkbXPuiqOxTb2W0Y=", + "path": "golang.org/x/sys/windows/svc", + "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", + "revisionTime": "2017-03-08T15:04:45Z" + }, + { + "checksumSHA1": "1ouiyoHRaaMDNeq/nap9B0UuQw4=", + "path": "golang.org/x/sys/windows/svc/debug", + "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", + "revisionTime": "2017-03-08T15:04:45Z" + }, + { + "checksumSHA1": "uVlUSSKplihZG7N+QJ6fzDZ4Kh8=", + "path": "golang.org/x/sys/windows/svc/eventlog", + "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", + "revisionTime": "2017-03-08T15:04:45Z" + }, + { + "checksumSHA1": "6dvXhF7BOHN87mMJ8SaAuFxai0Q=", + "path": "golang.org/x/sys/windows/svc/mgr", + "revision": "8fd966b47dbdd4faa03de0d06e3d733baeb9a1a9", + "revisionTime": "2016-04-10T19:02:41Z" + }, { "checksumSHA1": "WCpgePNKiUKt0S0VE/e5kF5OnzU=", "path": "gopkg.in/fsnotify.v1",