vaguerent/internal/core/factory.go

273 lines
6.0 KiB
Go

// 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
}