packer: remove Command
This commit is contained in:
parent
fa36cf82ee
commit
3e1f4ae1d2
|
@ -1,23 +0,0 @@
|
||||||
package packer
|
|
||||||
|
|
||||||
// A command is a runnable sub-command of the `packer` application.
|
|
||||||
// When `packer` is called with the proper subcommand, this will be
|
|
||||||
// called.
|
|
||||||
//
|
|
||||||
// The mapping of command names to command interfaces is in the
|
|
||||||
// Environment struct.
|
|
||||||
type Command interface {
|
|
||||||
// Help should return long-form help text that includes the command-line
|
|
||||||
// usage, a brief few sentences explaining the function of the command,
|
|
||||||
// and the complete list of flags the command accepts.
|
|
||||||
Help() string
|
|
||||||
|
|
||||||
// Run should run the actual command with the given environmet and
|
|
||||||
// command-line arguments. It should return the exit status when it is
|
|
||||||
// finished.
|
|
||||||
Run(env Environment, args []string) int
|
|
||||||
|
|
||||||
// Synopsis should return a one-line, short synopsis of the command.
|
|
||||||
// This should be less than 50 characters ideally.
|
|
||||||
Synopsis() string
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package packer
|
|
||||||
|
|
||||||
type TestCommand struct {
|
|
||||||
runArgs []string
|
|
||||||
runCalled bool
|
|
||||||
runEnv Environment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TestCommand) Help() string {
|
|
||||||
return "bar"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TestCommand) Run(env Environment, args []string) int {
|
|
||||||
tc.runCalled = true
|
|
||||||
tc.runArgs = args
|
|
||||||
tc.runEnv = env
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TestCommand) Synopsis() string {
|
|
||||||
return "foo"
|
|
||||||
}
|
|
|
@ -4,19 +4,12 @@ package packer
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The function type used to lookup Builder implementations.
|
// The function type used to lookup Builder implementations.
|
||||||
type BuilderFunc func(name string) (Builder, error)
|
type BuilderFunc func(name string) (Builder, error)
|
||||||
|
|
||||||
// The function type used to lookup Command implementations.
|
|
||||||
type CommandFunc func(name string) (Command, error)
|
|
||||||
|
|
||||||
// The function type used to lookup Hook implementations.
|
// The function type used to lookup Hook implementations.
|
||||||
type HookFunc func(name string) (Hook, error)
|
type HookFunc func(name string) (Hook, error)
|
||||||
|
|
||||||
|
@ -31,7 +24,6 @@ type ProvisionerFunc func(name string) (Provisioner, error)
|
||||||
// commands, etc.
|
// commands, etc.
|
||||||
type ComponentFinder struct {
|
type ComponentFinder struct {
|
||||||
Builder BuilderFunc
|
Builder BuilderFunc
|
||||||
Command CommandFunc
|
|
||||||
Hook HookFunc
|
Hook HookFunc
|
||||||
PostProcessor PostProcessorFunc
|
PostProcessor PostProcessorFunc
|
||||||
Provisioner ProvisionerFunc
|
Provisioner ProvisionerFunc
|
||||||
|
@ -45,7 +37,6 @@ type ComponentFinder struct {
|
||||||
type Environment interface {
|
type Environment interface {
|
||||||
Builder(string) (Builder, error)
|
Builder(string) (Builder, error)
|
||||||
Cache() Cache
|
Cache() Cache
|
||||||
Cli([]string) (int, error)
|
|
||||||
Hook(string) (Hook, error)
|
Hook(string) (Hook, error)
|
||||||
PostProcessor(string) (PostProcessor, error)
|
PostProcessor(string) (PostProcessor, error)
|
||||||
Provisioner(string) (Provisioner, error)
|
Provisioner(string) (Provisioner, error)
|
||||||
|
@ -56,7 +47,6 @@ type Environment interface {
|
||||||
// environment.
|
// environment.
|
||||||
type coreEnvironment struct {
|
type coreEnvironment struct {
|
||||||
cache Cache
|
cache Cache
|
||||||
commands []string
|
|
||||||
components ComponentFinder
|
components ComponentFinder
|
||||||
ui Ui
|
ui Ui
|
||||||
}
|
}
|
||||||
|
@ -64,22 +54,14 @@ type coreEnvironment struct {
|
||||||
// This struct configures new environments.
|
// This struct configures new environments.
|
||||||
type EnvironmentConfig struct {
|
type EnvironmentConfig struct {
|
||||||
Cache Cache
|
Cache Cache
|
||||||
Commands []string
|
|
||||||
Components ComponentFinder
|
Components ComponentFinder
|
||||||
Ui Ui
|
Ui Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
type helpCommandEntry struct {
|
|
||||||
i int
|
|
||||||
key string
|
|
||||||
synopsis string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultEnvironmentConfig returns a default EnvironmentConfig that can
|
// DefaultEnvironmentConfig returns a default EnvironmentConfig that can
|
||||||
// be used to create a new enviroment with NewEnvironment with sane defaults.
|
// be used to create a new enviroment with NewEnvironment with sane defaults.
|
||||||
func DefaultEnvironmentConfig() *EnvironmentConfig {
|
func DefaultEnvironmentConfig() *EnvironmentConfig {
|
||||||
config := &EnvironmentConfig{}
|
config := &EnvironmentConfig{}
|
||||||
config.Commands = make([]string, 0)
|
|
||||||
config.Ui = &BasicUi{
|
config.Ui = &BasicUi{
|
||||||
Reader: os.Stdin,
|
Reader: os.Stdin,
|
||||||
Writer: os.Stdout,
|
Writer: os.Stdout,
|
||||||
|
@ -98,7 +80,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error
|
||||||
|
|
||||||
env := &coreEnvironment{}
|
env := &coreEnvironment{}
|
||||||
env.cache = config.Cache
|
env.cache = config.Cache
|
||||||
env.commands = config.Commands
|
|
||||||
env.components = config.Components
|
env.components = config.Components
|
||||||
env.ui = config.Ui
|
env.ui = config.Ui
|
||||||
|
|
||||||
|
@ -109,10 +90,6 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error
|
||||||
env.components.Builder = func(string) (Builder, error) { return nil, nil }
|
env.components.Builder = func(string) (Builder, error) { return nil, nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.components.Command == nil {
|
|
||||||
env.components.Command = func(string) (Command, error) { return nil, nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
if env.components.Hook == nil {
|
if env.components.Hook == nil {
|
||||||
env.components.Hook = func(string) (Hook, error) { return nil, nil }
|
env.components.Hook = func(string) (Hook, error) { return nil, nil }
|
||||||
}
|
}
|
||||||
|
@ -199,159 +176,6 @@ func (e *coreEnvironment) Provisioner(name string) (p Provisioner, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executes a command as if it was typed on the command-line interface.
|
|
||||||
// The return value is the exit code of the command.
|
|
||||||
func (e *coreEnvironment) Cli(args []string) (result int, err error) {
|
|
||||||
log.Printf("Environment.Cli: %#v\n", args)
|
|
||||||
|
|
||||||
// If we have no arguments, just short-circuit here and print the help
|
|
||||||
if len(args) == 0 {
|
|
||||||
e.printHelp()
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This variable will track whether or not we're supposed to print
|
|
||||||
// the help or not.
|
|
||||||
isHelp := false
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == "-h" || arg == "--help" {
|
|
||||||
isHelp = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim up to the command name
|
|
||||||
for i, v := range args {
|
|
||||||
if len(v) > 0 && v[0] != '-' {
|
|
||||||
args = args[i:]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("command + args: %#v", args)
|
|
||||||
|
|
||||||
version := args[0] == "version"
|
|
||||||
if !version {
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == "--version" || arg == "-v" {
|
|
||||||
version = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var command Command
|
|
||||||
if version {
|
|
||||||
command = new(versionCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
if command == nil {
|
|
||||||
command, err = e.components.Command(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we still don't have a command, show the help.
|
|
||||||
if command == nil {
|
|
||||||
e.ui.Error(fmt.Sprintf("Unknown command: %s\n", args[0]))
|
|
||||||
e.printHelp()
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're supposed to print help, then print the help of the
|
|
||||||
// command rather than running it.
|
|
||||||
if isHelp {
|
|
||||||
e.ui.Say(command.Help())
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Executing command: %s\n", args[0])
|
|
||||||
return command.Run(e, args[1:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints the CLI help to the UI.
|
|
||||||
func (e *coreEnvironment) printHelp() {
|
|
||||||
// Created a sorted slice of the map keys and record the longest
|
|
||||||
// command name so we can better format the output later.
|
|
||||||
maxKeyLen := 0
|
|
||||||
for _, command := range e.commands {
|
|
||||||
if len(command) > maxKeyLen {
|
|
||||||
maxKeyLen = len(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the keys
|
|
||||||
sort.Strings(e.commands)
|
|
||||||
|
|
||||||
// Create the communication/sync mechanisms to get the synopsis' of
|
|
||||||
// the various commands. We do this in parallel since the overhead
|
|
||||||
// of the subprocess underneath is very expensive and this speeds things
|
|
||||||
// up an incredible amount.
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
ch := make(chan *helpCommandEntry)
|
|
||||||
|
|
||||||
for i, key := range e.commands {
|
|
||||||
wg.Add(1)
|
|
||||||
|
|
||||||
// Get the synopsis in a goroutine since it may take awhile
|
|
||||||
// to subprocess out.
|
|
||||||
go func(i int, key string) {
|
|
||||||
defer wg.Done()
|
|
||||||
var synopsis string
|
|
||||||
command, err := e.components.Command(key)
|
|
||||||
if err != nil {
|
|
||||||
synopsis = fmt.Sprintf("Error loading command: %s", err.Error())
|
|
||||||
} else if command == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
synopsis = command.Synopsis()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pad the key with spaces so that they're all the same width
|
|
||||||
key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key)))
|
|
||||||
|
|
||||||
// Output the command and the synopsis
|
|
||||||
ch <- &helpCommandEntry{
|
|
||||||
i: i,
|
|
||||||
key: key,
|
|
||||||
synopsis: synopsis,
|
|
||||||
}
|
|
||||||
}(i, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.ui.Say("usage: packer [--version] [--help] <command> [<args>]\n")
|
|
||||||
e.ui.Say("Available commands are:")
|
|
||||||
|
|
||||||
// Make a goroutine that just waits for all the synopsis gathering
|
|
||||||
// to complete, and then output it.
|
|
||||||
synopsisDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(synopsisDone)
|
|
||||||
entries := make([]string, len(e.commands))
|
|
||||||
|
|
||||||
for entry := range ch {
|
|
||||||
e.ui.Machine("command", entry.key, entry.synopsis)
|
|
||||||
message := fmt.Sprintf(" %s %s", entry.key, entry.synopsis)
|
|
||||||
entries[entry.i] = message
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, message := range entries {
|
|
||||||
if message != "" {
|
|
||||||
e.ui.Say(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait to complete getting the synopsis' then close the channel
|
|
||||||
wg.Wait()
|
|
||||||
close(ch)
|
|
||||||
<-synopsisDone
|
|
||||||
|
|
||||||
e.ui.Say("\nGlobally recognized options:")
|
|
||||||
e.ui.Say(" -machine-readable Machine-readable output format.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the UI for the environment. The UI is the interface that should
|
// Returns the UI for the environment. The UI is the interface that should
|
||||||
// be used for all communication with the outside world.
|
// be used for all communication with the outside world.
|
||||||
func (e *coreEnvironment) Ui() Ui {
|
func (e *coreEnvironment) Ui() Ui {
|
||||||
|
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,13 +41,6 @@ func testEnvironment() Environment {
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvironment_DefaultConfig_Commands(t *testing.T) {
|
|
||||||
config := DefaultEnvironmentConfig()
|
|
||||||
if len(config.Commands) != 0 {
|
|
||||||
t.Fatalf("bad: %#v", config.Commands)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
|
func TestEnvironment_DefaultConfig_Ui(t *testing.T) {
|
||||||
config := DefaultEnvironmentConfig()
|
config := DefaultEnvironmentConfig()
|
||||||
if config.Ui == nil {
|
if config.Ui == nil {
|
||||||
|
@ -91,7 +82,6 @@ func TestEnvironment_NilComponents(t *testing.T) {
|
||||||
// anything but if there is a panic in the test then yeah, something
|
// anything but if there is a panic in the test then yeah, something
|
||||||
// went wrong.
|
// went wrong.
|
||||||
env.Builder("foo")
|
env.Builder("foo")
|
||||||
env.Cli([]string{"foo"})
|
|
||||||
env.Hook("foo")
|
env.Hook("foo")
|
||||||
env.PostProcessor("foo")
|
env.PostProcessor("foo")
|
||||||
env.Provisioner("foo")
|
env.Provisioner("foo")
|
||||||
|
@ -154,117 +144,6 @@ func TestEnvironment_Cache(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnvironment_Cli_Error(t *testing.T) {
|
|
||||||
config := DefaultEnvironmentConfig()
|
|
||||||
config.Components.Command = func(n string) (Command, error) { return nil, errors.New("foo") }
|
|
||||||
|
|
||||||
env, _ := NewEnvironment(config)
|
|
||||||
_, err := env.Cli([]string{"foo"})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("should have error")
|
|
||||||
}
|
|
||||||
if err.Error() != "foo" {
|
|
||||||
t.Fatalf("bad: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironment_Cli_CallsRun(t *testing.T) {
|
|
||||||
command := &TestCommand{}
|
|
||||||
commands := make(map[string]Command)
|
|
||||||
commands["foo"] = command
|
|
||||||
|
|
||||||
config := &EnvironmentConfig{}
|
|
||||||
config.Commands = []string{"foo"}
|
|
||||||
config.Components.Command = func(n string) (Command, error) { return commands[n], nil }
|
|
||||||
|
|
||||||
env, _ := NewEnvironment(config)
|
|
||||||
exitCode, err := env.Cli([]string{"foo", "bar", "baz"})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if exitCode != 0 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
if !command.runCalled {
|
|
||||||
t.Fatal("command should be run")
|
|
||||||
}
|
|
||||||
if command.runEnv != env {
|
|
||||||
t.Fatalf("bad env: %#v", command.runEnv)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(command.runArgs, []string{"bar", "baz"}) {
|
|
||||||
t.Fatalf("bad: %#v", command.runArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironment_DefaultCli_Empty(t *testing.T) {
|
|
||||||
defaultEnv := testEnvironment()
|
|
||||||
|
|
||||||
// Test with no args
|
|
||||||
exitCode, _ := defaultEnv.Cli([]string{})
|
|
||||||
if exitCode != 1 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with only blank args
|
|
||||||
exitCode, _ = defaultEnv.Cli([]string{""})
|
|
||||||
if exitCode != 1 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironment_DefaultCli_Help(t *testing.T) {
|
|
||||||
defaultEnv := testEnvironment()
|
|
||||||
|
|
||||||
// A little lambda to help us test the output actually contains help
|
|
||||||
testOutput := func() {
|
|
||||||
buffer := defaultEnv.Ui().(*BasicUi).Writer.(*bytes.Buffer)
|
|
||||||
output := buffer.String()
|
|
||||||
buffer.Reset()
|
|
||||||
if !strings.Contains(output, "usage: packer") {
|
|
||||||
t.Fatalf("should contain help: %#v", output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test "--help"
|
|
||||||
exitCode, _ := defaultEnv.Cli([]string{"--help"})
|
|
||||||
if exitCode != 1 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
testOutput()
|
|
||||||
|
|
||||||
// Test "-h"
|
|
||||||
exitCode, _ = defaultEnv.Cli([]string{"--help"})
|
|
||||||
if exitCode != 1 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
testOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironment_DefaultCli_Version(t *testing.T) {
|
|
||||||
defaultEnv := testEnvironment()
|
|
||||||
|
|
||||||
versionCommands := []string{"version", "--version", "-v"}
|
|
||||||
for _, command := range versionCommands {
|
|
||||||
exitCode, _ := defaultEnv.Cli([]string{command})
|
|
||||||
if exitCode != 0 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the --version and -v can appear anywhere
|
|
||||||
exitCode, _ = defaultEnv.Cli([]string{"bad", command})
|
|
||||||
|
|
||||||
if command != "version" {
|
|
||||||
if exitCode != 0 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if exitCode != 1 {
|
|
||||||
t.Fatalf("bad: %d", exitCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvironment_Hook(t *testing.T) {
|
func TestEnvironment_Hook(t *testing.T) {
|
||||||
hook := &MockHook{}
|
hook := &MockHook{}
|
||||||
hooks := make(map[string]Hook)
|
hooks := make(map[string]Hook)
|
||||||
|
|
Loading…
Reference in New Issue