Add release-grade logic for finding legacy Vagrant
After lots of experimentation I have landed on this as my proposal for how we have our Go binary find its Ruby counterpart: just have it grab it from the $PATH! @evanphx showed me this neat trick where by borrowing a couple of helper methods from `exec` and tweaking them we can get logic that will do a $PATH lookup that excludes "ourself". This allows us to have both `vagrant` executables on the path... and means that switching between Gogo-by-default or Legacy-by-default is just a matter of tweaking $PATH order. It _also_ means that we don't need any different lookup logic for "release mode" vs "development mode" which is what I was looking at before this solution. In order to continue to facilitate development, I've generated a binstub for vagrant using `bundle binstubs vagrant --standalone --path ./binstubs`, and I've updated the Nix development setup to prepend this directory to the $PATH. NOTE: Non-Nix users will need to modify their $PATH in the same way to get the same behavior in development.
This commit is contained in:
parent
73a1be95fe
commit
4c21cb6ae5
14
binstubs/vagrant
Executable file
14
binstubs/vagrant
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'vagrant' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "pathname"
|
||||
path = Pathname.new(__FILE__)
|
||||
$:.unshift File.expand_path "../..", path.realpath
|
||||
|
||||
require "bundler/setup"
|
||||
load File.expand_path "../../bin/vagrant", path.realpath
|
||||
@ -2,11 +2,12 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
@ -180,22 +181,20 @@ func (c *Client) initLocalServer(ctx context.Context) (_ *grpc.ClientConn, err e
|
||||
return client.Conn(), nil
|
||||
}
|
||||
|
||||
// initVagrantRubyRuntime launches legacy vagrant as a gRPC server using the
|
||||
// "serve" command.
|
||||
//
|
||||
// NOTE: We are assuming that the first executable we find in $PATH that is not
|
||||
// _us_ is the legacy vagrant executable. It's up the packaging to ensure
|
||||
// that is how things are set up.
|
||||
func (c *Client) initVagrantRubyRuntime() (rubyRuntime plugin.ClientProtocol, err error) {
|
||||
// TODO: Update for actual release usage. This is dev only now.
|
||||
_, this_dir, _, _ := runtime.Caller(0)
|
||||
cmd := exec.Command(
|
||||
"bundle", "exec", "vagrant", "serve",
|
||||
)
|
||||
level := os.Getenv("VAGRANT_LOG")
|
||||
cmd.Env = []string{
|
||||
"BUNDLE_GEMFILE=" + filepath.Join(this_dir, "../../..", "Gemfile"),
|
||||
"VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET=true",
|
||||
"VAGRANT_LOG=" + level,
|
||||
"VAGRANT_LOG_FILE=/tmp/vagrant.log",
|
||||
var vagrantPath string
|
||||
vagrantPath, err = lookPathSkippingSelf("vagrant")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config := serverclient.RubyVagrantPluginConfig(c.logger)
|
||||
config.Cmd = cmd
|
||||
config.Cmd = exec.Command(vagrantPath, "serve")
|
||||
rc := plugin.NewClient(config)
|
||||
if _, err = rc.Start(); err != nil {
|
||||
return
|
||||
@ -235,3 +234,51 @@ func (c *Client) negotiateApiVersion(ctx context.Context) error {
|
||||
c.logger.Info("negotiated api version", "version", vsn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookPathSkippingSelf is a copy of exec.LookPath modified to skip the
|
||||
// currently running executable.
|
||||
func lookPathSkippingSelf(file string) (string, error) {
|
||||
myselfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
myself, err := os.Stat(myselfPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file, myself)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
return "", &exec.Error{Name: file, Err: err}
|
||||
}
|
||||
path := os.Getenv("PATH")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
if dir == "" {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
dir = "."
|
||||
}
|
||||
path := filepath.Join(dir, file)
|
||||
if err := findExecutable(path, myself); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", &exec.Error{Name: file, Err: exec.ErrNotFound}
|
||||
}
|
||||
|
||||
// findExecutableSkippingSelf is a copy of exec.findExecutable modified to skip
|
||||
// the provided FileInfo. It's used to power lookPathSkippingSelf.
|
||||
func findExecutable(file string, skip os.FileInfo) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if os.SameFile(d, skip) {
|
||||
return fs.ErrPermission
|
||||
}
|
||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return fs.ErrPermission
|
||||
}
|
||||
|
||||
@ -61,9 +61,13 @@ mkShell rec {
|
||||
zlib
|
||||
];
|
||||
|
||||
# workaround for npm/gulp dep compilation
|
||||
# https://github.com/imagemin/optipng-bin/issues/108
|
||||
shellHook = ''
|
||||
# workaround for npm/gulp dep compilation
|
||||
# https://github.com/imagemin/optipng-bin/issues/108
|
||||
LD=$CC
|
||||
|
||||
# Prepend binstubs to PATH for development, which causes Vagrant-agogo
|
||||
# to use the legacy Vagrant in this repo. See client.initVagrantRubyRuntime
|
||||
PATH=$PWD/binstubs:$PATH
|
||||
'';
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user