From b8ac1a800d254a22d539b8fda606bd821890d1ad Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Tue, 4 Jun 2019 14:17:50 -0700 Subject: [PATCH 01/51] implement a packer console analogous to the terraform console --- command/console.go | 176 +++++++++++++++++++++++++++++++++++++++++++++ commands.go | 5 ++ packer/core.go | 1 - packer/ui.go | 6 +- 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 command/console.go diff --git a/command/console.go b/command/console.go new file mode 100644 index 000000000..e48744b49 --- /dev/null +++ b/command/console.go @@ -0,0 +1,176 @@ +package command + +import ( + "errors" + "fmt" + "io" + "strings" + + "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/template" + "github.com/hashicorp/packer/template/interpolate" + "github.com/posener/complete" +) + +const TiniestBuilder = `{ + "builders": [ + { + "type":"null", + "communicator": "none" + } + ] +}` + +type ConsoleCommand struct { + Meta +} + +func (c *ConsoleCommand) Run(args []string) int { + flags := c.Meta.FlagSet("console", FlagSetVars) + flags.Usage = func() { c.Ui.Say(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + var templ *template.Template + + args = flags.Args() + if len(args) < 1 { + // If user has not definied a builder, create a tiny null placeholder + // builder so that we can properly initialize the core + tpl, err := template.Parse(strings.NewReader(TiniestBuilder)) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to generate placeholder template: %s", err)) + return 1 + } + templ = tpl + } else if len(args) == 1 { + // Parse the provided template + tpl, err := template.ParseFile(args[0]) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err)) + return 1 + } + templ = tpl + } else { + // User provided too many arguments + flags.Usage() + return 1 + } + + // Get the core + core, err := c.Meta.Core(templ) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + + // IO Loop + session := &REPLSession{ + Core: core, + } + + return c.modeInteractive(session) +} + +func (*ConsoleCommand) Help() string { + helpText := ` +Usage: packer console [options] [TEMPLATE] + + Creates a console for testing variable interpolation. + If a template is provided, this command will load the template and any + variables defined therein into its context to be referenced during + interpolation. + +Options: + -var 'key=value' Variable for templates, can be used multiple times. + -var-file=path JSON file containing user variables. +` + + return strings.TrimSpace(helpText) +} + +func (*ConsoleCommand) Synopsis() string { + return "check that a template is valid" +} + +func (*ConsoleCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (*ConsoleCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-var": complete.PredictNothing, + "-var-file": complete.PredictNothing, + } +} + +func (c *ConsoleCommand) modeInteractive(session *REPLSession) int { + for { + // Read a line + line, err := c.Ui.Ask("> ") + if err == packer.ErrInterrupted { + break + } else if err == io.EOF { + break + } + out, err := session.Handle(line) + if err == ErrSessionExit { + break + } + if err != nil { + c.Ui.Error(err.Error()) + continue + } + + c.Ui.Say(out) + } + + return 0 +} + +// ErrSessionExit is a special error result that should be checked for +// from Handle to signal a graceful exit. +var ErrSessionExit = errors.New("Session exit") + +// Session represents the state for a single REPL session. +type REPLSession struct { + // Core is used for constructing interpolations based off packer templates + Core *packer.Core +} + +// Handle handles a single line of input from the REPL. +// +// The return value is the output and the error to show. +func (s *REPLSession) Handle(line string) (string, error) { + switch { + case strings.TrimSpace(line) == "exit": + return "", ErrSessionExit + case strings.TrimSpace(line) == "help": + return s.handleHelp() + default: + return s.handleEval(line) + } +} + +func (s *REPLSession) handleEval(line string) (string, error) { + ctx := s.Core.Context() + rendered, err := interpolate.Render(line, ctx) + if err != nil { + return "", fmt.Errorf("Error interpolating: %s", err) + } + return rendered, nil +} + +func (s *REPLSession) handleHelp() (string, error) { + text := ` +The Packer console allows you to experiment with Packer interpolations. +You may access variables in the Packer config you called the console with. + +Type in the interpolation to test and hit to see the result. + +To exit the console, type "exit" and hit , or use Control-C. +` + + return strings.TrimSpace(text), nil +} diff --git a/commands.go b/commands.go index aff2ec9f8..f7533588c 100644 --- a/commands.go +++ b/commands.go @@ -22,6 +22,11 @@ func init() { Meta: *CommandMeta, }, nil }, + "console": func() (cli.Command, error) { + return &command.ConsoleCommand{ + Meta: *CommandMeta, + }, nil + }, "fix": func() (cli.Command, error) { return &command.FixCommand{ diff --git a/packer/core.go b/packer/core.go index 0b3670371..281d1c9c0 100644 --- a/packer/core.go +++ b/packer/core.go @@ -97,7 +97,6 @@ func NewCore(c *CoreConfig) (*Core, error) { result.builds[v] = b } - return result, nil } diff --git a/packer/ui.go b/packer/ui.go index 2ee866d2b..f2e25c050 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -18,6 +18,8 @@ import ( getter "github.com/hashicorp/go-getter" ) +var ErrInterrupted = errors.New("interrupted") + type UiColor uint const ( @@ -190,7 +192,7 @@ func (rw *BasicUi) Ask(query string) (string, error) { defer rw.l.Unlock() if rw.interrupted { - return "", errors.New("interrupted") + return "", ErrInterrupted } if rw.TTY == nil { @@ -228,7 +230,7 @@ func (rw *BasicUi) Ask(query string) (string, error) { // Mark that we were interrupted so future Ask calls fail. rw.interrupted = true - return "", errors.New("interrupted") + return "", ErrInterrupted } } From 3845186c4c074d4206c6817628253912d447f189 Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Thu, 6 Jun 2019 11:13:17 +0530 Subject: [PATCH 02/51] feat: add feature to import user-data from a file Signed-off-by: Pratyush singhal --- builder/googlecompute/config.go | 1 + builder/googlecompute/step_create_instance.go | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index df55d99d2..41444d757 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -62,6 +62,7 @@ type Config struct { Subnetwork string `mapstructure:"subnetwork"` Tags []string `mapstructure:"tags"` UseInternalIP bool `mapstructure:"use_internal_ip"` + UserdataFile string `mapstructure:"userdata_file"` Zone string `mapstructure:"zone"` Account AccountFile diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 5ed854670..5fd0de5ce 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -19,7 +19,6 @@ type StepCreateInstance struct { func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) (map[string]string, error) { instanceMetadata := make(map[string]string) var err error - // Copy metadata from config. for k, v := range c.Metadata { instanceMetadata[k] = v @@ -45,6 +44,13 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) } else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists { instanceMetadata[StartupWrappedScriptKey] = wrappedStartupScript } + + if c.UserdataFile != "" { + var content []byte + content, err = ioutil.ReadFile(c.UserdataFile) + instanceMetadata["user-data"] = string(content) + } + if sourceImage.IsWindows() { // Windows startup script support is not yet implemented. // Mark the startup script as done. From 1e1af353416ccbee6fd26f8ffda4e92a0589bbbe Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Thu, 6 Jun 2019 16:14:57 +0530 Subject: [PATCH 03/51] refactor: replace userdata_files with generic metadata_files map Signed-off-by: Pratyush singhal --- builder/googlecompute/config.go | 2 +- builder/googlecompute/step_create_instance.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index 41444d757..7c78a93cd 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -62,7 +62,7 @@ type Config struct { Subnetwork string `mapstructure:"subnetwork"` Tags []string `mapstructure:"tags"` UseInternalIP bool `mapstructure:"use_internal_ip"` - UserdataFile string `mapstructure:"userdata_file"` + MetadataFiles map[string]string `mapstructure:"metadata_files"` Zone string `mapstructure:"zone"` Account AccountFile diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 5fd0de5ce..0688d6280 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -45,10 +45,11 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) instanceMetadata[StartupWrappedScriptKey] = wrappedStartupScript } - if c.UserdataFile != "" { + // Read metadata from files specified with metadata_files + for key, value := range c.MetadataFiles { var content []byte - content, err = ioutil.ReadFile(c.UserdataFile) - instanceMetadata["user-data"] = string(content) + content, err = ioutil.ReadFile(value) + instanceMetadata[key] = string(content) } if sourceImage.IsWindows() { From df916e805e56563ac1a418d688a3617f874acbf2 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 10:52:12 -0700 Subject: [PATCH 04/51] borrow wrappedreadline workarounds from terraform and implement a similar check for piped commands; this makes the cli experience much cleaner --- command/console.go | 51 ++++++- command/meta.go | 13 ++ go.mod | 1 + go.sum | 2 + helper/wrappedreadline/wrappedreadline.go | 125 ++++++++++++++++++ .../wrappedreadline/wrappedreadline_unix.go | 52 ++++++++ .../wrappedreadline_windows.go | 57 ++++++++ 7 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 helper/wrappedreadline/wrappedreadline.go create mode 100644 helper/wrappedreadline/wrappedreadline_unix.go create mode 100644 helper/wrappedreadline/wrappedreadline_windows.go diff --git a/command/console.go b/command/console.go index e48744b49..17f1fc1c3 100644 --- a/command/console.go +++ b/command/console.go @@ -1,11 +1,14 @@ package command import ( + "bufio" "errors" "fmt" "io" "strings" + "github.com/chzyer/readline" + "github.com/hashicorp/packer/helper/wrappedreadline" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template" "github.com/hashicorp/packer/template/interpolate" @@ -70,6 +73,11 @@ func (c *ConsoleCommand) Run(args []string) int { Core: core, } + // Determine if stdin is a pipe. If so, we evaluate directly. + if c.StdinPiped() { + return c.modePiped(session) + } + return c.modeInteractive(session) } @@ -105,12 +113,45 @@ func (*ConsoleCommand) AutocompleteFlags() complete.Flags { } } -func (c *ConsoleCommand) modeInteractive(session *REPLSession) int { +func (c *ConsoleCommand) modePiped(session *REPLSession) int { + var lastResult string + scanner := bufio.NewScanner(wrappedreadline.Stdin()) + for scanner.Scan() { + result, err := session.Handle(strings.TrimSpace(scanner.Text())) + if err != nil { + return 0 + } + // Store the last result + lastResult = result + } + + // Output the final result + c.Ui.Message(lastResult) + return 0 +} + +func (c *ConsoleCommand) modeInteractive(session *REPLSession) int { // Setup the UI so we can output directly to stdout + l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{ + Prompt: "> ", + InterruptPrompt: "^C", + EOFPrompt: "exit", + HistorySearchFold: true, + })) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error initializing console: %s", + err)) + return 1 + } for { // Read a line - line, err := c.Ui.Ask("> ") - if err == packer.ErrInterrupted { - break + line, err := l.Readline() + if err == readline.ErrInterrupt { + if len(line) == 0 { + break + } else { + continue + } } else if err == io.EOF { break } @@ -144,6 +185,8 @@ type REPLSession struct { // The return value is the output and the error to show. func (s *REPLSession) Handle(line string) (string, error) { switch { + case strings.TrimSpace(line) == "": + return "", nil case strings.TrimSpace(line) == "exit": return "", ErrSessionExit case strings.TrimSpace(line) == "help": diff --git a/command/meta.go b/command/meta.go index 8c7101347..b92116428 100644 --- a/command/meta.go +++ b/command/meta.go @@ -5,9 +5,11 @@ import ( "flag" "fmt" "io" + "os" kvflag "github.com/hashicorp/packer/helper/flag-kv" sliceflag "github.com/hashicorp/packer/helper/flag-slice" + "github.com/hashicorp/packer/helper/wrappedreadline" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template" ) @@ -140,3 +142,14 @@ func (m *Meta) ValidateFlags() error { // TODO return nil } + +// StdinPiped returns true if the input is piped. +func (m *Meta) StdinPiped() bool { + fi, err := wrappedreadline.Stdin().Stat() + if err != nil { + // If there is an error, let's just say its not piped + return false + } + + return fi.Mode()&os.ModeNamedPipe != 0 +} diff --git a/go.mod b/go.mod index 74e6a706d..8d35249dd 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3 github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae github.com/cheggaaa/pb v1.0.27 + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/creack/goselect v0.1.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/digitalocean/godo v1.11.1 diff --git a/go.sum b/go.sum index a844f081d..776cc1a18 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXG github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY= diff --git a/helper/wrappedreadline/wrappedreadline.go b/helper/wrappedreadline/wrappedreadline.go new file mode 100644 index 000000000..3bf7db0a0 --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline.go @@ -0,0 +1,125 @@ +// Shamelessly copied from the Terraform repo because it wasn't worth vendoring +// out two hundred lines of code so Packer could use it too. +// +// wrappedreadline is a package that has helpers for interacting with +// readline from a panicwrap executable. +// +// panicwrap overrides the standard file descriptors so that the child process +// no longer looks like a TTY. The helpers here access the extra file descriptors +// passed by panicwrap to fix that. +// +// panicwrap should be checked for with panicwrap.Wrapped before using this +// librar, since this library won't adapt if the binary is not wrapped. +package wrappedreadline + +import ( + "os" + "runtime" + + "github.com/chzyer/readline" + "github.com/mitchellh/panicwrap" +) + +// Override overrides the values in readline.Config that need to be +// set with wrapped values. +func Override(cfg *readline.Config) *readline.Config { + cfg.Stdin = Stdin() + cfg.Stdout = Stdout() + cfg.Stderr = Stderr() + + cfg.FuncGetWidth = TerminalWidth + cfg.FuncIsTerminal = IsTerminal + + rm := RawMode{StdinFd: int(Stdin().Fd())} + cfg.FuncMakeRaw = rm.Enter + cfg.FuncExitRaw = rm.Exit + + return cfg +} + +// IsTerminal determines if this process is attached to a TTY. +func IsTerminal() bool { + // Windows is always a terminal + if runtime.GOOS == "windows" { + return true + } + + // Same implementation as readline but with our custom fds + return readline.IsTerminal(int(Stdin().Fd())) && + (readline.IsTerminal(int(Stdout().Fd())) || + readline.IsTerminal(int(Stderr().Fd()))) +} + +// TerminalWidth gets the terminal width in characters. +func TerminalWidth() int { + if runtime.GOOS == "windows" { + return readline.GetScreenWidth() + } + + return getWidth() +} + +// RawMode is a helper for entering and exiting raw mode. +type RawMode struct { + StdinFd int + + state *readline.State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = readline.MakeRaw(r.StdinFd) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + + return readline.Restore(r.StdinFd, r.state) +} + +// Package provides access to the standard OS streams +// (stdin, stdout, stderr) even if wrapped under panicwrap. +// Stdin returns the true stdin of the process. +func Stdin() *os.File { + stdin := os.Stdin + if panicwrap.Wrapped(nil) { + stdin = wrappedStdin + } + + return stdin +} + +// Stdout returns the true stdout of the process. +func Stdout() *os.File { + stdout := os.Stdout + if panicwrap.Wrapped(nil) { + stdout = wrappedStdout + } + + return stdout +} + +// Stderr returns the true stderr of the process. +func Stderr() *os.File { + stderr := os.Stderr + if panicwrap.Wrapped(nil) { + stderr = wrappedStderr + } + + return stderr +} + +// These are the wrapped standard streams. These are setup by the +// platform specific code in initPlatform. +var ( + wrappedStdin *os.File + wrappedStdout *os.File + wrappedStderr *os.File +) + +func init() { + // Initialize the platform-specific code + initPlatform() +} diff --git a/helper/wrappedreadline/wrappedreadline_unix.go b/helper/wrappedreadline/wrappedreadline_unix.go new file mode 100644 index 000000000..130f2987a --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline_unix.go @@ -0,0 +1,52 @@ +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package wrappedreadline + +import ( + "os" + "syscall" + "unsafe" +) + +// getWidth impl for Unix +func getWidth() int { + stdoutFd := int(Stdout().Fd()) + stderrFd := int(Stderr().Fd()) + + w := getWidthFd(stdoutFd) + if w < 0 { + w = getWidthFd(stderrFd) + } + + return w +} + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// get width of the terminal +func getWidthFd(stdoutFd int) int { + ws := &winsize{} + retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, + uintptr(stdoutFd), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(ws))) + + if int(retCode) == -1 { + _ = errno + return -1 + } + + return int(ws.Col) +} + +func initPlatform() { + // The standard streams are passed in via extra file descriptors. + wrappedStdin = os.NewFile(uintptr(3), "stdin") + wrappedStdout = os.NewFile(uintptr(4), "stdout") + wrappedStderr = os.NewFile(uintptr(5), "stderr") +} diff --git a/helper/wrappedreadline/wrappedreadline_windows.go b/helper/wrappedreadline/wrappedreadline_windows.go new file mode 100644 index 000000000..459479af9 --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline_windows.go @@ -0,0 +1,57 @@ +// +build windows + +package wrappedreadline + +import ( + "log" + "os" + "syscall" +) + +// getWidth impl for other +func getWidth() int { + return 0 +} + +func initPlatform() { + wrappedStdin = openConsole("CONIN$", os.Stdin) + wrappedStdout = openConsole("CONOUT$", os.Stdout) + wrappedStderr = wrappedStdout +} + +// openConsole opens a console handle, using a backup if it fails. +// This is used to get the exact console handle instead of the redirected +// handles from panicwrap. +func openConsole(name string, backup *os.File) *os.File { + // Convert to UTF16 + path, err := syscall.UTF16PtrFromString(name) + if err != nil { + log.Printf("[ERROR] wrappedstreams: %s", err) + return backup + } + + // Determine the share mode + var shareMode uint32 + switch name { + case "CONIN$": + shareMode = syscall.FILE_SHARE_READ + case "CONOUT$": + shareMode = syscall.FILE_SHARE_WRITE + } + + // Get the file + h, err := syscall.CreateFile( + path, + syscall.GENERIC_READ|syscall.GENERIC_WRITE, + shareMode, + nil, + syscall.OPEN_EXISTING, + 0, 0) + if err != nil { + log.Printf("[ERROR] wrappedstreams: %s", err) + return backup + } + + // Create the Go file + return os.NewFile(uintptr(h), name) +} From 668e92f2ca05dfd7b3e8c30f12249fcc8f545a0c Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 14:26:12 -0700 Subject: [PATCH 05/51] add docs and the option to list variables from inside the console --- command/console.go | 11 ++ website/source/docs/commands/console.html.md | 103 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 website/source/docs/commands/console.html.md diff --git a/command/console.go b/command/console.go index 17f1fc1c3..455537482 100644 --- a/command/console.go +++ b/command/console.go @@ -191,6 +191,8 @@ func (s *REPLSession) Handle(line string) (string, error) { return "", ErrSessionExit case strings.TrimSpace(line) == "help": return s.handleHelp() + case strings.TrimSpace(line) == "variables": + return s.handleVariables() default: return s.handleEval(line) } @@ -205,6 +207,15 @@ func (s *REPLSession) handleEval(line string) (string, error) { return rendered, nil } +func (s *REPLSession) handleVariables() (string, error) { + varsstring := "\n" + for k, v := range s.Core.Context().UserVariables { + varsstring += fmt.Sprintf("%s: %+v,\n", k, v) + } + + return varsstring, nil +} + func (s *REPLSession) handleHelp() (string, error) { text := ` The Packer console allows you to experiment with Packer interpolations. diff --git a/website/source/docs/commands/console.html.md b/website/source/docs/commands/console.html.md new file mode 100644 index 000000000..e2cdc653e --- /dev/null +++ b/website/source/docs/commands/console.html.md @@ -0,0 +1,103 @@ +--- +description: | + The `packer fix` command allows you to experiment with Packer variable +interpolations. +layout: docs +page_title: 'packer console - Commands' +sidebar_current: 'docs-commands-console' +--- + +# `console` Command + +The `packer console` command allows you to experiment with Packer variable +interpolations. You may access variables in the Packer config you called the +console with, or provide variables when you call console using the -var or +-var-file command line options. + +Type in the interpolation to test and hit to see the result. + +To exit the console, type "exit" and hit , or use Control-C. + +``` shell +$ packer console my_template.json +``` + +The full list of options that the console command will accept is visible in the +help output, which can be seen via `packer console -h`. + +## Options + +- `-var` - Set a variable in your packer template. This option can be used + multiple times. This is useful for setting version numbers for your build. + example: `-var "myvar=asdf"` + +- `-var-file` - Set template variables from a file. + example: `-var-file myvars.json` + +## REPL commands +- `help` - displays help text for Packer console. + +- `exit` - exits the console + +- `variables` - prints a list of all variables read into the console from the + `-var` option, `-var-files` option, and template. + +## Usage Examples + +Let's say you launch a console using a Packer template `example_template.json`: + + +``` +$ packer console example_template.json +``` + +You'll be dropped into a prompt that allows you to enter template functions and +see how they're evaluated; for example, if the variable `myvar` is defined in +your example_template's variable section: + +``` +"variables":{ + "myvar": "asdfasdf" +}, +... +``` +and you enter `{{user `myvar`}}` in the Packer console, you'll see the value of +myvar: + +``` +> {{user `myvar`}} +> asdfasdf +``` + +From there you can test more complicated interpolations: + +``` +> {{user `myvar`}}-{{timestamp}} +> asdfasdf-1559854396 +``` + +And when you're done using the console, just type "exit" or CTRL-C + +``` +> exit +$ +``` + +If you'd like to provide a variable or variable files, you'd do this: + +``` +packer console -var "myvar=fdsafdsa" -var-file myvars.json example_template.json +``` + +If you don't have specific variables or var files you want to test, and just +want to experiment with a particular template engine, you can do so by simply +calling `packer console` without a template file. + +If you'd like to just see a specific single interpolation without launching +the REPL, you can do so by echoing and piping the string into the console +command: + +``` +$ echo {{timestamp}} | packer console +1559855090 +``` \ No newline at end of file From bc5a48327d8188bd2ec6e7519bddf595bcf935ad Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 14:30:09 -0700 Subject: [PATCH 06/51] fix copypasta --- website/source/docs/commands/console.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/commands/console.html.md b/website/source/docs/commands/console.html.md index e2cdc653e..203d3709d 100644 --- a/website/source/docs/commands/console.html.md +++ b/website/source/docs/commands/console.html.md @@ -1,6 +1,6 @@ --- description: | - The `packer fix` command allows you to experiment with Packer variable + The `packer console` command allows you to experiment with Packer variable interpolations. layout: docs page_title: 'packer console - Commands' From 2b8fe72e3025af79ca2efd664ed88c6762623e8f Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 14:31:02 -0700 Subject: [PATCH 07/51] add to sidebar nav --- website/source/layouts/docs.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 9f3a84d5a..3bf714087 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -12,6 +12,9 @@ > build + > + build + > fix From 435bda05a7309fd22e051af7ff326c4e7581c6d9 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 15:02:22 -0700 Subject: [PATCH 08/51] clean up docs --- website/source/docs/commands/console.html.md | 11 +++++------ website/source/layouts/docs.erb | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/website/source/docs/commands/console.html.md b/website/source/docs/commands/console.html.md index 203d3709d..7b99d4024 100644 --- a/website/source/docs/commands/console.html.md +++ b/website/source/docs/commands/console.html.md @@ -1,7 +1,7 @@ --- description: | The `packer console` command allows you to experiment with Packer variable -interpolations. + interpolations. layout: docs page_title: 'packer console - Commands' sidebar_current: 'docs-commands-console' @@ -35,18 +35,17 @@ help output, which can be seen via `packer console -h`. example: `-var-file myvars.json` ## REPL commands -- `help` - displays help text for Packer console. +- `help` - displays help text for Packer console. -- `exit` - exits the console +- `exit` - exits the console -- `variables` - prints a list of all variables read into the console from the - `-var` option, `-var-files` option, and template. +- `variables` - prints a list of all variables read into the console from the + `-var` option, `-var-files` option, and template. ## Usage Examples Let's say you launch a console using a Packer template `example_template.json`: - ``` $ packer console example_template.json ``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 3bf714087..7aa2da186 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -13,7 +13,7 @@ build > - build + console > fix From 9ffdbc1f20f765867d1c5a0434d9c2e4a8927a5a Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 15:03:49 -0700 Subject: [PATCH 09/51] go vendors --- vendor/modules.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vendor/modules.txt b/vendor/modules.txt index 0af5d43ae..5644a8eaf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -140,6 +140,8 @@ github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1 github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1 # github.com/cheggaaa/pb v1.0.27 github.com/cheggaaa/pb +# github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e +github.com/chzyer/readline # github.com/creack/goselect v0.1.0 github.com/creack/goselect # github.com/davecgh/go-spew v1.1.1 From 6fecd7d362534b4d6c890a2723692d5dad892ba3 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 6 Jun 2019 15:04:02 -0700 Subject: [PATCH 10/51] vendors --- vendor/github.com/chzyer/readline/.gitignore | 1 + vendor/github.com/chzyer/readline/.travis.yml | 8 + .../github.com/chzyer/readline/CHANGELOG.md | 58 ++ vendor/github.com/chzyer/readline/LICENSE | 22 + vendor/github.com/chzyer/readline/README.md | 114 ++++ .../chzyer/readline/ansi_windows.go | 249 +++++++ vendor/github.com/chzyer/readline/complete.go | 285 ++++++++ .../chzyer/readline/complete_helper.go | 165 +++++ .../chzyer/readline/complete_segment.go | 82 +++ vendor/github.com/chzyer/readline/history.go | 330 +++++++++ .../github.com/chzyer/readline/operation.go | 531 +++++++++++++++ vendor/github.com/chzyer/readline/password.go | 33 + .../chzyer/readline/rawreader_windows.go | 125 ++++ vendor/github.com/chzyer/readline/readline.go | 326 +++++++++ vendor/github.com/chzyer/readline/remote.go | 475 +++++++++++++ vendor/github.com/chzyer/readline/runebuf.go | 629 ++++++++++++++++++ vendor/github.com/chzyer/readline/runes.go | 223 +++++++ vendor/github.com/chzyer/readline/search.go | 164 +++++ vendor/github.com/chzyer/readline/std.go | 197 ++++++ .../github.com/chzyer/readline/std_windows.go | 9 + vendor/github.com/chzyer/readline/term.go | 123 ++++ vendor/github.com/chzyer/readline/term_bsd.go | 29 + .../github.com/chzyer/readline/term_linux.go | 33 + .../chzyer/readline/term_solaris.go | 32 + .../github.com/chzyer/readline/term_unix.go | 24 + .../chzyer/readline/term_windows.go | 171 +++++ vendor/github.com/chzyer/readline/terminal.go | 238 +++++++ vendor/github.com/chzyer/readline/utils.go | 277 ++++++++ .../github.com/chzyer/readline/utils_unix.go | 83 +++ .../chzyer/readline/utils_windows.go | 41 ++ vendor/github.com/chzyer/readline/vim.go | 176 +++++ .../github.com/chzyer/readline/windows_api.go | 152 +++++ 32 files changed, 5405 insertions(+) create mode 100644 vendor/github.com/chzyer/readline/.gitignore create mode 100644 vendor/github.com/chzyer/readline/.travis.yml create mode 100644 vendor/github.com/chzyer/readline/CHANGELOG.md create mode 100644 vendor/github.com/chzyer/readline/LICENSE create mode 100644 vendor/github.com/chzyer/readline/README.md create mode 100644 vendor/github.com/chzyer/readline/ansi_windows.go create mode 100644 vendor/github.com/chzyer/readline/complete.go create mode 100644 vendor/github.com/chzyer/readline/complete_helper.go create mode 100644 vendor/github.com/chzyer/readline/complete_segment.go create mode 100644 vendor/github.com/chzyer/readline/history.go create mode 100644 vendor/github.com/chzyer/readline/operation.go create mode 100644 vendor/github.com/chzyer/readline/password.go create mode 100644 vendor/github.com/chzyer/readline/rawreader_windows.go create mode 100644 vendor/github.com/chzyer/readline/readline.go create mode 100644 vendor/github.com/chzyer/readline/remote.go create mode 100644 vendor/github.com/chzyer/readline/runebuf.go create mode 100644 vendor/github.com/chzyer/readline/runes.go create mode 100644 vendor/github.com/chzyer/readline/search.go create mode 100644 vendor/github.com/chzyer/readline/std.go create mode 100644 vendor/github.com/chzyer/readline/std_windows.go create mode 100644 vendor/github.com/chzyer/readline/term.go create mode 100644 vendor/github.com/chzyer/readline/term_bsd.go create mode 100644 vendor/github.com/chzyer/readline/term_linux.go create mode 100644 vendor/github.com/chzyer/readline/term_solaris.go create mode 100644 vendor/github.com/chzyer/readline/term_unix.go create mode 100644 vendor/github.com/chzyer/readline/term_windows.go create mode 100644 vendor/github.com/chzyer/readline/terminal.go create mode 100644 vendor/github.com/chzyer/readline/utils.go create mode 100644 vendor/github.com/chzyer/readline/utils_unix.go create mode 100644 vendor/github.com/chzyer/readline/utils_windows.go create mode 100644 vendor/github.com/chzyer/readline/vim.go create mode 100644 vendor/github.com/chzyer/readline/windows_api.go diff --git a/vendor/github.com/chzyer/readline/.gitignore b/vendor/github.com/chzyer/readline/.gitignore new file mode 100644 index 000000000..a3062beae --- /dev/null +++ b/vendor/github.com/chzyer/readline/.gitignore @@ -0,0 +1 @@ +.vscode/* diff --git a/vendor/github.com/chzyer/readline/.travis.yml b/vendor/github.com/chzyer/readline/.travis.yml new file mode 100644 index 000000000..9c3595543 --- /dev/null +++ b/vendor/github.com/chzyer/readline/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.x +script: + - GOOS=windows go install github.com/chzyer/readline/example/... + - GOOS=linux go install github.com/chzyer/readline/example/... + - GOOS=darwin go install github.com/chzyer/readline/example/... + - go test -race -v diff --git a/vendor/github.com/chzyer/readline/CHANGELOG.md b/vendor/github.com/chzyer/readline/CHANGELOG.md new file mode 100644 index 000000000..14ff5be13 --- /dev/null +++ b/vendor/github.com/chzyer/readline/CHANGELOG.md @@ -0,0 +1,58 @@ +# ChangeLog + +### 1.4 - 2016-07-25 + +* [#60][60] Support dynamic autocompletion +* Fix ANSI parser on Windows +* Fix wrong column width in complete mode on Windows +* Remove dependent package "golang.org/x/crypto/ssh/terminal" + +### 1.3 - 2016-05-09 + +* [#38][38] add SetChildren for prefix completer interface +* [#42][42] improve multiple lines compatibility +* [#43][43] remove sub-package(runes) for gopkg compatibility +* [#46][46] Auto complete with space prefixed line +* [#48][48] support suspend process (ctrl+Z) +* [#49][49] fix bug that check equals with previous command +* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty + +### 1.2 - 2016-03-05 + +* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib) +* [#23][23], support stdin remapping +* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM. +* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines. +* Supports performs even stdin/stdout is not a tty. +* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api. +* [#28][28], fixes the history is not working as expected. +* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)` + +### 1.1 - 2015-11-20 + +* [#12][12] Add support for key ``/``/`` +* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`. +* Bugs fixed for `PrefixCompleter` +* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience. +* Customable Interrupt/EOF prompt in `Config` +* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices +* Provides a new password user experience(`readline.ReadPasswordEx()`). + +### 1.0 - 2015-10-14 + +* Initial public release. + +[12]: https://github.com/chzyer/readline/pull/12 +[17]: https://github.com/chzyer/readline/pull/17 +[23]: https://github.com/chzyer/readline/pull/23 +[27]: https://github.com/chzyer/readline/pull/27 +[28]: https://github.com/chzyer/readline/pull/28 +[33]: https://github.com/chzyer/readline/pull/33 +[38]: https://github.com/chzyer/readline/pull/38 +[42]: https://github.com/chzyer/readline/pull/42 +[43]: https://github.com/chzyer/readline/pull/43 +[46]: https://github.com/chzyer/readline/pull/46 +[48]: https://github.com/chzyer/readline/pull/48 +[49]: https://github.com/chzyer/readline/pull/49 +[53]: https://github.com/chzyer/readline/pull/53 +[60]: https://github.com/chzyer/readline/pull/60 diff --git a/vendor/github.com/chzyer/readline/LICENSE b/vendor/github.com/chzyer/readline/LICENSE new file mode 100644 index 000000000..c9afab3dc --- /dev/null +++ b/vendor/github.com/chzyer/readline/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Chzyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/chzyer/readline/README.md b/vendor/github.com/chzyer/readline/README.md new file mode 100644 index 000000000..fab974b7f --- /dev/null +++ b/vendor/github.com/chzyer/readline/README.md @@ -0,0 +1,114 @@ +[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) +[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases) +[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline) +[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) +[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors) + +

+ + + +

+ +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` + +## Guide + +* [Demo](example/readline-demo/readline-demo.go) +* [Shortcut](doc/shortcut.md) + +## Repos using readline + +[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach) +[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto) +[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire) +[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg) +[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql) +[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman) +[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp) +[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell) +[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001) +[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p) + + +## Feedback + +If you have any questions, please submit a github issue and any pull requests is welcomed :) + +* [https://twitter.com/chzyer](https://twitter.com/chzyer) +* [http://weibo.com/2145262190](http://weibo.com/2145262190) + + +## Backers + +Love Readline? Help me keep it alive by donating funds to cover project expenses!
+[[Become a backer](https://opencollective.com/readline#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Sponsors + +Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/chzyer/readline/ansi_windows.go b/vendor/github.com/chzyer/readline/ansi_windows.go new file mode 100644 index 000000000..63b908c18 --- /dev/null +++ b/vendor/github.com/chzyer/readline/ansi_windows.go @@ -0,0 +1,249 @@ +// +build windows + +package readline + +import ( + "bufio" + "io" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" +) + +const ( + _ = uint16(0) + COLOR_FBLUE = 0x0001 + COLOR_FGREEN = 0x0002 + COLOR_FRED = 0x0004 + COLOR_FINTENSITY = 0x0008 + + COLOR_BBLUE = 0x0010 + COLOR_BGREEN = 0x0020 + COLOR_BRED = 0x0040 + COLOR_BINTENSITY = 0x0080 + + COMMON_LVB_UNDERSCORE = 0x8000 + COMMON_LVB_BOLD = 0x0007 +) + +var ColorTableFg = []word{ + 0, // 30: Black + COLOR_FRED, // 31: Red + COLOR_FGREEN, // 32: Green + COLOR_FRED | COLOR_FGREEN, // 33: Yellow + COLOR_FBLUE, // 34: Blue + COLOR_FRED | COLOR_FBLUE, // 35: Magenta + COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan + COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White +} + +var ColorTableBg = []word{ + 0, // 40: Black + COLOR_BRED, // 41: Red + COLOR_BGREEN, // 42: Green + COLOR_BRED | COLOR_BGREEN, // 43: Yellow + COLOR_BBLUE, // 44: Blue + COLOR_BRED | COLOR_BBLUE, // 45: Magenta + COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan + COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White +} + +type ANSIWriter struct { + target io.Writer + wg sync.WaitGroup + ctx *ANSIWriterCtx + sync.Mutex +} + +func NewANSIWriter(w io.Writer) *ANSIWriter { + a := &ANSIWriter{ + target: w, + ctx: NewANSIWriterCtx(w), + } + return a +} + +func (a *ANSIWriter) Close() error { + a.wg.Wait() + return nil +} + +type ANSIWriterCtx struct { + isEsc bool + isEscSeq bool + arg []string + target *bufio.Writer + wantFlush bool +} + +func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { + return &ANSIWriterCtx{ + target: bufio.NewWriter(target), + } +} + +func (a *ANSIWriterCtx) Flush() { + a.target.Flush() +} + +func (a *ANSIWriterCtx) process(r rune) bool { + if a.wantFlush { + if r == 0 || r == CharEsc { + a.wantFlush = false + a.target.Flush() + } + } + if a.isEscSeq { + a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) + return true + } + + switch r { + case CharEsc: + a.isEsc = true + case '[': + if a.isEsc { + a.arg = nil + a.isEscSeq = true + a.isEsc = false + break + } + fallthrough + default: + a.target.WriteRune(r) + a.wantFlush = true + } + return true +} + +func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { + arg := *argptr + var err error + + if r >= 'A' && r <= 'D' { + count := short(GetInt(arg, 1)) + info, err := GetConsoleScreenBufferInfo() + if err != nil { + return false + } + switch r { + case 'A': // up + info.dwCursorPosition.y -= count + case 'B': // down + info.dwCursorPosition.y += count + case 'C': // right + info.dwCursorPosition.x += count + case 'D': // left + info.dwCursorPosition.x -= count + } + SetConsoleCursorPosition(&info.dwCursorPosition) + return false + } + + switch r { + case 'J': + killLines() + case 'K': + eraseLine() + case 'm': + color := word(0) + for _, item := range arg { + var c int + c, err = strconv.Atoi(item) + if err != nil { + w.WriteString("[" + strings.Join(arg, ";") + "m") + break + } + if c >= 30 && c < 40 { + color ^= COLOR_FINTENSITY + color |= ColorTableFg[c-30] + } else if c >= 40 && c < 50 { + color ^= COLOR_BINTENSITY + color |= ColorTableBg[c-40] + } else if c == 4 { + color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7] + } else if c == 1 { + color |= COMMON_LVB_BOLD | COLOR_FINTENSITY + } else { // unknown code treat as reset + color = ColorTableFg[7] + } + } + if err != nil { + break + } + kernel.SetConsoleTextAttribute(stdout, uintptr(color)) + case '\007': // set title + case ';': + if len(arg) == 0 || arg[len(arg)-1] != "" { + arg = append(arg, "") + *argptr = arg + } + return true + default: + if len(arg) == 0 { + arg = append(arg, "") + } + arg[len(arg)-1] += string(r) + *argptr = arg + return true + } + *argptr = nil + return false +} + +func (a *ANSIWriter) Write(b []byte) (int, error) { + a.Lock() + defer a.Unlock() + + off := 0 + for len(b) > off { + r, size := utf8.DecodeRune(b[off:]) + if size == 0 { + return off, io.ErrShortWrite + } + off += size + a.ctx.process(r) + } + a.ctx.Flush() + return off, nil +} + +func killLines() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x + size += sbi.dwCursorPosition.x + + var written int + kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} + +func eraseLine() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := sbi.dwSize.x + sbi.dwCursorPosition.x = 0 + var written int + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} diff --git a/vendor/github.com/chzyer/readline/complete.go b/vendor/github.com/chzyer/readline/complete.go new file mode 100644 index 000000000..c08c99414 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete.go @@ -0,0 +1,285 @@ +package readline + +import ( + "bufio" + "bytes" + "fmt" + "io" +) + +type AutoCompleter interface { + // Readline will pass the whole line and current offset to it + // Completer need to pass all the candidates, and how long they shared the same characters in line + // Example: + // [go, git, git-shell, grep] + // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 + // Do("gi", 2) => ["t", "t-shell"], 2 + // Do("git", 3) => ["", "-shell"], 3 + Do(line []rune, pos int) (newLine [][]rune, length int) +} + +type TabCompleter struct{} + +func (t *TabCompleter) Do([]rune, int) ([][]rune, int) { + return [][]rune{[]rune("\t")}, 0 +} + +type opCompleter struct { + w io.Writer + op *Operation + width int + + inCompleteMode bool + inSelectMode bool + candidate [][]rune + candidateSource []rune + candidateOff int + candidateChoise int + candidateColNum int +} + +func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { + return &opCompleter{ + w: w, + op: op, + width: width, + } +} + +func (o *opCompleter) doSelect() { + if len(o.candidate) == 1 { + o.op.buf.WriteRunes(o.candidate[0]) + o.ExitCompleteMode(false) + return + } + o.nextCandidate(1) + o.CompleteRefresh() +} + +func (o *opCompleter) nextCandidate(i int) { + o.candidateChoise += i + o.candidateChoise = o.candidateChoise % len(o.candidate) + if o.candidateChoise < 0 { + o.candidateChoise = len(o.candidate) + o.candidateChoise + } +} + +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } + if o.IsInCompleteSelectMode() { + o.doSelect() + return true + } + + buf := o.op.buf + rs := buf.Runes() + + if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { + o.EnterCompleteSelectMode() + o.doSelect() + return true + } + + o.ExitCompleteSelectMode() + o.candidateSource = rs + newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) + if len(newLines) == 0 { + o.ExitCompleteMode(false) + return true + } + + // only Aggregate candidates in non-complete mode + if !o.IsInCompleteMode() { + if len(newLines) == 1 { + buf.WriteRunes(newLines[0]) + o.ExitCompleteMode(false) + return true + } + + same, size := runes.Aggregate(newLines) + if size > 0 { + buf.WriteRunes(same) + o.ExitCompleteMode(false) + return true + } + } + + o.EnterCompleteMode(offset, newLines) + return true +} + +func (o *opCompleter) IsInCompleteSelectMode() bool { + return o.inSelectMode +} + +func (o *opCompleter) IsInCompleteMode() bool { + return o.inCompleteMode +} + +func (o *opCompleter) HandleCompleteSelect(r rune) bool { + next := true + switch r { + case CharEnter, CharCtrlJ: + next = false + o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise]) + o.ExitCompleteMode(false) + case CharLineStart: + num := o.candidateChoise % o.candidateColNum + o.nextCandidate(-num) + case CharLineEnd: + num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1 + o.candidateChoise += num + if o.candidateChoise >= len(o.candidate) { + o.candidateChoise = len(o.candidate) - 1 + } + case CharBackspace: + o.ExitCompleteSelectMode() + next = false + case CharTab, CharForward: + o.doSelect() + case CharBell, CharInterrupt: + o.ExitCompleteMode(true) + next = false + case CharNext: + tmpChoise := o.candidateChoise + o.candidateColNum + if tmpChoise >= o.getMatrixSize() { + tmpChoise -= o.getMatrixSize() + } else if tmpChoise >= len(o.candidate) { + tmpChoise += o.candidateColNum + tmpChoise -= o.getMatrixSize() + } + o.candidateChoise = tmpChoise + case CharBackward: + o.nextCandidate(-1) + case CharPrev: + tmpChoise := o.candidateChoise - o.candidateColNum + if tmpChoise < 0 { + tmpChoise += o.getMatrixSize() + if tmpChoise >= len(o.candidate) { + tmpChoise -= o.candidateColNum + } + } + o.candidateChoise = tmpChoise + default: + next = false + o.ExitCompleteSelectMode() + } + if next { + o.CompleteRefresh() + return true + } + return false +} + +func (o *opCompleter) getMatrixSize() int { + line := len(o.candidate) / o.candidateColNum + if len(o.candidate)%o.candidateColNum != 0 { + line++ + } + return line * o.candidateColNum +} + +func (o *opCompleter) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opCompleter) CompleteRefresh() { + if !o.inCompleteMode { + return + } + lineCnt := o.op.buf.CursorLineCount() + colWidth := 0 + for _, c := range o.candidate { + w := runes.WidthAll(c) + if w > colWidth { + colWidth = w + } + } + colWidth += o.candidateOff + 1 + same := o.op.buf.RuneSlice(-o.candidateOff) + + // -1 to avoid reach the end of line + width := o.width - 1 + colNum := width / colWidth + if colNum != 0 { + colWidth += (width - (colWidth * colNum)) / colNum + } + + o.candidateColNum = colNum + buf := bufio.NewWriter(o.w) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + + colIdx := 0 + lines := 1 + buf.WriteString("\033[J") + for idx, c := range o.candidate { + inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() + if inSelect { + buf.WriteString("\033[30;47m") + } + buf.WriteString(string(same)) + buf.WriteString(string(c)) + buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same))) + + if inSelect { + buf.WriteString("\033[0m") + } + + colIdx++ + if colIdx == colNum { + buf.WriteString("\n") + lines++ + colIdx = 0 + } + } + + // move back + fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) + fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) + buf.Flush() +} + +func (o *opCompleter) aggCandidate(candidate [][]rune) int { + offset := 0 + for i := 0; i < len(candidate[0]); i++ { + for j := 0; j < len(candidate)-1; j++ { + if i > len(candidate[j]) { + goto aggregate + } + if candidate[j][i] != candidate[j+1][i] { + goto aggregate + } + } + offset = i + } +aggregate: + return offset +} + +func (o *opCompleter) EnterCompleteSelectMode() { + o.inSelectMode = true + o.candidateChoise = -1 + o.CompleteRefresh() +} + +func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) { + o.inCompleteMode = true + o.candidate = candidate + o.candidateOff = offset + o.CompleteRefresh() +} + +func (o *opCompleter) ExitCompleteSelectMode() { + o.inSelectMode = false + o.candidate = nil + o.candidateChoise = -1 + o.candidateOff = -1 + o.candidateSource = nil +} + +func (o *opCompleter) ExitCompleteMode(revent bool) { + o.inCompleteMode = false + o.ExitCompleteSelectMode() +} diff --git a/vendor/github.com/chzyer/readline/complete_helper.go b/vendor/github.com/chzyer/readline/complete_helper.go new file mode 100644 index 000000000..58d724872 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_helper.go @@ -0,0 +1,165 @@ +package readline + +import ( + "bytes" + "strings" +) + +// Caller type for dynamic completion +type DynamicCompleteFunc func(string) []string + +type PrefixCompleterInterface interface { + Print(prefix string, level int, buf *bytes.Buffer) + Do(line []rune, pos int) (newLine [][]rune, length int) + GetName() []rune + GetChildren() []PrefixCompleterInterface + SetChildren(children []PrefixCompleterInterface) +} + +type DynamicPrefixCompleterInterface interface { + PrefixCompleterInterface + IsDynamic() bool + GetDynamicNames(line []rune) [][]rune +} + +type PrefixCompleter struct { + Name []rune + Dynamic bool + Callback DynamicCompleteFunc + Children []PrefixCompleterInterface +} + +func (p *PrefixCompleter) Tree(prefix string) string { + buf := bytes.NewBuffer(nil) + p.Print(prefix, 0, buf) + return buf.String() +} + +func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.GetName())) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.GetName()) + "\n") + level++ + } + for _, ch := range p.GetChildren() { + ch.Print(prefix, level, buf) + } +} + +func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { + Print(p, prefix, level, buf) +} + +func (p *PrefixCompleter) IsDynamic() bool { + return p.Dynamic +} + +func (p *PrefixCompleter) GetName() []rune { + return p.Name +} + +func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { + var names = [][]rune{} + for _, name := range p.Callback(string(line)) { + names = append(names, []rune(name+" ")) + } + return names +} + +func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { + return p.Children +} + +func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) { + p.Children = children +} + +func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter { + return PcItem("", pc...) +} + +func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { + name += " " + return &PrefixCompleter{ + Name: []rune(name), + Dynamic: false, + Children: pc, + } +} + +func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { + return &PrefixCompleter{ + Callback: callback, + Dynamic: true, + Children: pc, + } +} + +func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { + line = runes.TrimSpaceLeft(line[:pos]) + goNext := false + var lineCompleter PrefixCompleterInterface + for _, child := range p.GetChildren() { + childNames := make([][]rune, 1) + + childDynamic, ok := child.(DynamicPrefixCompleterInterface) + if ok && childDynamic.IsDynamic() { + childNames = childDynamic.GetDynamicNames(origLine) + } else { + childNames[0] = child.GetName() + } + + for _, childName := range childNames { + if len(line) >= len(childName) { + if runes.HasPrefix(line, childName) { + if len(line) == len(childName) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, childName) + } + offset = len(childName) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[len(line):]) + offset = len(line) + lineCompleter = child + } + } + } + } + + if len(newLine) != 1 { + return + } + + tmpLine := make([]rune, 0, len(line)) + for i := offset; i < len(line); i++ { + if line[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) + } + + if goNext { + return doInternal(lineCompleter, nil, 0, origLine) + } + return +} diff --git a/vendor/github.com/chzyer/readline/complete_segment.go b/vendor/github.com/chzyer/readline/complete_segment.go new file mode 100644 index 000000000..5ceadd80f --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_segment.go @@ -0,0 +1,82 @@ +package readline + +type SegmentCompleter interface { + // a + // |- a1 + // |--- a11 + // |- a2 + // b + // input: + // DoTree([], 0) [a, b] + // DoTree([a], 1) [a] + // DoTree([a, ], 0) [a1, a2] + // DoTree([a, a], 1) [a1, a2] + // DoTree([a, a1], 2) [a1] + // DoTree([a, a1, ], 0) [a11] + // DoTree([a, a1, a], 1) [a11] + DoSegment([][]rune, int) [][]rune +} + +type dumpSegmentCompleter struct { + f func([][]rune, int) [][]rune +} + +func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { + return d.f(segment, n) +} + +func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { + return &SegmentComplete{&dumpSegmentCompleter{f}} +} + +func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete { + return &SegmentComplete{ + SegmentCompleter: completer, + } +} + +type SegmentComplete struct { + SegmentCompleter +} + +func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { + ret := make([][]rune, 0, len(cands)) + lastSegment := segments[len(segments)-1] + for _, cand := range cands { + if !runes.HasPrefix(cand, lastSegment) { + continue + } + ret = append(ret, cand[len(lastSegment):]) + } + return ret, idx +} + +func SplitSegment(line []rune, pos int) ([][]rune, int) { + segs := [][]rune{} + lastIdx := -1 + line = line[:pos] + pos = 0 + for idx, l := range line { + if l == ' ' { + pos = 0 + segs = append(segs, line[lastIdx+1:idx]) + lastIdx = idx + } else { + pos++ + } + } + segs = append(segs, line[lastIdx+1:]) + return segs, pos +} + +func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { + + segment, idx := SplitSegment(line, pos) + + cands := c.DoSegment(segment, idx) + newLine, offset = RetSegment(segment, cands, idx) + for idx := range newLine { + newLine[idx] = append(newLine[idx], ' ') + } + return newLine, offset +} diff --git a/vendor/github.com/chzyer/readline/history.go b/vendor/github.com/chzyer/readline/history.go new file mode 100644 index 000000000..6b17c464b --- /dev/null +++ b/vendor/github.com/chzyer/readline/history.go @@ -0,0 +1,330 @@ +package readline + +import ( + "bufio" + "container/list" + "fmt" + "os" + "strings" + "sync" +) + +type hisItem struct { + Source []rune + Version int64 + Tmp []rune +} + +func (h *hisItem) Clean() { + h.Source = nil + h.Tmp = nil +} + +type opHistory struct { + cfg *Config + history *list.List + historyVer int64 + current *list.Element + fd *os.File + fdLock sync.Mutex + enable bool +} + +func newOpHistory(cfg *Config) (o *opHistory) { + o = &opHistory{ + cfg: cfg, + history: list.New(), + enable: true, + } + return o +} + +func (o *opHistory) Reset() { + o.history = list.New() + o.current = nil +} + +func (o *opHistory) IsHistoryClosed() bool { + o.fdLock.Lock() + defer o.fdLock.Unlock() + return o.fd.Fd() == ^(uintptr(0)) +} + +func (o *opHistory) Init() { + if o.IsHistoryClosed() { + o.initHistory() + } +} + +func (o *opHistory) initHistory() { + if o.cfg.HistoryFile != "" { + o.historyUpdatePath(o.cfg.HistoryFile) + } +} + +// only called by newOpHistory +func (o *opHistory) historyUpdatePath(path string) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return + } + o.fd = f + r := bufio.NewReader(o.fd) + total := 0 + for ; ; total++ { + line, err := r.ReadString('\n') + if err != nil { + break + } + // ignore the empty line + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + o.Push([]rune(line)) + o.Compact() + } + if total > o.cfg.HistoryLimit { + o.rewriteLocked() + } + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Compact() { + for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { + o.history.Remove(o.history.Front()) + } +} + +func (o *opHistory) Rewrite() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + o.rewriteLocked() +} + +func (o *opHistory) rewriteLocked() { + if o.cfg.HistoryFile == "" { + return + } + + tmpFile := o.cfg.HistoryFile + ".tmp" + fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) + if err != nil { + return + } + + buf := bufio.NewWriter(fd) + for elem := o.history.Front(); elem != nil; elem = elem.Next() { + buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") + } + buf.Flush() + + // replace history file + if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { + fd.Close() + return + } + + if o.fd != nil { + o.fd.Close() + } + // fd is write only, just satisfy what we need. + o.fd = fd +} + +func (o *opHistory) Close() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + if o.fd != nil { + o.fd.Close() + } +} + +func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Prev() { + item := o.showItem(elem.Value) + if isNewSearch { + start += len(rs) + } + if elem == o.current { + if len(item) >= start { + item = item[:start] + } + } + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Next() { + item := o.showItem(elem.Value) + if isNewSearch { + start -= len(rs) + if start < 0 { + start = 0 + } + } + if elem == o.current { + if len(item)-1 >= start { + item = item[start:] + } else { + continue + } + } + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + if elem == o.current { + idx += start + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) showItem(obj interface{}) []rune { + item := obj.(*hisItem) + if item.Version == o.historyVer { + return item.Tmp + } + return item.Source +} + +func (o *opHistory) Prev() []rune { + if o.current == nil { + return nil + } + current := o.current.Prev() + if current == nil { + return nil + } + o.current = current + return runes.Copy(o.showItem(current.Value)) +} + +func (o *opHistory) Next() ([]rune, bool) { + if o.current == nil { + return nil, false + } + current := o.current.Next() + if current == nil { + return nil, false + } + + o.current = current + return runes.Copy(o.showItem(current.Value)), true +} + +// Disable the current history +func (o *opHistory) Disable() { + o.enable = false +} + +// Enable the current history +func (o *opHistory) Enable() { + o.enable = true +} + +func (o *opHistory) debug() { + Debug("-------") + for item := o.history.Front(); item != nil; item = item.Next() { + Debug(fmt.Sprintf("%+v", item.Value)) + } +} + +// save history +func (o *opHistory) New(current []rune) (err error) { + + // history deactivated + if !o.enable { + return nil + } + + current = runes.Copy(current) + + // if just use last command without modify + // just clean lastest history + if back := o.history.Back(); back != nil { + prev := back.Prev() + if prev != nil { + if runes.Equal(current, prev.Value.(*hisItem).Source) { + o.current = o.history.Back() + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + } + + if len(current) == 0 { + o.current = o.history.Back() + if o.current != nil { + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + + if o.current != o.history.Back() { + // move history item to current command + currentItem := o.current.Value.(*hisItem) + // set current to last item + o.current = o.history.Back() + + current = runes.Copy(currentItem.Tmp) + } + + // err only can be a IO error, just report + err = o.Update(current, true) + + // push a new one to commit current command + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Revert() { + o.historyVer++ + o.current = o.history.Back() +} + +func (o *opHistory) Update(s []rune, commit bool) (err error) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + s = runes.Copy(s) + if o.current == nil { + o.Push(s) + o.Compact() + return + } + r := o.current.Value.(*hisItem) + r.Version = o.historyVer + if commit { + r.Source = s + if o.fd != nil { + // just report the error + _, err = o.fd.Write([]byte(string(r.Source) + "\n")) + } + } else { + r.Tmp = append(r.Tmp[:0], s...) + } + o.current.Value = r + o.Compact() + return +} + +func (o *opHistory) Push(s []rune) { + s = runes.Copy(s) + elem := o.history.PushBack(&hisItem{Source: s}) + o.current = elem +} diff --git a/vendor/github.com/chzyer/readline/operation.go b/vendor/github.com/chzyer/readline/operation.go new file mode 100644 index 000000000..4c31624f8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/operation.go @@ -0,0 +1,531 @@ +package readline + +import ( + "errors" + "io" + "sync" +) + +var ( + ErrInterrupt = errors.New("Interrupt") +) + +type InterruptError struct { + Line []rune +} + +func (*InterruptError) Error() string { + return "Interrupted" +} + +type Operation struct { + m sync.Mutex + cfg *Config + t *Terminal + buf *RuneBuffer + outchan chan []rune + errchan chan error + w io.Writer + + history *opHistory + *opSearch + *opCompleter + *opPassword + *opVim +} + +func (o *Operation) SetBuffer(what string) { + o.buf.Set([]rune(what)) +} + +type wrapWriter struct { + r *Operation + t *Terminal + target io.Writer +} + +func (w *wrapWriter) Write(b []byte) (int, error) { + if !w.t.IsReading() { + return w.target.Write(b) + } + + var ( + n int + err error + ) + w.r.buf.Refresh(func() { + n, err = w.target.Write(b) + }) + + if w.r.IsSearchMode() { + w.r.SearchRefresh(-1) + } + if w.r.IsInCompleteMode() { + w.r.CompleteRefresh() + } + return n, err +} + +func NewOperation(t *Terminal, cfg *Config) *Operation { + width := cfg.FuncGetWidth() + op := &Operation{ + t: t, + buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), + outchan: make(chan []rune), + errchan: make(chan error, 1), + } + op.w = op.buf.w + op.SetConfig(cfg) + op.opVim = newVimMode(op) + op.opCompleter = newOpCompleter(op.buf.w, op, width) + op.opPassword = newOpPassword(op) + op.cfg.FuncOnWidthChanged(func() { + newWidth := cfg.FuncGetWidth() + op.opCompleter.OnWidthChange(newWidth) + op.opSearch.OnWidthChange(newWidth) + op.buf.OnWidthChange(newWidth) + }) + go op.ioloop() + return op +} + +func (o *Operation) SetPrompt(s string) { + o.buf.SetPrompt(s) +} + +func (o *Operation) SetMaskRune(r rune) { + o.buf.SetMask(r) +} + +func (o *Operation) GetConfig() *Config { + o.m.Lock() + cfg := *o.cfg + o.m.Unlock() + return &cfg +} + +func (o *Operation) ioloop() { + for { + keepInSearchMode := false + keepInCompleteMode := false + r := o.t.ReadRune() + if o.GetConfig().FuncFilterInputRune != nil { + var process bool + r, process = o.GetConfig().FuncFilterInputRune(r) + if !process { + o.buf.Refresh(nil) // to refresh the line + continue // ignore this rune + } + } + + if r == 0 { // io.EOF + if o.buf.Len() == 0 { + o.buf.Clean() + select { + case o.errchan <- io.EOF: + } + break + } else { + // if stdin got io.EOF and there is something left in buffer, + // let's flush them by sending CharEnter. + // And we will got io.EOF int next loop. + r = CharEnter + } + } + isUpdateHistory := true + + if o.IsInCompleteSelectMode() { + keepInCompleteMode = o.HandleCompleteSelect(r) + if keepInCompleteMode { + continue + } + + o.buf.Refresh(nil) + switch r { + case CharEnter, CharCtrlJ: + o.history.Update(o.buf.Runes(), false) + fallthrough + case CharInterrupt: + o.t.KickRead() + fallthrough + case CharBell: + continue + } + } + + if o.IsEnableVimMode() { + r = o.HandleVim(r, o.t.ReadRune) + if r == 0 { + continue + } + } + + switch r { + case CharBell: + if o.IsSearchMode() { + o.ExitSearchMode(true) + o.buf.Refresh(nil) + } + if o.IsInCompleteMode() { + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + } + case CharTab: + if o.GetConfig().AutoComplete == nil { + o.t.Bell() + break + } + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + + case CharBckSearch: + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharCtrlU: + o.buf.KillFront() + case CharFwdSearch: + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharKill: + o.buf.Kill() + keepInCompleteMode = true + case MetaForward: + o.buf.MoveToNextWord() + case CharTranspose: + o.buf.Transpose() + case MetaBackward: + o.buf.MoveToPrevWord() + case MetaDelete: + o.buf.DeleteWord() + case CharLineStart: + o.buf.MoveToLineStart() + case CharLineEnd: + o.buf.MoveToLineEnd() + case CharBackspace, CharCtrlH: + if o.IsSearchMode() { + o.SearchBackspace() + keepInSearchMode = true + break + } + + if o.buf.Len() == 0 { + o.t.Bell() + break + } + o.buf.Backspace() + if o.IsInCompleteMode() { + o.OnComplete() + } + case CharCtrlZ: + o.buf.Clean() + o.t.SleepToResume() + o.Refresh() + case CharCtrlL: + ClearScreen(o.w) + o.Refresh() + case MetaBackspace, CharCtrlW: + o.buf.BackEscapeWord() + case CharCtrlY: + o.buf.Yank() + case CharEnter, CharCtrlJ: + if o.IsSearchMode() { + o.ExitSearchMode(false) + } + o.buf.MoveToLineEnd() + var data []rune + if !o.GetConfig().UniqueEditLine { + o.buf.WriteRune('\n') + data = o.buf.Reset() + data = data[:len(data)-1] // trim \n + } else { + o.buf.Clean() + data = o.buf.Reset() + } + o.outchan <- data + if !o.GetConfig().DisableAutoSaveHistory { + // ignore IO error + _ = o.history.New(data) + } else { + isUpdateHistory = false + } + case CharBackward: + o.buf.MoveBackward() + case CharForward: + o.buf.MoveForward() + case CharPrev: + buf := o.history.Prev() + if buf != nil { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharNext: + buf, ok := o.history.Next() + if ok { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharDelete: + if o.buf.Len() > 0 || !o.IsNormalMode() { + o.t.KickRead() + if !o.buf.Delete() { + o.t.Bell() + } + break + } + + // treat as EOF + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(o.GetConfig().EOFPrompt + "\n") + } + o.buf.Reset() + isUpdateHistory = false + o.history.Revert() + o.errchan <- io.EOF + if o.GetConfig().UniqueEditLine { + o.buf.Clean() + } + case CharInterrupt: + if o.IsSearchMode() { + o.t.KickRead() + o.ExitSearchMode(true) + break + } + if o.IsInCompleteMode() { + o.t.KickRead() + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + break + } + o.buf.MoveToLineEnd() + o.buf.Refresh(nil) + hint := o.GetConfig().InterruptPrompt + "\n" + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(hint) + } + remain := o.buf.Reset() + if !o.GetConfig().UniqueEditLine { + remain = remain[:len(remain)-len([]rune(hint))] + } + isUpdateHistory = false + o.history.Revert() + o.errchan <- &InterruptError{remain} + default: + if o.IsSearchMode() { + o.SearchChar(r) + keepInSearchMode = true + break + } + o.buf.WriteRune(r) + if o.IsInCompleteMode() { + o.OnComplete() + keepInCompleteMode = true + } + } + + listener := o.GetConfig().Listener + if listener != nil { + newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r) + if ok { + o.buf.SetWithIdx(newPos, newLine) + } + } + + o.m.Lock() + if !keepInSearchMode && o.IsSearchMode() { + o.ExitSearchMode(false) + o.buf.Refresh(nil) + } else if o.IsInCompleteMode() { + if !keepInCompleteMode { + o.ExitCompleteMode(false) + o.Refresh() + } else { + o.buf.Refresh(nil) + o.CompleteRefresh() + } + } + if isUpdateHistory && !o.IsSearchMode() { + // it will cause null history + o.history.Update(o.buf.Runes(), false) + } + o.m.Unlock() + } +} + +func (o *Operation) Stderr() io.Writer { + return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t} +} + +func (o *Operation) Stdout() io.Writer { + return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} +} + +func (o *Operation) String() (string, error) { + r, err := o.Runes() + return string(r), err +} + +func (o *Operation) Runes() ([]rune, error) { + o.t.EnterRawMode() + defer o.t.ExitRawMode() + + listener := o.GetConfig().Listener + if listener != nil { + listener.OnChange(nil, 0, 0) + } + + o.buf.Refresh(nil) // print prompt + o.t.KickRead() + select { + case r := <-o.outchan: + return r, nil + case err := <-o.errchan: + if e, ok := err.(*InterruptError); ok { + return e.Line, ErrInterrupt + } + return nil, err + } +} + +func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { + cfg := o.GenPasswordConfig() + cfg.Prompt = prompt + cfg.Listener = l + return o.PasswordWithConfig(cfg) +} + +func (o *Operation) GenPasswordConfig() *Config { + return o.opPassword.PasswordConfig() +} + +func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { + if err := o.opPassword.EnterPasswordMode(cfg); err != nil { + return nil, err + } + defer o.opPassword.ExitPasswordMode() + return o.Slice() +} + +func (o *Operation) Password(prompt string) ([]byte, error) { + return o.PasswordEx(prompt, nil) +} + +func (o *Operation) SetTitle(t string) { + o.w.Write([]byte("\033[2;" + t + "\007")) +} + +func (o *Operation) Slice() ([]byte, error) { + r, err := o.Runes() + if err != nil { + return nil, err + } + return []byte(string(r)), nil +} + +func (o *Operation) Close() { + o.history.Close() +} + +func (o *Operation) SetHistoryPath(path string) { + if o.history != nil { + o.history.Close() + } + o.cfg.HistoryFile = path + o.history = newOpHistory(o.cfg) +} + +func (o *Operation) IsNormalMode() bool { + return !o.IsInCompleteMode() && !o.IsSearchMode() +} + +func (op *Operation) SetConfig(cfg *Config) (*Config, error) { + op.m.Lock() + defer op.m.Unlock() + if op.cfg == cfg { + return op.cfg, nil + } + if err := cfg.Init(); err != nil { + return op.cfg, err + } + old := op.cfg + op.cfg = cfg + op.SetPrompt(cfg.Prompt) + op.SetMaskRune(cfg.MaskRune) + op.buf.SetConfig(cfg) + width := op.cfg.FuncGetWidth() + + if cfg.opHistory == nil { + op.SetHistoryPath(cfg.HistoryFile) + cfg.opHistory = op.history + cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) + } + op.history = cfg.opHistory + + // SetHistoryPath will close opHistory which already exists + // so if we use it next time, we need to reopen it by `InitHistory()` + op.history.Init() + + if op.cfg.AutoComplete != nil { + op.opCompleter = newOpCompleter(op.buf.w, op, width) + } + + op.opSearch = cfg.opSearch + return old, nil +} + +func (o *Operation) ResetHistory() { + o.history.Reset() +} + +// if err is not nil, it just mean it fail to write to file +// other things goes fine. +func (o *Operation) SaveHistory(content string) error { + return o.history.New([]rune(content)) +} + +func (o *Operation) Refresh() { + if o.t.IsReading() { + o.buf.Refresh(nil) + } +} + +func (o *Operation) Clean() { + o.buf.Clean() +} + +func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { + return &DumpListener{f: f} +} + +type DumpListener struct { + f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + return d.f(line, pos, key) +} + +type Listener interface { + OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +type Painter interface { + Paint(line []rune, pos int) []rune +} + +type defaultPainter struct{} + +func (p *defaultPainter) Paint(line []rune, _ int) []rune { + return line +} diff --git a/vendor/github.com/chzyer/readline/password.go b/vendor/github.com/chzyer/readline/password.go new file mode 100644 index 000000000..414288c2a --- /dev/null +++ b/vendor/github.com/chzyer/readline/password.go @@ -0,0 +1,33 @@ +package readline + +type opPassword struct { + o *Operation + backupCfg *Config +} + +func newOpPassword(o *Operation) *opPassword { + return &opPassword{o: o} +} + +func (o *opPassword) ExitPasswordMode() { + o.o.SetConfig(o.backupCfg) + o.backupCfg = nil +} + +func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) { + o.backupCfg, err = o.o.SetConfig(cfg) + return +} + +func (o *opPassword) PasswordConfig() *Config { + return &Config{ + EnableMask: true, + InterruptPrompt: "\n", + EOFPrompt: "\n", + HistoryLimit: -1, + Painter: &defaultPainter{}, + + Stdout: o.o.cfg.Stdout, + Stderr: o.o.cfg.Stderr, + } +} diff --git a/vendor/github.com/chzyer/readline/rawreader_windows.go b/vendor/github.com/chzyer/readline/rawreader_windows.go new file mode 100644 index 000000000..073ef150a --- /dev/null +++ b/vendor/github.com/chzyer/readline/rawreader_windows.go @@ -0,0 +1,125 @@ +// +build windows + +package readline + +import "unsafe" + +const ( + VK_CANCEL = 0x03 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_ESCAPE = 0x1B + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_DELETE = 0x2E + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 +) + +// RawReader translate input record to ANSI escape sequence. +// To provides same behavior as unix terminal. +type RawReader struct { + ctrlKey bool + altKey bool +} + +func NewRawReader() *RawReader { + r := new(RawReader) + return r +} + +// only process one action in one read +func (r *RawReader) Read(buf []byte) (int, error) { + ir := new(_INPUT_RECORD) + var read int + var err error +next: + err = kernel.ReadConsoleInputW(stdin, + uintptr(unsafe.Pointer(ir)), + 1, + uintptr(unsafe.Pointer(&read)), + ) + if err != nil { + return 0, err + } + if ir.EventType != EVENT_KEY { + goto next + } + ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0])) + if ker.bKeyDown == 0 { // keyup + if r.ctrlKey || r.altKey { + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = false + case VK_MENU: //alt + r.altKey = false + } + } + goto next + } + + if ker.unicodeChar == 0 { + var target rune + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = true + case VK_MENU: //alt + r.altKey = true + case VK_LEFT: + target = CharBackward + case VK_RIGHT: + target = CharForward + case VK_UP: + target = CharPrev + case VK_DOWN: + target = CharNext + } + if target != 0 { + return r.write(buf, target) + } + goto next + } + char := rune(ker.unicodeChar) + if r.ctrlKey { + switch char { + case 'A': + char = CharLineStart + case 'E': + char = CharLineEnd + case 'R': + char = CharBckSearch + case 'S': + char = CharFwdSearch + } + } else if r.altKey { + switch char { + case VK_BACK: + char = CharBackspace + } + return r.writeEsc(buf, char) + } + return r.write(buf, char) +} + +func (r *RawReader) writeEsc(b []byte, char rune) (int, error) { + b[0] = '\033' + n := copy(b[1:], []byte(string(char))) + return n + 1, nil +} + +func (r *RawReader) write(b []byte, char rune) (int, error) { + n := copy(b, []byte(string(char))) + return n, nil +} + +func (r *RawReader) Close() error { + return nil +} diff --git a/vendor/github.com/chzyer/readline/readline.go b/vendor/github.com/chzyer/readline/readline.go new file mode 100644 index 000000000..0e7aca06d --- /dev/null +++ b/vendor/github.com/chzyer/readline/readline.go @@ -0,0 +1,326 @@ +// Readline is a pure go implementation for GNU-Readline kind library. +// +// example: +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() +// +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// println(line) +// } +// +package readline + +import "io" + +type Instance struct { + Config *Config + Terminal *Terminal + Operation *Operation +} + +type Config struct { + // prompt supports ANSI escape sequence, so we can color some characters even in windows + Prompt string + + // readline will persist historys to file where HistoryFile specified + HistoryFile string + // specify the max length of historys, it's 500 by default, set it to -1 to disable history + HistoryLimit int + DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool + + // AutoCompleter will called once user press TAB + AutoComplete AutoCompleter + + // Any key press will pass to Listener + // NOTE: Listener will be triggered by (nil, 0, 0) immediately + Listener Listener + + Painter Painter + + // If VimMode is true, readline will in vim.insert mode by default + VimMode bool + + InterruptPrompt string + EOFPrompt string + + FuncGetWidth func() int + + Stdin io.ReadCloser + StdinWriter io.Writer + Stdout io.Writer + Stderr io.Writer + + EnableMask bool + MaskRune rune + + // erase the editing line after user submited it + // it use in IM usually. + UniqueEditLine bool + + // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) + // -> output = new (translated) rune and true/false if continue with processing this one + FuncFilterInputRune func(rune) (rune, bool) + + // force use interactive even stdout is not a tty + FuncIsTerminal func() bool + FuncMakeRaw func() error + FuncExitRaw func() error + FuncOnWidthChanged func(func()) + ForceUseInteractive bool + + // private fields + inited bool + opHistory *opHistory + opSearch *opSearch +} + +func (c *Config) useInteractive() bool { + if c.ForceUseInteractive { + return true + } + return c.FuncIsTerminal() +} + +func (c *Config) Init() error { + if c.inited { + return nil + } + c.inited = true + if c.Stdin == nil { + c.Stdin = NewCancelableStdin(Stdin) + } + + c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) + + if c.Stdout == nil { + c.Stdout = Stdout + } + if c.Stderr == nil { + c.Stderr = Stderr + } + if c.HistoryLimit == 0 { + c.HistoryLimit = 500 + } + + if c.InterruptPrompt == "" { + c.InterruptPrompt = "^C" + } else if c.InterruptPrompt == "\n" { + c.InterruptPrompt = "" + } + if c.EOFPrompt == "" { + c.EOFPrompt = "^D" + } else if c.EOFPrompt == "\n" { + c.EOFPrompt = "" + } + + if c.AutoComplete == nil { + c.AutoComplete = &TabCompleter{} + } + if c.FuncGetWidth == nil { + c.FuncGetWidth = GetScreenWidth + } + if c.FuncIsTerminal == nil { + c.FuncIsTerminal = DefaultIsTerminal + } + rm := new(RawMode) + if c.FuncMakeRaw == nil { + c.FuncMakeRaw = rm.Enter + } + if c.FuncExitRaw == nil { + c.FuncExitRaw = rm.Exit + } + if c.FuncOnWidthChanged == nil { + c.FuncOnWidthChanged = DefaultOnWidthChanged + } + + return nil +} + +func (c Config) Clone() *Config { + c.opHistory = nil + c.opSearch = nil + return &c +} + +func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { + c.Listener = FuncListener(f) +} + +func (c *Config) SetPainter(p Painter) { + c.Painter = p +} + +func NewEx(cfg *Config) (*Instance, error) { + t, err := NewTerminal(cfg) + if err != nil { + return nil, err + } + rl := t.Readline() + if cfg.Painter == nil { + cfg.Painter = &defaultPainter{} + } + return &Instance{ + Config: cfg, + Terminal: t, + Operation: rl, + }, nil +} + +func New(prompt string) (*Instance, error) { + return NewEx(&Config{Prompt: prompt}) +} + +func (i *Instance) ResetHistory() { + i.Operation.ResetHistory() +} + +func (i *Instance) SetPrompt(s string) { + i.Operation.SetPrompt(s) +} + +func (i *Instance) SetMaskRune(r rune) { + i.Operation.SetMaskRune(r) +} + +// change history persistence in runtime +func (i *Instance) SetHistoryPath(p string) { + i.Operation.SetHistoryPath(p) +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stdout() io.Writer { + return i.Operation.Stdout() +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stderr() io.Writer { + return i.Operation.Stderr() +} + +// switch VimMode in runtime +func (i *Instance) SetVimMode(on bool) { + i.Operation.SetVimMode(on) +} + +func (i *Instance) IsVimMode() bool { + return i.Operation.IsEnableVimMode() +} + +func (i *Instance) GenPasswordConfig() *Config { + return i.Operation.GenPasswordConfig() +} + +// we can generate a config by `i.GenPasswordConfig()` +func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { + return i.Operation.PasswordWithConfig(cfg) +} + +func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { + return i.Operation.PasswordEx(prompt, l) +} + +func (i *Instance) ReadPassword(prompt string) ([]byte, error) { + return i.Operation.Password(prompt) +} + +type Result struct { + Line string + Error error +} + +func (l *Result) CanContinue() bool { + return len(l.Line) != 0 && l.Error == ErrInterrupt +} + +func (l *Result) CanBreak() bool { + return !l.CanContinue() && l.Error != nil +} + +func (i *Instance) Line() *Result { + ret, err := i.Readline() + return &Result{ret, err} +} + +// err is one of (nil, io.EOF, readline.ErrInterrupt) +func (i *Instance) Readline() (string, error) { + return i.Operation.String() +} + +func (i *Instance) ReadlineWithDefault(what string) (string, error) { + i.Operation.SetBuffer(what) + return i.Operation.String() +} + +func (i *Instance) SaveHistory(content string) error { + return i.Operation.SaveHistory(content) +} + +// same as readline +func (i *Instance) ReadSlice() ([]byte, error) { + return i.Operation.Slice() +} + +// we must make sure that call Close() before process exit. +func (i *Instance) Close() error { + if err := i.Terminal.Close(); err != nil { + return err + } + i.Config.Stdin.Close() + i.Operation.Close() + return nil +} +func (i *Instance) Clean() { + i.Operation.Clean() +} + +func (i *Instance) Write(b []byte) (int, error) { + return i.Stdout().Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +// ie : +// i := readline.New() +// i.WriteStdin([]byte("test")) +// _, _= i.Readline() +// +// gives +// +// > test[cursor] +func (i *Instance) WriteStdin(val []byte) (int, error) { + return i.Terminal.WriteStdin(val) +} + +func (i *Instance) SetConfig(cfg *Config) *Config { + if i.Config == cfg { + return cfg + } + old := i.Config + i.Config = cfg + i.Operation.SetConfig(cfg) + i.Terminal.SetConfig(cfg) + return old +} + +func (i *Instance) Refresh() { + i.Operation.Refresh() +} + +// HistoryDisable the save of the commands into the history +func (i *Instance) HistoryDisable() { + i.Operation.history.Disable() +} + +// HistoryEnable the save of the commands into the history (default on) +func (i *Instance) HistoryEnable() { + i.Operation.history.Enable() +} diff --git a/vendor/github.com/chzyer/readline/remote.go b/vendor/github.com/chzyer/readline/remote.go new file mode 100644 index 000000000..74dbf5690 --- /dev/null +++ b/vendor/github.com/chzyer/readline/remote.go @@ -0,0 +1,475 @@ +package readline + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "os" + "sync" + "sync/atomic" +) + +type MsgType int16 + +const ( + T_DATA = MsgType(iota) + T_WIDTH + T_WIDTH_REPORT + T_ISTTY_REPORT + T_RAW + T_ERAW // exit raw + T_EOF +) + +type RemoteSvr struct { + eof int32 + closed int32 + width int32 + reciveChan chan struct{} + writeChan chan *writeCtx + conn net.Conn + isTerminal bool + funcWidthChan func() + stopChan chan struct{} + + dataBufM sync.Mutex + dataBuf bytes.Buffer +} + +type writeReply struct { + n int + err error +} + +type writeCtx struct { + msg *Message + reply chan *writeReply +} + +func newWriteCtx(msg *Message) *writeCtx { + return &writeCtx{ + msg: msg, + reply: make(chan *writeReply), + } +} + +func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) { + rs := &RemoteSvr{ + width: -1, + conn: conn, + writeChan: make(chan *writeCtx), + reciveChan: make(chan struct{}), + stopChan: make(chan struct{}), + } + buf := bufio.NewReader(rs.conn) + + if err := rs.init(buf); err != nil { + return nil, err + } + + go rs.readLoop(buf) + go rs.writeLoop() + return rs, nil +} + +func (r *RemoteSvr) init(buf *bufio.Reader) error { + m, err := ReadMessage(buf) + if err != nil { + return err + } + // receive isTerminal + if m.Type != T_ISTTY_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotIsTerminal(m.Data) + + // receive width + m, err = ReadMessage(buf) + if err != nil { + return err + } + if m.Type != T_WIDTH_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotReportWidth(m.Data) + + return nil +} + +func (r *RemoteSvr) HandleConfig(cfg *Config) { + cfg.Stderr = r + cfg.Stdout = r + cfg.Stdin = r + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncIsTerminal = r.IsTerminal + cfg.FuncMakeRaw = r.EnterRawMode + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncGetWidth = r.GetWidth + cfg.FuncOnWidthChanged = func(f func()) { + r.funcWidthChan = f + } +} + +func (r *RemoteSvr) IsTerminal() bool { + return r.isTerminal +} + +func (r *RemoteSvr) checkEOF() error { + if atomic.LoadInt32(&r.eof) == 1 { + return io.EOF + } + return nil +} + +func (r *RemoteSvr) Read(b []byte) (int, error) { + r.dataBufM.Lock() + n, err := r.dataBuf.Read(b) + r.dataBufM.Unlock() + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + if n == 0 && err == io.EOF { + <-r.reciveChan + r.dataBufM.Lock() + n, err = r.dataBuf.Read(b) + r.dataBufM.Unlock() + } + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + return n, err +} + +func (r *RemoteSvr) writeMsg(m *Message) error { + ctx := newWriteCtx(m) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.err +} + +func (r *RemoteSvr) Write(b []byte) (int, error) { + ctx := newWriteCtx(NewMessage(T_DATA, b)) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.n, reply.err +} + +func (r *RemoteSvr) EnterRawMode() error { + return r.writeMsg(NewMessage(T_RAW, nil)) +} + +func (r *RemoteSvr) ExitRawMode() error { + return r.writeMsg(NewMessage(T_ERAW, nil)) +} + +func (r *RemoteSvr) writeLoop() { + defer r.Close() + +loop: + for { + select { + case ctx, ok := <-r.writeChan: + if !ok { + break + } + n, err := ctx.msg.WriteTo(r.conn) + ctx.reply <- &writeReply{n, err} + case <-r.stopChan: + break loop + } + } +} + +func (r *RemoteSvr) Close() error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + close(r.stopChan) + r.conn.Close() + } + return nil +} + +func (r *RemoteSvr) readLoop(buf *bufio.Reader) { + defer r.Close() + for { + m, err := ReadMessage(buf) + if err != nil { + break + } + switch m.Type { + case T_EOF: + atomic.StoreInt32(&r.eof, 1) + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_DATA: + r.dataBufM.Lock() + r.dataBuf.Write(m.Data) + r.dataBufM.Unlock() + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_WIDTH_REPORT: + r.GotReportWidth(m.Data) + case T_ISTTY_REPORT: + r.GotIsTerminal(m.Data) + } + } +} + +func (r *RemoteSvr) GotIsTerminal(data []byte) { + if binary.BigEndian.Uint16(data) == 0 { + r.isTerminal = false + } else { + r.isTerminal = true + } +} + +func (r *RemoteSvr) GotReportWidth(data []byte) { + atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data))) + if r.funcWidthChan != nil { + r.funcWidthChan() + } +} + +func (r *RemoteSvr) GetWidth() int { + return int(atomic.LoadInt32(&r.width)) +} + +// ----------------------------------------------------------------------------- + +type Message struct { + Type MsgType + Data []byte +} + +func ReadMessage(r io.Reader) (*Message, error) { + m := new(Message) + var length int32 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil { + return nil, err + } + m.Data = make([]byte, int(length)-2) + if _, err := io.ReadFull(r, m.Data); err != nil { + return nil, err + } + return m, nil +} + +func NewMessage(t MsgType, data []byte) *Message { + return &Message{t, data} +} + +func (m *Message) WriteTo(w io.Writer) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4)) + binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2)) + binary.Write(buf, binary.BigEndian, m.Type) + buf.Write(m.Data) + n, err := buf.WriteTo(w) + return int(n), err +} + +// ----------------------------------------------------------------------------- + +type RemoteCli struct { + conn net.Conn + raw RawMode + receiveChan chan struct{} + inited int32 + isTerminal *bool + + data bytes.Buffer + dataM sync.Mutex +} + +func NewRemoteCli(conn net.Conn) (*RemoteCli, error) { + r := &RemoteCli{ + conn: conn, + receiveChan: make(chan struct{}), + } + return r, nil +} + +func (r *RemoteCli) MarkIsTerminal(is bool) { + r.isTerminal = &is +} + +func (r *RemoteCli) init() error { + if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) { + return nil + } + + if err := r.reportIsTerminal(); err != nil { + return err + } + + if err := r.reportWidth(); err != nil { + return err + } + + // register sig for width changed + DefaultOnWidthChanged(func() { + r.reportWidth() + }) + return nil +} + +func (r *RemoteCli) writeMsg(m *Message) error { + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return err +} + +func (r *RemoteCli) Write(b []byte) (int, error) { + m := NewMessage(T_DATA, b) + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return len(b), err +} + +func (r *RemoteCli) reportWidth() error { + screenWidth := GetScreenWidth() + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(screenWidth)) + msg := NewMessage(T_WIDTH_REPORT, data) + + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) reportIsTerminal() error { + var isTerminal bool + if r.isTerminal != nil { + isTerminal = *r.isTerminal + } else { + isTerminal = DefaultIsTerminal() + } + data := make([]byte, 2) + if isTerminal { + binary.BigEndian.PutUint16(data, 1) + } else { + binary.BigEndian.PutUint16(data, 0) + } + msg := NewMessage(T_ISTTY_REPORT, data) + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) readLoop() { + buf := bufio.NewReader(r.conn) + for { + msg, err := ReadMessage(buf) + if err != nil { + break + } + switch msg.Type { + case T_ERAW: + r.raw.Exit() + case T_RAW: + r.raw.Enter() + case T_DATA: + os.Stdout.Write(msg.Data) + } + } +} + +func (r *RemoteCli) ServeBy(source io.Reader) error { + if err := r.init(); err != nil { + return err + } + + go func() { + defer r.Close() + for { + n, _ := io.Copy(r, source) + if n == 0 { + break + } + } + }() + defer r.raw.Exit() + r.readLoop() + return nil +} + +func (r *RemoteCli) Close() { + r.writeMsg(NewMessage(T_EOF, nil)) +} + +func (r *RemoteCli) Serve() error { + return r.ServeBy(os.Stdin) +} + +func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error { + ln, err := net.Listen(n, addr) + if err != nil { + return err + } + if len(onListen) > 0 { + if err := onListen[0](ln); err != nil { + return err + } + } + for { + conn, err := ln.Accept() + if err != nil { + break + } + go func() { + defer conn.Close() + rl, err := HandleConn(*cfg, conn) + if err != nil { + return + } + h(rl) + }() + } + return nil +} + +func HandleConn(cfg Config, conn net.Conn) (*Instance, error) { + r, err := NewRemoteSvr(conn) + if err != nil { + return nil, err + } + r.HandleConfig(&cfg) + + rl, err := NewEx(&cfg) + if err != nil { + return nil, err + } + return rl, nil +} + +func DialRemote(n, addr string) error { + conn, err := net.Dial(n, addr) + if err != nil { + return err + } + defer conn.Close() + + cli, err := NewRemoteCli(conn) + if err != nil { + return err + } + return cli.Serve() +} diff --git a/vendor/github.com/chzyer/readline/runebuf.go b/vendor/github.com/chzyer/readline/runebuf.go new file mode 100644 index 000000000..81d2da50c --- /dev/null +++ b/vendor/github.com/chzyer/readline/runebuf.go @@ -0,0 +1,629 @@ +package readline + +import ( + "bufio" + "bytes" + "io" + "strconv" + "strings" + "sync" +) + +type runeBufferBck struct { + buf []rune + idx int +} + +type RuneBuffer struct { + buf []rune + idx int + prompt []rune + w io.Writer + + hadClean bool + interactive bool + cfg *Config + + width int + + bck *runeBufferBck + + offset string + + lastKill []rune + + sync.Mutex +} + +func (r* RuneBuffer) pushKill(text []rune) { + r.lastKill = append([]rune{}, text...) +} + +func (r *RuneBuffer) OnWidthChange(newWidth int) { + r.Lock() + r.width = newWidth + r.Unlock() +} + +func (r *RuneBuffer) Backup() { + r.Lock() + r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() +} + +func (r *RuneBuffer) Restore() { + r.Refresh(func() { + if r.bck == nil { + return + } + r.buf = r.bck.buf + r.idx = r.bck.idx + }) +} + +func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { + rb := &RuneBuffer{ + w: w, + interactive: cfg.useInteractive(), + cfg: cfg, + width: width, + } + rb.SetPrompt(prompt) + return rb +} + +func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() + r.cfg = cfg + r.interactive = cfg.useInteractive() + r.Unlock() +} + +func (r *RuneBuffer) SetMask(m rune) { + r.Lock() + r.cfg.MaskRune = m + r.Unlock() +} + +func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() + return runes.WidthAll(r.buf[:x]) +} + +func (r *RuneBuffer) PromptLen() int { + r.Lock() + width := r.promptLen() + r.Unlock() + return width +} + +func (r *RuneBuffer) promptLen() int { + return runes.WidthAll(runes.ColorFilter(r.prompt)) +} + +func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + + if i > 0 { + rs := make([]rune, i) + copy(rs, r.buf[r.idx:r.idx+i]) + return rs + } + rs := make([]rune, -i) + copy(rs, r.buf[r.idx+i:r.idx]) + return rs +} + +func (r *RuneBuffer) Runes() []rune { + r.Lock() + newr := make([]rune, len(r.buf)) + copy(newr, r.buf) + r.Unlock() + return newr +} + +func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() + return r.idx +} + +func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() + return len(r.buf) +} + +func (r *RuneBuffer) MoveToLineStart() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx = 0 + }) +} + +func (r *RuneBuffer) MoveBackward() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx-- + }) +} + +func (r *RuneBuffer) WriteString(s string) { + r.WriteRunes([]rune(s)) +} + +func (r *RuneBuffer) WriteRune(s rune) { + r.WriteRunes([]rune{s}) +} + +func (r *RuneBuffer) WriteRunes(s []rune) { + r.Refresh(func() { + tail := append(s, r.buf[r.idx:]...) + r.buf = append(r.buf[:r.idx], tail...) + r.idx += len(s) + }) +} + +func (r *RuneBuffer) MoveForward() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.idx++ + }) +} + +func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() + return r.idx == len(r.buf) +} + +func (r *RuneBuffer) Replace(ch rune) { + r.Refresh(func() { + r.buf[r.idx] = ch + }) +} + +func (r *RuneBuffer) Erase() { + r.Refresh(func() { + r.idx = 0 + r.pushKill(r.buf[:]) + r.buf = r.buf[:0] + }) +} + +func (r *RuneBuffer) Delete() (success bool) { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.pushKill(r.buf[r.idx : r.idx+1]) + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + success = true + }) + return +} + +func (r *RuneBuffer) DeleteWord() { + if r.idx == len(r.buf) { + return + } + init := r.idx + for init < len(r.buf) && IsWordBreak(r.buf[init]) { + init++ + } + for i := init + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[r.idx:i-1]) + r.Refresh(func() { + r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) + }) + return + } + } + r.Kill() +} + +func (r *RuneBuffer) MoveToPrevWord() (success bool) { + r.Refresh(func() { + if r.idx == 0 { + return + } + + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + success = true + return + } + } + r.idx = 0 + success = true + }) + return +} + +func (r *RuneBuffer) KillFront() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + length := len(r.buf) - r.idx + r.pushKill(r.buf[:r.idx]) + copy(r.buf[:length], r.buf[r.idx:]) + r.idx = 0 + r.buf = r.buf[:length] + }) +} + +func (r *RuneBuffer) Kill() { + r.Refresh(func() { + r.pushKill(r.buf[r.idx:]) + r.buf = r.buf[:r.idx] + }) +} + +func (r *RuneBuffer) Transpose() { + r.Refresh(func() { + if len(r.buf) == 1 { + r.idx++ + } + + if len(r.buf) < 2 { + return + } + + if r.idx == 0 { + r.idx = 1 + } else if r.idx >= len(r.buf) { + r.idx = len(r.buf) - 1 + } + r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] + r.idx++ + }) +} + +func (r *RuneBuffer) MoveToNextWord() { + r.Refresh(func() { + for i := r.idx + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + return + } + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) MoveToEndWord() { + r.Refresh(func() { + // already at the end, so do nothing + if r.idx == len(r.buf) { + return + } + // if we are at the end of a word already, go to next + if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { + r.idx++ + } + + // keep going until at the end of a word + for i := r.idx + 1; i < len(r.buf); i++ { + if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { + r.idx = i - 1 + return + } + } + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) BackEscapeWord() { + r.Refresh(func() { + if r.idx == 0 { + return + } + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[i:r.idx]) + r.buf = append(r.buf[:i], r.buf[r.idx:]...) + r.idx = i + return + } + } + + r.buf = r.buf[:0] + r.idx = 0 + }) +} + +func (r *RuneBuffer) Yank() { + if len(r.lastKill) == 0 { + return + } + r.Refresh(func() { + buf := make([]rune, 0, len(r.buf) + len(r.lastKill)) + buf = append(buf, r.buf[:r.idx]...) + buf = append(buf, r.lastKill...) + buf = append(buf, r.buf[r.idx:]...) + r.buf = buf + r.idx += len(r.lastKill) + }) +} + +func (r *RuneBuffer) Backspace() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + r.idx-- + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + }) +} + +func (r *RuneBuffer) MoveToLineEnd() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) LineCount(width int) int { + if width == -1 { + width = r.width + } + return LineCount(width, + runes.WidthAll(r.buf)+r.PromptLen()) +} + +func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { + r.Refresh(func() { + if reverse { + for i := r.idx - 1; i >= 0; i-- { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx++ + } + success = true + return + } + } + return + } + for i := r.idx + 1; i < len(r.buf); i++ { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx-- + } + success = true + return + } + } + }) + return +} + +func (r *RuneBuffer) isInLineEdge() bool { + if isWindows { + return false + } + sp := r.getSplitByLine(r.buf) + return len(sp[len(sp)-1]) == 0 +} + +func (r *RuneBuffer) getSplitByLine(rs []rune) []string { + return SplitByLine(r.promptLen(), r.width, rs) +} + +func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { + if width == 0 { + return 0 + } + sp := r.getSplitByLine(r.buf[:r.idx]) + return len(sp) - 1 +} + +func (r *RuneBuffer) CursorLineCount() int { + return r.LineCount(r.width) - r.IdxLine(r.width) +} + +func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + + if !r.interactive { + if f != nil { + f() + } + return + } + + r.clean() + if f != nil { + f() + } + r.print() +} + +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + +func (r *RuneBuffer) print() { + r.w.Write(r.output()) + r.hadClean = false +} + +func (r *RuneBuffer) output() []byte { + buf := bytes.NewBuffer(nil) + buf.WriteString(string(r.prompt)) + if r.cfg.EnableMask && len(r.buf) > 0 { + buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) + if r.buf[len(r.buf)-1] == '\n' { + buf.Write([]byte{'\n'}) + } else { + buf.Write([]byte(string(r.cfg.MaskRune))) + } + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + + } else { + for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { + if e == '\t' { + buf.WriteString(strings.Repeat(" ", TabWidth)) + } else { + buf.WriteRune(e) + } + } + if r.isInLineEdge() { + buf.Write([]byte(" \b")) + } + } + // cursor position + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + return buf.Bytes() +} + +func (r *RuneBuffer) getBackspaceSequence() []byte { + var sep = map[int]bool{} + + var i int + for { + if i >= runes.WidthAll(r.buf) { + break + } + + if i == 0 { + i -= r.promptLen() + } + i += r.width + + sep[i] = true + } + var buf []byte + for i := len(r.buf); i > r.idx; i-- { + // move input to the left of one + buf = append(buf, '\b') + if sep[i] { + // up one line, go to the start of the line and move cursor right to the end (r.width) + buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) + } + } + + return buf + +} + +func (r *RuneBuffer) Reset() []rune { + ret := runes.Copy(r.buf) + r.buf = r.buf[:0] + r.idx = 0 + return ret +} + +func (r *RuneBuffer) calWidth(m int) int { + if m > 0 { + return runes.WidthAll(r.buf[r.idx : r.idx+m]) + } + return runes.WidthAll(r.buf[r.idx+m : r.idx]) +} + +func (r *RuneBuffer) SetStyle(start, end int, style string) { + if end < start { + panic("end < start") + } + + // goto start + move := start - r.idx + if move > 0 { + r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) + } else { + r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) + } + r.w.Write([]byte("\033[" + style + "m")) + r.w.Write([]byte(string(r.buf[start:end]))) + r.w.Write([]byte("\033[0m")) + // TODO: move back +} + +func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { + r.Refresh(func() { + r.buf = buf + r.idx = idx + }) +} + +func (r *RuneBuffer) Set(buf []rune) { + r.SetWithIdx(len(buf), buf) +} + +func (r *RuneBuffer) SetPrompt(prompt string) { + r.Lock() + r.prompt = []rune(prompt) + r.Unlock() +} + +func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { + buf := bufio.NewWriter(w) + + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) + buf.Write([]byte("\033[J")) + } else { + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") + } + } + buf.Flush() + return +} + +func (r *RuneBuffer) Clean() { + r.Lock() + r.clean() + r.Unlock() +} + +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) +} + +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { + if r.hadClean || !r.interactive { + return + } + r.hadClean = true + r.cleanOutput(r.w, idxLine) +} diff --git a/vendor/github.com/chzyer/readline/runes.go b/vendor/github.com/chzyer/readline/runes.go new file mode 100644 index 000000000..a669bc48c --- /dev/null +++ b/vendor/github.com/chzyer/readline/runes.go @@ -0,0 +1,223 @@ +package readline + +import ( + "bytes" + "unicode" + "unicode/utf8" +) + +var runes = Runes{} +var TabWidth = 4 + +type Runes struct{} + +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + +func (Runes) Equal(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { + for i := len(r) - len(sub); i >= 0; i-- { + found := true + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + +// Search in runes from front to end +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { + for i := 0; i < len(r); i++ { + found := true + if len(r[i:]) < len(sub) { + return -1 + } + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +func (Runes) Index(r rune, rs []rune) int { + for i := 0; i < len(rs); i++ { + if rs[i] == r { + return i + } + } + return -1 +} + +func (Runes) ColorFilter(r []rune) []rune { + newr := make([]rune, 0, len(r)) + for pos := 0; pos < len(r); pos++ { + if r[pos] == '\033' && r[pos+1] == '[' { + idx := runes.Index('m', r[pos+2:]) + if idx == -1 { + continue + } + pos += idx + 2 + continue + } + newr = append(newr, r[pos]) + } + return newr +} + +var zeroWidth = []*unicode.RangeTable{ + unicode.Mn, + unicode.Me, + unicode.Cc, + unicode.Cf, +} + +var doubleWidth = []*unicode.RangeTable{ + unicode.Han, + unicode.Hangul, + unicode.Hiragana, + unicode.Katakana, +} + +func (Runes) Width(r rune) int { + if r == '\t' { + return TabWidth + } + if unicode.IsOneOf(zeroWidth, r) { + return 0 + } + if unicode.IsOneOf(doubleWidth, r) { + return 2 + } + return 1 +} + +func (Runes) WidthAll(r []rune) (length int) { + for i := 0; i < len(r); i++ { + length += runes.Width(r[i]) + } + return +} + +func (Runes) Backspace(r []rune) []byte { + return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r)) +} + +func (Runes) Copy(r []rune) []rune { + n := make([]rune, len(r)) + copy(n, r) + return n +} + +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + +func (Runes) HasPrefix(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.Equal(r[:len(prefix)], prefix) +} + +func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) { + for i := 0; i < len(candicate[0]); i++ { + for j := 0; j < len(candicate)-1; j++ { + if i >= len(candicate[j]) || i >= len(candicate[j+1]) { + goto aggregate + } + if candicate[j][i] != candicate[j+1][i] { + goto aggregate + } + } + size = i + 1 + } +aggregate: + if size > 0 { + same = runes.Copy(candicate[0][:size]) + for i := 0; i < len(candicate); i++ { + n := runes.Copy(candicate[i]) + copy(n, n[size:]) + candicate[i] = n[:len(n)-size] + } + } + return +} + +func (Runes) TrimSpaceLeft(in []rune) []rune { + firstIndex := len(in) + for i, r := range in { + if unicode.IsSpace(r) == false { + firstIndex = i + break + } + } + return in[firstIndex:] +} diff --git a/vendor/github.com/chzyer/readline/search.go b/vendor/github.com/chzyer/readline/search.go new file mode 100644 index 000000000..52e8ff099 --- /dev/null +++ b/vendor/github.com/chzyer/readline/search.go @@ -0,0 +1,164 @@ +package readline + +import ( + "bytes" + "container/list" + "fmt" + "io" +) + +const ( + S_STATE_FOUND = iota + S_STATE_FAILING +) + +const ( + S_DIR_BCK = iota + S_DIR_FWD +) + +type opSearch struct { + inMode bool + state int + dir int + source *list.Element + w io.Writer + buf *RuneBuffer + data []rune + history *opHistory + cfg *Config + markStart int + markEnd int + width int +} + +func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { + return &opSearch{ + w: w, + buf: buf, + cfg: cfg, + history: history, + width: width, + } +} + +func (o *opSearch) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opSearch) IsSearchMode() bool { + return o.inMode +} + +func (o *opSearch) SearchBackspace() { + if len(o.data) > 0 { + o.data = o.data[:len(o.data)-1] + o.search(true) + } +} + +func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { + if o.dir == S_DIR_BCK { + return o.history.FindBck(isNewSearch, o.data, o.buf.idx) + } + return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) +} + +func (o *opSearch) search(isChange bool) bool { + if len(o.data) == 0 { + o.state = S_STATE_FOUND + o.SearchRefresh(-1) + return true + } + idx, elem := o.findHistoryBy(isChange) + if elem == nil { + o.SearchRefresh(-2) + return false + } + o.history.current = elem + + item := o.history.showItem(o.history.current.Value) + start, end := 0, 0 + if o.dir == S_DIR_BCK { + start, end = idx, idx+len(o.data) + } else { + start, end = idx, idx+len(o.data) + idx += len(o.data) + } + o.buf.SetWithIdx(idx, item) + o.markStart, o.markEnd = start, end + o.SearchRefresh(idx) + return true +} + +func (o *opSearch) SearchChar(r rune) { + o.data = append(o.data, r) + o.search(true) +} + +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } + alreadyInMode := o.inMode + o.inMode = true + o.dir = dir + o.source = o.history.current + if alreadyInMode { + o.search(false) + } else { + o.SearchRefresh(-1) + } + return true +} + +func (o *opSearch) ExitSearchMode(revert bool) { + if revert { + o.history.current = o.source + o.buf.Set(o.history.showItem(o.history.current.Value)) + } + o.markStart, o.markEnd = 0, 0 + o.state = S_STATE_FOUND + o.inMode = false + o.source = nil + o.data = nil +} + +func (o *opSearch) SearchRefresh(x int) { + if x == -2 { + o.state = S_STATE_FAILING + } else if x >= 0 { + o.state = S_STATE_FOUND + } + if x < 0 { + x = o.buf.idx + } + x = o.buf.CurrentWidth(x) + x += o.buf.PromptLen() + x = x % o.width + + if o.markStart > 0 { + o.buf.SetStyle(o.markStart, o.markEnd, "4") + } + + lineCnt := o.buf.CursorLineCount() + buf := bytes.NewBuffer(nil) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + buf.WriteString("\033[J") + if o.state == S_STATE_FAILING { + buf.WriteString("failing ") + } + if o.dir == S_DIR_BCK { + buf.WriteString("bck") + } else if o.dir == S_DIR_FWD { + buf.WriteString("fwd") + } + buf.WriteString("-i-search: ") + buf.WriteString(string(o.data)) // keyword + buf.WriteString("\033[4m \033[0m") // _ + fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev + if x > 0 { + fmt.Fprintf(buf, "\033[%dC", x) // move forward + } + o.w.Write(buf.Bytes()) +} diff --git a/vendor/github.com/chzyer/readline/std.go b/vendor/github.com/chzyer/readline/std.go new file mode 100644 index 000000000..61d44b759 --- /dev/null +++ b/vendor/github.com/chzyer/readline/std.go @@ -0,0 +1,197 @@ +package readline + +import ( + "io" + "os" + "sync" + "sync/atomic" +) + +var ( + Stdin io.ReadCloser = os.Stdin + Stdout io.WriteCloser = os.Stdout + Stderr io.WriteCloser = os.Stderr +) + +var ( + std *Instance + stdOnce sync.Once +) + +// global instance will not submit history automatic +func getInstance() *Instance { + stdOnce.Do(func() { + std, _ = NewEx(&Config{ + DisableAutoSaveHistory: true, + }) + }) + return std +} + +// let readline load history from filepath +// and try to persist history into disk +// set fp to "" to prevent readline persisting history to disk +// so the `AddHistory` will return nil error forever. +func SetHistoryPath(fp string) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.HistoryFile = fp + ins.SetConfig(cfg) +} + +// set auto completer to global instance +func SetAutoComplete(completer AutoCompleter) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.AutoComplete = completer + ins.SetConfig(cfg) +} + +// add history to global instance manually +// raise error only if `SetHistoryPath` is set with a non-empty path +func AddHistory(content string) error { + ins := getInstance() + return ins.SaveHistory(content) +} + +func Password(prompt string) ([]byte, error) { + ins := getInstance() + return ins.ReadPassword(prompt) +} + +// readline with global configs +func Line(prompt string) (string, error) { + ins := getInstance() + ins.SetPrompt(prompt) + return ins.Readline() +} + +type CancelableStdin struct { + r io.Reader + mutex sync.Mutex + stop chan struct{} + closed int32 + notify chan struct{} + data []byte + read int + err error +} + +func NewCancelableStdin(r io.Reader) *CancelableStdin { + c := &CancelableStdin{ + r: r, + notify: make(chan struct{}), + stop: make(chan struct{}), + } + go c.ioloop() + return c +} + +func (c *CancelableStdin) ioloop() { +loop: + for { + select { + case <-c.notify: + c.read, c.err = c.r.Read(c.data) + select { + case c.notify <- struct{}{}: + case <-c.stop: + break loop + } + case <-c.stop: + break loop + } + } +} + +func (c *CancelableStdin) Read(b []byte) (n int, err error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if atomic.LoadInt32(&c.closed) == 1 { + return 0, io.EOF + } + + c.data = b + select { + case c.notify <- struct{}{}: + case <-c.stop: + return 0, io.EOF + } + select { + case <-c.notify: + return c.read, c.err + case <-c.stop: + return 0, io.EOF + } +} + +func (c *CancelableStdin) Close() error { + if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { + close(c.stop) + } + return nil +} + +// FillableStdin is a stdin reader which can prepend some data before +// reading into the real stdin +type FillableStdin struct { + sync.Mutex + stdin io.Reader + stdinBuffer io.ReadCloser + buf []byte + bufErr error +} + +// NewFillableStdin gives you FillableStdin +func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) { + r, w := io.Pipe() + s := &FillableStdin{ + stdinBuffer: r, + stdin: stdin, + } + s.ioloop() + return s, w +} + +func (s *FillableStdin) ioloop() { + go func() { + for { + bufR := make([]byte, 100) + var n int + n, s.bufErr = s.stdinBuffer.Read(bufR) + if s.bufErr != nil { + if s.bufErr == io.ErrClosedPipe { + break + } + } + s.Lock() + s.buf = append(s.buf, bufR[:n]...) + s.Unlock() + } + }() +} + +// Read will read from the local buffer and if no data, read from stdin +func (s *FillableStdin) Read(p []byte) (n int, err error) { + s.Lock() + i := len(s.buf) + if len(p) < i { + i = len(p) + } + if i > 0 { + n := copy(p, s.buf) + s.buf = s.buf[:0] + cerr := s.bufErr + s.bufErr = nil + s.Unlock() + return n, cerr + } + s.Unlock() + n, err = s.stdin.Read(p) + return n, err +} + +func (s *FillableStdin) Close() error { + s.stdinBuffer.Close() + return nil +} diff --git a/vendor/github.com/chzyer/readline/std_windows.go b/vendor/github.com/chzyer/readline/std_windows.go new file mode 100644 index 000000000..b10f91bcb --- /dev/null +++ b/vendor/github.com/chzyer/readline/std_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package readline + +func init() { + Stdin = NewRawReader() + Stdout = NewANSIWriter(Stdout) + Stderr = NewANSIWriter(Stderr) +} diff --git a/vendor/github.com/chzyer/readline/term.go b/vendor/github.com/chzyer/readline/term.go new file mode 100644 index 000000000..133993ca8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term.go @@ -0,0 +1,123 @@ +// Copyright 2011 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 darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + termios Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := getTermios(fd) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + + if termios, err := getTermios(fd); err != nil { + return nil, err + } else { + oldState.termios = *termios + } + + newState := oldState.termios + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + // newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + return &oldState, setTermios(fd, &newState) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := getTermios(fd) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + return setTermios(fd, &state.termios) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + oldState, err := getTermios(fd) + if err != nil { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if err := setTermios(fd, newState); err != nil { + return nil, err + } + + defer func() { + setTermios(fd, oldState) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/term_bsd.go b/vendor/github.com/chzyer/readline/term_bsd.go new file mode 100644 index 000000000..68b56ea6b --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_bsd.go @@ -0,0 +1,29 @@ +// Copyright 2013 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 darwin dragonfly freebsd netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_linux.go b/vendor/github.com/chzyer/readline/term_linux.go new file mode 100644 index 000000000..e3392b4ac --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_linux.go @@ -0,0 +1,33 @@ +// Copyright 2013 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. + +package readline + +import ( + "syscall" + "unsafe" +) + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_solaris.go b/vendor/github.com/chzyer/readline/term_solaris.go new file mode 100644 index 000000000..4c27273c7 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_solaris.go @@ -0,0 +1,32 @@ +// Copyright 2013 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 solaris + +package readline + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} + +type Termios unix.Termios + +func getTermios(fd int) (*Termios, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + return (*Termios)(termios), nil +} + +func setTermios(fd int, termios *Termios) error { + return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios)) +} diff --git a/vendor/github.com/chzyer/readline/term_unix.go b/vendor/github.com/chzyer/readline/term_unix.go new file mode 100644 index 000000000..d3ea24244 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_unix.go @@ -0,0 +1,24 @@ +// Copyright 2011 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 darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +type Termios syscall.Termios + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + var dimensions [4]uint16 + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0) + if err != 0 { + return 0, 0, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} diff --git a/vendor/github.com/chzyer/readline/term_windows.go b/vendor/github.com/chzyer/readline/term_windows.go new file mode 100644 index 000000000..1290e00bc --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_windows.go @@ -0,0 +1,171 @@ +// Copyright 2011 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 terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/terminal.go b/vendor/github.com/chzyer/readline/terminal.go new file mode 100644 index 000000000..1078631c1 --- /dev/null +++ b/vendor/github.com/chzyer/readline/terminal.go @@ -0,0 +1,238 @@ +package readline + +import ( + "bufio" + "fmt" + "io" + "strings" + "sync" + "sync/atomic" +) + +type Terminal struct { + m sync.Mutex + cfg *Config + outchan chan rune + closed int32 + stopChan chan struct{} + kickChan chan struct{} + wg sync.WaitGroup + isReading int32 + sleeping int32 + + sizeChan chan string +} + +func NewTerminal(cfg *Config) (*Terminal, error) { + if err := cfg.Init(); err != nil { + return nil, err + } + t := &Terminal{ + cfg: cfg, + kickChan: make(chan struct{}, 1), + outchan: make(chan rune), + stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), + } + + go t.ioloop() + return t, nil +} + +// SleepToResume will sleep myself, and return only if I'm resumed. +func (t *Terminal) SleepToResume() { + if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { + return + } + defer atomic.StoreInt32(&t.sleeping, 0) + + t.ExitRawMode() + ch := WaitForResume() + SuspendMe() + <-ch + t.EnterRawMode() +} + +func (t *Terminal) EnterRawMode() (err error) { + return t.cfg.FuncMakeRaw() +} + +func (t *Terminal) ExitRawMode() (err error) { + return t.cfg.FuncExitRaw() +} + +func (t *Terminal) Write(b []byte) (int, error) { + return t.cfg.Stdout.Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +func (t *Terminal) WriteStdin(b []byte) (int, error) { + return t.cfg.StdinWriter.Write(b) +} + +type termSize struct { + left int + top int +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + +func (t *Terminal) Print(s string) { + fmt.Fprintf(t.cfg.Stdout, "%s", s) +} + +func (t *Terminal) PrintRune(r rune) { + fmt.Fprintf(t.cfg.Stdout, "%c", r) +} + +func (t *Terminal) Readline() *Operation { + return NewOperation(t, t.cfg) +} + +// return rune(0) if meet EOF +func (t *Terminal) ReadRune() rune { + ch, ok := <-t.outchan + if !ok { + return rune(0) + } + return ch +} + +func (t *Terminal) IsReading() bool { + return atomic.LoadInt32(&t.isReading) == 1 +} + +func (t *Terminal) KickRead() { + select { + case t.kickChan <- struct{}{}: + default: + } +} + +func (t *Terminal) ioloop() { + t.wg.Add(1) + defer func() { + t.wg.Done() + close(t.outchan) + }() + + var ( + isEscape bool + isEscapeEx bool + expectNextChar bool + ) + + buf := bufio.NewReader(t.getStdin()) + for { + if !expectNextChar { + atomic.StoreInt32(&t.isReading, 0) + select { + case <-t.kickChan: + atomic.StoreInt32(&t.isReading, 1) + case <-t.stopChan: + return + } + } + expectNextChar = false + r, _, err := buf.ReadRune() + if err != nil { + if strings.Contains(err.Error(), "interrupted system call") { + expectNextChar = true + continue + } + break + } + + if isEscape { + isEscape = false + if r == CharEscapeEx { + expectNextChar = true + isEscapeEx = true + continue + } + r = escapeKey(r, buf) + } else if isEscapeEx { + isEscapeEx = false + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } + } + + expectNextChar = true + switch r { + case CharEsc: + if t.cfg.VimMode { + t.outchan <- r + break + } + isEscape = true + case CharInterrupt, CharEnter, CharCtrlJ, CharDelete: + expectNextChar = false + fallthrough + default: + t.outchan <- r + } + } + +} + +func (t *Terminal) Bell() { + fmt.Fprintf(t, "%c", CharBell) +} + +func (t *Terminal) Close() error { + if atomic.SwapInt32(&t.closed, 1) != 0 { + return nil + } + if closer, ok := t.cfg.Stdin.(io.Closer); ok { + closer.Close() + } + close(t.stopChan) + t.wg.Wait() + return t.ExitRawMode() +} + +func (t *Terminal) GetConfig() *Config { + t.m.Lock() + cfg := *t.cfg + t.m.Unlock() + return &cfg +} + +func (t *Terminal) getStdin() io.Reader { + t.m.Lock() + r := t.cfg.Stdin + t.m.Unlock() + return r +} + +func (t *Terminal) SetConfig(c *Config) error { + if err := c.Init(); err != nil { + return err + } + t.m.Lock() + t.cfg = c + t.m.Unlock() + return nil +} diff --git a/vendor/github.com/chzyer/readline/utils.go b/vendor/github.com/chzyer/readline/utils.go new file mode 100644 index 000000000..af4e00521 --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils.go @@ -0,0 +1,277 @@ +package readline + +import ( + "bufio" + "bytes" + "container/list" + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode" +) + +var ( + isWindows = false +) + +const ( + CharLineStart = 1 + CharBackward = 2 + CharInterrupt = 3 + CharDelete = 4 + CharLineEnd = 5 + CharForward = 6 + CharBell = 7 + CharCtrlH = 8 + CharTab = 9 + CharCtrlJ = 10 + CharKill = 11 + CharCtrlL = 12 + CharEnter = 13 + CharNext = 14 + CharPrev = 16 + CharBckSearch = 18 + CharFwdSearch = 19 + CharTranspose = 20 + CharCtrlU = 21 + CharCtrlW = 23 + CharCtrlY = 25 + CharCtrlZ = 26 + CharEsc = 27 + CharEscapeEx = 91 + CharBackspace = 127 +) + +const ( + MetaBackward rune = -iota - 1 + MetaForward + MetaDelete + MetaBackspace + MetaTranspose +) + +// WaitForResume need to call before current process got suspend. +// It will run a ticker until a long duration is occurs, +// which means this process is resumed. +func WaitForResume() chan struct{} { + ch := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + ticker := time.NewTicker(10 * time.Millisecond) + t := time.Now() + wg.Done() + for { + now := <-ticker.C + if now.Sub(t) > 100*time.Millisecond { + break + } + t = now + } + ticker.Stop() + ch <- struct{}{} + }() + wg.Wait() + return ch +} + +func Restore(fd int, state *State) error { + err := restoreTerm(fd, state) + if err != nil { + // errno 0 means everything is ok :) + if err.Error() == "errno 0" { + return nil + } else { + return err + } + } + return nil +} + +func IsPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// translate Esc[X +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { + case 'D': + r = CharBackward + case 'C': + r = CharForward + case 'A': + r = CharPrev + case 'B': + r = CharNext + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + case '~': + if key.attr == "3" { + r = CharDelete + } + default: + } + return r +} + +type escapeKeyPair struct { + attr string + typ rune +} + +func (e *escapeKeyPair) Get2() (int, int, bool) { + sp := strings.Split(e.attr, ";") + if len(sp) < 2 { + return -1, -1, false + } + s1, err := strconv.Atoi(sp[0]) + if err != nil { + return -1, -1, false + } + s2, err := strconv.Atoi(sp[1]) + if err != nil { + return -1, -1, false + } + return s1, s2, true +} + +func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { + p := escapeKeyPair{} + buf := bytes.NewBuffer(nil) + for { + if r == ';' { + } else if unicode.IsNumber(r) { + } else { + p.typ = r + break + } + buf.WriteRune(r) + r, _, _ = reader.ReadRune() + } + p.attr = buf.String() + return &p +} + +// translate EscX to Meta+X +func escapeKey(r rune, reader *bufio.Reader) rune { + switch r { + case 'b': + r = MetaBackward + case 'f': + r = MetaForward + case 'd': + r = MetaDelete + case CharTranspose: + r = MetaTranspose + case CharBackspace: + r = MetaBackspace + case 'O': + d, _, _ := reader.ReadRune() + switch d { + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + default: + reader.UnreadRune() + } + case CharEsc: + + } + return r +} + +func SplitByLine(start, screenWidth int, rs []rune) []string { + var ret []string + buf := bytes.NewBuffer(nil) + currentWidth := start + for _, r := range rs { + w := runes.Width(r) + currentWidth += w + buf.WriteRune(r) + if currentWidth >= screenWidth { + ret = append(ret, buf.String()) + buf.Reset() + currentWidth = 0 + } + } + ret = append(ret, buf.String()) + return ret +} + +// calculate how many lines for N character +func LineCount(screenWidth, w int) int { + r := w / screenWidth + if w%screenWidth != 0 { + r++ + } + return r +} + +func IsWordBreak(i rune) bool { + switch { + case i >= 'a' && i <= 'z': + case i >= 'A' && i <= 'Z': + case i >= '0' && i <= '9': + default: + return true + } + return false +} + +func GetInt(s []string, def int) int { + if len(s) == 0 { + return def + } + c, err := strconv.Atoi(s[0]) + if err != nil { + return def + } + return c +} + +type RawMode struct { + state *State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = MakeRaw(GetStdin()) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + return Restore(GetStdin(), r.state) +} + +// ----------------------------------------------------------------------------- + +func sleep(n int) { + Debug(n) + time.Sleep(2000 * time.Millisecond) +} + +// print a linked list to Debug() +func debugList(l *list.List) { + idx := 0 + for e := l.Front(); e != nil; e = e.Next() { + Debug(idx, fmt.Sprintf("%+v", e.Value)) + idx++ + } +} + +// append log info to another file +func Debug(o ...interface{}) { + f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fmt.Fprintln(f, o...) + f.Close() +} diff --git a/vendor/github.com/chzyer/readline/utils_unix.go b/vendor/github.com/chzyer/readline/utils_unix.go new file mode 100644 index 000000000..f88dac97b --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_unix.go @@ -0,0 +1,83 @@ +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +package readline + +import ( + "io" + "os" + "os/signal" + "sync" + "syscall" +) + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// SuspendMe use to send suspend signal to myself, when we in the raw mode. +// For OSX it need to send to parent's pid +// For Linux it need to send to myself +func SuspendMe() { + p, _ := os.FindProcess(os.Getppid()) + p.Signal(syscall.SIGTSTP) + p, _ = os.FindProcess(os.Getpid()) + p.Signal(syscall.SIGTSTP) +} + +// get width of the terminal +func getWidth(stdoutFd int) int { + cols, _, err := GetSize(stdoutFd) + if err != nil { + return -1 + } + return cols +} + +func GetScreenWidth() int { + w := getWidth(syscall.Stdout) + if w < 0 { + w = getWidth(syscall.Stderr) + } + return w +} + +// ClearScreen clears the console screen +func ClearScreen(w io.Writer) (int, error) { + return w.Write([]byte("\033[H")) +} + +func DefaultIsTerminal() bool { + return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr)) +} + +func GetStdin() int { + return syscall.Stdin +} + +// ----------------------------------------------------------------------------- + +var ( + widthChange sync.Once + widthChangeCallback func() +) + +func DefaultOnWidthChanged(f func()) { + widthChangeCallback = f + widthChange.Do(func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + + go func() { + for { + _, ok := <-ch + if !ok { + break + } + widthChangeCallback() + } + }() + }) +} diff --git a/vendor/github.com/chzyer/readline/utils_windows.go b/vendor/github.com/chzyer/readline/utils_windows.go new file mode 100644 index 000000000..5bfa55dcc --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package readline + +import ( + "io" + "syscall" +) + +func SuspendMe() { +} + +func GetStdin() int { + return int(syscall.Stdin) +} + +func init() { + isWindows = true +} + +// get width of the terminal +func GetScreenWidth() int { + info, _ := GetConsoleScreenBufferInfo() + if info == nil { + return -1 + } + return int(info.dwSize.x) +} + +// ClearScreen clears the console screen +func ClearScreen(_ io.Writer) error { + return SetConsoleCursorPosition(&_COORD{0, 0}) +} + +func DefaultIsTerminal() bool { + return true +} + +func DefaultOnWidthChanged(func()) { + +} diff --git a/vendor/github.com/chzyer/readline/vim.go b/vendor/github.com/chzyer/readline/vim.go new file mode 100644 index 000000000..bedf2c1a6 --- /dev/null +++ b/vendor/github.com/chzyer/readline/vim.go @@ -0,0 +1,176 @@ +package readline + +const ( + VIM_NORMAL = iota + VIM_INSERT + VIM_VISUAL +) + +type opVim struct { + cfg *Config + op *Operation + vimMode int +} + +func newVimMode(op *Operation) *opVim { + ov := &opVim{ + cfg: op.cfg, + op: op, + } + ov.SetVimMode(ov.cfg.VimMode) + return ov +} + +func (o *opVim) SetVimMode(on bool) { + if o.cfg.VimMode && !on { // turn off + o.ExitVimMode() + } + o.cfg.VimMode = on + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) IsEnableVimMode() bool { + return o.cfg.VimMode +} + +func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'h': + t = CharBackward + case 'j': + t = CharNext + case 'k': + t = CharPrev + case 'l': + t = CharForward + case '0', '^': + rb.MoveToLineStart() + case '$': + rb.MoveToLineEnd() + case 'x': + rb.Delete() + if rb.IsCursorInEnd() { + rb.MoveBackward() + } + case 'r': + rb.Replace(readNext()) + case 'd': + next := readNext() + switch next { + case 'd': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + case 'p': + rb.Yank() + case 'b', 'B': + rb.MoveToPrevWord() + case 'w', 'W': + rb.MoveToNextWord() + case 'e', 'E': + rb.MoveToEndWord() + case 'f', 'F', 't', 'T': + next := readNext() + prevChar := r == 't' || r == 'T' + reverse := r == 'F' || r == 'T' + switch next { + case CharEsc: + default: + rb.MoveTo(next, prevChar, reverse) + } + default: + return r, false + } + return t, true +} + +func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'i': + case 'I': + rb.MoveToLineStart() + case 'a': + rb.MoveForward() + case 'A': + rb.MoveToLineEnd() + case 's': + rb.Delete() + case 'S': + rb.Erase() + case 'c': + next := readNext() + switch next { + case 'c': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + default: + return r, false + } + + o.EnterVimInsertMode() + return +} + +func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) { + switch r { + case CharEnter, CharInterrupt: + o.ExitVimMode() + return r + } + + if r, handled := o.handleVimNormalMovement(r, readNext); handled { + return r + } + + if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled { + return r + } + + // invalid operation + o.op.t.Bell() + return 0 +} + +func (o *opVim) EnterVimInsertMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimInsertMode() { + o.vimMode = VIM_NORMAL +} + +func (o *opVim) HandleVim(r rune, readNext func() rune) rune { + if o.vimMode == VIM_NORMAL { + return o.HandleVimNormal(r, readNext) + } + if r == CharEsc { + o.ExitVimInsertMode() + return 0 + } + + switch o.vimMode { + case VIM_INSERT: + return r + case VIM_VISUAL: + } + return r +} diff --git a/vendor/github.com/chzyer/readline/windows_api.go b/vendor/github.com/chzyer/readline/windows_api.go new file mode 100644 index 000000000..63f4f7b78 --- /dev/null +++ b/vendor/github.com/chzyer/readline/windows_api.go @@ -0,0 +1,152 @@ +// +build windows + +package readline + +import ( + "reflect" + "syscall" + "unsafe" +) + +var ( + kernel = NewKernel() + stdout = uintptr(syscall.Stdout) + stdin = uintptr(syscall.Stdin) +) + +type Kernel struct { + SetConsoleCursorPosition, + SetConsoleTextAttribute, + FillConsoleOutputCharacterW, + FillConsoleOutputAttribute, + ReadConsoleInputW, + GetConsoleScreenBufferInfo, + GetConsoleCursorInfo, + GetStdHandle CallFunc +} + +type short int16 +type word uint16 +type dword uint32 +type wchar uint16 + +type _COORD struct { + x short + y short +} + +func (c *_COORD) ptr() uintptr { + return uintptr(*(*int32)(unsafe.Pointer(c))) +} + +const ( + EVENT_KEY = 0x0001 + EVENT_MOUSE = 0x0002 + EVENT_WINDOW_BUFFER_SIZE = 0x0004 + EVENT_MENU = 0x0008 + EVENT_FOCUS = 0x0010 +) + +type _KEY_EVENT_RECORD struct { + bKeyDown int32 + wRepeatCount word + wVirtualKeyCode word + wVirtualScanCode word + unicodeChar wchar + dwControlKeyState dword +} + +// KEY_EVENT_RECORD KeyEvent; +// MOUSE_EVENT_RECORD MouseEvent; +// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; +// MENU_EVENT_RECORD MenuEvent; +// FOCUS_EVENT_RECORD FocusEvent; +type _INPUT_RECORD struct { + EventType word + Padding uint16 + Event [16]byte +} + +type _CONSOLE_SCREEN_BUFFER_INFO struct { + dwSize _COORD + dwCursorPosition _COORD + wAttributes word + srWindow _SMALL_RECT + dwMaximumWindowSize _COORD +} + +type _SMALL_RECT struct { + left short + top short + right short + bottom short +} + +type _CONSOLE_CURSOR_INFO struct { + dwSize dword + bVisible bool +} + +type CallFunc func(u ...uintptr) error + +func NewKernel() *Kernel { + k := &Kernel{} + kernel32 := syscall.NewLazyDLL("kernel32.dll") + v := reflect.ValueOf(k).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + name := t.Field(i).Name + f := kernel32.NewProc(name) + v.Field(i).Set(reflect.ValueOf(k.Wrap(f))) + } + return k +} + +func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc { + return func(args ...uintptr) error { + var r0 uintptr + var e1 syscall.Errno + size := uintptr(len(args)) + if len(args) <= 3 { + buf := make([]uintptr, 3) + copy(buf, args) + r0, _, e1 = syscall.Syscall(p.Addr(), size, + buf[0], buf[1], buf[2]) + } else { + buf := make([]uintptr, 6) + copy(buf, args) + r0, _, e1 = syscall.Syscall6(p.Addr(), size, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + ) + } + + if int(r0) == 0 { + if e1 != 0 { + return error(e1) + } else { + return syscall.EINVAL + } + } + return nil + } + +} + +func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { + t := new(_CONSOLE_SCREEN_BUFFER_INFO) + err := kernel.GetConsoleScreenBufferInfo( + stdout, + uintptr(unsafe.Pointer(t)), + ) + return t, err +} + +func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { + t := new(_CONSOLE_CURSOR_INFO) + err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) + return t, err +} + +func SetConsoleCursorPosition(c *_COORD) error { + return kernel.SetConsoleCursorPosition(stdout, c.ptr()) +} From d16fd8a2b1f8fdf1f92b6b7199dc7a485cc18cef Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Fri, 7 Jun 2019 12:42:38 +0200 Subject: [PATCH 11/51] allow to build on solaris --- helper/wrappedreadline/wrappedreadline.go | 5 ----- helper/wrappedreadline/wrappedreadline_solaris.go | 11 +++++++++++ helper/wrappedreadline/wrappedreadline_unix.go | 2 +- helper/wrappedreadline/wrappedreadline_windows.go | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 helper/wrappedreadline/wrappedreadline_solaris.go diff --git a/helper/wrappedreadline/wrappedreadline.go b/helper/wrappedreadline/wrappedreadline.go index 3bf7db0a0..67cb80653 100644 --- a/helper/wrappedreadline/wrappedreadline.go +++ b/helper/wrappedreadline/wrappedreadline.go @@ -118,8 +118,3 @@ var ( wrappedStdout *os.File wrappedStderr *os.File ) - -func init() { - // Initialize the platform-specific code - initPlatform() -} diff --git a/helper/wrappedreadline/wrappedreadline_solaris.go b/helper/wrappedreadline/wrappedreadline_solaris.go new file mode 100644 index 000000000..26c4e6c7b --- /dev/null +++ b/helper/wrappedreadline/wrappedreadline_solaris.go @@ -0,0 +1,11 @@ +package wrappedreadline + +// getWidth impl for Solaris +func getWidth() int { + return 80 +} + +// get width of the terminal +func getWidthFd(stdoutFd int) int { + return getWidth() +} diff --git a/helper/wrappedreadline/wrappedreadline_unix.go b/helper/wrappedreadline/wrappedreadline_unix.go index 130f2987a..8db87d1fa 100644 --- a/helper/wrappedreadline/wrappedreadline_unix.go +++ b/helper/wrappedreadline/wrappedreadline_unix.go @@ -44,7 +44,7 @@ func getWidthFd(stdoutFd int) int { return int(ws.Col) } -func initPlatform() { +func init() { // The standard streams are passed in via extra file descriptors. wrappedStdin = os.NewFile(uintptr(3), "stdin") wrappedStdout = os.NewFile(uintptr(4), "stdout") diff --git a/helper/wrappedreadline/wrappedreadline_windows.go b/helper/wrappedreadline/wrappedreadline_windows.go index 459479af9..fc1793409 100644 --- a/helper/wrappedreadline/wrappedreadline_windows.go +++ b/helper/wrappedreadline/wrappedreadline_windows.go @@ -13,7 +13,7 @@ func getWidth() int { return 0 } -func initPlatform() { +func init() { wrappedStdin = openConsole("CONIN$", os.Stdin) wrappedStdout = openConsole("CONOUT$", os.Stdout) wrappedStderr = wrappedStdout From d4b0cb68e3f37e03d68f8408ba0c3b61fda860d5 Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Mon, 10 Jun 2019 22:45:02 +0530 Subject: [PATCH 12/51] feat: add retry for temp key-pair generation in amazon-ebs Signed-off-by: Pratyush singhal --- builder/amazon/common/step_key_pair.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index 5ae534cc2..a5a2fffd8 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -5,8 +5,10 @@ import ( "fmt" "os" "runtime" + "time" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -53,10 +55,19 @@ func (s *StepKeyPair) Run(ctx context.Context, state multistep.StateBag) multist } ec2conn := state.Get("ec2").(*ec2.EC2) + var keyResp *ec2.CreateKeyPairOutput ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName)) - keyResp, err := ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{ - KeyName: &s.Comm.SSHTemporaryKeyPairName}) + err := retry.Config{ + Tries: 11, + RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30, Multiplier: 2}).Linear, + }.Run(ctx, func(ctx context.Context) error { + var err error + keyResp, err = ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{ + KeyName: &s.Comm.SSHTemporaryKeyPairName}) + return err + }) + if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt From 1a9adc29b3c74d90c2ee70d2626180ab5837d196 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 7 Jun 2019 15:56:20 -0700 Subject: [PATCH 13/51] fixing interpolation fix sensitive_vars test which never worked but somehow was passing before this change. --- packer/core.go | 61 +++++++------- packer/core_test.go | 81 +++++++++++++++++-- .../build-basic-interpolated-required.json | 9 +++ .../build-var-file-interpolate.json | 3 + .../build-var-file-interpolate2.json | 3 + ....json => build-variables-interpolate.json} | 0 ...json => build-variables-interpolate2.json} | 0 7 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 packer/test-fixtures/build-basic-interpolated-required.json create mode 100644 packer/test-fixtures/build-var-file-interpolate.json create mode 100644 packer/test-fixtures/build-var-file-interpolate2.json rename packer/test-fixtures/{variables.json => build-variables-interpolate.json} (100%) rename packer/test-fixtures/{variables2.json => build-variables-interpolate2.json} (100%) diff --git a/packer/core.go b/packer/core.go index 0b3670371..d9b12fcb5 100644 --- a/packer/core.go +++ b/packer/core.go @@ -2,6 +2,7 @@ package packer import ( "fmt" + "log" "sort" ttmp "text/template" @@ -306,7 +307,6 @@ func (c *Core) init() error { if c.variables == nil { c.variables = make(map[string]string) } - // Go through the variables and interpolate the environment and // user variables @@ -314,7 +314,6 @@ func (c *Core) init() error { ctx.EnableEnv = true ctx.UserVariables = make(map[string]string) shouldRetry := true - tryCount := 0 changed := false failedInterpolation := "" @@ -337,32 +336,43 @@ func (c *Core) init() error { // interpolating them. Please don't actually nest your variables in 100 // layers of other variables. Please. - for shouldRetry == true { + // c.Template.Variables is populated by variables defined within the Template + // itself + // c.variables is populated by variables read in from the command line and + // var-files. + // We need to read the keys from both, then loop over all of them to figure + // out the appropriate interpolations. + + allVariables := make(map[string]string) + // load in template variables + log.Printf("\n\n\n\nMegan template.Variables is %#v", c.Template.Variables) + for k, v := range c.Template.Variables { + allVariables[k] = v.Default + } + + // overwrite template variables with command-line-read variables + log.Printf("Megan c.variables is %#v", c.variables) + for k, v := range c.variables { + allVariables[k] = v + } + + for i := 0; i < 100; i++ { shouldRetry = false - for k, v := range c.Template.Variables { - // Ignore variables that are required - if v.Required { - continue - } - - // Ignore variables that have a value already - if _, ok := c.variables[k]; ok { - continue - } - + // First, loop over the variables in the template + for k, v := range allVariables { // Interpolate the default - def, err := interpolate.Render(v.Default, ctx) + renderedV, err := interpolate.Render(v, ctx) + log.Printf("Megan k is %s, renderedV is %s and err is %s", k, renderedV, err) switch err.(type) { case nil: // We only get here if interpolation has succeeded, so something is // different in this loop than in the last one. changed = true - c.variables[k] = def + c.variables[k] = renderedV ctx.UserVariables = c.variables case ttmp.ExecError: shouldRetry = true - tryCount++ - failedInterpolation = fmt.Sprintf(`"%s": "%s"`, k, v.Default) + failedInterpolation = fmt.Sprintf(`"%s": "%s"`, k, v) continue default: return fmt.Errorf( @@ -371,10 +381,9 @@ func (c *Core) init() error { k, err) } } - if tryCount >= 100 { + if !shouldRetry { break } - } if (changed == false) && (shouldRetry == true) { @@ -384,20 +393,18 @@ func (c *Core) init() error { "required.", failedInterpolation) } + log.Printf("Megan rendering sensitive variables now...") for _, v := range c.Template.SensitiveVariables { - def, err := interpolate.Render(v.Default, ctx) - if err != nil { - return fmt.Errorf( - "error interpolating default value for '%#v': %s", - v, err) - } - c.secrets = append(c.secrets, def) + // log.Printf("k is %#v, v is %#v", k, v) + secret := ctx.UserVariables[v.Key] + c.secrets = append(c.secrets, secret) } // Interpolate the push configuration if _, err := interpolate.RenderInterface(&c.Template.Push, c.Context()); err != nil { return fmt.Errorf("Error interpolating 'push': %s", err) } + log.Printf("Megan ctx.UserVariables is %#v", ctx.UserVariables) return nil } diff --git a/packer/core_test.go b/packer/core_test.go index dfe6b29b7..e1ffa5b59 100644 --- a/packer/core_test.go +++ b/packer/core_test.go @@ -524,6 +524,8 @@ func TestCoreValidate(t *testing.T) { } } +// Tests that we can properly interpolate user variables defined within the +// packer template func TestCore_InterpolateUserVars(t *testing.T) { cases := []struct { File string @@ -531,7 +533,7 @@ func TestCore_InterpolateUserVars(t *testing.T) { Err bool }{ { - "variables.json", + "build-variables-interpolate.json", map[string]string{ "foo": "bar", "bar": "bar", @@ -541,7 +543,7 @@ func TestCore_InterpolateUserVars(t *testing.T) { false, }, { - "variables2.json", + "build-variables-interpolate2.json", map[string]string{}, true, }, @@ -576,6 +578,74 @@ func TestCore_InterpolateUserVars(t *testing.T) { } } +// Tests that we can properly interpolate user variables defined within a +// var-file provided alongside the Packer template +func TestCore_InterpolateUserVars_VarFile(t *testing.T) { + cases := []struct { + File string + Variables map[string]string + Expected map[string]string + Err bool + }{ + { + // tests that we can interpolate from var files when var isn't set in + // originating template + "build-basic-interpolated.json", + map[string]string{ + "name": "gotta-{{user `my_var`}}", + "my_var": "interpolate-em-all", + }, + map[string]string{ + "name": "gotta-interpolate-em-all", + "my_var": "interpolate-em-all"}, + false, + }, + { + // tests that we can interpolate from var files when var is set in + // originating template as required + "build-basic-interpolated-required.json", + map[string]string{ + "name": "gotta-{{user `my_var`}}", + "my_var": "interpolate-em-all", + }, + map[string]string{ + "name": "gotta-interpolate-em-all", + "my_var": "interpolate-em-all"}, + false, + }, + } + for _, tc := range cases { + f, err := os.Open(fixtureDir(tc.File)) + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl, err := template.Parse(f) + f.Close() + if err != nil { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + + ccf, err := NewCore(&CoreConfig{ + Template: tpl, + Version: "1.0.0", + Variables: tc.Variables, + }) + + if (err != nil) != tc.Err { + t.Fatalf("err: %s\n\n%s", tc.File, err) + } + if !tc.Err { + for k, v := range ccf.variables { + if tc.Expected[k] != v { + t.Fatalf("Expected value %s for key %s but got %s", + tc.Expected[k], k, v) + } + } + } + } +} + func TestSensitiveVars(t *testing.T) { cases := []struct { File string @@ -595,9 +665,10 @@ func TestSensitiveVars(t *testing.T) { // interpolated { "sensitive-variables.json", - map[string]string{"foo": "{{build_name}}"}, - []string{"foo"}, - "test", + map[string]string{"foo": "bar", + "bang": "{{ user `foo`}}"}, + []string{"bang"}, + "bar", false, }, } diff --git a/packer/test-fixtures/build-basic-interpolated-required.json b/packer/test-fixtures/build-basic-interpolated-required.json new file mode 100644 index 000000000..2cdd3df63 --- /dev/null +++ b/packer/test-fixtures/build-basic-interpolated-required.json @@ -0,0 +1,9 @@ +{ + "variables": { + "name": null + }, + "builders": [{ + "name": "{{upper `name`}}", + "type": "test" + }] +} diff --git a/packer/test-fixtures/build-var-file-interpolate.json b/packer/test-fixtures/build-var-file-interpolate.json new file mode 100644 index 000000000..d2f8f5225 --- /dev/null +++ b/packer/test-fixtures/build-var-file-interpolate.json @@ -0,0 +1,3 @@ +{ + "name": "{{ user `my_var` }}" +} \ No newline at end of file diff --git a/packer/test-fixtures/build-var-file-interpolate2.json b/packer/test-fixtures/build-var-file-interpolate2.json new file mode 100644 index 000000000..c7a4c9635 --- /dev/null +++ b/packer/test-fixtures/build-var-file-interpolate2.json @@ -0,0 +1,3 @@ +{ + "my_var": "interpolate this, yall" +} \ No newline at end of file diff --git a/packer/test-fixtures/variables.json b/packer/test-fixtures/build-variables-interpolate.json similarity index 100% rename from packer/test-fixtures/variables.json rename to packer/test-fixtures/build-variables-interpolate.json diff --git a/packer/test-fixtures/variables2.json b/packer/test-fixtures/build-variables-interpolate2.json similarity index 100% rename from packer/test-fixtures/variables2.json rename to packer/test-fixtures/build-variables-interpolate2.json From ca99cbd2d224d57e2926cf4e1bd70598db9af896 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 10 Jun 2019 11:34:57 -0700 Subject: [PATCH 14/51] remove loglines --- packer/core.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packer/core.go b/packer/core.go index d9b12fcb5..d4d004649 100644 --- a/packer/core.go +++ b/packer/core.go @@ -2,7 +2,6 @@ package packer import ( "fmt" - "log" "sort" ttmp "text/template" @@ -345,13 +344,11 @@ func (c *Core) init() error { allVariables := make(map[string]string) // load in template variables - log.Printf("\n\n\n\nMegan template.Variables is %#v", c.Template.Variables) for k, v := range c.Template.Variables { allVariables[k] = v.Default } // overwrite template variables with command-line-read variables - log.Printf("Megan c.variables is %#v", c.variables) for k, v := range c.variables { allVariables[k] = v } @@ -362,7 +359,6 @@ func (c *Core) init() error { for k, v := range allVariables { // Interpolate the default renderedV, err := interpolate.Render(v, ctx) - log.Printf("Megan k is %s, renderedV is %s and err is %s", k, renderedV, err) switch err.(type) { case nil: // We only get here if interpolation has succeeded, so something is @@ -393,7 +389,6 @@ func (c *Core) init() error { "required.", failedInterpolation) } - log.Printf("Megan rendering sensitive variables now...") for _, v := range c.Template.SensitiveVariables { // log.Printf("k is %#v, v is %#v", k, v) secret := ctx.UserVariables[v.Key] @@ -404,7 +399,6 @@ func (c *Core) init() error { if _, err := interpolate.RenderInterface(&c.Template.Push, c.Context()); err != nil { return fmt.Errorf("Error interpolating 'push': %s", err) } - log.Printf("Megan ctx.UserVariables is %#v", ctx.UserVariables) return nil } From 7cce3157a8b7b266e4eaf00b16b5d09fd0eec640 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 10 Jun 2019 11:38:14 -0700 Subject: [PATCH 15/51] Update command/console.go Co-Authored-By: Adrien Delorme --- command/console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/console.go b/command/console.go index 455537482..c45576499 100644 --- a/command/console.go +++ b/command/console.go @@ -39,7 +39,7 @@ func (c *ConsoleCommand) Run(args []string) int { args = flags.Args() if len(args) < 1 { - // If user has not definied a builder, create a tiny null placeholder + // If user has not defined a builder, create a tiny null placeholder // builder so that we can properly initialize the core tpl, err := template.Parse(strings.NewReader(TiniestBuilder)) if err != nil { From 53a5e90d780fd135a225278c48d06870e6ba7695 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 10 Jun 2019 11:38:29 -0700 Subject: [PATCH 16/51] Update command/console.go Co-Authored-By: Adrien Delorme --- command/console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/console.go b/command/console.go index c45576499..d0a435482 100644 --- a/command/console.go +++ b/command/console.go @@ -180,7 +180,7 @@ type REPLSession struct { Core *packer.Core } -// Handle handles a single line of input from the REPL. +// Handle a single line of input from the REPL. // // The return value is the output and the error to show. func (s *REPLSession) Handle(line string) (string, error) { From f7bf80724c62ae8c2ae9780b4eee5c57057907df Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 10 Jun 2019 11:39:42 -0700 Subject: [PATCH 17/51] document what a REPL is --- command/console.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/console.go b/command/console.go index d0a435482..da2faf52b 100644 --- a/command/console.go +++ b/command/console.go @@ -174,7 +174,7 @@ func (c *ConsoleCommand) modeInteractive(session *REPLSession) int { // Setup th // from Handle to signal a graceful exit. var ErrSessionExit = errors.New("Session exit") -// Session represents the state for a single REPL session. +// Session represents the state for a single Read-Evaluate-Print-Loop (REPL) session. type REPLSession struct { // Core is used for constructing interpolations based off packer templates Core *packer.Core From 3a88c299a96b5f0c3d2c589cd6910bf219da359f Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 10 Jun 2019 15:53:57 -0700 Subject: [PATCH 18/51] update changelog --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b4673a8c..eb69ad092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ ## 1.4.2 (upcoming) +### IMPROVEMENTS: +* builder/amazon: Enable encrypted AMI sharing across accounts [GH-7707] +* builder/amazon: New SpotInstanceTypes feature for spot instance users. + [GH-7682] +* builder/azure: Update Azure SDK for Go to v30.0.0 [GH-7706] +* builder/cloudstack: Add tags to instance upon creation [GH0-7526] +* builder/docker: Better windows defaults [GH-7678] +* builder/openstack: Add image filtering on properties. [GH-7597] +* provisioner/powershell: Fix null file descriptor error that occurred when + remote_path provided is a directory and not a file. [GH-7705] + + +### BUG FIXES: +* builder/amazon: Fix bug in region copy which produced badly-named AMIs in the + build region. [GH-7691] +* builder/amazon: Fix failure that happened when spot_tags was set but ami_tags + wasn't [GH-7699] +* builder/cloudstack: Update go-cloudstack sdk, fixing compatability with + CloudStack v 4.12 [GH-7694] +* provisioner/chef: Accept chef license by default to prevent hangs in latest + Chef [GH-7653] +* provisioner/powershell: Fix crash caused by error in retry logic check in + powershell provisioner [GH-7657] + ## 1.4.1 (May 15, 2019) ### IMPROVEMENTS: From 45249fc764e69dca17c081260358d38bc27f2f98 Mon Sep 17 00:00:00 2001 From: "bozhi.ch" Date: Tue, 11 Jun 2019 15:22:06 +0800 Subject: [PATCH 19/51] fix describing snapshots issue when image_ignore_data_disks is provided --- builder/alicloud/ecs/builder_acc_test.go | 81 +++++++++++++++++--- builder/alicloud/ecs/client.go | 26 +++++++ builder/alicloud/ecs/step_create_snapshot.go | 47 +++--------- 3 files changed, 108 insertions(+), 46 deletions(-) diff --git a/builder/alicloud/ecs/builder_acc_test.go b/builder/alicloud/ecs/builder_acc_test.go index 8d93db640..8d2022b64 100644 --- a/builder/alicloud/ecs/builder_acc_test.go +++ b/builder/alicloud/ecs/builder_acc_test.go @@ -57,7 +57,7 @@ const testBuilderAccBasic = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_name": "packer-test-basic_{{timestamp}}" @@ -81,7 +81,7 @@ const testBuilderAccWithDiskSettings = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_name": "packer-test-withDiskSettings_{{timestamp}}", @@ -189,6 +189,69 @@ func checkImageDisksSettings() builderT.TestCheckFunc { } } +func TestBuilderAcc_withIgnoreDataDisks(t *testing.T) { + t.Parallel() + builderT.Test(t, builderT.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Builder: &Builder{}, + Template: testBuilderAccIgnoreDataDisks, + Check: checkIgnoreDataDisks(), + }) +} + +const testBuilderAccIgnoreDataDisks = ` +{ "builders": [{ + "type": "test", + "region": "cn-beijing", + "instance_type": "ecs.gn5-c8g1.2xlarge", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", + "io_optimized":"true", + "ssh_username":"root", + "image_name": "packer-test-ignoreDataDisks_{{timestamp}}", + "image_ignore_data_disks": true + }] +}` + +func checkIgnoreDataDisks() builderT.TestCheckFunc { + return func(artifacts []packer.Artifact) error { + if len(artifacts) > 1 { + return fmt.Errorf("more than 1 artifact") + } + + // Get the actual *Artifact pointer so we can access the AMIs directly + artifactRaw := artifacts[0] + artifact, ok := artifactRaw.(*Artifact) + if !ok { + return fmt.Errorf("unknown artifact: %#v", artifactRaw) + } + imageId := artifact.AlicloudImages[defaultTestRegion] + + // describe the image, get block devices with a snapshot + client, _ := testAliyunClient() + + describeImagesRequest := ecs.CreateDescribeImagesRequest() + describeImagesRequest.RegionId = defaultTestRegion + describeImagesRequest.ImageId = imageId + imagesResponse, err := client.DescribeImages(describeImagesRequest) + if err != nil { + return fmt.Errorf("describe images failed due to %s", err) + } + + if len(imagesResponse.Images.Image) == 0 { + return fmt.Errorf("image %s generated can not be found", imageId) + } + + image := imagesResponse.Images.Image[0] + if len(image.DiskDeviceMappings.DiskDeviceMapping) != 1 { + return fmt.Errorf("image %s should only contain one disks", imageId) + } + + return nil + } +} + func TestBuilderAcc_windows(t *testing.T) { t.Parallel() builderT.Test(t, builderT.TestCase{ @@ -234,7 +297,7 @@ const testBuilderAccRegionCopy = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_name": "packer-test-regionCopy_{{timestamp}}", @@ -338,7 +401,7 @@ const testBuilderAccForceDelete = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_force_delete": "%s", @@ -366,7 +429,7 @@ const testBuilderAccSharing = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_name": "packer-test-ECSImageSharing_{{timestamp}}", @@ -461,7 +524,7 @@ const testBuilderAccForceDeleteSnapshot = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_force_delete_snapshots": "%s", @@ -513,7 +576,7 @@ const testBuilderAccImageTags = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "ssh_username": "root", "io_optimized":"true", "image_name": "packer-test-imageTags_{{timestamp}}", @@ -627,7 +690,7 @@ const testBuilderAccDataDiskEncrypted = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_name": "packer-test-dataDiskEncrypted_{{timestamp}}", @@ -744,7 +807,7 @@ const testBuilderAccSystemDiskEncrypted = ` "type": "test", "region": "cn-beijing", "instance_type": "ecs.n1.tiny", - "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", + "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", "io_optimized":"true", "ssh_username":"root", "image_name": "packer-test_{{timestamp}}", diff --git a/builder/alicloud/ecs/client.go b/builder/alicloud/ecs/client.go index 5a8d436a9..ac599049f 100644 --- a/builder/alicloud/ecs/client.go +++ b/builder/alicloud/ecs/client.go @@ -247,6 +247,32 @@ func (c *ClientWrapper) WaitForImageStatus(regionId string, imageId string, expe }) } +func (c *ClientWrapper) WaitForSnapshotStatus(regionId string, snapshotId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) { + return c.WaitForExpected(&WaitForExpectArgs{ + RequestFunc: func() (responses.AcsResponse, error) { + request := ecs.CreateDescribeSnapshotsRequest() + request.RegionId = regionId + request.SnapshotIds = fmt.Sprintf("[\"%s\"]", snapshotId) + return c.DescribeSnapshots(request) + }, + EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { + if err != nil { + return WaitForExpectToRetry + } + + snapshotsResponse := response.(*ecs.DescribeSnapshotsResponse) + snapshots := snapshotsResponse.Snapshots.Snapshot + for _, snapshot := range snapshots { + if snapshot.Status == expectedStatus { + return WaitForExpectSuccess + } + } + return WaitForExpectToRetry + }, + RetryTimeout: timeout, + }) +} + type EvalErrorType bool const ( diff --git a/builder/alicloud/ecs/step_create_snapshot.go b/builder/alicloud/ecs/step_create_snapshot.go index 339fbef83..c9e934d60 100644 --- a/builder/alicloud/ecs/step_create_snapshot.go +++ b/builder/alicloud/ecs/step_create_snapshot.go @@ -3,9 +3,9 @@ package ecs import ( "context" "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" "time" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -36,9 +36,6 @@ func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.St return halt(state, err, "Unable to find system disk of instance") } - // Create the alicloud snapshot - ui.Say(fmt.Sprintf("Creating snapshot from system disk: %s", disks[0].DiskId)) - createSnapshotRequest := ecs.CreateCreateSnapshotRequest() createSnapshotRequest.DiskId = disks[0].DiskId snapshot, err := client.CreateSnapshot(createSnapshotRequest) @@ -46,44 +43,20 @@ func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.St return halt(state, err, "Error creating snapshot") } - _, err = client.WaitForExpected(&WaitForExpectArgs{ - RequestFunc: func() (responses.AcsResponse, error) { - request := ecs.CreateDescribeSnapshotsRequest() - request.RegionId = config.AlicloudRegion - request.SnapshotIds = snapshot.SnapshotId - return client.DescribeSnapshots(request) - }, - EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { - if err != nil { - return WaitForExpectToRetry - } - - snapshotsResponse := response.(*ecs.DescribeSnapshotsResponse) - snapshots := snapshotsResponse.Snapshots.Snapshot - for _, snapshot := range snapshots { - if snapshot.Status == SnapshotStatusAccomplished { - return WaitForExpectSuccess - } - } - return WaitForExpectToRetry - }, - RetryTimeout: time.Duration(s.WaitSnapshotReadyTimeout) * time.Second, - }) + // Create the alicloud snapshot + ui.Say(fmt.Sprintf("Creating snapshot from system disk %s: %s", disks[0].DiskId, snapshot.SnapshotId)) + snapshotsResponse, err := client.WaitForSnapshotStatus(config.AlicloudRegion, snapshot.SnapshotId, SnapshotStatusAccomplished, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second) if err != nil { + _, ok := err.(errors.Error) + if ok { + return halt(state, err, "Error querying created snapshot") + } + return halt(state, err, "Timeout waiting for snapshot to be created") } - describeSnapshotsRequest := ecs.CreateDescribeSnapshotsRequest() - describeSnapshotsRequest.RegionId = config.AlicloudRegion - describeSnapshotsRequest.SnapshotIds = snapshot.SnapshotId - - snapshotsResponse, err := client.DescribeSnapshots(describeSnapshotsRequest) - if err != nil { - return halt(state, err, "Error querying created snapshot") - } - - snapshots := snapshotsResponse.Snapshots.Snapshot + snapshots := snapshotsResponse.(*ecs.DescribeSnapshotsResponse).Snapshots.Snapshot if len(snapshots) == 0 { return halt(state, err, "Unable to find created snapshot") } From ddb4d77dc86b1305d8e1f0f01c9c4653b18b3c03 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 11 Jun 2019 11:09:22 +0200 Subject: [PATCH 20/51] Update packer/core.go remove commented log line --- packer/core.go | 1 - 1 file changed, 1 deletion(-) diff --git a/packer/core.go b/packer/core.go index d4d004649..4d01a846c 100644 --- a/packer/core.go +++ b/packer/core.go @@ -390,7 +390,6 @@ func (c *Core) init() error { } for _, v := range c.Template.SensitiveVariables { - // log.Printf("k is %#v, v is %#v", k, v) secret := ctx.UserVariables[v.Key] c.secrets = append(c.secrets, secret) } From 87b44a58798c78999f11582946e780ff00d0925c Mon Sep 17 00:00:00 2001 From: Gennady Lipenkov Date: Thu, 6 Jun 2019 16:41:58 +0300 Subject: [PATCH 21/51] Bulk fixes and enhancements 1) graceful shutdown instance 2) support metadata from file 3) support preemptible instance --- builder/yandex/config.go | 9 + builder/yandex/config_test.go | 37 +- builder/yandex/step_create_instance.go | 20 +- builder/yandex/step_create_instance_test.go | 125 ++ builder/yandex/step_teardown_instance.go | 15 +- .../stretchr/testify/require/doc.go | 28 + .../testify/require/forward_requirements.go | 16 + .../stretchr/testify/require/require.go | 1227 +++++++++++++++++ .../stretchr/testify/require/require.go.tmpl | 6 + .../testify/require/require_forward.go | 957 +++++++++++++ .../testify/require/require_forward.go.tmpl | 5 + .../stretchr/testify/require/requirements.go | 29 + vendor/modules.txt | 1 + 13 files changed, 2467 insertions(+), 8 deletions(-) create mode 100644 builder/yandex/step_create_instance_test.go create mode 100644 vendor/github.com/stretchr/testify/require/doc.go create mode 100644 vendor/github.com/stretchr/testify/require/forward_requirements.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/requirements.go diff --git a/builder/yandex/config.go b/builder/yandex/config.go index c725aba4c..813f161d4 100644 --- a/builder/yandex/config.go +++ b/builder/yandex/config.go @@ -44,7 +44,9 @@ type Config struct { InstanceName string `mapstructure:"instance_name"` Labels map[string]string `mapstructure:"labels"` PlatformID string `mapstructure:"platform_id"` + Preemptible bool `mapstructure:"preemptible"` Metadata map[string]string `mapstructure:"metadata"` + MetadataFromFile map[string]string `mapstructure:"metadata_from_file"` SerialLogFile string `mapstructure:"serial_log_file"` SourceImageFamily string `mapstructure:"source_image_family"` SourceImageFolderID string `mapstructure:"source_image_folder_id"` @@ -192,6 +194,13 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs, errors.New("a folder_id must be specified")) } + for key, file := range c.MetadataFromFile { + if _, err := os.Stat(file); err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("cannot access file '%s' with content for value of metadata key '%s': %s", file, key, err)) + } + } + if c.StateTimeout == 0 { c.StateTimeout = 5 * time.Minute } diff --git a/builder/yandex/config_test.go b/builder/yandex/config_test.go index e526f6f8c..682a26211 100644 --- a/builder/yandex/config_test.go +++ b/builder/yandex/config_test.go @@ -5,15 +5,16 @@ import ( "os" "strings" "testing" + + "github.com/stretchr/testify/require" ) const TestServiceAccountKeyFile = "./test_data/fake-sa-key.json" func TestConfigPrepare(t *testing.T) { tf, err := ioutil.TempFile("", "packer") - if err != nil { - t.Fatalf("err: %s", err) - } + require.NoError(t, err, "create temporary file failed") + defer os.Remove(tf.Name()) tf.Close() @@ -144,6 +145,25 @@ func TestConfigPrepare(t *testing.T) { } } +func TestConfigPrepareStartupScriptFile(t *testing.T) { + config := testConfig(t) + + config["metadata"] = map[string]string{ + "key": "value", + } + + config["metadata_from_file"] = map[string]string{ + "key": "file_not_exist", + } + + _, _, errs := NewConfig(config) + + if errs == nil || !strings.Contains(errs.Error(), "cannot access file 'file_not_exist' with content for value of metadata "+ + "key 'key': stat file_not_exist: no such file or directory") { + t.Fatalf("should error: metadata_from_file") + } +} + func TestConfigDefaults(t *testing.T) { cases := []struct { Read func(c *Config) interface{} @@ -212,6 +232,17 @@ func testConfig(t *testing.T) (config map[string]interface{}) { return config } +func testConfigStruct(t *testing.T) *Config { + raw := testConfig(t) + + c, warns, errs := NewConfig(raw) + + require.True(t, len(warns) == 0, "bad: %#v", warns) + require.NoError(t, errs, "should not have error: %s", errs) + + return c +} + func testConfigErr(t *testing.T, warns []string, err error, extra string) { if len(warns) > 0 { t.Fatalf("bad: %#v", warns) diff --git a/builder/yandex/step_create_instance.go b/builder/yandex/step_create_instance.go index 993c53152..538d78ad7 100644 --- a/builder/yandex/step_create_instance.go +++ b/builder/yandex/step_create_instance.go @@ -146,7 +146,10 @@ func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) // Create an instance based on the configuration ui.Say("Creating instance...") - instanceMetadata := config.createInstanceMetadata(string(config.Communicator.SSHPublicKey)) + instanceMetadata, err := config.createInstanceMetadata(string(config.Communicator.SSHPublicKey)) + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error preparing instance metadata: %s", err)) + } // TODO make part metadata prepare process if config.UseIPv6 { @@ -164,6 +167,9 @@ runcmd: Labels: config.Labels, ZoneId: config.Zone, PlatformId: config.PlatformID, + SchedulingPolicy: &compute.SchedulingPolicy{ + Preemptible: config.Preemptible, + }, ResourcesSpec: &compute.ResourcesSpec{ Memory: toBytes(config.InstanceMemory), Cores: int64(config.InstanceCores), @@ -336,10 +342,18 @@ func (s *stepCreateInstance) writeSerialLogFile(ctx context.Context, state multi return nil } -func (c *Config) createInstanceMetadata(sshPublicKey string) map[string]string { +func (c *Config) createInstanceMetadata(sshPublicKey string) (map[string]string, error) { instanceMetadata := make(map[string]string) // Copy metadata from config. + for k, file := range c.MetadataFromFile { + contents, err := ioutil.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("error while read file '%s' with content for value of metadata key '%s': %s", file, k, err) + } + instanceMetadata[k] = string(contents) + } + for k, v := range c.Metadata { instanceMetadata[k] = v } @@ -353,5 +367,5 @@ func (c *Config) createInstanceMetadata(sshPublicKey string) map[string]string { instanceMetadata[sshMetaKey] = sshKeys } - return instanceMetadata + return instanceMetadata, nil } diff --git a/builder/yandex/step_create_instance_test.go b/builder/yandex/step_create_instance_test.go new file mode 100644 index 000000000..af8ee67af --- /dev/null +++ b/builder/yandex/step_create_instance_test.go @@ -0,0 +1,125 @@ +package yandex + +import ( + "bytes" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testMetadataFileContent = `meta data value` + +func testMetadataFile(t *testing.T) string { + tf, err := ioutil.TempFile("", "packer") + require.NoErrorf(t, err, "create temporary file failed") + defer tf.Close() + + _, err = tf.Write([]byte(testMetadataFileContent)) + require.NoErrorf(t, err, "write to file failed") + + return tf.Name() +} + +func TestCreateInstanceMetadata(t *testing.T) { + state := testState(t) + c := state.Get("config").(*Config) + pubKey := "abcdefgh123456789" + + // create our metadata + metadata, err := c.createInstanceMetadata(pubKey) + require.NoError(t, err, "Metadata creation should have succeeded.") + + // ensure our pubKey is listed + assert.True(t, strings.Contains(metadata["ssh-keys"], pubKey), "Instance metadata should contain provided public key") +} + +func TestCreateInstanceMetadata_noPublicKey(t *testing.T) { + state := testState(t) + c := state.Get("config").(*Config) + sshKeys := c.Metadata["sshKeys"] + + // create our metadata + metadata, err := c.createInstanceMetadata("") + + // ensure the metadata created without err + require.NoError(t, err, "Metadata creation should have succeeded.") + + // ensure the ssh metadata hasn't changed + assert.Equal(t, metadata["sshKeys"], sshKeys, "Instance metadata should not have been modified") +} + +func TestCreateInstanceMetadata_fromFile(t *testing.T) { + state := testState(t) + metadataFile := testMetadataFile(t) + defer os.Remove(metadataFile) + + state.Put("config", testConfigStruct(t)) + c := state.Get("config").(*Config) + c.MetadataFromFile = map[string]string{ + "test-key": metadataFile, + } + + // create our metadata + metadata, err := c.createInstanceMetadata("") + require.NoError(t, err, "Metadata creation should have succeeded.") + + // ensure the metadata from file hasn't changed + assert.Equal(t, testMetadataFileContent, metadata["test-key"], "Instance metadata should not have been modified") +} + +func TestCreateInstanceMetadata_fromFileAndTemplate(t *testing.T) { + state := testState(t) + metadataFile := testMetadataFile(t) + defer os.Remove(metadataFile) + + state.Put("config", testConfigStruct(t)) + c := state.Get("config").(*Config) + c.MetadataFromFile = map[string]string{ + "test-key": metadataFile, + } + c.Metadata = map[string]string{ + "test-key": "override value", + "test-key-2": "new-value", + } + + // create our metadata + metadata, err := c.createInstanceMetadata("") + require.NoError(t, err, "Metadata creation should have succeeded.") + + // ensure the metadata merged + assert.Equal(t, "override value", metadata["test-key"], "Instance metadata should not have been modified") + assert.Equal(t, "new-value", metadata["test-key-2"], "Instance metadata should not have been modified") +} + +func TestCreateInstanceMetadata_fromNotExistFile(t *testing.T) { + state := testState(t) + metadataFile := "not-exist-file" + + state.Put("config", testConfigStruct(t)) + c := state.Get("config").(*Config) + c.MetadataFromFile = map[string]string{ + "test-key": metadataFile, + } + + // create our metadata + _, err := c.createInstanceMetadata("") + + assert.True(t, err != nil, "Metadata creation should have an error.") +} + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("config", testConfigStruct(t)) + state.Put("hook", &packer.MockHook{}) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/builder/yandex/step_teardown_instance.go b/builder/yandex/step_teardown_instance.go index eb901094a..ee3d0dbd3 100644 --- a/builder/yandex/step_teardown_instance.go +++ b/builder/yandex/step_teardown_instance.go @@ -20,11 +20,22 @@ func (s *stepTeardownInstance) Run(ctx context.Context, state multistep.StateBag instanceID := state.Get("instance_id").(string) - ui.Say("Deleting instance...") + ui.Say("Stopping instance...") ctx, cancel := context.WithTimeout(ctx, c.StateTimeout) defer cancel() + op, err := sdk.WrapOperation(sdk.Compute().Instance().Stop(ctx, &compute.StopInstanceRequest{ + InstanceId: instanceID, + })) + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error stopping instance: %s", err)) + } + err = op.Wait(ctx) + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error stopping instance: %s", err)) + } - op, err := sdk.WrapOperation(sdk.Compute().Instance().Delete(ctx, &compute.DeleteInstanceRequest{ + ui.Say("Deleting instance...") + op, err = sdk.WrapOperation(sdk.Compute().Instance().Delete(ctx, &compute.DeleteInstanceRequest{ InstanceId: instanceID, })) if err != nil { diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 000000000..169de3922 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,28 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// Example Usage +// +// The following is a complete example using require in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 000000000..ac71d4058 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -0,0 +1,16 @@ +package require + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 000000000..535f29349 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,1227 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if assert.Condition(t, comp, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if assert.Conditionf(t, comp, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if assert.Contains(t, s, contains, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if assert.Containsf(t, s, contains, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if assert.DirExists(t, path, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if assert.DirExistsf(t, path, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if assert.ElementsMatchf(t, listA, listB, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if assert.Empty(t, object, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if assert.Emptyf(t, object, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if assert.Equal(t, expected, actual, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if assert.EqualError(t, theError, errString, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { + if assert.EqualErrorf(t, theError, errString, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if assert.EqualValues(t, expected, actual, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123)) +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if assert.EqualValuesf(t, expected, actual, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if assert.Equalf(t, expected, actual, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if assert.Error(t, err, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if assert.Errorf(t, err, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if assert.Exactly(t, expected, actual, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if assert.Exactlyf(t, expected, actual, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if assert.Fail(t, failureMessage, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if assert.FailNow(t, failureMessage, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if assert.FailNowf(t, failureMessage, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if assert.Failf(t, failureMessage, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if assert.False(t, value, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) { + if assert.Falsef(t, value, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if assert.FileExists(t, path, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if assert.FileExistsf(t, path, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if assert.Implements(t, interfaceObject, object, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if assert.Implementsf(t, interfaceObject, object, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, (22 / 7.0), 0.01) +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if assert.InDeltaf(t, expected, actual, delta, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if assert.IsType(t, expectedType, object, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if assert.IsTypef(t, expectedType, object, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if assert.JSONEq(t, expected, actual, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if assert.JSONEqf(t, expected, actual, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if assert.Len(t, object, length, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { + if assert.Lenf(t, object, length, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if assert.Nil(t, object, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if assert.Nilf(t, object, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if assert.NoError(t, err, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { + if assert.NoErrorf(t, err, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if assert.NotContains(t, s, contains, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if assert.NotContainsf(t, s, contains, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if assert.NotEmpty(t, object, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if assert.NotEmptyf(t, object, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if assert.NotEqual(t, expected, actual, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if assert.NotEqualf(t, expected, actual, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if assert.NotNil(t, object, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if assert.NotNilf(t, object, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if assert.NotPanics(t, f, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if assert.NotPanicsf(t, f, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if assert.NotRegexp(t, rx, str, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if assert.NotRegexpf(t, rx, str, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if assert.NotSubset(t, list, subset, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if assert.NotSubsetf(t, list, subset, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if assert.NotZero(t, i, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if assert.NotZerof(t, i, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if assert.Panics(t, f, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if assert.PanicsWithValuef(t, expected, f, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if assert.Panicsf(t, f, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if assert.Regexp(t, rx, str, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if assert.Regexpf(t, rx, str, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if assert.Subset(t, list, subset, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if assert.Subsetf(t, list, subset, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if assert.True(t, value, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) { + if assert.Truef(t, value, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if assert.Zero(t, i, msgAndArgs...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if assert.Zerof(t, i, msg, args...) { + return + } + if h, ok := t.(tHelper); ok { + h.Helper() + } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl new file mode 100644 index 000000000..6ffc751b5 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{.Comment}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } + if h, ok := t.(tHelper); ok { h.Helper() } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 000000000..9fe41dbdc --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,957 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123)) +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Error(a.t, err, msgAndArgs...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123)) +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject)) +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, (22 / 7.0), 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01) +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl new file mode 100644 index 000000000..54124df1d --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + if h, ok := a.t.(tHelper); ok { h.Helper() } + {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 000000000..6b85c5ece --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -0,0 +1,29 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +type tHelper interface { + Helper() +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) + +//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs diff --git a/vendor/modules.txt b/vendor/modules.txt index 0af5d43ae..951aa0239 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -397,6 +397,7 @@ github.com/scaleway/scaleway-cli/pkg/sshcommand github.com/sirupsen/logrus # github.com/stretchr/testify v1.3.0 github.com/stretchr/testify/assert +github.com/stretchr/testify/require # github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile From 3ac82de00ca5bc274fb739873ee33257430d22d4 Mon Sep 17 00:00:00 2001 From: Gennady Lipenkov Date: Tue, 11 Jun 2019 12:30:01 +0300 Subject: [PATCH 22/51] Update doc for yandex builder --- website/source/docs/builders/yandex.html.md | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/website/source/docs/builders/yandex.html.md b/website/source/docs/builders/yandex.html.md index 24a292271..e28cde4e7 100644 --- a/website/source/docs/builders/yandex.html.md +++ b/website/source/docs/builders/yandex.html.md @@ -72,19 +72,15 @@ can be configured for this builder. ### Optional: -- `endpoint` (string) - Non standard api endpoint URL. - -- `instance_cores` (number) - The number of cores available to the instance. - -- `instance_mem_gb` (number) - The amount of memory available to the instance, specified in gigabytes. - - `disk_name` (string) - The name of the disk, if unset the instance name will be used. - + - `disk_size_gb` (number) - The size of the disk in GB. This defaults to `10`, which is 10GB. - `disk_type` (string) - Specify disk type for the launched instance. Defaults to `network-hdd`. +- `endpoint` (string) - Non standard api endpoint URL. + - `image_description` (string) - The description of the resulting image. - `image_family` (string) - The family name of the resulting image. @@ -97,16 +93,22 @@ can be configured for this builder. - `image_product_ids` (list) - License IDs that indicate which licenses are attached to resulting image. +- `instance_cores` (number) - The number of cores available to the instance. + +- `instance_mem_gb` (number) - The amount of memory available to the instance, specified in gigabytes. + - `instance_name` (string) - The name assigned to the instance. - `labels` (object of key/value strings) - Key/value pair labels to apply to the launched instance. -- `platform_id` (string) - Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`. - - `metadata` (object of key/value strings) - Metadata applied to the launched instance. +- `platform_id` (string) - Identifier of the hardware platform configuration for the instance. This defaults to `standard-v1`. + +- `preemptible` (boolean) - Launch a preemptible instance. This defaults to `false`. + - `serial_log_file` (string) - File path to save serial port output of the launched instance. - `service_account_key_file` (string) - Path to file with Service Account key in json format. This @@ -122,6 +124,9 @@ can be configured for this builder. the new image from. The image family always returns its latest image that is not deprecated. Example: `ubuntu-1804-lts`. +- `state_timeout` (string) - The time to wait for instance state changes. + Defaults to `5m`. + - `subnet_id` (string) - The Yandex VPC subnet id to use for the launched instance. Note, the zone of the subnet must match the `zone` in which the VM is launched. @@ -136,7 +141,4 @@ can be configured for this builder. created. This defaults to `false`, or not enabled. -> **Note:** ~> Usage of IPv6 will be available in the future. -- `state_timeout` (string) - The time to wait for instance state changes. - Defaults to `5m`. - - `zone` (string) - The name of the zone to launch the instance. This defaults to `ru-central1-a`. From 36e4eaff991eb0bcb9bb6bd5c2244ce670a8768d Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 11 Jun 2019 12:20:00 +0200 Subject: [PATCH 23/51] document retry.Backoff better --- common/retry/retry.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/common/retry/retry.go b/common/retry/retry.go index 2b1a4f231..3189c4c97 100644 --- a/common/retry/retry.go +++ b/common/retry/retry.go @@ -76,12 +76,23 @@ func (cfg Config) Run(ctx context.Context, fn func(context.Context) error) error } } +// Backoff is a self contained backoff time calculator. This struct should be +// passed around as a copy as it changes its own fields upon any Backoff call. +// Backoff is not thread safe. For now only a Linear backoff call is +// implemented and the Exponential call will be implemented when needed. type Backoff struct { + // Initial time to wait. A Backoff call will change this value. InitialBackoff time.Duration - MaxBackoff time.Duration - Multiplier float64 + // Maximum time returned. + MaxBackoff time.Duration + // For a Linear backoff, InitialBackoff will be multiplied by Multiplier + // after each call. + Multiplier float64 } +// Linear Backoff returns a linearly increasing Duration. +// n = n * Multiplier. +// the first value of n is InitialBackoff. n is maxed by MaxBackoff. func (lb *Backoff) Linear() time.Duration { wait := lb.InitialBackoff lb.InitialBackoff = time.Duration(lb.Multiplier * float64(lb.InitialBackoff)) @@ -90,3 +101,8 @@ func (lb *Backoff) Linear() time.Duration { } return wait } + +// Exponential backoff panics: not implemented, yet. +func (lb *Backoff) Exponential() time.Duration { + panic("not implemented, yet") +} From d92e82c43fcc2fbd5007d9a88174ef76f8207a9a Mon Sep 17 00:00:00 2001 From: Pratyush Singhal Date: Tue, 11 Jun 2019 15:58:02 +0530 Subject: [PATCH 24/51] refactor: replace the maxBackoff from 30 nanosecond to 30 seconds Co-Authored-By: Adrien Delorme --- builder/amazon/common/step_key_pair.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon/common/step_key_pair.go b/builder/amazon/common/step_key_pair.go index a5a2fffd8..631226ab3 100644 --- a/builder/amazon/common/step_key_pair.go +++ b/builder/amazon/common/step_key_pair.go @@ -60,7 +60,7 @@ func (s *StepKeyPair) Run(ctx context.Context, state multistep.StateBag) multist ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName)) err := retry.Config{ Tries: 11, - RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30, Multiplier: 2}).Linear, + RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear, }.Run(ctx, func(ctx context.Context) error { var err error keyResp, err = ec2conn.CreateKeyPair(&ec2.CreateKeyPairInput{ From 98206d59d764025906589c3eb0ba644fd7688a56 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 11 Jun 2019 12:37:52 +0200 Subject: [PATCH 25/51] aws: step_create_tags make the max waiting time 30s and not 30ns --- builder/amazon/common/step_create_tags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/amazon/common/step_create_tags.go b/builder/amazon/common/step_create_tags.go index 337bc1daa..68adc9878 100644 --- a/builder/amazon/common/step_create_tags.go +++ b/builder/amazon/common/step_create_tags.go @@ -102,7 +102,7 @@ func (s *StepCreateTags) Run(ctx context.Context, state multistep.StateBag) mult } return false }, - RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30, Multiplier: 2}).Linear, + RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 30 * time.Second, Multiplier: 2}).Linear, }.Run(ctx, func(ctx context.Context) error { // Tag images and snapshots From 39cfacd5fa6a7317a4c9f8658a1753812cc48bc0 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 11 Jun 2019 12:41:21 +0200 Subject: [PATCH 26/51] Backoff.Linear: panic when InitialBackoff > MaxBackoff this probably means there's a configuration issue. Since this struct is mainly set manually from code, I think it is okay to panic here. --- common/retry/retry.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/retry/retry.go b/common/retry/retry.go index 3189c4c97..38e2ba88d 100644 --- a/common/retry/retry.go +++ b/common/retry/retry.go @@ -94,6 +94,9 @@ type Backoff struct { // n = n * Multiplier. // the first value of n is InitialBackoff. n is maxed by MaxBackoff. func (lb *Backoff) Linear() time.Duration { + if lb.InitialBackoff > lb.MaxBackoff { + panic("InitialBackoff > MaxBackoff, did you forgot setting the seconds ?") + } wait := lb.InitialBackoff lb.InitialBackoff = time.Duration(lb.Multiplier * float64(lb.InitialBackoff)) if lb.MaxBackoff != 0 && lb.InitialBackoff > lb.MaxBackoff { From ca33f8bc5c23aea3e5025001b0e179b88c8eccc7 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 11 Jun 2019 12:53:06 +0200 Subject: [PATCH 27/51] Revert "Backoff.Linear: panic when InitialBackoff > MaxBackoff" This reverts commit 39cfacd5fa6a7317a4c9f8658a1753812cc48bc0. --- common/retry/retry.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/retry/retry.go b/common/retry/retry.go index 38e2ba88d..3189c4c97 100644 --- a/common/retry/retry.go +++ b/common/retry/retry.go @@ -94,9 +94,6 @@ type Backoff struct { // n = n * Multiplier. // the first value of n is InitialBackoff. n is maxed by MaxBackoff. func (lb *Backoff) Linear() time.Duration { - if lb.InitialBackoff > lb.MaxBackoff { - panic("InitialBackoff > MaxBackoff, did you forgot setting the seconds ?") - } wait := lb.InitialBackoff lb.InitialBackoff = time.Duration(lb.Multiplier * float64(lb.InitialBackoff)) if lb.MaxBackoff != 0 && lb.InitialBackoff > lb.MaxBackoff { From 529dff0abbc7fc33f42541b7b422caf7a4bdf452 Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Tue, 11 Jun 2019 16:36:16 +0530 Subject: [PATCH 28/51] refactor: add error handling in createInstanceMetadata method Signed-off-by: Pratyush singhal --- builder/googlecompute/step_create_instance.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 0688d6280..9cd26863a 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -16,9 +16,11 @@ type StepCreateInstance struct { Debug bool } -func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) (map[string]string, error) { +func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string, state multistep.StateBag) (map[string]string, error) { instanceMetadata := make(map[string]string) var err error + ui := state.Get("ui").(packer.Ui) + // Copy metadata from config. for k, v := range c.Metadata { instanceMetadata[k] = v @@ -40,6 +42,11 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) if c.StartupScriptFile != "" { var content []byte content, err = ioutil.ReadFile(c.StartupScriptFile) + if err != nil { + err = fmt.Errorf("Error reading startup script file: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + } instanceMetadata[StartupWrappedScriptKey] = string(content) } else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists { instanceMetadata[StartupWrappedScriptKey] = wrappedStartupScript @@ -49,6 +56,11 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) for key, value := range c.MetadataFiles { var content []byte content, err = ioutil.ReadFile(value) + if err != nil { + err = fmt.Errorf("Error getting %s metadata from %s: %s", key, value, err) + state.Put("error", err) + ui.Error(err.Error()) + } instanceMetadata[key] = string(content) } @@ -105,7 +117,7 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) var errCh <-chan error var metadata map[string]string - metadata, err = c.createInstanceMetadata(sourceImage, string(c.Comm.SSHPublicKey)) + metadata, err = c.createInstanceMetadata(sourceImage, string(c.Comm.SSHPublicKey), state) errCh, err = d.RunInstance(&InstanceConfig{ AcceleratorType: c.AcceleratorType, AcceleratorCount: c.AcceleratorCount, From 99a3e9cf0a1f01da0fbc7b2ebbd532660365d71e Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Tue, 11 Jun 2019 16:47:16 +0530 Subject: [PATCH 29/51] chore: update tests for createInstaceMetadata Signed-off-by: Pratyush singhal --- builder/googlecompute/step_create_instance_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/googlecompute/step_create_instance_test.go b/builder/googlecompute/step_create_instance_test.go index e56159138..8468fd92e 100644 --- a/builder/googlecompute/step_create_instance_test.go +++ b/builder/googlecompute/step_create_instance_test.go @@ -303,7 +303,7 @@ func TestCreateInstanceMetadata(t *testing.T) { key := "abcdefgh12345678" // create our metadata - metadata, err := c.createInstanceMetadata(image, key) + metadata, err := c.createInstanceMetadata(image, key, state) assert.True(t, err == nil, "Metadata creation should have succeeded.") @@ -318,7 +318,7 @@ func TestCreateInstanceMetadata_noPublicKey(t *testing.T) { sshKeys := c.Metadata["sshKeys"] // create our metadata - metadata, err := c.createInstanceMetadata(image, "") + metadata, err := c.createInstanceMetadata(image, "", state) assert.True(t, err == nil, "Metadata creation should have succeeded.") From 4a369b4ef1284f072b36264b0c4f7ae842a958a4 Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Tue, 11 Jun 2019 17:45:30 +0530 Subject: [PATCH 30/51] chore: add test for MetadataFiles option Signed-off-by: Pratyush singhal --- builder/googlecompute/config_test.go | 18 +++++++++++++++++- .../googlecompute/step_create_instance_test.go | 17 +++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/builder/googlecompute/config_test.go b/builder/googlecompute/config_test.go index 4db82040b..f562d4b8f 100644 --- a/builder/googlecompute/config_test.go +++ b/builder/googlecompute/config_test.go @@ -431,7 +431,8 @@ func testConfig(t *testing.T) (config map[string]interface{}, tempAccountFile st "image_licenses": []string{ "test-license", }, - "zone": "us-east1-a", + "metadata_files": map[string]string{}, + "zone": "us-east1-a", } return config, tempAccountFile @@ -484,6 +485,21 @@ func testAccountFile(t *testing.T) string { return tf.Name() } +const testMetadataFileContent = `testMetadata` + +func testMetadataFile(t *testing.T) string { + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer tf.Close() + if _, err := tf.Write([]byte(testMetadataFileContent)); err != nil { + t.Fatalf("err: %s", err) + } + + return tf.Name() +} + // This is just some dummy data that doesn't actually work (it was revoked // a long time ago). const testAccountContent = `{}` diff --git a/builder/googlecompute/step_create_instance_test.go b/builder/googlecompute/step_create_instance_test.go index 8468fd92e..8f765d34f 100644 --- a/builder/googlecompute/step_create_instance_test.go +++ b/builder/googlecompute/step_create_instance_test.go @@ -325,3 +325,20 @@ func TestCreateInstanceMetadata_noPublicKey(t *testing.T) { // ensure the ssh metadata hasn't changed assert.Equal(t, metadata["sshKeys"], sshKeys, "Instance metadata should not have been modified") } + +func TestCreateInstanceMetadata_metadataFile(t *testing.T) { + state := testState(t) + c := state.Get("config").(*Config) + image := StubImage("test-image", "test-project", []string{}, 100) + content := testMetadataFileContent + fileName := testMetadataFile(t) + c.MetadataFiles["user-data"] = fileName + + // create our metadata + metadata, err := c.createInstanceMetadata(image, "", state) + + assert.True(t, err == nil, "Metadata creation should have succeeded.") + + // ensure the user-data key in metadata is updated with file content + assert.Equal(t, metadata["user-data"], content, "user-data field of the instance metadata should have been updated.") +} From 9c7e6a0aec3acc9cc2410dc6ca92f6c025d29d82 Mon Sep 17 00:00:00 2001 From: Gennady Lipenkov Date: Tue, 11 Jun 2019 16:49:34 +0300 Subject: [PATCH 31/51] fix test --- builder/yandex/config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/yandex/config_test.go b/builder/yandex/config_test.go index 682a26211..df8e7a0bb 100644 --- a/builder/yandex/config_test.go +++ b/builder/yandex/config_test.go @@ -158,8 +158,8 @@ func TestConfigPrepareStartupScriptFile(t *testing.T) { _, _, errs := NewConfig(config) - if errs == nil || !strings.Contains(errs.Error(), "cannot access file 'file_not_exist' with content for value of metadata "+ - "key 'key': stat file_not_exist: no such file or directory") { + if errs == nil || !strings.Contains(errs.Error(), "cannot access file 'file_not_exist' with content "+ + "for value of metadata key 'key':") { t.Fatalf("should error: metadata_from_file") } } From 6ce6bd8ad33093df6b06e3032d2906b6d70882ff Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Tue, 11 Jun 2019 20:08:03 +0530 Subject: [PATCH 32/51] refactor: add multiError in createInstanceMetadata method to capture multiple errors Signed-off-by: Pratyush singhal --- builder/googlecompute/step_create_instance.go | 21 ++++++++++--------- .../step_create_instance_test.go | 6 +++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index 9cd26863a..ec056d596 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -16,10 +16,10 @@ type StepCreateInstance struct { Debug bool } -func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string, state multistep.StateBag) (map[string]string, error) { +func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) (map[string]string, *packer.MultiError) { instanceMetadata := make(map[string]string) var err error - ui := state.Get("ui").(packer.Ui) + var errs *packer.MultiError // Copy metadata from config. for k, v := range c.Metadata { @@ -43,9 +43,7 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string, var content []byte content, err = ioutil.ReadFile(c.StartupScriptFile) if err != nil { - err = fmt.Errorf("Error reading startup script file: %s", err) - state.Put("error", err) - ui.Error(err.Error()) + errs = packer.MultiErrorAppend(errs, err) } instanceMetadata[StartupWrappedScriptKey] = string(content) } else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists { @@ -57,9 +55,7 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string, var content []byte content, err = ioutil.ReadFile(value) if err != nil { - err = fmt.Errorf("Error getting %s metadata from %s: %s", key, value, err) - state.Put("error", err) - ui.Error(err.Error()) + errs = packer.MultiErrorAppend(errs, err) } instanceMetadata[key] = string(content) } @@ -74,7 +70,7 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string, instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusNotDone } - return instanceMetadata, err + return instanceMetadata, errs } func getImage(c *Config, d Driver) (*Image, error) { @@ -117,7 +113,12 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) var errCh <-chan error var metadata map[string]string - metadata, err = c.createInstanceMetadata(sourceImage, string(c.Comm.SSHPublicKey), state) + metadata, errs := c.createInstanceMetadata(sourceImage, string(c.Comm.SSHPublicKey)) + if errs != nil && len(errs.Errors) > 0 { + state.Put("error", errs.Error()) + ui.Error(errs.Error()) + } + errCh, err = d.RunInstance(&InstanceConfig{ AcceleratorType: c.AcceleratorType, AcceleratorCount: c.AcceleratorCount, diff --git a/builder/googlecompute/step_create_instance_test.go b/builder/googlecompute/step_create_instance_test.go index 8f765d34f..33f0371fb 100644 --- a/builder/googlecompute/step_create_instance_test.go +++ b/builder/googlecompute/step_create_instance_test.go @@ -303,7 +303,7 @@ func TestCreateInstanceMetadata(t *testing.T) { key := "abcdefgh12345678" // create our metadata - metadata, err := c.createInstanceMetadata(image, key, state) + metadata, err := c.createInstanceMetadata(image, key) assert.True(t, err == nil, "Metadata creation should have succeeded.") @@ -318,7 +318,7 @@ func TestCreateInstanceMetadata_noPublicKey(t *testing.T) { sshKeys := c.Metadata["sshKeys"] // create our metadata - metadata, err := c.createInstanceMetadata(image, "", state) + metadata, err := c.createInstanceMetadata(image, "") assert.True(t, err == nil, "Metadata creation should have succeeded.") @@ -335,7 +335,7 @@ func TestCreateInstanceMetadata_metadataFile(t *testing.T) { c.MetadataFiles["user-data"] = fileName // create our metadata - metadata, err := c.createInstanceMetadata(image, "", state) + metadata, err := c.createInstanceMetadata(image, "") assert.True(t, err == nil, "Metadata creation should have succeeded.") From 92af5847a7de1fddb2f4051cdc3ee7ddfe1ee26d Mon Sep 17 00:00:00 2001 From: Pratyush singhal Date: Tue, 11 Jun 2019 21:01:26 +0530 Subject: [PATCH 33/51] refactor: replace *packer.MultiError from type signature of createInstanceMetadata with generic error interface Signed-off-by: Pratyush singhal --- builder/googlecompute/step_create_instance.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builder/googlecompute/step_create_instance.go b/builder/googlecompute/step_create_instance.go index ec056d596..6004c2fab 100644 --- a/builder/googlecompute/step_create_instance.go +++ b/builder/googlecompute/step_create_instance.go @@ -16,7 +16,7 @@ type StepCreateInstance struct { Debug bool } -func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) (map[string]string, *packer.MultiError) { +func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) (map[string]string, error) { instanceMetadata := make(map[string]string) var err error var errs *packer.MultiError @@ -43,7 +43,7 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) var content []byte content, err = ioutil.ReadFile(c.StartupScriptFile) if err != nil { - errs = packer.MultiErrorAppend(errs, err) + return nil, err } instanceMetadata[StartupWrappedScriptKey] = string(content) } else if wrappedStartupScript, exists := instanceMetadata[StartupScriptKey]; exists { @@ -70,7 +70,10 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string) instanceMetadata[StartupScriptStatusKey] = StartupScriptStatusNotDone } - return instanceMetadata, errs + if errs != nil && len(errs.Errors) > 0 { + return instanceMetadata, errs + } + return instanceMetadata, nil } func getImage(c *Config, d Driver) (*Image, error) { @@ -114,9 +117,10 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) var errCh <-chan error var metadata map[string]string metadata, errs := c.createInstanceMetadata(sourceImage, string(c.Comm.SSHPublicKey)) - if errs != nil && len(errs.Errors) > 0 { + if errs != nil { state.Put("error", errs.Error()) ui.Error(errs.Error()) + return multistep.ActionHalt } errCh, err = d.RunInstance(&InstanceConfig{ From 6dcff18d36bded8e21a7ccc2f598c09b5db5beee Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Tue, 11 Jun 2019 13:22:03 -0700 Subject: [PATCH 34/51] prevent nil pointer dereference by defining IsUserAuthority. This occurred as a regression when we updated the crypto library in v1.4.0 --- provisioner/ansible/provisioner.go | 1 + provisioner/inspec/provisioner.go | 1 + 2 files changed, 2 insertions(+) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 50e0702cb..378fd7beb 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -241,6 +241,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C return nil, nil }, + IsUserAuthority: func(k ssh.PublicKey) bool { return true }, } config := &ssh.ServerConfig{ diff --git a/provisioner/inspec/provisioner.go b/provisioner/inspec/provisioner.go index 15c975c20..c90b66d49 100644 --- a/provisioner/inspec/provisioner.go +++ b/provisioner/inspec/provisioner.go @@ -234,6 +234,7 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C return nil, nil }, + IsUserAuthority: func(k ssh.PublicKey) bool { return true }, } config := &ssh.ServerConfig{ From 1e5866bd2a3f8a95b43c50eee42efd4e3089f4c1 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Tue, 11 Jun 2019 14:04:36 -0700 Subject: [PATCH 35/51] apply logSecretFilter to output from ui.Say --- packer/ui.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packer/ui.go b/packer/ui.go index 2ee866d2b..0492ab314 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -236,6 +236,13 @@ func (rw *BasicUi) Say(message string) { rw.l.Lock() defer rw.l.Unlock() + // Use LogSecretFilter to scrub out sensitive variables + for s := range LogSecretFilter.s { + if s != "" { + message = strings.Replace(message, s, "", -1) + } + } + log.Printf("ui: %s", message) _, err := fmt.Fprint(rw.Writer, message+"\n") if err != nil { From df977926ba769fe4cc764042317ac0a123ed6e5a Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 12 Jun 2019 09:50:27 -0700 Subject: [PATCH 36/51] filter machine readable UI --- packer/ui.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packer/ui.go b/packer/ui.go index 0492ab314..4c0b0857a 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -336,6 +336,12 @@ func (u *MachineReadableUi) Machine(category string, args ...string) { args[i] = strings.Replace(v, ",", "%!(PACKER_COMMA)", -1) args[i] = strings.Replace(args[i], "\r", "\\r", -1) args[i] = strings.Replace(args[i], "\n", "\\n", -1) + // Use LogSecretFilter to scrub out sensitive variables + for s := range LogSecretFilter.s { + if s != "" { + args[i] = strings.Replace(args[i], s, "", -1) + } + } } argsString := strings.Join(args, ",") From 6a5db1e948a9b0b160119545e719cd6fd467eae9 Mon Sep 17 00:00:00 2001 From: "bozhi.ch" Date: Thu, 13 Jun 2019 11:17:39 +0800 Subject: [PATCH 37/51] cleanup image and snapshot if target image is still not available after timeout --- builder/alicloud/ecs/client.go | 14 ++++++++++---- builder/alicloud/ecs/step_create_image.go | 9 +++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/builder/alicloud/ecs/client.go b/builder/alicloud/ecs/client.go index ac599049f..c18a1585c 100644 --- a/builder/alicloud/ecs/client.go +++ b/builder/alicloud/ecs/client.go @@ -161,6 +161,7 @@ func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsR timeoutPoint = time.Now().Add(args.RetryTimeout) } + var lastResponse responses.AcsResponse var lastError error for i := 0; ; i++ { @@ -173,6 +174,7 @@ func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsR } response, err := args.RequestFunc() + lastResponse = response lastError = err evalResult := args.EvalFunc(response, err) @@ -180,17 +182,21 @@ func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsR return response, nil } if evalResult.stopRetry { - return nil, err + return response, err } time.Sleep(args.RetryInterval) } - if args.RetryTimeout > 0 { - return nil, fmt.Errorf("evaluate failed after %d seconds timeout with %d seconds retry interval: %s", int(args.RetryTimeout.Seconds()), int(args.RetryInterval.Seconds()), lastError) + if lastError == nil { + lastError = fmt.Errorf("") } - return nil, fmt.Errorf("evaluate failed after %d times retry with %d seconds retry interval: %s", args.RetryTimes, int(args.RetryInterval.Seconds()), lastError) + if args.RetryTimeout > 0 { + return lastResponse, fmt.Errorf("evaluate failed after %d seconds timeout with %d seconds retry interval: %s", int(args.RetryTimeout.Seconds()), int(args.RetryInterval.Seconds()), lastError) + } + + return lastResponse, fmt.Errorf("evaluate failed after %d times retry with %d seconds retry interval: %s", args.RetryTimes, int(args.RetryInterval.Seconds()), lastError) } func (c *ClientWrapper) WaitForInstanceStatus(regionId string, instanceId string, expectedStatus string) (responses.AcsResponse, error) { diff --git a/builder/alicloud/ecs/step_create_image.go b/builder/alicloud/ecs/step_create_image.go index 6b460d35b..f69842733 100644 --- a/builder/alicloud/ecs/step_create_image.go +++ b/builder/alicloud/ecs/step_create_image.go @@ -52,17 +52,18 @@ func (s *stepCreateAlicloudImage) Run(ctx context.Context, state multistep.State imageId := createImageResponse.(*ecs.CreateImageResponse).ImageId imagesResponse, err := client.WaitForImageStatus(config.AlicloudRegion, imageId, ImageStatusAvailable, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second) - if err != nil { - return halt(state, err, "Timeout waiting for image to be created") - } + // save image first for cleaning up if timeout images := imagesResponse.(*ecs.DescribeImagesResponse).Images.Image if len(images) == 0 { return halt(state, err, "Unable to find created image") } - s.image = &images[0] + if err != nil { + return halt(state, err, "Timeout waiting for image to be created") + } + var snapshotIds []string for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping { snapshotIds = append(snapshotIds, device.SnapshotId) From 6982ec796f0fb22554c06ab0eabbe792fa9d467d Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 12 Jun 2019 13:18:57 -0700 Subject: [PATCH 38/51] remove redundant error check --- provisioner/shell-local/provisioner.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/provisioner/shell-local/provisioner.go b/provisioner/shell-local/provisioner.go index 861e98427..9768f85b8 100644 --- a/provisioner/shell-local/provisioner.go +++ b/provisioner/shell-local/provisioner.go @@ -27,11 +27,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, _ packer.Communicator) error { _, retErr := sl.Run(ctx, ui, &p.config) - if retErr != nil { - return retErr - } - return nil + return retErr } func (p *Provisioner) Cancel() { From 88e5a21170d0aae3454291e34c24d941b1af561c Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 13 Jun 2019 10:48:13 -0700 Subject: [PATCH 39/51] make sure machine readable logs print what's come through the UI into the logs --- packer/ui.go | 1 + 1 file changed, 1 insertion(+) diff --git a/packer/ui.go b/packer/ui.go index 9ba44518b..cc4dd0a7e 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -350,6 +350,7 @@ func (u *MachineReadableUi) Machine(category string, args ...string) { panic(err) } } + log.Printf("%d,%s,%s,%s\n", now.Unix(), target, category, argsString) } // TimestampedUi is a UI that wraps another UI implementation and From 2e821da84b4f6eaa5e60076348019279e0c16ae0 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 13 Jun 2019 14:09:45 -0700 Subject: [PATCH 40/51] check to make sure a vm-name isn't already in use before trying to launch a vm with said name. --- builder/hyperv/common/driver.go | 2 ++ builder/hyperv/common/driver_mock.go | 8 ++++++++ builder/hyperv/common/driver_ps_4.go | 4 ++++ builder/hyperv/common/step_create_vm.go | 10 +++++++++- common/powershell/hyperv/hyperv.go | 15 ++++++++++++++- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index c8bf506aa..29b282611 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -73,6 +73,8 @@ type Driver interface { DeleteVirtualSwitch(string) error + CheckVMName(string) error + CreateVirtualMachine(string, string, string, int64, int64, int64, string, uint, bool, bool, string) error AddVirtualMachineHardDrive(string, string, string, int64, int64, string) error diff --git a/builder/hyperv/common/driver_mock.go b/builder/hyperv/common/driver_mock.go index 290687829..4005b35e2 100644 --- a/builder/hyperv/common/driver_mock.go +++ b/builder/hyperv/common/driver_mock.go @@ -110,6 +110,9 @@ type DriverMock struct { DeleteVirtualSwitch_SwitchName string DeleteVirtualSwitch_Err error + CheckVMName_Called bool + CheckVMName_Err error + CreateVirtualSwitch_Called bool CreateVirtualSwitch_SwitchName string CreateVirtualSwitch_SwitchType string @@ -421,6 +424,11 @@ func (d *DriverMock) AddVirtualMachineHardDrive(vmName string, vhdFile string, v return d.AddVirtualMachineHardDrive_Err } +func (d *DriverMock) CheckVMName(vmName string) error { + d.CheckVMName_Called = true + return d.CheckVMName_Err +} + func (d *DriverMock) CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool, version string) error { diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 118c429ec..d365ae2e7 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -186,6 +186,10 @@ func (d *HypervPS4Driver) AddVirtualMachineHardDrive(vmName string, vhdFile stri diskBlockSize, controllerType) } +func (d *HypervPS4Driver) CheckVMName(vmName string) error { + return hyperv.CheckVMName(vmName) +} + func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool, version string) error { diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index a9371c245..f9a05f797 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -48,6 +48,14 @@ func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis path = v.(string) } + err := driver.CheckVMName(s.VMName) + if err != nil { + s.KeepRegistered = true + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + // Determine if we even have an existing virtual harddrive to attach harddrivePath := "" if harddrivePathRaw, ok := state.GetOk("iso_path"); ok { @@ -66,7 +74,7 @@ func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis diskSize := int64(s.DiskSize) * 1024 * 1024 diskBlockSize := int64(s.DiskBlockSize) * 1024 * 1024 - err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, ramSize, diskSize, diskBlockSize, + err = driver.CreateVirtualMachine(s.VMName, path, harddrivePath, ramSize, diskSize, diskBlockSize, s.SwitchName, s.Generation, s.DifferencingDisk, s.FixedVHD, s.Version) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) diff --git a/common/powershell/hyperv/hyperv.go b/common/powershell/hyperv/hyperv.go index 25bd635c8..aa5ee1db3 100644 --- a/common/powershell/hyperv/hyperv.go +++ b/common/powershell/hyperv/hyperv.go @@ -283,10 +283,23 @@ Hyper-V\New-VM -Name "{{ .VMName }}" -Path "{{ .Path }}" -MemoryStartupBytes {{ return final, nil } +func CheckVMName(vmName string) error { + // Check that no vm with the same name is registered, to prevent + // namespace collisions + var gs powershell.PowerShellCmd + getVMCmd := fmt.Sprintf(`Hyper-V\Get-VM -Name "%s"`, vmName) + if err := gs.Run(getVMCmd); err == nil { + return fmt.Errorf("A virtual machine with the name %s is already"+ + " defined in Hyper-V. To avoid a name collision, please set your "+ + "vm_name to a unique value", vmName) + } + + return nil +} + func CreateVirtualMachine(vmName string, path string, harddrivePath string, ram int64, diskSize int64, diskBlockSize int64, switchName string, generation uint, diffDisks bool, fixedVHD bool, version string) error { - opts := scriptOptions{ Version: version, VMName: vmName, From daddb65da8f746df9e17c7a78eab9dfb06983a93 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Thu, 13 Jun 2019 14:29:22 -0700 Subject: [PATCH 41/51] add tests --- builder/hyperv/common/step_create_vm_test.go | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 builder/hyperv/common/step_create_vm_test.go diff --git a/builder/hyperv/common/step_create_vm_test.go b/builder/hyperv/common/step_create_vm_test.go new file mode 100644 index 000000000..93725d9cb --- /dev/null +++ b/builder/hyperv/common/step_create_vm_test.go @@ -0,0 +1,58 @@ +package common + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/packer/helper/multistep" +) + +func TestStepCreateVM_impl(t *testing.T) { + var _ multistep.Step = new(StepCreateVM) +} + +func TestStepCreateVM(t *testing.T) { + state := testState(t) + step := new(StepCreateVM) + + step.VMName = "test-VM-Name" + driver := state.Get("driver").(*DriverMock) + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("Bad action: %v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("Should NOT have error") + } + + // Test the driver + if !driver.CheckVMName_Called { + t.Fatal("Should have called CheckVMName") + } +} + +func TestStepCreateVM_CheckVMNameErr(t *testing.T) { + state := testState(t) + step := new(StepCreateVM) + + step.VMName = "test-VM-Name" + driver := state.Get("driver").(*DriverMock) + driver.CheckVMName_Err = fmt.Errorf("A virtual machine with the name is already" + + " defined in Hyper-V. To avoid a name collision, please set your " + + "vm_name to a unique value") + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionHalt { + t.Fatalf("Bad action: %v", action) + } + if _, ok := state.GetOk("error"); !ok { + t.Fatal("Should have error") + } + + // Test the driver + if !driver.CheckVMName_Called { + t.Fatal("Should have called CheckVMName") + } +} From b3277698f6cfbe5d78743bfc0ecb81c00f9ae4b7 Mon Sep 17 00:00:00 2001 From: "bozhi.ch" Date: Fri, 14 Jun 2019 11:49:42 +0800 Subject: [PATCH 42/51] let product API determine the default value of io_optimized --- builder/alicloud/ecs/run_config.go | 2 +- builder/alicloud/ecs/step_create_instance.go | 12 +++++++----- website/source/docs/builders/alicloud-ecs.html.md | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builder/alicloud/ecs/run_config.go b/builder/alicloud/ecs/run_config.go index e36a33854..ce4eda5b0 100644 --- a/builder/alicloud/ecs/run_config.go +++ b/builder/alicloud/ecs/run_config.go @@ -14,7 +14,7 @@ import ( type RunConfig struct { AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"` ZoneId string `mapstructure:"zone_id"` - IOOptimized bool `mapstructure:"io_optimized"` + IOOptimized *bool `mapstructure:"io_optimized"` InstanceType string `mapstructure:"instance_type"` Description string `mapstructure:"description"` AlicloudSourceImage string `mapstructure:"source_image"` diff --git a/builder/alicloud/ecs/step_create_instance.go b/builder/alicloud/ecs/step_create_instance.go index 512ea5c98..ad3854751 100644 --- a/builder/alicloud/ecs/step_create_instance.go +++ b/builder/alicloud/ecs/step_create_instance.go @@ -17,7 +17,7 @@ import ( ) type stepCreateAlicloudInstance struct { - IOOptimized bool + IOOptimized *bool InstanceType string UserData string UserDataFile string @@ -142,11 +142,13 @@ func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep. request.InternetChargeType = s.InternetChargeType request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut)) - ioOptimized := IOOptimizedNone - if s.IOOptimized { - ioOptimized = IOOptimizedOptimized + if s.IOOptimized != nil { + if *s.IOOptimized { + request.IoOptimized = IOOptimizedOptimized + } else { + request.IoOptimized = IOOptimizedNone + } } - request.IoOptimized = ioOptimized config := state.Get("config").(*Config) password := config.Comm.SSHPassword diff --git a/website/source/docs/builders/alicloud-ecs.html.md b/website/source/docs/builders/alicloud-ecs.html.md index 22839d032..ef113f487 100644 --- a/website/source/docs/builders/alicloud-ecs.html.md +++ b/website/source/docs/builders/alicloud-ecs.html.md @@ -218,7 +218,8 @@ builder. error is returned. - `io_optimized` (boolean) - Whether an ECS instance is I/O optimized or not. - The default value is `false`. + If this option is not provided, the value will be determined by product API + according to what `instance_type` is used. - `security_group_id` (string) - ID of the security group to which a newly created instance belongs. Mutual access is allowed between instances in one From d8b91cd7a5c4eea27015c09988ff7c807b44b83b Mon Sep 17 00:00:00 2001 From: wrolisonaflac <47217958+wrolisonaflac@users.noreply.github.com> Date: Fri, 14 Jun 2019 07:54:00 -0400 Subject: [PATCH 43/51] Update ncloud.html.md Fixed blog link at cloudwindows --- website/source/docs/builders/ncloud.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/builders/ncloud.html.md b/website/source/docs/builders/ncloud.html.md index 32199cf6e..154fc1c8d 100644 --- a/website/source/docs/builders/ncloud.html.md +++ b/website/source/docs/builders/ncloud.html.md @@ -91,7 +91,7 @@ Here is a basic example for windows server. } -> **Warning:** Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: -https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/ +https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/ Here is a basic example for linux server. From 065fee86a8d2cea7c809dfcee5f263a8d59a367a Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Fri, 14 Jun 2019 15:08:59 +0200 Subject: [PATCH 44/51] fix more urls --- website/source/community-tools.html.md | 2 +- website/source/docs/builders/hyperv-iso.html.md.erb | 2 +- website/source/docs/builders/hyperv-vmcx.html.md.erb | 2 +- website/source/docs/provisioners/ansible.html.md.erb | 2 +- website/source/intro/getting-started/build-image.html.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/source/community-tools.html.md b/website/source/community-tools.html.md index 644775cd8..027ad6d26 100644 --- a/website/source/community-tools.html.md +++ b/website/source/community-tools.html.md @@ -54,4 +54,4 @@ power of Packer templates. ## Other - [suitcase](https://github.com/tmclaugh/suitcase) - Packer based build system for CentOS OS images -- [Undo-WinRMConfig](https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/) - Open source automation to stage WinRM reset to pristine state at next shtudown +- [Undo-WinRMConfig](https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/) - Open source automation to stage WinRM reset to pristine state at next shtudown diff --git a/website/source/docs/builders/hyperv-iso.html.md.erb b/website/source/docs/builders/hyperv-iso.html.md.erb index 72693ce7a..716285d5c 100644 --- a/website/source/docs/builders/hyperv-iso.html.md.erb +++ b/website/source/docs/builders/hyperv-iso.html.md.erb @@ -877,7 +877,7 @@ Finish proxy after sysprep --> ``` -> **Warning:** Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: -https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/ +https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/ ## Example For Ubuntu Vivid Generation 2 diff --git a/website/source/docs/builders/hyperv-vmcx.html.md.erb b/website/source/docs/builders/hyperv-vmcx.html.md.erb index 34121076c..493a78109 100644 --- a/website/source/docs/builders/hyperv-vmcx.html.md.erb +++ b/website/source/docs/builders/hyperv-vmcx.html.md.erb @@ -894,7 +894,7 @@ Finish proxy after sysprep --> ``` -> **Warning:** Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: -https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/ +https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/ ## Example For Ubuntu Vivid Generation 2 diff --git a/website/source/docs/provisioners/ansible.html.md.erb b/website/source/docs/provisioners/ansible.html.md.erb index be33fbf13..0e98a7601 100644 --- a/website/source/docs/provisioners/ansible.html.md.erb +++ b/website/source/docs/provisioners/ansible.html.md.erb @@ -325,7 +325,7 @@ Platform: ``` -> **Warning:** Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: -https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/ +https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/ ### Post i/o timeout errors diff --git a/website/source/intro/getting-started/build-image.html.md b/website/source/intro/getting-started/build-image.html.md index 672fe8dac..272d30ef8 100644 --- a/website/source/intro/getting-started/build-image.html.md +++ b/website/source/intro/getting-started/build-image.html.md @@ -405,7 +405,7 @@ Start-Service -Name WinRM ``` -> **Warning:** Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: -https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/ +https://cloudywindows.io/post/winrm-for-provisioning-close-the-door-on-the-way-out-eh/ Save the above code in a file named `bootstrap_win.txt`. From f7793649ec69314f68211ac999f47f41f3c67f12 Mon Sep 17 00:00:00 2001 From: bzhaoopenstack Date: Sat, 15 Jun 2019 00:34:33 +0800 Subject: [PATCH 45/51] Fix failed to copy binary when using make bin If we use make bin, XC_OS and XC_ARCH will be None, the binary will failed to be copied. --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 3422f8ec3..5ff399039 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -144,7 +144,7 @@ IFS="${OLDIFS}" # Copy our OS/Arch to the bin/ directory echo "==> Copying binaries for this platform..." -DEV_PLATFORM="./pkg/${XC_OS}_${XC_ARCH}" +DEV_PLATFORM="./pkg/$(go env GOOS)_$(go env GOARCH)" for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f); do cp -v ${F} bin/ cp -v ${F} "${MAIN_GOPATH}/bin/" From 0a2f4d884a8441b8cdf766877598d5fdbae6ceb0 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 14 Jun 2019 11:12:58 -0700 Subject: [PATCH 46/51] make bin currently won't work outside of GOPATH --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f9518936d..f33955362 100644 --- a/Makefile +++ b/Makefile @@ -28,14 +28,14 @@ release: install-build-deps test releasebin package ## Build a release build bin: install-build-deps ## Build debug/test build @echo "WARN: 'make bin' is for debug / test builds only. Use 'make release' for release builds." - @GO111MODULE=off sh -c "$(CURDIR)/scripts/build.sh" + @GO111MODULE=auto sh -c "$(CURDIR)/scripts/build.sh" releasebin: install-build-deps @grep 'const VersionPrerelease = "dev"' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \ echo "ERROR: You must remove prerelease tags from version/version.go prior to release."; \ exit 1; \ fi - @GO111MODULE=off sh -c "$(CURDIR)/scripts/build.sh" + @GO111MODULE=auto sh -c "$(CURDIR)/scripts/build.sh" package: $(if $(VERSION),,@echo 'VERSION= needed to release; Use make package skip compilation'; exit 1) @@ -115,7 +115,7 @@ testrace: mode-check vet ## Test with race detection enabled @GO111MODULE=off go test -race $(TEST) $(TESTARGS) -timeout=3m -p=8 check-vendor-vs-mod: ## Check that go modules and vendored code are on par - @GO111MODULE=on go mod vendor + @GO111MODULE=on go mod vendor @git diff --exit-code --ignore-space-change --ignore-space-at-eol -- vendor ; if [ $$? -eq 1 ]; then \ echo "ERROR: vendor dir is not on par with go modules definition." && \ exit 1; \ From 352438c0908ee56ee69456a80e95e3bc0f5d8304 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Fri, 14 Jun 2019 11:14:12 -0700 Subject: [PATCH 47/51] vendoring --- go.mod | 1 + go.sum | 3 +++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 8d35249dd..6587e5bdd 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,7 @@ require ( github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7 github.com/mitchellh/go-homedir v1.0.0 github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed + github.com/mitchellh/gox v1.0.1 // indirect github.com/mitchellh/iochan v1.0.0 github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557 diff --git a/go.sum b/go.sum index 776cc1a18..951486cf8 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,7 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= @@ -288,6 +289,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= +github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= From ab52c4f87ee17c5a9e860e8ce65844b59d9c1ab6 Mon Sep 17 00:00:00 2001 From: Julien Brochet Date: Fri, 14 Jun 2019 20:24:50 +0200 Subject: [PATCH 48/51] fix(promox): update proxmox-api-go dependency --- builder/proxmox/step_start_vm.go | 4 ++-- go.mod | 2 +- go.sum | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/builder/proxmox/step_start_vm.go b/builder/proxmox/step_start_vm.go index a7521b14b..551174ea1 100644 --- a/builder/proxmox/step_start_vm.go +++ b/builder/proxmox/step_start_vm.go @@ -21,9 +21,9 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist client := state.Get("proxmoxClient").(*proxmox.Client) c := state.Get("config").(*Config) - agent := "1" + agent := 1 if c.Agent == false { - agent = "0" + agent = 0 } ui.Say("Creating VM") diff --git a/go.mod b/go.mod index 8d35249dd..460d1e7af 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 // indirect github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591 - github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d + github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4 github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af // indirect github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f diff --git a/go.sum b/go.sum index 776cc1a18..04acf6538 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d h1:igrCnHheXb+lZ1bW9Ths8JZZIjh9D4Vi/49JqiHE+cI= github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= +github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4 h1:o//09WenT9BNcQypCYfOBfRe5gtLUvUfTPq0xQqPMEI= +github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= From 5efaba6dd1e049a5497b7b10fd65d4f1c1ee4ce8 Mon Sep 17 00:00:00 2001 From: Julien Brochet Date: Mon, 17 Jun 2019 16:10:22 +0200 Subject: [PATCH 49/51] fix(proxmox): update vendor folder with latest Proxmox dependency --- .../Telmate/proxmox-api-go/proxmox/client.go | 2 ++ .../proxmox-api-go/proxmox/config_qemu.go | 26 ++++++++++++++----- vendor/modules.txt | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go index 50f2d9e09..75ebf1a8a 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go @@ -497,6 +497,8 @@ func (c *Client) GetNextID(currentID int) (nextID int, err error) { } } nextID, err = strconv.Atoi(data["data"].(string)) + } else if strings.HasPrefix(err.Error(), "400 ") { + return c.GetNextID(currentID + 1) } return } diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go index 7c0ffd897..f0ee911b5 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go @@ -26,7 +26,7 @@ type ConfigQemu struct { Name string `json:"name"` Description string `json:"desc"` Onboot bool `json:"onboot"` - Agent string `json:"agent"` + Agent int `json:"agent"` Memory int `json:"memory"` QemuOs string `json:"os"` QemuCores int `json:"cores"` @@ -251,9 +251,16 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e if _, isSet := vmConfig["onboot"]; isSet { onboot = Itob(int(vmConfig["onboot"].(float64))) } - agent := "1" + + agent := 0 if _, isSet := vmConfig["agent"]; isSet { - agent = vmConfig["agent"].(string) + switch vmConfig["agent"].(type) { + case float64: + agent = int(vmConfig["agent"].(float64)) + case string: + agent, _ = strconv.Atoi(vmConfig["agent"].(string)) + } + } ostype := "other" if _, isSet := vmConfig["ostype"]; isSet { @@ -299,6 +306,9 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e if _, isSet := vmConfig["searchdomain"]; isSet { config.Searchdomain = vmConfig["searchdomain"].(string) } + if _, isSet := vmConfig["nameserver"]; isSet { + config.Nameserver = vmConfig["nameserver"].(string) + } if _, isSet := vmConfig["sshkeys"]; isSet { config.Sshkeys, _ = url.PathUnescape(vmConfig["sshkeys"].(string)) } @@ -330,6 +340,7 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e // diskConfMap := QemuDevice{ + "id": diskID, "type": diskType, "storage": storageName, "file": fileName, @@ -345,11 +356,10 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e } // Add networks. - nicNameRe := regexp.MustCompile(`net\d+`) nicNames := []string{} for k, _ := range vmConfig { - if nicName := nicNameRe.FindStringSubmatch(k); len(nicName) > 0 { + if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 { nicNames = append(nicNames, nicName[0]) } } @@ -358,13 +368,13 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e nicConfStr := vmConfig[nicName] nicConfList := strings.Split(nicConfStr.(string), ",") - // id := rxDeviceID.FindStringSubmatch(nicName) nicID, _ := strconv.Atoi(id[0]) model, macaddr := ParseSubConf(nicConfList[0], "=") // Add model and MAC address. nicConfMap := QemuDevice{ + "id": nicID, "model": model, "macaddr": macaddr, } @@ -441,7 +451,7 @@ func RemoveSshForwardUsernet(vmr *VmRef, client *Client) (err error) { func MaxVmId(client *Client) (max int, err error) { resp, err := client.GetVmList() vms := resp["data"].([]interface{}) - max = 0 + max = 100 for vmii := range vms { vm := vms[vmii].(map[string]interface{}) vmid := int(vm["vmid"].(float64)) @@ -524,6 +534,7 @@ func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interfa // For backward compatibility. if len(c.QemuNetworks) == 0 && len(c.QemuNicModel) > 0 { deprecatedStyleMap := QemuDevice{ + "id": 0, "model": c.QemuNicModel, "bridge": c.QemuBrige, "macaddr": c.QemuMacAddr, @@ -602,6 +613,7 @@ func (c ConfigQemu) CreateQemuDisksParams( } } deprecatedStyleMap := QemuDevice{ + "id": 0, "type": dType, "storage": c.Storage, "size": c.DiskSize, diff --git a/vendor/modules.txt b/vendor/modules.txt index 35aa98200..490ebdb6b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -49,7 +49,7 @@ github.com/NaverCloudPlatform/ncloud-sdk-go/sdk github.com/NaverCloudPlatform/ncloud-sdk-go/common github.com/NaverCloudPlatform/ncloud-sdk-go/request github.com/NaverCloudPlatform/ncloud-sdk-go/oauth -# github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d +# github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4 github.com/Telmate/proxmox-api-go/proxmox # github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors From b174258c5770b41970a3748da89c1623eec6ad64 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Wed, 19 Jun 2019 13:12:40 -0400 Subject: [PATCH 50/51] regularize indentation --- website/source/partials/provisioners/_common-config.html.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/partials/provisioners/_common-config.html.md b/website/source/partials/provisioners/_common-config.html.md index 6d4d48c35..c1b74a39d 100644 --- a/website/source/partials/provisioners/_common-config.html.md +++ b/website/source/partials/provisioners/_common-config.html.md @@ -15,11 +15,11 @@ Parameters common to all provisioners: "script": "script.sh", "override": { "vmware-iso": { - "execute_command": "echo 'password' | sudo -S bash {{.Path}}" + "execute_command": "echo 'password' | sudo -S bash {{.Path}}" } } } ``` - `timeout` (duration) - If the provisioner takes more than for example - `1h10m1s` or `10m` to finish, the provisioner will timeout and fail. \ No newline at end of file + `1h10m1s` or `10m` to finish, the provisioner will timeout and fail. From 1fa3f8204ea5fab15ef2f896a569798919347911 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Thu, 20 Jun 2019 18:01:45 +0200 Subject: [PATCH 51/51] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb69ad092..dffe812af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,22 @@ ## 1.4.2 (upcoming) ### IMPROVEMENTS: +* **new feature:** Packer console [GH-7726] +* builder/alicloud: let product API determine the default value of io_optimized + [GH-7747] +* builder/alicloud: cleanup image and snapshot if target image is still not + available after timeout [GH-7744] * builder/amazon: Enable encrypted AMI sharing across accounts [GH-7707] * builder/amazon: New SpotInstanceTypes feature for spot instance users. [GH-7682] * builder/azure: Update Azure SDK for Go to v30.0.0 [GH-7706] * builder/cloudstack: Add tags to instance upon creation [GH0-7526] * builder/docker: Better windows defaults [GH-7678] +* builder/google: Add feature to import user-data from a file [GH-7720] +* builder/hyperv: Abort build if there's a name collision [GH-7746] * builder/openstack: Add image filtering on properties. [GH-7597] +* core: scrub out sensitive variables in scrub out sensitive variables logs + [GH-7743] * provisioner/powershell: Fix null file descriptor error that occurred when remote_path provided is a directory and not a file. [GH-7705] @@ -19,6 +28,8 @@ wasn't [GH-7699] * builder/cloudstack: Update go-cloudstack sdk, fixing compatability with CloudStack v 4.12 [GH-7694] +* provisioner/ansible: prevent nil pointer dereference after a language change + [GH-7738] * provisioner/chef: Accept chef license by default to prevent hangs in latest Chef [GH-7653] * provisioner/powershell: Fix crash caused by error in retry logic check in