Refactor how scopes are created and initialized
This commit is contained in:
parent
b82d55cc37
commit
51d8c84740
@ -12,8 +12,9 @@ import (
|
||||
"github.com/hashicorp/go-argmapper"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
goplugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
@ -41,47 +42,40 @@ import (
|
||||
// finished with the basis to properly clean
|
||||
// up any open resources.
|
||||
type Basis struct {
|
||||
basis *vagrant_server.Basis
|
||||
boxCollection *BoxCollection
|
||||
cache cacher.Cache
|
||||
cleaner cleanup.Cleanup
|
||||
corePlugins *CoreManager
|
||||
ctx context.Context
|
||||
client *serverclient.VagrantClient
|
||||
dir *datadir.Basis
|
||||
factory *Factory
|
||||
index *TargetIndex
|
||||
jobInfo *component.JobInfo
|
||||
logger hclog.Logger
|
||||
m sync.Mutex
|
||||
mappers []*argmapper.Func
|
||||
plugins *plugin.Manager
|
||||
projects map[string]*Project
|
||||
seedValues *core.Seeds
|
||||
statebag core.StateBag
|
||||
ui terminal.UI
|
||||
vagrantfile *Vagrantfile
|
||||
}
|
||||
basis *vagrant_server.Basis // stored basis data
|
||||
boxCollection *BoxCollection // box collection for this basis
|
||||
cache cacher.Cache // local basis cache
|
||||
cleaner cleanup.Cleanup // cleanup tasks to be run on close
|
||||
client *serverclient.VagrantClient // client to vagrant server
|
||||
corePlugins *CoreManager // manager for the core plugin types
|
||||
ctx context.Context // local context
|
||||
dir *datadir.Basis // data directory for basis
|
||||
factory *Factory // scope factory
|
||||
index *TargetIndex // index of targets within basis
|
||||
jobInfo *component.JobInfo // jobInfo is the base job info for executed functions
|
||||
logger hclog.Logger // basis specific logger
|
||||
mappers []*argmapper.Func // mappers for basis
|
||||
plugins *plugin.Manager // basis scoped plugin manager
|
||||
ready bool // flag that instance is ready
|
||||
seedValues *core.Seeds // seed values to be applied when running commands
|
||||
statebag core.StateBag // statebag to persist values
|
||||
ui terminal.UI // basis UI (non-prefixed)
|
||||
vagrantfile *Vagrantfile // vagrantfile instance for basis
|
||||
|
||||
// Cache implements originScope
|
||||
func (b *Basis) Cache() cacher.Cache {
|
||||
return b.cache
|
||||
}
|
||||
|
||||
// Broker implements originScope
|
||||
func (b *Basis) Broker() *goplugin.GRPCBroker {
|
||||
return b.plugins.LegacyBroker()
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// NewBasis creates a new Basis with the given options.
|
||||
func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
|
||||
b = &Basis{
|
||||
func NewBasis(ctx context.Context, opts ...BasisOption) (*Basis, error) {
|
||||
var err error
|
||||
b := &Basis{
|
||||
basis: &vagrant_server.Basis{},
|
||||
cache: cacher.New(),
|
||||
cleaner: cleanup.New(),
|
||||
ctx: ctx,
|
||||
logger: hclog.L(),
|
||||
mappers: []*argmapper.Func{},
|
||||
jobInfo: &component.JobInfo{},
|
||||
projects: map[string]*Project{},
|
||||
seedValues: core.NewSeeds(),
|
||||
statebag: NewStateBag(),
|
||||
}
|
||||
@ -93,33 +87,80 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b.logger.IsTrace() {
|
||||
b.logger = b.logger.Named("basis")
|
||||
} else {
|
||||
b.logger = b.logger.ResetNamed("vagrant.core.basis")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Create the manager for handling core plugins
|
||||
b.corePlugins = NewCoreManager(ctx, b.logger)
|
||||
func (b *Basis) Init() error {
|
||||
var err error
|
||||
|
||||
if b.basis == nil {
|
||||
return nil, fmt.Errorf("basis data was not properly loaded")
|
||||
// If ready then Init was already run
|
||||
if b.ready {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Client is required to be provided
|
||||
if b.client == nil {
|
||||
return nil, fmt.Errorf("client was not provided to basis")
|
||||
return fmt.Errorf("vagrant server client was not provided to basis")
|
||||
}
|
||||
|
||||
// If we don't have a data directory set, lets do that now
|
||||
// TODO(spox): actually do that
|
||||
if b.dir == nil {
|
||||
return nil, fmt.Errorf("WithDataDir must be specified")
|
||||
// If no plugin manager was provided, force an error
|
||||
if b.plugins == nil {
|
||||
return fmt.Errorf("plugin manager was not provided to basis")
|
||||
}
|
||||
|
||||
// Update our plugin manager to be a sub manager so we close
|
||||
// it early if needed
|
||||
b.plugins = b.plugins.Sub("basis")
|
||||
|
||||
// Configure our logger
|
||||
b.logger = b.logger.Named("basis")
|
||||
b.logger = b.logger.With("basis", b)
|
||||
|
||||
// Attempt to reload the basis to populate our
|
||||
// data. If the basis is not found, create it.
|
||||
err = b.Reload()
|
||||
if err != nil {
|
||||
stat, ok := status.FromError(err)
|
||||
if !ok || stat.Code() != codes.NotFound {
|
||||
return err
|
||||
}
|
||||
// Project doesn't exist so save it to persist
|
||||
if err = b.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If the basis directory is unset, set it
|
||||
if b.dir == nil {
|
||||
if b.dir, err = datadir.NewBasis(b.basis.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If the mappers aren't already set, load known mappers
|
||||
if len(b.mappers) == 0 {
|
||||
b.mappers, err = argmapper.NewFuncList(protomappers.All,
|
||||
argmapper.Logger(dynamic.Logger),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
locals, err := argmapper.NewFuncList(Mappers, argmapper.Logger(dynamic.Logger))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.mappers = append(b.mappers, locals...)
|
||||
}
|
||||
|
||||
// Create the manager for handling core plugins
|
||||
b.corePlugins = NewCoreManager(b.ctx, b.logger)
|
||||
|
||||
// Setup our index
|
||||
b.index = &TargetIndex{
|
||||
ctx: b.ctx,
|
||||
@ -130,37 +171,86 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
|
||||
|
||||
// If no UI was provided, initialize a console UI
|
||||
if b.ui == nil {
|
||||
b.ui = terminal.ConsoleUI(ctx)
|
||||
b.ui = terminal.ConsoleUI(b.ctx)
|
||||
}
|
||||
|
||||
// Create our vagrantfile
|
||||
b.vagrantfile = NewVagrantfile(b, b.plugins.RubyClient(), b.mappers, b.logger)
|
||||
b.vagrantfile = NewVagrantfile(b.factory, b.boxCollection, b.mappers, b.logger)
|
||||
|
||||
// Register the basis as a Vagrantfile source
|
||||
b.vagrantfile.Source(b.basis.Configuration, VAGRANTFILE_BASIS)
|
||||
// If the mappers aren't already set, load known mappers
|
||||
if len(b.mappers) == 0 {
|
||||
b.mappers, err = argmapper.NewFuncList(protomappers.All,
|
||||
argmapper.Logger(dynamic.Logger),
|
||||
)
|
||||
|
||||
// Register configuration plugins when they are loaded
|
||||
b.plugins.Initializer(b.configRegistration)
|
||||
|
||||
// Register any configuration plugins already loaded
|
||||
cfgs, err := b.plugins.Typed(component.ConfigType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cp := range cfgs {
|
||||
b.logger.Trace("registering existing config plugin",
|
||||
"name", cp,
|
||||
)
|
||||
p, err := b.plugins.Get(cp, component.ConfigType)
|
||||
if err != nil {
|
||||
return
|
||||
b.logger.Error("failed to get requested plugin",
|
||||
"name", cp,
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
if err = b.configRegistration(p, b.logger); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure any modifications to the basis are persisted
|
||||
// Configure plugins with cache instance
|
||||
b.plugins.Configure(b.setPluginCache)
|
||||
|
||||
// Configure plugins to have seeds set
|
||||
b.plugins.Configure(b.setPluginSeeds)
|
||||
|
||||
// If we have legacy vagrant loaded, configure managers
|
||||
if b.plugins.LegacyEnabled() {
|
||||
// Configure plugins to have plugin manager set (used by legacy)
|
||||
b.plugins.Configure(b.setPluginManager)
|
||||
|
||||
// Configure plugins to have a core plugin manager set (used by legacy)
|
||||
b.plugins.Configure(b.setPluginCoreManager)
|
||||
}
|
||||
|
||||
// Load any plugins that may be available
|
||||
if err = b.plugins.Discover(b.dir.ConfigDir().Join("plugins")); err != nil {
|
||||
b.logger.Error("basis setup failed during plugin discovery",
|
||||
"directory", b.dir.ConfigDir().Join("plugins"),
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Set seeds for any plugins that may be used
|
||||
b.seed(nil)
|
||||
|
||||
// Initialize the Vagrantfile for the basis
|
||||
if err = b.vagrantfile.Init(); err != nil {
|
||||
b.logger.Error("basis setup failed to initialize vagrantfile",
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Store our configuration
|
||||
sv, err := b.vagrantfile.GetSource(VAGRANTFILE_BASIS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.basis.Configuration = sv
|
||||
|
||||
// Close the plugin manager
|
||||
b.Closer(func() error {
|
||||
// Update our configuration before we save
|
||||
v, err := b.vagrantfile.GetSource(VAGRANTFILE_BASIS)
|
||||
if err != nil {
|
||||
b.logger.Debug("failed to retrieve vagrantfile configuration",
|
||||
"reason", err,
|
||||
)
|
||||
} else {
|
||||
b.basis.Configuration = v
|
||||
}
|
||||
return b.Save()
|
||||
return b.plugins.Close()
|
||||
})
|
||||
|
||||
// Close the core manager
|
||||
@ -178,215 +268,31 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
|
||||
return b.index.Close()
|
||||
})
|
||||
|
||||
// Add in local mappers
|
||||
for _, fn := range Mappers {
|
||||
f, err := argmapper.NewFunc(fn,
|
||||
argmapper.Logger(dynamic.Logger),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.mappers = append(b.mappers, f)
|
||||
}
|
||||
// Save ourself when closed
|
||||
b.Closer(func() error {
|
||||
return b.Save()
|
||||
})
|
||||
|
||||
// If no plugin manager was provided, force an error
|
||||
if b.plugins == nil {
|
||||
return nil, fmt.Errorf("no plugin manager provided")
|
||||
}
|
||||
|
||||
// Register configuration plugins when they are loaded
|
||||
regFn := func(p *plugin.Plugin, l hclog.Logger) error {
|
||||
if !p.HasType(component.ConfigType) {
|
||||
b.logger.Warn("plugin does not implement config component type",
|
||||
"name", p.Name,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
b.logger.Debug("registering configuration component",
|
||||
"name", p.Name,
|
||||
)
|
||||
|
||||
i, err := p.Manager().Find(p.Name, component.ConfigType)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to load configuration component",
|
||||
"name", p.Name,
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
c, ok := i.Component.(core.Config)
|
||||
if !ok {
|
||||
return fmt.Errorf("component instance is not valid config: %s", p.Name)
|
||||
}
|
||||
info, err := c.Register()
|
||||
if err != nil {
|
||||
b.logger.Error("failed to get registration information from plugin",
|
||||
"name", p.Name,
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
b.logger.Info("registering configuration component",
|
||||
"plugin", p.Name,
|
||||
"info", *info,
|
||||
)
|
||||
return b.vagrantfile.Register(info, p)
|
||||
}
|
||||
b.plugins.Initializer(regFn)
|
||||
|
||||
// Register any configuration plugins already loaded
|
||||
cfgs, err := b.plugins.Typed(component.ConfigType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, cp := range cfgs {
|
||||
b.logger.Trace("registering existing config plugin",
|
||||
"name", cp,
|
||||
)
|
||||
p, err := b.plugins.Get(cp, component.ConfigType)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to get requested plugin",
|
||||
"name", cp,
|
||||
"error", err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
if err = regFn(p, b.logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Configure plugins with cache instance
|
||||
b.plugins.Configure(
|
||||
func(i *plugin.Instance, l hclog.Logger) error {
|
||||
if c, ok := i.Component.(interface {
|
||||
SetCache(cacher.Cache)
|
||||
}); ok {
|
||||
b.logger.Trace("setting cache on plugin instance",
|
||||
"name", i.Name,
|
||||
"component", hclog.Fmt("%T", i.Component),
|
||||
)
|
||||
c.SetCache(b.cache)
|
||||
} else {
|
||||
b.logger.Warn("cannot set cache on plugin instance",
|
||||
"name", i.Name,
|
||||
"component", hclog.Fmt("%T", i.Component),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
// Configure plugins to have seeds set
|
||||
b.plugins.Configure(
|
||||
func(i *plugin.Instance, l hclog.Logger) error {
|
||||
if s, ok := i.Component.(core.Seeder); ok {
|
||||
if err := s.Seed(b.seedValues); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
// If we have legacy vagrant loaded, configure managers
|
||||
if b.plugins.LegacyEnabled() {
|
||||
// Configure plugins to have plugin manager set (used by legacy)
|
||||
b.plugins.Configure(
|
||||
func(i *plugin.Instance, l hclog.Logger) error {
|
||||
s, ok := i.Component.(plugin.HasPluginMetadata)
|
||||
if !ok {
|
||||
l.Warn("plugin does not support metadata, cannot assign plugin manager",
|
||||
"component", i.Type.String(),
|
||||
"name", i.Name,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
srv, err := b.plugins.Servinfo()
|
||||
if err != nil {
|
||||
l.Warn("failed to get plugin manager information",
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
s.SetRequestMetadata("plugin_manager", string(srv))
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
// Configure plugins to have a core plugin manager set (used by legacy)
|
||||
b.plugins.Configure(
|
||||
func(i *plugin.Instance, l hclog.Logger) error {
|
||||
s, ok := i.Component.(plugin.HasPluginMetadata)
|
||||
if !ok {
|
||||
l.Warn("plugin does not support metadata, cannot assign plugin manager",
|
||||
"component", i.Type.String(),
|
||||
"name", i.Name,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
srv, err := b.corePlugins.Servinfo(b.plugins.LegacyBroker())
|
||||
if err != nil {
|
||||
l.Warn("failed to get plugin manager information",
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
s.SetRequestMetadata("core_plugin_manager", string(srv))
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if err = b.plugins.Discover(b.dir.ConfigDir().Join("plugins")); err != nil {
|
||||
b.logger.Error("basis setup failed during plugin discovery",
|
||||
"directory", b.dir.ConfigDir().Join("plugins").String(),
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set seeds for any plugins that may be used
|
||||
b.seed(nil)
|
||||
|
||||
// Initialize the Vagrantfile for the basis
|
||||
if err = b.vagrantfile.Init(); err != nil {
|
||||
b.logger.Error("basis setup failed to initialize vagrantfile",
|
||||
"error", err,
|
||||
)
|
||||
return
|
||||
}
|
||||
// Mark basis as being initialized
|
||||
b.ready = true
|
||||
|
||||
b.logger.Info("basis initialized")
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Basis) LoadTarget(topts ...TargetOption) (t *Target, err error) {
|
||||
return nil, fmt.Errorf("targets cannot be loaded from a basis")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide nice output in logger
|
||||
func (b *Basis) String() string {
|
||||
return fmt.Sprintf("core.Basis:[name: %s resource_id: %s address: %p]",
|
||||
b.basis.Name, b.basis.ResourceId, b)
|
||||
}
|
||||
|
||||
// Config implements core.Basis
|
||||
func (b *Basis) Config() (core.Vagrantfile, error) {
|
||||
return b.vagrantfile, nil
|
||||
}
|
||||
|
||||
// CWD implements core.Basis
|
||||
func (p *Basis) CWD() (path path.Path, err error) {
|
||||
return paths.VagrantCwd()
|
||||
}
|
||||
@ -654,7 +560,7 @@ func (b *Basis) Host() (host core.Host, err error) {
|
||||
// Initializes the basis for running a command. This will inspect
|
||||
// all registered components and extract things like custom command
|
||||
// information before an actual command is run
|
||||
func (b *Basis) Init() (result *vagrant_server.Job_InitResult, err error) {
|
||||
func (b *Basis) RunInit() (result *vagrant_server.Job_InitResult, err error) {
|
||||
b.logger.Debug("running init for basis")
|
||||
result = &vagrant_server.Job_InitResult{
|
||||
Commands: []*vagrant_plugin_sdk.Command_CommandInfo{},
|
||||
@ -684,133 +590,6 @@ func (b *Basis) Init() (result *vagrant_server.Job_InitResult, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Looks up a project which has already been loaded and is cached
|
||||
// by the project's name or resource ID. Will return nil if the
|
||||
// project is not cached.
|
||||
//
|
||||
// NOTE: Generally the `LoadProject` function will be preferred
|
||||
// as it will return the cached value if previously loaded
|
||||
// or load the project if not found.
|
||||
func (b *Basis) Project(nameOrId string) *Project {
|
||||
if p, ok := b.projects[nameOrId]; ok {
|
||||
return p
|
||||
}
|
||||
for _, p := range b.projects {
|
||||
if p.project.ResourceId == nameOrId {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load a project within the current basis. If the project is not found, it
|
||||
// will be created.
|
||||
func (b *Basis) LoadProject(popts ...ProjectOption) (p *Project, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
// Create our project
|
||||
p = &Project{
|
||||
ctx: b.ctx,
|
||||
cleanup: cleanup.New(),
|
||||
basis: b,
|
||||
logger: b.logger,
|
||||
mappers: b.mappers,
|
||||
targets: map[string]*Target{},
|
||||
ui: b.ui,
|
||||
}
|
||||
|
||||
// Apply any options provided
|
||||
for _, opt := range popts {
|
||||
if oerr := opt(p); oerr != nil {
|
||||
err = multierror.Append(err, oerr)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If we already have this project setup, use it instead
|
||||
if project := b.Project(p.project.ResourceId); project != nil {
|
||||
return project, nil
|
||||
}
|
||||
|
||||
if p.logger.IsTrace() {
|
||||
p.logger = p.logger.Named("project")
|
||||
} else {
|
||||
p.logger = p.logger.ResetNamed("vagrant.core.project")
|
||||
}
|
||||
|
||||
// Ensure project directory is set
|
||||
if p.dir == nil {
|
||||
if p.dir, err = b.dir.Project(p.project.Name); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Load any plugins that may be installed locally to the project
|
||||
if err = b.plugins.Discover(path.NewPath(p.project.Path).Join(".vagrant").Join("plugins")); err != nil {
|
||||
b.logger.Error("project setup failed during plugin discovery",
|
||||
"directory", path.NewPath(p.project.Path).Join(".vagrant").Join("plugins").String(),
|
||||
"error", err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clone our vagrantfile to use in the new project
|
||||
v := b.vagrantfile.clone("project", p)
|
||||
p.Closer(func() error { return v.Close() })
|
||||
|
||||
// Add the project vagrantfile
|
||||
err = v.Source(p.project.Configuration, VAGRANTFILE_PROJECT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Init the vagrantfile so the config is available
|
||||
if err = v.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.vagrantfile = v
|
||||
|
||||
// Ensure any modifications to the project are persisted
|
||||
p.Closer(func() error {
|
||||
// Save any configuration updates
|
||||
v, err := p.vagrantfile.GetSource(VAGRANTFILE_PROJECT)
|
||||
if err != nil {
|
||||
p.logger.Debug("failed to retrieve vagrantfile",
|
||||
"reason", err,
|
||||
)
|
||||
}
|
||||
p.project.Configuration = v
|
||||
return p.Save()
|
||||
})
|
||||
|
||||
// Remove ourself from cached projects
|
||||
p.Closer(func() error {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
delete(b.projects, p.project.ResourceId)
|
||||
delete(b.projects, p.Name())
|
||||
return nil
|
||||
})
|
||||
|
||||
// Set seeds for any plugins that may be used
|
||||
p.seed(nil)
|
||||
|
||||
// Initialize any targets defined in the project
|
||||
if err = p.InitTargets(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set our loaded project into the basis
|
||||
b.projects[p.project.ResourceId] = p
|
||||
|
||||
b.logger.Info("done setting up new project instance")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Register functions to be called when closing this basis
|
||||
func (b *Basis) Closer(c func() error) {
|
||||
b.cleaner.Do(c)
|
||||
@ -819,25 +598,31 @@ func (b *Basis) Closer(c func() error) {
|
||||
// Close is called to clean up resources allocated by the basis.
|
||||
// This should be called and blocked on to gracefully stop the basis.
|
||||
func (b *Basis) Close() (err error) {
|
||||
b.logger.Debug("closing basis",
|
||||
"basis", b.basis.ResourceId)
|
||||
b.logger.Debug("closing basis")
|
||||
|
||||
// Close down any projects that were loaded
|
||||
for name, p := range b.projects {
|
||||
b.logger.Trace("closing project",
|
||||
"project", name)
|
||||
if cerr := p.Close(); cerr != nil {
|
||||
b.logger.Warn("error closing project",
|
||||
"project", name,
|
||||
"error", cerr)
|
||||
err = multierror.Append(err, cerr)
|
||||
}
|
||||
return b.cleaner.Close()
|
||||
}
|
||||
|
||||
// Reload basis data
|
||||
func (b *Basis) Reload() (err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
if b.basis.ResourceId == "" {
|
||||
return status.Error(codes.NotFound, "basis does not exist")
|
||||
}
|
||||
|
||||
if cerr := b.cleaner.Close(); cerr != nil {
|
||||
err = multierror.Append(err, cerr)
|
||||
result, err := b.client.FindBasis(b.ctx,
|
||||
&vagrant_server.FindBasisRequest{
|
||||
Basis: b.basis,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.basis = result.Basis
|
||||
return
|
||||
}
|
||||
|
||||
@ -846,8 +631,7 @@ func (b *Basis) Save() (err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
b.logger.Debug("saving basis to db",
|
||||
"basis", b.basis.ResourceId)
|
||||
b.logger.Debug("saving basis to db")
|
||||
|
||||
result, err := b.Client().UpsertBasis(b.ctx,
|
||||
&vagrant_server.UpsertBasisRequest{
|
||||
@ -855,7 +639,6 @@ func (b *Basis) Save() (err error) {
|
||||
|
||||
if err != nil {
|
||||
b.logger.Trace("failed to save basis",
|
||||
"basis", b.basis.ResourceId,
|
||||
"error", err)
|
||||
}
|
||||
|
||||
@ -863,32 +646,6 @@ func (b *Basis) Save() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Saves the basis to the db as well as any projects that have been
|
||||
// loaded. This will "cascade" to targets as well since `SaveFull` will
|
||||
// be called on the project.
|
||||
func (b *Basis) SaveFull() (err error) {
|
||||
b.logger.Debug("performing full save",
|
||||
"basis", b.basis.ResourceId)
|
||||
|
||||
for _, p := range b.projects {
|
||||
b.logger.Trace("saving project",
|
||||
"basis", b.basis.ResourceId,
|
||||
"project", p.project.ResourceId)
|
||||
|
||||
if perr := p.SaveFull(); perr != nil {
|
||||
b.logger.Trace("error while saving project",
|
||||
"project", p.project.ResourceId,
|
||||
"error", err)
|
||||
|
||||
err = multierror.Append(err, perr)
|
||||
}
|
||||
}
|
||||
if berr := b.Save(); berr != nil {
|
||||
err = multierror.Append(err, berr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Basis) TargetIndex() (core.TargetIndex, error) {
|
||||
return b.index, nil
|
||||
}
|
||||
@ -906,7 +663,6 @@ func (b *Basis) Components(ctx context.Context) ([]*Component, error) {
|
||||
// component name. This is the entry point for running commands.
|
||||
func (b *Basis) Run(ctx context.Context, task *vagrant_server.Task) (err error) {
|
||||
b.logger.Debug("running new task",
|
||||
"basis", b,
|
||||
"task", task)
|
||||
|
||||
// Build the component to run
|
||||
@ -1082,6 +838,123 @@ func (b *Basis) seed(fn func(*core.Seeds)) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basis) configRegistration(p *plugin.Plugin, l hclog.Logger) error {
|
||||
if !p.HasType(component.ConfigType) {
|
||||
b.logger.Warn("plugin does not implement config component type",
|
||||
"name", p.Name,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
b.logger.Debug("registering configuration component",
|
||||
"name", p.Name,
|
||||
)
|
||||
|
||||
i, err := p.Manager().Find(p.Name, component.ConfigType)
|
||||
if err != nil {
|
||||
b.logger.Error("failed to load configuration component",
|
||||
"name", p.Name,
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
c, ok := i.Component.(core.Config)
|
||||
if !ok {
|
||||
return fmt.Errorf("component instance is not valid config: %s", p.Name)
|
||||
}
|
||||
info, err := c.Register()
|
||||
if err != nil {
|
||||
b.logger.Error("failed to get registration information from plugin",
|
||||
"name", p.Name,
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
b.logger.Info("registering configuration component",
|
||||
"plugin", p.Name,
|
||||
"info", *info,
|
||||
)
|
||||
return b.vagrantfile.Register(info, p)
|
||||
}
|
||||
|
||||
func (b *Basis) setPluginCache(i *plugin.Instance, l hclog.Logger) error {
|
||||
if c, ok := i.Component.(interface {
|
||||
SetCache(cacher.Cache)
|
||||
}); ok {
|
||||
b.logger.Trace("setting cache on plugin instance",
|
||||
"name", i.Name,
|
||||
"component", hclog.Fmt("%T", i.Component),
|
||||
)
|
||||
c.SetCache(b.cache)
|
||||
} else {
|
||||
b.logger.Warn("cannot set cache on plugin instance",
|
||||
"name", i.Name,
|
||||
"component", hclog.Fmt("%T", i.Component),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basis) setPluginSeeds(i *plugin.Instance, l hclog.Logger) error {
|
||||
if s, ok := i.Component.(core.Seeder); ok {
|
||||
if err := s.Seed(b.seedValues); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basis) setPluginManager(i *plugin.Instance, l hclog.Logger) error {
|
||||
s, ok := i.Component.(plugin.HasPluginMetadata)
|
||||
if !ok {
|
||||
l.Warn("plugin does not support metadata, cannot assign plugin manager",
|
||||
"component", i.Type.String(),
|
||||
"name", i.Name,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
srv, err := b.plugins.Servinfo()
|
||||
if err != nil {
|
||||
l.Warn("failed to get plugin manager information",
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
s.SetRequestMetadata("plugin_manager", string(srv))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basis) setPluginCoreManager(i *plugin.Instance, l hclog.Logger) error {
|
||||
s, ok := i.Component.(plugin.HasPluginMetadata)
|
||||
if !ok {
|
||||
l.Warn("plugin does not support metadata, cannot assign plugin manager",
|
||||
"component", i.Type.String(),
|
||||
"name", i.Name,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
srv, err := b.corePlugins.Servinfo(b.plugins.LegacyBroker())
|
||||
if err != nil {
|
||||
l.Warn("failed to get plugin manager information",
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
s.SetRequestMetadata("core_plugin_manager", string(srv))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basis) execHook(
|
||||
ctx context.Context,
|
||||
log hclog.Logger,
|
||||
@ -1160,43 +1033,16 @@ func WithBasisDataDir(dir *datadir.Basis) BasisOption {
|
||||
// WithBasisRef is used to load or initialize the basis
|
||||
func WithBasisRef(r *vagrant_plugin_sdk.Ref_Basis) BasisOption {
|
||||
return func(b *Basis) (err error) {
|
||||
var basis *vagrant_server.Basis
|
||||
// if we don't have a resource ID we need to upsert
|
||||
if r.ResourceId == "" {
|
||||
var result *vagrant_server.UpsertBasisResponse
|
||||
result, err = b.client.UpsertBasis(
|
||||
context.Background(),
|
||||
&vagrant_server.UpsertBasisRequest{
|
||||
Basis: &vagrant_server.Basis{
|
||||
Name: r.Name,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
basis = result.Basis
|
||||
} else {
|
||||
var result *vagrant_server.GetBasisResponse
|
||||
result, err = b.client.GetBasis(
|
||||
context.Background(),
|
||||
&vagrant_server.GetBasisRequest{
|
||||
Basis: r,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
basis = result.Basis
|
||||
if r.ResourceId != "" {
|
||||
b.basis.ResourceId = r.ResourceId
|
||||
}
|
||||
b.basis = basis
|
||||
// if the datadir isn't set, do that now
|
||||
if b.dir == nil {
|
||||
b.dir, err = datadir.NewBasis(basis.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if r.Name != "" {
|
||||
b.basis.Name = r.Name
|
||||
}
|
||||
if r.Path != "" {
|
||||
b.basis.Path = r.Path
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1241,3 +1087,4 @@ func FromBasis(basis *Basis) BasisOption {
|
||||
}
|
||||
|
||||
var _ core.Basis = (*Basis)(nil)
|
||||
var _ Scope = (*Basis)(nil)
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/hashicorp/go-argmapper"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
goplugin "github.com/hashicorp/go-plugin"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
@ -29,6 +28,7 @@ import (
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/config"
|
||||
"github.com/hashicorp/vagrant/internal/plugin"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
"github.com/hashicorp/vagrant/internal/serverclient"
|
||||
)
|
||||
@ -38,46 +38,176 @@ import (
|
||||
// The Close function should be called when finished with the project
|
||||
// to properly clean up any open resources.
|
||||
type Project struct {
|
||||
project *vagrant_server.Project
|
||||
ctx context.Context
|
||||
basis *Basis
|
||||
logger hclog.Logger
|
||||
basis *Basis // basis which owns this project
|
||||
cache cacher.Cache // local project cache
|
||||
cleanup cleanup.Cleanup // cleanup tasks to be run on close
|
||||
client *serverclient.VagrantClient // client to vagrant server
|
||||
ctx context.Context // local context
|
||||
dir *datadir.Project // data directory for project
|
||||
factory *Factory // scope factory
|
||||
jobInfo *component.JobInfo // jobInfo is the base job info for executed functions
|
||||
logger hclog.Logger // project specific logger
|
||||
mappers []*argmapper.Func // mappers for project
|
||||
plugins *plugin.Manager // project scoped plugin manager
|
||||
project *vagrant_server.Project // stored project data
|
||||
ready bool // flag that instance is ready
|
||||
targets map[string]*Target
|
||||
dir *datadir.Project
|
||||
mappers []*argmapper.Func
|
||||
vagrantfile *Vagrantfile
|
||||
ui terminal.UI // project UI (non-prefixed)
|
||||
vagrantfile *Vagrantfile // vagrantfile instance for project
|
||||
|
||||
// jobInfo is the base job info for executed functions.
|
||||
jobInfo *component.JobInfo
|
||||
|
||||
// This lock only needs to be held currently to protect closers.
|
||||
m sync.Mutex
|
||||
|
||||
// Registered actions for cleanup on close
|
||||
cleanup cleanup.Cleanup
|
||||
|
||||
// UI is the terminal UI to use for messages related to the project
|
||||
// as a whole. These messages will show up unprefixed for example compared
|
||||
// to the app-specific UI.
|
||||
ui terminal.UI
|
||||
}
|
||||
|
||||
// Create a new blank project instance
|
||||
func NewProject(opts ...ProjectOption) (*Project, error) {
|
||||
var p *Project
|
||||
var err error
|
||||
p = &Project{
|
||||
cache: cacher.New(),
|
||||
cleanup: cleanup.New(),
|
||||
ctx: context.Background(),
|
||||
logger: hclog.L(),
|
||||
project: &vagrant_server.Project{},
|
||||
}
|
||||
|
||||
for _, fn := range opts {
|
||||
if optErr := fn(p); optErr != nil {
|
||||
err = multierror.Append(err, optErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Project) Init() error {
|
||||
var err error
|
||||
|
||||
// If ready then Init was already run
|
||||
if p.ready {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure our logger
|
||||
p.logger = p.logger.ResetNamed("vagrant.core.project")
|
||||
p.logger = p.logger.With("project", p)
|
||||
|
||||
// If the client isn't set, grab it from the basis
|
||||
if p.client == nil && p.basis != nil {
|
||||
p.client = p.basis.client
|
||||
}
|
||||
|
||||
// Attempt to reload the project to populate our
|
||||
// data. If the project is not found, create it.
|
||||
err = p.Reload()
|
||||
if err != nil {
|
||||
stat, ok := status.FromError(err)
|
||||
if !ok || stat.Code() != codes.NotFound {
|
||||
return err
|
||||
}
|
||||
// Project doesn't exist so save it to persist
|
||||
if err = p.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a basis set, load it
|
||||
if p.basis == nil {
|
||||
p.basis, err = p.factory.NewBasis(p.project.Basis.ResourceId, WithBasisRef(p.project.Basis))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load project basis: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set our plugin manager as a sub manager of the basis
|
||||
p.plugins = p.basis.plugins.Sub("project")
|
||||
|
||||
// If our project closes early, close the plugin sub manager
|
||||
// so it isn't just hanging around
|
||||
p.Closer(func() error {
|
||||
return p.plugins.Close()
|
||||
})
|
||||
|
||||
// Always ensure the basis reference is set
|
||||
p.project.Basis = p.basis.Ref().(*vagrant_plugin_sdk.Ref_Basis)
|
||||
|
||||
// If the project directory is unset, set it
|
||||
if p.dir == nil {
|
||||
if p.dir, err = p.basis.dir.Project(p.project.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If the ui is unset, use basis ui
|
||||
if p.ui == nil {
|
||||
p.ui = p.basis.ui
|
||||
}
|
||||
|
||||
// Load any plugins that may be installed locally to the project
|
||||
if err = p.plugins.Discover(path.NewPath(p.project.Path).Join(".vagrant").Join("plugins")); err != nil {
|
||||
p.logger.Error("project setup failed during plugin discovery",
|
||||
"directory", path.NewPath(p.project.Path).Join(".vagrant").Join("plugins"),
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clone our vagrantfile to use in the new project
|
||||
v := p.basis.vagrantfile.clone("project")
|
||||
v.logger = p.logger.Named("vagrantfile")
|
||||
|
||||
// Add the project vagrantfile
|
||||
err = v.Source(p.project.Configuration, VAGRANTFILE_PROJECT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Init the vagrantfile so the config is available
|
||||
if err = v.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.vagrantfile = v
|
||||
|
||||
// Store our configuration
|
||||
sv, err := v.GetSource(VAGRANTFILE_PROJECT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.project.Configuration = sv
|
||||
|
||||
// Set our ref into vagrantfile
|
||||
p.vagrantfile.targetSource = p.Ref().(*vagrant_plugin_sdk.Ref_Project)
|
||||
|
||||
// Set project seeds
|
||||
p.seed(nil)
|
||||
|
||||
// Remove any stale targets
|
||||
if err = p.scrubTargets(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save ourself when closed
|
||||
p.Closer(func() error {
|
||||
return p.Save()
|
||||
})
|
||||
|
||||
// Set flag that this instance is setup
|
||||
p.ready = true
|
||||
|
||||
p.logger.Info("project initialized")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide nice output in logger
|
||||
func (p *Project) String() string {
|
||||
return fmt.Sprintf("core.Project:[basis: %s, name: %s, resource_id: %s, address: %p]",
|
||||
p.basis.Name(), p.project.Name, p.project.ResourceId, p)
|
||||
return fmt.Sprintf("core.Project:[basis: %v, name: %s, resource_id: %s, address: %p]",
|
||||
p.basis, p.project.Name, p.project.ResourceId, p)
|
||||
}
|
||||
|
||||
// Cache implements originScope
|
||||
func (p *Project) Cache() cacher.Cache {
|
||||
return p.basis.cache
|
||||
}
|
||||
|
||||
// Broker implements originScope
|
||||
func (p *Project) Broker() *goplugin.GRPCBroker {
|
||||
return p.basis.plugins.LegacyBroker()
|
||||
}
|
||||
|
||||
// Vagrantfile implements originScope
|
||||
// Vagrantfile implements core.Project
|
||||
func (p *Project) Vagrantfile() (core.Vagrantfile, error) {
|
||||
return p.vagrantfile, nil
|
||||
}
|
||||
@ -167,23 +297,30 @@ func (p *Project) DefaultProvider(opts *core.DefaultProviderOptions) (string, er
|
||||
}
|
||||
|
||||
for _, n := range targets {
|
||||
targetConfig, err := p.vagrantfile.TargetConfig(n, "", false)
|
||||
target, err := p.Target(n, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil
|
||||
}
|
||||
tv := targetConfig.(*Vagrantfile)
|
||||
if target.(*Target).target.Provider != "" {
|
||||
configProviders = append(configProviders, target.(*Target).target.Provider)
|
||||
} else {
|
||||
tv := target.(*Target).vagrantfile
|
||||
|
||||
pRaw, err := tv.GetValue("vm", "__provider_order")
|
||||
providers, ok := pRaw.([]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected type for target provider list (%T)", pRaw)
|
||||
}
|
||||
for _, pint := range providers {
|
||||
pstring, err := optionToString(pint)
|
||||
pRaw, err := tv.GetValue("vm", "__provider_order")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unexpected type for target provider (%T)", pint)
|
||||
continue
|
||||
}
|
||||
providers, ok := pRaw.([]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected type for target provider list (%T)", pRaw)
|
||||
}
|
||||
for _, pint := range providers {
|
||||
pstring, err := optionToString(pint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unexpected type for target provider (%T)", pint)
|
||||
}
|
||||
configProviders = append(configProviders, pstring)
|
||||
}
|
||||
configProviders = append(configProviders, pstring)
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,8 +490,7 @@ func (p *Project) LocalData() (d path.Path, err error) {
|
||||
|
||||
// PrimaryTargetName implements core.Project
|
||||
func (p *Project) PrimaryTargetName() (name string, err error) {
|
||||
// TODO: This needs the Vagrantfile service to be implemented
|
||||
return
|
||||
return p.vagrantfile.PrimaryTargetName()
|
||||
}
|
||||
|
||||
// Resource implements core.Project
|
||||
@ -363,21 +499,16 @@ func (p *Project) ResourceId() (string, error) {
|
||||
}
|
||||
|
||||
// RootPath implements core.Project
|
||||
func (p *Project) RootPath() (path path.Path, err error) {
|
||||
// TODO: need vagrantfile loading to be completed in order to implement
|
||||
return
|
||||
func (p *Project) RootPath() (path.Path, error) {
|
||||
return path.NewPath(p.project.Configuration.Path.Path), nil
|
||||
}
|
||||
|
||||
func (p *Project) Factory() *Factory {
|
||||
return p.basis.factory
|
||||
}
|
||||
|
||||
// Target implements core.Project
|
||||
|
||||
func (p *Project) Target(nameOrId string, provider string) (core.Target, error) {
|
||||
// TODO(spox): do we need to add a check here if the
|
||||
// already loaded target doesn't match the
|
||||
// provided provider name?
|
||||
if t, ok := p.targets[nameOrId]; ok {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
return p.vagrantfile.Target(nameOrId, provider)
|
||||
}
|
||||
|
||||
@ -416,14 +547,19 @@ func (p *Project) UI() (terminal.UI, error) {
|
||||
|
||||
// Targets
|
||||
func (p *Project) Targets() ([]core.Target, error) {
|
||||
var targets []core.Target
|
||||
for _, ref := range p.project.Targets {
|
||||
t, err := p.LoadTarget(WithTargetRef(ref))
|
||||
names, err := p.TargetNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets := make([]core.Target, len(names))
|
||||
for i, n := range names {
|
||||
t, err := p.Target(n, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets = append(targets, t)
|
||||
targets[i] = t
|
||||
}
|
||||
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
@ -437,103 +573,9 @@ func (p *Project) JobInfo() *component.JobInfo {
|
||||
return p.jobInfo
|
||||
}
|
||||
|
||||
// LoadTarget loads a target within the current project. If the target is not
|
||||
// found, it will be created.
|
||||
func (p *Project) LoadTarget(topts ...TargetOption) (*Target, error) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
// Create our target
|
||||
t := &Target{
|
||||
cache: cacher.New(),
|
||||
ctx: p.ctx,
|
||||
project: p,
|
||||
logger: p.logger,
|
||||
target: &vagrant_server.Target{
|
||||
Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project),
|
||||
},
|
||||
ui: p.ui,
|
||||
}
|
||||
var err error
|
||||
|
||||
// Apply any options provided
|
||||
for _, opt := range topts {
|
||||
if oerr := opt(t); oerr != nil {
|
||||
err = multierror.Append(err, oerr)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Lookup target in cached list by name
|
||||
if c, ok := p.targets[t.target.Name]; ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Lookup target in cached list by resource id
|
||||
if c, ok := p.targets[t.target.ResourceId]; ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// If we don't have a vagrantfile assigned to
|
||||
// this target, request it and set it
|
||||
if t.vagrantfile == nil {
|
||||
p.logger.Info("target does not have vagrantfile set, loading", "target", t.target.Name)
|
||||
tv, err := p.vagrantfile.TargetConfig(t.target.Name, "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set the vagrantfile if one was returned
|
||||
if tv != nil {
|
||||
t.vagrantfile = tv.(*Vagrantfile)
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the first time through, re-init the target
|
||||
if err = t.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the data directory is set, set it
|
||||
if t.dir == nil {
|
||||
if t.dir, err = p.dir.Target(t.target.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Update the logger name based on the level
|
||||
if t.logger.IsTrace() {
|
||||
t.logger = t.logger.Named("target")
|
||||
} else {
|
||||
t.logger = t.logger.ResetNamed("vagrant.core.target")
|
||||
}
|
||||
|
||||
// Ensure any modifications to the target are persisted
|
||||
t.Closer(func() error { return t.Save() })
|
||||
|
||||
// Remove the target from the list when closed
|
||||
t.Closer(func() error {
|
||||
delete(p.targets, t.target.ResourceId)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Close the target when the project is closed
|
||||
p.Closer(func() error {
|
||||
return t.Close()
|
||||
})
|
||||
|
||||
// Add the target to target list in project
|
||||
p.targets[t.target.ResourceId] = t
|
||||
p.targets[t.target.Name] = t
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Client returns the API client for the backend server.
|
||||
func (p *Project) Client() *serverclient.VagrantClient {
|
||||
return p.basis.client
|
||||
return p.client
|
||||
}
|
||||
|
||||
// Ref returns the project ref for API calls.
|
||||
@ -547,9 +589,13 @@ func (p *Project) Ref() interface{} {
|
||||
|
||||
func (p *Project) Run(ctx context.Context, task *vagrant_server.Task) (err error) {
|
||||
p.logger.Debug("running new task",
|
||||
"project", p,
|
||||
"task", task)
|
||||
|
||||
// Initialize our targets before running
|
||||
if err = p.InitTargets(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd, err := p.basis.component(
|
||||
ctx, component.CommandType, task.Component.Name)
|
||||
if err != nil {
|
||||
@ -590,7 +636,10 @@ func (p *Project) Run(ctx context.Context, task *vagrant_server.Task) (err error
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Project) seed(fn func(*core.Seeds)) {
|
||||
// Set project specific seeds
|
||||
func (p *Project) seed(
|
||||
fn func(*core.Seeds), // callback for adding seeds
|
||||
) {
|
||||
p.basis.seed(
|
||||
func(s *core.Seeds) {
|
||||
s.AddNamed("project", p)
|
||||
@ -611,23 +660,34 @@ func (p *Project) Closer(c func() error) {
|
||||
// Close is called to clean up resources allocated by the project.
|
||||
// This should be called and blocked on to gracefully stop the project.
|
||||
func (p *Project) Close() (err error) {
|
||||
p.logger.Debug("closing project",
|
||||
"project", p)
|
||||
|
||||
// Remove this project from basis project list
|
||||
delete(p.basis.projects, p.Name())
|
||||
delete(p.basis.projects, p.project.ResourceId)
|
||||
p.logger.Trace("closing project")
|
||||
|
||||
return p.cleanup.Close()
|
||||
}
|
||||
|
||||
// Saves the project to the db
|
||||
func (p *Project) Save() (err error) {
|
||||
func (p *Project) Save() error {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
p.logger.Trace("saving project to db",
|
||||
"project", p.project.ResourceId)
|
||||
p.logger.Trace("saving project to db")
|
||||
|
||||
// Remove the defined vms from finalized data to
|
||||
// prevent it from being used on subsequent runs
|
||||
if err := p.vagrantfile.DeleteValue("vm", "__defined_vms"); err != nil {
|
||||
p.logger.Warn("failed to remove defined vms configuration before save",
|
||||
"error", err,
|
||||
)
|
||||
}
|
||||
|
||||
val, err := p.vagrantfile.rootToStore()
|
||||
if err != nil {
|
||||
p.logger.Warn("failed to convert modified configuration for save",
|
||||
"error", err,
|
||||
)
|
||||
} else {
|
||||
p.project.Configuration.Finalized = val.Data
|
||||
}
|
||||
|
||||
result, err := p.Client().UpsertProject(p.ctx,
|
||||
&vagrant_server.UpsertProjectRequest{
|
||||
@ -636,132 +696,110 @@ func (p *Project) Save() (err error) {
|
||||
)
|
||||
if err != nil {
|
||||
p.logger.Trace("failed to save project",
|
||||
"project", p.project.ResourceId)
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
p.project = result.Project
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Saves the project to the db as well as any targets that have been loaded
|
||||
func (p *Project) SaveFull() (err error) {
|
||||
p.logger.Debug("performing full save",
|
||||
"project", p.project.ResourceId)
|
||||
|
||||
for _, t := range p.targets {
|
||||
p.logger.Trace("saving target",
|
||||
"project", p.project.ResourceId,
|
||||
"target", t.target.ResourceId)
|
||||
|
||||
if terr := t.Save(); terr != nil {
|
||||
p.logger.Trace("error while saving target",
|
||||
"target", t.target.ResourceId,
|
||||
"error", err)
|
||||
|
||||
err = multierror.Append(err, terr)
|
||||
}
|
||||
}
|
||||
if perr := p.Save(); perr != nil {
|
||||
err = multierror.Append(err, perr)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) Components(ctx context.Context) ([]*Component, error) {
|
||||
return p.basis.components(ctx)
|
||||
}
|
||||
|
||||
func (p *Project) InitTargets() (err error) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
p.logger.Error("failed to initialize targets",
|
||||
"error", err,
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
p.logger.Trace("initializing targets defined within project",
|
||||
"project", p.Name())
|
||||
|
||||
targets, err := p.vagrantfile.TargetNames()
|
||||
func (p *Project) scrubTargets() (err error) {
|
||||
var updated bool
|
||||
targets, err := p.TargetNames()
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
current := map[string]struct{}{}
|
||||
for _, name := range targets {
|
||||
current[name] = struct{}{}
|
||||
}
|
||||
|
||||
if len(targets) == 0 {
|
||||
p.logger.Trace("no targets defined within current project",
|
||||
"project", p.Name())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Get list of all currently known targets for project
|
||||
var existingTargets []string
|
||||
for _, t := range p.project.Targets {
|
||||
existingTargets = append(existingTargets, t.Name)
|
||||
}
|
||||
|
||||
p.logger.Trace("targets associated with project",
|
||||
"project", p,
|
||||
"existing", existingTargets,
|
||||
"defined", targets,
|
||||
)
|
||||
|
||||
updated := false
|
||||
seen := map[string]struct{}{}
|
||||
for _, t := range targets {
|
||||
_, err = p.createTarget(t)
|
||||
if err != nil {
|
||||
p.logger.Error("failed to initialize target with project",
|
||||
"project", p.Name(),
|
||||
"target", t,
|
||||
"error", err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
seen[t] = struct{}{}
|
||||
updated = true
|
||||
}
|
||||
|
||||
// If any existing targets are not in the defined list and are
|
||||
// not in a created state, delete them as they were removed
|
||||
// from the vagrantfile
|
||||
for _, existName := range existingTargets {
|
||||
if _, ok := seen[existName]; ok {
|
||||
if _, ok := current[t.Name]; ok {
|
||||
continue
|
||||
}
|
||||
resp, err := p.Client().FindTarget(p.ctx,
|
||||
&vagrant_server.FindTargetRequest{
|
||||
Target: &vagrant_server.Target{
|
||||
Name: existName,
|
||||
Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project),
|
||||
},
|
||||
|
||||
resp, err := p.client.GetTarget(p.ctx,
|
||||
&vagrant_server.GetTargetRequest{
|
||||
Target: t,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If the state is not created or unknown, remove it
|
||||
|
||||
if resp.Target.State == vagrant_server.Operation_NOT_CREATED ||
|
||||
resp.Target.State == vagrant_server.Operation_UNKNOWN {
|
||||
_, err := p.Client().DeleteTarget(p.ctx,
|
||||
resp.Target.State == vagrant_server.Operation_UNKNOWN ||
|
||||
resp.Target.State == vagrant_server.Operation_DESTROYED {
|
||||
_, err = p.client.DeleteTarget(p.ctx,
|
||||
&vagrant_server.DeleteTargetRequest{
|
||||
Target: &vagrant_plugin_sdk.Ref_Target{
|
||||
Name: existName,
|
||||
ResourceId: resp.Target.ResourceId,
|
||||
Project: p.Ref().(*vagrant_plugin_sdk.Ref_Project),
|
||||
},
|
||||
Target: t,
|
||||
},
|
||||
)
|
||||
if err != nil && status.Code(err) != codes.NotFound {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
if updated {
|
||||
err = p.Reload()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize all targets for this project
|
||||
func (p *Project) InitTargets() (err error) {
|
||||
p.logger.Trace("initializing targets defined within project")
|
||||
var updated bool
|
||||
|
||||
targets, err := p.Targets()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
current := map[string]struct{}{}
|
||||
for _, t := range targets {
|
||||
rid, err := t.ResourceId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current[rid] = struct{}{}
|
||||
}
|
||||
|
||||
// Cycle over targets registered to project and if any
|
||||
// are not in an "exist" type state, delete them as
|
||||
// they were removed from the vagrantfile
|
||||
for _, t := range p.project.Targets {
|
||||
if _, ok := current[t.ResourceId]; ok {
|
||||
continue
|
||||
}
|
||||
target, err := p.Factory().NewTarget(
|
||||
WithProject(p),
|
||||
WithTargetRef(t),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := target.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if state == core.NOT_CREATED || state == core.UNKNOWN {
|
||||
if err = target.Destroy(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
@ -771,26 +809,27 @@ func (p *Project) InitTargets() (err error) {
|
||||
// If targets have been updated then refresh the project. This is required
|
||||
// since upserting targets will also update the project to have a reference
|
||||
// to the new targets.
|
||||
err = p.refreshProject()
|
||||
err = p.Reload()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get's the latest project from the DB
|
||||
func (p *Project) refreshProject() (err error) {
|
||||
// Reload the project data
|
||||
func (p *Project) Reload() (err error) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.project.ResourceId == "" {
|
||||
return status.Error(codes.NotFound, "project does not exist")
|
||||
}
|
||||
|
||||
result, err := p.Client().FindProject(p.ctx,
|
||||
&vagrant_server.FindProjectRequest{
|
||||
Project: &vagrant_server.Project{
|
||||
ResourceId: p.project.ResourceId,
|
||||
},
|
||||
Project: p.project,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
p.logger.Error("failed to refresh project data",
|
||||
"project", p.Name(),
|
||||
"error", err,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -881,12 +920,20 @@ type ProjectOption func(*Project) error
|
||||
func WithBasis(b *Basis) ProjectOption {
|
||||
return func(p *Project) (err error) {
|
||||
p.basis = b
|
||||
p.project.Basis = b.Ref().(*vagrant_plugin_sdk.Ref_Basis)
|
||||
// NOTE: only set the UI if it's unset
|
||||
if p.ui == nil {
|
||||
p.ui = b.ui
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func WithProjectDataDir(dir *datadir.Project) ProjectOption {
|
||||
return func(p *Project) (err error) {
|
||||
if dir == nil {
|
||||
return errors.New("directory value cannot be nil")
|
||||
}
|
||||
p.dir = dir
|
||||
return
|
||||
}
|
||||
@ -894,37 +941,10 @@ func WithProjectDataDir(dir *datadir.Project) ProjectOption {
|
||||
|
||||
func WithProjectName(name string) ProjectOption {
|
||||
return func(p *Project) (err error) {
|
||||
if p.basis == nil {
|
||||
return errors.New("basis must be set before loading project")
|
||||
if name == "" {
|
||||
return errors.New("name cannot be empty")
|
||||
}
|
||||
if ex := p.basis.Project(name); ex != nil {
|
||||
p.project = ex.project
|
||||
return
|
||||
}
|
||||
|
||||
var match *vagrant_plugin_sdk.Ref_Project
|
||||
for _, m := range p.basis.basis.Projects {
|
||||
if m.Name == name {
|
||||
match = m
|
||||
break
|
||||
}
|
||||
}
|
||||
if match == nil {
|
||||
return errors.New("project is not registered in basis")
|
||||
}
|
||||
result, err := p.Client().FindProject(p.ctx, &vagrant_server.FindProjectRequest{
|
||||
Project: &vagrant_server.Project{Name: name},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
p.logger.Error("failed to locate project during setup", "project", name,
|
||||
"basis", p.basis.Ref())
|
||||
return errors.New("failed to load project")
|
||||
}
|
||||
p.project = result.Project
|
||||
|
||||
p.project.Name = name
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -936,57 +956,22 @@ func WithProjectRef(r *vagrant_plugin_sdk.Ref_Project) ProjectOption {
|
||||
if r == nil {
|
||||
return errors.New("project reference cannot be nil")
|
||||
}
|
||||
// Basis must be set before we continue
|
||||
if p.basis == nil {
|
||||
return errors.New("basis must be set before loading project")
|
||||
if r.Name != "" {
|
||||
p.project.Name = r.Name
|
||||
}
|
||||
|
||||
var project *vagrant_server.Project
|
||||
// Check if the basis has already loaded the project. If so,
|
||||
// then initialize on that project
|
||||
if ex := p.basis.projects[r.Name]; ex != nil {
|
||||
project = ex.project
|
||||
return
|
||||
if r.Path != "" {
|
||||
p.project.Path = r.Path
|
||||
}
|
||||
result, err := p.Client().FindProject(p.ctx,
|
||||
&vagrant_server.FindProjectRequest{
|
||||
Project: &vagrant_server.Project{
|
||||
Basis: r.Basis,
|
||||
Name: r.Name,
|
||||
Path: r.Path,
|
||||
ResourceId: r.ResourceId,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
var result *vagrant_server.UpsertProjectResponse
|
||||
result, err = p.Client().UpsertProject(p.ctx,
|
||||
&vagrant_server.UpsertProjectRequest{
|
||||
Project: &vagrant_server.Project{
|
||||
Name: r.Name,
|
||||
Path: r.Name,
|
||||
Basis: r.Basis,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
project = result.Project
|
||||
} else {
|
||||
project = result.Project
|
||||
if r.ResourceId != "" {
|
||||
p.project.ResourceId = r.ResourceId
|
||||
}
|
||||
|
||||
// Before we init, validate basis is consistent
|
||||
if r.Basis != nil && project.Basis.ResourceId != r.Basis.ResourceId {
|
||||
p.logger.Error("invalid basis for project", "request-basis", r.Basis,
|
||||
"project-basis", project.Basis)
|
||||
return errors.New("project basis configuration is invalid")
|
||||
if r.Basis != nil {
|
||||
p.project.Basis = r.Basis
|
||||
}
|
||||
p.project = project
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var _ core.Project = (*Project)(nil)
|
||||
var _ Scope = (*Project)(nil)
|
||||
|
||||
@ -13,7 +13,8 @@ import (
|
||||
"github.com/hashicorp/go-argmapper"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
goplugin "github.com/hashicorp/go-plugin"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
@ -21,11 +22,10 @@ import (
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/core"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/datadir"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cacher"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/dynamic"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cleanup"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/config"
|
||||
"github.com/hashicorp/vagrant/internal/plugin"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
"github.com/hashicorp/vagrant/internal/serverclient"
|
||||
)
|
||||
@ -33,23 +33,147 @@ import (
|
||||
const DEFAULT_COMMUNICATOR_NAME = "ssh"
|
||||
|
||||
type Target struct {
|
||||
ctx context.Context
|
||||
target *vagrant_server.Target
|
||||
project *Project
|
||||
logger hclog.Logger
|
||||
dir *datadir.Target
|
||||
cache cacher.Cache // local target cache
|
||||
cleanup cleanup.Cleanup // cleanup tasks to be run on close
|
||||
client *serverclient.VagrantClient // client to vagrant server
|
||||
ctx context.Context // local target context
|
||||
dir *datadir.Target // data directory for target
|
||||
factory *Factory // scope factory
|
||||
jobInfo *component.JobInfo // jobInfo is the base job info for executed functions
|
||||
logger hclog.Logger // target specific logger
|
||||
project *Project // project which owns this target
|
||||
ready bool // flag that instance is ready
|
||||
target *vagrant_server.Target // stored target data
|
||||
ui terminal.UI // target UI
|
||||
vagrantfile *Vagrantfile // vagrantfile instance for target
|
||||
|
||||
m sync.Mutex
|
||||
jobInfo *component.JobInfo
|
||||
closers []func() error
|
||||
ui terminal.UI
|
||||
cache cacher.Cache
|
||||
vagrantfile *Vagrantfile
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func NewTarget(opts ...TargetOption) (*Target, error) {
|
||||
var t *Target
|
||||
var err error
|
||||
t = &Target{
|
||||
cache: cacher.New(),
|
||||
cleanup: cleanup.New(),
|
||||
ctx: context.Background(),
|
||||
logger: hclog.L(),
|
||||
target: &vagrant_server.Target{},
|
||||
}
|
||||
|
||||
for _, fn := range opts {
|
||||
if optErr := fn(t); optErr != nil {
|
||||
err = multierror.Append(err, optErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (t *Target) Init() error {
|
||||
var err error
|
||||
|
||||
// If ready then Init was already run
|
||||
if t.ready {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure our logger
|
||||
t.logger = t.logger.ResetNamed("vagrant.core.target")
|
||||
t.logger = t.logger.With("target", t)
|
||||
|
||||
// If no client is set, grab it from the project
|
||||
if t.client == nil && t.project != nil {
|
||||
t.client = t.project.client
|
||||
}
|
||||
|
||||
// Attempt to reload the target to populate our
|
||||
// data. If the target is not found, create it.
|
||||
err = t.Reload()
|
||||
if err != nil {
|
||||
stat, ok := status.FromError(err)
|
||||
if !ok || stat.Code() != codes.NotFound {
|
||||
return err
|
||||
}
|
||||
// Target doesn't exist so save it to persist
|
||||
if err = t.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have a project set, load it
|
||||
if t.project == nil {
|
||||
t.project, err = t.factory.NewProject(WithProjectRef(t.target.Project))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load target project: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Always ensure the project reference is set
|
||||
t.target.Project = t.project.Ref().(*vagrant_plugin_sdk.Ref_Project)
|
||||
|
||||
// If the target directory is unset, set it
|
||||
if t.dir == nil {
|
||||
if t.dir, err = t.project.dir.Target(t.target.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If the ui is unset, use the project ui
|
||||
if t.ui == nil {
|
||||
t.ui = t.project.ui
|
||||
}
|
||||
|
||||
// Save ourself when closed
|
||||
t.Closer(func() error {
|
||||
return t.Save()
|
||||
})
|
||||
|
||||
// If we have a vagrantfile set, we are done
|
||||
if t.vagrantfile != nil {
|
||||
t.vagrantfile.logger = t.logger.Named("vagrantfile")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// We don't have a vagrantfile set so we need to restore
|
||||
// our stored configuration. First, make sure we have some
|
||||
// store configuration!
|
||||
if t.target.Configuration == nil {
|
||||
t.target.Configuration = &vagrant_plugin_sdk.Args_ConfigData{}
|
||||
|
||||
// Since we don't have any data to load, just stub and return
|
||||
t.vagrantfile = t.project.vagrantfile.clone("target")
|
||||
t.vagrantfile.root = &component.ConfigData{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
v := t.project.vagrantfile.clone("target")
|
||||
v.logger = t.logger.Named("vagrantfile")
|
||||
|
||||
if err = v.loadToRoot(t.target.Configuration); err != nil {
|
||||
return err
|
||||
}
|
||||
t.vagrantfile = v
|
||||
|
||||
// Set flag that this instance is setup
|
||||
t.ready = true
|
||||
|
||||
t.logger.Info("target initialized")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Target) String() string {
|
||||
return fmt.Sprintf("core.Target[basis: %s, project: %s, resource_id: %s, address: %p]",
|
||||
t.project.basis.Name(), t.project.Name(), t.target.ResourceId, t,
|
||||
return fmt.Sprintf("core.Target[basis: %s, project: %s, resource_id: %s, name: %s, address: %p]",
|
||||
t.project.basis.Name(), t.project.Name(), t.target.ResourceId, t.target.Name, t,
|
||||
)
|
||||
}
|
||||
|
||||
@ -247,29 +371,37 @@ func (t *Target) JobInfo() *component.JobInfo {
|
||||
|
||||
// Client returns the API client for the backend server.
|
||||
func (t *Target) Client() *serverclient.VagrantClient {
|
||||
return t.project.basis.client
|
||||
return t.client
|
||||
}
|
||||
|
||||
func (t *Target) Closer(c func() error) {
|
||||
t.closers = append(t.closers, c)
|
||||
t.cleanup.Do(c)
|
||||
}
|
||||
|
||||
// Close is called to clean up resources allocated by the target.
|
||||
// This should be called and blocked on to gracefully stop the target.
|
||||
func (t *Target) Close() (err error) {
|
||||
t.logger.Debug("closing target",
|
||||
"target", t)
|
||||
t.logger.Debug("closing target")
|
||||
|
||||
for _, c := range t.closers {
|
||||
if cerr := c(); cerr != nil {
|
||||
t.logger.Warn("error executing closer",
|
||||
"error", cerr)
|
||||
return t.cleanup.Close()
|
||||
}
|
||||
|
||||
err = multierror.Append(err, cerr)
|
||||
}
|
||||
// Reload the target data
|
||||
func (t *Target) Reload() (err error) {
|
||||
t.m.Lock()
|
||||
defer t.m.Unlock()
|
||||
|
||||
result, err := t.Client().FindTarget(t.ctx,
|
||||
&vagrant_server.FindTargetRequest{
|
||||
Target: t.target,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Remove this target from built target list in project
|
||||
delete(t.project.targets, t.target.Name)
|
||||
|
||||
t.target = result.Target
|
||||
return
|
||||
}
|
||||
|
||||
@ -278,16 +410,23 @@ func (t *Target) Save() (err error) {
|
||||
t.m.Lock()
|
||||
defer t.m.Unlock()
|
||||
|
||||
t.logger.Debug("saving target to db",
|
||||
"target", t.target.ResourceId,
|
||||
"name", t.target.Name,
|
||||
)
|
||||
t.logger.Debug("saving target to db")
|
||||
|
||||
// If there were any modification to the configuration
|
||||
// after init, be sure we capture them
|
||||
t.target.Configuration, err = t.vagrantfile.rootToStore()
|
||||
if err != nil {
|
||||
t.logger.Warn("failed to serialize configuration prior to save",
|
||||
"error", err,
|
||||
)
|
||||
// Only warn since we want to save whatever information we can
|
||||
err = nil
|
||||
}
|
||||
|
||||
result, uerr := t.Client().UpsertTarget(t.ctx, &vagrant_server.UpsertTargetRequest{
|
||||
Target: t.target})
|
||||
if uerr != nil {
|
||||
t.logger.Trace("failed to save target",
|
||||
"target", t.target.ResourceId,
|
||||
"error", uerr)
|
||||
|
||||
err = multierror.Append(err, uerr)
|
||||
@ -299,9 +438,13 @@ func (t *Target) Save() (err error) {
|
||||
}
|
||||
|
||||
func (t *Target) Destroy() (err error) {
|
||||
t.Close()
|
||||
t.m.Lock()
|
||||
defer t.m.Unlock()
|
||||
|
||||
// Run all the cleanup tasks on the target
|
||||
t.Close()
|
||||
|
||||
// Delete the target from the database
|
||||
_, err = t.Client().DeleteTarget(t.ctx, &vagrant_server.DeleteTargetRequest{
|
||||
Target: t.Ref().(*vagrant_plugin_sdk.Ref_Target),
|
||||
})
|
||||
@ -317,20 +460,15 @@ func (t *Target) Destroy() (err error) {
|
||||
err = multierror.Append(err, rerr)
|
||||
}
|
||||
}
|
||||
t.target = &vagrant_server.Target{}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Target) Run(ctx context.Context, task *vagrant_server.Task) (err error) {
|
||||
t.logger.Debug("running new task",
|
||||
"target", t,
|
||||
"task", task)
|
||||
|
||||
// Intialize targets
|
||||
if err = t.project.InitTargets(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, err := t.project.basis.component(
|
||||
ctx, component.CommandType, task.Component.Name)
|
||||
|
||||
@ -369,31 +507,11 @@ func (t *Target) Run(ctx context.Context, task *vagrant_server.Task) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
// LoadTarget implements originScope
|
||||
func (t *Target) LoadTarget(topts ...TargetOption) (*Target, error) {
|
||||
return nil, fmt.Errorf("targets cannot be loaded from a target")
|
||||
}
|
||||
|
||||
// Boxes implements originScope
|
||||
func (t *Target) Boxes() (core.BoxCollection, error) {
|
||||
return nil, fmt.Errorf("boxes cannot be loaded from a target")
|
||||
}
|
||||
|
||||
// Vagrantfile implements originScope / core.Target
|
||||
// Vagrantfile implements core.Target
|
||||
func (t *Target) Vagrantfile() (core.Vagrantfile, error) {
|
||||
return t.vagrantfile, nil
|
||||
}
|
||||
|
||||
// Cache implements originScope
|
||||
func (t *Target) Cache() cacher.Cache {
|
||||
return t.project.basis.cache
|
||||
}
|
||||
|
||||
// Broker implements originScope
|
||||
func (t *Target) Broker() *goplugin.GRPCBroker {
|
||||
return t.project.basis.plugins.LegacyBroker()
|
||||
}
|
||||
|
||||
func (t *Target) seed(fn func(*core.Seeds)) {
|
||||
t.project.seed(
|
||||
func(s *core.Seeds) {
|
||||
@ -418,7 +536,7 @@ func (t *Target) Machine() core.Machine {
|
||||
t.target.Record.UnmarshalTo(targetMachine)
|
||||
m := &Machine{
|
||||
Target: t,
|
||||
logger: t.logger,
|
||||
logger: t.logger.Named("machine"),
|
||||
machine: targetMachine,
|
||||
cache: cacher.New(),
|
||||
vagrantfile: t.vagrantfile,
|
||||
@ -471,104 +589,16 @@ func (t *Target) doOperation(
|
||||
return doOperation(ctx, log, t, op)
|
||||
}
|
||||
|
||||
// Initialize the target instance
|
||||
func (t *Target) init() (err error) {
|
||||
// As long as no error is encountered,
|
||||
// update the target configuration.
|
||||
defer func() {
|
||||
if err == nil {
|
||||
t.target.Configuration, err = t.vagrantfile.rootToStore()
|
||||
}
|
||||
}()
|
||||
t.logger.Info("running init on target", "target", t.target.Name)
|
||||
// Name or resource id is required for a target to be loaded
|
||||
if t.target.Name == "" && t.target.ResourceId == "" {
|
||||
return fmt.Errorf("cannot load a target without name or resource id")
|
||||
}
|
||||
|
||||
// A parent project is also required
|
||||
if t.project == nil {
|
||||
return fmt.Errorf("cannot load a target without defined project")
|
||||
}
|
||||
|
||||
// If the configuration was updated during load, save it so
|
||||
// we can re-apply after loading stored data
|
||||
var conf *vagrant_plugin_sdk.Args_ConfigData
|
||||
if t.target.Configuration != nil {
|
||||
conf = t.target.Configuration
|
||||
}
|
||||
|
||||
// Pull target info
|
||||
resp, err := t.Client().FindTarget(t.ctx,
|
||||
&vagrant_server.FindTargetRequest{
|
||||
Target: t.target,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.target = resp.Target
|
||||
|
||||
// If we have configuration data, re-apply it
|
||||
if conf != nil {
|
||||
t.target.Configuration = conf
|
||||
}
|
||||
|
||||
// Set the project into the target
|
||||
t.target.Project = t.project.Ref().(*vagrant_plugin_sdk.Ref_Project)
|
||||
|
||||
// If we have a vagrantfile attached, we're done
|
||||
if t.vagrantfile != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If we don't have configuration data, just stub
|
||||
if t.target.Configuration == nil {
|
||||
t.target.Configuration = &vagrant_plugin_sdk.Args_ConfigData{}
|
||||
t.vagrantfile = t.project.vagrantfile.clone("target", t)
|
||||
t.vagrantfile.root = &component.ConfigData{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
t.logger.Info("vagrantfile has not been defined so generating from store config",
|
||||
"name", t.target.Name,
|
||||
)
|
||||
|
||||
internal := plugin.NewInternal(
|
||||
t.project.basis.plugins.LegacyBroker(),
|
||||
t.project.basis.cache,
|
||||
t.project.basis.cleaner,
|
||||
t.logger,
|
||||
t.project.basis.mappers,
|
||||
)
|
||||
|
||||
// Load the configuration data we have
|
||||
raw, err := dynamic.Map(
|
||||
t.target.Configuration,
|
||||
(**component.ConfigData)(nil),
|
||||
argmapper.ConverterFunc(t.project.basis.mappers...),
|
||||
argmapper.Typed(
|
||||
t.ctx,
|
||||
t.logger,
|
||||
internal,
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.vagrantfile = t.project.vagrantfile.clone("target", t)
|
||||
t.vagrantfile.root = raw.(*component.ConfigData)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Options type for target loading
|
||||
type TargetOption func(*Target) error
|
||||
|
||||
func WithProject(p *Project) TargetOption {
|
||||
return func(t *Target) (err error) {
|
||||
t.project = p
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set a vagrantfile instance on target
|
||||
func WithTargetVagrantfile(v *Vagrantfile) TargetOption {
|
||||
return func(t *Target) (err error) {
|
||||
@ -587,34 +617,18 @@ func WithTargetName(name string) TargetOption {
|
||||
|
||||
// Configure target with proto ref
|
||||
func WithTargetRef(r *vagrant_plugin_sdk.Ref_Target) TargetOption {
|
||||
return func(t *Target) error {
|
||||
// Target ref must include a resource id or name
|
||||
if r.Name == "" && r.ResourceId == "" {
|
||||
return fmt.Errorf("target ref must include ResourceId and/or Name")
|
||||
return func(t *Target) (err error) {
|
||||
if r.Name != "" {
|
||||
t.target.Name = r.Name
|
||||
}
|
||||
if r.ResourceId != "" {
|
||||
t.target.ResourceId = r.ResourceId
|
||||
}
|
||||
if r.Project != nil {
|
||||
t.target.Project = r.Project
|
||||
}
|
||||
|
||||
// Target ref must include project ref if resource id is empty
|
||||
if r.Name == "" && r.Project == nil {
|
||||
return fmt.Errorf("target ref must include Project for name lookup")
|
||||
}
|
||||
|
||||
result, err := t.Client().FindTarget(t.ctx,
|
||||
&vagrant_server.FindTargetRequest{
|
||||
Target: &vagrant_server.Target{
|
||||
ResourceId: r.ResourceId,
|
||||
Name: r.Name,
|
||||
Project: r.Project,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.target = result.Target
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,3 +642,4 @@ func WithProvider(provider string) TargetOption {
|
||||
}
|
||||
|
||||
var _ core.Target = (*Target)(nil)
|
||||
var _ Scope = (*Target)(nil)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user