Basis cleanup, docs, and mapper usage for command info setup

This commit is contained in:
Chris Roberts 2021-07-12 11:44:38 -07:00 committed by Paul Hinze
parent 79f22407ba
commit 2cc1ba75ce
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0

View File

@ -2,7 +2,6 @@ package core
import (
"context"
"errors"
"fmt"
"strings"
"sync"
@ -36,14 +35,15 @@ import (
// finished with the basis to properly clean
// up any open resources.
type Basis struct {
basis *vagrant_server.Basis
logger hclog.Logger
config *config.Config
projects map[string]*Project
factories map[component.Type]*factory.Factory
mappers []*argmapper.Func
dir *datadir.Basis
ctx context.Context
basis *vagrant_server.Basis
logger hclog.Logger
config *config.Config
components map[component.Type]map[string]*Component
projects map[string]*Project
factories map[component.Type]*factory.Factory
mappers []*argmapper.Func
dir *datadir.Basis
ctx context.Context
lock sync.Mutex
client *serverclient.VagrantClient
@ -80,12 +80,12 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
}
if b.basis == nil {
return nil, errors.New("basis data was not properly loaded")
return nil, fmt.Errorf("basis data was not properly loaded")
}
// Client is required to be provided
if b.client == nil {
return nil, errors.New("client was not provided to basis")
return nil, fmt.Errorf("client was not provided to basis")
}
// If we don't have a data directory set, lets do that now
@ -102,7 +102,7 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
// If the mappers aren't already set, load known mappers
if len(b.mappers) == 0 {
b.mappers, err = argmapper.NewFuncList(protomappers.All,
argmapper.Logger(dynamicLogger),
argmapper.Logger(dynamic.Logger),
)
if err != nil {
@ -116,7 +116,8 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
// configuration loading
if b.config == nil {
if b.config, err = config.Load("", ""); err != nil {
b.logger.Warn("failed to load config, using stub", "error", err)
b.logger.Warn("failed to load config, using stub",
"error", err)
b.config = &config.Config{}
err = nil
}
@ -125,18 +126,38 @@ func NewBasis(ctx context.Context, opts ...BasisOption) (b *Basis, err error) {
// Ensure any modifications to the basis are persisted
b.Closer(func() error { return b.Save() })
// Setup our caching area for components
b.components = map[component.Type]map[string]*Component{}
for t, _ := range component.TypeMap {
b.components[t] = map[string]*Component{}
}
// Add in our 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)
}
b.logger.Info("basis initialized")
return
}
// Basis UI is the "default" UI with no prefix modifications
func (b *Basis) UI() (terminal.UI, error) {
return b.ui, nil
}
// Data directory used for this basis
func (b *Basis) DataDir() (*datadir.Basis, error) {
return b.dir, nil
}
// Generic function for providing ref to a scope
func (b *Basis) Ref() interface{} {
return &vagrant_plugin_sdk.Ref_Basis{
ResourceId: b.ResourceId(),
@ -144,6 +165,7 @@ func (b *Basis) Ref() interface{} {
}
}
// Custom name defined for this basis
func (b *Basis) Name() string {
if b.basis == nil {
return ""
@ -152,6 +174,7 @@ func (b *Basis) Name() string {
return b.basis.Name
}
// Resource ID for this basis
func (b *Basis) ResourceId() string {
if b.basis == nil {
return ""
@ -160,23 +183,39 @@ func (b *Basis) ResourceId() string {
return b.basis.ResourceId
}
// Returns the job info if currently set
func (b *Basis) JobInfo() *component.JobInfo {
return b.jobInfo
}
// Client connection to the Vagrant server
func (b *Basis) Client() *serverclient.VagrantClient {
return b.client
}
// Returns the detected host for the current platform
func (b *Basis) Host() (host core.Host, err error) {
h, err := b.findHostPlugin(b.ctx)
hosts, err := b.typeComponents(b.ctx, component.HostType)
if err != nil {
return
return nil, err
}
host = h.Value.(core.Host)
return
for _, h := range hosts {
host := h.Value.(component.Host)
fn := host.DetectFunc()
detected, err := b.callDynamicFunc(b.ctx, b.logger, fn, (*bool)(nil))
if err != nil {
return nil, err
}
if detected.(bool) {
return host.(core.Host), nil
}
}
return nil, fmt.Errorf("failed to detect host plugin for current platform")
}
// 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) {
b.logger.Debug("running init for basis")
f := b.factories[component.CommandType]
@ -197,19 +236,27 @@ func (b *Basis) Init() (result *vagrant_server.Job_InitResult, err error) {
}
fn := cmd.Value.(component.Command).CommandInfoFunc()
raw, err := b.callDynamicFunc(ctx, b.logger, fn, (**component.CommandInfo)(nil))
raw, err := b.callDynamicFunc(ctx, b.logger, fn,
(*[]*vagrant_server.Job_Command)(nil))
if err != nil {
return nil, err
}
result.Commands = append(result.Commands,
b.convertCommandInfo(raw.(*component.CommandInfo), []string{})...)
raw.([]*vagrant_server.Job_Command)...)
}
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
@ -222,6 +269,8 @@ func (b *Basis) Project(nameOrId string) *Project {
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) {
// Create our project
p = &Project{
@ -279,21 +328,27 @@ func (b *Basis) LoadProject(popts ...ProjectOption) (p *Project, err error) {
return
}
// Register functions to be called when closing this basis
func (b *Basis) Closer(c func() error) {
b.closers = append(b.closers, c)
}
// Close the basis. This should be called when work with this
// basis has been completed.
func (b *Basis) Close() (err error) {
defer b.lock.Unlock()
b.lock.Lock()
b.logger.Debug("closing basis", "basis", b.ResourceId())
b.logger.Debug("closing basis",
"basis", b.ResourceId())
// Close down any projects that were loaded
for name, p := range b.projects {
b.logger.Trace("closing project", "project", name)
b.logger.Trace("closing project",
"project", name)
if cerr := p.Close(); cerr != nil {
b.logger.Warn("error closing project", "project", name,
b.logger.Warn("error closing project",
"project", name,
"error", cerr)
err = multierror.Append(err, cerr)
}
@ -302,7 +357,8 @@ func (b *Basis) Close() (err error) {
// Call any closers that were registered locally
for _, c := range b.closers {
if cerr := c(); cerr != nil {
b.logger.Warn("error executing closer", "error", cerr)
b.logger.Warn("error executing closer",
"error", cerr)
err = multierror.Append(err, cerr)
}
}
@ -312,22 +368,38 @@ func (b *Basis) Close() (err error) {
// Saves the basis to the db
func (b *Basis) Save() (err error) {
b.logger.Debug("saving basis to db", "basis", b.ResourceId())
_, err = b.Client().UpsertBasis(b.ctx, &vagrant_server.UpsertBasisRequest{
Basis: b.basis})
b.logger.Debug("saving basis to db",
"basis", b.ResourceId())
_, err = b.Client().UpsertBasis(b.ctx,
&vagrant_server.UpsertBasisRequest{
Basis: b.basis})
if err != nil {
b.logger.Trace("failed to save basis", "basis", b.ResourceId(), "error", err)
b.logger.Trace("failed to save basis",
"basis", b.ResourceId(),
"error", err)
}
return
}
// Saves the basis to the db as well as any projects that have been loaded
// 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.ResourceId())
b.logger.Debug("performing full save",
"basis", b.ResourceId())
for _, p := range b.projects {
b.logger.Trace("saving project", "basis", b.ResourceId(), "project", p.ResourceId())
b.logger.Trace("saving project",
"basis", b.ResourceId(),
"project", p.ResourceId())
if perr := p.SaveFull(); perr != nil {
b.logger.Trace("error while saving project", "project", p.ResourceId(), "error", err)
b.logger.Trace("error while saving project",
"project", p.ResourceId(),
"error", err)
err = multierror.Append(err, perr)
}
}
@ -337,6 +409,7 @@ func (b *Basis) SaveFull() (err error) {
return
}
// Returns the list of all known components
func (b *Basis) Components(ctx context.Context) ([]*Component, error) {
var results []*Component
for _, cc := range componentCreatorMap {
@ -362,8 +435,12 @@ func (b *Basis) Components(ctx context.Context) ([]*Component, error) {
return results, nil
}
// Runs a specific task via component which matches the task's
// 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)
b.logger.Debug("running new task",
"basis", b,
"task", task)
// Build the component to run
cmd, err := b.component(ctx, component.CommandType, task.Component.Name)
@ -393,40 +470,53 @@ func (b *Basis) Run(ctx context.Context, task *vagrant_server.Task) (err error)
return
}
func (b *Basis) findHostPlugin(ctx context.Context) (*Component, error) {
f := b.factories[component.HostType]
for _, name := range f.Registered() {
if name != "myplugin" {
continue
}
h, err := componentCreatorMap[component.HostType].Create(ctx, b, name)
if err != nil {
return nil, err
}
fn := h.Value.(component.Host).DetectFunc()
detected, err := b.callDynamicFunc(ctx, b.logger, fn, (*bool)(nil))
if err != nil {
return nil, err
}
if detected.(bool) {
return h, nil
}
// h.Close()
}
return nil, errors.New("host plugin not found")
}
func (b *Basis) component(ctx context.Context, typ component.Type, name string) (*Component, error) {
// Load a specific component
func (b *Basis) component(
ctx context.Context, // context for the plugin
typ component.Type, // type of component
name string, // name of the component
) (*Component, error) {
// If this is a command type component, the plugin is registered
// as only the root command
if typ == component.CommandType {
name = strings.Split(name, " ")[0]
}
return componentCreatorMap[typ].Create(ctx, b, name)
if c, ok := b.components[typ][name]; ok {
return c, nil
}
c, err := componentCreatorMap[typ].Create(ctx, b, name)
if err != nil {
return nil, err
}
b.Closer(func() error { return c.Close() })
return c, nil
}
func (b *Basis) specializeComponent(c *Component) (cmp plugin.PluginMetadata, err error) {
// Load all components of a specific type
func (b *Basis) typeComponents(
ctx context.Context, // context for the plugins,
typ component.Type, // type of the components,
) ([]*Component, error) {
f := b.factories[typ]
result := []*Component{}
for _, name := range f.Registered() {
c, err := b.component(ctx, typ, name)
if err != nil {
return nil, err
}
result = append(result, c)
}
return result, nil
}
// Specialize a given component. This is specifically used for
// Ruby based legacy Vagrant components.
//
// TODO: Since legacy Vagrant is no longer directly connecting
// to the Vagrant server, this shoudl probably be removed.
func (b *Basis) specializeComponent(
c *Component, // component to specialize
) (cmp plugin.PluginMetadata, err error) {
var ok bool
if cmp, ok = c.Value.(plugin.PluginMetadata); !ok {
return nil, fmt.Errorf("component does not support specialization")
@ -437,31 +527,15 @@ func (b *Basis) specializeComponent(c *Component) (cmp plugin.PluginMetadata, er
return
}
func (b *Basis) convertCommandInfo(c *component.CommandInfo, names []string) []*vagrant_server.Job_Command {
names = append(names, c.Name)
cmds := []*vagrant_server.Job_Command{
{
Name: strings.Join(names, " "),
Synopsis: c.Synopsis,
Help: c.Help,
Flags: FlagsToProtoMapper(c.Flags),
},
}
for _, scmd := range c.Subcommands {
cmds = append(cmds, b.convertCommandInfo(scmd, names)...)
}
return cmds
}
// startPlugin starts a plugin with the given type and name. The returned
// value must be closed to clean up the plugin properly.
func (b *Basis) startPlugin(
ctx context.Context,
typ component.Type,
n string,
ctx context.Context, // context used for the plugin
typ component.Type, // type of component plugin to start
n string, // name of component plugin to start
) (*plugin.Instance, error) {
log := b.logger.ResetNamed(fmt.Sprintf("vagrant.plugin.%s.%s", strings.ToLower(typ.String()), n))
log := b.logger.ResetNamed(
fmt.Sprintf("vagrant.plugin.%s.%s", strings.ToLower(typ.String()), n))
f, ok := b.factories[typ]
if !ok {
@ -475,11 +549,16 @@ func (b *Basis) startPlugin(
}
// Call the factory to get our raw value (interface{} type)
fnResult := fn.Call(argmapper.Typed(ctx, log), argmapper.Logger(dynamicLogger))
fnResult := fn.Call(argmapper.Typed(ctx, log),
argmapper.Logger(dynamic.Logger))
if err := fnResult.Err(); err != nil {
return nil, err
}
log.Info("initialized component", "type", typ.String(), "name", n)
log.Info("initialized component", "type",
typ.String(),
"name", n)
raw := fnResult.Out(0)
// If we have a plugin.Instance then we can extract other information
@ -600,6 +679,7 @@ func WithJobInfo(info *component.JobInfo) BasisOption {
}
}
// WithBasisDataDir customizes the datadir for the Basis
func WithBasisDataDir(dir *datadir.Basis) BasisOption {
return func(b *Basis) (err error) {
b.dir = dir
@ -607,6 +687,7 @@ 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
@ -663,7 +744,7 @@ func WithBasisResourceId(rid string) BasisOption {
}
if !result.Found {
b.logger.Error("failed to locate basis during setup", "resource-id", rid)
return errors.New("requested basis is not found")
return fmt.Errorf("requested basis is not found")
}
b.basis = result.Basis
return
@ -671,8 +752,3 @@ func WithBasisResourceId(rid string) BasisOption {
}
var _ core.Basis = (*Basis)(nil)
var dynamicLogger hclog.Logger = hclog.New(&hclog.LoggerOptions{
Name: "vagrant.core.dynamic-function",
Level: hclog.Error,
})