// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package core import ( "context" "github.com/hashicorp/go-hclog" "github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cacher" "github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cleanup" "github.com/hashicorp/vagrant-plugin-sdk/terminal" "github.com/hashicorp/vagrant/internal/plugin" "github.com/hashicorp/vagrant/internal/serverclient" ) type Scope interface { Close() error Closer(func() error) Init() error Reload() error ResourceId() (string, error) Save() error } type Factory struct { cache cacher.Cache cleanup cleanup.Cleanup client *serverclient.VagrantClient ctx context.Context logger hclog.Logger plugins *plugin.Manager ui terminal.UI } func NewFactory( ctx context.Context, client *serverclient.VagrantClient, logger hclog.Logger, plugins *plugin.Manager, ui terminal.UI, ) *Factory { return &Factory{ cache: cacher.New(), ctx: ctx, cleanup: cleanup.New(), client: client, logger: logger.Named("factory"), plugins: plugins, ui: ui, } } func (f *Factory) Closer(fn cleanup.CleanupFn) { f.cleanup.Do(fn) } func (f *Factory) Close() error { f.logger.Trace("closing factory") return f.cleanup.Close() } func (f *Factory) NewBasis(resourceId string, opts ...BasisOption) (*Basis, error) { f.logger.Trace("factory basis load started") defer func() { f.logger.Trace("factory basis load completed") }() // If we have a name, check if it's registered and return // the existing basis if available if resourceId != "" { if b, ok := f.cache.Fetch(resourceId); ok { return b.(*Basis), nil } } opts = append(opts, WithFactory(f), ) b, err := NewBasis(f.ctx, opts...) if err != nil { return nil, err } // Set any unset values which we can provide if b.client == nil { b.client = f.client } if b.logger == nil { b.logger = f.logger } if b.ui == nil { b.ui = f.ui } if b.plugins == nil { b.plugins = f.plugins } // Now that we have the basis information loaded, check if // we have a cached version and return that if so if existingB, ok := f.cache.Fetch(b.basis.ResourceId); ok { f.logger.Debug("found existing basis in cache, closing new instance") if err := b.Close(); err != nil { return nil, err } return existingB.(*Basis), nil } // Initialize the basis to complete setup if err = b.Init(); err != nil { b.logger.Error("failed to initialize basis", "error", err, ) b.Close() return nil, err } // Now that basis is fully setup, add it to the cache f.cache.Register(b.basis.ResourceId, b) // Remove the basis from the cache when closed b.Closer(func() error { f.cache.Delete(b.basis.ResourceId) return nil }) // When the factory is closed, ensure this basis is closed f.Closer(func() (err error) { return b.Close() }) return b, nil } func (f *Factory) NewProject(popts ...ProjectOption) (*Project, error) { f.logger.Trace("factory project load started") defer func() { f.logger.Trace("factory project load completed") }() // Get a new project instance p, err := NewProject(popts...) if err != nil { return nil, err } // Attach our logger p.logger = f.logger // Set the client directly so we can attempt to reload if p.client == nil { p.client = f.client } // Set the factory so we can load basis if required if p.factory == nil { p.factory = f } // If the resource id isn't set, attempt a reload. We // don't care if it fails, at this point. If it is // successful, it will allow us to properly check // the cache if p.project.ResourceId == "" { _ = p.Reload() } // Check if we already have an instance loaded if p.project.ResourceId != "" { if project, ok := f.cache.Fetch(p.project.ResourceId); ok { f.logger.Debug("found existing project in cache, closing new instance") if err = p.Close(); err != nil { return nil, err } return project.(*Project), nil } } // Initialize the project so it is ready for use if err = p.Init(); err != nil { return nil, err } // Close the project when the basis is closed p.basis.Closer(func() error { return p.Close() }) // Cache the project f.cache.Register(p.project.ResourceId, p) // Remove the project from the cache when closed p.Closer(func() error { f.cache.Delete(p.project.ResourceId) return nil }) return p, nil } func (f *Factory) NewTarget(topts ...TargetOption) (*Target, error) { f.logger.Trace("factory target load started") defer func() { f.logger.Trace("factory target load completed") }() // Get a new target instance t, err := NewTarget(topts...) if err != nil { return nil, err } // Attach our logger t.logger = f.logger // Set the client directly so we can attempt to reload if t.client == nil { t.client = f.client } // Set the client directly so we can attempt to reload if t.factory == nil { t.factory = f } // If the resource id isn't set, attempt a reload. We // don't care if it fails, at this point. If it is // successful, it will allow us to properly check // the cache if t.target.ResourceId == "" { _ = t.Reload() } // Check if we already have an instance loaded if t.target.ResourceId != "" { if target, ok := f.cache.Fetch(t.target.ResourceId); ok { f.logger.Debug("found existing target in cache, closing new instance") if err = t.Close(); err != nil { return nil, err } return target.(*Target), nil } } // If we have no project set, load the project if t.project == nil && t.target.Project != nil { t.project, err = f.NewProject( WithProjectRef(t.target.Project), ) if err != nil { return nil, err } } // Initialize the target so it is ready for use if err = t.Init(); err != nil { return nil, err } // Close the target when the project is closed t.project.Closer(func() error { return t.Close() }) // Cache the target f.cache.Register(t.target.ResourceId, t) // Remove the target from the cache when closed t.Closer(func() error { f.cache.Delete(t.target.ResourceId) return nil }) return t, nil }