This also removes the "Running (command)..." output since users will expect to be able to redirect the ssh-config output to a file without that noise. If we really like the "Running" thing we can try and swing around and get fancy by detecting whether or not we have a terminal or a file as output and doing conditional stuff, but this seemed like the simplest way forward for now.
164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
|
"github.com/hashicorp/vagrant/internal/client"
|
|
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
|
)
|
|
|
|
type DynamicCommand struct {
|
|
*baseCommand
|
|
|
|
name string
|
|
synopsis string
|
|
help string
|
|
parent *DynamicCommand
|
|
flags []*component.CommandFlag
|
|
}
|
|
|
|
func (c *DynamicCommand) Run(args []string) int {
|
|
if err := c.Init(
|
|
WithArgs(args),
|
|
WithFlags(c.Flags()),
|
|
); err != nil {
|
|
return 1
|
|
}
|
|
|
|
var r *vagrant_server.Job_RunResult
|
|
err := c.Do(c.Ctx, func(ctx context.Context, cl *client.Client, modifier client.JobModifier) (err error) {
|
|
taskArgs := &vagrant_plugin_sdk.Command_Arguments{
|
|
Args: c.args,
|
|
Flags: []*vagrant_plugin_sdk.Command_Arguments_Flag{},
|
|
}
|
|
for f, v := range c.flagData {
|
|
cmdFlag := &vagrant_plugin_sdk.Command_Arguments_Flag{Name: f.LongName}
|
|
switch f.Type {
|
|
case component.FlagBool:
|
|
cmdFlag.Type = vagrant_plugin_sdk.Command_Arguments_Flag_BOOL
|
|
cmdFlag.Value = &vagrant_plugin_sdk.Command_Arguments_Flag_Bool{
|
|
Bool: v.(bool),
|
|
}
|
|
case component.FlagString:
|
|
cmdFlag.Type = vagrant_plugin_sdk.Command_Arguments_Flag_STRING
|
|
cmdFlag.Value = &vagrant_plugin_sdk.Command_Arguments_Flag_String_{
|
|
String_: v.(string),
|
|
}
|
|
}
|
|
taskArgs.Flags = append(taskArgs.Flags, cmdFlag)
|
|
}
|
|
|
|
c.Log.Debug("collected argument flags",
|
|
"flags", taskArgs.Flags,
|
|
"args", args,
|
|
"remaining", c.args,
|
|
)
|
|
|
|
t := &vagrant_server.Task{
|
|
Task: c.name,
|
|
Component: &vagrant_server.Component{
|
|
Type: vagrant_server.Component_COMMAND,
|
|
Name: c.name,
|
|
},
|
|
CliArgs: taskArgs,
|
|
CommandName: c.name,
|
|
}
|
|
|
|
if c.basis != nil {
|
|
t.Scope = &vagrant_server.Task_Basis{
|
|
Basis: c.basis.Ref(),
|
|
}
|
|
}
|
|
|
|
if c.project != nil {
|
|
t.Scope = &vagrant_server.Task_Project{
|
|
Project: c.project.Ref(),
|
|
}
|
|
}
|
|
|
|
if c.target != nil {
|
|
t.Scope = &vagrant_server.Task_Target{
|
|
Target: c.target.Ref(),
|
|
}
|
|
}
|
|
|
|
r, err = cl.Task(ctx,
|
|
&vagrant_server.Job_RunOp{Task: t},
|
|
modifier,
|
|
)
|
|
|
|
// If nothing failed but we didn't get a Result back, something may
|
|
// have gone wrong on the far side so we need to interpret the error.
|
|
if err == nil && !r.RunResult {
|
|
runErrorStatus := status.FromProto(r.RunError)
|
|
details := runErrorStatus.Details()
|
|
userError := false
|
|
for _, msg := range details {
|
|
switch m := msg.(type) {
|
|
case *errdetails.LocalizedMessage:
|
|
// Errors from Ruby with LocalizedMessages are user-facing,
|
|
// so can be output directly.
|
|
userError = true
|
|
cl.UI().Output(m.Message, terminal.WithErrorStyle())
|
|
// All user-facing errors from Ruby use a 1 exit code. See
|
|
// Vagrant::Errors::VagrantError.
|
|
r.ExitCode = 1
|
|
}
|
|
}
|
|
// If there wasn't a user-facing error, just assign the returned
|
|
// error (if any) from the response and assign that back out so it
|
|
// can be displayed as an unexpected error.
|
|
if !userError {
|
|
err = runErrorStatus.Err()
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
cl.UI().Output("Running of task "+c.name+" failed unexpectedly\n", terminal.WithErrorStyle())
|
|
cl.UI().Output("Error: "+err.Error(), terminal.WithErrorStyle())
|
|
}
|
|
|
|
c.Log.Debug("result from operation", "task", c.name, "result", r)
|
|
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
c.Log.Error("Got error from task, so exiting 255", "error", err)
|
|
return int(-1)
|
|
}
|
|
|
|
c.Log.Info("Task did not error, so exiting with provided code", "code", r.ExitCode)
|
|
return int(r.ExitCode)
|
|
}
|
|
|
|
func (c *DynamicCommand) Synopsis() string {
|
|
return c.synopsis
|
|
}
|
|
|
|
func (c *DynamicCommand) Help() string {
|
|
fset := c.generateCliFlags(c.Flags())
|
|
return formatHelp(fmt.Sprintf("%s\n%s\n", c.help, fset.Display()))
|
|
}
|
|
|
|
func (c *DynamicCommand) Flags() component.CommandFlags {
|
|
return c.flagSet(flagSetOperation, func(opts []*component.CommandFlag) []*component.CommandFlag {
|
|
return append(c.flags, opts...)
|
|
})
|
|
}
|
|
|
|
func (c *DynamicCommand) fullName() string {
|
|
var v string
|
|
if c.parent != nil {
|
|
v = c.parent.fullName() + " "
|
|
}
|
|
return v + c.name
|
|
}
|