diff --git a/.gitignore b/.gitignore index 7df3f0de4..422f7f145 100644 --- a/.gitignore +++ b/.gitignore @@ -48,5 +48,10 @@ doc/ .ruby-version .rvmrc +# Extensions +*.so +*.bundle +tmp/* + # Box storage for spec test/vagrant-spec/boxes/*.box diff --git a/.travis.yml b/.travis.yml index 4e2432ee5..c81acbe48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,17 @@ sudo: false cache: bundler +before_install: + - which go + - sudo apt-get remove --purge golang-go + - sudo add-apt-repository ppa:gophers/archive -y + - sudo apt-get update -q + - sudo apt-get install golang-1.11-go -yq + addons: apt: packages: - - bsdtar + - bsdtar rvm: - 2.3.8 @@ -22,5 +29,13 @@ branches: env: global: - NOKOGIRI_USE_SYSTEM_LIBRARIES=true + - GO111MODULE=on + - GOPATH=$HOME/go + - GOROOT=/usr/lib/go-1.11 + - PATH=/usr/lib/go-1.11/bin:$PATH -script: bundle exec rake test:unit +script: + - go version + - go test ./... + - bundle exec rake compile + - bundle exec rake test:unit diff --git a/Rakefile b/Rakefile index 2af398a1e..9dbb59508 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,10 @@ require 'rubygems' require 'bundler/setup' +require 'rake/extensiontask' + +Rake::ExtensionTask.new "go-plugin" do |ext| + ext.lib_dir = "lib/vagrant/go_plugin" +end # Immediately sync all stdout so that tools like buildbot can # immediately load in the output. diff --git a/ext/go-plugin/extconf.rb b/ext/go-plugin/extconf.rb new file mode 100644 index 000000000..e3044dabd --- /dev/null +++ b/ext/go-plugin/extconf.rb @@ -0,0 +1,68 @@ +lib = File.expand_path('./../../../lib', File.expand_path(__FILE__)) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +require 'mkmf' +require 'time' + +find_executable('go') + +go_version = /go version go(\d+\.\d+)/.match(`go version`).captures.first +raise "'go' version >=1.5.0 is required, found go #{go_version}" unless Gem::Dependency.new('', '>=1.5.0').match?('', go_version) + +makefile = "Makefile" +makefile_content = < -1 { + line = line[idx+1:] + } + + return line +} + +// ExitError is returned by Wait to indicate and error executing the remote +// command, or a non-zero exit status. +type ExitError struct { + Command string + ExitStatus int + Err error +} + +func (e *ExitError) Error() string { + if e.Err != nil { + return fmt.Sprintf("error executing %q: %v", e.Command, e.Err) + } + return fmt.Sprintf("%q exit status: %d", e.Command, e.ExitStatus) +} diff --git a/ext/go-plugin/vagrant/communicator/command_test.go b/ext/go-plugin/vagrant/communicator/command_test.go new file mode 100644 index 000000000..211f4759e --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/command_test.go @@ -0,0 +1 @@ +package communicator diff --git a/ext/go-plugin/vagrant/communicator/communicator.go b/ext/go-plugin/vagrant/communicator/communicator.go new file mode 100644 index 000000000..1911e6550 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/communicator.go @@ -0,0 +1,117 @@ +package communicator + +import ( + "context" + "fmt" + "io" + "os" + "sync/atomic" + "time" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +type Communicator interface { + Connect() error + Disconnect() error + Timeout() time.Duration + Start(*Cmd) error + Download(path string, output io.Writer) error + DownloadDir(dst, src string, excludes []string) error + Upload(dst string, src io.Reader, srcinfo *os.FileInfo) error + UploadDir(dst, src string, excludes []string) error +} + +// maxBackoffDelay is the maximum delay between retry attempts +var maxBackoffDelay = 20 * time.Second +var initialBackoffDelay = time.Second +var logger = vagrant.DefaultLogger().Named("communicator") + +// Fatal is an interface that error values can return to halt Retry +type Fatal interface { + FatalError() error +} + +// Retry retries the function f until it returns a nil error, a Fatal error, or +// the context expires. +func Retry(ctx context.Context, f func() error) error { + // container for atomic error value + type errWrap struct { + E error + } + + // Try the function in a goroutine + var errVal atomic.Value + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + + delay := time.Duration(0) + for { + // If our context ended, we want to exit right away. + select { + case <-ctx.Done(): + return + case <-time.After(delay): + } + + // Try the function call + err := f() + + // return if we have no error, or a FatalError + done := false + switch e := err.(type) { + case nil: + done = true + case Fatal: + err = e.FatalError() + done = true + } + + errVal.Store(errWrap{err}) + + if done { + return + } + + logger.Warn("retryable error", "error", err) + + delay *= 2 + + if delay == 0 { + delay = initialBackoffDelay + } + + if delay > maxBackoffDelay { + delay = maxBackoffDelay + } + + logger.Info("sleeping for retry", "duration", delay) + } + }() + + // Wait for completion + select { + case <-ctx.Done(): + case <-doneCh: + } + + var lastErr error + // Check if we got an error executing + if ev, ok := errVal.Load().(errWrap); ok { + lastErr = ev.E + } + + // Check if we have a context error to check if we're interrupted or timeout + switch ctx.Err() { + case context.Canceled: + return fmt.Errorf("interrupted - last error: %v", lastErr) + case context.DeadlineExceeded: + return fmt.Errorf("timeout - last error: %v", lastErr) + } + + if lastErr != nil { + return lastErr + } + return nil +} diff --git a/ext/go-plugin/vagrant/communicator/none/communicator.go b/ext/go-plugin/vagrant/communicator/none/communicator.go new file mode 100644 index 000000000..169f01e1f --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/none/communicator.go @@ -0,0 +1,59 @@ +package none + +import ( + "errors" + "io" + "os" + "time" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/communicator" +) + +type Communicator struct { + config string +} + +// Creates a null vagrant.Communicator implementation. This takes +// an already existing configuration. +func New(config string) (result *Communicator, err error) { + // Establish an initial connection and connect + result = &Communicator{ + config: config, + } + + return +} + +func (c *Communicator) Connect() (err error) { + return +} + +func (c *Communicator) Disconnect() (err error) { + return +} + +func (c *Communicator) Start(cmd *communicator.Cmd) (err error) { + cmd.Init() + cmd.SetExitStatus(0, nil) + return +} + +func (c *Communicator) Upload(path string, input io.Reader, fi *os.FileInfo) error { + return errors.New("Upload is not implemented when communicator = 'none'") +} + +func (c *Communicator) UploadDir(dst string, src string, excl []string) error { + return errors.New("UploadDir is not implemented when communicator = 'none'") +} + +func (c *Communicator) Download(path string, output io.Writer) error { + return errors.New("Download is not implemented when communicator = 'none'") +} + +func (c *Communicator) DownloadDir(dst string, src string, excl []string) error { + return errors.New("DownloadDir is not implemented when communicator = 'none'") +} + +func (c *Communicator) Timeout() time.Duration { + return 0 +} diff --git a/ext/go-plugin/vagrant/communicator/none/communicator_test.go b/ext/go-plugin/vagrant/communicator/none/communicator_test.go new file mode 100644 index 000000000..89032aad3 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/none/communicator_test.go @@ -0,0 +1,12 @@ +package none + +import ( + "testing" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/communicator" +) + +func TestCommIsCommunicator(t *testing.T) { + // Force failure with explanation of why it's not valid + var _ communicator.Communicator = new(Communicator) +} diff --git a/ext/go-plugin/vagrant/communicator/ssh/communicator.go b/ext/go-plugin/vagrant/communicator/ssh/communicator.go new file mode 100644 index 000000000..1e7edb063 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/ssh/communicator.go @@ -0,0 +1,949 @@ +package ssh + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/communicator" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +// ErrHandshakeTimeout is returned from New() whenever we're unable to establish +// an ssh connection within a certain timeframe. By default the handshake time- +// out period is 1 minute. You can change it with Config.HandshakeTimeout. +var ErrHandshakeTimeout = fmt.Errorf("Timeout during SSH handshake") + +var logger = vagrant.DefaultLogger().Named("communicator.ssh") + +type Communicator struct { + client *ssh.Client + config *Config + conn net.Conn + address string +} + +// Config is the structure used to configure the SSH communicator. +type Config struct { + // The configuration of the Go SSH connection + SSHConfig *ssh.ClientConfig + + // Connection returns a new connection. The current connection + // in use will be closed as part of the Close method, or in the + // case an error occurs. + Connection func() (net.Conn, error) + + // Pty, if true, will request a pty from the remote end. + Pty bool + + // DisableAgentForwarding, if true, will not forward the SSH agent. + DisableAgentForwarding bool + + // HandshakeTimeout limits the amount of time we'll wait to handshake before + // saying the connection failed. + HandshakeTimeout time.Duration + + // UseSftp, if true, sftp will be used instead of scp for file transfers + UseSftp bool + + // KeepAliveInterval sets how often we send a channel request to the + // server. A value < 0 disables. + KeepAliveInterval time.Duration + + // Timeout is how long to wait for a read or write to succeed. + Timeout time.Duration +} + +// Creates a new vagrant.Communicator implementation over SSH. This takes +// an already existing TCP connection and SSH configuration. +func New(address string, config *Config) (result *Communicator, err error) { + // Establish an initial connection and connect + result = &Communicator{ + config: config, + address: address, + } + + // reset the logger in case custom default has been set + logger = vagrant.DefaultLogger().Named("communicator.ssh") + + return +} + +func (c *Communicator) Connect() error { + return c.reconnect() +} + +func (c *Communicator) Disconnect() (err error) { + if c.conn != nil { + logger.Info("closing connection") + err = c.conn.Close() + } else { + err = errors.New("No connection currently established to close") + } + return +} + +func (c *Communicator) Timeout() time.Duration { + return c.config.Timeout +} + +func (c *Communicator) Start(cmd *communicator.Cmd) (err error) { + session, err := c.newSession() + if err != nil { + return + } + + // Setup our session + session.Stdin = cmd.Stdin + session.Stdout = cmd.Stdout + session.Stderr = cmd.Stderr + + if c.config.Pty { + // Request a PTY + termModes := ssh.TerminalModes{ + ssh.ECHO: 0, // do not echo + ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud + ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud + } + + if err = session.RequestPty("xterm", 40, 80, termModes); err != nil { + return + } + } + + logger.Debug("starting remote command", "command", cmd.Command) + err = session.Start(cmd.Command + "\n") + if err != nil { + return + } + + go func() { + if c.config.KeepAliveInterval <= 0 { + return + } + c := time.NewTicker(c.config.KeepAliveInterval) + defer c.Stop() + for range c.C { + _, err := session.SendRequest("keepalive@vagrantup.com", true, nil) + if err != nil { + return + } + } + }() + + // Start a goroutine to wait for the session to end and set the + // exit boolean and status. + go func() { + defer session.Close() + + err := session.Wait() + exitStatus := 0 + if err != nil { + switch err.(type) { + case *ssh.ExitError: + exitStatus = err.(*ssh.ExitError).ExitStatus() + logger.Error("remote command exited non-zero", + "exitcode", exitStatus, "command", cmd.Command) + case *ssh.ExitMissingError: + logger.Error("remote command exited without exit status or exit signal", + "command", cmd.Command) + exitStatus = communicator.CmdDisconnect + default: + logger.Error("error waiting for ssh session", "error", err) + } + } + cmd.SetExitStatus(exitStatus, err) + }() + return +} + +func (c *Communicator) Upload(path string, input io.Reader, fi *os.FileInfo) error { + if c.config.UseSftp { + return c.sftpUploadSession(path, input, fi) + } else { + return c.scpUploadSession(path, input, fi) + } +} + +func (c *Communicator) UploadDir(dst string, src string, excl []string) error { + logger.Debug("uploading directory", "source", src, "destination", dst) + if c.config.UseSftp { + return c.sftpUploadDirSession(dst, src, excl) + } else { + return c.scpUploadDirSession(dst, src, excl) + } +} + +func (c *Communicator) DownloadDir(src string, dst string, excl []string) error { + logger.Debug("downloading directory", "source", src, "destination", dst) + scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error { + dirStack := []string{dst} + for { + fmt.Fprint(w, "\x00") + + // read file info + fi, err := stdoutR.ReadString('\n') + if err != nil { + return err + } + + if len(fi) < 0 { + return fmt.Errorf("empty response from server") + } + + switch fi[0] { + case '\x01', '\x02': + return fmt.Errorf("%s", fi[1:]) + case 'C', 'D': + break + case 'E': + dirStack = dirStack[:len(dirStack)-1] + if len(dirStack) == 0 { + fmt.Fprint(w, "\x00") + return nil + } + continue + default: + return fmt.Errorf("unexpected server response (%x)", fi[0]) + } + + var mode int64 + var size int64 + var name string + logger.Debug("download directory", "str", fi) + n, err := fmt.Sscanf(fi[1:], "%o %d %s", &mode, &size, &name) + if err != nil || n != 3 { + return fmt.Errorf("can't parse server response (%s)", fi) + } + if size < 0 { + return fmt.Errorf("negative file size") + } + + logger.Debug("download directory", "mode", mode, "size", size, "name", name) + + dst = filepath.Join(dirStack...) + switch fi[0] { + case 'D': + err = os.MkdirAll(filepath.Join(dst, name), os.FileMode(mode)) + if err != nil { + return err + } + dirStack = append(dirStack, name) + continue + case 'C': + fmt.Fprint(w, "\x00") + err = scpDownloadFile(filepath.Join(dst, name), stdoutR, size, os.FileMode(mode)) + if err != nil { + return err + } + } + + if err := checkSCPStatus(stdoutR); err != nil { + return err + } + } + } + return c.scpSession("scp -vrf "+src, scpFunc) +} + +func (c *Communicator) Download(path string, output io.Writer) error { + if c.config.UseSftp { + return c.sftpDownloadSession(path, output) + } + return c.scpDownloadSession(path, output) +} + +func (c *Communicator) newSession() (session *ssh.Session, err error) { + logger.Debug("opening new ssh session") + if c.client == nil { + err = errors.New("client not available") + } else { + session, err = c.client.NewSession() + } + + if err != nil { + logger.Error("ssh session open failure", "error", err) + if err := c.reconnect(); err != nil { + return nil, err + } + + if c.client == nil { + return nil, errors.New("client not available") + } else { + return c.client.NewSession() + } + } + + return session, nil +} + +func (c *Communicator) reconnect() (err error) { + // Ignore errors here because we don't care if it fails + c.Disconnect() + // Set the conn and client to nil since we'll recreate it + c.conn = nil + c.client = nil + + logger.Debug("reconnection to tcp connection for ssh") + c.conn, err = c.config.Connection() + if err != nil { + // Explicitly set this to the REAL nil. Connection() can return + // a nil implementation of net.Conn which will make the + // "if c.conn == nil" check fail above. Read here for more information + // on this psychotic language feature: + // + // http://golang.org/doc/faq#nil_error + c.conn = nil + + logger.Error("reconnection failure", "error", err) + return + } + + if c.config.Timeout > 0 { + c.conn = &timeoutConn{c.conn, c.config.Timeout, c.config.Timeout} + } + + logger.Debug("handshaking with ssh") + + // Default timeout to 1 minute if it wasn't specified (zero value). For + // when you need to handshake from low orbit. + var duration time.Duration + if c.config.HandshakeTimeout == 0 { + duration = 1 * time.Minute + } else { + duration = c.config.HandshakeTimeout + } + + connectionEstablished := make(chan struct{}, 1) + + var sshConn ssh.Conn + var sshChan <-chan ssh.NewChannel + var req <-chan *ssh.Request + + go func() { + sshConn, sshChan, req, err = ssh.NewClientConn(c.conn, c.address, c.config.SSHConfig) + close(connectionEstablished) + }() + + select { + case <-connectionEstablished: + // We don't need to do anything here. We just want select to block until + // we connect or timeout. + case <-time.After(duration): + if c.conn != nil { + c.conn.Close() + } + if sshConn != nil { + sshConn.Close() + } + return ErrHandshakeTimeout + } + + if err != nil { + return + } + logger.Debug("handshake complete") + if sshConn != nil { + c.client = ssh.NewClient(sshConn, sshChan, req) + } + c.connectToAgent() + + return +} + +func (c *Communicator) connectToAgent() { + if c.client == nil { + return + } + + if c.config.DisableAgentForwarding { + logger.Info("SSH agent forwarding is disabled") + return + } + + // open connection to the local agent + socketLocation := os.Getenv("SSH_AUTH_SOCK") + if socketLocation == "" { + logger.Info("no local agent socket, will not connect agent") + return + } + agentConn, err := net.Dial("unix", socketLocation) + if err != nil { + logger.Error("could not connect to local agent socket", "path", socketLocation) + return + } + + // create agent and add in auth + forwardingAgent := agent.NewClient(agentConn) + if forwardingAgent == nil { + logger.Error("could not create agent client") + agentConn.Close() + return + } + + // add callback for forwarding agent to SSH config + // XXX - might want to handle reconnects appending multiple callbacks + auth := ssh.PublicKeysCallback(forwardingAgent.Signers) + c.config.SSHConfig.Auth = append(c.config.SSHConfig.Auth, auth) + agent.ForwardToAgent(c.client, forwardingAgent) + + // Setup a session to request agent forwarding + session, err := c.newSession() + if err != nil { + return + } + defer session.Close() + + err = agent.RequestAgentForwarding(session) + if err != nil { + logger.Error("request agent forwarding failed", "error", err) + return + } + + logger.Info("agent forwarding enabled") + return +} + +func (c *Communicator) sftpUploadSession(path string, input io.Reader, fi *os.FileInfo) error { + sftpFunc := func(client *sftp.Client) error { + return c.sftpUploadFile(path, input, client, fi) + } + + return c.sftpSession(sftpFunc) +} + +func (c *Communicator) sftpUploadFile(path string, input io.Reader, client *sftp.Client, fi *os.FileInfo) error { + logger.Debug("sftp uploading", "path", path) + f, err := client.Create(path) + if err != nil { + return err + } + defer f.Close() + + if _, err = io.Copy(f, input); err != nil { + return err + } + + if fi != nil && (*fi).Mode().IsRegular() { + mode := (*fi).Mode().Perm() + err = client.Chmod(path, mode) + if err != nil { + return err + } + } + + return nil +} + +func (c *Communicator) sftpUploadDirSession(dst string, src string, excl []string) error { + sftpFunc := func(client *sftp.Client) error { + rootDst := dst + if src[len(src)-1] != '/' { + logger.Debug("no trailing slash, creating the source directory name") + rootDst = filepath.Join(dst, filepath.Base(src)) + } + walkFunc := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // Calculate the final destination using the + // base source and root destination + relSrc, err := filepath.Rel(src, path) + if err != nil { + return err + } + finalDst := filepath.Join(rootDst, relSrc) + + // In Windows, Join uses backslashes which we don't want to get + // to the sftp server + finalDst = filepath.ToSlash(finalDst) + + // Skip the creation of the target destination directory since + // it should exist and we might not even own it + if finalDst == dst { + return nil + } + + return c.sftpVisitFile(finalDst, path, info, client) + } + + return filepath.Walk(src, walkFunc) + } + + return c.sftpSession(sftpFunc) +} + +func (c *Communicator) sftpMkdir(path string, client *sftp.Client, fi os.FileInfo) error { + logger.Debug("sftp create directory", "path", path) + + if err := client.Mkdir(path); err != nil { + // Do not consider it an error if the directory existed + remoteFi, fiErr := client.Lstat(path) + if fiErr != nil || !remoteFi.IsDir() { + return err + } + } + + mode := fi.Mode().Perm() + if err := client.Chmod(path, mode); err != nil { + return err + } + return nil +} + +func (c *Communicator) sftpVisitFile(dst string, src string, fi os.FileInfo, client *sftp.Client) error { + if !fi.IsDir() { + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + return c.sftpUploadFile(dst, f, client, &fi) + } else { + err := c.sftpMkdir(dst, client, fi) + return err + } +} + +func (c *Communicator) sftpDownloadSession(path string, output io.Writer) error { + sftpFunc := func(client *sftp.Client) error { + f, err := client.Open(path) + if err != nil { + return err + } + defer f.Close() + + if _, err = io.Copy(output, f); err != nil { + return err + } + + return nil + } + + return c.sftpSession(sftpFunc) +} + +func (c *Communicator) sftpSession(f func(*sftp.Client) error) error { + client, err := c.newSftpClient() + if err != nil { + return fmt.Errorf("sftpSession error: %s", err.Error()) + } + defer client.Close() + + return f(client) +} + +func (c *Communicator) newSftpClient() (*sftp.Client, error) { + session, err := c.newSession() + if err != nil { + return nil, err + } + + if err := session.RequestSubsystem("sftp"); err != nil { + return nil, err + } + + pw, err := session.StdinPipe() + if err != nil { + return nil, err + } + pr, err := session.StdoutPipe() + if err != nil { + return nil, err + } + + // Capture stdout so we can return errors to the user + var stdout bytes.Buffer + tee := io.TeeReader(pr, &stdout) + client, err := sftp.NewClientPipe(tee, pw) + if err != nil && stdout.Len() > 0 { + logger.Error("upload failed", "error", stdout.Bytes()) + } + + return client, err +} + +func (c *Communicator) scpUploadSession(path string, input io.Reader, fi *os.FileInfo) error { + + // The target directory and file for talking the SCP protocol + target_dir := filepath.Dir(path) + target_file := filepath.Base(path) + + // On windows, filepath.Dir uses backslash separators (ie. "\tmp"). + // This does not work when the target host is unix. Switch to forward slash + // which works for unix and windows + target_dir = filepath.ToSlash(target_dir) + + // Escape spaces in remote directory + target_dir = strings.Replace(target_dir, " ", "\\ ", -1) + + scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error { + return scpUploadFile(target_file, input, w, stdoutR, fi) + } + + return c.scpSession("scp -vt "+target_dir, scpFunc) +} + +func (c *Communicator) scpUploadDirSession(dst string, src string, excl []string) error { + scpFunc := func(w io.Writer, r *bufio.Reader) error { + uploadEntries := func() error { + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + + entries, err := f.Readdir(-1) + if err != nil { + return err + } + + return scpUploadDir(src, entries, w, r) + } + + if src[len(src)-1] != '/' { + logger.Debug("no trailing slash, creating the source directory name") + fi, err := os.Stat(src) + if err != nil { + return err + } + return scpUploadDirProtocol(filepath.Base(src), w, r, uploadEntries, fi) + } else { + // Trailing slash, so only upload the contents + return uploadEntries() + } + } + + return c.scpSession("scp -rvt "+dst, scpFunc) +} + +func (c *Communicator) scpDownloadSession(path string, output io.Writer) error { + scpFunc := func(w io.Writer, stdoutR *bufio.Reader) error { + fmt.Fprint(w, "\x00") + + // read file info + fi, err := stdoutR.ReadString('\n') + if err != nil { + return err + } + + if len(fi) < 0 { + return fmt.Errorf("empty response from server") + } + + switch fi[0] { + case '\x01', '\x02': + return fmt.Errorf("%s", fi[1:]) + case 'C': + case 'D': + return fmt.Errorf("remote file is directory") + default: + return fmt.Errorf("unexpected server response (%x)", fi[0]) + } + + var mode string + var size int64 + + n, err := fmt.Sscanf(fi, "%6s %d ", &mode, &size) + if err != nil || n != 2 { + return fmt.Errorf("can't parse server response (%s)", fi) + } + if size < 0 { + return fmt.Errorf("negative file size") + } + + fmt.Fprint(w, "\x00") + + if _, err := io.CopyN(output, stdoutR, size); err != nil { + return err + } + + fmt.Fprint(w, "\x00") + + return checkSCPStatus(stdoutR) + } + + if !strings.Contains(path, " ") { + return c.scpSession("scp -vf "+path, scpFunc) + } + return c.scpSession("scp -vf "+strconv.Quote(path), scpFunc) +} + +func (c *Communicator) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) error) error { + session, err := c.newSession() + if err != nil { + return err + } + defer session.Close() + + // Get a pipe to stdin so that we can send data down + stdinW, err := session.StdinPipe() + if err != nil { + return err + } + + // We only want to close once, so we nil w after we close it, + // and only close in the defer if it hasn't been closed already. + defer func() { + if stdinW != nil { + stdinW.Close() + } + }() + + // Get a pipe to stdout so that we can get responses back + stdoutPipe, err := session.StdoutPipe() + if err != nil { + return err + } + stdoutR := bufio.NewReader(stdoutPipe) + + // Set stderr to a bytes buffer + stderr := new(bytes.Buffer) + session.Stderr = stderr + + // Start the sink mode on the other side + // TODO(mitchellh): There are probably issues with shell escaping the path + logger.Debug("starting remote scp process", "command", scpCommand) + if err := session.Start(scpCommand); err != nil { + return err + } + + // Call our callback that executes in the context of SCP. We ignore + // EOF errors if they occur because it usually means that SCP prematurely + // ended on the other side. + logger.Debug("started scp session, beginning transfers") + if err := f(stdinW, stdoutR); err != nil && err != io.EOF { + return err + } + + // Close the stdin, which sends an EOF, and then set w to nil so that + // our defer func doesn't close it again since that is unsafe with + // the Go SSH package. + logger.Debug("scp sessiono complete, closing stdin pipe") + stdinW.Close() + stdinW = nil + + // Wait for the SCP connection to close, meaning it has consumed all + // our data and has completed. Or has errored. + logger.Debug("waiting for ssh session to complete") + err = session.Wait() + if err != nil { + if exitErr, ok := err.(*ssh.ExitError); ok { + // Otherwise, we have an ExitError, meaning we can just read + // the exit status + logger.Debug("non-zero exit status", "exitcode", exitErr.ExitStatus()) + stdoutB, err := ioutil.ReadAll(stdoutR) + if err != nil { + return err + } + logger.Debug("scp output", "output", stdoutB) + + // If we exited with status 127, it means SCP isn't available. + // Return a more descriptive error for that. + if exitErr.ExitStatus() == 127 { + return errors.New( + "SCP failed to start. This usually means that SCP is not\n" + + "properly installed on the remote system.") + } + } + + return err + } + + logger.Debug("scp stderr", "length", stderr.Len(), "content", stderr.String()) + return nil +} + +// checkSCPStatus checks that a prior command sent to SCP completed +// successfully. If it did not complete successfully, an error will +// be returned. +func checkSCPStatus(r *bufio.Reader) error { + code, err := r.ReadByte() + if err != nil { + return err + } + + if code != 0 { + // Treat any non-zero (really 1 and 2) as fatal errors + message, _, err := r.ReadLine() + if err != nil { + return fmt.Errorf("Error reading error message: %s", err) + } + + return errors.New(string(message)) + } + + return nil +} + +func scpDownloadFile(dst string, src io.Reader, size int64, mode os.FileMode) error { + f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) + if err != nil { + return err + } + defer f.Close() + if _, err := io.CopyN(f, src, size); err != nil { + return err + } + return nil +} + +func scpUploadFile(dst string, src io.Reader, w io.Writer, r *bufio.Reader, fi *os.FileInfo) error { + var mode os.FileMode + var size int64 + + if fi != nil && (*fi).Mode().IsRegular() { + mode = (*fi).Mode().Perm() + size = (*fi).Size() + } else { + // Create a temporary file where we can copy the contents of the src + // so that we can determine the length, since SCP is length-prefixed. + tf, err := ioutil.TempFile("", "vagrant-upload") + if err != nil { + return fmt.Errorf("Error creating temporary file for upload: %s", err) + } + defer os.Remove(tf.Name()) + defer tf.Close() + + mode = 0644 + + logger.Debug("copying input data to temporary file to read length") + if _, err := io.Copy(tf, src); err != nil { + return fmt.Errorf("Error copying input data into local temporary "+ + "file. Check that TEMPDIR has enough space. Error: %s", err) + } + + // Sync the file so that the contents are definitely on disk, then + // read the length of it. + if err := tf.Sync(); err != nil { + return fmt.Errorf("Error creating temporary file for upload: %s", err) + } + + // Seek the file to the beginning so we can re-read all of it + if _, err := tf.Seek(0, 0); err != nil { + return fmt.Errorf("Error creating temporary file for upload: %s", err) + } + + tfi, err := tf.Stat() + if err != nil { + return fmt.Errorf("Error creating temporary file for upload: %s", err) + } + + size = tfi.Size() + src = tf + } + + // Start the protocol + perms := fmt.Sprintf("C%04o", mode) + logger.Debug("scp uploading", "path", dst, "perms", perms, "size", size) + + fmt.Fprintln(w, perms, size, dst) + if err := checkSCPStatus(r); err != nil { + return err + } + + if _, err := io.CopyN(w, src, size); err != nil { + return err + } + + fmt.Fprint(w, "\x00") + return checkSCPStatus(r) +} + +func scpUploadDirProtocol(name string, w io.Writer, r *bufio.Reader, f func() error, fi os.FileInfo) error { + logger.Debug("scp directory upload", "path", name) + + mode := fi.Mode().Perm() + + perms := fmt.Sprintf("D%04o 0", mode) + + fmt.Fprintln(w, perms, name) + err := checkSCPStatus(r) + if err != nil { + return err + } + + if err := f(); err != nil { + return err + } + + fmt.Fprintln(w, "E") + return err +} + +func scpUploadDir(root string, fs []os.FileInfo, w io.Writer, r *bufio.Reader) error { + for _, fi := range fs { + realPath := filepath.Join(root, fi.Name()) + + // Track if this is actually a symlink to a directory. If it is + // a symlink to a file we don't do any special behavior because uploading + // a file just works. If it is a directory, we need to know so we + // treat it as such. + isSymlinkToDir := false + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + symPath, err := filepath.EvalSymlinks(realPath) + if err != nil { + return err + } + + symFi, err := os.Lstat(symPath) + if err != nil { + return err + } + + isSymlinkToDir = symFi.IsDir() + } + + if !fi.IsDir() && !isSymlinkToDir { + // It is a regular file (or symlink to a file), just upload it + f, err := os.Open(realPath) + if err != nil { + return err + } + + err = func() error { + defer f.Close() + return scpUploadFile(fi.Name(), f, w, r, &fi) + }() + + if err != nil { + return err + } + + continue + } + + // It is a directory, recursively upload + err := scpUploadDirProtocol(fi.Name(), w, r, func() error { + f, err := os.Open(realPath) + if err != nil { + return err + } + defer f.Close() + + entries, err := f.Readdir(-1) + if err != nil { + return err + } + + return scpUploadDir(realPath, entries, w, r) + }, fi) + if err != nil { + return err + } + } + + return nil +} diff --git a/ext/go-plugin/vagrant/communicator/ssh/communicator_test.go b/ext/go-plugin/vagrant/communicator/ssh/communicator_test.go new file mode 100644 index 000000000..5533ba696 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/ssh/communicator_test.go @@ -0,0 +1,235 @@ +// +build !race + +package ssh + +import ( + "bytes" + "fmt" + "net" + "testing" + "time" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/communicator" + "golang.org/x/crypto/ssh" +) + +// private key for mock server +const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU +70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx +9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF +tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z +s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc +qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT ++IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea +riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH +D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh +atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT +b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN +ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M +MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4 +KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8 +e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1 +D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+ +3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj +orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw +64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc +XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc +QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g +/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ +I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk +gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl +NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw== +-----END RSA PRIVATE KEY-----` + +var serverConfig = &ssh.ServerConfig{ + PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { + if c.User() == "user" && string(pass) == "pass" { + return nil, nil + } + return nil, fmt.Errorf("password rejected for %q", c.User()) + }, +} + +func init() { + // Parse and set the private key of the server, required to accept connections + signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey)) + if err != nil { + panic("unable to parse private key: " + err.Error()) + } + serverConfig.AddHostKey(signer) +} + +func newMockLineServer(t *testing.T) string { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Unable to listen for connection: %s", err) + } + + go func() { + defer l.Close() + c, err := l.Accept() + if err != nil { + t.Errorf("Unable to accept incoming connection: %s", err) + } + defer c.Close() + conn, chans, _, err := ssh.NewServerConn(c, serverConfig) + if err != nil { + t.Logf("Handshaking error: %v", err) + } + t.Log("Accepted SSH connection") + for newChannel := range chans { + channel, _, err := newChannel.Accept() + if err != nil { + t.Errorf("Unable to accept channel.") + } + t.Log("Accepted channel") + + go func(channelType string) { + defer channel.Close() + conn.OpenChannel(channelType, nil) + }(newChannel.ChannelType()) + } + conn.Close() + }() + + return l.Addr().String() +} + +func newMockBrokenServer(t *testing.T) string { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("Unable tp listen for connection: %s", err) + } + + go func() { + defer l.Close() + c, err := l.Accept() + if err != nil { + t.Errorf("Unable to accept incoming connection: %s", err) + } + defer c.Close() + // This should block for a period of time longer than our timeout in + // the test case. That way we invoke a failure scenario. + t.Log("Block on handshaking for SSH connection") + time.Sleep(5 * time.Second) + }() + + return l.Addr().String() +} + +func TestCommIsCommunicator(t *testing.T) { + var raw interface{} + raw = &Communicator{} + if _, ok := raw.(communicator.Communicator); !ok { + t.Fatalf("Communicator must be a communicator") + } +} + +func TestNew_Invalid(t *testing.T) { + clientConfig := &ssh.ClientConfig{ + User: "user", + Auth: []ssh.AuthMethod{ + ssh.Password("i-am-invalid"), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + address := newMockLineServer(t) + conn := func() (net.Conn, error) { + conn, err := net.Dial("tcp", address) + if err != nil { + t.Errorf("Unable to accept incoming connection: %v", err) + } + return conn, err + } + + config := &Config{ + Connection: conn, + SSHConfig: clientConfig, + } + + comm, err := New(address, config) + if err != nil { + t.Fatalf("Failed to setup communicator: %s", err) + } + err = comm.Connect() + if err == nil { + t.Fatal("should have had an error connecting") + } +} + +func TestStart(t *testing.T) { + clientConfig := &ssh.ClientConfig{ + User: "user", + Auth: []ssh.AuthMethod{ + ssh.Password("pass"), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + address := newMockLineServer(t) + conn := func() (net.Conn, error) { + conn, err := net.Dial("tcp", address) + if err != nil { + t.Fatalf("unable to dial to remote side: %s", err) + } + return conn, err + } + + config := &Config{ + Connection: conn, + SSHConfig: clientConfig, + } + + client, err := New(address, config) + if err != nil { + t.Fatalf("error connecting to SSH: %s", err) + } + + cmd := &communicator.Cmd{ + Command: "echo foo", + Stdout: new(bytes.Buffer), + } + + client.Start(cmd) +} + +func TestHandshakeTimeout(t *testing.T) { + clientConfig := &ssh.ClientConfig{ + User: "user", + Auth: []ssh.AuthMethod{ + ssh.Password("pass"), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + address := newMockBrokenServer(t) + conn := func() (net.Conn, error) { + conn, err := net.Dial("tcp", address) + if err != nil { + t.Fatalf("unable to dial to remote side: %s", err) + } + return conn, err + } + + config := &Config{ + Connection: conn, + SSHConfig: clientConfig, + HandshakeTimeout: 50 * time.Millisecond, + } + + comm, err := New(address, config) + if err != nil { + t.Fatalf("Failed to setup communicator: %s", err) + } + err = comm.Connect() + if err != ErrHandshakeTimeout { + // Note: there's another error that can come back from this call: + // ssh: handshake failed: EOF + // This should appear in cases where the handshake fails because of + // malformed (or no) data sent back by the server, but should not happen + // in a timeout scenario. + t.Fatalf("Expected handshake timeout, got: %s", err) + } +} diff --git a/ext/go-plugin/vagrant/communicator/ssh/connect.go b/ext/go-plugin/vagrant/communicator/ssh/connect.go new file mode 100644 index 000000000..80bf0a9f4 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/ssh/connect.go @@ -0,0 +1,88 @@ +package ssh + +import ( + "fmt" + "net" + "time" + + "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" +) + +// ConnectFunc is a convenience method for returning a function +// that just uses net.Dial to communicate with the remote end that +// is suitable for use with the SSH communicator configuration. +func ConnectFunc(network, addr string) func() (net.Conn, error) { + return func() (net.Conn, error) { + c, err := net.DialTimeout(network, addr, 15*time.Second) + if err != nil { + return nil, err + } + + if tcpConn, ok := c.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(5 * time.Second) + } + + return c, nil + } +} + +// ProxyConnectFunc is a convenience method for returning a function +// that connects to a host using SOCKS5 proxy +func ProxyConnectFunc(socksProxy string, socksAuth *proxy.Auth, network, addr string) func() (net.Conn, error) { + return func() (net.Conn, error) { + // create a socks5 dialer + dialer, err := proxy.SOCKS5("tcp", socksProxy, socksAuth, proxy.Direct) + if err != nil { + return nil, fmt.Errorf("Can't connect to the proxy: %s", err) + } + + c, err := dialer.Dial(network, addr) + if err != nil { + return nil, err + } + + return c, nil + } +} + +// BastionConnectFunc is a convenience method for returning a function +// that connects to a host over a bastion connection. +func BastionConnectFunc( + bProto string, + bAddr string, + bConf *ssh.ClientConfig, + proto string, + addr string) func() (net.Conn, error) { + return func() (net.Conn, error) { + // Connect to the bastion + bastion, err := ssh.Dial(bProto, bAddr, bConf) + if err != nil { + return nil, fmt.Errorf("Error connecting to bastion: %s", err) + } + + // Connect through to the end host + conn, err := bastion.Dial(proto, addr) + if err != nil { + bastion.Close() + return nil, err + } + + // Wrap it up so we close both things properly + return &bastionConn{ + Conn: conn, + Bastion: bastion, + }, nil + } +} + +type bastionConn struct { + net.Conn + Bastion *ssh.Client +} + +func (c *bastionConn) Close() error { + c.Conn.Close() + return c.Bastion.Close() +} diff --git a/ext/go-plugin/vagrant/communicator/ssh/connection.go b/ext/go-plugin/vagrant/communicator/ssh/connection.go new file mode 100644 index 000000000..c3df04543 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/ssh/connection.go @@ -0,0 +1,30 @@ +package ssh + +import ( + "net" + "time" +) + +// timeoutConn wraps a net.Conn, and sets a deadline for every read +// and write operation. +type timeoutConn struct { + net.Conn + ReadTimeout time.Duration + WriteTimeout time.Duration +} + +func (c *timeoutConn) Read(b []byte) (int, error) { + err := c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout)) + if err != nil { + return 0, err + } + return c.Conn.Read(b) +} + +func (c *timeoutConn) Write(b []byte) (int, error) { + err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout)) + if err != nil { + return 0, err + } + return c.Conn.Write(b) +} diff --git a/ext/go-plugin/vagrant/communicator/ssh/password.go b/ext/go-plugin/vagrant/communicator/ssh/password.go new file mode 100644 index 000000000..b1bfedd64 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/ssh/password.go @@ -0,0 +1,25 @@ +package ssh + +import ( + "golang.org/x/crypto/ssh" +) + +// An implementation of ssh.KeyboardInteractiveChallenge that simply sends +// back the password for all questions. The questions are logged. +func PasswordKeyboardInteractive(password string) ssh.KeyboardInteractiveChallenge { + return func(user, instruction string, questions []string, echos []bool) ([]string, error) { + logger.Info("keyboard interactive challenge", "user", user, + "instructions", instruction) + for i, question := range questions { + logger.Info("challenge question", "number", i+1, "question", question) + } + + // Just send the password back for all questions + answers := make([]string, len(questions)) + for i := range answers { + answers[i] = password + } + + return answers, nil + } +} diff --git a/ext/go-plugin/vagrant/communicator/ssh/password_test.go b/ext/go-plugin/vagrant/communicator/ssh/password_test.go new file mode 100644 index 000000000..47a4c7782 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/ssh/password_test.go @@ -0,0 +1,28 @@ +package ssh + +import ( + "reflect" + "testing" + + "golang.org/x/crypto/ssh" +) + +func TestPasswordKeyboardInteractive_Impl(t *testing.T) { + var raw interface{} + raw = PasswordKeyboardInteractive("foo") + if _, ok := raw.(ssh.KeyboardInteractiveChallenge); !ok { + t.Fatal("PasswordKeyboardInteractive must implement KeyboardInteractiveChallenge") + } +} + +func TestPasswordKeyboardInteractive_Challenge(t *testing.T) { + p := PasswordKeyboardInteractive("foo") + result, err := p("foo", "bar", []string{"one", "two"}, nil) + if err != nil { + t.Fatalf("err not nil: %s", err) + } + + if !reflect.DeepEqual(result, []string{"foo", "foo"}) { + t.Fatalf("invalid password: %#v", result) + } +} diff --git a/ext/go-plugin/vagrant/communicator/winrm/communicator.go b/ext/go-plugin/vagrant/communicator/winrm/communicator.go new file mode 100644 index 000000000..a165030bc --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/winrm/communicator.go @@ -0,0 +1,257 @@ +package winrm + +import ( + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/communicator" + "github.com/masterzen/winrm" + "github.com/packer-community/winrmcp/winrmcp" +) + +var logger = vagrant.DefaultLogger().Named("communicator.winrm") + +// Communicator represents the WinRM communicator +type Communicator struct { + config *Config + client *winrm.Client + endpoint *winrm.Endpoint +} + +// New creates a new communicator implementation over WinRM. +func New(config *Config) (*Communicator, error) { + endpoint := &winrm.Endpoint{ + Host: config.Host, + Port: config.Port, + HTTPS: config.Https, + Insecure: config.Insecure, + + /* + TODO + HTTPS: connInfo.HTTPS, + Insecure: connInfo.Insecure, + CACert: connInfo.CACert, + */ + } + + // Create the client + params := *winrm.DefaultParameters + + if config.TransportDecorator != nil { + params.TransportDecorator = config.TransportDecorator + } + + params.Timeout = formatDuration(config.Timeout) + client, err := winrm.NewClientWithParameters( + endpoint, config.Username, config.Password, ¶ms) + if err != nil { + return nil, err + } + + return &Communicator{ + config: config, + client: client, + endpoint: endpoint, + }, nil +} + +func (c *Communicator) Connect() (err error) { + // Create the shell to verify the connection + logger.Debug("connecting to remote shell") + shell, err := c.client.CreateShell() + if err != nil { + logger.Error("connection failure", "error", err) + return + } + if err = shell.Close(); err != nil { + logger.Error("connection close failure", "error", err) + } + return +} + +// Start implementation of communicator.Communicator interface +func (c *Communicator) Start(rc *communicator.Cmd) error { + shell, err := c.client.CreateShell() + if err != nil { + return err + } + + logger.Info("starting remote command", "commmand", rc.Command) + + rc.Init() + cmd, err := shell.Execute(rc.Command) + if err != nil { + return err + } + + go runCommand(shell, cmd, rc) + return nil +} + +func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *communicator.Cmd) { + defer shell.Close() + var wg sync.WaitGroup + + copyFunc := func(w io.Writer, r io.Reader) { + defer wg.Done() + io.Copy(w, r) + } + + if rc.Stdout != nil && cmd.Stdout != nil { + wg.Add(1) + go copyFunc(rc.Stdout, cmd.Stdout) + } else { + logger.Warn("failed to read stdout", "command", rc.Command) + } + + if rc.Stderr != nil && cmd.Stderr != nil { + wg.Add(1) + go copyFunc(rc.Stderr, cmd.Stderr) + } else { + logger.Warn("failed to read stderr", "command", rc.Command) + } + + cmd.Wait() + wg.Wait() + + code := cmd.ExitCode() + logger.Info("command complete", "exitcode", code, "command", rc.Command) + rc.SetExitStatus(code, nil) +} + +// Upload implementation of communicator.Communicator interface +func (c *Communicator) Upload(path string, input io.Reader, fi *os.FileInfo) error { + wcp, err := c.newCopyClient() + if err != nil { + return fmt.Errorf("Was unable to create winrm client: %s", err) + } + if strings.HasSuffix(path, `\`) { + // path is a directory + path += filepath.Base((*fi).Name()) + } + logger.Info("uploading file", "path", path) + return wcp.Write(path, input) +} + +// UploadDir implementation of communicator.Communicator interface +func (c *Communicator) UploadDir(dst string, src string, exclude []string) error { + if !strings.HasSuffix(src, "/") { + dst = fmt.Sprintf("%s\\%s", dst, filepath.Base(src)) + } + logger.Info("uploading directory", "source", src, "destination", dst) + wcp, err := c.newCopyClient() + if err != nil { + return err + } + return wcp.Copy(src, dst) +} + +func (c *Communicator) Download(src string, dst io.Writer) error { + client, err := c.newWinRMClient() + if err != nil { + return err + } + + encodeScript := `$file=[System.IO.File]::ReadAllBytes("%s"); Write-Output $([System.Convert]::ToBase64String($file))` + + base64DecodePipe := &Base64Pipe{w: dst} + + cmd := winrm.Powershell(fmt.Sprintf(encodeScript, src)) + _, err = client.Run(cmd, base64DecodePipe, ioutil.Discard) + + return err +} + +func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error { + return fmt.Errorf("WinRM doesn't support download dir.") +} + +func (c *Communicator) getClientConfig() *winrmcp.Config { + return &winrmcp.Config{ + Auth: winrmcp.Auth{ + User: c.config.Username, + Password: c.config.Password, + }, + Https: c.config.Https, + Insecure: c.config.Insecure, + OperationTimeout: c.config.Timeout, + MaxOperationsPerShell: 15, // lowest common denominator + TransportDecorator: c.config.TransportDecorator, + } +} + +func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) { + addr := fmt.Sprintf("%s:%d", c.endpoint.Host, c.endpoint.Port) + clientConfig := c.getClientConfig() + return winrmcp.New(addr, clientConfig) +} + +func (c *Communicator) newWinRMClient() (*winrm.Client, error) { + conf := c.getClientConfig() + + // Shamelessly borrowed from the winrmcp client to ensure + // that the client is configured using the same defaulting behaviors that + // winrmcp uses even we we aren't using winrmcp. This ensures similar + // behavior between upload, download, and copy functions. We can't use the + // one generated by winrmcp because it isn't exported. + var endpoint *winrm.Endpoint + endpoint = &winrm.Endpoint{ + Host: c.endpoint.Host, + Port: c.endpoint.Port, + HTTPS: conf.Https, + Insecure: conf.Insecure, + TLSServerName: conf.TLSServerName, + CACert: conf.CACertBytes, + Timeout: conf.ConnectTimeout, + } + params := winrm.NewParameters( + winrm.DefaultParameters.Timeout, + winrm.DefaultParameters.Locale, + winrm.DefaultParameters.EnvelopeSize, + ) + + params.TransportDecorator = conf.TransportDecorator + params.Timeout = "PT3M" + + client, err := winrm.NewClientWithParameters( + endpoint, conf.Auth.User, conf.Auth.Password, params) + return client, err +} + +type Base64Pipe struct { + w io.Writer // underlying writer (file, buffer) +} + +func (d *Base64Pipe) ReadFrom(r io.Reader) (int64, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return 0, err + } + + var i int + i, err = d.Write(b) + + if err != nil { + return 0, err + } + + return int64(i), err +} + +func (d *Base64Pipe) Write(p []byte) (int, error) { + dst := make([]byte, base64.StdEncoding.DecodedLen(len(p))) + + decodedBytes, err := base64.StdEncoding.Decode(dst, p) + if err != nil { + return 0, err + } + + return d.w.Write(dst[0:decodedBytes]) +} diff --git a/ext/go-plugin/vagrant/communicator/winrm/communicator_test.go b/ext/go-plugin/vagrant/communicator/winrm/communicator_test.go new file mode 100644 index 000000000..7590ddc31 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/winrm/communicator_test.go @@ -0,0 +1,123 @@ +package winrm + +import ( + "bytes" + "io" + "strings" + "testing" + "time" + + "github.com/dylanmei/winrmtest" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/communicator" +) + +const PAYLOAD = "stuff" +const BASE64_ENCODED_PAYLOAD = "c3R1ZmY=" + +func newMockWinRMServer(t *testing.T) *winrmtest.Remote { + wrm := winrmtest.NewRemote() + + wrm.CommandFunc( + winrmtest.MatchText("echo foo"), + func(out, err io.Writer) int { + out.Write([]byte("foo")) + return 0 + }) + + wrm.CommandFunc( + winrmtest.MatchPattern(`^echo c29tZXRoaW5n >> ".*"$`), + func(out, err io.Writer) int { + return 0 + }) + + wrm.CommandFunc( + winrmtest.MatchPattern(`^echo `+BASE64_ENCODED_PAYLOAD+` >> ".*"$`), + func(out, err io.Writer) int { + return 0 + }) + + wrm.CommandFunc( + winrmtest.MatchPattern(`^powershell.exe -EncodedCommand .*$`), + func(out, err io.Writer) int { + out.Write([]byte(BASE64_ENCODED_PAYLOAD)) + return 0 + }) + + wrm.CommandFunc( + winrmtest.MatchText("powershell"), + func(out, err io.Writer) int { + return 0 + }) + wrm.CommandFunc( + winrmtest.MatchText(`powershell -Command "(Get-Item C:/Temp/vagrant.cmd) -is [System.IO.DirectoryInfo]"`), + func(out, err io.Writer) int { + out.Write([]byte("False")) + return 0 + }) + + return wrm +} + +func TestStart(t *testing.T) { + wrm := newMockWinRMServer(t) + defer wrm.Close() + + c, err := New(&Config{ + Host: wrm.Host, + Port: wrm.Port, + Username: "user", + Password: "pass", + Timeout: 30 * time.Second, + }) + if err != nil { + t.Fatalf("error creating communicator: %s", err) + } + + var cmd communicator.Cmd + stdout := new(bytes.Buffer) + cmd.Command = "echo foo" + cmd.Stdout = stdout + + err = c.Start(&cmd) + if err != nil { + t.Fatalf("error executing remote command: %s", err) + } + cmd.Wait() + + if stdout.String() != "foo" { + t.Fatalf("bad command response: expected %q, got %q", "foo", stdout.String()) + } +} + +func TestUpload(t *testing.T) { + wrm := newMockWinRMServer(t) + defer wrm.Close() + + c, err := New(&Config{ + Host: wrm.Host, + Port: wrm.Port, + Username: "user", + Password: "pass", + Timeout: 30 * time.Second, + }) + if err != nil { + t.Fatalf("error creating communicator: %s", err) + } + file := "C:/Temp/vagrant.cmd" + err = c.Upload(file, strings.NewReader(PAYLOAD), nil) + if err != nil { + t.Fatalf("error uploading file: %s", err) + } + + dest := new(bytes.Buffer) + err = c.Download(file, dest) + if err != nil { + t.Fatalf("error downloading file: %s", err) + } + downloadedPayload := dest.String() + + if downloadedPayload != PAYLOAD { + t.Fatalf("files are not equal: expected [%s] length: %v, got [%s] length %v", PAYLOAD, len(PAYLOAD), downloadedPayload, len(downloadedPayload)) + } + +} diff --git a/ext/go-plugin/vagrant/communicator/winrm/config.go b/ext/go-plugin/vagrant/communicator/winrm/config.go new file mode 100644 index 000000000..728336734 --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/winrm/config.go @@ -0,0 +1,19 @@ +package winrm + +import ( + "time" + + "github.com/masterzen/winrm" +) + +// Config is used to configure the WinRM connection +type Config struct { + Host string + Port int + Username string + Password string + Timeout time.Duration + Https bool + Insecure bool + TransportDecorator func() winrm.Transporter +} diff --git a/ext/go-plugin/vagrant/communicator/winrm/time.go b/ext/go-plugin/vagrant/communicator/winrm/time.go new file mode 100644 index 000000000..f8fb6fe8d --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/winrm/time.go @@ -0,0 +1,32 @@ +package winrm + +import ( + "fmt" + "time" +) + +// formatDuration formats the given time.Duration into an ISO8601 +// duration string. +func formatDuration(duration time.Duration) string { + // We're not supporting negative durations + if duration.Seconds() <= 0 { + return "PT0S" + } + + h := int(duration.Hours()) + m := int(duration.Minutes()) - (h * 60) + s := int(duration.Seconds()) - (h*3600 + m*60) + + res := "PT" + if h > 0 { + res = fmt.Sprintf("%s%dH", res, h) + } + if m > 0 { + res = fmt.Sprintf("%s%dM", res, m) + } + if s > 0 { + res = fmt.Sprintf("%s%dS", res, s) + } + + return res +} diff --git a/ext/go-plugin/vagrant/communicator/winrm/time_test.go b/ext/go-plugin/vagrant/communicator/winrm/time_test.go new file mode 100644 index 000000000..4daf4cedf --- /dev/null +++ b/ext/go-plugin/vagrant/communicator/winrm/time_test.go @@ -0,0 +1,36 @@ +package winrm + +import ( + "testing" + "time" +) + +func TestFormatDuration(t *testing.T) { + // Test complex duration with hours, minutes, seconds + d := time.Duration(3701) * time.Second + s := formatDuration(d) + if s != "PT1H1M41S" { + t.Fatalf("bad ISO 8601 duration string: %s", s) + } + + // Test only minutes duration + d = time.Duration(20) * time.Minute + s = formatDuration(d) + if s != "PT20M" { + t.Fatalf("bad ISO 8601 duration string for 20M: %s", s) + } + + // Test only seconds + d = time.Duration(1) * time.Second + s = formatDuration(d) + if s != "PT1S" { + t.Fatalf("bad ISO 8601 duration string for 1S: %s", s) + } + + // Test negative duration (unsupported) + d = time.Duration(-1) * time.Second + s = formatDuration(d) + if s != "PT0S" { + t.Fatalf("bad ISO 8601 duration string for negative: %s", s) + } +} diff --git a/ext/go-plugin/vagrant/config.go b/ext/go-plugin/vagrant/config.go new file mode 100644 index 000000000..9761121be --- /dev/null +++ b/ext/go-plugin/vagrant/config.go @@ -0,0 +1,25 @@ +package vagrant + +import ( + "context" +) + +type Config interface { + ConfigAttributes() (attrs []string, err error) + ConfigLoad(ctx context.Context, data map[string]interface{}) (loaddata map[string]interface{}, err error) + ConfigValidate(ctx context.Context, data map[string]interface{}, m *Machine) (errors []string, err error) + ConfigFinalize(ctx context.Context, data map[string]interface{}) (finaldata map[string]interface{}, err error) +} + +type NoConfig struct{} + +func (c *NoConfig) ConfigAttributes() (a []string, e error) { return } +func (c *NoConfig) ConfigLoad(context.Context, map[string]interface{}) (d map[string]interface{}, e error) { + return +} +func (c *NoConfig) ConfigValidate(context.Context, map[string]interface{}, *Machine) (es []string, e error) { + return +} +func (c *NoConfig) ConfigFinalize(context.Context, map[string]interface{}) (f map[string]interface{}, e error) { + return +} diff --git a/ext/go-plugin/vagrant/environment.go b/ext/go-plugin/vagrant/environment.go new file mode 100644 index 000000000..4f7396fe9 --- /dev/null +++ b/ext/go-plugin/vagrant/environment.go @@ -0,0 +1,64 @@ +package vagrant + +import ( + "encoding/json" + "io" + "os" +) + +type Environment struct { + ActiveMachines map[string]string `json:"active_machines,omitempty"` + AliasesPath string `json:"aliases_path,omitempty"` + BoxesPath string `json:"boxes_path,omitempty"` + CWD string `json:"cwd,omitempty"` + DataDir string `json:"data_dir,omitempty"` + DefaultPrivateKeyPath string `json:"default_private_key_path,omitempty"` + GemsPath string `json:"gems_path,omitempty"` + HomePath string `json:"home_path,omitempty"` + LocalDataPath string `json:"local_data_path,omitempty"` + MachineNames []string `json:"machine_names,omitempty"` + PrimaryMachineName string `json:"primary_machine_name,omitempty"` + RootPath string `json:"root_path,omitempty"` + TmpPath string `json:"tmp_path,omitempty"` + VagrantfileName string `json:"vagrantfile_name,omitempty"` + UI Ui `json:"-"` +} + +func DumpEnvironment(e *Environment) (s string, err error) { + DefaultLogger().Debug("dumping environment to serialized data") + b, err := json.Marshal(e) + if err != nil { + DefaultLogger().Error("environment dump failure", "error", err) + return + } + s = string(b) + return +} + +func LoadEnvironment(edata string, ios IOServer) (e *Environment, err error) { + DefaultLogger().Debug("loading environment from serialized data") + e = &Environment{} + err = json.Unmarshal([]byte(edata), e) + if err != nil { + return + } + var stdout io.Writer + var stderr io.Writer + if ios == nil { + stdout = os.Stdout + stderr = os.Stderr + } else { + stdout = &IOWriter{target: "stdout", srv: ios} + stderr = &IOWriter{target: "stderr", srv: ios} + } + e.UI = &TargetedUi{ + Target: "vagrant", + Ui: &ColoredUi{ + ErrorColor: UiColorRed, + Ui: &BasicUi{ + Reader: os.Stdin, + Writer: stdout, + ErrorWriter: stderr}, + }} + return +} diff --git a/ext/go-plugin/vagrant/environment_test.go b/ext/go-plugin/vagrant/environment_test.go new file mode 100644 index 000000000..118902cf6 --- /dev/null +++ b/ext/go-plugin/vagrant/environment_test.go @@ -0,0 +1,63 @@ +package vagrant + +import ( + "strings" + "testing" +) + +func TestLoadEnvironment(t *testing.T) { + env, err := LoadEnvironment("{}", nil) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + if env.UI == nil { + t.Fatalf("no UI configured for environment") + } +} + +func TestBadLoadEnvironment(t *testing.T) { + _, err := LoadEnvironment("ack", nil) + if err == nil { + t.Fatalf("expected load error but none provided") + } +} + +func TestLoadEnvironmentUIStdout(t *testing.T) { + iosrv := buildio() + env, err := LoadEnvironment("{}", iosrv) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + go func() { env.UI.Info("test string") }() + str := <-iosrv.Streams()["stdout"] + if !strings.Contains(str, "test string") { + t.Fatalf("unexpected output: %s", str) + } +} + +func TestLoadEnvironmentUIStderr(t *testing.T) { + iosrv := buildio() + env, err := LoadEnvironment("{}", iosrv) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + go func() { env.UI.Error("test string") }() + str, err := iosrv.Read("stderr") + if !strings.Contains(str, "test string") { + t.Fatalf("unexpected output: %s", str) + } +} + +func TestDumpEnvironment(t *testing.T) { + env, err := LoadEnvironment("{}", nil) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + d, err := DumpEnvironment(env) + if err != nil { + t.Fatalf("unexpected dump error: %s", err) + } + if d != "{}" { + t.Fatalf("unexpected dump information: %s", d) + } +} diff --git a/ext/go-plugin/vagrant/global.go b/ext/go-plugin/vagrant/global.go new file mode 100644 index 000000000..5f4c6bc82 --- /dev/null +++ b/ext/go-plugin/vagrant/global.go @@ -0,0 +1,17 @@ +package vagrant + +import ( + hclog "github.com/hashicorp/go-hclog" +) + +var GlobalIOServer *IOServer + +var defaultLogger = hclog.Default().Named("vagrant") + +func DefaultLogger() hclog.Logger { + return defaultLogger +} + +func SetDefaultLogger(l hclog.Logger) { + defaultLogger = l +} diff --git a/ext/go-plugin/vagrant/io.go b/ext/go-plugin/vagrant/io.go new file mode 100644 index 000000000..9c96a37b1 --- /dev/null +++ b/ext/go-plugin/vagrant/io.go @@ -0,0 +1,53 @@ +package vagrant + +import ( + "errors" +) + +type StreamIO interface { + Read(target string) (content string, err error) + Write(content, target string) (n int, err error) +} + +type IOServer interface { + Streams() map[string]chan (string) + StreamIO +} + +type IOSrv struct { + Targets map[string]chan (string) +} + +func (i *IOSrv) Streams() map[string]chan (string) { + return i.Targets +} + +type IOWriter struct { + target string + srv IOServer +} + +func (i *IOWriter) Write(b []byte) (n int, err error) { + content := string(b) + n, err = i.srv.Write(content, i.target) + return +} + +func (i *IOSrv) Read(target string) (content string, err error) { + if _, ok := i.Streams()[target]; !ok { + err = errors.New("Unknown target defined") + return + } + content = <-i.Streams()[target] + return +} + +func (i *IOSrv) Write(content, target string) (n int, err error) { + if _, ok := i.Streams()[target]; !ok { + err = errors.New("Unknown target defined") + return + } + i.Streams()[target] <- content + n = len(content) + return +} diff --git a/ext/go-plugin/vagrant/io_test.go b/ext/go-plugin/vagrant/io_test.go new file mode 100644 index 000000000..c0ae959bb --- /dev/null +++ b/ext/go-plugin/vagrant/io_test.go @@ -0,0 +1,48 @@ +package vagrant + +import ( + "testing" +) + +func buildio() IOServer { + return &IOSrv{ + Targets: map[string]chan (string){ + "stdout": make(chan string), + "stderr": make(chan string)}} +} + +func TestIOSrvWrite(t *testing.T) { + iosrv := buildio() + var i int + go func() { i, _ = iosrv.Write("test string", "stdout") }() + _, _ = iosrv.Read("stdout") + if i != len("test string") { + t.Fatalf("unexpected write bytes %d != %d", + len("test string"), i) + } +} + +func TestIOSrvRead(t *testing.T) { + iosrv := buildio() + go func() { _, _ = iosrv.Write("test string", "stdout") }() + r, _ := iosrv.Read("stdout") + if r != "test string" { + t.Fatalf("unexpected read result: %s", r) + } +} + +func TestIOSrvWriteBadTarget(t *testing.T) { + iosrv := buildio() + _, err := iosrv.Write("test string", "stdno") + if err == nil { + t.Fatalf("expected error on write") + } +} + +func TestIOSrvReadBadTarget(t *testing.T) { + iosrv := buildio() + _, err := iosrv.Read("stdno") + if err == nil { + t.Fatalf("expected error on read") + } +} diff --git a/ext/go-plugin/vagrant/machine.go b/ext/go-plugin/vagrant/machine.go new file mode 100644 index 000000000..a13a060a4 --- /dev/null +++ b/ext/go-plugin/vagrant/machine.go @@ -0,0 +1,59 @@ +package vagrant + +import ( + "encoding/json" + "io" + "os" +) + +type Machine struct { + Box Box `json:"box"` + Config map[string]interface{} `json:"config"` + DataDir string `json:"data_dir,omitempty"` + Env Environment `json:"environment"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + ProviderConfig map[string]interface{} `json:"provider_config"` + ProviderName string `json:"provider_name,omitempty"` + ProviderOptions map[string]string `json:"provider_options"` + UI Ui `json:"-"` +} + +func DumpMachine(m *Machine) (s string, err error) { + DefaultLogger().Debug("dumping machine to serialized data") + b, err := json.Marshal(m) + if err != nil { + DefaultLogger().Debug("machine dump failure", "error", err) + return + } + s = string(b) + return +} + +func LoadMachine(mdata string, ios IOServer) (m *Machine, err error) { + DefaultLogger().Debug("loading machine from serialized data") + m = &Machine{} + err = json.Unmarshal([]byte(mdata), m) + if err != nil { + return + } + var stdout io.Writer + var stderr io.Writer + if ios == nil { + stdout = os.Stdout + stderr = os.Stderr + } else { + stdout = &IOWriter{target: "stdout", srv: ios} + stderr = &IOWriter{target: "stderr", srv: ios} + } + m.UI = &TargetedUi{ + Target: m.Name, + Ui: &ColoredUi{ + ErrorColor: UiColorRed, + Ui: &BasicUi{ + Reader: os.Stdin, + Writer: stdout, + ErrorWriter: stderr}, + }} + return +} diff --git a/ext/go-plugin/vagrant/machine_state.go b/ext/go-plugin/vagrant/machine_state.go new file mode 100644 index 000000000..d80349179 --- /dev/null +++ b/ext/go-plugin/vagrant/machine_state.go @@ -0,0 +1,7 @@ +package vagrant + +type MachineState struct { + Id string `json:"id"` + ShortDesc string `json:"short_description"` + LongDesc string `json:"long_description"` +} diff --git a/ext/go-plugin/vagrant/machine_test.go b/ext/go-plugin/vagrant/machine_test.go new file mode 100644 index 000000000..e3675032a --- /dev/null +++ b/ext/go-plugin/vagrant/machine_test.go @@ -0,0 +1,53 @@ +package vagrant + +import ( + "strings" + "testing" +) + +func TestMachineLoad(t *testing.T) { + _, err := LoadMachine("{}", nil) + if err != nil { + t.Fatalf("failed to load machine: %s", err) + } +} + +func TestMachineDump(t *testing.T) { + m, err := LoadMachine("{}", nil) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + _, err = DumpMachine(m) + if err != nil { + t.Fatalf("failed to dump machine: %s", err) + } +} + +func TestMachineUI(t *testing.T) { + iosrv := buildio() + m, err := LoadMachine("{}", iosrv) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + go func() { m.UI.Info("test string") }() + r, _ := iosrv.Read("stdout") + if !strings.Contains(r, "test string") { + t.Fatalf("unexpected read result: %s", r) + } +} + +func TestMachineUINamed(t *testing.T) { + iosrv := buildio() + m, err := LoadMachine("{\"name\":\"plugintest\"}", iosrv) + if err != nil { + t.Fatalf("unexpected load error: %s", err) + } + go func() { m.UI.Info("test string") }() + r, _ := iosrv.Read("stdout") + if !strings.Contains(r, "test string") { + t.Fatalf("unexpected read result: %s", r) + } + if !strings.Contains(r, "plugintest") { + t.Fatalf("output does not contain name: %s", r) + } +} diff --git a/ext/go-plugin/vagrant/plugin/base.go b/ext/go-plugin/vagrant/plugin/base.go new file mode 100644 index 000000000..a4d0fcd43 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/base.go @@ -0,0 +1,294 @@ +package plugin + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + hclog "github.com/hashicorp/go-hclog" + go_plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +var ( + Handshake = go_plugin.HandshakeConfig{ + MagicCookieKey: "VAGRANT_PLUGIN_MAGIC_COOKIE", + MagicCookieValue: "1561a662a76642f98df77ad025aa13a9b16225d93f90475e91090fbe577317ed", + ProtocolVersion: 1} + ErrPluginShutdown = errors.New("plugin has shutdown") +) + +type RemotePlugin interface { + Impl() interface{} +} + +type RemoteConfig struct { + Client *go_plugin.Client + Config vagrant.Config +} + +func (r *RemoteConfig) Impl() interface{} { + return r.Config +} + +type RemoteProvider struct { + Client *go_plugin.Client + Provider Provider +} + +func (r *RemoteProvider) Impl() interface{} { + return r.Provider +} + +type RemoteGuestCapabilities struct { + Client *go_plugin.Client + GuestCapabilities vagrant.GuestCapabilities +} + +func (r *RemoteGuestCapabilities) Impl() interface{} { + return r.GuestCapabilities +} + +type RemoteHostCapabilities struct { + Client *go_plugin.Client + HostCapabilities vagrant.HostCapabilities +} + +func (r *RemoteHostCapabilities) Impl() interface{} { + return r.HostCapabilities +} + +type RemoteProviderCapabilities struct { + Client *go_plugin.Client + ProviderCapabilities vagrant.ProviderCapabilities +} + +func (r *RemoteProviderCapabilities) Impl() interface{} { + return r.ProviderCapabilities +} + +type RemoteSyncedFolder struct { + Client *go_plugin.Client + SyncedFolder vagrant.SyncedFolder +} + +func (r *RemoteSyncedFolder) Impl() interface{} { + return r.SyncedFolder +} + +type VagrantPlugin struct { + Providers map[string]*RemoteProvider + SyncedFolders map[string]*RemoteSyncedFolder + PluginDirectories []string + PluginLookup func(name, kind string) (p interface{}, err error) + Logger hclog.Logger +} + +func VagrantPluginInit() *VagrantPlugin { + v := &VagrantPlugin{ + PluginDirectories: []string{}, + Providers: map[string]*RemoteProvider{}, + SyncedFolders: map[string]*RemoteSyncedFolder{}, + Logger: vagrant.DefaultLogger().Named("go-plugin")} + v.PluginLookup = v.DefaultPluginLookup + return v +} + +func (v *VagrantPlugin) DefaultPluginLookup(name, kind string) (p interface{}, err error) { + switch kind { + case "provider": + p = v.Providers[name].Impl() + case "synced_folder": + p = v.SyncedFolders[name].Impl() + default: + err = errors.New("invalid plugin type") + return + } + if p == nil { + err = errors.New(fmt.Sprintf("Failed to locate %s plugin of type %s", name, kind)) + } + return +} + +func (v *VagrantPlugin) LoadPlugins(pluginPath string) error { + for _, p := range v.PluginDirectories { + if p == pluginPath { + v.Logger.Error("plugin directory path already loaded", "path", pluginPath) + return errors.New("plugin directory already loaded") + } + } + v.PluginDirectories = append(v.PluginDirectories, pluginPath) + if err := v.LoadProviders(pluginPath); err != nil { + return err + } + if err := v.LoadSyncedFolders(pluginPath); err != nil { + return err + } + return nil +} + +func (v *VagrantPlugin) LoadProviders(pluginPath string) error { + providerPaths, err := go_plugin.Discover("*_provider", pluginPath) + if err != nil { + v.Logger.Error("error during plugin discovery", "type", "provider", + "error", err, "path", pluginPath) + return err + } + for _, providerPath := range providerPaths { + v.Logger.Info("loading provider plugin", "path", providerPath) + + client := go_plugin.NewClient(&go_plugin.ClientConfig{ + AllowedProtocols: []go_plugin.Protocol{go_plugin.ProtocolGRPC}, + Logger: v.Logger, + HandshakeConfig: Handshake, + Cmd: exec.Command(providerPath), + VersionedPlugins: map[int]go_plugin.PluginSet{ + 2: {"provider": &ProviderPlugin{}}}}) + gclient, err := client.Client() + if err != nil { + v.Logger.Error("error loading provider client", "error", err, "path", providerPath) + return err + } + raw, err := gclient.Dispense("provider") + if err != nil { + v.Logger.Error("error loading provider plugin", "error", err, "path", providerPath) + return err + } + prov := raw.(Provider) + n := prov.Name() + v.Providers[n] = &RemoteProvider{ + Client: client, + Provider: prov} + v.Logger.Info("plugin loaded", "type", "provider", "name", n, "path", providerPath) + go v.StreamIO("stdout", prov, n, "provider") + go v.StreamIO("stderr", prov, n, "provider") + } + return nil +} + +func (v *VagrantPlugin) LoadSyncedFolders(pluginPath string) error { + folderPaths, err := go_plugin.Discover("*_synced_folder", pluginPath) + if err != nil { + v.Logger.Error("error during plugin discovery", "type", "synced_folder", + "error", err, "path", pluginPath) + return err + } + for _, folderPath := range folderPaths { + v.Logger.Info("loading synced_folder plugin", "path", folderPath) + + client := go_plugin.NewClient(&go_plugin.ClientConfig{ + AllowedProtocols: []go_plugin.Protocol{go_plugin.ProtocolGRPC}, + Logger: v.Logger, + HandshakeConfig: Handshake, + Cmd: exec.Command(folderPath), + VersionedPlugins: map[int]go_plugin.PluginSet{ + 2: {"synced_folders": &SyncedFolderPlugin{}}}}) + gclient, err := client.Client() + if err != nil { + v.Logger.Error("error loading synced_folder client", "error", err, "path", folderPath) + return err + } + raw, err := gclient.Dispense("synced_folder") + if err != nil { + v.Logger.Error("error loading synced_folder plugin", "error", err, "path", folderPath) + return err + } + fold := raw.(SyncedFolder) + n := fold.Name() + v.SyncedFolders[n] = &RemoteSyncedFolder{ + Client: client, + SyncedFolder: fold} + v.Logger.Info("plugin loaded", "type", "synced_folder", "name", n, "path", folderPath) + go v.StreamIO("stdout", fold, n, "synced_folder") + go v.StreamIO("stderr", fold, n, "synced_folder") + } + return nil +} + +func (v *VagrantPlugin) StreamIO(target string, i vagrant.IOServer, name, kind string) { + v.Logger.Info("starting plugin IO streaming", "target", target, "plugin", name, "type", kind) + for { + str, err := i.Read(target) + if err != nil { + v.Logger.Error("plugin IO streaming failure", "target", target, "plugin", name, + "type", kind, "error", err) + break + } + v.Logger.Debug("received plugin IO content", "target", target, "plugin", name, + "type", kind, "content", str) + if target == "stdout" { + os.Stdout.Write([]byte(str)) + } else if target == "stderr" { + os.Stderr.Write([]byte(str)) + } + } + v.Logger.Info("completed plugin IO streaming", "target", target, "plugin", name, "type", kind) +} + +func (v *VagrantPlugin) Kill() { + v.Logger.Debug("killing all running plugins") + for n, p := range v.Providers { + v.Logger.Debug("killing plugin", "name", n, "type", "provider") + p.Client.Kill() + v.Logger.Info("plugin killed", "name", n, "type", "provider") + } + for n, p := range v.SyncedFolders { + v.Logger.Debug("killing plugin", "name", n, "type", "synced_folder") + p.Client.Kill() + v.Logger.Info("plugin killed", "name", n, "type", "synced_folder") + } +} + +// Helper used for inspect GRPC related errors and providing "correct" +// error message +func handleGrpcError(err error, pluginCtx context.Context, reqCtx context.Context) error { + // If there was no error then nothing to process + if err == nil { + return nil + } + + // If a request context is provided, check that it + // was not canceled or timed out. If no context + // provided, stub one for later. + if reqCtx != nil { + s := status.FromContextError(reqCtx.Err()) + switch s.Code() { + case codes.Canceled: + return context.Canceled + case codes.DeadlineExceeded: + return context.DeadlineExceeded + } + } else { + reqCtx = context.Background() + } + + s, ok := status.FromError(err) + if ok && (s.Code() == codes.Unavailable || s.Code() == codes.Canceled) { + select { + case <-pluginCtx.Done(): + err = ErrPluginShutdown + case <-reqCtx.Done(): + err = reqCtx.Err() + select { + case <-pluginCtx.Done(): + err = ErrPluginShutdown + default: + } + case <-time.After(5): + return errors.New("exceeded context check timeout - " + err.Error()) + } + return err + } else if s != nil && s.Message() != "" { + // Extract actual error message received + // and create new error + return errors.New(s.Message()) + } + + return err +} diff --git a/ext/go-plugin/vagrant/plugin/capabilities.go b/ext/go-plugin/vagrant/plugin/capabilities.go new file mode 100644 index 000000000..0ad6f522f --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/capabilities.go @@ -0,0 +1,365 @@ +package plugin + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc" + + go_plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/plugin/proto" + + "github.com/LK4D4/joincontext" +) + +type GuestCapabilities interface { + vagrant.GuestCapabilities + Meta +} + +type GuestCapabilitiesPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl GuestCapabilities +} + +func (g *GuestCapabilitiesPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + g.Impl.Init() + vagrant_proto.RegisterGuestCapabilitiesServer(s, &GRPCGuestCapabilitiesServer{ + Impl: g.Impl, + GRPCIOServer: GRPCIOServer{ + Impl: g.Impl}}) + return nil +} + +func (g *GuestCapabilitiesPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + client := vagrant_proto.NewGuestCapabilitiesClient(c) + return &GRPCGuestCapabilitiesClient{ + client: client, + doneCtx: ctx, + GRPCIOClient: GRPCIOClient{ + client: client, + doneCtx: ctx}}, nil +} + +type GRPCGuestCapabilitiesServer struct { + GRPCIOServer + Impl GuestCapabilities +} + +func (s *GRPCGuestCapabilitiesServer) GuestCapabilities(ctx context.Context, req *vagrant_proto.Empty) (resp *vagrant_proto.SystemCapabilityList, err error) { + resp = &vagrant_proto.SystemCapabilityList{} + r, err := s.Impl.GuestCapabilities() + if err != nil { + return + } + for _, cap := range r { + rcap := &vagrant_proto.SystemCapability{Name: cap.Name, Platform: cap.Platform} + resp.Capabilities = append(resp.Capabilities, rcap) + } + return +} + +func (s *GRPCGuestCapabilitiesServer) GuestCapability(ctx context.Context, req *vagrant_proto.GuestCapabilityRequest) (resp *vagrant_proto.GenericResponse, err error) { + resp = &vagrant_proto.GenericResponse{} + var args interface{} + if err = json.Unmarshal([]byte(req.Arguments), &args); err != nil { + return + } + machine, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + cap := &vagrant.SystemCapability{ + Name: req.Capability.Name, + Platform: req.Capability.Platform} + r, err := s.Impl.GuestCapability(ctx, cap, args, machine) + result, err := json.Marshal(r) + if err != nil { + return + } + resp.Result = string(result) + return +} + +type GRPCGuestCapabilitiesClient struct { + GRPCCoreClient + GRPCIOClient + client vagrant_proto.GuestCapabilitiesClient + doneCtx context.Context +} + +func (c *GRPCGuestCapabilitiesClient) GuestCapabilities() (caps []vagrant.SystemCapability, err error) { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.GuestCapabilities(jctx, &vagrant_proto.Empty{}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + caps = make([]vagrant.SystemCapability, len(resp.Capabilities)) + for i := 0; i < len(resp.Capabilities); i++ { + cap := vagrant.SystemCapability{ + Name: resp.Capabilities[i].Name, + Platform: resp.Capabilities[i].Platform} + caps[i] = cap + } + return +} + +func (c *GRPCGuestCapabilitiesClient) GuestCapability(ctx context.Context, cap *vagrant.SystemCapability, args interface{}, machine *vagrant.Machine) (result interface{}, err error) { + a, err := json.Marshal(args) + if err != nil { + return + } + m, err := vagrant.DumpMachine(machine) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.GuestCapability(jctx, &vagrant_proto.GuestCapabilityRequest{ + Capability: &vagrant_proto.SystemCapability{Name: cap.Name, Platform: cap.Platform}, + Machine: m, + Arguments: string(a)}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + err = json.Unmarshal([]byte(resp.Result), &result) + return +} + +type HostCapabilities interface { + vagrant.HostCapabilities + Meta +} + +type HostCapabilitiesPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl HostCapabilities +} + +func (h *HostCapabilitiesPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + h.Impl.Init() + vagrant_proto.RegisterHostCapabilitiesServer(s, &GRPCHostCapabilitiesServer{ + Impl: h.Impl, + GRPCIOServer: GRPCIOServer{ + Impl: h.Impl}}) + return nil +} + +func (h *HostCapabilitiesPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + client := vagrant_proto.NewHostCapabilitiesClient(c) + return &GRPCHostCapabilitiesClient{ + client: client, + doneCtx: ctx, + GRPCIOClient: GRPCIOClient{ + client: client, + doneCtx: ctx}}, nil +} + +type GRPCHostCapabilitiesServer struct { + GRPCIOServer + Impl HostCapabilities +} + +func (s *GRPCHostCapabilitiesServer) HostCapabilities(ctx context.Context, req *vagrant_proto.Empty) (resp *vagrant_proto.SystemCapabilityList, err error) { + resp = &vagrant_proto.SystemCapabilityList{} + r, err := s.Impl.HostCapabilities() + if err != nil { + return + } + for _, cap := range r { + rcap := &vagrant_proto.SystemCapability{Name: cap.Name, Platform: cap.Platform} + resp.Capabilities = append(resp.Capabilities, rcap) + } + return +} + +func (s *GRPCHostCapabilitiesServer) HostCapability(ctx context.Context, req *vagrant_proto.HostCapabilityRequest) (resp *vagrant_proto.GenericResponse, err error) { + resp = &vagrant_proto.GenericResponse{} + var args interface{} + if err = json.Unmarshal([]byte(req.Arguments), &args); err != nil { + return + } + env, err := vagrant.LoadEnvironment(req.Environment, s.Impl) + if err != nil { + return + } + cap := &vagrant.SystemCapability{ + Name: req.Capability.Name, + Platform: req.Capability.Platform} + + r, err := s.Impl.HostCapability(ctx, cap, args, env) + result, err := json.Marshal(r) + if err != nil { + return + } + resp.Result = string(result) + return +} + +type GRPCHostCapabilitiesClient struct { + GRPCCoreClient + GRPCIOClient + client vagrant_proto.HostCapabilitiesClient + doneCtx context.Context +} + +func (c *GRPCHostCapabilitiesClient) HostCapabilities() (caps []vagrant.SystemCapability, err error) { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.HostCapabilities(jctx, &vagrant_proto.Empty{}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + caps = make([]vagrant.SystemCapability, len(resp.Capabilities)) + for i := 0; i < len(resp.Capabilities); i++ { + cap := vagrant.SystemCapability{ + Name: resp.Capabilities[i].Name, + Platform: resp.Capabilities[i].Platform} + caps[i] = cap + } + return +} + +func (c *GRPCHostCapabilitiesClient) HostCapability(ctx context.Context, cap *vagrant.SystemCapability, args interface{}, env *vagrant.Environment) (result interface{}, err error) { + a, err := json.Marshal(args) + if err != nil { + return + } + e, err := vagrant.DumpEnvironment(env) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.HostCapability(jctx, &vagrant_proto.HostCapabilityRequest{ + Capability: &vagrant_proto.SystemCapability{ + Name: cap.Name, + Platform: cap.Platform}, + Environment: e, + Arguments: string(a)}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + err = json.Unmarshal([]byte(resp.Result), &result) + return +} + +type ProviderCapabilities interface { + vagrant.ProviderCapabilities + Meta +} + +type ProviderCapabilitiesPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl ProviderCapabilities +} + +func (p *ProviderCapabilitiesPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + p.Impl.Init() + vagrant_proto.RegisterProviderCapabilitiesServer(s, &GRPCProviderCapabilitiesServer{ + Impl: p.Impl, + GRPCIOServer: GRPCIOServer{ + Impl: p.Impl}}) + return nil +} + +func (p *ProviderCapabilitiesPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + client := vagrant_proto.NewProviderCapabilitiesClient(c) + return &GRPCProviderCapabilitiesClient{ + client: client, + doneCtx: ctx, + GRPCIOClient: GRPCIOClient{ + client: client, + doneCtx: ctx}}, nil +} + +type GRPCProviderCapabilitiesServer struct { + GRPCIOServer + Impl ProviderCapabilities +} + +func (s *GRPCProviderCapabilitiesServer) ProviderCapabilities(ctx context.Context, req *vagrant_proto.Empty) (resp *vagrant_proto.ProviderCapabilityList, err error) { + resp = &vagrant_proto.ProviderCapabilityList{} + r, err := s.Impl.ProviderCapabilities() + if err != nil { + return + } + for _, cap := range r { + rcap := &vagrant_proto.ProviderCapability{Name: cap.Name, Provider: cap.Provider} + resp.Capabilities = append(resp.Capabilities, rcap) + } + return +} + +func (s *GRPCProviderCapabilitiesServer) ProviderCapability(ctx context.Context, req *vagrant_proto.ProviderCapabilityRequest) (resp *vagrant_proto.GenericResponse, err error) { + resp = &vagrant_proto.GenericResponse{} + var args interface{} + if err = json.Unmarshal([]byte(req.Arguments), &args); err != nil { + return + } + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + cap := &vagrant.ProviderCapability{ + Name: req.Capability.Name, + Provider: req.Capability.Provider} + + r, err := s.Impl.ProviderCapability(ctx, cap, args, m) + if err != nil { + return + } + result, err := json.Marshal(r) + if err != nil { + return + } + resp.Result = string(result) + return +} + +type GRPCProviderCapabilitiesClient struct { + GRPCCoreClient + GRPCIOClient + client vagrant_proto.ProviderCapabilitiesClient + doneCtx context.Context +} + +func (c *GRPCProviderCapabilitiesClient) ProviderCapabilities() (caps []vagrant.ProviderCapability, err error) { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.ProviderCapabilities(jctx, &vagrant_proto.Empty{}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + caps = make([]vagrant.ProviderCapability, len(resp.Capabilities)) + for i := 0; i < len(resp.Capabilities); i++ { + cap := vagrant.ProviderCapability{ + Name: resp.Capabilities[i].Name, + Provider: resp.Capabilities[i].Provider} + caps[i] = cap + } + return +} + +func (c *GRPCProviderCapabilitiesClient) ProviderCapability(ctx context.Context, cap *vagrant.ProviderCapability, args interface{}, machine *vagrant.Machine) (result interface{}, err error) { + a, err := json.Marshal(args) + if err != nil { + return + } + m, err := vagrant.DumpMachine(machine) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.ProviderCapability(jctx, &vagrant_proto.ProviderCapabilityRequest{ + Capability: &vagrant_proto.ProviderCapability{ + Name: cap.Name, + Provider: cap.Provider}, + Machine: m, + Arguments: string(a)}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + err = json.Unmarshal([]byte(resp.Result), &result) + return +} diff --git a/ext/go-plugin/vagrant/plugin/capabilities_test.go b/ext/go-plugin/vagrant/plugin/capabilities_test.go new file mode 100644 index 000000000..583de5510 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/capabilities_test.go @@ -0,0 +1,499 @@ +package plugin + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +func TestCapabilities_GuestCapabilities(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &GuestCapabilitiesPlugin{Impl: &MockGuestCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(GuestCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + resp, err := impl.GuestCapabilities() + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if len(resp) != 1 { + t.Fatalf("length %d != 1", len(resp)) + } + if resp[0].Name != "test_cap" { + t.Errorf("name - %s != test_cap", resp[0].Name) + } + if resp[0].Platform != "testOS" { + t.Errorf("platform - %s != testOS", resp[0].Platform) + } +} + +func TestCapabilities_GuestCapability(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &GuestCapabilitiesPlugin{Impl: &MockGuestCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(GuestCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + m := &vagrant.Machine{} + args := []string{"test_value", "next_test_value"} + + resp, err := impl.GuestCapability(context.Background(), cap, args, m) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + result, ok := resp.([]interface{}) + if !ok { + t.Fatalf("bad %#v", result) + } + if result[0] != "test_cap" { + t.Errorf("%s != test_cap", result[0]) + } + if result[1] != "test_value" { + t.Errorf("%s != test_value", result[1]) + } +} + +func TestCapabilities_GuestCapability_noargs(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &GuestCapabilitiesPlugin{Impl: &MockGuestCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(GuestCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + m := &vagrant.Machine{} + var args interface{} + args = nil + + resp, err := impl.GuestCapability(context.Background(), cap, args, m) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + result, ok := resp.([]interface{}) + if !ok { + t.Fatalf("bad %#v", result) + } + if result[0] != "test_cap" { + t.Errorf("%s != test_cap", result[0]) + } +} + +func TestCapabilities_GuestCapability_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &GuestCapabilitiesPlugin{Impl: &MockGuestCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(GuestCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + m := &vagrant.Machine{} + args := []string{"pause", "test_value", "next_test_value"} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.GuestCapability(ctx, cap, args, m) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestCapabilities_GuestCapability_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &GuestCapabilitiesPlugin{Impl: &MockGuestCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(GuestCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + m := &vagrant.Machine{} + args := []string{"pause", "test_value", "next_test_value"} + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.GuestCapability(ctx, cap, args, m) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestCapabilities_HostCapabilities(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &HostCapabilitiesPlugin{Impl: &MockHostCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(HostCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + resp, err := impl.HostCapabilities() + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if len(resp) != 1 { + t.Fatalf("length %d != 1", len(resp)) + } + if resp[0].Name != "test_cap" { + t.Errorf("name - %s != test_cap", resp[0].Name) + } + if resp[0].Platform != "testOS" { + t.Errorf("platform - %s != testOS", resp[0].Platform) + } +} + +func TestCapabilities_HostCapability(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &HostCapabilitiesPlugin{Impl: &MockHostCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(HostCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + e := &vagrant.Environment{} + args := []string{"test_value", "next_test_value"} + + resp, err := impl.HostCapability(context.Background(), cap, args, e) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + result, ok := resp.([]interface{}) + if !ok { + t.Fatalf("bad %#v", result) + } + if result[0] != "test_cap" { + t.Errorf("%s != test_cap", result[0]) + } + if result[1] != "test_value" { + t.Errorf("%s != test_value", result[1]) + } +} + +func TestCapabilities_HostCapability_noargs(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &HostCapabilitiesPlugin{Impl: &MockHostCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(HostCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + e := &vagrant.Environment{} + var args interface{} + args = nil + + resp, err := impl.HostCapability(context.Background(), cap, args, e) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + result, ok := resp.([]interface{}) + if !ok { + t.Fatalf("bad %#v", result) + } + if result[0] != "test_cap" { + t.Errorf("%s != test_cap", result[0]) + } +} + +func TestCapabilities_HostCapability_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &HostCapabilitiesPlugin{Impl: &MockHostCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(HostCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + e := &vagrant.Environment{} + args := []string{"pause", "test_value", "next_test_value"} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.HostCapability(ctx, cap, args, e) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestCapabilities_HostCapability_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &HostCapabilitiesPlugin{Impl: &MockHostCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(HostCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.SystemCapability{ + Name: "test_cap", + Platform: "TestOS"} + e := &vagrant.Environment{} + args := []string{"pause", "test_value", "next_test_value"} + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.HostCapability(ctx, cap, args, e) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestCapabilities_ProviderCapabilities(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &ProviderCapabilitiesPlugin{Impl: &MockProviderCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(ProviderCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + resp, err := impl.ProviderCapabilities() + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if len(resp) != 1 { + t.Fatalf("length %d != 1", len(resp)) + } + if resp[0].Name != "test_cap" { + t.Errorf("name - %s != test_cap", resp[0].Name) + } + if resp[0].Provider != "testProvider" { + t.Errorf("provider - %s != testProvdier", resp[0].Provider) + } +} + +func TestCapabilities_ProviderCapability(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &ProviderCapabilitiesPlugin{Impl: &MockProviderCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(ProviderCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.ProviderCapability{ + Name: "test_cap", + Provider: "test_provider"} + m := &vagrant.Machine{} + args := []string{"test_value", "next_test_value"} + + resp, err := impl.ProviderCapability(context.Background(), cap, args, m) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + result, ok := resp.([]interface{}) + if !ok { + t.Fatalf("bad %#v", result) + } + if result[0] != "test_cap" { + t.Errorf("%s != test_cap", result[0]) + } + if result[1] != "test_value" { + t.Errorf("%s != test_value", result[1]) + } +} + +func TestCapabilities_ProviderCapability_noargs(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &ProviderCapabilitiesPlugin{Impl: &MockProviderCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(ProviderCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.ProviderCapability{ + Name: "test_cap", + Provider: "test_provider"} + m := &vagrant.Machine{} + var args interface{} + args = nil + + resp, err := impl.ProviderCapability(context.Background(), cap, args, m) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + result, ok := resp.([]interface{}) + if !ok { + t.Fatalf("bad %#v", result) + } + if result[0] != "test_cap" { + t.Errorf("%s != test_cap", result[0]) + } +} + +func TestCapabilities_ProviderCapability_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &ProviderCapabilitiesPlugin{Impl: &MockProviderCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(ProviderCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.ProviderCapability{ + Name: "test_cap", + Provider: "test_provider"} + m := &vagrant.Machine{} + args := []string{"pause", "test_value", "next_test_value"} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.ProviderCapability(ctx, cap, args, m) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestCapabilities_ProviderCapability_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "caps": &ProviderCapabilitiesPlugin{Impl: &MockProviderCapabilities{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("caps") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(ProviderCapabilities) + if !ok { + t.Fatalf("bad %#v", raw) + } + + cap := &vagrant.ProviderCapability{ + Name: "test_cap", + Provider: "test_provider"} + m := &vagrant.Machine{} + args := []string{"pause", "test_value", "next_test_value"} + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.ProviderCapability(ctx, cap, args, m) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} diff --git a/ext/go-plugin/vagrant/plugin/config.go b/ext/go-plugin/vagrant/plugin/config.go new file mode 100644 index 000000000..5712219f6 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/config.go @@ -0,0 +1,175 @@ +package plugin + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc" + + go_plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/plugin/proto" + + "github.com/LK4D4/joincontext" +) + +type Config interface { + vagrant.Config + Meta +} + +type ConfigPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl Config +} + +func (c *ConfigPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + c.Impl.Init() + vagrant_proto.RegisterConfigServer(s, &GRPCConfigServer{ + Impl: c.Impl, + GRPCIOServer: GRPCIOServer{ + Impl: c.Impl}}) + return nil +} + +func (c *ConfigPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, con *grpc.ClientConn) (interface{}, error) { + client := vagrant_proto.NewConfigClient(con) + return &GRPCConfigClient{ + client: client, + doneCtx: ctx, + GRPCIOClient: GRPCIOClient{ + client: client, + doneCtx: ctx}}, nil +} + +type GRPCConfigServer struct { + GRPCIOServer + Impl Config +} + +func (s *GRPCConfigServer) ConfigAttributes(ctx context.Context, req *vagrant_proto.Empty) (resp *vagrant_proto.ListResponse, err error) { + resp = &vagrant_proto.ListResponse{} + resp.Items, err = s.Impl.ConfigAttributes() + return +} + +func (s *GRPCConfigServer) ConfigLoad(ctx context.Context, req *vagrant_proto.Configuration) (resp *vagrant_proto.Configuration, err error) { + resp = &vagrant_proto.Configuration{} + var data map[string]interface{} + err = json.Unmarshal([]byte(req.Data), &data) + if err != nil { + return + } + r, err := s.Impl.ConfigLoad(ctx, data) + if err != nil { + return + } + mdata, err := json.Marshal(r) + if err != nil { + return + } + resp.Data = string(mdata) + return +} + +func (s *GRPCConfigServer) ConfigValidate(ctx context.Context, req *vagrant_proto.Configuration) (resp *vagrant_proto.ListResponse, err error) { + resp = &vagrant_proto.ListResponse{} + var data map[string]interface{} + err = json.Unmarshal([]byte(req.Data), &data) + if err != nil { + return + } + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + resp.Items, err = s.Impl.ConfigValidate(ctx, data, m) + return +} + +func (s *GRPCConfigServer) ConfigFinalize(ctx context.Context, req *vagrant_proto.Configuration) (resp *vagrant_proto.Configuration, err error) { + resp = &vagrant_proto.Configuration{} + var data map[string]interface{} + err = json.Unmarshal([]byte(req.Data), &data) + if err != nil { + return + } + r, err := s.Impl.ConfigFinalize(ctx, data) + if err != nil { + return + } + mdata, err := json.Marshal(r) + if err != nil { + return + } + resp.Data = string(mdata) + return +} + +type GRPCConfigClient struct { + GRPCCoreClient + GRPCIOClient + client vagrant_proto.ConfigClient + doneCtx context.Context +} + +func (c *GRPCConfigClient) ConfigAttributes() (attrs []string, err error) { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.ConfigAttributes(jctx, &vagrant_proto.Empty{}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, nil) + } + attrs = resp.Items + return +} + +func (c *GRPCConfigClient) ConfigLoad(ctx context.Context, data map[string]interface{}) (loaddata map[string]interface{}, err error) { + mdata, err := json.Marshal(data) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.ConfigLoad(jctx, &vagrant_proto.Configuration{ + Data: string(mdata)}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + err = json.Unmarshal([]byte(resp.Data), &loaddata) + return +} + +func (c *GRPCConfigClient) ConfigValidate(ctx context.Context, data map[string]interface{}, m *vagrant.Machine) (errs []string, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + mdata, err := json.Marshal(data) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.ConfigValidate(jctx, &vagrant_proto.Configuration{ + Data: string(mdata), + Machine: machData}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + errs = resp.Items + return +} + +func (c *GRPCConfigClient) ConfigFinalize(ctx context.Context, data map[string]interface{}) (finaldata map[string]interface{}, err error) { + mdata, err := json.Marshal(data) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.ConfigFinalize(jctx, &vagrant_proto.Configuration{ + Data: string(mdata)}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + err = json.Unmarshal([]byte(resp.Data), &finaldata) + return +} diff --git a/ext/go-plugin/vagrant/plugin/config_test.go b/ext/go-plugin/vagrant/plugin/config_test.go new file mode 100644 index 000000000..2100123c2 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/config_test.go @@ -0,0 +1,246 @@ +package plugin + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +func TestConfigPlugin_Attributes(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp, err := impl.ConfigAttributes() + if err != nil { + t.Fatalf("bad resp %s", err) + } + if resp[0] != "fubar" { + t.Errorf("%s != fubar", resp[0]) + } + if resp[1] != "foobar" { + t.Errorf("%s != foobar", resp[1]) + } +} + +func TestConfigPlugin_Load(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{} + var resp map[string]interface{} + resp, err = impl.ConfigLoad(context.Background(), data) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if _, ok := resp["test_key"]; !ok { + t.Fatalf("bad resp content %#v", resp) + } + v := resp["test_key"].(string) + if v != "test_val" { + t.Errorf("%s != test_val", v) + } +} + +func TestConfigPlugin_Load_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{"pause": true} + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.ConfigLoad(ctx, data) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestConfigPlugin_Load_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{"pause": true} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.ConfigLoad(ctx, data) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestConfigPlugin_Validate(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{} + machine := &vagrant.Machine{} + + resp, err := impl.ConfigValidate(context.Background(), data, machine) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if len(resp) != 1 { + t.Fatalf("bad size %d != 1", len(resp)) + } + if resp[0] != "test error" { + t.Errorf("%s != test error", resp[0]) + } +} + +func TestConfigPlugin_Validate_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{"pause": true} + machine := &vagrant.Machine{} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.ConfigValidate(ctx, data, machine) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestConfigPlugin_Finalize(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{ + "test_key": "test_val", + "other_key": "other_val"} + + resp, err := impl.ConfigFinalize(context.Background(), data) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if _, ok := resp["test_key"]; !ok { + t.Fatalf("bad resp content %#v", resp) + } + v := resp["test_key"].(string) + if v != "test_val-updated" { + t.Errorf("%s != test_val-updated", v) + } + v = resp["other_key"].(string) + if v != "other_val-updated" { + t.Errorf("%s != other_val-updated", v) + } +} + +func TestConfigPlugin_Finalize_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "configs": &ConfigPlugin{Impl: &MockConfig{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("configs") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Config) + if !ok { + t.Fatalf("bad %#v", raw) + } + + data := map[string]interface{}{ + "pause": true, + "test_key": "test_val", + "other_key": "other_val"} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.ConfigFinalize(ctx, data) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} diff --git a/ext/go-plugin/vagrant/plugin/io.go b/ext/go-plugin/vagrant/plugin/io.go new file mode 100644 index 000000000..c4bfe1041 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/io.go @@ -0,0 +1,84 @@ +package plugin + +import ( + "context" + + "google.golang.org/grpc" + + go_plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/plugin/proto" + + "github.com/LK4D4/joincontext" +) + +type IO interface { + vagrant.StreamIO +} + +type IOPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl vagrant.StreamIO +} + +type GRPCIOServer struct { + Impl vagrant.StreamIO +} + +func (s *GRPCIOServer) Read(ctx context.Context, req *vagrant_proto.Identifier) (r *vagrant_proto.Content, err error) { + r = &vagrant_proto.Content{} + r.Value, err = s.Impl.Read(req.Name) + return +} + +func (s *GRPCIOServer) Write(ctx context.Context, req *vagrant_proto.Content) (r *vagrant_proto.WriteResponse, err error) { + r = &vagrant_proto.WriteResponse{} + bytes := 0 + bytes, err = s.Impl.Write(req.Value, req.Target) + if err != nil { + return + } + r.Length = int32(bytes) + return +} + +type GRPCIOClient struct { + client vagrant_proto.IOClient + doneCtx context.Context +} + +func (c *GRPCIOClient) Read(target string) (content string, err error) { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.Read(jctx, &vagrant_proto.Identifier{ + Name: target}) + if err != nil { + return content, handleGrpcError(err, c.doneCtx, ctx) + } + content = resp.Value + return +} + +func (c *GRPCIOClient) Write(content, target string) (length int, err error) { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.Write(jctx, &vagrant_proto.Content{ + Value: content, + Target: target}) + if err != nil { + return length, handleGrpcError(err, c.doneCtx, ctx) + } + length = int(resp.Length) + return +} + +func (i *IOPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + vagrant_proto.RegisterIOServer(s, &GRPCIOServer{Impl: i.Impl}) + return nil +} + +func (i *IOPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCIOClient{ + client: vagrant_proto.NewIOClient(c), + doneCtx: ctx}, nil +} diff --git a/ext/go-plugin/vagrant/plugin/io_test.go b/ext/go-plugin/vagrant/plugin/io_test.go new file mode 100644 index 000000000..ac1f104ba --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/io_test.go @@ -0,0 +1,89 @@ +package plugin + +import ( + "testing" + + "github.com/hashicorp/go-plugin" +) + +type MockIO struct { + Core +} + +func TestIO_ReadWrite(t *testing.T) { + ioplugin := &MockIO{} + ioplugin.Init() + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "io": &IOPlugin{Impl: ioplugin}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("io") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(IO) + if !ok { + t.Fatalf("bad %#v", raw) + } + go func() { + length, err := impl.Write("test_message", "stdout") + if err != nil { + t.Fatalf("bad write: %s", err) + } + if length != len("test_message") { + t.Fatalf("bad length %d != %d", length, len("test_message")) + } + }() + resp, err := impl.Read("stdout") + if err != nil { + t.Fatalf("bad read: %s", err) + } + if resp != "test_message" { + t.Errorf("%s != test_message", resp) + } +} + +func TestIO_Write_bad(t *testing.T) { + ioplugin := &MockIO{} + ioplugin.Init() + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "io": &IOPlugin{Impl: ioplugin}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("io") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(IO) + if !ok { + t.Fatalf("bad %#v", raw) + } + _, err = impl.Write("test_message", "bad-target") + if err == nil { + t.Fatalf("illegal write") + } +} + +func TestIO_Read_bad(t *testing.T) { + ioplugin := &MockIO{} + ioplugin.Init() + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "io": &IOPlugin{Impl: ioplugin}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("io") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(IO) + if !ok { + t.Fatalf("bad %#v", raw) + } + _, err = impl.Read("bad-target") + if err == nil { + t.Fatalf("illegal read") + } +} diff --git a/ext/go-plugin/vagrant/plugin/meta.go b/ext/go-plugin/vagrant/plugin/meta.go new file mode 100644 index 000000000..e060af343 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/meta.go @@ -0,0 +1,39 @@ +package plugin + +import ( + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +type Meta interface { + Init() + vagrant.IOServer +} + +type GRPCCoreClient struct{} + +func (c *GRPCCoreClient) Init() {} +func (c *GRPCCoreClient) Streams() (s map[string]chan (string)) { return } + +type Core struct { + vagrant.IOServer + io vagrant.StreamIO +} + +func (c *Core) Init() { + if c.io == nil { + c.io = &vagrant.IOSrv{ + map[string]chan (string){ + "stdout": make(chan string), + "stderr": make(chan string), + }, + } + } +} + +func (c *Core) Read(target string) (string, error) { + return c.io.Read(target) +} + +func (c *Core) Write(content, target string) (int, error) { + return c.io.Write(content, target) +} diff --git a/ext/go-plugin/vagrant/plugin/mocks.go b/ext/go-plugin/vagrant/plugin/mocks.go new file mode 100644 index 000000000..61359c6fe --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/mocks.go @@ -0,0 +1,300 @@ +package plugin + +import ( + "context" + "errors" + "time" + + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +type MockGuestCapabilities struct{ Core } + +func (g *MockGuestCapabilities) GuestCapabilities() (caps []vagrant.SystemCapability, err error) { + caps = []vagrant.SystemCapability{ + vagrant.SystemCapability{Name: "test_cap", Platform: "testOS"}} + return +} + +func (g *MockGuestCapabilities) GuestCapability(ctx context.Context, cap *vagrant.SystemCapability, args interface{}, m *vagrant.Machine) (result interface{}, err error) { + if args != nil { + arguments := args.([]interface{}) + if arguments[0] == "pause" { + time.Sleep(1 * time.Second) + } + if len(arguments) > 0 { + result = []string{ + cap.Name, + arguments[0].(string)} + return + } + } + result = []string{cap.Name} + return +} + +type MockHostCapabilities struct{ Core } + +func (h *MockHostCapabilities) HostCapabilities() (caps []vagrant.SystemCapability, err error) { + caps = []vagrant.SystemCapability{ + vagrant.SystemCapability{Name: "test_cap", Platform: "testOS"}} + return +} + +func (h *MockHostCapabilities) HostCapability(ctx context.Context, cap *vagrant.SystemCapability, args interface{}, e *vagrant.Environment) (result interface{}, err error) { + if args != nil { + arguments := args.([]interface{}) + if arguments[0] == "pause" { + time.Sleep(1 * time.Second) + } + if len(arguments) > 0 { + result = []string{ + cap.Name, + arguments[0].(string)} + return + } + } + result = []string{cap.Name} + return +} + +type MockProviderCapabilities struct{ Core } + +func (p *MockProviderCapabilities) ProviderCapabilities() (caps []vagrant.ProviderCapability, err error) { + caps = []vagrant.ProviderCapability{ + vagrant.ProviderCapability{Name: "test_cap", Provider: "testProvider"}} + return +} + +func (p *MockProviderCapabilities) ProviderCapability(ctx context.Context, cap *vagrant.ProviderCapability, args interface{}, m *vagrant.Machine) (result interface{}, err error) { + if args != nil { + arguments := args.([]interface{}) + if arguments[0] == "pause" { + time.Sleep(1 * time.Second) + } + if len(arguments) > 0 { + result = []string{ + cap.Name, + arguments[0].(string)} + return + } + } + result = []string{cap.Name} + return +} + +type MockConfig struct { + Core +} + +func (c *MockConfig) ConfigAttributes() (attrs []string, err error) { + attrs = []string{"fubar", "foobar"} + return +} + +func (c *MockConfig) ConfigLoad(ctx context.Context, data map[string]interface{}) (loaddata map[string]interface{}, err error) { + if data["pause"] == true { + time.Sleep(1 * time.Second) + } + loaddata = map[string]interface{}{ + "test_key": "test_val"} + if data["test_key"] != nil { + loaddata["sent_key"] = data["test_key"] + } + return +} + +func (c *MockConfig) ConfigValidate(ctx context.Context, data map[string]interface{}, m *vagrant.Machine) (errors []string, err error) { + errors = []string{"test error"} + if data["pause"] == true { + time.Sleep(1 * time.Second) + } + return +} + +func (c *MockConfig) ConfigFinalize(ctx context.Context, data map[string]interface{}) (finaldata map[string]interface{}, err error) { + finaldata = make(map[string]interface{}) + for key, tval := range data { + val, ok := tval.(string) + if !ok { + continue + } + finaldata[key] = val + "-updated" + } + if data["pause"] == true { + time.Sleep(1 * time.Second) + } + return +} + +type MockProvider struct { + Core + vagrant.NoConfig + vagrant.NoGuestCapabilities + vagrant.NoHostCapabilities + vagrant.NoProviderCapabilities +} + +func (c *MockProvider) Action(ctx context.Context, actionName string, m *vagrant.Machine) (actions []string, err error) { + switch actionName { + case "valid": + actions = []string{"self::DoTask"} + case "pause": + time.Sleep(1 * time.Second) + default: + err = errors.New("Unknown action requested") + } + return +} + +func (c *MockProvider) IsInstalled(ctx context.Context, m *vagrant.Machine) (bool, error) { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + return true, nil +} + +func (c *MockProvider) IsUsable(ctx context.Context, m *vagrant.Machine) (bool, error) { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + return true, nil +} + +func (c *MockProvider) MachineIdChanged(ctx context.Context, m *vagrant.Machine) error { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + return nil +} + +func (c *MockProvider) Name() string { + return "mock_provider" +} + +func (c *MockProvider) RunAction(ctx context.Context, actionName string, args interface{}, m *vagrant.Machine) (r interface{}, err error) { + switch actionName { + case "send_output": + m.UI.Say("test_output_p") + case "pause": + time.Sleep(1 * time.Second) + case "valid": + default: + return nil, errors.New("invalid action name") + } + var arguments []interface{} + if args != nil { + arguments = args.([]interface{}) + } else { + arguments = []interface{}{"unset"} + } + r = []string{ + actionName, + arguments[0].(string)} + return +} + +func (c *MockProvider) SshInfo(ctx context.Context, m *vagrant.Machine) (*vagrant.SshInfo, error) { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + return &vagrant.SshInfo{ + Host: "localhost", + Port: 2222}, nil +} + +func (c *MockProvider) State(ctx context.Context, m *vagrant.Machine) (*vagrant.MachineState, error) { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + return &vagrant.MachineState{ + Id: "default", + ShortDesc: "running"}, nil +} + +func (c *MockProvider) Info() *vagrant.ProviderInfo { + return &vagrant.ProviderInfo{ + Description: "Custom", + Priority: 10} +} + +type MockSyncedFolder struct { + Core + vagrant.NoGuestCapabilities + vagrant.NoHostCapabilities +} + +func (s *MockSyncedFolder) Cleanup(ctx context.Context, m *vagrant.Machine, opts vagrant.FolderOptions) error { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + if opts != nil { + err, _ := opts["error"].(bool) + ui, _ := opts["ui"].(bool) + if err { + return errors.New("cleanup error") + } + if ui { + m.UI.Say("test_output_sf") + return nil + } + } + return nil +} + +func (s *MockSyncedFolder) Disable(ctx context.Context, m *vagrant.Machine, f vagrant.FolderList, opts vagrant.FolderOptions) error { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + if opts != nil && opts["error"].(bool) { + return errors.New("disable error") + } + return nil +} + +func (s *MockSyncedFolder) Enable(ctx context.Context, m *vagrant.Machine, f vagrant.FolderList, opts vagrant.FolderOptions) error { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + if opts != nil && opts["error"].(bool) { + return errors.New("enable error") + } + return nil +} + +func (s *MockSyncedFolder) Info() *vagrant.SyncedFolderInfo { + return &vagrant.SyncedFolderInfo{ + Description: "mock_folder", + Priority: 100} +} + +func (s *MockSyncedFolder) IsUsable(ctx context.Context, m *vagrant.Machine) (bool, error) { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + return true, nil +} + +func (s *MockSyncedFolder) Name() string { + return "mock_folder" +} + +func (s *MockSyncedFolder) Prepare(ctx context.Context, m *vagrant.Machine, f vagrant.FolderList, opts vagrant.FolderOptions) error { + if m.Name == "pause" { + time.Sleep(1 * time.Second) + } + + if opts != nil && opts["error"].(bool) { + return errors.New("prepare error") + } + return nil +} diff --git a/ext/go-plugin/vagrant/plugin/proto/genproto b/ext/go-plugin/vagrant/plugin/proto/genproto new file mode 100755 index 000000000..503a867b5 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/proto/genproto @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +echo -n "Parsing proto files and generating go output... " + +for i in * +do + if [ -d "${i}" ]; then + protoc --proto_path=`go env GOPATH`/src --proto_path=. --go_out=plugins=grpc:. "${i}"/*.proto; + if [ $? -ne 0 ]; then + echo "failed!" + exit 1 + fi + fi +done + +protoc --proto_path=`go env GOPATH`/src --proto_path=. --go_out=plugins=grpc:. *.proto; + +if [ $? -ne 0 ]; then + echo "failed!" + exit 1 +fi + +echo "done!" diff --git a/ext/go-plugin/vagrant/plugin/proto/vagrant.pb.go b/ext/go-plugin/vagrant/plugin/proto/vagrant.pb.go new file mode 100644 index 000000000..1999b4ee8 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/proto/vagrant.pb.go @@ -0,0 +1,3152 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: vagrant.proto + +package vagrant_proto + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Empty struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto.CompactTextString(m) } +func (*Empty) ProtoMessage() {} +func (*Empty) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{0} +} +func (m *Empty) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Empty.Unmarshal(m, b) +} +func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Empty.Marshal(b, m, deterministic) +} +func (m *Empty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Empty.Merge(m, src) +} +func (m *Empty) XXX_Size() int { + return xxx_messageInfo_Empty.Size(m) +} +func (m *Empty) XXX_DiscardUnknown() { + xxx_messageInfo_Empty.DiscardUnknown(m) +} + +var xxx_messageInfo_Empty proto.InternalMessageInfo + +type Machine struct { + Machine string `protobuf:"bytes,1,opt,name=machine,proto3" json:"machine,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Machine) Reset() { *m = Machine{} } +func (m *Machine) String() string { return proto.CompactTextString(m) } +func (*Machine) ProtoMessage() {} +func (*Machine) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{1} +} +func (m *Machine) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Machine.Unmarshal(m, b) +} +func (m *Machine) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Machine.Marshal(b, m, deterministic) +} +func (m *Machine) XXX_Merge(src proto.Message) { + xxx_messageInfo_Machine.Merge(m, src) +} +func (m *Machine) XXX_Size() int { + return xxx_messageInfo_Machine.Size(m) +} +func (m *Machine) XXX_DiscardUnknown() { + xxx_messageInfo_Machine.DiscardUnknown(m) +} + +var xxx_messageInfo_Machine proto.InternalMessageInfo + +func (m *Machine) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +type Valid struct { + Result bool `protobuf:"varint,1,opt,name=result,proto3" json:"result,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Valid) Reset() { *m = Valid{} } +func (m *Valid) String() string { return proto.CompactTextString(m) } +func (*Valid) ProtoMessage() {} +func (*Valid) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{2} +} +func (m *Valid) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Valid.Unmarshal(m, b) +} +func (m *Valid) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Valid.Marshal(b, m, deterministic) +} +func (m *Valid) XXX_Merge(src proto.Message) { + xxx_messageInfo_Valid.Merge(m, src) +} +func (m *Valid) XXX_Size() int { + return xxx_messageInfo_Valid.Size(m) +} +func (m *Valid) XXX_DiscardUnknown() { + xxx_messageInfo_Valid.DiscardUnknown(m) +} + +var xxx_messageInfo_Valid proto.InternalMessageInfo + +func (m *Valid) GetResult() bool { + if m != nil { + return m.Result + } + return false +} + +type Identifier struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Identifier) Reset() { *m = Identifier{} } +func (m *Identifier) String() string { return proto.CompactTextString(m) } +func (*Identifier) ProtoMessage() {} +func (*Identifier) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{3} +} +func (m *Identifier) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Identifier.Unmarshal(m, b) +} +func (m *Identifier) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Identifier.Marshal(b, m, deterministic) +} +func (m *Identifier) XXX_Merge(src proto.Message) { + xxx_messageInfo_Identifier.Merge(m, src) +} +func (m *Identifier) XXX_Size() int { + return xxx_messageInfo_Identifier.Size(m) +} +func (m *Identifier) XXX_DiscardUnknown() { + xxx_messageInfo_Identifier.DiscardUnknown(m) +} + +var xxx_messageInfo_Identifier proto.InternalMessageInfo + +func (m *Identifier) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type PluginInfo struct { + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + Priority int64 `protobuf:"varint,2,opt,name=priority,proto3" json:"priority,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PluginInfo) Reset() { *m = PluginInfo{} } +func (m *PluginInfo) String() string { return proto.CompactTextString(m) } +func (*PluginInfo) ProtoMessage() {} +func (*PluginInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{4} +} +func (m *PluginInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PluginInfo.Unmarshal(m, b) +} +func (m *PluginInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PluginInfo.Marshal(b, m, deterministic) +} +func (m *PluginInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginInfo.Merge(m, src) +} +func (m *PluginInfo) XXX_Size() int { + return xxx_messageInfo_PluginInfo.Size(m) +} +func (m *PluginInfo) XXX_DiscardUnknown() { + xxx_messageInfo_PluginInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginInfo proto.InternalMessageInfo + +func (m *PluginInfo) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *PluginInfo) GetPriority() int64 { + if m != nil { + return m.Priority + } + return 0 +} + +type Content struct { + Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Content) Reset() { *m = Content{} } +func (m *Content) String() string { return proto.CompactTextString(m) } +func (*Content) ProtoMessage() {} +func (*Content) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{5} +} +func (m *Content) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Content.Unmarshal(m, b) +} +func (m *Content) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Content.Marshal(b, m, deterministic) +} +func (m *Content) XXX_Merge(src proto.Message) { + xxx_messageInfo_Content.Merge(m, src) +} +func (m *Content) XXX_Size() int { + return xxx_messageInfo_Content.Size(m) +} +func (m *Content) XXX_DiscardUnknown() { + xxx_messageInfo_Content.DiscardUnknown(m) +} + +var xxx_messageInfo_Content proto.InternalMessageInfo + +func (m *Content) GetTarget() string { + if m != nil { + return m.Target + } + return "" +} + +func (m *Content) GetValue() string { + if m != nil { + return m.Value + } + return "" +} + +type WriteResponse struct { + Length int32 `protobuf:"varint,1,opt,name=length,proto3" json:"length,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WriteResponse) Reset() { *m = WriteResponse{} } +func (m *WriteResponse) String() string { return proto.CompactTextString(m) } +func (*WriteResponse) ProtoMessage() {} +func (*WriteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{6} +} +func (m *WriteResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WriteResponse.Unmarshal(m, b) +} +func (m *WriteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WriteResponse.Marshal(b, m, deterministic) +} +func (m *WriteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WriteResponse.Merge(m, src) +} +func (m *WriteResponse) XXX_Size() int { + return xxx_messageInfo_WriteResponse.Size(m) +} +func (m *WriteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WriteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WriteResponse proto.InternalMessageInfo + +func (m *WriteResponse) GetLength() int32 { + if m != nil { + return m.Length + } + return 0 +} + +type SystemCapability struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Platform string `protobuf:"bytes,2,opt,name=platform,proto3" json:"platform,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SystemCapability) Reset() { *m = SystemCapability{} } +func (m *SystemCapability) String() string { return proto.CompactTextString(m) } +func (*SystemCapability) ProtoMessage() {} +func (*SystemCapability) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{7} +} +func (m *SystemCapability) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SystemCapability.Unmarshal(m, b) +} +func (m *SystemCapability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SystemCapability.Marshal(b, m, deterministic) +} +func (m *SystemCapability) XXX_Merge(src proto.Message) { + xxx_messageInfo_SystemCapability.Merge(m, src) +} +func (m *SystemCapability) XXX_Size() int { + return xxx_messageInfo_SystemCapability.Size(m) +} +func (m *SystemCapability) XXX_DiscardUnknown() { + xxx_messageInfo_SystemCapability.DiscardUnknown(m) +} + +var xxx_messageInfo_SystemCapability proto.InternalMessageInfo + +func (m *SystemCapability) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *SystemCapability) GetPlatform() string { + if m != nil { + return m.Platform + } + return "" +} + +type ProviderCapability struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ProviderCapability) Reset() { *m = ProviderCapability{} } +func (m *ProviderCapability) String() string { return proto.CompactTextString(m) } +func (*ProviderCapability) ProtoMessage() {} +func (*ProviderCapability) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{8} +} +func (m *ProviderCapability) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ProviderCapability.Unmarshal(m, b) +} +func (m *ProviderCapability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ProviderCapability.Marshal(b, m, deterministic) +} +func (m *ProviderCapability) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProviderCapability.Merge(m, src) +} +func (m *ProviderCapability) XXX_Size() int { + return xxx_messageInfo_ProviderCapability.Size(m) +} +func (m *ProviderCapability) XXX_DiscardUnknown() { + xxx_messageInfo_ProviderCapability.DiscardUnknown(m) +} + +var xxx_messageInfo_ProviderCapability proto.InternalMessageInfo + +func (m *ProviderCapability) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *ProviderCapability) GetProvider() string { + if m != nil { + return m.Provider + } + return "" +} + +type SystemCapabilityList struct { + Capabilities []*SystemCapability `protobuf:"bytes,1,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SystemCapabilityList) Reset() { *m = SystemCapabilityList{} } +func (m *SystemCapabilityList) String() string { return proto.CompactTextString(m) } +func (*SystemCapabilityList) ProtoMessage() {} +func (*SystemCapabilityList) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{9} +} +func (m *SystemCapabilityList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SystemCapabilityList.Unmarshal(m, b) +} +func (m *SystemCapabilityList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SystemCapabilityList.Marshal(b, m, deterministic) +} +func (m *SystemCapabilityList) XXX_Merge(src proto.Message) { + xxx_messageInfo_SystemCapabilityList.Merge(m, src) +} +func (m *SystemCapabilityList) XXX_Size() int { + return xxx_messageInfo_SystemCapabilityList.Size(m) +} +func (m *SystemCapabilityList) XXX_DiscardUnknown() { + xxx_messageInfo_SystemCapabilityList.DiscardUnknown(m) +} + +var xxx_messageInfo_SystemCapabilityList proto.InternalMessageInfo + +func (m *SystemCapabilityList) GetCapabilities() []*SystemCapability { + if m != nil { + return m.Capabilities + } + return nil +} + +type ProviderCapabilityList struct { + Capabilities []*ProviderCapability `protobuf:"bytes,1,rep,name=capabilities,proto3" json:"capabilities,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ProviderCapabilityList) Reset() { *m = ProviderCapabilityList{} } +func (m *ProviderCapabilityList) String() string { return proto.CompactTextString(m) } +func (*ProviderCapabilityList) ProtoMessage() {} +func (*ProviderCapabilityList) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{10} +} +func (m *ProviderCapabilityList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ProviderCapabilityList.Unmarshal(m, b) +} +func (m *ProviderCapabilityList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ProviderCapabilityList.Marshal(b, m, deterministic) +} +func (m *ProviderCapabilityList) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProviderCapabilityList.Merge(m, src) +} +func (m *ProviderCapabilityList) XXX_Size() int { + return xxx_messageInfo_ProviderCapabilityList.Size(m) +} +func (m *ProviderCapabilityList) XXX_DiscardUnknown() { + xxx_messageInfo_ProviderCapabilityList.DiscardUnknown(m) +} + +var xxx_messageInfo_ProviderCapabilityList proto.InternalMessageInfo + +func (m *ProviderCapabilityList) GetCapabilities() []*ProviderCapability { + if m != nil { + return m.Capabilities + } + return nil +} + +type GenericResponse struct { + Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GenericResponse) Reset() { *m = GenericResponse{} } +func (m *GenericResponse) String() string { return proto.CompactTextString(m) } +func (*GenericResponse) ProtoMessage() {} +func (*GenericResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{11} +} +func (m *GenericResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericResponse.Unmarshal(m, b) +} +func (m *GenericResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericResponse.Marshal(b, m, deterministic) +} +func (m *GenericResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericResponse.Merge(m, src) +} +func (m *GenericResponse) XXX_Size() int { + return xxx_messageInfo_GenericResponse.Size(m) +} +func (m *GenericResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GenericResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericResponse proto.InternalMessageInfo + +func (m *GenericResponse) GetResult() string { + if m != nil { + return m.Result + } + return "" +} + +type GuestCapabilityRequest struct { + Capability *SystemCapability `protobuf:"bytes,1,opt,name=capability,proto3" json:"capability,omitempty"` + Machine string `protobuf:"bytes,2,opt,name=machine,proto3" json:"machine,omitempty"` + Arguments string `protobuf:"bytes,3,opt,name=arguments,proto3" json:"arguments,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GuestCapabilityRequest) Reset() { *m = GuestCapabilityRequest{} } +func (m *GuestCapabilityRequest) String() string { return proto.CompactTextString(m) } +func (*GuestCapabilityRequest) ProtoMessage() {} +func (*GuestCapabilityRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{12} +} +func (m *GuestCapabilityRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GuestCapabilityRequest.Unmarshal(m, b) +} +func (m *GuestCapabilityRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GuestCapabilityRequest.Marshal(b, m, deterministic) +} +func (m *GuestCapabilityRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GuestCapabilityRequest.Merge(m, src) +} +func (m *GuestCapabilityRequest) XXX_Size() int { + return xxx_messageInfo_GuestCapabilityRequest.Size(m) +} +func (m *GuestCapabilityRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GuestCapabilityRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GuestCapabilityRequest proto.InternalMessageInfo + +func (m *GuestCapabilityRequest) GetCapability() *SystemCapability { + if m != nil { + return m.Capability + } + return nil +} + +func (m *GuestCapabilityRequest) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +func (m *GuestCapabilityRequest) GetArguments() string { + if m != nil { + return m.Arguments + } + return "" +} + +type HostCapabilityRequest struct { + Capability *SystemCapability `protobuf:"bytes,1,opt,name=capability,proto3" json:"capability,omitempty"` + Environment string `protobuf:"bytes,2,opt,name=environment,proto3" json:"environment,omitempty"` + Arguments string `protobuf:"bytes,3,opt,name=arguments,proto3" json:"arguments,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *HostCapabilityRequest) Reset() { *m = HostCapabilityRequest{} } +func (m *HostCapabilityRequest) String() string { return proto.CompactTextString(m) } +func (*HostCapabilityRequest) ProtoMessage() {} +func (*HostCapabilityRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{13} +} +func (m *HostCapabilityRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_HostCapabilityRequest.Unmarshal(m, b) +} +func (m *HostCapabilityRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_HostCapabilityRequest.Marshal(b, m, deterministic) +} +func (m *HostCapabilityRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_HostCapabilityRequest.Merge(m, src) +} +func (m *HostCapabilityRequest) XXX_Size() int { + return xxx_messageInfo_HostCapabilityRequest.Size(m) +} +func (m *HostCapabilityRequest) XXX_DiscardUnknown() { + xxx_messageInfo_HostCapabilityRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_HostCapabilityRequest proto.InternalMessageInfo + +func (m *HostCapabilityRequest) GetCapability() *SystemCapability { + if m != nil { + return m.Capability + } + return nil +} + +func (m *HostCapabilityRequest) GetEnvironment() string { + if m != nil { + return m.Environment + } + return "" +} + +func (m *HostCapabilityRequest) GetArguments() string { + if m != nil { + return m.Arguments + } + return "" +} + +type ProviderCapabilityRequest struct { + Capability *ProviderCapability `protobuf:"bytes,1,opt,name=capability,proto3" json:"capability,omitempty"` + Machine string `protobuf:"bytes,2,opt,name=machine,proto3" json:"machine,omitempty"` + Arguments string `protobuf:"bytes,3,opt,name=arguments,proto3" json:"arguments,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ProviderCapabilityRequest) Reset() { *m = ProviderCapabilityRequest{} } +func (m *ProviderCapabilityRequest) String() string { return proto.CompactTextString(m) } +func (*ProviderCapabilityRequest) ProtoMessage() {} +func (*ProviderCapabilityRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{14} +} +func (m *ProviderCapabilityRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ProviderCapabilityRequest.Unmarshal(m, b) +} +func (m *ProviderCapabilityRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ProviderCapabilityRequest.Marshal(b, m, deterministic) +} +func (m *ProviderCapabilityRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProviderCapabilityRequest.Merge(m, src) +} +func (m *ProviderCapabilityRequest) XXX_Size() int { + return xxx_messageInfo_ProviderCapabilityRequest.Size(m) +} +func (m *ProviderCapabilityRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ProviderCapabilityRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ProviderCapabilityRequest proto.InternalMessageInfo + +func (m *ProviderCapabilityRequest) GetCapability() *ProviderCapability { + if m != nil { + return m.Capability + } + return nil +} + +func (m *ProviderCapabilityRequest) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +func (m *ProviderCapabilityRequest) GetArguments() string { + if m != nil { + return m.Arguments + } + return "" +} + +type Configuration struct { + Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Machine string `protobuf:"bytes,2,opt,name=machine,proto3" json:"machine,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Configuration) Reset() { *m = Configuration{} } +func (m *Configuration) String() string { return proto.CompactTextString(m) } +func (*Configuration) ProtoMessage() {} +func (*Configuration) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{15} +} +func (m *Configuration) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Configuration.Unmarshal(m, b) +} +func (m *Configuration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Configuration.Marshal(b, m, deterministic) +} +func (m *Configuration) XXX_Merge(src proto.Message) { + xxx_messageInfo_Configuration.Merge(m, src) +} +func (m *Configuration) XXX_Size() int { + return xxx_messageInfo_Configuration.Size(m) +} +func (m *Configuration) XXX_DiscardUnknown() { + xxx_messageInfo_Configuration.DiscardUnknown(m) +} + +var xxx_messageInfo_Configuration proto.InternalMessageInfo + +func (m *Configuration) GetData() string { + if m != nil { + return m.Data + } + return "" +} + +func (m *Configuration) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +type ListResponse struct { + Items []string `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListResponse) Reset() { *m = ListResponse{} } +func (m *ListResponse) String() string { return proto.CompactTextString(m) } +func (*ListResponse) ProtoMessage() {} +func (*ListResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{16} +} +func (m *ListResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListResponse.Unmarshal(m, b) +} +func (m *ListResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListResponse.Marshal(b, m, deterministic) +} +func (m *ListResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListResponse.Merge(m, src) +} +func (m *ListResponse) XXX_Size() int { + return xxx_messageInfo_ListResponse.Size(m) +} +func (m *ListResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListResponse proto.InternalMessageInfo + +func (m *ListResponse) GetItems() []string { + if m != nil { + return m.Items + } + return nil +} + +type SyncedFolders struct { + Machine string `protobuf:"bytes,1,opt,name=machine,proto3" json:"machine,omitempty"` + Folders string `protobuf:"bytes,2,opt,name=folders,proto3" json:"folders,omitempty"` + Options string `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SyncedFolders) Reset() { *m = SyncedFolders{} } +func (m *SyncedFolders) String() string { return proto.CompactTextString(m) } +func (*SyncedFolders) ProtoMessage() {} +func (*SyncedFolders) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{17} +} +func (m *SyncedFolders) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SyncedFolders.Unmarshal(m, b) +} +func (m *SyncedFolders) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SyncedFolders.Marshal(b, m, deterministic) +} +func (m *SyncedFolders) XXX_Merge(src proto.Message) { + xxx_messageInfo_SyncedFolders.Merge(m, src) +} +func (m *SyncedFolders) XXX_Size() int { + return xxx_messageInfo_SyncedFolders.Size(m) +} +func (m *SyncedFolders) XXX_DiscardUnknown() { + xxx_messageInfo_SyncedFolders.DiscardUnknown(m) +} + +var xxx_messageInfo_SyncedFolders proto.InternalMessageInfo + +func (m *SyncedFolders) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +func (m *SyncedFolders) GetFolders() string { + if m != nil { + return m.Folders + } + return "" +} + +func (m *SyncedFolders) GetOptions() string { + if m != nil { + return m.Options + } + return "" +} + +type GenericAction struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Machine string `protobuf:"bytes,2,opt,name=machine,proto3" json:"machine,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GenericAction) Reset() { *m = GenericAction{} } +func (m *GenericAction) String() string { return proto.CompactTextString(m) } +func (*GenericAction) ProtoMessage() {} +func (*GenericAction) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{18} +} +func (m *GenericAction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GenericAction.Unmarshal(m, b) +} +func (m *GenericAction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GenericAction.Marshal(b, m, deterministic) +} +func (m *GenericAction) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenericAction.Merge(m, src) +} +func (m *GenericAction) XXX_Size() int { + return xxx_messageInfo_GenericAction.Size(m) +} +func (m *GenericAction) XXX_DiscardUnknown() { + xxx_messageInfo_GenericAction.DiscardUnknown(m) +} + +var xxx_messageInfo_GenericAction proto.InternalMessageInfo + +func (m *GenericAction) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GenericAction) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +type ExecuteAction struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Machine string `protobuf:"bytes,3,opt,name=machine,proto3" json:"machine,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ExecuteAction) Reset() { *m = ExecuteAction{} } +func (m *ExecuteAction) String() string { return proto.CompactTextString(m) } +func (*ExecuteAction) ProtoMessage() {} +func (*ExecuteAction) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{19} +} +func (m *ExecuteAction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ExecuteAction.Unmarshal(m, b) +} +func (m *ExecuteAction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ExecuteAction.Marshal(b, m, deterministic) +} +func (m *ExecuteAction) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExecuteAction.Merge(m, src) +} +func (m *ExecuteAction) XXX_Size() int { + return xxx_messageInfo_ExecuteAction.Size(m) +} +func (m *ExecuteAction) XXX_DiscardUnknown() { + xxx_messageInfo_ExecuteAction.DiscardUnknown(m) +} + +var xxx_messageInfo_ExecuteAction proto.InternalMessageInfo + +func (m *ExecuteAction) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *ExecuteAction) GetData() string { + if m != nil { + return m.Data + } + return "" +} + +func (m *ExecuteAction) GetMachine() string { + if m != nil { + return m.Machine + } + return "" +} + +type MachineSshInfo struct { + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Port int64 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` + PrivateKeyPath string `protobuf:"bytes,3,opt,name=private_key_path,json=privateKeyPath,proto3" json:"private_key_path,omitempty"` + Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MachineSshInfo) Reset() { *m = MachineSshInfo{} } +func (m *MachineSshInfo) String() string { return proto.CompactTextString(m) } +func (*MachineSshInfo) ProtoMessage() {} +func (*MachineSshInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{20} +} +func (m *MachineSshInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MachineSshInfo.Unmarshal(m, b) +} +func (m *MachineSshInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MachineSshInfo.Marshal(b, m, deterministic) +} +func (m *MachineSshInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_MachineSshInfo.Merge(m, src) +} +func (m *MachineSshInfo) XXX_Size() int { + return xxx_messageInfo_MachineSshInfo.Size(m) +} +func (m *MachineSshInfo) XXX_DiscardUnknown() { + xxx_messageInfo_MachineSshInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_MachineSshInfo proto.InternalMessageInfo + +func (m *MachineSshInfo) GetHost() string { + if m != nil { + return m.Host + } + return "" +} + +func (m *MachineSshInfo) GetPort() int64 { + if m != nil { + return m.Port + } + return 0 +} + +func (m *MachineSshInfo) GetPrivateKeyPath() string { + if m != nil { + return m.PrivateKeyPath + } + return "" +} + +func (m *MachineSshInfo) GetUsername() string { + if m != nil { + return m.Username + } + return "" +} + +type MachineState struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ShortDescription string `protobuf:"bytes,2,opt,name=short_description,json=shortDescription,proto3" json:"short_description,omitempty"` + LongDescription string `protobuf:"bytes,3,opt,name=long_description,json=longDescription,proto3" json:"long_description,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MachineState) Reset() { *m = MachineState{} } +func (m *MachineState) String() string { return proto.CompactTextString(m) } +func (*MachineState) ProtoMessage() {} +func (*MachineState) Descriptor() ([]byte, []int) { + return fileDescriptor_05d5fddafc02be38, []int{21} +} +func (m *MachineState) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MachineState.Unmarshal(m, b) +} +func (m *MachineState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MachineState.Marshal(b, m, deterministic) +} +func (m *MachineState) XXX_Merge(src proto.Message) { + xxx_messageInfo_MachineState.Merge(m, src) +} +func (m *MachineState) XXX_Size() int { + return xxx_messageInfo_MachineState.Size(m) +} +func (m *MachineState) XXX_DiscardUnknown() { + xxx_messageInfo_MachineState.DiscardUnknown(m) +} + +var xxx_messageInfo_MachineState proto.InternalMessageInfo + +func (m *MachineState) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *MachineState) GetShortDescription() string { + if m != nil { + return m.ShortDescription + } + return "" +} + +func (m *MachineState) GetLongDescription() string { + if m != nil { + return m.LongDescription + } + return "" +} + +func init() { + proto.RegisterType((*Empty)(nil), "vagrant.proto.Empty") + proto.RegisterType((*Machine)(nil), "vagrant.proto.Machine") + proto.RegisterType((*Valid)(nil), "vagrant.proto.Valid") + proto.RegisterType((*Identifier)(nil), "vagrant.proto.Identifier") + proto.RegisterType((*PluginInfo)(nil), "vagrant.proto.PluginInfo") + proto.RegisterType((*Content)(nil), "vagrant.proto.Content") + proto.RegisterType((*WriteResponse)(nil), "vagrant.proto.WriteResponse") + proto.RegisterType((*SystemCapability)(nil), "vagrant.proto.SystemCapability") + proto.RegisterType((*ProviderCapability)(nil), "vagrant.proto.ProviderCapability") + proto.RegisterType((*SystemCapabilityList)(nil), "vagrant.proto.SystemCapabilityList") + proto.RegisterType((*ProviderCapabilityList)(nil), "vagrant.proto.ProviderCapabilityList") + proto.RegisterType((*GenericResponse)(nil), "vagrant.proto.GenericResponse") + proto.RegisterType((*GuestCapabilityRequest)(nil), "vagrant.proto.GuestCapabilityRequest") + proto.RegisterType((*HostCapabilityRequest)(nil), "vagrant.proto.HostCapabilityRequest") + proto.RegisterType((*ProviderCapabilityRequest)(nil), "vagrant.proto.ProviderCapabilityRequest") + proto.RegisterType((*Configuration)(nil), "vagrant.proto.Configuration") + proto.RegisterType((*ListResponse)(nil), "vagrant.proto.ListResponse") + proto.RegisterType((*SyncedFolders)(nil), "vagrant.proto.SyncedFolders") + proto.RegisterType((*GenericAction)(nil), "vagrant.proto.GenericAction") + proto.RegisterType((*ExecuteAction)(nil), "vagrant.proto.ExecuteAction") + proto.RegisterType((*MachineSshInfo)(nil), "vagrant.proto.MachineSshInfo") + proto.RegisterType((*MachineState)(nil), "vagrant.proto.MachineState") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// IOClient is the client API for IO service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type IOClient interface { + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) +} + +type iOClient struct { + cc *grpc.ClientConn +} + +func NewIOClient(cc *grpc.ClientConn) IOClient { + return &iOClient{cc} +} + +func (c *iOClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.IO/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *iOClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.IO/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// IOServer is the server API for IO service. +type IOServer interface { + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) +} + +func RegisterIOServer(s *grpc.Server, srv IOServer) { + s.RegisterService(&_IO_serviceDesc, srv) +} + +func _IO_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IOServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.IO/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IOServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _IO_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IOServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.IO/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IOServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +var _IO_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.IO", + HandlerType: (*IOServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Read", + Handler: _IO_Read_Handler, + }, + { + MethodName: "Write", + Handler: _IO_Write_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +// GuestCapabilitiesClient is the client API for GuestCapabilities service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GuestCapabilitiesClient interface { + GuestCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) + GuestCapability(ctx context.Context, in *GuestCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) + // IO helpers for streaming (copied from Stream service) + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) +} + +type guestCapabilitiesClient struct { + cc *grpc.ClientConn +} + +func NewGuestCapabilitiesClient(cc *grpc.ClientConn) GuestCapabilitiesClient { + return &guestCapabilitiesClient{cc} +} + +func (c *guestCapabilitiesClient) GuestCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) { + out := new(SystemCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.GuestCapabilities/GuestCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *guestCapabilitiesClient) GuestCapability(ctx context.Context, in *GuestCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.GuestCapabilities/GuestCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *guestCapabilitiesClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.GuestCapabilities/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *guestCapabilitiesClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.GuestCapabilities/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GuestCapabilitiesServer is the server API for GuestCapabilities service. +type GuestCapabilitiesServer interface { + GuestCapabilities(context.Context, *Empty) (*SystemCapabilityList, error) + GuestCapability(context.Context, *GuestCapabilityRequest) (*GenericResponse, error) + // IO helpers for streaming (copied from Stream service) + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) +} + +func RegisterGuestCapabilitiesServer(s *grpc.Server, srv GuestCapabilitiesServer) { + s.RegisterService(&_GuestCapabilities_serviceDesc, srv) +} + +func _GuestCapabilities_GuestCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestCapabilitiesServer).GuestCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.GuestCapabilities/GuestCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestCapabilitiesServer).GuestCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _GuestCapabilities_GuestCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GuestCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestCapabilitiesServer).GuestCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.GuestCapabilities/GuestCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestCapabilitiesServer).GuestCapability(ctx, req.(*GuestCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GuestCapabilities_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestCapabilitiesServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.GuestCapabilities/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestCapabilitiesServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _GuestCapabilities_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GuestCapabilitiesServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.GuestCapabilities/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GuestCapabilitiesServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +var _GuestCapabilities_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.GuestCapabilities", + HandlerType: (*GuestCapabilitiesServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GuestCapabilities", + Handler: _GuestCapabilities_GuestCapabilities_Handler, + }, + { + MethodName: "GuestCapability", + Handler: _GuestCapabilities_GuestCapability_Handler, + }, + { + MethodName: "Read", + Handler: _GuestCapabilities_Read_Handler, + }, + { + MethodName: "Write", + Handler: _GuestCapabilities_Write_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +// HostCapabilitiesClient is the client API for HostCapabilities service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type HostCapabilitiesClient interface { + HostCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) + HostCapability(ctx context.Context, in *HostCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) + // IO helpers for streaming (copied from Stream service) + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) +} + +type hostCapabilitiesClient struct { + cc *grpc.ClientConn +} + +func NewHostCapabilitiesClient(cc *grpc.ClientConn) HostCapabilitiesClient { + return &hostCapabilitiesClient{cc} +} + +func (c *hostCapabilitiesClient) HostCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) { + out := new(SystemCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.HostCapabilities/HostCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostCapabilitiesClient) HostCapability(ctx context.Context, in *HostCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.HostCapabilities/HostCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostCapabilitiesClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.HostCapabilities/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *hostCapabilitiesClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.HostCapabilities/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HostCapabilitiesServer is the server API for HostCapabilities service. +type HostCapabilitiesServer interface { + HostCapabilities(context.Context, *Empty) (*SystemCapabilityList, error) + HostCapability(context.Context, *HostCapabilityRequest) (*GenericResponse, error) + // IO helpers for streaming (copied from Stream service) + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) +} + +func RegisterHostCapabilitiesServer(s *grpc.Server, srv HostCapabilitiesServer) { + s.RegisterService(&_HostCapabilities_serviceDesc, srv) +} + +func _HostCapabilities_HostCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostCapabilitiesServer).HostCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.HostCapabilities/HostCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostCapabilitiesServer).HostCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostCapabilities_HostCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HostCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostCapabilitiesServer).HostCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.HostCapabilities/HostCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostCapabilitiesServer).HostCapability(ctx, req.(*HostCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostCapabilities_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostCapabilitiesServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.HostCapabilities/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostCapabilitiesServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _HostCapabilities_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HostCapabilitiesServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.HostCapabilities/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HostCapabilitiesServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +var _HostCapabilities_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.HostCapabilities", + HandlerType: (*HostCapabilitiesServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "HostCapabilities", + Handler: _HostCapabilities_HostCapabilities_Handler, + }, + { + MethodName: "HostCapability", + Handler: _HostCapabilities_HostCapability_Handler, + }, + { + MethodName: "Read", + Handler: _HostCapabilities_Read_Handler, + }, + { + MethodName: "Write", + Handler: _HostCapabilities_Write_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +// ProviderCapabilitiesClient is the client API for ProviderCapabilities service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ProviderCapabilitiesClient interface { + ProviderCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ProviderCapabilityList, error) + ProviderCapability(ctx context.Context, in *ProviderCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) + // IO helpers for streaming (copied from Stream service) + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) +} + +type providerCapabilitiesClient struct { + cc *grpc.ClientConn +} + +func NewProviderCapabilitiesClient(cc *grpc.ClientConn) ProviderCapabilitiesClient { + return &providerCapabilitiesClient{cc} +} + +func (c *providerCapabilitiesClient) ProviderCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ProviderCapabilityList, error) { + out := new(ProviderCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.ProviderCapabilities/ProviderCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerCapabilitiesClient) ProviderCapability(ctx context.Context, in *ProviderCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.ProviderCapabilities/ProviderCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerCapabilitiesClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.ProviderCapabilities/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerCapabilitiesClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.ProviderCapabilities/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProviderCapabilitiesServer is the server API for ProviderCapabilities service. +type ProviderCapabilitiesServer interface { + ProviderCapabilities(context.Context, *Empty) (*ProviderCapabilityList, error) + ProviderCapability(context.Context, *ProviderCapabilityRequest) (*GenericResponse, error) + // IO helpers for streaming (copied from Stream service) + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) +} + +func RegisterProviderCapabilitiesServer(s *grpc.Server, srv ProviderCapabilitiesServer) { + s.RegisterService(&_ProviderCapabilities_serviceDesc, srv) +} + +func _ProviderCapabilities_ProviderCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderCapabilitiesServer).ProviderCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.ProviderCapabilities/ProviderCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderCapabilitiesServer).ProviderCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProviderCapabilities_ProviderCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProviderCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderCapabilitiesServer).ProviderCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.ProviderCapabilities/ProviderCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderCapabilitiesServer).ProviderCapability(ctx, req.(*ProviderCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProviderCapabilities_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderCapabilitiesServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.ProviderCapabilities/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderCapabilitiesServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ProviderCapabilities_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderCapabilitiesServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.ProviderCapabilities/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderCapabilitiesServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +var _ProviderCapabilities_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.ProviderCapabilities", + HandlerType: (*ProviderCapabilitiesServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ProviderCapabilities", + Handler: _ProviderCapabilities_ProviderCapabilities_Handler, + }, + { + MethodName: "ProviderCapability", + Handler: _ProviderCapabilities_ProviderCapability_Handler, + }, + { + MethodName: "Read", + Handler: _ProviderCapabilities_Read_Handler, + }, + { + MethodName: "Write", + Handler: _ProviderCapabilities_Write_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +// ConfigClient is the client API for Config service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ConfigClient interface { + ConfigAttributes(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListResponse, error) + ConfigLoad(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) + ConfigValidate(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*ListResponse, error) + ConfigFinalize(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) + // IO helpers for streaming (copied from Stream service) + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) +} + +type configClient struct { + cc *grpc.ClientConn +} + +func NewConfigClient(cc *grpc.ClientConn) ConfigClient { + return &configClient{cc} +} + +func (c *configClient) ConfigAttributes(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Config/ConfigAttributes", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configClient) ConfigLoad(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) { + out := new(Configuration) + err := c.cc.Invoke(ctx, "/vagrant.proto.Config/ConfigLoad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configClient) ConfigValidate(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Config/ConfigValidate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configClient) ConfigFinalize(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) { + out := new(Configuration) + err := c.cc.Invoke(ctx, "/vagrant.proto.Config/ConfigFinalize", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.Config/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Config/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ConfigServer is the server API for Config service. +type ConfigServer interface { + ConfigAttributes(context.Context, *Empty) (*ListResponse, error) + ConfigLoad(context.Context, *Configuration) (*Configuration, error) + ConfigValidate(context.Context, *Configuration) (*ListResponse, error) + ConfigFinalize(context.Context, *Configuration) (*Configuration, error) + // IO helpers for streaming (copied from Stream service) + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) +} + +func RegisterConfigServer(s *grpc.Server, srv ConfigServer) { + s.RegisterService(&_Config_serviceDesc, srv) +} + +func _Config_ConfigAttributes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigServer).ConfigAttributes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Config/ConfigAttributes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigServer).ConfigAttributes(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Config_ConfigLoad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigServer).ConfigLoad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Config/ConfigLoad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigServer).ConfigLoad(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _Config_ConfigValidate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigServer).ConfigValidate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Config/ConfigValidate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigServer).ConfigValidate(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _Config_ConfigFinalize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigServer).ConfigFinalize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Config/ConfigFinalize", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigServer).ConfigFinalize(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _Config_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Config/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _Config_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Config/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +var _Config_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.Config", + HandlerType: (*ConfigServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ConfigAttributes", + Handler: _Config_ConfigAttributes_Handler, + }, + { + MethodName: "ConfigLoad", + Handler: _Config_ConfigLoad_Handler, + }, + { + MethodName: "ConfigValidate", + Handler: _Config_ConfigValidate_Handler, + }, + { + MethodName: "ConfigFinalize", + Handler: _Config_ConfigFinalize_Handler, + }, + { + MethodName: "Read", + Handler: _Config_Read_Handler, + }, + { + MethodName: "Write", + Handler: _Config_Write_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +// SyncedFolderClient is the client API for SyncedFolder service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type SyncedFolderClient interface { + Cleanup(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) + Disable(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) + Enable(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) + Info(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PluginInfo, error) + IsUsable(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Valid, error) + Name(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Identifier, error) + Prepare(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) + // IO helpers for streaming (copied from Stream service) + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) + // Guest capabilities helpers (copied from GuestCapabilities service) + GuestCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) + GuestCapability(ctx context.Context, in *GuestCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) + // Host capabilities helpers (copied from GuestCapabilities service) + HostCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) + HostCapability(ctx context.Context, in *HostCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) +} + +type syncedFolderClient struct { + cc *grpc.ClientConn +} + +func NewSyncedFolderClient(cc *grpc.ClientConn) SyncedFolderClient { + return &syncedFolderClient{cc} +} + +func (c *syncedFolderClient) Cleanup(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Cleanup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Disable(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Disable", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Enable(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Enable", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Info(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PluginInfo, error) { + out := new(PluginInfo) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Info", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) IsUsable(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Valid, error) { + out := new(Valid) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/IsUsable", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Name(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Identifier, error) { + out := new(Identifier) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Name", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Prepare(ctx context.Context, in *SyncedFolders, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Prepare", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) GuestCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) { + out := new(SystemCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/GuestCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) GuestCapability(ctx context.Context, in *GuestCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/GuestCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) HostCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) { + out := new(SystemCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/HostCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *syncedFolderClient) HostCapability(ctx context.Context, in *HostCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.SyncedFolder/HostCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SyncedFolderServer is the server API for SyncedFolder service. +type SyncedFolderServer interface { + Cleanup(context.Context, *SyncedFolders) (*Empty, error) + Disable(context.Context, *SyncedFolders) (*Empty, error) + Enable(context.Context, *SyncedFolders) (*Empty, error) + Info(context.Context, *Empty) (*PluginInfo, error) + IsUsable(context.Context, *Machine) (*Valid, error) + Name(context.Context, *Empty) (*Identifier, error) + Prepare(context.Context, *SyncedFolders) (*Empty, error) + // IO helpers for streaming (copied from Stream service) + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) + // Guest capabilities helpers (copied from GuestCapabilities service) + GuestCapabilities(context.Context, *Empty) (*SystemCapabilityList, error) + GuestCapability(context.Context, *GuestCapabilityRequest) (*GenericResponse, error) + // Host capabilities helpers (copied from GuestCapabilities service) + HostCapabilities(context.Context, *Empty) (*SystemCapabilityList, error) + HostCapability(context.Context, *HostCapabilityRequest) (*GenericResponse, error) +} + +func RegisterSyncedFolderServer(s *grpc.Server, srv SyncedFolderServer) { + s.RegisterService(&_SyncedFolder_serviceDesc, srv) +} + +func _SyncedFolder_Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncedFolders) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Cleanup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Cleanup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Cleanup(ctx, req.(*SyncedFolders)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Disable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncedFolders) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Disable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Disable", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Disable(ctx, req.(*SyncedFolders)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Enable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncedFolders) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Enable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Enable", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Enable(ctx, req.(*SyncedFolders)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Info(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Info", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Info(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_IsUsable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Machine) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).IsUsable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/IsUsable", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).IsUsable(ctx, req.(*Machine)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Name_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Name(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Name", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Name(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Prepare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncedFolders) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Prepare(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Prepare", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Prepare(ctx, req.(*SyncedFolders)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_GuestCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).GuestCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/GuestCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).GuestCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_GuestCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GuestCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).GuestCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/GuestCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).GuestCapability(ctx, req.(*GuestCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_HostCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).HostCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/HostCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).HostCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _SyncedFolder_HostCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HostCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SyncedFolderServer).HostCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.SyncedFolder/HostCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SyncedFolderServer).HostCapability(ctx, req.(*HostCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _SyncedFolder_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.SyncedFolder", + HandlerType: (*SyncedFolderServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Cleanup", + Handler: _SyncedFolder_Cleanup_Handler, + }, + { + MethodName: "Disable", + Handler: _SyncedFolder_Disable_Handler, + }, + { + MethodName: "Enable", + Handler: _SyncedFolder_Enable_Handler, + }, + { + MethodName: "Info", + Handler: _SyncedFolder_Info_Handler, + }, + { + MethodName: "IsUsable", + Handler: _SyncedFolder_IsUsable_Handler, + }, + { + MethodName: "Name", + Handler: _SyncedFolder_Name_Handler, + }, + { + MethodName: "Prepare", + Handler: _SyncedFolder_Prepare_Handler, + }, + { + MethodName: "Read", + Handler: _SyncedFolder_Read_Handler, + }, + { + MethodName: "Write", + Handler: _SyncedFolder_Write_Handler, + }, + { + MethodName: "GuestCapabilities", + Handler: _SyncedFolder_GuestCapabilities_Handler, + }, + { + MethodName: "GuestCapability", + Handler: _SyncedFolder_GuestCapability_Handler, + }, + { + MethodName: "HostCapabilities", + Handler: _SyncedFolder_HostCapabilities_Handler, + }, + { + MethodName: "HostCapability", + Handler: _SyncedFolder_HostCapability_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +// ProviderClient is the client API for Provider service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ProviderClient interface { + Action(ctx context.Context, in *GenericAction, opts ...grpc.CallOption) (*ListResponse, error) + Info(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PluginInfo, error) + IsInstalled(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Valid, error) + IsUsable(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Valid, error) + MachineIdChanged(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Machine, error) + Name(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Identifier, error) + RunAction(ctx context.Context, in *ExecuteAction, opts ...grpc.CallOption) (*GenericResponse, error) + SshInfo(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*MachineSshInfo, error) + State(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*MachineState, error) + // IO helpers for streaming (copied from Stream service) + Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) + Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) + // Config helpers (copied from Config service) + ConfigAttributes(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListResponse, error) + ConfigLoad(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) + ConfigValidate(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*ListResponse, error) + ConfigFinalize(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) + // Guest capabilities helpers (copied from GuestCapabilities service) + GuestCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) + GuestCapability(ctx context.Context, in *GuestCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) + // Host capabilities helpers (copied from HostCapabilities service) + HostCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) + HostCapability(ctx context.Context, in *HostCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) + // Provider capabilities helpers (copied from ProviderCapabilities service) + ProviderCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ProviderCapabilityList, error) + ProviderCapability(ctx context.Context, in *ProviderCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) +} + +type providerClient struct { + cc *grpc.ClientConn +} + +func NewProviderClient(cc *grpc.ClientConn) ProviderClient { + return &providerClient{cc} +} + +func (c *providerClient) Action(ctx context.Context, in *GenericAction, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/Action", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) Info(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PluginInfo, error) { + out := new(PluginInfo) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/Info", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) IsInstalled(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Valid, error) { + out := new(Valid) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/IsInstalled", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) IsUsable(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Valid, error) { + out := new(Valid) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/IsUsable", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) MachineIdChanged(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*Machine, error) { + out := new(Machine) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/MachineIdChanged", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) Name(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Identifier, error) { + out := new(Identifier) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/Name", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) RunAction(ctx context.Context, in *ExecuteAction, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/RunAction", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) SshInfo(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*MachineSshInfo, error) { + out := new(MachineSshInfo) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/SshInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) State(ctx context.Context, in *Machine, opts ...grpc.CallOption) (*MachineState, error) { + out := new(MachineState) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/State", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) Read(ctx context.Context, in *Identifier, opts ...grpc.CallOption) (*Content, error) { + out := new(Content) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/Read", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) Write(ctx context.Context, in *Content, opts ...grpc.CallOption) (*WriteResponse, error) { + out := new(WriteResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/Write", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) ConfigAttributes(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/ConfigAttributes", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) ConfigLoad(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) { + out := new(Configuration) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/ConfigLoad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) ConfigValidate(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/ConfigValidate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) ConfigFinalize(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) { + out := new(Configuration) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/ConfigFinalize", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) GuestCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) { + out := new(SystemCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/GuestCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) GuestCapability(ctx context.Context, in *GuestCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/GuestCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) HostCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*SystemCapabilityList, error) { + out := new(SystemCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/HostCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) HostCapability(ctx context.Context, in *HostCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/HostCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) ProviderCapabilities(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ProviderCapabilityList, error) { + out := new(ProviderCapabilityList) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/ProviderCapabilities", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *providerClient) ProviderCapability(ctx context.Context, in *ProviderCapabilityRequest, opts ...grpc.CallOption) (*GenericResponse, error) { + out := new(GenericResponse) + err := c.cc.Invoke(ctx, "/vagrant.proto.Provider/ProviderCapability", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ProviderServer is the server API for Provider service. +type ProviderServer interface { + Action(context.Context, *GenericAction) (*ListResponse, error) + Info(context.Context, *Empty) (*PluginInfo, error) + IsInstalled(context.Context, *Machine) (*Valid, error) + IsUsable(context.Context, *Machine) (*Valid, error) + MachineIdChanged(context.Context, *Machine) (*Machine, error) + Name(context.Context, *Empty) (*Identifier, error) + RunAction(context.Context, *ExecuteAction) (*GenericResponse, error) + SshInfo(context.Context, *Machine) (*MachineSshInfo, error) + State(context.Context, *Machine) (*MachineState, error) + // IO helpers for streaming (copied from Stream service) + Read(context.Context, *Identifier) (*Content, error) + Write(context.Context, *Content) (*WriteResponse, error) + // Config helpers (copied from Config service) + ConfigAttributes(context.Context, *Empty) (*ListResponse, error) + ConfigLoad(context.Context, *Configuration) (*Configuration, error) + ConfigValidate(context.Context, *Configuration) (*ListResponse, error) + ConfigFinalize(context.Context, *Configuration) (*Configuration, error) + // Guest capabilities helpers (copied from GuestCapabilities service) + GuestCapabilities(context.Context, *Empty) (*SystemCapabilityList, error) + GuestCapability(context.Context, *GuestCapabilityRequest) (*GenericResponse, error) + // Host capabilities helpers (copied from HostCapabilities service) + HostCapabilities(context.Context, *Empty) (*SystemCapabilityList, error) + HostCapability(context.Context, *HostCapabilityRequest) (*GenericResponse, error) + // Provider capabilities helpers (copied from ProviderCapabilities service) + ProviderCapabilities(context.Context, *Empty) (*ProviderCapabilityList, error) + ProviderCapability(context.Context, *ProviderCapabilityRequest) (*GenericResponse, error) +} + +func RegisterProviderServer(s *grpc.Server, srv ProviderServer) { + s.RegisterService(&_Provider_serviceDesc, srv) +} + +func _Provider_Action_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenericAction) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).Action(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/Action", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).Action(ctx, req.(*GenericAction)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).Info(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/Info", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).Info(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_IsInstalled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Machine) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).IsInstalled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/IsInstalled", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).IsInstalled(ctx, req.(*Machine)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_IsUsable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Machine) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).IsUsable(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/IsUsable", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).IsUsable(ctx, req.(*Machine)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_MachineIdChanged_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Machine) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).MachineIdChanged(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/MachineIdChanged", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).MachineIdChanged(ctx, req.(*Machine)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_Name_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).Name(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/Name", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).Name(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_RunAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExecuteAction) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).RunAction(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/RunAction", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).RunAction(ctx, req.(*ExecuteAction)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_SshInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Machine) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).SshInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/SshInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).SshInfo(ctx, req.(*Machine)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_State_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Machine) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).State(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/State", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).State(ctx, req.(*Machine)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_Read_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Identifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).Read(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/Read", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).Read(ctx, req.(*Identifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_Write_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Content) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).Write(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/Write", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).Write(ctx, req.(*Content)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_ConfigAttributes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).ConfigAttributes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/ConfigAttributes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).ConfigAttributes(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_ConfigLoad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).ConfigLoad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/ConfigLoad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).ConfigLoad(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_ConfigValidate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).ConfigValidate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/ConfigValidate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).ConfigValidate(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_ConfigFinalize_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).ConfigFinalize(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/ConfigFinalize", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).ConfigFinalize(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_GuestCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).GuestCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/GuestCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).GuestCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_GuestCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GuestCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).GuestCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/GuestCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).GuestCapability(ctx, req.(*GuestCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_HostCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).HostCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/HostCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).HostCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_HostCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HostCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).HostCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/HostCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).HostCapability(ctx, req.(*HostCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_ProviderCapabilities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).ProviderCapabilities(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/ProviderCapabilities", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).ProviderCapabilities(ctx, req.(*Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Provider_ProviderCapability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProviderCapabilityRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ProviderServer).ProviderCapability(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/vagrant.proto.Provider/ProviderCapability", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ProviderServer).ProviderCapability(ctx, req.(*ProviderCapabilityRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Provider_serviceDesc = grpc.ServiceDesc{ + ServiceName: "vagrant.proto.Provider", + HandlerType: (*ProviderServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Action", + Handler: _Provider_Action_Handler, + }, + { + MethodName: "Info", + Handler: _Provider_Info_Handler, + }, + { + MethodName: "IsInstalled", + Handler: _Provider_IsInstalled_Handler, + }, + { + MethodName: "IsUsable", + Handler: _Provider_IsUsable_Handler, + }, + { + MethodName: "MachineIdChanged", + Handler: _Provider_MachineIdChanged_Handler, + }, + { + MethodName: "Name", + Handler: _Provider_Name_Handler, + }, + { + MethodName: "RunAction", + Handler: _Provider_RunAction_Handler, + }, + { + MethodName: "SshInfo", + Handler: _Provider_SshInfo_Handler, + }, + { + MethodName: "State", + Handler: _Provider_State_Handler, + }, + { + MethodName: "Read", + Handler: _Provider_Read_Handler, + }, + { + MethodName: "Write", + Handler: _Provider_Write_Handler, + }, + { + MethodName: "ConfigAttributes", + Handler: _Provider_ConfigAttributes_Handler, + }, + { + MethodName: "ConfigLoad", + Handler: _Provider_ConfigLoad_Handler, + }, + { + MethodName: "ConfigValidate", + Handler: _Provider_ConfigValidate_Handler, + }, + { + MethodName: "ConfigFinalize", + Handler: _Provider_ConfigFinalize_Handler, + }, + { + MethodName: "GuestCapabilities", + Handler: _Provider_GuestCapabilities_Handler, + }, + { + MethodName: "GuestCapability", + Handler: _Provider_GuestCapability_Handler, + }, + { + MethodName: "HostCapabilities", + Handler: _Provider_HostCapabilities_Handler, + }, + { + MethodName: "HostCapability", + Handler: _Provider_HostCapability_Handler, + }, + { + MethodName: "ProviderCapabilities", + Handler: _Provider_ProviderCapabilities_Handler, + }, + { + MethodName: "ProviderCapability", + Handler: _Provider_ProviderCapability_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "vagrant.proto", +} + +func init() { proto.RegisterFile("vagrant.proto", fileDescriptor_05d5fddafc02be38) } + +var fileDescriptor_05d5fddafc02be38 = []byte{ + // 1119 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0xdf, 0x6e, 0x1b, 0x45, + 0x17, 0x97, 0x1d, 0xff, 0x89, 0x8f, 0x63, 0xc7, 0x1d, 0xf9, 0x8b, 0x5c, 0x37, 0x1f, 0x35, 0xdb, + 0x56, 0xb8, 0x42, 0xca, 0x45, 0xb8, 0x08, 0x88, 0x46, 0x90, 0x3a, 0x6e, 0x6b, 0x9a, 0x96, 0xb0, + 0x11, 0x05, 0x09, 0x41, 0x34, 0xf1, 0x8e, 0xed, 0x11, 0xeb, 0xd9, 0x65, 0x76, 0xd6, 0xc2, 0x5c, + 0x54, 0xbc, 0x02, 0x37, 0x88, 0xa7, 0xe0, 0x86, 0x0b, 0x9e, 0x80, 0x27, 0xe2, 0x05, 0xd0, 0xce, + 0xce, 0x3a, 0xbb, 0xeb, 0x5d, 0xdb, 0xb8, 0x45, 0x69, 0xa4, 0xde, 0xcd, 0x39, 0x33, 0xbf, 0x33, + 0xe7, 0x9c, 0x39, 0xf3, 0x3b, 0x33, 0x50, 0x99, 0xe0, 0x21, 0xc7, 0x4c, 0xec, 0xd9, 0xdc, 0x12, + 0x16, 0x8a, 0x8a, 0x5a, 0x11, 0xf2, 0xdd, 0xb1, 0x2d, 0xa6, 0xda, 0x1d, 0x28, 0x3e, 0xc3, 0xfd, + 0x11, 0x65, 0x04, 0x35, 0xa0, 0x38, 0xf6, 0x87, 0x8d, 0x4c, 0x2b, 0xd3, 0x2e, 0xe9, 0x81, 0xa8, + 0xdd, 0x86, 0xfc, 0x0b, 0x6c, 0x52, 0x03, 0xed, 0x40, 0x81, 0x13, 0xc7, 0x35, 0x85, 0x5c, 0xb1, + 0xa9, 0x2b, 0x49, 0x6b, 0x01, 0xf4, 0x0c, 0xc2, 0x04, 0x1d, 0x50, 0xc2, 0x11, 0x82, 0x1c, 0xc3, + 0xe3, 0xc0, 0x8a, 0x1c, 0x6b, 0x9f, 0x01, 0x9c, 0x9a, 0xee, 0x90, 0xb2, 0x1e, 0x1b, 0x58, 0xa8, + 0x05, 0x65, 0x83, 0x38, 0x7d, 0x4e, 0x6d, 0x41, 0x2d, 0xa6, 0x16, 0x86, 0x55, 0xa8, 0x09, 0x9b, + 0x36, 0xa7, 0x16, 0xa7, 0x62, 0xda, 0xc8, 0xb6, 0x32, 0xed, 0x0d, 0x7d, 0x26, 0x6b, 0x07, 0x50, + 0xec, 0x58, 0x4c, 0x10, 0x26, 0x3c, 0x87, 0x04, 0xe6, 0x43, 0x22, 0x94, 0x0d, 0x25, 0xa1, 0x3a, + 0xe4, 0x27, 0xd8, 0x74, 0x89, 0xc4, 0x96, 0x74, 0x5f, 0xd0, 0xde, 0x83, 0xca, 0x57, 0x9c, 0x0a, + 0xa2, 0x13, 0xc7, 0xb6, 0x98, 0x43, 0x3c, 0xb8, 0x49, 0xd8, 0x50, 0x8c, 0x24, 0x3c, 0xaf, 0x2b, + 0x49, 0x7b, 0x08, 0xb5, 0xb3, 0xa9, 0x23, 0xc8, 0xb8, 0x83, 0x6d, 0x7c, 0x41, 0x4d, 0x2a, 0xa6, + 0x49, 0x51, 0x49, 0x2f, 0x4d, 0x2c, 0x06, 0x16, 0x1f, 0xab, 0x9d, 0x66, 0xb2, 0x76, 0x0c, 0xe8, + 0x94, 0x5b, 0x13, 0x6a, 0x10, 0xbe, 0x82, 0x15, 0xb5, 0x72, 0x66, 0x45, 0xc9, 0xda, 0x37, 0x50, + 0x8f, 0x7b, 0x72, 0x42, 0x1d, 0x81, 0x3a, 0xb0, 0xd5, 0x0f, 0x34, 0x94, 0x38, 0x8d, 0x4c, 0x6b, + 0xa3, 0x5d, 0xde, 0xbf, 0xbd, 0x17, 0x39, 0xe6, 0xbd, 0x38, 0x54, 0x8f, 0x80, 0xb4, 0x73, 0xd8, + 0x99, 0x77, 0x51, 0x9a, 0xef, 0x26, 0x9a, 0x7f, 0x37, 0x66, 0x7e, 0x1e, 0x1c, 0xdb, 0xe0, 0x3e, + 0x6c, 0x3f, 0x26, 0x8c, 0x70, 0xda, 0x0f, 0xa7, 0x3c, 0x54, 0x42, 0xa5, 0x59, 0x09, 0xfd, 0x92, + 0x81, 0x9d, 0xc7, 0x2e, 0x71, 0x44, 0xc8, 0x18, 0xf9, 0xc1, 0x53, 0xa0, 0x4f, 0x00, 0x66, 0x56, + 0xa7, 0x12, 0xb6, 0x42, 0xa4, 0x21, 0x48, 0xb8, 0xb2, 0xb3, 0x91, 0xca, 0x46, 0xbb, 0x50, 0xc2, + 0x7c, 0xe8, 0x8e, 0x09, 0x13, 0x4e, 0x63, 0x43, 0xce, 0x5d, 0x2a, 0xb4, 0xdf, 0x32, 0xf0, 0xbf, + 0x27, 0xd6, 0x7f, 0xe2, 0x52, 0x0b, 0xca, 0x84, 0x4d, 0x28, 0xb7, 0x98, 0xb7, 0x95, 0x72, 0x2b, + 0xac, 0x5a, 0xe2, 0xda, 0xaf, 0x19, 0xb8, 0x99, 0x90, 0x7e, 0xe5, 0xde, 0x51, 0x82, 0x7b, 0x2b, + 0x1c, 0xde, 0xeb, 0xc8, 0xd9, 0x21, 0x54, 0x3a, 0x16, 0x1b, 0xd0, 0xa1, 0xcb, 0xb1, 0xbc, 0xc9, + 0x08, 0x72, 0x06, 0x16, 0x38, 0xa8, 0x78, 0x6f, 0x9c, 0x6e, 0x5c, 0xbb, 0x0b, 0x5b, 0x5e, 0x01, + 0xce, 0xca, 0xa5, 0x0e, 0x79, 0x2a, 0xc8, 0xd8, 0xaf, 0xc0, 0x92, 0xee, 0x0b, 0xda, 0xb7, 0x50, + 0x39, 0x9b, 0xb2, 0x3e, 0x31, 0x1e, 0x59, 0xa6, 0x41, 0xb8, 0x93, 0xce, 0x5d, 0xde, 0xcc, 0xc0, + 0x5f, 0x14, 0x6c, 0x35, 0xb8, 0xc4, 0x58, 0x92, 0x6c, 0x82, 0x28, 0x02, 0xd1, 0x8b, 0x41, 0x95, + 0xed, 0x51, 0x3f, 0x88, 0x61, 0xee, 0xd6, 0xa6, 0xc7, 0xf0, 0x05, 0x54, 0xba, 0x3f, 0x92, 0xbe, + 0x2b, 0xc8, 0x02, 0x78, 0x90, 0x96, 0x6c, 0x72, 0x5a, 0x36, 0xa2, 0x26, 0x5f, 0x42, 0x55, 0xd1, + 0xf4, 0x99, 0x33, 0x92, 0x14, 0x8a, 0x20, 0x37, 0xb2, 0x9c, 0xe0, 0x16, 0xc9, 0xb1, 0xa7, 0xb3, + 0x2d, 0x2e, 0x14, 0x61, 0xca, 0x31, 0x6a, 0x43, 0xcd, 0xe6, 0x74, 0x82, 0x05, 0x39, 0xff, 0x9e, + 0x4c, 0xcf, 0x6d, 0x2c, 0x46, 0xca, 0x78, 0x55, 0xe9, 0x9f, 0x92, 0xe9, 0x29, 0x16, 0x23, 0x8f, + 0x86, 0x5c, 0x87, 0x70, 0xe9, 0x69, 0xce, 0xa7, 0xa1, 0x40, 0xd6, 0x26, 0xb0, 0x15, 0xec, 0x2f, + 0xb0, 0x20, 0xa8, 0x0a, 0x59, 0x6a, 0xa8, 0xbd, 0xb3, 0xd4, 0x40, 0xef, 0xc3, 0x0d, 0x67, 0x64, + 0x71, 0x71, 0x1e, 0xa6, 0x75, 0x3f, 0xb4, 0x9a, 0x9c, 0x38, 0x0e, 0x71, 0xfb, 0x7d, 0xa8, 0x99, + 0x16, 0x1b, 0x46, 0xd6, 0xfa, 0x2e, 0x6d, 0x7b, 0xfa, 0xd0, 0xd2, 0xfd, 0x97, 0x90, 0xed, 0x7d, + 0x8e, 0x3e, 0x82, 0x9c, 0x4e, 0xb0, 0x81, 0x6e, 0xc6, 0x4a, 0xf8, 0xb2, 0xe7, 0x34, 0x77, 0x62, + 0x53, 0x41, 0x83, 0x38, 0x84, 0xbc, 0xa4, 0x7c, 0x94, 0xb2, 0xa0, 0xb9, 0x1b, 0xd3, 0x47, 0x1a, + 0xc4, 0xfe, 0x9f, 0x59, 0xb8, 0x11, 0x65, 0x25, 0x4a, 0x1c, 0xf4, 0x3c, 0x49, 0x59, 0x8f, 0x19, + 0x92, 0xfd, 0xb5, 0x79, 0x67, 0x09, 0x29, 0x48, 0xb6, 0xfd, 0x1a, 0xb6, 0x63, 0xd4, 0x87, 0xee, + 0xc5, 0x70, 0xc9, 0xd4, 0xd8, 0x7c, 0x27, 0xbe, 0x2c, 0xc6, 0xb6, 0x57, 0x97, 0xb9, 0x3f, 0xb2, + 0x50, 0x8b, 0x70, 0xa7, 0x97, 0xa3, 0x67, 0x09, 0xba, 0x57, 0xc8, 0xdb, 0x0b, 0xa8, 0x46, 0xe9, + 0x19, 0xdd, 0x8d, 0xc1, 0x12, 0xd9, 0xfb, 0x0d, 0xce, 0xda, 0x5f, 0x59, 0xa8, 0xcf, 0x11, 0xb3, + 0x97, 0xa5, 0xb3, 0x14, 0x7d, 0x72, 0xf6, 0xee, 0x2d, 0xe5, 0x7a, 0x99, 0xbf, 0xef, 0x12, 0x9f, + 0x28, 0xed, 0xe5, 0x8d, 0xe2, 0x8d, 0xcf, 0xe3, 0xef, 0x1b, 0x50, 0xf0, 0xdb, 0x10, 0xea, 0x42, + 0xcd, 0x1f, 0x1d, 0x09, 0xc1, 0xe9, 0x85, 0x2b, 0x52, 0xb3, 0x76, 0x2b, 0xa6, 0x8d, 0x34, 0xa2, + 0x27, 0x00, 0xbe, 0x99, 0x13, 0x0b, 0x1b, 0x68, 0x77, 0xde, 0xab, 0xcb, 0x96, 0xd7, 0x5c, 0x38, + 0x8b, 0x9e, 0x42, 0xd5, 0x57, 0xc8, 0x37, 0xb5, 0xc7, 0xa6, 0x8b, 0xad, 0x2d, 0x74, 0xeb, 0x24, + 0x30, 0xf6, 0x88, 0x32, 0x6c, 0xd2, 0x9f, 0xc8, 0x2b, 0xb9, 0x76, 0x75, 0x07, 0xf6, 0x73, 0x11, + 0xb6, 0xc2, 0x2d, 0x1d, 0x1d, 0x42, 0xb1, 0x63, 0x12, 0xcc, 0x5c, 0x7b, 0x2e, 0xa2, 0x48, 0xeb, + 0x6f, 0x26, 0x9e, 0xa5, 0x07, 0x3f, 0xa6, 0x0e, 0xbe, 0x30, 0xc9, 0x5a, 0xf0, 0x07, 0x50, 0xe8, + 0xb2, 0xb5, 0xd1, 0x07, 0x90, 0x93, 0x3d, 0x3a, 0xb9, 0xcc, 0xe2, 0xc9, 0x0d, 0xfd, 0x8b, 0x3e, + 0x84, 0xcd, 0x9e, 0xf3, 0xa5, 0xef, 0x76, 0x3c, 0x8f, 0xaa, 0xff, 0xce, 0x6d, 0xe9, 0xff, 0xcc, + 0x0e, 0x20, 0xf7, 0xdc, 0x7b, 0x56, 0xac, 0xb6, 0x65, 0xe8, 0xb3, 0x76, 0x08, 0xc5, 0x53, 0x4e, + 0x6c, 0xcc, 0xd7, 0x0b, 0xf5, 0xca, 0x2a, 0xe6, 0x1a, 0x35, 0xe1, 0xeb, 0xd1, 0xf5, 0xf6, 0xff, + 0x2e, 0xc3, 0x66, 0xc0, 0xf5, 0xa8, 0x03, 0x05, 0xf5, 0x78, 0xdd, 0x4d, 0x86, 0xf9, 0xb3, 0x8b, + 0xc9, 0x69, 0xed, 0x7b, 0xf0, 0x31, 0x94, 0x7b, 0x4e, 0x8f, 0x39, 0x02, 0x9b, 0x26, 0x31, 0xfe, + 0xe5, 0x55, 0x58, 0xff, 0x12, 0x3d, 0x84, 0x9a, 0x5a, 0xd0, 0x33, 0x3a, 0x23, 0xcc, 0x86, 0x0b, + 0xf6, 0x4e, 0xd1, 0xaf, 0x7f, 0x11, 0x7b, 0x50, 0xd2, 0x5d, 0x96, 0x92, 0xf4, 0xc8, 0x7f, 0x62, + 0x69, 0xc1, 0x7d, 0x0a, 0xc5, 0xe0, 0x9b, 0x90, 0xe6, 0xfe, 0xff, 0x93, 0xf5, 0x01, 0xec, 0x01, + 0xe4, 0xfd, 0x87, 0x7e, 0x1a, 0xfe, 0x56, 0x0a, 0x5e, 0x82, 0xae, 0x8e, 0x14, 0xde, 0x36, 0xfb, + 0xd5, 0x5d, 0x7b, 0x4b, 0xa0, 0xaf, 0xf9, 0xdb, 0x70, 0x1d, 0xdf, 0xe8, 0x17, 0x05, 0xa9, 0xfe, + 0xe0, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x65, 0xa8, 0x70, 0xea, 0x30, 0x16, 0x00, 0x00, +} diff --git a/ext/go-plugin/vagrant/plugin/proto/vagrant.proto b/ext/go-plugin/vagrant/plugin/proto/vagrant.proto new file mode 100644 index 000000000..902be05b7 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/proto/vagrant.proto @@ -0,0 +1,196 @@ +syntax = "proto3"; +package vagrant.proto; + +message Empty{} + +message Machine { + string machine = 1; +} + +message Valid { + bool result = 1; +} + +message Identifier { + string name = 1; +} + +message PluginInfo { + string description = 1; + int64 priority = 2; +} + +message Content { + string target = 1; + string value = 2; +} + +message WriteResponse { + int32 length = 1; +} + +service IO { + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); +} + +message SystemCapability { + string name = 1; + string platform = 2; +} + +message ProviderCapability { + string name = 1; + string provider = 2; +} + +message SystemCapabilityList { + repeated SystemCapability capabilities = 1; +} + +message ProviderCapabilityList { + repeated ProviderCapability capabilities = 1; +} + +message GenericResponse { + string result = 1; +} + +message GuestCapabilityRequest { + SystemCapability capability = 1; + string machine = 2; + string arguments = 3; +} + +message HostCapabilityRequest { + SystemCapability capability = 1; + string environment = 2; + string arguments = 3; +} + +message ProviderCapabilityRequest { + ProviderCapability capability = 1; + string machine = 2; + string arguments = 3; +} + +service GuestCapabilities { + rpc GuestCapabilities(Empty) returns (SystemCapabilityList); + rpc GuestCapability(GuestCapabilityRequest) returns (GenericResponse); + // IO helpers for streaming (copied from Stream service) + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); +} + +service HostCapabilities { + rpc HostCapabilities(Empty) returns (SystemCapabilityList); + rpc HostCapability(HostCapabilityRequest) returns (GenericResponse); + // IO helpers for streaming (copied from Stream service) + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); +} + +service ProviderCapabilities { + rpc ProviderCapabilities (Empty) returns (ProviderCapabilityList); + rpc ProviderCapability (ProviderCapabilityRequest) returns (GenericResponse); + // IO helpers for streaming (copied from Stream service) + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); +} + +message Configuration { + string data = 1; + string machine = 2; +} + +message ListResponse { + repeated string items = 1; +} + +service Config { + rpc ConfigAttributes(Empty) returns (ListResponse); + rpc ConfigLoad(Configuration) returns (Configuration); + rpc ConfigValidate(Configuration) returns (ListResponse); + rpc ConfigFinalize(Configuration) returns (Configuration); + // IO helpers for streaming (copied from Stream service) + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); +} + +message SyncedFolders { + string machine = 1; + string folders = 2; + string options = 3; +} + +service SyncedFolder { + rpc Cleanup(SyncedFolders) returns (Empty); + rpc Disable(SyncedFolders) returns (Empty); + rpc Enable(SyncedFolders) returns (Empty); + rpc Info(Empty) returns (PluginInfo); + rpc IsUsable(Machine) returns (Valid); + rpc Name(Empty) returns (Identifier); + rpc Prepare(SyncedFolders) returns (Empty); + // IO helpers for streaming (copied from Stream service) + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); + // Guest capabilities helpers (copied from GuestCapabilities service) + rpc GuestCapabilities(Empty) returns (SystemCapabilityList); + rpc GuestCapability(GuestCapabilityRequest) returns (GenericResponse); + // Host capabilities helpers (copied from GuestCapabilities service) + rpc HostCapabilities(Empty) returns (SystemCapabilityList); + rpc HostCapability(HostCapabilityRequest) returns (GenericResponse); +} + +message GenericAction { + string name = 1; + string machine = 2; +} + +message ExecuteAction { + string name = 1; + string data = 2; + string machine = 3; +} + +message MachineSshInfo { + string host = 1; + int64 port = 2; + string private_key_path = 3; + string username = 4; +} + +message MachineState { + string id = 1; + string short_description = 2; + string long_description = 3; +} + +service Provider { + rpc Action(GenericAction) returns (ListResponse); + rpc Info(Empty) returns (PluginInfo); + rpc IsInstalled(Machine) returns (Valid); + rpc IsUsable(Machine) returns (Valid); + rpc MachineIdChanged(Machine) returns (Machine); + rpc Name(Empty) returns (Identifier); + rpc RunAction(ExecuteAction) returns (GenericResponse); + rpc SshInfo(Machine) returns (MachineSshInfo); + rpc State(Machine) returns (MachineState); + // IO helpers for streaming (copied from Stream service) + rpc Read(Identifier) returns (Content); + rpc Write(Content) returns (WriteResponse); + // Config helpers (copied from Config service) + rpc ConfigAttributes(Empty) returns (ListResponse); + rpc ConfigLoad(Configuration) returns (Configuration); + rpc ConfigValidate(Configuration) returns (ListResponse); + rpc ConfigFinalize(Configuration) returns (Configuration); + // Guest capabilities helpers (copied from GuestCapabilities service) + rpc GuestCapabilities(Empty) returns (SystemCapabilityList); + rpc GuestCapability(GuestCapabilityRequest) returns (GenericResponse); + // Host capabilities helpers (copied from HostCapabilities service) + rpc HostCapabilities(Empty) returns (SystemCapabilityList); + rpc HostCapability(HostCapabilityRequest) returns (GenericResponse); + // Provider capabilities helpers (copied from ProviderCapabilities service) + rpc ProviderCapabilities (Empty) returns (ProviderCapabilityList); + rpc ProviderCapability (ProviderCapabilityRequest) returns (GenericResponse); +} diff --git a/ext/go-plugin/vagrant/plugin/provider.go b/ext/go-plugin/vagrant/plugin/provider.go new file mode 100644 index 000000000..ba5c871d2 --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/provider.go @@ -0,0 +1,346 @@ +package plugin + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc" + + go_plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/plugin/proto" + + "github.com/LK4D4/joincontext" +) + +type Provider interface { + vagrant.Provider + Meta +} + +type ProviderPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl Provider +} + +type GRPCProviderClient struct { + GRPCCoreClient + GRPCConfigClient + GRPCGuestCapabilitiesClient + GRPCHostCapabilitiesClient + GRPCProviderCapabilitiesClient + GRPCIOClient + client vagrant_proto.ProviderClient + doneCtx context.Context +} + +func (c *GRPCProviderClient) Action(ctx context.Context, actionName string, m *vagrant.Machine) (r []string, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.Action(jctx, &vagrant_proto.GenericAction{ + Name: actionName, + Machine: machData}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + r = resp.Items + return +} + +func (c *GRPCProviderClient) Info() *vagrant.ProviderInfo { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.Info(jctx, &vagrant_proto.Empty{}) + if err != nil { + return &vagrant.ProviderInfo{} + } + return &vagrant.ProviderInfo{ + Description: resp.Description, + Priority: resp.Priority} +} + +func (c *GRPCProviderClient) IsInstalled(ctx context.Context, m *vagrant.Machine) (r bool, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.IsInstalled(jctx, &vagrant_proto.Machine{ + Machine: machData}) + if err != nil { + return false, handleGrpcError(err, c.doneCtx, ctx) + } + r = resp.Result + return +} + +func (c *GRPCProviderClient) IsUsable(ctx context.Context, m *vagrant.Machine) (r bool, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.IsUsable(jctx, &vagrant_proto.Machine{ + Machine: machData}) + if err != nil { + return false, handleGrpcError(err, c.doneCtx, ctx) + } + r = resp.Result + return +} + +func (c *GRPCProviderClient) MachineIdChanged(ctx context.Context, m *vagrant.Machine) (err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + _, err = c.client.MachineIdChanged(jctx, &vagrant_proto.Machine{ + Machine: machData}) + if err != nil { + return handleGrpcError(err, c.doneCtx, ctx) + } + return +} + +func (c *GRPCProviderClient) RunAction(ctx context.Context, actName string, args interface{}, m *vagrant.Machine) (r interface{}, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + runData, err := json.Marshal(args) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.RunAction(jctx, &vagrant_proto.ExecuteAction{ + Name: actName, + Data: string(runData), + Machine: machData}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + err = json.Unmarshal([]byte(resp.Result), &r) + if err != nil { + return + } + return +} + +func (c *GRPCProviderClient) SshInfo(ctx context.Context, m *vagrant.Machine) (r *vagrant.SshInfo, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.SshInfo(jctx, &vagrant_proto.Machine{ + Machine: machData}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + r = &vagrant.SshInfo{ + Host: resp.Host, + Port: resp.Port, + PrivateKeyPath: resp.PrivateKeyPath, + Username: resp.Username} + return +} + +func (c *GRPCProviderClient) State(ctx context.Context, m *vagrant.Machine) (r *vagrant.MachineState, err error) { + machData, err := vagrant.DumpMachine(m) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.State(jctx, &vagrant_proto.Machine{ + Machine: machData}) + if err != nil { + return nil, handleGrpcError(err, c.doneCtx, ctx) + } + r = &vagrant.MachineState{ + Id: resp.Id, + ShortDesc: resp.ShortDescription, + LongDesc: resp.LongDescription} + return +} + +func (c *GRPCProviderClient) Name() string { + ctx := context.Background() + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.Name(jctx, &vagrant_proto.Empty{}) + if err != nil { + return "" + } + return resp.Name +} + +func (p *ProviderPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + client := vagrant_proto.NewProviderClient(c) + return &GRPCProviderClient{ + GRPCConfigClient: GRPCConfigClient{ + client: client, + doneCtx: ctx}, + GRPCGuestCapabilitiesClient: GRPCGuestCapabilitiesClient{ + client: client, + doneCtx: ctx}, + GRPCHostCapabilitiesClient: GRPCHostCapabilitiesClient{ + client: client, + doneCtx: ctx}, + GRPCProviderCapabilitiesClient: GRPCProviderCapabilitiesClient{ + client: client, + doneCtx: ctx}, + GRPCIOClient: GRPCIOClient{ + client: client, + doneCtx: ctx}, + client: client, + doneCtx: ctx, + }, nil +} + +func (p *ProviderPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + p.Impl.Init() + vagrant_proto.RegisterProviderServer(s, &GRPCProviderServer{ + Impl: p.Impl, + GRPCConfigServer: GRPCConfigServer{ + Impl: p.Impl}, + GRPCGuestCapabilitiesServer: GRPCGuestCapabilitiesServer{ + Impl: p.Impl}, + GRPCHostCapabilitiesServer: GRPCHostCapabilitiesServer{ + Impl: p.Impl}, + GRPCProviderCapabilitiesServer: GRPCProviderCapabilitiesServer{ + Impl: p.Impl}, + GRPCIOServer: GRPCIOServer{ + Impl: p.Impl}}) + return nil +} + +type GRPCProviderServer struct { + GRPCIOServer + GRPCConfigServer + GRPCGuestCapabilitiesServer + GRPCHostCapabilitiesServer + GRPCProviderCapabilitiesServer + Impl Provider +} + +func (s *GRPCProviderServer) Action(ctx context.Context, req *vagrant_proto.GenericAction) (resp *vagrant_proto.ListResponse, err error) { + resp = &vagrant_proto.ListResponse{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + r, err := s.Impl.Action(ctx, req.Name, m) + if err != nil { + return + } + resp.Items = r + return +} + +func (s *GRPCProviderServer) RunAction(ctx context.Context, req *vagrant_proto.ExecuteAction) (resp *vagrant_proto.GenericResponse, err error) { + resp = &vagrant_proto.GenericResponse{} + var args interface{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + if err = json.Unmarshal([]byte(req.Data), &args); err != nil { + return + } + r, err := s.Impl.RunAction(ctx, req.Name, args, m) + if err != nil { + return + } + result, err := json.Marshal(r) + if err != nil { + return + } + resp.Result = string(result) + return +} + +func (s *GRPCProviderServer) Info(ctx context.Context, req *vagrant_proto.Empty) (resp *vagrant_proto.PluginInfo, err error) { + resp = &vagrant_proto.PluginInfo{} + r := s.Impl.Info() + resp.Description = r.Description + resp.Priority = r.Priority + return +} + +func (s *GRPCProviderServer) IsInstalled(ctx context.Context, req *vagrant_proto.Machine) (resp *vagrant_proto.Valid, err error) { + resp = &vagrant_proto.Valid{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + resp.Result, err = s.Impl.IsInstalled(ctx, m) + return +} + +func (s *GRPCProviderServer) IsUsable(ctx context.Context, req *vagrant_proto.Machine) (resp *vagrant_proto.Valid, err error) { + resp = &vagrant_proto.Valid{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + resp.Result, err = s.Impl.IsUsable(ctx, m) + return +} + +func (s *GRPCProviderServer) SshInfo(ctx context.Context, req *vagrant_proto.Machine) (resp *vagrant_proto.MachineSshInfo, err error) { + resp = &vagrant_proto.MachineSshInfo{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + r, err := s.Impl.SshInfo(ctx, m) + if err != nil { + return + } + resp.Host = r.Host + resp.Port = r.Port + resp.Username = r.Username + resp.PrivateKeyPath = r.PrivateKeyPath + return +} + +func (s *GRPCProviderServer) State(ctx context.Context, req *vagrant_proto.Machine) (resp *vagrant_proto.MachineState, err error) { + resp = &vagrant_proto.MachineState{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + r, err := s.Impl.State(ctx, m) + if err != nil { + return + } + resp.Id = r.Id + resp.ShortDescription = r.ShortDesc + resp.LongDescription = r.LongDesc + return +} + +func (s *GRPCProviderServer) MachineIdChanged(ctx context.Context, req *vagrant_proto.Machine) (resp *vagrant_proto.Machine, err error) { + resp = &vagrant_proto.Machine{} + m, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + if err = s.Impl.MachineIdChanged(ctx, m); err != nil { + return + } + mdata, err := vagrant.DumpMachine(m) + if err != nil { + return + } + resp = &vagrant_proto.Machine{Machine: mdata} + return +} + +func (s *GRPCProviderServer) Name(ctx context.Context, req *vagrant_proto.Empty) (*vagrant_proto.Identifier, error) { + return &vagrant_proto.Identifier{Name: s.Impl.Name()}, nil +} diff --git a/ext/go-plugin/vagrant/plugin/provider_test.go b/ext/go-plugin/vagrant/plugin/provider_test.go new file mode 100644 index 000000000..d9af0071b --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/provider_test.go @@ -0,0 +1,667 @@ +package plugin + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +func TestProvider_Action(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp, err := impl.Action(context.Background(), "valid", &vagrant.Machine{}) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if resp[0] != "self::DoTask" { + t.Errorf("%s != self::DoTask", resp[0]) + } +} + +func TestProvider_Action_invalid(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + _, err = impl.Action(context.Background(), "invalid", &vagrant.Machine{}) + if err == nil { + t.Errorf("illegal action") + } +} + +func TestProvider_Action_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.Action(ctx, "pause", &vagrant.Machine{}) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_Action_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.Action(ctx, "pause", &vagrant.Machine{}) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_IsInstalled(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + installed, err := impl.IsInstalled(context.Background(), &vagrant.Machine{}) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if !installed { + t.Errorf("bad result") + } +} + +func TestProvider_IsInstalled_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.IsInstalled(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_IsInstalled_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.IsInstalled(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} +func TestProvider_IsUsable(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + usable, err := impl.IsUsable(context.Background(), &vagrant.Machine{}) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if !usable { + t.Errorf("bad result") + } +} + +func TestProvider_IsUsable_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.IsUsable(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_IsUsable_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.IsUsable(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} +func TestProvider_MachineIdChanged(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + err = impl.MachineIdChanged(context.Background(), &vagrant.Machine{}) + if err != nil { + t.Errorf("err: %s", err) + } +} + +func TestProvider_MachineIdChanged_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + err = impl.MachineIdChanged(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_MachineIdChanged_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + err = impl.MachineIdChanged(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_Name(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp := impl.Name() + if resp != "mock_provider" { + t.Errorf("%s != mock_provider", resp) + } +} + +func TestProvider_RunAction(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + args := []string{"test_arg", "other_arg"} + m := &vagrant.Machine{} + + resp, err := impl.RunAction(context.Background(), "valid", args, m) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + + result := resp.([]interface{}) + if result[0] != "valid" { + t.Errorf("%s != valid", result[0]) + } + if result[1] != "test_arg" { + t.Errorf("%s != test_arg", result[1]) + } +} + +func TestProvider_RunAction_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + args := []string{"test_arg", "other_arg"} + m := &vagrant.Machine{} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.RunAction(ctx, "pause", args, m) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_RunAction_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + args := []string{"test_arg", "other_arg"} + m := &vagrant.Machine{} + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.RunAction(ctx, "pause", args, m) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestProvider_RunAction_invalid(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + args := []string{"test_arg", "other_arg"} + m := &vagrant.Machine{} + + _, err = impl.RunAction(context.Background(), "invalid", args, m) + if err == nil { + t.Fatalf("illegal action run") + } +} + +func TestProvider_SshInfo(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp, err := impl.SshInfo(context.Background(), &vagrant.Machine{}) + if err != nil { + t.Fatalf("invalid resp: %s", err) + } + + if resp.Host != "localhost" { + t.Errorf("%s != localhost", resp.Host) + } + if resp.Port != 2222 { + t.Errorf("%d != 2222", resp.Port) + } +} + +func TestProvider_SshInfo_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.SshInfo(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.Canceled { + t.Fatalf("invalid resp: %s", err) + } +} + +func TestProvider_SshInfo_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.SshInfo(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.DeadlineExceeded { + t.Fatalf("invalid resp: %s", err) + } +} + +func TestProvider_State(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp, err := impl.State(context.Background(), &vagrant.Machine{}) + if err != nil { + t.Fatalf("invalid resp: %s", err) + } + + if resp.Id != "default" { + t.Errorf("%s != default", resp.Id) + } + if resp.ShortDesc != "running" { + t.Errorf("%s != running", resp.ShortDesc) + } +} + +func TestProvider_State_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.State(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.Canceled { + t.Fatalf("invalid resp: %s", err) + } +} + +func TestProvider_State_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.State(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.DeadlineExceeded { + t.Fatalf("invalid resp: %s", err) + } +} + +func TestProvider_Info(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp := impl.Info() + + if resp.Description != "Custom" { + t.Errorf("%s != Custom", resp.Description) + } + if resp.Priority != 10 { + t.Errorf("%d != 10", resp.Priority) + } +} + +func TestProvider_MachineUI_output(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "provider": &ProviderPlugin{Impl: &MockProvider{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("provider") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(Provider) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx := context.Background() + go func() { + _, err = impl.RunAction(ctx, "send_output", nil, &vagrant.Machine{}) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + }() + + resp, err := impl.Read("stdout") + if err != nil { + t.Fatalf("bad resp: %s", err) + } + + if !strings.Contains(resp, "test_output_p") { + t.Errorf("%s !~ test_output_p", resp) + } +} diff --git a/ext/go-plugin/vagrant/plugin/synced_folder.go b/ext/go-plugin/vagrant/plugin/synced_folder.go new file mode 100644 index 000000000..6b575327b --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/synced_folder.go @@ -0,0 +1,283 @@ +package plugin + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc" + + go_plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant/plugin/proto" + + "github.com/LK4D4/joincontext" +) + +type SyncedFolder interface { + vagrant.SyncedFolder + Meta +} + +type SyncedFolderPlugin struct { + go_plugin.NetRPCUnsupportedPlugin + Impl SyncedFolder +} + +type GRPCSyncedFolderClient struct { + GRPCCoreClient + GRPCGuestCapabilitiesClient + GRPCHostCapabilitiesClient + GRPCIOClient + client vagrant_proto.SyncedFolderClient + doneCtx context.Context +} + +func (c *GRPCSyncedFolderClient) Cleanup(ctx context.Context, m *vagrant.Machine, o vagrant.FolderOptions) (err error) { + machine, err := vagrant.DumpMachine(m) + if err != nil { + return + } + opts, err := json.Marshal(o) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + _, err = c.client.Cleanup(jctx, &vagrant_proto.SyncedFolders{ + Machine: machine, + Options: string(opts)}) + return handleGrpcError(err, c.doneCtx, ctx) +} + +func (c *GRPCSyncedFolderClient) Disable(ctx context.Context, m *vagrant.Machine, f vagrant.FolderList, o vagrant.FolderOptions) (err error) { + machine, err := vagrant.DumpMachine(m) + if err != nil { + return + } + folders, err := json.Marshal(f) + if err != nil { + return + } + opts, err := json.Marshal(o) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + _, err = c.client.Disable(jctx, &vagrant_proto.SyncedFolders{ + Machine: machine, + Folders: string(folders), + Options: string(opts)}) + return handleGrpcError(err, c.doneCtx, ctx) +} + +func (c *GRPCSyncedFolderClient) Enable(ctx context.Context, m *vagrant.Machine, f vagrant.FolderList, o vagrant.FolderOptions) (err error) { + machine, err := vagrant.DumpMachine(m) + if err != nil { + return + } + folders, err := json.Marshal(f) + if err != nil { + return + } + opts, err := json.Marshal(o) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + _, err = c.client.Enable(jctx, &vagrant_proto.SyncedFolders{ + Machine: machine, + Folders: string(folders), + Options: string(opts)}) + return handleGrpcError(err, c.doneCtx, ctx) +} + +func (c *GRPCSyncedFolderClient) Info() *vagrant.SyncedFolderInfo { + resp, err := c.client.Info(context.Background(), &vagrant_proto.Empty{}) + if err != nil { + return &vagrant.SyncedFolderInfo{} + } + return &vagrant.SyncedFolderInfo{ + Description: resp.Description, + Priority: resp.Priority} +} + +func (c *GRPCSyncedFolderClient) IsUsable(ctx context.Context, m *vagrant.Machine) (u bool, err error) { + machine, err := vagrant.DumpMachine(m) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + resp, err := c.client.IsUsable(jctx, &vagrant_proto.Machine{ + Machine: machine}) + if err != nil { + return false, handleGrpcError(err, c.doneCtx, ctx) + } + u = resp.Result + return +} + +func (c *GRPCSyncedFolderClient) Name() string { + resp, err := c.client.Name(context.Background(), &vagrant_proto.Empty{}) + if err != nil { + return "" + } + return resp.Name +} + +func (c *GRPCSyncedFolderClient) Prepare(ctx context.Context, m *vagrant.Machine, f vagrant.FolderList, o vagrant.FolderOptions) (err error) { + machine, err := vagrant.DumpMachine(m) + if err != nil { + return + } + folders, err := json.Marshal(f) + if err != nil { + return + } + opts, err := json.Marshal(o) + if err != nil { + return + } + jctx, _ := joincontext.Join(ctx, c.doneCtx) + _, err = c.client.Prepare(jctx, &vagrant_proto.SyncedFolders{ + Machine: machine, + Folders: string(folders), + Options: string(opts)}) + return handleGrpcError(err, c.doneCtx, ctx) +} + +type GRPCSyncedFolderServer struct { + GRPCGuestCapabilitiesServer + GRPCHostCapabilitiesServer + GRPCIOServer + Impl SyncedFolder +} + +func (s *GRPCSyncedFolderServer) Cleanup(ctx context.Context, req *vagrant_proto.SyncedFolders) (resp *vagrant_proto.Empty, err error) { + resp = &vagrant_proto.Empty{} + machine, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + var options vagrant.FolderOptions + err = json.Unmarshal([]byte(req.Options), &options) + if err != nil { + return + } + err = s.Impl.Cleanup(ctx, machine, options) + return +} + +func (s *GRPCSyncedFolderServer) Disable(ctx context.Context, req *vagrant_proto.SyncedFolders) (resp *vagrant_proto.Empty, err error) { + resp = &vagrant_proto.Empty{} + machine, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + var folders vagrant.FolderList + err = json.Unmarshal([]byte(req.Folders), &folders) + if err != nil { + return + } + var options vagrant.FolderOptions + err = json.Unmarshal([]byte(req.Options), &options) + if err != nil { + return + } + err = s.Impl.Disable(ctx, machine, folders, options) + return +} + +func (s *GRPCSyncedFolderServer) Enable(ctx context.Context, req *vagrant_proto.SyncedFolders) (resp *vagrant_proto.Empty, err error) { + resp = &vagrant_proto.Empty{} + machine, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + var folders vagrant.FolderList + err = json.Unmarshal([]byte(req.Folders), &folders) + if err != nil { + return + } + var options vagrant.FolderOptions + err = json.Unmarshal([]byte(req.Options), &options) + if err != nil { + return + } + err = s.Impl.Enable(ctx, machine, folders, options) + return +} + +func (s *GRPCSyncedFolderServer) Info(ctx context.Context, req *vagrant_proto.Empty) (resp *vagrant_proto.PluginInfo, err error) { + resp = &vagrant_proto.PluginInfo{} + r := s.Impl.Info() + resp.Description = r.Description + resp.Priority = r.Priority + return +} + +func (s *GRPCSyncedFolderServer) IsUsable(ctx context.Context, req *vagrant_proto.Machine) (resp *vagrant_proto.Valid, err error) { + resp = &vagrant_proto.Valid{} + machine, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + r, err := s.Impl.IsUsable(ctx, machine) + if err != nil { + return + } + resp.Result = r + return +} + +func (s *GRPCSyncedFolderServer) Name(_ context.Context, req *vagrant_proto.Empty) (*vagrant_proto.Identifier, error) { + return &vagrant_proto.Identifier{Name: s.Impl.Name()}, nil +} + +func (s *GRPCSyncedFolderServer) Prepare(ctx context.Context, req *vagrant_proto.SyncedFolders) (resp *vagrant_proto.Empty, err error) { + resp = &vagrant_proto.Empty{} + machine, err := vagrant.LoadMachine(req.Machine, s.Impl) + if err != nil { + return + } + var folders vagrant.FolderList + err = json.Unmarshal([]byte(req.Folders), &folders) + if err != nil { + return + } + var options vagrant.FolderOptions + err = json.Unmarshal([]byte(req.Options), &options) + if err != nil { + return + } + err = s.Impl.Prepare(ctx, machine, folders, options) + return +} + +func (f *SyncedFolderPlugin) GRPCServer(broker *go_plugin.GRPCBroker, s *grpc.Server) error { + f.Impl.Init() + vagrant_proto.RegisterSyncedFolderServer(s, + &GRPCSyncedFolderServer{ + Impl: f.Impl, + GRPCIOServer: GRPCIOServer{ + Impl: f.Impl}, + GRPCGuestCapabilitiesServer: GRPCGuestCapabilitiesServer{ + Impl: f.Impl}, + GRPCHostCapabilitiesServer: GRPCHostCapabilitiesServer{ + Impl: f.Impl}}) + return nil +} + +func (f *SyncedFolderPlugin) GRPCClient(ctx context.Context, broker *go_plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + client := vagrant_proto.NewSyncedFolderClient(c) + return &GRPCSyncedFolderClient{ + GRPCIOClient: GRPCIOClient{ + client: client, + doneCtx: ctx}, + GRPCGuestCapabilitiesClient: GRPCGuestCapabilitiesClient{ + client: client, + doneCtx: ctx}, + GRPCHostCapabilitiesClient: GRPCHostCapabilitiesClient{ + client: client, + doneCtx: ctx}, + client: client, + doneCtx: ctx}, nil +} diff --git a/ext/go-plugin/vagrant/plugin/synced_folder_test.go b/ext/go-plugin/vagrant/plugin/synced_folder_test.go new file mode 100644 index 000000000..856b15adf --- /dev/null +++ b/ext/go-plugin/vagrant/plugin/synced_folder_test.go @@ -0,0 +1,562 @@ +package plugin + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/vagrant/ext/go-plugin/vagrant" +) + +func TestSyncedFolder_Cleanup(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + err = impl.Cleanup(context.Background(), &vagrant.Machine{}, nil) + if err != nil { + t.Fatalf("bad resp: %#v", err) + } +} + +func TestSyncedFolder_Cleanup_error(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + args := map[string]interface{}{ + "error": true} + + err = impl.Cleanup(context.Background(), &vagrant.Machine{}, args) + if err == nil { + t.Fatalf("illegal cleanup") + } + if err.Error() != "cleanup error" { + t.Errorf("%s != cleanup error", err.Error()) + } +} + +func TestSyncedFolder_Cleanup_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + err = impl.Cleanup(ctx, &vagrant.Machine{Name: "pause"}, nil) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Cleanup_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + err = impl.Cleanup(ctx, &vagrant.Machine{Name: "pause"}, nil) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Disable(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + err = impl.Disable(context.Background(), &vagrant.Machine{}, nil, nil) + if err != nil { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Disable_error(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + folders := map[string]interface{}{ + "folder_name": "options"} + args := map[string]interface{}{ + "error": true} + + err = impl.Disable(context.Background(), &vagrant.Machine{}, folders, args) + if err == nil { + t.Fatalf("illegal disable") + } + if err.Error() != "disable error" { + t.Errorf("%s != disable error", err.Error()) + } +} + +func TestSyncedFolder_Disable_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + err = impl.Disable(ctx, &vagrant.Machine{Name: "pause"}, nil, nil) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Disable_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + err = impl.Disable(ctx, &vagrant.Machine{Name: "pause"}, nil, nil) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Enable(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + err = impl.Enable(context.Background(), &vagrant.Machine{}, nil, nil) + if err != nil { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Enable_error(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + folders := map[string]interface{}{ + "folder_name": "options"} + args := map[string]interface{}{ + "error": true} + + err = impl.Enable(context.Background(), &vagrant.Machine{}, folders, args) + if err == nil { + t.Fatalf("illegal enable") + } + if err.Error() != "enable error" { + t.Errorf("%s != enable error", err.Error()) + } +} + +func TestSyncedFolder_Enable_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + err = impl.Enable(ctx, &vagrant.Machine{Name: "pause"}, nil, nil) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Enable_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + err = impl.Enable(ctx, &vagrant.Machine{Name: "pause"}, nil, nil) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Prepare(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + err = impl.Prepare(context.Background(), &vagrant.Machine{}, nil, nil) + if err != nil { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Prepare_error(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + folders := map[string]interface{}{ + "folder_name": "options"} + args := map[string]interface{}{ + "error": true} + + err = impl.Prepare(context.Background(), &vagrant.Machine{}, folders, args) + if err == nil { + t.Fatalf("illegal prepare") + } + if err.Error() != "prepare error" { + t.Errorf("%s != prepare error", err.Error()) + } +} + +func TestSyncedFolder_Prepare_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + err = impl.Prepare(ctx, &vagrant.Machine{Name: "pause"}, nil, nil) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Prepare_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + err = impl.Prepare(ctx, &vagrant.Machine{Name: "pause"}, nil, nil) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} +func TestSyncedFolder_Info(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp := impl.Info() + if resp == nil { + t.Fatalf("bad resp") + } + + if resp.Description != "mock_folder" { + t.Errorf("%s != mock_folder", resp.Description) + } + if resp.Priority != 100 { + t.Errorf("%d != 100", resp.Priority) + } +} + +func TestSyncedFolder_IsUsable(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp, err := impl.IsUsable(context.Background(), &vagrant.Machine{}) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if !resp { + t.Errorf("bad result") + } +} + +func TestSyncedFolder_IsUsable_context_cancel(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + time.Sleep(2 * time.Millisecond) + cancel() + }() + _, err = impl.IsUsable(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.Canceled { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_IsUsable_context_timeout(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) + defer cancel() + _, err = impl.IsUsable(ctx, &vagrant.Machine{Name: "pause"}) + if err != context.DeadlineExceeded { + t.Fatalf("bad resp: %s", err) + } +} + +func TestSyncedFolder_Name(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + resp := impl.Name() + if resp != "mock_folder" { + t.Errorf("%s != mock_folder", resp) + } +} + +func TestSyncedFolder_MachineUI_output(t *testing.T) { + client, server := plugin.TestPluginGRPCConn(t, map[string]plugin.Plugin{ + "folder": &SyncedFolderPlugin{Impl: &MockSyncedFolder{}}}) + defer server.Stop() + defer client.Close() + + raw, err := client.Dispense("folder") + if err != nil { + t.Fatalf("err: %s", err) + } + impl, ok := raw.(SyncedFolder) + if !ok { + t.Fatalf("bad %#v", raw) + } + + go func() { + err := impl.Cleanup(context.Background(), &vagrant.Machine{}, map[string]interface{}{"ui": true}) + if err != nil { + t.Fatalf("bad resp: %s", err) + } + }() + + resp, err := impl.Read("stdout") + if err != nil { + t.Fatalf("bad resp: %s", err) + } + if !strings.Contains(resp, "test_output") { + t.Errorf("%s !~ test_output", resp) + } +} diff --git a/ext/go-plugin/vagrant/provider.go b/ext/go-plugin/vagrant/provider.go new file mode 100644 index 000000000..7527df279 --- /dev/null +++ b/ext/go-plugin/vagrant/provider.go @@ -0,0 +1,27 @@ +package vagrant + +import ( + "context" +) + +type Provider interface { + Info() *ProviderInfo + Action(ctx context.Context, actionName string, machData *Machine) ([]string, error) + IsInstalled(ctx context.Context, machData *Machine) (bool, error) + IsUsable(ctx context.Context, machData *Machine) (bool, error) + MachineIdChanged(ctx context.Context, machData *Machine) error + Name() string + RunAction(ctx context.Context, actionName string, args interface{}, machData *Machine) (interface{}, error) + SshInfo(ctx context.Context, machData *Machine) (*SshInfo, error) + State(ctx context.Context, machData *Machine) (*MachineState, error) + + Config + GuestCapabilities + HostCapabilities + ProviderCapabilities +} + +type ProviderInfo struct { + Description string `json:"description"` + Priority int64 `json:"priority"` +} diff --git a/ext/go-plugin/vagrant/ssh_info.go b/ext/go-plugin/vagrant/ssh_info.go new file mode 100644 index 000000000..625aa845c --- /dev/null +++ b/ext/go-plugin/vagrant/ssh_info.go @@ -0,0 +1,8 @@ +package vagrant + +type SshInfo struct { + Host string `json:"host"` + Port int64 `json:"port"` + Username string `json:"username"` + PrivateKeyPath string `json:"private_key_path"` +} diff --git a/ext/go-plugin/vagrant/synced_folder.go b/ext/go-plugin/vagrant/synced_folder.go new file mode 100644 index 000000000..c3c260b52 --- /dev/null +++ b/ext/go-plugin/vagrant/synced_folder.go @@ -0,0 +1,26 @@ +package vagrant + +import ( + "context" +) + +type FolderList map[string]interface{} +type FolderOptions map[string]interface{} + +type SyncedFolderInfo struct { + Description string `json:"description"` + Priority int64 `json:"priority"` +} + +type SyncedFolder interface { + Cleanup(ctx context.Context, m *Machine, opts FolderOptions) error + Disable(ctx context.Context, m *Machine, f FolderList, opts FolderOptions) error + Enable(ctx context.Context, m *Machine, f FolderList, opts FolderOptions) error + Info() *SyncedFolderInfo + IsUsable(ctx context.Context, m *Machine) (bool, error) + Name() string + Prepare(ctx context.Context, m *Machine, f FolderList, opts FolderOptions) error + + GuestCapabilities + HostCapabilities +} diff --git a/ext/go-plugin/vagrant/ui.go b/ext/go-plugin/vagrant/ui.go new file mode 100644 index 000000000..7a61daf14 --- /dev/null +++ b/ext/go-plugin/vagrant/ui.go @@ -0,0 +1,472 @@ +package vagrant + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "os" + "os/signal" + "runtime" + "strings" + "sync" + "syscall" + "time" + "unicode" +) + +type UiColor uint + +const ( + UiColorRed UiColor = 31 + UiColorGreen = 32 + UiColorYellow = 33 + UiColorBlue = 34 + UiColorMagenta = 35 + UiColorCyan = 36 +) + +type UiChannel uint + +const ( + UiOutput UiChannel = 1 + UiError = 2 +) + +var logger = DefaultLogger().Named("ui") + +type Options struct { + Channel UiChannel + NewLine bool +} + +var defaultOptions = &Options{ + Channel: UiOutput, + NewLine: true, +} + +// The Ui interface handles all communication for Vagrant with the outside +// world. This sort of control allows us to strictly control how output +// is formatted and various levels of output. +type Ui interface { + Ask(string) (string, error) + Detail(string) + Info(string) + Error(string) + Machine(string, ...string) + Message(string, *Options) + Output(string) + Say(string) + Success(string) + Warn(string) +} + +// The BasicUI is a UI that reads and writes from a standard Go reader +// and writer. It is safe to be called from multiple goroutines. Machine +// readable output is simply logged for this UI. +type BasicUi struct { + Reader io.Reader + Writer io.Writer + ErrorWriter io.Writer + l sync.Mutex + interrupted bool + scanner *bufio.Scanner +} + +var _ Ui = new(BasicUi) + +func (rw *BasicUi) Ask(query string) (string, error) { + rw.l.Lock() + defer rw.l.Unlock() + + if rw.interrupted { + return "", errors.New("interrupted") + } + + if rw.scanner == nil { + rw.scanner = bufio.NewScanner(rw.Reader) + } + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(sigCh) + + logger.Info("ask", query) + if query != "" { + if _, err := fmt.Fprint(rw.Writer, query+" "); err != nil { + return "", err + } + } + + result := make(chan string, 1) + go func() { + var line string + if rw.scanner.Scan() { + line = rw.scanner.Text() + } + if err := rw.scanner.Err(); err != nil { + logger.Error("scan failure", "error", err) + return + } + result <- line + }() + + select { + case line := <-result: + return line, nil + case <-sigCh: + // Print a newline so that any further output starts properly + // on a new line. + fmt.Fprintln(rw.Writer) + + // Mark that we were interrupted so future Ask calls fail. + rw.interrupted = true + + return "", errors.New("interrupted") + } +} + +func (rw *BasicUi) Detail(message string) { rw.Say(message) } +func (rw *BasicUi) Info(message string) { rw.Say(message) } +func (rw *BasicUi) Output(message string) { rw.Say(message) } +func (rw *BasicUi) Success(message string) { rw.Say(message) } +func (rw *BasicUi) Warn(message string) { rw.Say(message) } + +func (rw *BasicUi) Say(message string) { + rw.Message(message, nil) +} + +func (rw *BasicUi) Message(message string, opts *Options) { + rw.l.Lock() + defer rw.l.Unlock() + + if opts == nil { + opts = &Options{Channel: UiOutput, NewLine: true} + } + + logger.Debug("write message", "content", message, "options", opts) + target := rw.Writer + if opts.Channel == UiError { + if rw.ErrorWriter == nil { + logger.Error("error writer unset using writer") + } else { + target = rw.ErrorWriter + } + } + suffix := "" + if opts.NewLine { + suffix = "\n" + } + + _, err := fmt.Fprint(target, message+suffix) + if err != nil { + logger.Error("write failure", "error", err) + } +} + +func (rw *BasicUi) Error(message string) { + rw.Message(message, &Options{Channel: UiError, NewLine: true}) +} + +func (rw *BasicUi) Machine(t string, args ...string) { + logger.Info("machine readable", "category", t, "args", args) +} + +// MachineReadableUi is a UI that only outputs machine-readable output +// to the given Writer. +type MachineReadableUi struct { + Writer io.Writer +} + +var _ Ui = new(MachineReadableUi) + +func (u *MachineReadableUi) Ask(query string) (string, error) { + return "", errors.New("machine-readable UI can't ask") +} + +func (u *MachineReadableUi) Detail(message string) { + u.Machine("ui", "detail", message) +} + +func (u *MachineReadableUi) Info(message string) { + u.Machine("ui", "info", message) +} + +func (u *MachineReadableUi) Output(message string) { + u.Machine("ui", "output", message) +} + +func (u *MachineReadableUi) Success(message string) { + u.Machine("ui", "success", message) +} + +func (u *MachineReadableUi) Warn(message string) { + u.Machine("ui", "warn", message) +} + +func (u *MachineReadableUi) Say(message string) { + u.Machine("ui", "say", message) +} + +func (u *MachineReadableUi) Message(message string, opts *Options) { + u.Machine("ui", "message", message) +} + +func (u *MachineReadableUi) Error(message string) { + u.Machine("ui", "error", message) +} + +// TODO: Do we want to update this to match Vagrant machine style? +func (u *MachineReadableUi) Machine(category string, args ...string) { + now := time.Now().UTC() + + // Determine if we have a target, and set it + target := "" + commaIdx := strings.Index(category, ",") + if commaIdx > -1 { + target = category[0:commaIdx] + category = category[commaIdx+1:] + } + + // Prepare the args + for i, v := range args { + args[i] = strings.Replace(v, ",", "%!(VAGRANT_COMMA)", -1) + args[i] = strings.Replace(args[i], "\r", "\\r", -1) + args[i] = strings.Replace(args[i], "\n", "\\n", -1) + } + argsString := strings.Join(args, ",") + + _, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString) + if err != nil { + if err == syscall.EPIPE || strings.Contains(err.Error(), "broken pipe") { + // Ignore epipe errors because that just means that the file + // is probably closed or going to /dev/null or something. + } else { + panic(err) + } + } +} + +type NoopUi struct{} + +var _ Ui = new(NoopUi) + +func (*NoopUi) Ask(string) (string, error) { return "", errors.New("this is a noop ui") } +func (*NoopUi) Detail(string) { return } +func (*NoopUi) Info(string) { return } +func (*NoopUi) Error(string) { return } +func (*NoopUi) Machine(string, ...string) { return } +func (*NoopUi) Message(string, *Options) { return } +func (*NoopUi) Output(string) { return } +func (*NoopUi) Say(string) { return } +func (*NoopUi) Success(string) { return } +func (*NoopUi) Warn(string) { return } + +// ColoredUi is a UI that is colored using terminal colors. +type ColoredUi struct { + Color UiColor + ErrorColor UiColor + SuccessColor UiColor + WarnColor UiColor + Ui Ui +} + +var _ Ui = new(ColoredUi) + +func (u *ColoredUi) Ask(query string) (string, error) { + return u.Ui.Ask(u.colorize(query, u.Color, true)) +} + +func (u *ColoredUi) Detail(message string) { + u.Say(message) +} + +func (u *ColoredUi) Info(message string) { + u.Say(message) +} + +func (u *ColoredUi) Error(message string) { + color := u.ErrorColor + if color == 0 { + color = UiColorRed + } + + u.Ui.Error(u.colorize(message, color, true)) +} + +func (u *ColoredUi) Machine(t string, args ...string) { + // Don't colorize machine-readable output + u.Ui.Machine(t, args...) +} + +func (u *ColoredUi) Message(message string, opts *Options) { + u.Ui.Message(u.colorize(message, u.Color, false), opts) +} + +func (u *ColoredUi) Output(message string) { + u.Say(message) +} + +func (u *ColoredUi) Say(message string) { + u.Ui.Say(u.colorize(message, u.Color, true)) +} + +func (u *ColoredUi) Success(message string) { + u.Ui.Say(u.colorize(message, u.SuccessColor, true)) +} + +func (u *ColoredUi) Warn(message string) { + u.Ui.Say(u.colorize(message, u.WarnColor, true)) +} + +func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string { + if !u.supportsColors() { + return message + } + + attr := 0 + if bold { + attr = 1 + } + + return fmt.Sprintf("\033[%d;%dm%s\033[0m", attr, color, message) +} + +func (u *ColoredUi) supportsColors() bool { + // Never use colors if we have this environmental variable + if os.Getenv("VAGRANT_NO_COLOR") != "" { + return false + } + + // For now, on non-Windows machine, just assume it does + if runtime.GOOS != "windows" { + return true + } + + // On Windows, if we appear to be in Cygwin, then it does + cygwin := os.Getenv("CYGWIN") != "" || + os.Getenv("OSTYPE") == "cygwin" || + os.Getenv("TERM") == "cygwin" + + return cygwin +} + +// TargetedUi is a UI that wraps another UI implementation and modifies +// the output to indicate a specific target. Specifically, all Say output +// is prefixed with the target name. Message output is not prefixed but +// is offset by the length of the target so that output is lined up properly +// with Say output. Machine-readable output has the proper target set. +type TargetedUi struct { + Target string + Ui Ui +} + +var _ Ui = new(TargetedUi) + +func (u *TargetedUi) Ask(query string) (string, error) { + return u.Ui.Ask(u.prefixLines(true, query)) +} + +func (u *TargetedUi) Detail(message string) { + u.Ui.Detail(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Info(message string) { + u.Ui.Info(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Output(message string) { + u.Ui.Output(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Success(message string) { + u.Ui.Success(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Warn(message string) { + u.Ui.Warn(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Say(message string) { + u.Ui.Say(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Message(message string, opts *Options) { + u.Ui.Message(u.prefixLines(false, message), opts) +} + +func (u *TargetedUi) Error(message string) { + u.Ui.Error(u.prefixLines(true, message)) +} + +func (u *TargetedUi) Machine(t string, args ...string) { + // Prefix in the target, then pass through + u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...) +} + +func (u *TargetedUi) prefixLines(arrow bool, message string) string { + arrowText := "==>" + if !arrow { + arrowText = strings.Repeat(" ", len(arrowText)) + } + + var result bytes.Buffer + + for _, line := range strings.Split(message, "\n") { + result.WriteString(fmt.Sprintf("%s %s: %s\n", arrowText, u.Target, line)) + } + + return strings.TrimRightFunc(result.String(), unicode.IsSpace) +} + +// TimestampedUi is a UI that wraps another UI implementation and prefixes +// prefixes each message with an RFC3339 timestamp +type TimestampedUi struct { + Ui Ui +} + +var _ Ui = new(TimestampedUi) + +func (u *TimestampedUi) Ask(query string) (string, error) { + return u.Ui.Ask(query) +} + +func (u *TimestampedUi) Detail(message string) { + u.Ui.Detail(u.timestampLine(message)) +} + +func (u *TimestampedUi) Info(message string) { + u.Ui.Info(u.timestampLine(message)) +} + +func (u *TimestampedUi) Output(message string) { + u.Ui.Output(u.timestampLine(message)) +} + +func (u *TimestampedUi) Success(message string) { + u.Ui.Success(u.timestampLine(message)) +} + +func (u *TimestampedUi) Warn(message string) { + u.Ui.Warn(u.timestampLine(message)) +} + +func (u *TimestampedUi) Say(message string) { + u.Ui.Say(u.timestampLine(message)) +} + +func (u *TimestampedUi) Message(message string, opts *Options) { + u.Ui.Message(u.timestampLine(message), opts) +} + +func (u *TimestampedUi) Error(message string) { + u.Ui.Error(u.timestampLine(message)) +} + +func (u *TimestampedUi) Machine(message string, args ...string) { + u.Ui.Machine(message, args...) +} + +func (u *TimestampedUi) timestampLine(string string) string { + return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string) +} diff --git a/ext/go-plugin/vagrant/ui_test.go b/ext/go-plugin/vagrant/ui_test.go new file mode 100644 index 000000000..225401b40 --- /dev/null +++ b/ext/go-plugin/vagrant/ui_test.go @@ -0,0 +1,290 @@ +package vagrant + +import ( + "bytes" + "os" + "strings" + "testing" +) + +// This reads the output from the bytes.Buffer in our test object +// and then resets the buffer. +func readWriter(ui *BasicUi) (result string) { + buffer := ui.Writer.(*bytes.Buffer) + result = buffer.String() + buffer.Reset() + return +} + +// Reset the input Reader then add some input to it. +func writeReader(ui *BasicUi, input string) { + buffer := ui.Reader.(*bytes.Buffer) + buffer.WriteString(input) +} + +func readErrorWriter(ui *BasicUi) (result string) { + buffer := ui.ErrorWriter.(*bytes.Buffer) + result = buffer.String() + buffer.Reset() + return +} + +func testUi() *BasicUi { + return &BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + ErrorWriter: new(bytes.Buffer), + } +} + +func TestColoredUi(t *testing.T) { + bufferUi := testUi() + ui := &ColoredUi{UiColorBlue, UiColorRed, UiColorGreen, + UiColorYellow, bufferUi} + + if !ui.supportsColors() { + t.Skip("skipping for ui without color support") + } + + ui.Say("foo") + result := readWriter(bufferUi) + if result != "\033[1;34mfoo\033[0m\n" { + t.Fatalf("invalid output: %s", result) + } + + ui.Message("foo", nil) + result = readWriter(bufferUi) + if result != "\033[0;34mfoo\033[0m\n" { + t.Fatalf("invalid output: %s", result) + } + + ui.Error("foo") + result = readWriter(bufferUi) + if result != "" { + t.Fatalf("invalid output: %s", result) + } + + result = readErrorWriter(bufferUi) + if result != "\033[1;31mfoo\033[0m\n" { + t.Fatalf("invalid output: %s", result) + } +} + +func TestColoredUi_noColorEnv(t *testing.T) { + bufferUi := testUi() + ui := &ColoredUi{UiColorBlue, UiColorRed, UiColorGreen, + UiColorYellow, bufferUi} + + // Set the env var to get rid of the color + oldenv := os.Getenv("VAGRANT_NO_COLOR") + os.Setenv("VAGRANT_NO_COLOR", "1") + defer os.Setenv("VAGRANT_NO_COLOR", oldenv) + + ui.Say("foo") + result := readWriter(bufferUi) + if result != "foo\n" { + t.Fatalf("invalid output: %s", result) + } + + ui.Message("foo", nil) + result = readWriter(bufferUi) + if result != "foo\n" { + t.Fatalf("invalid output: %s", result) + } + + ui.Error("foo") + result = readErrorWriter(bufferUi) + if result != "foo\n" { + t.Fatalf("invalid output: %s", result) + } +} + +func TestTargetedUi(t *testing.T) { + bufferUi := testUi() + targetedUi := &TargetedUi{ + Target: "foo", + Ui: bufferUi, + } + + var actual, expected string + targetedUi.Say("foo") + actual = readWriter(bufferUi) + expected = "==> foo: foo\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } + + targetedUi.Message("foo", nil) + actual = readWriter(bufferUi) + expected = " foo: foo\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } + + targetedUi.Error("bar") + actual = readErrorWriter(bufferUi) + expected = "==> foo: bar\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } + + targetedUi.Say("foo\nbar") + actual = readWriter(bufferUi) + expected = "==> foo: foo\n==> foo: bar\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } +} + +func TestColoredUi_ImplUi(t *testing.T) { + var raw interface{} + raw = &ColoredUi{} + if _, ok := raw.(Ui); !ok { + t.Fatalf("ColoredUi must implement Ui") + } +} + +func TestTargetedUi_ImplUi(t *testing.T) { + var raw interface{} + raw = &TargetedUi{} + if _, ok := raw.(Ui); !ok { + t.Fatalf("TargetedUi must implement Ui") + } +} + +func TestBasicUi_ImplUi(t *testing.T) { + var raw interface{} + raw = &BasicUi{} + if _, ok := raw.(Ui); !ok { + t.Fatalf("BasicUi must implement Ui") + } +} + +func TestBasicUi_Error(t *testing.T) { + bufferUi := testUi() + + var actual, expected string + bufferUi.Error("foo") + actual = readErrorWriter(bufferUi) + expected = "foo\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } + + bufferUi.ErrorWriter = nil + bufferUi.Error("5") + actual = readWriter(bufferUi) + expected = "5\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } +} + +func TestBasicUi_Say(t *testing.T) { + bufferUi := testUi() + + var actual, expected string + + bufferUi.Say("foo") + actual = readWriter(bufferUi) + expected = "foo\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } + + bufferUi.Say("5") + actual = readWriter(bufferUi) + expected = "5\n" + if actual != expected { + t.Fatalf("bad: %#v", actual) + } +} + +func TestBasicUi_Ask(t *testing.T) { + + var actual, expected string + var err error + + var testCases = []struct { + Prompt, Input, Answer string + }{ + {"[c]ontinue or [a]bort", "c\n", "c"}, + {"[c]ontinue or [a]bort", "c", "c"}, + // Empty input shouldn't give an error + {"Name", "Joe Bloggs\n", "Joe Bloggs"}, + {"Name", "Joe Bloggs", "Joe Bloggs"}, + {"Name", "\n", ""}, + } + + for _, testCase := range testCases { + // Because of the internal bufio we can't easily reset the input, so create a new one each time + bufferUi := testUi() + writeReader(bufferUi, testCase.Input) + + actual, err = bufferUi.Ask(testCase.Prompt) + if err != nil { + t.Fatal(err) + } + + if actual != testCase.Answer { + t.Fatalf("bad answer: %#v", actual) + } + + actual = readWriter(bufferUi) + expected = testCase.Prompt + " " + if actual != expected { + t.Fatalf("bad prompt: %#v", actual) + } + } + +} + +func TestMachineReadableUi_ImplUi(t *testing.T) { + var raw interface{} + raw = &MachineReadableUi{} + if _, ok := raw.(Ui); !ok { + t.Fatalf("MachineReadableUi must implement Ui") + } +} + +func TestMachineReadableUi(t *testing.T) { + var data, expected string + + buf := new(bytes.Buffer) + ui := &MachineReadableUi{Writer: buf} + + // No target + ui.Machine("foo", "bar", "baz") + data = strings.SplitN(buf.String(), ",", 2)[1] + expected = ",foo,bar,baz\n" + if data != expected { + t.Fatalf("bad: %s", data) + } + + // Target + buf.Reset() + ui.Machine("mitchellh,foo", "bar", "baz") + data = strings.SplitN(buf.String(), ",", 2)[1] + expected = "mitchellh,foo,bar,baz\n" + if data != expected { + t.Fatalf("bad: %s", data) + } + + // Commas + buf.Reset() + ui.Machine("foo", "foo,bar") + data = strings.SplitN(buf.String(), ",", 2)[1] + expected = ",foo,foo%!(VAGRANT_COMMA)bar\n" + if data != expected { + t.Fatalf("bad: %s", data) + } + + // New lines + buf.Reset() + ui.Machine("foo", "foo\n") + data = strings.SplitN(buf.String(), ",", 2)[1] + expected = ",foo,foo\\n\n" + if data != expected { + t.Fatalf("bad: %#v", data) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..bf27ef902 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/hashicorp/vagrant + +require ( + github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5 + github.com/dylanmei/iso8601 v0.1.0 // indirect + github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1 + github.com/golang/protobuf v1.3.0 + github.com/hashicorp/go-hclog v0.8.0 + github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104 + github.com/kr/fs v0.1.0 // indirect + github.com/masterzen/winrm v0.0.0-20190308153735-1d17eaf15943 + github.com/mitchellh/iochan v1.0.0 + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db + github.com/pkg/errors v0.8.1 // indirect + github.com/pkg/sftp v1.10.0 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 + golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 + google.golang.org/grpc v1.19.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..962f4c759 --- /dev/null +++ b/go.sum @@ -0,0 +1,91 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA= +github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022 h1:y8Gs8CzNfDF5AZvjr+5UyGQvQEBL7pwo+v+wX6q9JI8= +github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5 h1:U7q69tqXiCf6m097GRlNQB0/6SI1qWIOHYHhCEvDxF4= +github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5/go.mod h1:nxQPcNPR/34g+HcK2hEsF99O+GJgIkW/OmPl8wtzhmk= +github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e h1:ptBAamGVd6CfRsUtyHD+goy2JGhv1QC32v3gqM8mYAM= +github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0 h1:JaCC8jz0zdMLk2m+qCCVLLLM/PL93p84w4pK3aJWj60= +github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI= +github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= +github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1 h1:r1oACdS2XYiAWcfF8BJXkoU8l1J71KehGR+d99yWEDA= +github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.8.0 h1:z3ollgGRg8RjfJH6UVBaG54R70GFd++QOkvnJH3VSBY= +github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104 h1:9iQ/zrTOJqzP+kH37s6xNb6T1RysiT7fnDD3DJbspVw= +github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9 h1:SmVbOZFWAlyQshuMfOkiAx1f5oUTsOGG5IXplAEYeeM= +github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/winrm v0.0.0-20190308153735-1d17eaf15943 h1:Bteu9XN1gkBePnKr0v1edkUo2LJRsmK5ne2FrC6yVW4= +github.com/masterzen/winrm v0.0.0-20190308153735-1d17eaf15943/go.mod h1:bsMsaiOA3CXjbJxW0a94G4PfPDj9zUmH5JoFuJ9P4o0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU= +github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.0 h1:DGA1KlA9esU6WcicH+P8PxFZOl15O6GYtab1cIJdOlE= +github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU= +golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 66840226f..dc515720d 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -1,4 +1,5 @@ require "log4r" +require "log4r/logger" require "vagrant/util/credential_scrubber" # Update the default formatter within the log4r library to ensure # sensitive values are being properly scrubbed from logger data @@ -53,6 +54,8 @@ if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != "" # rest-client to write using the `<<` operator. # See https://github.com/rest-client/rest-client/issues/34#issuecomment-290858 # for more information + Log4r::PatternFormatter::DirectiveTable["l"] = + '"[" + ' + Log4r::PatternFormatter::DirectiveTable["l"] + ' + "]"' class VagrantLogger < Log4r::Logger def << (msg) debug(msg.strip) @@ -61,13 +64,10 @@ if ENV["VAGRANT_LOG"] && ENV["VAGRANT_LOG"] != "" logger = VagrantLogger.new("vagrant") logger.outputters = Log4r::Outputter.stderr logger.level = level - base_formatter = Log4r::BasicFormatter.new - if ENV["VAGRANT_LOG_TIMESTAMP"] - base_formatter = Log4r::PatternFormatter.new( - pattern: "%d [%5l] %m", - date_pattern: "%F %T" - ) - end + base_formatter = Log4r::PatternFormatter.new( + pattern: "%d %-7l %C: %m", + date_pattern: ENV["VAGRANT_LOG_TIMESTAMP"] ? "%FT%T.%L%z" : " " + ) # Vagrant Cloud gem uses RestClient to make HTTP requests, so # log them if debug is enabled and use Vagrants logger require 'rest_client' @@ -118,6 +118,7 @@ module Vagrant autoload :Driver, 'vagrant/driver' autoload :Environment, 'vagrant/environment' autoload :Errors, 'vagrant/errors' + autoload :GoPlugin, 'vagrant/go_plugin' autoload :Guest, 'vagrant/guest' autoload :Host, 'vagrant/host' autoload :Machine, 'vagrant/machine' diff --git a/lib/vagrant/box.rb b/lib/vagrant/box.rb index 2f12775f5..cd903c8be 100644 --- a/lib/vagrant/box.rb +++ b/lib/vagrant/box.rb @@ -221,5 +221,17 @@ module Vagrant "#{@name}-#{@version}-#{@provider}" <=> "#{other.name}-#{other.version}-#{other.provider}" end + + # @return [String] + def to_json(*args) + { + name: name, + provider: provider, + version: version, + directory: directory.to_s, + metadata: metadata, + metadata_url: metadata_url + }.to_json(*args) + end end end diff --git a/lib/vagrant/config/v2/dummy_config.rb b/lib/vagrant/config/v2/dummy_config.rb index 149b66b7a..044331580 100644 --- a/lib/vagrant/config/v2/dummy_config.rb +++ b/lib/vagrant/config/v2/dummy_config.rb @@ -7,6 +7,10 @@ module Vagrant def method_missing(name, *args, &block) DummyConfig.new end + + def to_json(*_) + "null" + end end end end diff --git a/lib/vagrant/config/v2/root.rb b/lib/vagrant/config/v2/root.rb index 1329c57c7..e1d718777 100644 --- a/lib/vagrant/config/v2/root.rb +++ b/lib/vagrant/config/v2/root.rb @@ -112,6 +112,14 @@ module Vagrant @keys = state["keys"] if state.key?("keys") @missing_key_calls = state["missing_key_calls"] if state.key?("missing_key_calls") end + + def to_json(*args) + Hash[ + @keys.find_all { |k,v| + !k.to_s.start_with?("_") + } + ].to_json(*args) + end end end end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 2eb35bec9..d5634a54c 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -175,6 +175,15 @@ module Vagrant # Load any global plugins Vagrant::Plugin::Manager.instance.load_plugins(plugins) + # Load any available go-plugins + Util::Experimental.guard_with(:go_plugin) do + begin + Vagrant::GoPlugin::Manager.instance.globalize! + rescue LoadError => err + @logger.warn("go plugin support is not available: #{err}") + end + end + plugins = process_configured_plugins # Call the hooks that does not require configurations to be loaded @@ -918,6 +927,26 @@ module Vagrant end end + # @return [String] + def to_json(*args) + { + cwd: cwd, + data_dir: data_dir, + vagrantfile_name: vagrantfile_name, + home_path: home_path, + local_data_path: local_data_path, + tmp_path: tmp_path, + aliases_path: aliases_path, + boxes_path: boxes_path, + gems_path: gems_path, + default_private_key_path: default_private_key_path, + root_path: root_path, + primary_machine_name: primary_machine_name, + machine_names: machine_names, + active_machines: Hash[active_machines] + }.to_json(*args) + end + protected # Attempt to guess the configured provider in use. Will fallback diff --git a/lib/vagrant/go_plugin.rb b/lib/vagrant/go_plugin.rb new file mode 100644 index 000000000..a5737e459 --- /dev/null +++ b/lib/vagrant/go_plugin.rb @@ -0,0 +1,26 @@ +module Vagrant + autoload :Proto, "vagrant/go_plugin/vagrant_proto/vagrant_services_pb" + + module GoPlugin + + # @return [String] + INSTALL_DIRECTORY = Vagrant.user_data_path.join("go-plugins").to_s.freeze + + autoload :CapabilityPlugin, "vagrant/go_plugin/capability_plugin" + autoload :ConfigPlugin, "vagrant/go_plugin/config_plugin" + autoload :Core, "vagrant/go_plugin/core" + autoload :GRPCPlugin, "vagrant/go_plugin/core" + autoload :Interface, "vagrant/go_plugin/interface" + autoload :Manager, "vagrant/go_plugin/manager" + autoload :ProviderPlugin, "vagrant/go_plugin/provider_plugin" + autoload :SyncedFolderPlugin, "vagrant/go_plugin/synced_folder_plugin" + + # @return [Interface] + def self.interface + unless @_interface + @_interface = Interface.new + end + @_interface + end + end +end diff --git a/lib/vagrant/go_plugin/capability_plugin.rb b/lib/vagrant/go_plugin/capability_plugin.rb new file mode 100644 index 000000000..e2c6635f8 --- /dev/null +++ b/lib/vagrant/go_plugin/capability_plugin.rb @@ -0,0 +1,108 @@ +require "vagrant/go_plugin/core" + +module Vagrant + module GoPlugin + # Contains all capability functionality for go-plugin + module CapabilityPlugin + extend Vagrant::Util::Logger + + # Wrapper class for go-plugin defined capabilities + class Capability + include GRPCPlugin + end + + # Fetch any defined guest capabilites for given plugin and register + # capabilities within given plugin class + # + # @param [Vagrant::Proto::GuestCapabilities::Stub] client Plugin client + # @param [Class] plugin_klass Plugin class to register capabilities + # @param [Symbol] plugin_type Type of plugin + def self.generate_guest_capabilities(client, plugin_klass, plugin_type) + logger.debug("checking for guest capabilities in #{plugin_type} plugin #{plugin_klass}") + result = client.guest_capabilities(Vagrant::Proto::Empty.new) + return if result.capabilities.empty? + logger.debug("guest capabilities support detected in #{plugin_type} plugin #{plugin_klass}") + result.capabilities.each do |cap| + cap_klass = Class.new(Capability).tap do |k| + k.define_singleton_method(cap.name) { |machine, *args| + response = plugin_client.guest_capability( + Vagrant::Proto::GuestCapabilityRequest.new( + machine: JSON.dump(machine), arguments: JSON.dump(args), + capability: Vagrant::Proto::SystemCapability.new( + name: cap.name, platform: cap.platform))).result + result = JSON.load(response) + if result.is_a?(Hash) + result = Vagrant::Util::HashWithIndifferentAccess.new(result) + end + result + } + end + cap_klass.plugin_client = client + plugin_klass.guest_capability(cap.platform.to_sym, cap.name.to_sym) { cap_klass } + end + end + + # Fetch any defined host capabilites for given plugin and register + # capabilities within given plugin class + # + # @param [Vagrant::Proto::HostCapabilities::Stub] client Plugin client + # @param [Class] plugin_klass Plugin class to register capabilities + # @param [Symbol] plugin_type Type of plugin + def self.generate_host_capabilities(client, plugin_klass, plugin_type) + logger.debug("checking for host capabilities in #{plugin_type} plugin #{plugin_klass}") + result = client.host_capabilities(Vagrant::Proto::Empty.new) + return if result.capabilities.empty? + logger.debug("host capabilities support detected in #{plugin_type} plugin #{plugin_klass}") + result.capabilities.each do |cap| + cap_klass = Class.new(Capability).tap do |k| + k.define_singleton_method(cap.name) { |environment, *args| + response = plugin_client.host_capability( + Vagrant::Proto::HostCapabilityRequest.new( + environment: JSON.dump(environment), arguments: JSON.dump(args), + capability: Vagrant::Proto::SystemCapability.new( + name: cap.name, platform: cap.platform))).result + result = JSON.load(response) + if result.is_a?(Hash) + result = Vagrant::Util::HashWithIndifferentAccess.new(result) + end + result + } + end + cap_klass.plugin_client = client + plugin_klass.host_capability(cap.platform.to_sym, cap.name.to_sym) { cap_klass } + end + end + + # Fetch any defined provider capabilites for given plugin and register + # capabilities within given plugin class + # + # @param [Vagrant::Proto::ProviderCapabilities::Stub] client Plugin client + # @param [Class] plugin_klass Plugin class to register capabilities + # @param [Symbol] plugin_type Type of plugin + def self.generate_provider_capabilities(client, plugin_klass, plugin_type) + logger.debug("checking for provider capabilities in #{plugin_type} plugin #{plugin_klass}") + result = client.provider_capabilities(Vagrant::Proto::Empty.new) + return if result.capabilities.empty? + logger.debug("provider capabilities support detected in #{plugin_type} plugin #{plugin_klass}") + result.capabilities.each do |cap| + cap_klass = Class.new(Capability).tap do |k| + k.define_singleton_method(cap.name) { |machine, *args| + response = plugin_client.provider_capability( + Vagrant::Proto::ProviderCapabilityRequest.new( + machine: JSON.dump(machine), arguments: JSON.dump(args), + capability: Vagrant::Proto::ProviderCapability.new( + name: cap.name, provider: cap.provider))).result + result = JSON.load(response) + if result.is_a?(Hash) + result = Vagrant::Util::HashWithIndifferentAccess.new(result) + end + result + } + end + cap_klass.plugin_client = client + plugin_klass.provider_capability(cap.provider.to_sym, cap.name.to_sym) { cap_klass } + end + end + end + end +end diff --git a/lib/vagrant/go_plugin/config_plugin.rb b/lib/vagrant/go_plugin/config_plugin.rb new file mode 100644 index 000000000..00759dc70 --- /dev/null +++ b/lib/vagrant/go_plugin/config_plugin.rb @@ -0,0 +1,64 @@ +require "vagrant/go_plugin/core" + +module Vagrant + module GoPlugin + # Contains all configuration functionality for go-plugin + module ConfigPlugin + # Generate configuration for the parent class + # + # @param [Vagrant::Proto::Config::Stub] client Plugin client + # @param [String] parent_name Parent plugin name + # @param [Class] parent_klass Parent class to register config + # @param [Symbol] parent_type Type of parent class (:provider, :synced_folder, etc) + def self.generate_config(client, parent_name, parent_klass, parent_type) + config_attrs = client.config_attributes(Vagrant::Proto::Empty.new).items + config_klass = Class.new(Config).tap do |c| + c.class_eval("def parent_name; '#{parent_name}'; end") + end + config_klass.plugin_client = client + Array(config_attrs).each do |att| + config_klass.instance_eval("attr_accessor :#{att}") + end + parent_klass.config(parent_name, parent_type) { config_klass } + end + + # Config plugin class used with go-plugin + class Config < Vagrant.plugin("2", :config) + include GRPCPlugin + + # Finalize the current configuration + def finalize! + data = local_data + response = plugin_client.config_finalize(Vagrant::Proto::Configuration.new( + data: JSON.dump(data))) + result = JSON.load(response.data) + if result && result.is_a?(Hash) + new_data = Vagrant::Util::HashWithIndifferentAccess.new(result) + new_data.each do |key, value| + next if data[key] == value + instance_variable_set("@#{key}", value) + end + end + self + end + + # Validate configuration + # + # @param [Vagrant::Machine] machine Guest machine + # @return [Array] list of errors + def validate(machine) + result = plugin_client.config_validate(Vagrant::Proto::Configuration.new( + machine: JSON.dump(machine), + data: JSON.dump(local_data))) + {parent_name => result.items} + end + + # @return [Hash] currently defined instance variables + def local_data + Vagrant::Util::HashWithIndifferentAccess. + new(instance_variables_hash) + end + end + end + end +end diff --git a/lib/vagrant/go_plugin/core.rb b/lib/vagrant/go_plugin/core.rb new file mode 100644 index 000000000..7d8963c22 --- /dev/null +++ b/lib/vagrant/go_plugin/core.rb @@ -0,0 +1,82 @@ +require "ffi" + +module Vagrant + module GoPlugin + # Base module for generic setup of module/class + module Core + # Loads FFI and core helpers into given module/class + def self.included(const) + const.class_eval do + include Vagrant::Util::Logger + extend FFI::Library + + ffi_lib FFI::Platform::LIBC + ffi_lib File.expand_path("./go-plugin.so", File.dirname(__FILE__)) + + typedef :strptr, :plugin_result + + # stdlib functions + if FFI::Platform.windows? + attach_function :free, :_free, [:pointer], :void + else + attach_function :free, [:pointer], :void + end + + # Load the result received from the extension. This will load + # the JSON result, raise an error if detected, and properly + # free the memory associated with the result. + def load_result(*args) + val, ptr = block_given? ? yield : args + FFI::AutoPointer.new(ptr, self.method(:free)) + begin + result = JSON.load(val) + if !result.is_a?(Hash) + raise TypeError.new "Expected Hash but received `#{result.class}`" + end + if !result["error"].to_s.empty? + raise ArgumentError.new result["error"].to_s + end + result = result["result"] + if result.is_a?(Hash) + result = Vagrant::Util::HashWithIndifferentAccess.new(result) + end + result + rescue => e + # TODO: Customize to provide formatted output on error + raise + end + end + end + end + end + + # Simple module to load into plugin wrapper classes + # to provide expected functionality + module GRPCPlugin + module ClassMethods + def plugin_client + @_plugin_client + end + + def plugin_client=(c) + if @_plugin_client + raise ArgumentError, "Plugin client has already been set" + end + @_plugin_client = c + end + end + + module InstanceMethods + def plugin_client + self.class.plugin_client + end + end + + def self.included(klass) + klass.include(Vagrant::Util::Logger) + klass.include(InstanceMethods) + klass.extend(ClassMethods) + end + end + end +end diff --git a/lib/vagrant/go_plugin/interface.rb b/lib/vagrant/go_plugin/interface.rb new file mode 100644 index 000000000..4cf9d36b0 --- /dev/null +++ b/lib/vagrant/go_plugin/interface.rb @@ -0,0 +1,187 @@ +require "log4r" +require "vagrant/go_plugin/core" + +require "vagrant/go_plugin/vagrant_proto/vagrant_services_pb" + +module RubyLogger + include Vagrant::Util::Logger +end + +module GRPC + extend Vagrant::Util::Logger +end + + +module Vagrant + module GoPlugin + # Interface for go-plugin integration + class Interface + include Core + + # go plugin functions + typedef :bool, :enable_logger + typedef :bool, :timestamps + typedef :string, :log_level + typedef :string, :plugin_directory + + attach_function :_setup, :Setup, [:enable_logger, :timestamps, :log_level], :bool + attach_function :_teardown, :Teardown, [], :void + attach_function :_reset, :Reset, [], :void + attach_function :_load_plugins, :LoadPlugins, [:plugin_directory], :bool + attach_function :_list_providers, :ListProviders, [], :plugin_result + attach_function :_list_synced_folders, :ListSyncedFolders, [], :plugin_result + + def initialize + Vagrant::Proto.instance_eval do + ::GRPC.extend(Vagrant::Util::Logger) + end + setup + end + + # List of provider plugins currently available + # + # @return [Hash>] + def list_providers + load_result { _list_providers } || {} + end + + # List of synced folder plugins currently available + # + # @return [Hash>] + def list_synced_folders + load_result { _list_synced_folders } || {} + end + + # Load any plugins found at the given directory + # + # @param [String, Pathname] path Directory to load + def load_plugins(path) + logger.debug("loading plugins from path: #{path}") + if !File.directory?(path.to_s) + raise ArgumentError, "Directory expected for plugin loading" + end + _load_plugins(path.to_s) + end + + # Register all available plugins + def register_plugins + logger.debug("registering provider plugins") + load_providers + logger.debug("registering synced folder plugins") + load_synced_folders + end + + # Load the plugins found at the given directory + # + # @param [String] plugin_directory Directory containing go-plugins + def setup + if !@setup + @setup = true + Kernel.at_exit { Vagrant::GoPlugin.interface.teardown } + logger.debug("running go-plugin interface setup") + _setup(!Vagrant.log_level.to_s.empty?, + !!ENV["VAGRANT_LOG_TIMESTAMP"], + Vagrant.log_level.to_s) + else + logger.warn("go-plugin interface already setup") + end + end + + def reset + logger.debug("running go-plugin interface reset") + _reset + logger.debug("completed go-plugin interface reset") + end + + # Teardown any plugins that may be currently active + def teardown + logger.debug("starting teardown of go-plugin interface") + _teardown + logger.debug("teardown of go-plugin interface complete") + end + + # @return [Boolean] go plugins have been setup + def configured? + !!@setup + end + + # Load any detected provider plugins + def load_providers + if !@_providers_loaded + @_providers_loaded + logger.debug("provider go-plugins have not been loaded... loading") + list_providers.each do |p_name, p_details| + logger.debug("loading go-plugin provider #{p_name}. details - #{p_details}") + client = Vagrant::Proto::Provider::Stub.new( + "#{p_details[:network]}://#{p_details[:address]}", + :this_channel_is_insecure) + # Create new provider class wrapper + provider_klass = Class.new(ProviderPlugin::Provider) + provider_klass.plugin_client = client + # Create new plugin to register the provider + plugin_klass = Class.new(Vagrant.plugin("2")) + # Define the plugin + plugin_klass.class_eval do + name "#{p_name} Provider" + description p_details[:description] + end + # Register the provider + plugin_klass.provider(p_name.to_sym, priority: p_details.fetch(:priority, 0)) do + provider_klass + end + # Setup any configuration support + ConfigPlugin.generate_config(client, p_name, plugin_klass, :provider) + # Register any guest capabilities + CapabilityPlugin.generate_guest_capabilities(client, plugin_klass, :provider) + # Register any host capabilities + CapabilityPlugin.generate_host_capabilities(client, plugin_klass, :provider) + # Register any provider capabilities + CapabilityPlugin.generate_provider_capabilities(client, plugin_klass, :provider) + logger.debug("completed loading provider go-plugin #{p_name}") + logger.info("loaded go-plugin provider - #{p_name}") + end + else + logger.warn("provider go-plugins have already been loaded. ignoring load request.") + end + end + + # Load any detected synced folder plugins + def load_synced_folders + if !@_synced_folders_loaded + @_synced_folders_loaded = true + logger.debug("synced folder go-plugins have not been loaded... loading") + Array(list_synced_folders).each do |f_name, f_details| + logger.debug("loading go-plugin synced folder #{f_name}. details - #{f_details}") + client = Vagrant::Proto::SyncedFolder::Stub.new( + "#{p_details[:network]}://#{p_details[:address]}", + :this_channel_is_insecure) + # Create new synced folder class wrapper + folder_klass = Class.new(SyncedFolderPlugin::SyncedFolder) + folder_klass.plugin_client = client + # Create new plugin to register the synced folder + plugin_klass = Class.new(Vagrant.plugin("2")) + # Define the plugin + plugin_klass.class_eval do + name "#{f_name} Synced Folder" + description f_details[:description] + end + # Register the synced folder + plugin_klass.synced_folder(f_name.to_sym, priority: f_details.fetch(:priority, 10)) do + folder_klass + end + # Register any guest capabilities + CapabilityPlugin.generate_guest_capabilities(client, plugin_klass, :synced_folder) + # Register any host capabilities + CapabilityPlugin.generate_host_capabilities(client, plugin_klass, :synced_folder) + # Register any provider capabilities + CapabilityPlugin.generate_provider_capabilities(client, plugin_klass, :synced_folder) + logger.debug("completed loading synced folder go-plugin #{f_name}") + logger.info("loaded go-plugin synced folder - #{f_name}") + end + else + logger.warn("synced folder go-plugins have already been loaded. ignoring load request.") + end + end + end + end +end diff --git a/lib/vagrant/go_plugin/manager.rb b/lib/vagrant/go_plugin/manager.rb new file mode 100644 index 000000000..f8003a029 --- /dev/null +++ b/lib/vagrant/go_plugin/manager.rb @@ -0,0 +1,87 @@ +require "zip" +require "vagrant/plugin/state_file" + +module Vagrant + module GoPlugin + class Manager + include Util::Logger + + # @return [Manager] + def self.instance + if !@instance + @instance = self.new + end + @instance + end + + # @return [StateFile] user defined plugins + attr_reader :user_file + # @return [StateFile, nil] project local defined plugins + attr_reader :local_file + + def initialize + FileUtils.mkdir_p(INSTALL_DIRECTORY) + FileUtils.mkdir_p(Vagrant.user_data_path.join("tmp").to_s) + @user_file = Plugin::StateFile.new(Vagrant.user_data_path.join("plugins.json")) + end + + # Load global plugins + def globalize! + Dir.glob(File.join(INSTALL_DIRECTORY, "*")).each do |entry| + next if !File.directory?(entry) + logger.debug("loading go plugins from directory: #{entry}") + GoPlugin.interface.load_plugins(entry) + end + GoPlugin.interface.register_plugins + end + + # Load local plugins + def localize! + raise NotImplementedError + end + + # Install a go plugin + # + # @param [String] plugin_name Name of plugin + # @param [String] remote_source Location of plugin for download + # @param [Hash] options Currently unused + def install_plugin(plugin_name, remote_source, options={}) + install_dir = File.join(INSTALL_DIRECTORY, plugin_name) + FileUtils.mkdir_p(install_dir) + Dir.mktmpdir("go-plugin", Vagrant.user_data_path.join("tmp").to_s) do |dir| + dest_file = File.join(dir, "plugin.zip") + logger.debug("downloading go plugin #{plugin_name} from #{remote_source}") + Util::Downloader.new(remote_source, dest_file).download! + logger.debug("extracting go plugin #{plugin_name} from #{dest_file}") + Zip::File.open(dest_file) do |zfile| + zfile.each do |entry| + install_path = File.join(install_dir, entry.name) + if File.file?(install_path) + logger.warn("removing existing plugin path for unpacking - #{install_path}") + File.delete(install_path) + end + entry.extract(install_path) + FileUtils.chmod(0755, install_path) + end + end + end + user_file.add_go_plugin(plugin_name, source: remote_source) + end + + # Uninstall a go plugin + # + # @param [String] plugin_name Name of plugin + # @param [Hash] options Currently unused + def uninstall_plugin(plugin_name, options={}) + plugin_path = File.join(INSTALL_DIRECTORY, plugin_name) + if !File.directory?(plugin_path) + logger.warn("Plugin directory does not exist for #{plugin_name} - #{plugin_path}") + else + logger.debug("deleting go plugin from path #{plugin_path}") + FileUtils.rm_rf(plugin_path) + end + user_file.remove_go_plugin(plugin_name) + end + end + end +end diff --git a/lib/vagrant/go_plugin/provider_plugin.rb b/lib/vagrant/go_plugin/provider_plugin.rb new file mode 100644 index 000000000..bfe4ba88e --- /dev/null +++ b/lib/vagrant/go_plugin/provider_plugin.rb @@ -0,0 +1,171 @@ +require "vagrant/go_plugin/core" + +module Vagrant + module GoPlugin + module ProviderPlugin + # Helper class for wrapping actions in a go-plugin into + # something which can be used by Vagrant::Action::Builder + class Action + include GRPCPlugin + + # @return [String] action name associated to this class + def self.action_name + @action_name + end + + # Set the action name for this class + # + # @param [String] n action name + # @return [String] + # @note can only be set once + def self.action_name=(n) + if @action_name + raise ArgumentError.new("Class action name has already been set") + end + @action_name = n.to_s.dup.freeze + end + + def initialize(app, env) + @app = app + end + + # Run the action + def call(env) + if env.is_a?(Hash) && !env.is_a?(Vagrant::Util::HashWithIndifferentAccess) + env = Vagrant::Util::HashWithIndifferentAccess.new(env) + end + machine = env.fetch(:machine, {}) + response = plugin_client.run_action( + Vagrant::Proto::ExecuteAction.new( + name: self.class.action_name, + data: JSON.dump(env), + machine: JSON.dump(machine))) + result = JSON.load(response.result) + if result.is_a?(Hash) + result = Vagrant::Util::HashWithIndifferentAccess.new(result) + result.each_pair do |k, v| + env[k] = v + end + end + @app.call(env) + end + end + + # Helper class used to provide a wrapper around a go-plugin + # provider so that it can be interacted with normally within + # Vagrant + class Provider < Vagrant.plugin("2", :provider) + include GRPCPlugin + + # @return [Vagrant::Machine] + attr_reader :machine + + def initialize(machine) + @machine = machine + end + + # @return [String] name of the provider plugin for this class + def name + if !@_name + @_name = plugin_client.name(Vagrant::Proto::Empty.new).name + end + @_name + end + + # Get callable action by name + # + # @param [Symbol] name name of the action + # @return [Class] callable action class + def action(name) + result = plugin_client.action( + Vagrant::Proto::GenericAction.new( + name: name.to_s, + machine: JSON.dump(machine))) + klasses = result.items.map do |klass_name| + if klass_name.start_with?("self::") + action_name = klass_name.split("::", 2).last + klass = Class.new(Action) + klass.plugin_client = plugin_client + klass.action_name = action_name + klass.class_eval do + def self.name + action_name.capitalize.tr("_", "") + end + end + klass + else + klass_name.split("::").inject(Object) do |memo, const| + if memo.const_defined?(const) + memo.const_get(const) + else + raise NameError, "Unknown action class `#{klass_name}`" + end + end + end + end + Vagrant::Action::Builder.new.tap do |builder| + klasses.each do |action_class| + builder.use action_class + end + end + end + + # Execute capability with given name + # + # @param [Symbol] cap_name Name of the capability + # @return [Object] + def capability(cap_name, *args) + r = plugin_client.provider_capability( + Vagrant::Proto::ProviderCapabilityRequest.new( + capability: Vagrant::Proto::ProviderCapability.new( + name: cap_name.to_s, + provider: name + ), + machine: JSON.dump(machine), + arguments: JSON.dump(args) + ) + ) + result = JSON.load(r.result) + if result.is_a?(Hash) + result = Vagrant::Util::HashWithIndifferentAccess.new(result) + end + result + end + + # @return [Boolean] provider is installed + def is_installed? + plugin_client.is_installed(Vagrant::Proto::Machine.new( + machine: JSON.dump(machine))).result + end + + # @return [Boolean] provider is usable + def is_usable? + plugin_client.is_usable(Vagrant::Proto::Machine.new( + machine: JSON.dump(machine))).result + end + + # @return [nil] + def machine_id_changed + plugin_client.machine_id_changed(Vagrant::Proto::Machine.new( + machine: JSON.dump(machine))) + nil + end + + # @return [Hash] SSH information + def ssh_info + result = plugin_client.ssh_info(Vagrant::Proto::Machine.new( + machine: JSON.dump(machine))).to_hash + Vagrant::Util::HashWithIndifferentAccess.new(result) + end + + # @return [Vagrant::MachineState] + def state + result = plugin_client.state(Vagrant::Proto::Machine.new( + machine: JSON.dump(machine))) + Vagrant::MachineState.new(result.id, + result.short_description, result.long_description) + end + end + end + end +end diff --git a/lib/vagrant/go_plugin/synced_folder_plugin.rb b/lib/vagrant/go_plugin/synced_folder_plugin.rb new file mode 100644 index 000000000..178ed40ce --- /dev/null +++ b/lib/vagrant/go_plugin/synced_folder_plugin.rb @@ -0,0 +1,89 @@ +require "vagrant/go_plugin/core" + +module Vagrant + module GoPlugin + # Contains all synced folder functionality for go-plugin + module SyncedFolderPlugin + + # Helper class used to provide a wrapper around a go-plugin + # synced folder so that it can be interacted with normally + # within Vagrant + class SyncedFolder < Vagrant.plugin("2", :synced_folder) + include GRPCPlugin + + # Cleanup synced folders + # + # @param [Vagrant::Machine] machine Vagrant guest + # @param [Hash] opts Folder options + def cleanup(machine, opts) + plugin_client.cleanup( + Vagrant::Proto::SyncedFolders.new( + machine: JSON.dump(machine), + options: JSON.dump(opts), + folders: JSON.dump({}))) + nil + end + + # Disable synced folders + # + # @param [Vagrant::Machine] machine Vagrant guest + # @param [Hash] folders Folders to enable + # @param [Hash] opts Folder options + def disable(machine, folders, opts) + plugin_client.disable( + Vagrant::Proto::SyncedFolders.new( + machine: JSON.dump(machine), + folders: JSON.dump(folders), + options: JSON.dump(opts))) + nil + end + + # Enable synced folders + # + # @param [Vagrant::Machine] machine Vagrant guest + # @param [Hash] folders Folders to enable + # @param [Hash] opts Folder options + def enable(machine, folders, opts) + plugin_client.enable( + Vagrant::Proto::SyncedFolders.new( + machine: JSON.dump(machine), + folders: JSON.dump(folders), + options: JSON.dump(options))) + nil + end + + # Prepare synced folders + # + # @param [Vagrant::Machine] machine Vagrant guest + # @param [Hash] folders Folders to enable + # @param [Hash] opts Folder options + def prepare(machine, folders, opts) + plugin_client.prepare( + Vagrant::Proto::SyncedFolders.new( + machine: JSON.dump(machine), + folders: JSON.dump(folders), + options: JSON.dump(options))) + nil + end + + # Check if plugin is usable + # + # @param [Vagrant::Machine] machine Vagrant guest + # @return [Boolean] + def usable?(machine, raise_error=false) + plugin_client.is_usable( + Vagrant::Proto::Machine.new( + machine: JSON.dump(machine))).result + end + + # @return [String] + def name + if !@_name + @_name = plugin_client.name(Vagrant::Proto::Empty.new).name + end + @_name + end + end + end + end +end diff --git a/lib/vagrant/go_plugin/vagrant_proto/vagrant_pb.rb b/lib/vagrant/go_plugin/vagrant_proto/vagrant_pb.rb new file mode 100644 index 000000000..847b9fbe6 --- /dev/null +++ b/lib/vagrant/go_plugin/vagrant_proto/vagrant_pb.rb @@ -0,0 +1,122 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: vagrant.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("vagrant.proto", :syntax => :proto3) do + add_message "vagrant.proto.Empty" do + end + add_message "vagrant.proto.Machine" do + optional :machine, :string, 1 + end + add_message "vagrant.proto.Valid" do + optional :result, :bool, 1 + end + add_message "vagrant.proto.Identifier" do + optional :name, :string, 1 + end + add_message "vagrant.proto.PluginInfo" do + optional :description, :string, 1 + optional :priority, :int64, 2 + end + add_message "vagrant.proto.Content" do + optional :target, :string, 1 + optional :value, :string, 2 + end + add_message "vagrant.proto.WriteResponse" do + optional :length, :int32, 1 + end + add_message "vagrant.proto.SystemCapability" do + optional :name, :string, 1 + optional :platform, :string, 2 + end + add_message "vagrant.proto.ProviderCapability" do + optional :name, :string, 1 + optional :provider, :string, 2 + end + add_message "vagrant.proto.SystemCapabilityList" do + repeated :capabilities, :message, 1, "vagrant.proto.SystemCapability" + end + add_message "vagrant.proto.ProviderCapabilityList" do + repeated :capabilities, :message, 1, "vagrant.proto.ProviderCapability" + end + add_message "vagrant.proto.GenericResponse" do + optional :result, :string, 1 + end + add_message "vagrant.proto.GuestCapabilityRequest" do + optional :capability, :message, 1, "vagrant.proto.SystemCapability" + optional :machine, :string, 2 + optional :arguments, :string, 3 + end + add_message "vagrant.proto.HostCapabilityRequest" do + optional :capability, :message, 1, "vagrant.proto.SystemCapability" + optional :environment, :string, 2 + optional :arguments, :string, 3 + end + add_message "vagrant.proto.ProviderCapabilityRequest" do + optional :capability, :message, 1, "vagrant.proto.ProviderCapability" + optional :machine, :string, 2 + optional :arguments, :string, 3 + end + add_message "vagrant.proto.Configuration" do + optional :data, :string, 1 + optional :machine, :string, 2 + end + add_message "vagrant.proto.ListResponse" do + repeated :items, :string, 1 + end + add_message "vagrant.proto.SyncedFolders" do + optional :machine, :string, 1 + optional :folders, :string, 2 + optional :options, :string, 3 + end + add_message "vagrant.proto.GenericAction" do + optional :name, :string, 1 + optional :machine, :string, 2 + end + add_message "vagrant.proto.ExecuteAction" do + optional :name, :string, 1 + optional :data, :string, 2 + optional :machine, :string, 3 + end + add_message "vagrant.proto.MachineSshInfo" do + optional :host, :string, 1 + optional :port, :int64, 2 + optional :private_key_path, :string, 3 + optional :username, :string, 4 + end + add_message "vagrant.proto.MachineState" do + optional :id, :string, 1 + optional :short_description, :string, 2 + optional :long_description, :string, 3 + end + end +end + +module Vagrant + module Proto + Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.Empty").msgclass + Machine = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.Machine").msgclass + Valid = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.Valid").msgclass + Identifier = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.Identifier").msgclass + PluginInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.PluginInfo").msgclass + Content = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.Content").msgclass + WriteResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.WriteResponse").msgclass + SystemCapability = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.SystemCapability").msgclass + ProviderCapability = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.ProviderCapability").msgclass + SystemCapabilityList = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.SystemCapabilityList").msgclass + ProviderCapabilityList = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.ProviderCapabilityList").msgclass + GenericResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.GenericResponse").msgclass + GuestCapabilityRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.GuestCapabilityRequest").msgclass + HostCapabilityRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.HostCapabilityRequest").msgclass + ProviderCapabilityRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.ProviderCapabilityRequest").msgclass + Configuration = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.Configuration").msgclass + ListResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.ListResponse").msgclass + SyncedFolders = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.SyncedFolders").msgclass + GenericAction = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.GenericAction").msgclass + ExecuteAction = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.ExecuteAction").msgclass + MachineSshInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.MachineSshInfo").msgclass + MachineState = Google::Protobuf::DescriptorPool.generated_pool.lookup("vagrant.proto.MachineState").msgclass + end +end diff --git a/lib/vagrant/go_plugin/vagrant_proto/vagrant_services_pb.rb b/lib/vagrant/go_plugin/vagrant_proto/vagrant_services_pb.rb new file mode 100644 index 000000000..5db46623e --- /dev/null +++ b/lib/vagrant/go_plugin/vagrant_proto/vagrant_services_pb.rb @@ -0,0 +1,223 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# Source: vagrant.proto for package 'vagrant.proto' + +require 'grpc' +require_relative 'vagrant_pb' + +module Vagrant + module Proto + module IO + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.IO' + + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + end + + Stub = Service.rpc_stub_class + end + module GuestCapabilities + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.GuestCapabilities' + + rpc :GuestCapabilities, Empty, SystemCapabilityList + rpc :GuestCapability, GuestCapabilityRequest, GenericResponse + # IO helpers for streaming (copied from Stream service) + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + end + + Stub = Service.rpc_stub_class + end + module HostCapabilities + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.HostCapabilities' + + rpc :HostCapabilities, Empty, SystemCapabilityList + rpc :HostCapability, HostCapabilityRequest, GenericResponse + # IO helpers for streaming (copied from Stream service) + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + end + + Stub = Service.rpc_stub_class + end + module ProviderCapabilities + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.ProviderCapabilities' + + rpc :ProviderCapabilities, Empty, ProviderCapabilityList + rpc :ProviderCapability, ProviderCapabilityRequest, GenericResponse + # IO helpers for streaming (copied from Stream service) + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + end + + Stub = Service.rpc_stub_class + end + module Config + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.Config' + + rpc :ConfigAttributes, Empty, ListResponse + rpc :ConfigLoad, Configuration, Configuration + rpc :ConfigValidate, Configuration, ListResponse + rpc :ConfigFinalize, Configuration, Configuration + # IO helpers for streaming (copied from Stream service) + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + end + + Stub = Service.rpc_stub_class + end + module SyncedFolder + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.SyncedFolder' + + rpc :Cleanup, SyncedFolders, Empty + rpc :Disable, SyncedFolders, Empty + rpc :Enable, SyncedFolders, Empty + rpc :Info, Empty, PluginInfo + rpc :IsUsable, Machine, Valid + rpc :Name, Empty, Identifier + rpc :Prepare, SyncedFolders, Empty + # IO helpers for streaming (copied from Stream service) + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + # Guest capabilities helpers (copied from GuestCapabilities service) + rpc :GuestCapabilities, Empty, SystemCapabilityList + rpc :GuestCapability, GuestCapabilityRequest, GenericResponse + # Host capabilities helpers (copied from GuestCapabilities service) + rpc :HostCapabilities, Empty, SystemCapabilityList + rpc :HostCapability, HostCapabilityRequest, GenericResponse + end + + Stub = Service.rpc_stub_class + end + module Provider + class Service + + include GRPC::GenericService + + self.marshal_class_method = :encode + self.unmarshal_class_method = :decode + self.service_name = 'vagrant.proto.Provider' + + rpc :Action, GenericAction, ListResponse + rpc :Info, Empty, PluginInfo + rpc :IsInstalled, Machine, Valid + rpc :IsUsable, Machine, Valid + rpc :MachineIdChanged, Machine, Machine + rpc :Name, Empty, Identifier + rpc :RunAction, ExecuteAction, GenericResponse + rpc :SshInfo, Machine, MachineSshInfo + rpc :State, Machine, MachineState + # IO helpers for streaming (copied from Stream service) + rpc :Read, Identifier, Content + rpc :Write, Content, WriteResponse + # Config helpers (copied from Config service) + rpc :ConfigAttributes, Empty, ListResponse + rpc :ConfigLoad, Configuration, Configuration + rpc :ConfigValidate, Configuration, ListResponse + rpc :ConfigFinalize, Configuration, Configuration + # Guest capabilities helpers (copied from GuestCapabilities service) + rpc :GuestCapabilities, Empty, SystemCapabilityList + rpc :GuestCapability, GuestCapabilityRequest, GenericResponse + # Host capabilities helpers (copied from HostCapabilities service) + rpc :HostCapabilities, Empty, SystemCapabilityList + rpc :HostCapability, HostCapabilityRequest, GenericResponse + # Provider capabilities helpers (copied from ProviderCapabilities service) + rpc :ProviderCapabilities, Empty, ProviderCapabilityList + rpc :ProviderCapability, ProviderCapabilityRequest, GenericResponse + end + + Stub = Service.rpc_stub_class + end + end +end + +require 'logger' + +# DebugIsTruncated extends the default Logger to truncate debug messages +class DebugIsTruncated < Logger + def debug(s) + super(truncate(s, 1024)) + end + + # Truncates a given +text+ after a given length if +text+ is longer than length: + # + # 'Once upon a time in a world far far away'.truncate(27) + # # => "Once upon a time in a wo..." + # + # Pass a string or regexp :separator to truncate +text+ at a natural break: + # + # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') + # # => "Once upon a time in a..." + # + # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) + # # => "Once upon a time in a..." + # + # The last characters will be replaced with the :omission string (defaults to "...") + # for a total length not exceeding length: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + def truncate(s, truncate_at, options = {}) + return s unless s.length > truncate_at + omission = options[:omission] || '...' + with_extra_room = truncate_at - omission.length + stop = \ + if options[:separator] + rindex(options[:separator], with_extra_room) || with_extra_room + else + with_extra_room + end + "#{s[0, stop]}#{omission}" + end +end + +# RubyLogger defines a logger for gRPC based on the standard ruby logger. +module RubyLogger + def logger + LOGGER + end + + LOGGER = DebugIsTruncated.new(STDOUT) + LOGGER.level = Logger::DEBUG +end + +# GRPC is the general RPC module +module GRPC + # Inject the noop #logger if no module-level logger method has been injected. + extend RubyLogger +end diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 0b1ca1b65..db80db050 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -578,6 +578,20 @@ module Vagrant end end + # @return [String] + def to_json(*args) + { + box: box, + config: config, + data_dir: data_dir, + environment: env, + id: id, + name: name, + provider_config: provider_config, + provider_name: provider_name + }.to_json(*args) + end + protected # Returns the path to the file that stores the UID. diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index c6872d4fd..50f7ac658 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -28,6 +28,7 @@ module Vagrant @data["version"] ||= "1" @data["installed"] ||= {} + @data["go_plugin"] ||= {} end # Add a plugin that is installed to the state file. @@ -47,6 +48,43 @@ module Vagrant save! end + # Add a go plugin that is installed to the state file + # + # @param [String, Symbol] name Plugin name + def add_go_plugin(name, **opts) + @data["go_plugin"][name] = { + "source" => opts[:source] + } + + save! + end + + # Remove a go plugin that is installed from the state file + # + # @param [String, Symbol] name Name of the plugin + def remove_go_plugin(name) + @data["go_plugin"].delete(name.to_s) + + save! + end + + # Check if go plugin is installed from the state file + # + # @param [String, Symbol] name Plugin name + # @return [Boolean] + def has_go_plugin?(name) + @data["go_plugin"].key?(name.to_s) + end + + # This returns a hash of installed go plugins according to the state + # file. Note that this may _not_ directly match over to actually + # installed plugins + # + # @return [Hash] + def installed_go_plugins + @data["go_plugin"] + end + # Adds a RubyGems index source to look up gems. # # @param [String] url URL of the source. diff --git a/lib/vagrant/plugin/v2/config.rb b/lib/vagrant/plugin/v2/config.rb index 20e08cbfe..8659e2c21 100644 --- a/lib/vagrant/plugin/v2/config.rb +++ b/lib/vagrant/plugin/v2/config.rb @@ -138,6 +138,12 @@ module Vagrant @__finalized = true end end + + class Set < ::Set + def to_json(*args) + self.to_a.to_json(*args) + end + end end end end diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index 32babe50d..a72729c9f 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -8,7 +8,9 @@ module Vagrant autoload :DeepMerge, 'vagrant/util/deep_merge' autoload :Env, 'vagrant/util/env' autoload :HashWithIndifferentAccess, 'vagrant/util/hash_with_indifferent_access' + autoload :Experimental, 'vagrant/util/experimental' autoload :GuestInspection, 'vagrant/util/guest_inspection' + autoload :Logger, 'vagrant/util/logger' autoload :LoggingFormatter, 'vagrant/util/logging_formatter' autoload :Platform, 'vagrant/util/platform' autoload :Retryable, 'vagrant/util/retryable' diff --git a/lib/vagrant/util/hash_with_indifferent_access.rb b/lib/vagrant/util/hash_with_indifferent_access.rb index ae5dcd025..8ac38f137 100644 --- a/lib/vagrant/util/hash_with_indifferent_access.rb +++ b/lib/vagrant/util/hash_with_indifferent_access.rb @@ -14,6 +14,9 @@ module Vagrant super(&block) hash.each do |key, value| + if value.is_a?(Hash) && !value.is_a?(self.class) + value = self.class.new(value) + end self[convert_key(key)] = value end end diff --git a/lib/vagrant/util/logger.rb b/lib/vagrant/util/logger.rb new file mode 100644 index 000000000..1a24529f8 --- /dev/null +++ b/lib/vagrant/util/logger.rb @@ -0,0 +1,22 @@ +require "log4r" + +module Vagrant + module Util + # Adds a logger method which provides automatically + # namespaced logger instance + module Logger + + # @return [Log4r::Logger] + def logger + if !@_logger + name = (self.is_a?(Module) ? self : self.class).name.downcase + if !name.start_with?("vagrant") + name = "vagrant::root::#{name}" + end + @_logger = Log4r::Logger.new(name) + end + @_logger + end + end + end +end diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index d0f9bf8e9..7979c2435 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -74,6 +74,23 @@ module VagrantPlugins autoload :RepairPluginsLocal, action_root.join("repair_plugins") autoload :UninstallPlugin, action_root.join("uninstall_plugin") autoload :UpdateGems, action_root.join("update_gems") + + Vagrant::Util::Experimental.guard_with(:go_plugin) do + autoload :UninstallGoPlugin, action_root.join("uninstall_go_plugin") + autoload :InstallGoPlugin, action_root.join("install_go_plugin") + + def self.action_go_install + Vagrant::Action::Builder.new.tap do |b| + b.use InstallGoPlugin + end + end + + def self.action_go_uninstall + Vagrant::Action::Builder.new.tap do |b| + b.use UninstallGoPlugin + end + end + end end end end diff --git a/plugins/commands/plugin/action/install_go_plugin.rb b/plugins/commands/plugin/action/install_go_plugin.rb new file mode 100644 index 000000000..b79e86158 --- /dev/null +++ b/plugins/commands/plugin/action/install_go_plugin.rb @@ -0,0 +1,35 @@ +require "log4r" +require "vagrant/go_plugin/manager" + +module VagrantPlugins + module CommandPlugin + module Action + class InstallGoPlugin + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::installgoplugin") + end + + def call(env) + plugin_name = env[:plugin_name] + plugin_source = env[:plugin_source] + + manager = Vagrant::GoPlugin::Manager.instance + + env[:ui].info(I18n.t("vagrant.commands.plugin.installing", + name: plugin_name)) + + manager.install_plugin(plugin_name, plugin_source) + + # Tell the user + env[:ui].success(I18n.t("vagrant.commands.plugin.installed", + name: plugin_name, + version: plugin_source)) + + # Continue + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/action/uninstall_go_plugin.rb b/plugins/commands/plugin/action/uninstall_go_plugin.rb new file mode 100644 index 000000000..7c3d0a48f --- /dev/null +++ b/plugins/commands/plugin/action/uninstall_go_plugin.rb @@ -0,0 +1,22 @@ +module VagrantPlugins + module CommandPlugin + module Action + class UninstallGoPlugin + def initialize(app, env) + @app = app + end + + def call(env) + # Remove it! + env[:ui].info(I18n.t("vagrant.commands.plugin.uninstalling", + name: env[:plugin_name])) + + manager = Vagrant::GoPlugin::Manager.instance + manager.uninstall_plugin(env[:plugin_name]) + + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/command/go_install.rb b/plugins/commands/plugin/command/go_install.rb new file mode 100644 index 000000000..a09691b0c --- /dev/null +++ b/plugins/commands/plugin/command/go_install.rb @@ -0,0 +1,43 @@ +require 'optparse' + +require_relative "base" + +module VagrantPlugins + module CommandPlugin + module Command + class GoInstall < Base + + def execute + options = { verbose: false } + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin goinstall " + o.separator "" + + o.on("--verbose", "Enable verbose output for plugin installation") do |v| + options[:verbose] = v + end + end + + # Parse the options + argv = parse_options(opts) + return if !argv + + if argv.length != 2 + raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp + end + + plugin_name, plugin_source = argv + + action(Action.action_go_install, + plugin_name: plugin_name, + plugin_source: plugin_source + ) + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/command/go_uninstall.rb b/plugins/commands/plugin/command/go_uninstall.rb new file mode 100644 index 000000000..8bae93023 --- /dev/null +++ b/plugins/commands/plugin/command/go_uninstall.rb @@ -0,0 +1,31 @@ +require 'optparse' + +require_relative "base" + +module VagrantPlugins + module CommandPlugin + module Command + class GoUninstall < Base + def execute + options = {} + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin gouninstall [ ...] [-h]" + end + + # Parse the options + argv = parse_options(opts) + return if !argv + raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length < 1 + + # Uninstall the plugins + argv.each do |entry| + action(Action.action_go_uninstall, plugin_name: entry) + end + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/command/root.rb b/plugins/commands/plugin/command/root.rb index 6953eb495..6529f4c0d 100644 --- a/plugins/commands/plugin/command/root.rb +++ b/plugins/commands/plugin/command/root.rb @@ -48,6 +48,18 @@ module VagrantPlugins require_relative "uninstall" Uninstall end + + Vagrant::Util::Experimental.guard_with(:go_plugin) do + @subcommands.register(:goinstall) do + require_relative "go_install" + GoInstall + end + + @subcommands.register(:gouninstall) do + require_relative "go_uninstall" + GoUninstall + end + end end def execute diff --git a/test/unit/vagrant/cli_test.rb b/test/unit/vagrant/cli_test.rb index db7e7e9bd..90839755c 100644 --- a/test/unit/vagrant/cli_test.rb +++ b/test/unit/vagrant/cli_test.rb @@ -30,6 +30,9 @@ describe Vagrant::CLI do describe "#execute" do let(:triggers) { double("triggers") } + before { allow(Vagrant::Util::Experimental).to receive(:feature_enabled?) } + + it "invokes help and exits with 1 if invalid command" do subject = described_class.new(["i-dont-exist"], env) expect(subject).to receive(:help).once @@ -59,7 +62,7 @@ describe Vagrant::CLI do it "fires triggers, if enabled" do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?). - with("typed_triggers").and_return("true") + with("typed_triggers").and_return(true) allow(triggers).to receive(:fire_triggers) commands[:destroy] = [command_lambda("destroy", 42), {}] @@ -76,7 +79,7 @@ describe Vagrant::CLI do it "does not fire triggers if disabled" do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?). - with("typed_triggers").and_return("false") + with("typed_triggers").and_return(false) commands[:destroy] = [command_lambda("destroy", 42), {}] diff --git a/test/unit/vagrant/go_plugin/capability_plugin_test.rb b/test/unit/vagrant/go_plugin/capability_plugin_test.rb new file mode 100644 index 000000000..079e03a68 --- /dev/null +++ b/test/unit/vagrant/go_plugin/capability_plugin_test.rb @@ -0,0 +1,215 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::CapabilityPlugin do + let(:client) { double("client") } + + describe Vagrant::GoPlugin::CapabilityPlugin::Capability do + it "should be a GRPCPlugin" do + expect(described_class.ancestors).to include(Vagrant::GoPlugin::GRPCPlugin) + end + end + + describe ".generate_guest_capabilities" do + let(:caps) { + [{platform: "dummy", name: "stub_cap"}, + {platform: "dummy", name: "other_cap"}]} + let(:cap_response) { + Vagrant::Proto::SystemCapabilityList.new( + capabilities: caps.map { |i| + Vagrant::Proto::SystemCapability.new(i)})} + let(:plugin_klass) { double("plugin_klass") } + let(:plugin_type) { :testing } + + + before do + allow(client).to receive(:guest_capabilities). + and_return(cap_response) + allow(plugin_klass).to receive(:guest_capability) + end + + it "should generate two new capability classes" do + expect(Class).to receive(:new).twice. + with(Vagrant::GoPlugin::CapabilityPlugin::Capability). + and_call_original + described_class.generate_guest_capabilities(client, plugin_klass, plugin_type) + end + + it "should create capability name methods" do + c1 = Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) + c2 = Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) + expect(Class).to receive(:new).and_return(c1) + expect(Class).to receive(:new).and_return(c2) + described_class.generate_guest_capabilities(client, plugin_klass, plugin_type) + expect(c1).to respond_to(:stub_cap) + expect(c2).to respond_to(:other_cap) + end + + it "should register guest capability" do + expect(plugin_klass).to receive(:guest_capability).with(:dummy, :stub_cap) + expect(plugin_klass).to receive(:guest_capability).with(:dummy, :other_cap) + described_class.generate_guest_capabilities(client, plugin_klass, plugin_type) + end + + context "generated capability" do + let(:cap_class) { Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) } + let(:response) { double(result: result) } + let(:result) { nil } + + before { + expect(Class).to receive(:new).and_return(cap_class) + allow(Class).to receive(:new).and_call_original + described_class.generate_guest_capabilities(client, plugin_klass, plugin_type) + } + + it "should call guest_capability on the plugin client" do + expect(client).to receive(:guest_capability).and_return(response) + cap_class.stub_cap(nil, nil) + end + + context "when result is a hash" do + let(:result) { "{}" } + + it "should convert hash to hash with indifferent access" do + expect(client).to receive(:guest_capability).and_return(response) + expect(cap_class.stub_cap(nil, nil)).to be_a(Vagrant::Util::HashWithIndifferentAccess) + end + end + end + end + + describe ".generate_host_capabilities" do + let(:caps) { + [{platform: "dummy", name: "stub_cap"}, + {platform: "dummy", name: "other_cap"}]} + let(:cap_response) { + Vagrant::Proto::SystemCapabilityList.new( + capabilities: caps.map { |i| + Vagrant::Proto::SystemCapability.new(i)})} + let(:plugin_klass) { double("plugin_klass") } + let(:plugin_type) { :testing } + + + before do + allow(client).to receive(:host_capabilities). + and_return(cap_response) + allow(plugin_klass).to receive(:host_capability) + end + + it "should generate two new capability classes" do + expect(Class).to receive(:new).twice. + with(Vagrant::GoPlugin::CapabilityPlugin::Capability). + and_call_original + described_class.generate_host_capabilities(client, plugin_klass, plugin_type) + end + + it "should create capability name methods" do + c1 = Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) + c2 = Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) + expect(Class).to receive(:new).and_return(c1) + expect(Class).to receive(:new).and_return(c2) + described_class.generate_host_capabilities(client, plugin_klass, plugin_type) + expect(c1).to respond_to(:stub_cap) + expect(c2).to respond_to(:other_cap) + end + + it "should register host capability" do + expect(plugin_klass).to receive(:host_capability).with(:dummy, :stub_cap) + expect(plugin_klass).to receive(:host_capability).with(:dummy, :other_cap) + described_class.generate_host_capabilities(client, plugin_klass, plugin_type) + end + + context "generated capability" do + let(:cap_class) { Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) } + let(:response) { double(result: result) } + let(:result) { nil } + + before { + expect(Class).to receive(:new).and_return(cap_class) + allow(Class).to receive(:new).and_call_original + described_class.generate_host_capabilities(client, plugin_klass, plugin_type) + } + + it "should call guest_capability on the plugin client" do + expect(client).to receive(:host_capability).and_return(response) + cap_class.stub_cap(nil, nil) + end + + context "when result is a hash" do + let(:result) { "{}" } + + it "should convert hash to hash with indifferent access" do + expect(client).to receive(:host_capability).and_return(response) + expect(cap_class.stub_cap(nil, nil)).to be_a(Vagrant::Util::HashWithIndifferentAccess) + end + end + end + end + + describe ".generate_provider_capabilities" do + let(:caps) { + [{provider: "dummy", name: "stub_cap"}, + {provider: "dummy", name: "other_cap"}]} + let(:cap_response) { + Vagrant::Proto::ProviderCapabilityList.new( + capabilities: caps.map { |i| + Vagrant::Proto::ProviderCapability.new(i)})} + let(:plugin_klass) { double("plugin_klass") } + let(:plugin_type) { :testing } + + + before do + allow(client).to receive(:provider_capabilities). + and_return(cap_response) + allow(plugin_klass).to receive(:provider_capability) + end + + it "should generate two new capability classes" do + expect(Class).to receive(:new).twice. + with(Vagrant::GoPlugin::CapabilityPlugin::Capability). + and_call_original + described_class.generate_provider_capabilities(client, plugin_klass, plugin_type) + end + + it "should create capability name methods" do + c1 = Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) + c2 = Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) + expect(Class).to receive(:new).and_return(c1) + expect(Class).to receive(:new).and_return(c2) + described_class.generate_provider_capabilities(client, plugin_klass, plugin_type) + expect(c1).to respond_to(:stub_cap) + expect(c2).to respond_to(:other_cap) + end + + it "should register provider capability" do + expect(plugin_klass).to receive(:provider_capability).with(:dummy, :stub_cap) + expect(plugin_klass).to receive(:provider_capability).with(:dummy, :other_cap) + described_class.generate_provider_capabilities(client, plugin_klass, plugin_type) + end + + context "generated capability" do + let(:cap_class) { Class.new(Vagrant::GoPlugin::CapabilityPlugin::Capability) } + let(:response) { double(result: result) } + let(:result) { nil } + + before { + expect(Class).to receive(:new).and_return(cap_class) + allow(Class).to receive(:new).and_call_original + described_class.generate_provider_capabilities(client, plugin_klass, plugin_type) + } + + it "should call guest_capability on the plugin client" do + expect(client).to receive(:provider_capability).and_return(response) + cap_class.stub_cap(nil, nil) + end + + context "when result is a hash" do + let(:result) { "{}" } + + it "should convert hash to hash with indifferent access" do + expect(client).to receive(:provider_capability).and_return(response) + expect(cap_class.stub_cap(nil, nil)).to be_a(Vagrant::Util::HashWithIndifferentAccess) + end + end + end + end +end diff --git a/test/unit/vagrant/go_plugin/config_plugin_test.rb b/test/unit/vagrant/go_plugin/config_plugin_test.rb new file mode 100644 index 000000000..e170dc3b3 --- /dev/null +++ b/test/unit/vagrant/go_plugin/config_plugin_test.rb @@ -0,0 +1,141 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::ConfigPlugin do + let(:client) { double("client") } + + describe ".generate_config" do + let(:parent_name) { "test" } + let(:parent_klass) { double("parent_klass") } + let(:parent_type) { "dummy" } + let(:attributes_response) { + double("attributes_response", + items: attributes) + } + let(:attributes) { [] } + + before do + allow(parent_klass).to receive(:config) + allow(client).to receive(:config_attributes). + and_return(attributes_response) + end + + it "should register a new config class" do + expect(parent_klass).to receive(:config). + with(parent_name, parent_type) { |&block| + expect(block.call.ancestors).to include(Vagrant::GoPlugin::ConfigPlugin::Config) + } + described_class.generate_config(client, parent_name, parent_klass, parent_type) + end + + it "should register the client within the class" do + expect(parent_klass).to receive(:config). + with(parent_name, parent_type) { |&block| + expect(block.call.plugin_client).to eq(client) + } + described_class.generate_config(client, parent_name, parent_klass, parent_type) + end + + context "when attributes are provided" do + let(:attributes) { ["a_one", "a_two"] } + + it "should add accessor instance methods to the class" do + expect(parent_klass).to receive(:config). + with(parent_name, parent_type) { |&block| + expect(block.call.instance_methods).to include(:a_one) + expect(block.call.instance_methods).to include(:a_two) + } + described_class.generate_config(client, parent_name, parent_klass, parent_type) + end + end + end + + describe Vagrant::GoPlugin::ConfigPlugin::Config do + let(:machine) { double("machine") } + let(:subject) { + c = Class.new(described_class) + c.plugin_client = client + c.new + } + + describe "#validate" do + let(:response) { double("response", items: errors) } + let(:errors) { ["errors"] } + + before do + allow(machine).to receive(:to_json).and_return("{}") + allow(client).to receive(:config_validate).and_return(response) + end + + it "should call client to validate" do + expect(client).to receive(:config_validate). + with(instance_of(Vagrant::Proto::Configuration)). + and_return(response) + subject.validate(machine) + end + + it "should return list of validation errors" do + expect(subject.validate(machine)).to eq(errors) + end + end + + describe "#finalize!" do + let(:response) { double("response", data: result) } + let(:result) { "null" } + + before do + allow(client).to receive(:config_finalize).and_return(response) + end + + it "should return the config instance" do + expect(subject.finalize!).to eq(subject) + end + + it "should call client to finalize" do + expect(client).to receive(:config_finalize). + with(instance_of(Vagrant::Proto::Configuration)). + and_return(response) + subject.finalize! + end + + context "when configuration data is returned" do + let(:result) { + {attr1: true, attr2: "value"}.to_json + } + + it "should create accessor methods to configuration data" do + subject.finalize! + expect(subject).to respond_to(:attr1) + expect(subject).to respond_to(:attr2) + end + + it "should return data from reader methods" do + subject.finalize! + expect(subject.attr1).to eq(true) + expect(subject.attr2).to eq("value") + end + end + + describe "#local_data" do + it "should return an empty hash" do + expect(subject.local_data).to be_empty + end + + context "with config attributes set" do + let(:response) { double("response", data: result) } + let(:result) { {attr1: true, attr2: "value"}.to_json } + + before do + allow(client).to receive(:config_finalize).and_return(response) + subject.finalize! + end + + it "should return data values" do + result = subject.local_data + expect(result[:attr1]).to eq(true) + expect(result[:attr2]).to eq("value") + end + end + end + end + end +end diff --git a/test/unit/vagrant/go_plugin/core_test.rb b/test/unit/vagrant/go_plugin/core_test.rb new file mode 100644 index 000000000..c74925d28 --- /dev/null +++ b/test/unit/vagrant/go_plugin/core_test.rb @@ -0,0 +1,41 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::GRPCPlugin do + let(:client) { double("client") } + let(:subject) { Class.new.tap { |c| c.include(described_class) } } + + describe ".plugin_client=" do + it "should set the plugin client" do + expect(subject.plugin_client = client).to eq(client) + expect(subject.plugin_client).to eq(client) + end + + it "should error if plugin is already set" do + subject.plugin_client = client + expect { subject.plugin_client = client }. + to raise_error(ArgumentError) + end + end + + describe ".plugin_client" do + it "should return nil when client has not been set" do + expect(subject.plugin_client).to be_nil + end + + it "should return client when it has been set" do + subject.plugin_client = client + expect(subject.plugin_client).to eq(client) + end + end + + describe "#plugin_client" do + it "should be nil when client has not been set" do + expect(subject.new.plugin_client).to be_nil + end + + it "should return client when client has been set" do + subject.plugin_client = client + expect(subject.new.plugin_client).to eq(client) + end + end +end diff --git a/test/unit/vagrant/go_plugin/interface_test.rb b/test/unit/vagrant/go_plugin/interface_test.rb new file mode 100644 index 000000000..e71f9b618 --- /dev/null +++ b/test/unit/vagrant/go_plugin/interface_test.rb @@ -0,0 +1,56 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::Interface do + before do + allow_any_instance_of(described_class).to receive(:_setup) + end + + describe "#load_plugins" do + let(:path) { double("path", to_s: "path") } + + it "should raise error if path is not a directory" do + expect(File).to receive(:directory?).with(path.to_s).and_return(false) + expect { subject.load_plugins(path) }.to raise_error(ArgumentError) + end + + it "should load plugins if path is a directory" do + expect(File).to receive(:directory?).with(path.to_s).and_return(true) + expect(subject).to receive(:_load_plugins).with(path.to_s) + subject.load_plugins(path) + end + end + + describe "#register_plugins" do + it "should load Provider and SyncedFolder plugins" do + expect(subject).to receive(:load_providers) + expect(subject).to receive(:load_synced_folders) + subject.register_plugins + end + end + + describe "#setup" do + after { subject } + + it "should register at_exit action" do + expect(Kernel).to receive(:at_exit) + subject + end + + it "should run the setup action" do + expect_any_instance_of(described_class).to receive(:_setup) + end + + it "should only run the setup process once" do + expect_any_instance_of(described_class).to receive(:_setup).once + expect(subject.logger).to receive(:warn) + subject.setup + end + end + + describe "#teardown" do + it "should run the teardown action" do + expect(subject).to receive(:_teardown) + subject.teardown + end + end +end diff --git a/test/unit/vagrant/go_plugin/manager_test.rb b/test/unit/vagrant/go_plugin/manager_test.rb new file mode 100644 index 000000000..fe373c199 --- /dev/null +++ b/test/unit/vagrant/go_plugin/manager_test.rb @@ -0,0 +1,148 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::Manager do + include_context "unit" + + let(:env) do + test_env.vagrantfile("") + test_env.create_vagrant_env + end + + before do + allow(FileUtils).to receive(:mkdir_p) + end + + describe ".instance" do + it "should return instance of manager" do + expect(described_class.instance).to be_a(described_class) + end + + it "should cache instance" do + expect(described_class.instance).to be(described_class.instance) + end + end + + describe ".new" do + it "should create the installation directory" do + expect(FileUtils).to receive(:mkdir_p).with(Vagrant::GoPlugin::INSTALL_DIRECTORY) + subject + end + + it "should create installation temporary directory" do + expect(FileUtils).to receive(:mkdir_p).with(/tmp$/) + subject + end + + it "should generate user state file" do + expect(subject.user_file).to be_a(Vagrant::Plugin::StateFile) + end + end + + describe "#globalize!" do + let(:entries) { [double("entry1"), double("entry2")] } + + before do + allow(File).to receive(:directory?).and_return(false) + allow(Dir).to receive(:glob).and_return(entries) + allow(Vagrant::GoPlugin).to receive_message_chain(:interface, :register_plugins) + end + + context "when entries are not directories" do + before { allow(File).to receive(:directory?).and_return(false) } + + it "should not load any plugins" do + interface = double("interface", register_plugins: nil) + allow(Vagrant::GoPlugin).to receive(:interface).and_return(interface) + expect(interface).not_to receive(:load_plugins) + subject.globalize! + end + end + + context "when entries are directories" do + before { allow(File).to receive(:directory?).and_return(true) } + + it "should load all entries" do + expect(Vagrant::GoPlugin).to receive_message_chain(:interface, :load_plugins).with(entries.first) + expect(Vagrant::GoPlugin).to receive_message_chain(:interface, :load_plugins).with(entries.last) + subject.globalize! + end + end + + it "should register plugins after loading" do + expect(Vagrant::GoPlugin).to receive_message_chain(:interface, :register_plugins) + subject.globalize! + end + end + + describe "#localize!" do + end + + describe "#install_plugin" do + let(:plugin_name) { "test_plugin_name" } + let(:remote_source) { double("remote_source") } + let(:downloader) { double("downloader", download!: nil) } + + before do + allow(FileUtils).to receive(:mkdir_p) + allow(Dir).to receive(:mktmpdir) + allow(Vagrant::Util::Downloader).to receive(:new).and_return(downloader) + allow(Zip::File).to receive(:open) + end + + after { subject.install_plugin(plugin_name, remote_source) } + + it "should create plugin directory for plugin name" do + expect(FileUtils).to receive(:mkdir_p).with(/test_plugin_name$/) + end + + it "should create a temporary directory to download and unpack" do + expect(Dir).to receive(:mktmpdir).with(/go-plugin/, any_args) + end + + it "should download the remote file" do + expect(Dir).to receive(:mktmpdir).with(any_args).and_yield("tmpdir") + expect(downloader).to receive(:download!) + end + + it "should unzip the downloaded file" do + expect(Dir).to receive(:mktmpdir).with(any_args).and_yield("tmpdir") + expect(Zip::File).to receive(:open).with(/plugin.zip/) + end + + it "should add the plugin to the user file" do + expect(subject.user_file).to receive(:add_go_plugin).and_call_original + expect(subject.user_file.has_go_plugin?("test_plugin_name")).to be_truthy + end + end + + describe "#uninstall_plugin" do + let(:plugin_name) { "test_plugin_name" } + + before do + allow(File).to receive(:directory?).and_call_original + allow(FileUtils).to receive(:rm_rf) + end + + after { subject.uninstall_plugin(plugin_name) } + + it "should remove plugin path when installed" do + expect(File).to receive(:directory?).with(/test_plugin_name/).and_return(true) + expect(FileUtils).to receive(:rm_rf).with(/test_plugin_name/) + end + + it "should not remove plugin path when not installed" do + expect(File).to receive(:directory?).with(/test_plugin_name/).and_return(false) + expect(FileUtils).not_to receive(:rm_rf).with(/test_plugin_name/) + end + + it "should have plugin name removed from user file when installed" do + expect(File).to receive(:directory?).with(/test_plugin_name/).and_return(true) + expect(subject.user_file).to receive(:remove_go_plugin).with(plugin_name) + end + + it "should have plugin name removed from user file when not installed" do + expect(File).to receive(:directory?).with(/test_plugin_name/).and_return(false) + expect(subject.user_file).to receive(:remove_go_plugin).with(plugin_name) + end + end +end diff --git a/test/unit/vagrant/go_plugin/provider_plugin_test.rb b/test/unit/vagrant/go_plugin/provider_plugin_test.rb new file mode 100644 index 000000000..f1e4d64dc --- /dev/null +++ b/test/unit/vagrant/go_plugin/provider_plugin_test.rb @@ -0,0 +1,273 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::ProviderPlugin do + let(:client) { double("client") } + + describe Vagrant::GoPlugin::ProviderPlugin::Action do + let(:described_class) { Class.new(Vagrant::GoPlugin::ProviderPlugin::Action) } + + it "should be a GRPCPlugin" do + expect(described_class.ancestors).to include(Vagrant::GoPlugin::GRPCPlugin) + end + + describe ".action_name" do + it "should return nil when unset" do + expect(described_class.action_name).to be_nil + end + + it "should return defined action name when set" do + described_class.action_name = "test_action" + expect(described_class.action_name).to eq("test_action") + end + end + + describe ".action_name=" do + it "should convert action name to string" do + described_class.action_name = :test_action + expect(described_class.action_name).to be_a(String) + end + + it "should set the action name" do + described_class.action_name = "test_action" + expect(described_class.action_name).to eq("test_action") + end + + it "should error if action name is already set" do + described_class.action_name = "test_action" + expect { described_class.action_name = "test_action" }. + to raise_error(ArgumentError) + end + + it "should freeze action name" do + described_class.action_name = "test_action" + expect(described_class.action_name).to be_frozen + end + end + + describe "#call" do + let(:app) { double("app") } + let(:env) { {"key1" => "value1", "key2" => "value2"} } + let(:response_env) { + {"key1" => "value1", "key2" => "value2"} + } + let(:response) { double("response", result: response_env.to_json) } + + let(:subject) { + described_class.plugin_client = client + described_class.new(app, {}) + } + + before do + described_class.action_name = "test_action" + allow(app).to receive(:call) + allow(client).to receive(:run_action).and_return(response) + end + + it "should call the next item in the middleware" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the client" do + expect(client).to receive(:run_action).and_return(response) + subject.call(env) + end + + context "when new data is provided in result" do + let(:response_env) { {"new_data" => "value"} } + + it "should include new data when calling next action" do + expect(app).to receive(:call). + with(hash_including("new_data" => "value", "key1" => "value1")) + subject.call(env) + end + end + end + end + + describe Vagrant::GoPlugin::ProviderPlugin::Provider do + let(:described_class) { + Class.new(Vagrant::GoPlugin::ProviderPlugin::Provider).tap { |c| + c.plugin_client = client + } + } + let(:machine) { double("machine") } + let(:subject) { described_class.new(machine) } + + it "should be a GRPCPlugin" do + expect(described_class.ancestors).to include(Vagrant::GoPlugin::GRPCPlugin) + end + + describe "#name" do + let(:name) { double("name") } + let(:response) { double("response", name: name) } + + before { allow(client).to receive(:name).and_return(response) } + + it "should request name from the client" do + expect(client).to receive(:name).and_return(response) + subject.name + end + + it "should return the plugin name" do + expect(subject.name).to eq(name) + end + + it "should only call the client once" do + expect(client).to receive(:name).once.and_return(response) + subject.name + end + end + + describe "#action" do + let(:action_name) { "test_action" } + let(:response) { double("response", items: actions) } + let(:actions) { ["self::TestAction"] } + + before do + allow(client).to receive(:action).and_return(response) + end + + it "should return a builder instance" do + expect(subject.action(action_name)).to be_a(Vagrant::Action::Builder) + end + + it "should call the plugin client" do + expect(client).to receive(:action). + with(instance_of(Vagrant::Proto::GenericAction)). + and_return(response) + subject.action(action_name) + end + + it "should create a new custom action class" do + builder = subject.action(action_name) + action = builder.stack.first.first + expect(action.ancestors).to include(Vagrant::GoPlugin::ProviderPlugin::Action) + expect(action.action_name).to eq("TestAction") + end + + context "when given non-local action name" do + let(:actions) { ["Vagrant::Action::Builtin::Call"] } + + it "should load the existing action class" do + builder = subject.action(action_name) + action = builder.stack.first.first + expect(action).to eq(Vagrant::Action::Builtin::Call) + end + end + end + + describe "#capability" do + let(:cap_name) { "test_cap" } + let(:response) { double("response", result: result.to_json) } + let(:result) { nil } + + before do + allow(subject).to receive(:name).and_return("dummy_provider") + allow(client).to receive(:provider_capability).and_return(response) + end + + it "should call the plugin client" do + expect(client).to receive(:provider_capability). + with(instance_of(Vagrant::Proto::ProviderCapabilityRequest)) + .and_return(response) + subject.capability(cap_name) + end + + it "should deserialize result" do + expect(subject.capability(cap_name)).to eq(result) + end + + context "when hash value is returned" do + let(:result) { {key: "value"} } + + it "should return indifferent access hash" do + r = subject.capability(cap_name) + expect(r[:key]).to eq(result[:key]) + expect(r).to be_a(Vagrant::Util::HashWithIndifferentAccess) + end + end + + context "when arguments are provided" do + let(:args) { ["arg1", {"key" => "value"}] } + + it "should serialize arguments when sent to plugin client" do + expect(client).to receive(:provider_capability) do |req| + expect(req.arguments).to eq(args.to_json) + response + end + subject.capability(cap_name, *args) + end + end + end + + describe "#is_installed?" do + it "should call plugin client" do + expect(client).to receive(:is_installed).and_return(double("response", result: true)) + expect(subject.is_installed?).to eq(true) + end + end + + describe "#is_usable?" do + it "should call plugin client" do + expect(client).to receive(:is_usable).and_return(double("response", result: true)) + expect(subject.is_usable?).to eq(true) + end + end + + describe "#machine_id_changed" do + it "should call plugin client" do + expect(client).to receive(:machine_id_changed).and_return(double("response", result: true)) + expect(subject.machine_id_changed).to be_nil + end + end + + describe "#ssh_info" do + let(:response) { + Vagrant::Proto::MachineSshInfo.new( + host: "localhost", + port: 2222, + private_key_path: "/key/path", + username: "vagrant" + ) + } + + before { allow(client).to receive(:ssh_info).and_return(response) } + + it "should return hash with indifferent access result" do + expect(subject.ssh_info).to be_a(Vagrant::Util::HashWithIndifferentAccess) + end + + it "should include ssh information" do + result = subject.ssh_info + expect(result[:host]).to eq(response.host) + expect(result[:port]).to eq(response.port) + expect(result[:private_key_path]).to eq(response.private_key_path) + expect(result[:username]).to eq(response.username) + end + end + + describe "#state" do + let(:response) { + Vagrant::Proto::MachineState.new( + id: "machine-id", + short_description: "running", + long_description: "it's really running" + ) + } + + before { allow(client).to receive(:state).and_return(response) } + + it "should return a MachineState instance" do + expect(subject.state).to be_a(Vagrant::MachineState) + end + + it "should set the state attributes" do + result = subject.state + expect(result.id).to eq(response.id) + expect(result.short_description).to eq(response.short_description) + expect(result.long_description).to eq(response.long_description) + end + end + end +end diff --git a/test/unit/vagrant/go_plugin/synced_folder_plugin_test.rb b/test/unit/vagrant/go_plugin/synced_folder_plugin_test.rb new file mode 100644 index 000000000..df43dbb64 --- /dev/null +++ b/test/unit/vagrant/go_plugin/synced_folder_plugin_test.rb @@ -0,0 +1,73 @@ +require_relative "../../base" + +describe Vagrant::GoPlugin::SyncedFolderPlugin::SyncedFolder do + let(:subject) { + Class.new(described_class).tap { |c| + c.plugin_client = client } } + let(:client) { double("client") } + let(:machine) { double("machine", to_json: "{}") } + let(:folders) { double("folders", to_json: "{}") } + let(:options) { double("options", to_json: "{}") } + + it "should be a GRPCPlugin" do + expect(subject).to be_a(Vagrant::GoPlugin::GRPCPlugin) + end + + describe "#cleanup" do + it "should call plugin client" do + expect(client).to receive(:cleanup). + with(instance_of(Vagrant::Proto::SyncedFolders)) + subject.cleanup(machine, options) + end + end + + describe "#disable" do + it "should call plugin client" do + expect(client).to receive(:disable). + with(instance_of(Vagrant::Proto::SyncedFolders)) + subject.disable(machine, folders, options) + end + end + + describe "#enable" do + it "should call plugin client" do + expect(client).to receive(:enable). + with(instance_of(Vagrant::Proto::SyncedFolders)) + subject.enable(machine, folders, options) + end + end + + describe "#prepare" do + it "should call plugin client" do + expect(client).to receive(:prepare). + with(instance_of(Vagrant::Proto::SyncedFolders)) + subject.prepare(machine, folders, options) + end + end + + describe "#usable?" do + let(:response) { Vagrant::Proto::Valid.new(result: true) } + + it "should call the plugin client" do + expect(client).to receive(:is_usable). + with(instance_of(Vagrant::Proto::Machine)). + and_return(response) + expect(subject.usable?).to eq(true) + end + end + + describe "#name" do + let(:response) { Vagrnat::Proto::Identifier.new(name: "dummy") } + + it "should call the plugin client" do + expect(client).to receive(:name).and_return(response) + expect(subject.name).to eq(response.name) + end + + it "should only call the plugin client once" do + expect(client).to receive(:name).once.and_return(response) + expect(subject.name).to eq(response.name) + expect(subject.name).to eq(response.name) + end + end +end diff --git a/test/unit/vagrant/go_plugin_test.rb b/test/unit/vagrant/go_plugin_test.rb new file mode 100644 index 000000000..1d0fdbecb --- /dev/null +++ b/test/unit/vagrant/go_plugin_test.rb @@ -0,0 +1,29 @@ +require_relative "../base" + +describe Vagrant::GoPlugin do + describe "INSTALL_DIRECTORY constant" do + let(:subject) { described_class.const_get(:INSTALL_DIRECTORY) } + + it "should be a String" do + expect(subject).to be_a(String) + end + + it "should be frozen" do + expect(subject).to be_frozen + end + + it "should be within the user data path" do + expect(subject).to start_with(Vagrant.user_data_path.to_s) + end + end + + describe ".interface" do + it "should return an interface instance" do + expect(described_class.interface).to be_a(Vagrant::GoPlugin::Interface) + end + + it "should cache the interface instance" do + expect(described_class.interface).to be(described_class.interface) + end + end +end diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index 9546db7bc..14897c366 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -118,4 +118,56 @@ describe Vagrant::Plugin::StateFile do to raise_error(Vagrant::Errors::PluginStateFileParseError) end end + + context "go plugin usage" do + describe "#add_go_plugin" do + it "should add plugin to list of installed go plugins" do + subject.add_go_plugin("foo", source: "http://localhost/foo.zip") + expect(subject.installed_go_plugins).to include("foo") + end + + it "should update source when added again" do + subject.add_go_plugin("foo", source: "http://localhost/foo.zip") + expect(subject.installed_go_plugins["foo"]["source"]).to eq("http://localhost/foo.zip") + subject.add_go_plugin("foo", source: "http://localhost/foo1.zip") + expect(subject.installed_go_plugins["foo"]["source"]).to eq("http://localhost/foo1.zip") + end + end + + describe "#remove_go_plugin" do + before do + subject.add_go_plugin("foo", source: "http://localhost/foo.zip") + end + + it "should remove the installed plugin" do + subject.remove_go_plugin("foo") + expect(subject.installed_go_plugins).not_to include("foo") + end + + it "should remove plugin not installed" do + subject.remove_go_plugin("foo") + expect(subject.installed_go_plugins).not_to include("foo") + subject.remove_go_plugin("foo") + expect(subject.installed_go_plugins).not_to include("foo") + end + end + + describe "#has_go_plugin?" do + before do + subject.add_go_plugin("foo", source: "http://localhost/foo.zip") + end + + it "should return true when plugin is installed" do + expect(subject.has_go_plugin?("foo")).to be_truthy + end + + it "should return false when plugin is not installed" do + expect(subject.has_go_plugin?("fee")).to be_falsey + end + + it "should allow symbol names" do + expect(subject.has_go_plugin?(:foo)).to be_truthy + end + end + end end diff --git a/vagrant.gemspec b/vagrant.gemspec index 885c0d359..429d293dc 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -13,6 +13,7 @@ Gem::Specification.new do |s| s.description = "Vagrant is a tool for building and distributing virtualized development environments." s.required_ruby_version = "~> 2.2", "< 2.7" + s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "vagrant" @@ -22,6 +23,8 @@ Gem::Specification.new do |s| s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "i18n", "~> 1.1.1" s.add_dependency "listen", "~> 3.1.5" + s.add_dependency "grpc", "~> 1.19.0" + s.add_dependency "grpc-tools", "~> 1.19.0" s.add_dependency "hashicorp-checkpoint", "~> 0.1.5" s.add_dependency "log4r", "~> 1.1.9", "< 1.1.11" s.add_dependency "net-ssh", "~> 5.1.0" @@ -49,6 +52,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rspec-its", "~> 1.2.0" s.add_development_dependency "webmock", "~> 2.3.1" s.add_development_dependency "fake_ftp", "~> 0.1.1" + s.add_development_dependency "rake-compiler" # The following block of code determines the files that should be included # in the gem. It does this by reading all the files in the directory where diff --git a/website/source/docs/experimental/index.html.md b/website/source/docs/experimental/index.html.md index 5de2365d7..17da3ee6f 100644 --- a/website/source/docs/experimental/index.html.md +++ b/website/source/docs/experimental/index.html.md @@ -54,3 +54,7 @@ Enabling this feature allows all provisioners to specify `before` and `after` options. These options allow provisioners to be configured to run before or after any given "root" provisioner. more information about these options can be found on the [base provisioner documentation page](/docs/provisioning/basic_usage.html) + +### `go_plugin` + +Enabling this feature turns on support for [go-plugin](https://github.com/hashicorp/go-plugin) based plugin for Vagrant.