376 lines
8.4 KiB
Go
376 lines
8.4 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/go-plugin"
|
|
|
|
"github.com/hashicorp/vagrant-plugin-sdk/datadir"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
|
configpkg "github.com/hashicorp/vagrant/internal/config"
|
|
"github.com/hashicorp/vagrant/internal/runner"
|
|
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
|
"github.com/hashicorp/vagrant/internal/serverclient"
|
|
)
|
|
|
|
type Basis struct {
|
|
ui terminal.UI
|
|
|
|
basis *vagrant_server.Basis
|
|
Project *Project
|
|
|
|
client *serverclient.VagrantClient
|
|
vagrantRubyRuntime plugin.ClientProtocol
|
|
logger hclog.Logger
|
|
runner *vagrant_server.Ref_Runner
|
|
cleanupFuncs []func()
|
|
|
|
config *configpkg.Config
|
|
|
|
labels map[string]string
|
|
dataSourceOverrides map[string]string
|
|
|
|
datadir *datadir.Basis
|
|
local bool
|
|
localServer bool // True when a local server is created
|
|
localRunner *runner.Runner
|
|
}
|
|
|
|
func New(ctx context.Context, opts ...Option) (basis *Basis, err error) {
|
|
basis = &Basis{
|
|
logger: hclog.L(),
|
|
runner: &vagrant_server.Ref_Runner{
|
|
Target: &vagrant_server.Ref_Runner_Any{
|
|
Any: &vagrant_server.Ref_RunnerAny{},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Apply any options
|
|
var cfg config
|
|
for _, opt := range opts {
|
|
err := opt(basis, &cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
basis.logger = basis.logger.Named("basis")
|
|
|
|
// If no internal basis was provided, set it up now
|
|
if basis.basis == nil {
|
|
dir, err := datadir.NewBasis("default")
|
|
if err != nil {
|
|
basis.logger.Error("failed to determine vagrant home",
|
|
"error", err)
|
|
|
|
return nil, err
|
|
}
|
|
basis.basis = &vagrant_server.Basis{
|
|
Name: "default",
|
|
Path: dir.ConfigDir().String(),
|
|
}
|
|
}
|
|
|
|
// If no UI was provided, create a default
|
|
if basis.ui == nil {
|
|
basis.ui = terminal.ConsoleUI(ctx)
|
|
}
|
|
|
|
// If a client was not provided, establish a new connection through
|
|
// the serverclient package, or by spinning up an in-process server
|
|
if basis.client == nil {
|
|
basis.logger.Trace("no API client provided, initializing connection if possible")
|
|
conn, err := basis.initServerClient(context.Background(), &cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
basis.client = serverclient.WrapVagrantClient(conn)
|
|
}
|
|
|
|
// If the ruby runtime isn't provided, set it up
|
|
if basis.vagrantRubyRuntime == nil {
|
|
if basis.vagrantRubyRuntime, err = basis.initVagrantRubyRuntime(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Negotiate the version
|
|
if err := basis.negotiateApiVersion(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Setup our basis within the database
|
|
result, err := basis.client.FindBasis(
|
|
context.Background(),
|
|
&vagrant_server.FindBasisRequest{
|
|
Basis: basis.basis,
|
|
},
|
|
)
|
|
if err == nil && result.Found {
|
|
basis.basis = result.Basis
|
|
if err = basis.LoadVagrantfiles(); err != nil {
|
|
return nil, err
|
|
}
|
|
return basis, nil
|
|
}
|
|
|
|
basis.logger.Trace("failed to locate existing basis", "basis", basis.basis,
|
|
"result", result, "error", err)
|
|
|
|
uresult, err := basis.client.UpsertBasis(
|
|
context.Background(),
|
|
&vagrant_server.UpsertBasisRequest{
|
|
Basis: basis.basis,
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
basis.basis = uresult.Basis
|
|
|
|
// Find and load Vagrantfiles for the basis
|
|
if err = basis.LoadVagrantfiles(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
basis.datadir, err = datadir.NewBasis(basis.basis.Name)
|
|
|
|
return basis, err
|
|
}
|
|
|
|
func (b *Basis) LoadProject(p *vagrant_server.Project) (*Project, error) {
|
|
result, err := b.client.FindProject(
|
|
context.Background(),
|
|
&vagrant_server.FindProjectRequest{
|
|
Project: p,
|
|
},
|
|
)
|
|
// d, err := b.datadir.Project(result.Project.Name)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
|
|
if err == nil && result.Found {
|
|
b.Project = &Project{
|
|
ui: b.ui,
|
|
basis: b,
|
|
project: result.Project,
|
|
logger: b.logger.Named("project"),
|
|
// datadir: d,
|
|
}
|
|
if err = b.Project.LoadVagrantfiles(); err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Project, nil
|
|
}
|
|
|
|
b.logger.Trace("failed to locate existing project", "project", p,
|
|
"result", result, "error", err)
|
|
|
|
uresult, err := b.client.UpsertProject(
|
|
context.Background(),
|
|
&vagrant_server.UpsertProjectRequest{
|
|
Project: p,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.Project = &Project{
|
|
ui: b.ui,
|
|
project: uresult.Project,
|
|
basis: b,
|
|
logger: b.logger.Named("project"),
|
|
//datadir: d,
|
|
}
|
|
|
|
if err = b.Project.LoadVagrantfiles(); err != nil {
|
|
return nil, err
|
|
}
|
|
return b.Project, nil
|
|
}
|
|
|
|
// Finds the Vagrantfile associated with the basis
|
|
func (b *Basis) LoadVagrantfiles() error {
|
|
vagrantfilePath, err := configpkg.FindPath(b.basis.Path, configpkg.GetVagrantfileName())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If the path does not exist, no Vagrantfile was found
|
|
if _, err := os.Stat(vagrantfilePath); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
raw, err := b.vagrantRubyRuntime.Dispense("vagrantrubyruntime")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rvc, ok := raw.(serverclient.RubyVagrantClient)
|
|
if !ok {
|
|
return fmt.Errorf("Couldn't attach to Ruby runtime")
|
|
}
|
|
vagrantfile, err := rvc.ParseVagrantfile(vagrantfilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.basis.Configuration = vagrantfile
|
|
// Push Vagrantfile updates to basis
|
|
b.client.UpsertBasis(
|
|
context.Background(),
|
|
&vagrant_server.UpsertBasisRequest{
|
|
Basis: b.basis,
|
|
},
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func (b *Basis) Ref() *vagrant_plugin_sdk.Ref_Basis {
|
|
return &vagrant_plugin_sdk.Ref_Basis{
|
|
Name: b.basis.Name,
|
|
ResourceId: b.basis.ResourceId,
|
|
}
|
|
}
|
|
|
|
func (b *Basis) Close() error {
|
|
for _, f := range b.cleanupFuncs {
|
|
f()
|
|
}
|
|
|
|
if closer, ok := b.ui.(io.Closer); ok {
|
|
closer.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Client returns the raw Vagrant server API client.
|
|
func (b *Basis) Client() *serverclient.VagrantClient {
|
|
return b.client
|
|
}
|
|
|
|
func (b *Basis) VagrantRubyRuntime() plugin.ClientProtocol {
|
|
return b.vagrantRubyRuntime
|
|
}
|
|
|
|
// Local is true if the server is an in-process just-in-time server.
|
|
func (b *Basis) Local() bool {
|
|
return b.localServer
|
|
}
|
|
|
|
func (b *Basis) UI() terminal.UI {
|
|
return b.ui
|
|
}
|
|
|
|
func (b *Basis) cleanup(f func()) {
|
|
b.cleanupFuncs = append(b.cleanupFuncs, f)
|
|
}
|
|
|
|
type config struct {
|
|
connectOpts []serverclient.ConnectOption
|
|
}
|
|
|
|
type Option func(*Basis, *config) error
|
|
|
|
func WithBasis(pbb *vagrant_server.Basis) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.basis = pbb
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func WithProject(p *Project) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
p.basis = b
|
|
b.Project = p
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithClient sets the client directly. In this case, the runner won't
|
|
// attempt any connection at all regardless of other configuration (env
|
|
// vars or vagrant config file). This will be used.
|
|
func WithClient(client *serverclient.VagrantClient) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
if client != nil {
|
|
b.client = client
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithClientConnect specifies the options for connecting to a client.
|
|
// If WithClient is specified, that client is always used.
|
|
//
|
|
// If WithLocal is set and no client is specified and no server creds
|
|
// can be found, then an in-process server will be created.
|
|
func WithClientConnect(opts ...serverclient.ConnectOption) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
cfg.connectOpts = opts
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithLocal puts the client in local exec mode. In this mode, the client
|
|
// will spin up a per-operation runner locally and reference the local on-disk
|
|
// data for all operations.
|
|
func WithLocal() Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.local = true
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithLogger sets the logger for the client.
|
|
func WithLogger(log hclog.Logger) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.logger = log
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithUI sets the UI to use for the client.
|
|
func WithUI(ui terminal.UI) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.ui = ui
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func WithCleanup(f func()) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.cleanup(f)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithSourceOverrides sets the data source overrides for queued jobs.
|
|
func WithSourceOverrides(m map[string]string) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.dataSourceOverrides = m
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithLabels sets the labels or any operations.
|
|
func WithLabels(m map[string]string) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.labels = m
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func WithConfig(c *configpkg.Config) Option {
|
|
return func(b *Basis, cfg *config) error {
|
|
b.config = c
|
|
return nil
|
|
}
|
|
}
|