mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
[cli] Add windows service commands (#26442)
Adds a new `windows` command which is available when running on a Windows hosts. The command includes two new subcommands: * `service install` * `service uninstall` The `service install` command will install the called binary into the Windows program files directory, create a new Windows service, setup configuration and data directories, and register the service with the Window eventlog. If the service and/or binary already exist, the service will be stopped, service and eventlog updated if needed, binary replaced, and the service started again. The `service uninstall` command will stop the service, remove the Windows service, and deregister the service with the eventlog. It will not remove the configuration/data directory nor will it remove the installed binary.
This commit is contained in:
@@ -5,7 +5,9 @@ package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/nomad/command/agent"
|
||||
@@ -1339,6 +1341,31 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
|
||||
},
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
maps.Copy(all, map[string]cli.CommandFactory{
|
||||
"windows": func() (cli.Command, error) {
|
||||
return &WindowsCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"windows service": func() (cli.Command, error) {
|
||||
return &WindowsServiceCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"windows service install": func() (cli.Command, error) {
|
||||
return &WindowsServiceInstallCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
"windows service uninstall": func() (cli.Command, error) {
|
||||
return &WindowsServiceUninstallCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
deprecated := map[string]cli.CommandFactory{
|
||||
"client-config": func() (cli.Command, error) {
|
||||
return &DeprecatedCommand{
|
||||
|
||||
35
command/windows.go
Normal file
35
command/windows.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
)
|
||||
|
||||
type WindowsCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *WindowsCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad windows <subcommand> [options]
|
||||
|
||||
This command groups subcommands for managing Nomad as a system service on Windows.
|
||||
|
||||
Service::
|
||||
|
||||
$ nomad windows service
|
||||
|
||||
Refer to the individual subcommand help for detailed usage information.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *WindowsCommand) Name() string { return "windows" }
|
||||
|
||||
func (c *WindowsCommand) Synopsis() string { return "Manage Nomad as a system service on Windows" }
|
||||
|
||||
func (c *WindowsCommand) Run(_ []string) int { return cli.RunResultHelp }
|
||||
41
command/windows_service.go
Normal file
41
command/windows_service.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
)
|
||||
|
||||
type WindowsServiceCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *WindowsServiceCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad windows service <subcommand> [options]
|
||||
|
||||
This command groups subcommands for managing Nomad as a system service on Windows.
|
||||
|
||||
Install:
|
||||
|
||||
$ nomad windows service install
|
||||
|
||||
Uninstall:
|
||||
|
||||
$ nomad windows service uninstall
|
||||
|
||||
Refer to the individual subcommand help for detailed usage information.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *WindowsServiceCommand) Name() string { return "windows service" }
|
||||
|
||||
func (c *WindowsServiceCommand) Synopsis() string {
|
||||
return "Manage nomad as a system service on Windows"
|
||||
}
|
||||
|
||||
func (c *WindowsServiceCommand) Run(_ []string) int { return cli.RunResultHelp }
|
||||
365
command/windows_service_install.go
Normal file
365
command/windows_service_install.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/nomad/helper/winsvc"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
type windowsInstallOpts struct {
|
||||
configDir, dataDir, installDir, binaryPath string
|
||||
reinstall bool
|
||||
}
|
||||
|
||||
type WindowsServiceInstallCommand struct {
|
||||
Meta
|
||||
serviceManagerFn func() (winsvc.WindowsServiceManager, error)
|
||||
privilegedCheckFn func() bool
|
||||
winPaths winsvc.WindowsPaths
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) Synopsis() string {
|
||||
return "Install the Nomad Windows system service"
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) AutocompleteFlags() complete.Flags {
|
||||
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetDefault),
|
||||
complete.Flags{
|
||||
"-config-dir": complete.PredictDirs("*"),
|
||||
"-data-dir": complete.PredictDirs("*"),
|
||||
"-install-dir": complete.PredictDirs("*"),
|
||||
"-reinstall": complete.PredictNothing,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) Name() string { return "windows service install" }
|
||||
|
||||
func (c *WindowsServiceInstallCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad windows service install [options]
|
||||
|
||||
This command installs Nomad as a Windows system service.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault) + `
|
||||
|
||||
Service Install Options:
|
||||
|
||||
-config-dir <dir>
|
||||
Directory to hold the Nomad agent configuration. Defaults
|
||||
to "{{.ProgramFiles}}\HashiCorp\nomad\bin"
|
||||
|
||||
-data-dir <dir>
|
||||
Directory to hold the Nomad agent state. Defaults
|
||||
to "{{.ProgramData}}\HashiCorp\nomad\data"
|
||||
|
||||
-install-dir <dir>
|
||||
Directory to install the Nomad binary. Defaults
|
||||
to "{{.ProgramData}}\HashiCorp\nomad\config"
|
||||
|
||||
-reinstall
|
||||
Allow the nomad Windows service to be stopped during install.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) Run(args []string) int {
|
||||
opts := &windowsInstallOpts{}
|
||||
|
||||
flags := c.Meta.FlagSet(c.Name(), FlagSetDefault)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
flags.StringVar(&opts.configDir, "config-dir", "", "")
|
||||
flags.StringVar(&opts.dataDir, "data-dir", "", "")
|
||||
flags.StringVar(&opts.installDir, "install-dir", "", "")
|
||||
flags.BoolVar(&opts.reinstall, "reinstall", false, "")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if args = flags.Args(); len(args) > 0 {
|
||||
c.Ui.Error("This command takes no arguments")
|
||||
c.Ui.Error(commandErrorText(c))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Set helper functions to defaults if unset
|
||||
if c.winPaths == nil {
|
||||
c.winPaths = winsvc.NewWindowsPaths()
|
||||
}
|
||||
if c.serviceManagerFn == nil {
|
||||
c.serviceManagerFn = winsvc.NewWindowsServiceManager
|
||||
}
|
||||
if c.privilegedCheckFn == nil {
|
||||
c.privilegedCheckFn = winsvc.IsPrivilegedProcess
|
||||
}
|
||||
|
||||
// Check that command is being run with elevated permissions
|
||||
if !c.privilegedCheckFn() {
|
||||
c.Ui.Error("Service install must be run with Administator privileges")
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output("Installing nomad as a Windows service...")
|
||||
|
||||
m, err := c.serviceManagerFn()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Could not connect to Windows service manager - %s", err))
|
||||
return 1
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
if err := c.performInstall(m, opts); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Service install failed: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Info("Successfully installed nomad Windows service")
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) performInstall(m winsvc.WindowsServiceManager, opts *windowsInstallOpts) error {
|
||||
// Check if the nomad service has already been
|
||||
// registered. If so the service needs to be
|
||||
// stopped before proceeding with the install.
|
||||
exists, err := m.IsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check for existing service - %w", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
nmdSvc, err := m.GetService(winsvc.WINDOWS_SERVICE_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get existing service to stop - %w", err)
|
||||
}
|
||||
|
||||
// If the service is running and the reinstall
|
||||
// option was not set, return an error
|
||||
running, err := nmdSvc.IsRunning()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to determine service state - %w", err)
|
||||
}
|
||||
if running && !opts.reinstall {
|
||||
return fmt.Errorf("service is already running. Please run the\ncommand again with -reinstall to stop the service and install.")
|
||||
}
|
||||
|
||||
if running {
|
||||
c.Ui.Output(" Stopping existing nomad service")
|
||||
if err := nmdSvc.Stop(); err != nil {
|
||||
return fmt.Errorf("unable to stop existing service - %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Install the nomad binary into the system
|
||||
if err = c.binaryInstall(opts); err != nil {
|
||||
return fmt.Errorf("binary install failed - %w", err)
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf(" Nomad binary installed to: %s", opts.binaryPath))
|
||||
|
||||
// Create a configuration directory and add
|
||||
// a basic configuration file if no configuration
|
||||
// currently exists
|
||||
if err = c.configInstall(opts); err != nil {
|
||||
return fmt.Errorf("configuration install failed - %w", err)
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf(" Nomad configuration directory: %s", opts.configDir))
|
||||
c.Ui.Output(fmt.Sprintf(" Nomad agent data directory: %s", opts.configDir))
|
||||
|
||||
// Now let's install that service
|
||||
if err := c.serviceInstall(m, opts); err != nil {
|
||||
return fmt.Errorf("service install failed - %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) serviceInstall(m winsvc.WindowsServiceManager, opts *windowsInstallOpts) error {
|
||||
var err error
|
||||
var srvc winsvc.WindowsService
|
||||
cmd := fmt.Sprintf("%s agent -config %s", opts.binaryPath, opts.configDir)
|
||||
|
||||
exists, err := m.IsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service registration check failed - %w", err)
|
||||
}
|
||||
|
||||
// If the service already exists, open it and update. Otherwise
|
||||
// create a new service.
|
||||
if exists {
|
||||
srvc, err = m.GetService(winsvc.WINDOWS_SERVICE_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get existing service - %w", err)
|
||||
}
|
||||
defer srvc.Close()
|
||||
if err := srvc.Configure(winsvc.WindowsServiceConfiguration{
|
||||
StartType: winsvc.StartAutomatic,
|
||||
DisplayName: winsvc.WINDOWS_SERVICE_DISPLAY_NAME,
|
||||
Description: winsvc.WINDOWS_SERVICE_DESCRIPTION,
|
||||
BinaryPathName: cmd,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("unable to configure service - %w", err)
|
||||
}
|
||||
} else {
|
||||
srvc, err = m.CreateService(winsvc.WINDOWS_SERVICE_NAME, opts.binaryPath,
|
||||
winsvc.WindowsServiceConfiguration{
|
||||
StartType: winsvc.StartAutomatic,
|
||||
DisplayName: winsvc.WINDOWS_SERVICE_DISPLAY_NAME,
|
||||
Description: winsvc.WINDOWS_SERVICE_DESCRIPTION,
|
||||
BinaryPathName: cmd,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create service - %w", err)
|
||||
}
|
||||
defer srvc.Close()
|
||||
}
|
||||
|
||||
// Enable the service in the Windows eventlog
|
||||
if err := srvc.EnableEventlog(); err != nil {
|
||||
return fmt.Errorf("could not configure eventlog - %w", err)
|
||||
}
|
||||
|
||||
// Ensure the service is stopped
|
||||
if err := srvc.Stop(); err != nil {
|
||||
return fmt.Errorf("could not stop service - %w", err)
|
||||
}
|
||||
|
||||
// Start the service so the new binary is in use
|
||||
if err := srvc.Start(); err != nil {
|
||||
return fmt.Errorf("could not start service - %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) configInstall(opts *windowsInstallOpts) error {
|
||||
// If the config or data directory are unset, default them
|
||||
if opts.configDir == "" {
|
||||
opts.configDir = filepath.Join(winsvc.WINDOWS_INSTALL_APPDATA_DIRECTORY, "config")
|
||||
}
|
||||
if opts.dataDir == "" {
|
||||
opts.dataDir = filepath.Join(winsvc.WINDOWS_INSTALL_APPDATA_DIRECTORY, "data")
|
||||
}
|
||||
|
||||
var err error
|
||||
if opts.configDir, err = c.winPaths.Expand(opts.configDir); err != nil {
|
||||
return fmt.Errorf("cannot generate configuration path - %s", err)
|
||||
}
|
||||
if opts.dataDir, err = c.winPaths.Expand(opts.dataDir); err != nil {
|
||||
return fmt.Errorf("cannot generate data path - %s", err)
|
||||
}
|
||||
|
||||
// Ensure directories exist
|
||||
if err = c.winPaths.CreateDirectory(opts.configDir, true); err != nil {
|
||||
return fmt.Errorf("cannot create configuration directory - %s", err)
|
||||
}
|
||||
|
||||
if err = c.winPaths.CreateDirectory(opts.dataDir, true); err != nil {
|
||||
return fmt.Errorf("cannot create data directory - %s", err)
|
||||
}
|
||||
|
||||
// Check if any configuration files exist
|
||||
matches, _ := filepath.Glob(filepath.Join(opts.configDir, "*"))
|
||||
if len(matches) < 1 {
|
||||
f, err := os.Create(filepath.Join(opts.configDir, "config.hcl"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create default configuration file - %s", err)
|
||||
}
|
||||
fmt.Fprintf(f, strings.TrimSpace(`
|
||||
# Full configuration options can be found at https://developer.hashicorp.com/nomad/docs/configuration
|
||||
|
||||
data_dir = "%s"
|
||||
bind_addr = "0.0.0.0"
|
||||
|
||||
server {
|
||||
# license_path is required for Nomad Enterprise as of Nomad v1.1.1+
|
||||
#license_path = "%s\license.hclic"
|
||||
enabled = true
|
||||
bootstrap_expect = 1
|
||||
}
|
||||
|
||||
client {
|
||||
enabled = true
|
||||
servers = ["127.0.0.1"]
|
||||
}
|
||||
|
||||
log_level = "WARN"
|
||||
eventlog {
|
||||
enabled = true
|
||||
level = "ERROR"
|
||||
}
|
||||
`), strings.ReplaceAll(opts.dataDir, `\`, `\\`), strings.ReplaceAll(opts.configDir, `\`, `\\`))
|
||||
f.Close()
|
||||
c.Ui.Output(fmt.Sprintf(" Added initial configuration file: %s", f.Name()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WindowsServiceInstallCommand) binaryInstall(opts *windowsInstallOpts) error {
|
||||
// Get the path to the currently executing nomad. This
|
||||
// will be installed for the service to call.
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot detect current nomad path - %s", err)
|
||||
}
|
||||
|
||||
// Build the needed paths
|
||||
if opts.installDir == "" {
|
||||
opts.installDir = winsvc.WINDOWS_INSTALL_BIN_DIRECTORY
|
||||
}
|
||||
opts.installDir, err = c.winPaths.Expand(opts.installDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate binary install path - %s", err)
|
||||
}
|
||||
opts.binaryPath = filepath.Join(opts.installDir, "nomad.exe")
|
||||
|
||||
// Ensure the install directory exists
|
||||
if err = c.winPaths.CreateDirectory(opts.installDir, false); err != nil {
|
||||
return fmt.Errorf("could not create binary install directory - %s", err)
|
||||
}
|
||||
|
||||
// Create a new copy of the current binary to install
|
||||
exeFile, err := os.Open(exePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open current nomad path for install - %s", err)
|
||||
}
|
||||
defer exeFile.Close()
|
||||
|
||||
// Copy into a temporary file which can then be moved
|
||||
// into the correct location.
|
||||
dstFile, err := os.CreateTemp(os.TempDir(), "nomad*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create copy - %s", err)
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
if _, err = io.Copy(dstFile, exeFile); err != nil {
|
||||
return fmt.Errorf("cannot write nomad binary for install - %s", err)
|
||||
}
|
||||
dstFile.Close()
|
||||
|
||||
// With a copy ready to be moved into place, ensure that
|
||||
// the path is clear then move the file.
|
||||
if err = os.RemoveAll(opts.binaryPath); err != nil {
|
||||
return fmt.Errorf("cannot remove existing nomad binary install - %s", err)
|
||||
}
|
||||
|
||||
if err = os.Rename(dstFile.Name(), opts.binaryPath); err != nil {
|
||||
return fmt.Errorf("cannot install new nomad binary - %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
391
command/windows_service_install_test.go
Normal file
391
command/windows_service_install_test.go
Normal file
@@ -0,0 +1,391 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/nomad/helper/winsvc"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestWindowsServiceInstallCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
freshInstallFn := func(m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectCreateService(winsvc.WINDOWS_SERVICE_NAME, "nomad.exe",
|
||||
winsvc.WindowsServiceConfiguration{}, srv, nil)
|
||||
srv.ExpectEnableEventlog(nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectStart(nil)
|
||||
}
|
||||
upgradeInstallFn := func(m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectIsRunning(false, nil)
|
||||
srv.ExpectConfigure(winsvc.WindowsServiceConfiguration{}, nil)
|
||||
srv.ExpectEnableEventlog(nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectStart(nil)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
privilegeFn func() bool
|
||||
setup func(string, *winsvc.MockWindowsServiceManager)
|
||||
after func(string)
|
||||
output string
|
||||
errOutput string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "fresh install success",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
freshInstallFn(m)
|
||||
},
|
||||
output: "Success",
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
desc: "fresh install writes config",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
freshInstallFn(m)
|
||||
},
|
||||
after: func(dir string) {
|
||||
must.FileExists(t, filepath.Join(dir, "programdata/HashiCorp/nomad/config/config.hcl"))
|
||||
},
|
||||
output: "initial configuration file",
|
||||
},
|
||||
{
|
||||
desc: "fresh install binary file",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
freshInstallFn(m)
|
||||
},
|
||||
after: func(dir string) {
|
||||
must.FileExists(t, filepath.Join(dir, "programfiles/HashiCorp/nomad/bin/nomad.exe"))
|
||||
},
|
||||
output: "binary installed",
|
||||
},
|
||||
{
|
||||
desc: "fresh install configuration already exists",
|
||||
setup: func(dir string, m *winsvc.MockWindowsServiceManager) {
|
||||
cdir := filepath.Join(dir, "programdata/HashiCorp/nomad/config")
|
||||
err := os.MkdirAll(cdir, 0o755)
|
||||
must.NoError(t, err)
|
||||
f, err := os.Create(filepath.Join(cdir, "custom.hcl"))
|
||||
must.NoError(t, err)
|
||||
f.Close()
|
||||
freshInstallFn(m)
|
||||
},
|
||||
after: func(dir string) {
|
||||
must.FileNotExists(t, filepath.Join(dir, "programdata/HashiCorp/nomad/config/config.hcl"))
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fresh install binary already exists",
|
||||
setup: func(dir string, m *winsvc.MockWindowsServiceManager) {
|
||||
cdir := filepath.Join(dir, "programfiles/HashiCorp/nomad/bin")
|
||||
err := os.MkdirAll(cdir, 0o755)
|
||||
must.NoError(t, err)
|
||||
// create empty binary file
|
||||
f, err := os.Create(filepath.Join(cdir, "nomad.exe"))
|
||||
must.NoError(t, err)
|
||||
f.Close()
|
||||
freshInstallFn(m)
|
||||
},
|
||||
after: func(dir string) {
|
||||
s, err := os.Stat(filepath.Join(dir, "programfiles/HashiCorp/nomad/bin/nomad.exe"))
|
||||
must.NoError(t, err)
|
||||
// ensure binary file is not empty
|
||||
must.NonZero(t, s.Size())
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "upgrade install success",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
upgradeInstallFn(m)
|
||||
},
|
||||
output: "Success",
|
||||
status: 0,
|
||||
},
|
||||
{
|
||||
desc: "with arguments",
|
||||
args: []string{"any", "value"},
|
||||
errOutput: "takes no arguments",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "with -install-dir",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
freshInstallFn(m)
|
||||
},
|
||||
args: []string{"-install-dir", "{{.ProgramFiles}}/custom/bin"},
|
||||
after: func(dir string) {
|
||||
_, err := os.Stat(filepath.Join(dir, "programfiles/custom/bin/nomad.exe"))
|
||||
must.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with -config-dir",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
freshInstallFn(m)
|
||||
},
|
||||
args: []string{"-config-dir", "{{.ProgramData}}/custom/nomad-configuration"},
|
||||
after: func(dir string) {
|
||||
_, err := os.Stat(filepath.Join(dir, "programdata/custom/nomad-configuration"))
|
||||
must.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with -data-dir",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
freshInstallFn(m)
|
||||
},
|
||||
args: []string{"-data-dir", "{{.ProgramData}}/custom/nomad-data"},
|
||||
after: func(dir string) {
|
||||
_, err := os.Stat(filepath.Join(dir, "programdata/custom/nomad-data"))
|
||||
must.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "service registered check failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, errors.New("lookup failure"))
|
||||
},
|
||||
errOutput: "unable to check for existing service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "service registered check failure during service install",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, errors.New("lookup failure"))
|
||||
},
|
||||
errOutput: "registration check failed",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "get existing service to stop failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, nil, errors.New("service get failure"))
|
||||
},
|
||||
errOutput: "could not get existing service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "stop existing service failure",
|
||||
args: []string{"-reinstall"},
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectIsRunning(true, nil)
|
||||
srv.ExpectStop(errors.New("cannot stop"))
|
||||
},
|
||||
errOutput: "unable to stop existing service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "get existing service to configure failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectIsRunning(false, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, nil, errors.New("service get failure"))
|
||||
},
|
||||
errOutput: "unable to get existing service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "configure service failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
srv.ExpectIsRunning(false, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectConfigure(winsvc.WindowsServiceConfiguration{}, errors.New("configure failure"))
|
||||
},
|
||||
errOutput: "unable to configure service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "create service failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectCreateService(winsvc.WINDOWS_SERVICE_NAME, "nomad.exe", winsvc.WindowsServiceConfiguration{}, nil, errors.New("create service failure"))
|
||||
},
|
||||
errOutput: "unable to create service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "eventlog setup failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectCreateService(winsvc.WINDOWS_SERVICE_NAME, "nomad.exe", winsvc.WindowsServiceConfiguration{}, srv, nil)
|
||||
srv.ExpectEnableEventlog(errors.New("eventlog configure failure"))
|
||||
},
|
||||
errOutput: "could not configure eventlog",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "service stop pre-start failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectCreateService(winsvc.WINDOWS_SERVICE_NAME, "nomad.exe", winsvc.WindowsServiceConfiguration{}, srv, nil)
|
||||
srv.ExpectEnableEventlog(nil)
|
||||
srv.ExpectStop(errors.New("service stop failure"))
|
||||
},
|
||||
errOutput: "could not stop service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "service start failure",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
m.ExpectCreateService(winsvc.WINDOWS_SERVICE_NAME, "nomad.exe", winsvc.WindowsServiceConfiguration{}, srv, nil)
|
||||
srv.ExpectEnableEventlog(nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectStart(errors.New("service start failure"))
|
||||
},
|
||||
errOutput: "could not start service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "upgrade without -reinstall and service running",
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectIsRunning(true, nil)
|
||||
},
|
||||
errOutput: "again with -reinstall",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "upgrade with -reinstall and service running",
|
||||
args: []string{"-reinstall"},
|
||||
setup: func(_ string, m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectIsRunning(true, nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectConfigure(winsvc.WindowsServiceConfiguration{}, nil)
|
||||
srv.ExpectEnableEventlog(nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectStart(nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "not running as administator",
|
||||
privilegeFn: func() bool { return false },
|
||||
errOutput: "must be run with Administator privileges",
|
||||
status: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testDir := t.TempDir()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
mgr := winsvc.NewMockWindowsServiceManager(t)
|
||||
if tc.setup != nil {
|
||||
tc.setup(testDir, mgr)
|
||||
}
|
||||
|
||||
pfn := tc.privilegeFn
|
||||
if pfn == nil {
|
||||
pfn = func() bool { return true }
|
||||
}
|
||||
|
||||
cmd := &WindowsServiceInstallCommand{
|
||||
Meta: Meta{Ui: ui},
|
||||
serviceManagerFn: func() (winsvc.WindowsServiceManager, error) {
|
||||
return mgr, nil
|
||||
},
|
||||
winPaths: createWinPaths(testDir),
|
||||
privilegedCheckFn: pfn,
|
||||
}
|
||||
result := cmd.Run(tc.args)
|
||||
|
||||
out := ui.OutputWriter.String()
|
||||
outErr := ui.ErrorWriter.String()
|
||||
must.Eq(t, result, tc.status)
|
||||
if tc.output != "" {
|
||||
must.StrContains(t, out, tc.output)
|
||||
}
|
||||
if tc.errOutput != "" {
|
||||
must.StrContains(t, outErr, tc.errOutput)
|
||||
}
|
||||
if tc.after != nil {
|
||||
tc.after(testDir)
|
||||
}
|
||||
|
||||
mgr.AssertExpectations()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createWinPaths(rootDir string) winsvc.WindowsPaths {
|
||||
return &testWindowsPaths{
|
||||
SystemRoot: filepath.Join(rootDir, "systemroot"),
|
||||
SystemDrive: rootDir,
|
||||
ProgramData: filepath.Join(rootDir, "programdata"),
|
||||
ProgramFiles: filepath.Join(rootDir, "programfiles"),
|
||||
}
|
||||
}
|
||||
|
||||
type testWindowsPaths struct {
|
||||
SystemRoot string
|
||||
SystemDrive string
|
||||
ProgramData string
|
||||
ProgramFiles string
|
||||
}
|
||||
|
||||
func (t *testWindowsPaths) Expand(path string) (string, error) {
|
||||
tmpl, err := template.New("expansion").Option("missingkey=error").Parse(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := new(bytes.Buffer)
|
||||
if err := tmpl.Execute(result, t); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(result.String(), `\`, "/"), nil
|
||||
}
|
||||
|
||||
func (t *testWindowsPaths) CreateDirectory(path string, _ bool) error {
|
||||
return os.MkdirAll(path, 0o755)
|
||||
}
|
||||
121
command/windows_service_uninstall.go
Normal file
121
command/windows_service_uninstall.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/nomad/helper/winsvc"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
type WindowsServiceUninstallCommand struct {
|
||||
Meta
|
||||
serviceManagerFn func() (winsvc.WindowsServiceManager, error)
|
||||
privilegedCheckFn func() bool
|
||||
}
|
||||
|
||||
func (c *WindowsServiceUninstallCommand) AutoCompleteFlags() complete.Flags {
|
||||
return c.Meta.AutocompleteFlags(FlagSetDefault)
|
||||
}
|
||||
|
||||
func (c *WindowsServiceUninstallCommand) Synopsis() string {
|
||||
return "Uninstall the nomad Windows system service"
|
||||
}
|
||||
|
||||
func (c *WindowsServiceUninstallCommand) Name() string { return "windows service uninstall" }
|
||||
|
||||
func (c *WindowsServiceUninstallCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad windows service uninstall [options]
|
||||
|
||||
This command uninstalls nomad as a Windows system service.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage(usageOptsDefault)
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *WindowsServiceUninstallCommand) Run(args []string) int {
|
||||
flags := c.Meta.FlagSet(c.Name(), FlagSetDefault)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if args = flags.Args(); len(args) > 0 {
|
||||
c.Ui.Error("This command takes no arguments")
|
||||
c.Ui.Error(commandErrorText(c))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Set helper functions to default if unset
|
||||
if c.serviceManagerFn == nil {
|
||||
c.serviceManagerFn = winsvc.NewWindowsServiceManager
|
||||
}
|
||||
if c.privilegedCheckFn == nil {
|
||||
c.privilegedCheckFn = winsvc.IsPrivilegedProcess
|
||||
}
|
||||
|
||||
// Check that command is being run with elevated permissions
|
||||
if !c.privilegedCheckFn() {
|
||||
c.Ui.Error("Service uninstall must be run with Administator privileges")
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output("Uninstalling nomad Windows service...")
|
||||
|
||||
m, err := c.serviceManagerFn()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Could not connect to Windows service manager - %s", err))
|
||||
return 1
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
if err := c.performUninstall(m); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Service uninstall failed: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Info("Successfully uninstalled nomad Windows service")
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *WindowsServiceUninstallCommand) performUninstall(m winsvc.WindowsServiceManager) error {
|
||||
// Check that the nomad service is currently registered
|
||||
exists, err := m.IsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check for existing service - %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Grab the service and ensure the service is stopped
|
||||
srvc, err := m.GetService(winsvc.WINDOWS_SERVICE_NAME)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get existing service - %w", err)
|
||||
}
|
||||
defer srvc.Close()
|
||||
|
||||
if err := srvc.Stop(); err != nil {
|
||||
return fmt.Errorf("unable to stop service - %w", err)
|
||||
}
|
||||
|
||||
// Remove the service from the event log
|
||||
if err := srvc.DisableEventlog(); err != nil {
|
||||
return fmt.Errorf("could not remove eventlog configuration - %w", err)
|
||||
}
|
||||
|
||||
// Finally, delete the service
|
||||
if err := srvc.Delete(); err != nil {
|
||||
return fmt.Errorf("could not delete service - %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
146
command/windows_service_uninstall_test.go
Normal file
146
command/windows_service_uninstall_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/nomad/helper/winsvc"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestWindowsServiceUninstallCommand_Run(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
privilegeFn func() bool
|
||||
setup func(*winsvc.MockWindowsServiceManager)
|
||||
output string
|
||||
errOutput string
|
||||
status int
|
||||
}{
|
||||
{
|
||||
desc: "service installed",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectDisableEventlog(nil)
|
||||
srv.ExpectDelete(nil)
|
||||
},
|
||||
output: "uninstalled nomad",
|
||||
},
|
||||
{
|
||||
desc: "service not installed",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, nil)
|
||||
},
|
||||
output: "uninstalled nomad",
|
||||
},
|
||||
{
|
||||
desc: "service registration check failure",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, false, errors.New("registered check failure"))
|
||||
},
|
||||
errOutput: "unable to check for existing service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "get service failure",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, nil, errors.New("get service failure"))
|
||||
},
|
||||
errOutput: "could not get existing service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "service stop failure",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectStop(errors.New("service stop failure"))
|
||||
},
|
||||
errOutput: "unable to stop service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "disable eventlog failure",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectDisableEventlog(errors.New("disable eventlog failure"))
|
||||
},
|
||||
errOutput: "could not remove eventlog configuration",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "delete service failure",
|
||||
setup: func(m *winsvc.MockWindowsServiceManager) {
|
||||
srv := m.NewMockWindowsService()
|
||||
m.ExpectIsServiceRegistered(winsvc.WINDOWS_SERVICE_NAME, true, nil)
|
||||
m.ExpectGetService(winsvc.WINDOWS_SERVICE_NAME, srv, nil)
|
||||
srv.ExpectStop(nil)
|
||||
srv.ExpectDisableEventlog(nil)
|
||||
srv.ExpectDelete(errors.New("service delete failure"))
|
||||
},
|
||||
errOutput: "could not delete service",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "with arguments",
|
||||
args: []string{"any", "value"},
|
||||
errOutput: "command takes no arguments",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
desc: "not running as administator",
|
||||
privilegeFn: func() bool { return false },
|
||||
errOutput: "must be run with Administator privileges",
|
||||
status: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
mgr := winsvc.NewMockWindowsServiceManager(t)
|
||||
if tc.setup != nil {
|
||||
tc.setup(mgr)
|
||||
}
|
||||
|
||||
pfn := tc.privilegeFn
|
||||
if pfn == nil {
|
||||
pfn = func() bool { return true }
|
||||
}
|
||||
|
||||
cmd := &WindowsServiceUninstallCommand{
|
||||
Meta: Meta{Ui: ui},
|
||||
serviceManagerFn: func() (winsvc.WindowsServiceManager, error) {
|
||||
return mgr, nil
|
||||
},
|
||||
privilegedCheckFn: pfn,
|
||||
}
|
||||
result := cmd.Run(tc.args)
|
||||
|
||||
out := ui.OutputWriter.String()
|
||||
outErr := ui.ErrorWriter.String()
|
||||
must.Eq(t, result, tc.status)
|
||||
if tc.output != "" {
|
||||
must.StrContains(t, out, tc.output)
|
||||
}
|
||||
if tc.errOutput != "" {
|
||||
must.StrContains(t, outErr, tc.errOutput)
|
||||
}
|
||||
|
||||
mgr.AssertExpectations()
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user