From 8ed313e7b542b706a756165c13b8215f92d32dc0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 10 May 2013 23:15:13 -0700 Subject: [PATCH] packer: Add concept of hooks to Environment --- packer.go | 4 +-- packer/build.go | 1 + packer/environment.go | 64 +++++++++++++++++++++++++++-------- packer/environment_test.go | 68 +++++++++++++++++++++++++++++++++++--- packer/hook_test.go | 9 +++++ 5 files changed, 126 insertions(+), 20 deletions(-) create mode 100644 packer/hook_test.go diff --git a/packer.go b/packer.go index ce2936100..7237c4e14 100644 --- a/packer.go +++ b/packer.go @@ -84,9 +84,9 @@ func main() { } envConfig := packer.DefaultEnvironmentConfig() - envConfig.BuilderFunc = config.LoadBuilder envConfig.Commands = config.CommandNames() - envConfig.CommandFunc = config.LoadCommand + envConfig.Components.Builder = config.LoadBuilder + envConfig.Components.Command = config.LoadCommand env, err := packer.NewEnvironment(envConfig) if err != nil { diff --git a/packer/build.go b/packer/build.go index a2c455ee2..cb90b6950 100644 --- a/packer/build.go +++ b/packer/build.go @@ -17,6 +17,7 @@ type Build interface { type coreBuild struct { name string builder Builder + hooks map[string]Hook rawConfig interface{} prepareCalled bool diff --git a/packer/environment.go b/packer/environment.go index 3917743bc..287c89b41 100644 --- a/packer/environment.go +++ b/packer/environment.go @@ -16,31 +16,42 @@ 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. +type HookFunc func(name string) (Hook, error) + +// ComponentFinder is a struct that contains the various function +// pointers necessary to look up components of Packer such as builders, +// commands, etc. +type ComponentFinder struct { + Builder BuilderFunc + Command CommandFunc + Hook HookFunc +} + // The environment interface provides access to the configuration and // state of a single Packer run. // // It allows for things such as executing CLI commands, getting the // list of available builders, and more. type Environment interface { - Builder(name string) (Builder, error) - Cli(args []string) (int, error) + Builder(string) (Builder, error) + Cli([]string) (int, error) + Hook(string) (Hook, error) Ui() Ui } // An implementation of an Environment that represents the Packer core // environment. type coreEnvironment struct { - builderFunc BuilderFunc commands []string - commandFunc CommandFunc + components ComponentFinder ui Ui } // This struct configures new environments. type EnvironmentConfig struct { - BuilderFunc BuilderFunc - CommandFunc CommandFunc Commands []string + Components ComponentFinder Ui Ui } @@ -48,8 +59,6 @@ type EnvironmentConfig struct { // be used to create a new enviroment with NewEnvironment with sane defaults. func DefaultEnvironmentConfig() *EnvironmentConfig { config := &EnvironmentConfig{} - config.BuilderFunc = func(string) (Builder, error) { return nil, nil } - config.CommandFunc = func(string) (Command, error) { return nil, nil } config.Commands = make([]string, 0) config.Ui = &ReaderWriterUi{os.Stdin, os.Stdout} return config @@ -63,11 +72,25 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error } env := &coreEnvironment{} - env.builderFunc = config.BuilderFunc - env.commandFunc = config.CommandFunc env.commands = config.Commands + env.components = config.Components env.ui = config.Ui + // We want to make sure the components have valid function pointers. + // If a function pointer was not given, we assume that the function + // will just return a nil component. + if env.components.Builder == 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 { + env.components.Hook = func(string) (Hook, error) { return nil, nil } + } + resultEnv = env return } @@ -75,7 +98,7 @@ func NewEnvironment(config *EnvironmentConfig) (resultEnv Environment, err error // Returns a builder of the given name that is registered with this // environment. func (e *coreEnvironment) Builder(name string) (b Builder, err error) { - b, err = e.builderFunc(name) + b, err = e.components.Builder(name) if err != nil { return } @@ -87,6 +110,21 @@ func (e *coreEnvironment) Builder(name string) (b Builder, err error) { return } +// Returns a hook of the given name that is registered with this +// environment. +func (e *coreEnvironment) Hook(name string) (h Hook, err error) { + h, err = e.components.Hook(name) + if err != nil { + return + } + + if h == nil { + err = fmt.Errorf("No hook returned for name: %s", name) + } + + 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) { @@ -113,7 +151,7 @@ func (e *coreEnvironment) Cli(args []string) (result int, err error) { } if command == nil { - command, err = e.commandFunc(args[0]) + command, err = e.components.Command(args[0]) if err != nil { return } @@ -152,7 +190,7 @@ func (e *coreEnvironment) printHelp() { for _, key := range e.commands { var synopsis string - command, err := e.commandFunc(key) + command, err := e.components.Command(key) if err != nil { synopsis = fmt.Sprintf("Error loading command: %s", err.Error()) } else if command == nil { diff --git a/packer/environment_test.go b/packer/environment_test.go index 2f16b4090..08bcc11c3 100644 --- a/packer/environment_test.go +++ b/packer/environment_test.go @@ -52,6 +52,23 @@ func TestNewEnvironment_NoConfig(t *testing.T) { assert.NotNil(err, "should be an error") } +func TestEnvironment_NilComponents(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + config := DefaultEnvironmentConfig() + config.Components = *new(ComponentFinder) + + env, err := NewEnvironment(config) + assert.Nil(err, "should not have an error") + + // All of these should not cause panics... so we don't assert + // anything but if there is a panic in the test then yeah, something + // went wrong. + env.Builder("foo") + env.Cli([]string{"foo"}) + env.Hook("foo") +} + func TestEnvironment_Builder(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) @@ -60,7 +77,7 @@ func TestEnvironment_Builder(t *testing.T) { builders["foo"] = builder config := DefaultEnvironmentConfig() - config.BuilderFunc = func(n string) (Builder, error) { return builders[n], nil } + config.Components.Builder = func(n string) (Builder, error) { return builders[n], nil } env, _ := NewEnvironment(config) returnedBuilder, err := env.Builder("foo") @@ -72,7 +89,7 @@ func TestEnvironment_Builder_NilError(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) config := DefaultEnvironmentConfig() - config.BuilderFunc = func(n string) (Builder, error) { return nil, nil } + config.Components.Builder = func(n string) (Builder, error) { return nil, nil } env, _ := NewEnvironment(config) returnedBuilder, err := env.Builder("foo") @@ -84,7 +101,7 @@ func TestEnvironment_Builder_Error(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) config := DefaultEnvironmentConfig() - config.BuilderFunc = func(n string) (Builder, error) { return nil, errors.New("foo") } + config.Components.Builder = func(n string) (Builder, error) { return nil, errors.New("foo") } env, _ := NewEnvironment(config) returnedBuilder, err := env.Builder("foo") @@ -97,7 +114,7 @@ func TestEnvironment_Cli_Error(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) config := DefaultEnvironmentConfig() - config.CommandFunc = func(n string) (Command, error) { return nil, errors.New("foo") } + config.Components.Command = func(n string) (Command, error) { return nil, errors.New("foo") } env, _ := NewEnvironment(config) _, err := env.Cli([]string{"foo"}) @@ -114,7 +131,7 @@ func TestEnvironment_Cli_CallsRun(t *testing.T) { config := &EnvironmentConfig{} config.Commands = []string{"foo"} - config.CommandFunc = func(n string) (Command, error) { return commands[n], nil } + config.Components.Command = func(n string) (Command, error) { return commands[n], nil } env, _ := NewEnvironment(config) exitCode, err := env.Cli([]string{"foo", "bar", "baz"}) @@ -179,6 +196,47 @@ func TestEnvironment_DefaultCli_Version(t *testing.T) { } } +func TestEnvironment_Hook(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + hook := &TestHook{} + hooks := make(map[string]Hook) + hooks["foo"] = hook + + config := DefaultEnvironmentConfig() + config.Components.Hook = func(n string) (Hook, error) { return hooks[n], nil } + + env, _ := NewEnvironment(config) + returned, err := env.Hook("foo") + assert.Nil(err, "should be no error") + assert.Equal(returned, hook, "should return correct hook") +} + +func TestEnvironment_Hook_NilError(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + config := DefaultEnvironmentConfig() + config.Components.Hook = func(n string) (Hook, error) { return nil, nil } + + env, _ := NewEnvironment(config) + returned, err := env.Hook("foo") + assert.NotNil(err, "should be an error") + assert.Nil(returned, "should be no hook") +} + +func TestEnvironment_Hook_Error(t *testing.T) { + assert := asserts.NewTestingAsserts(t, true) + + config := DefaultEnvironmentConfig() + config.Components.Hook = func(n string) (Hook, error) { return nil, errors.New("foo") } + + env, _ := NewEnvironment(config) + returned, err := env.Hook("foo") + assert.NotNil(err, "should be an error") + assert.Equal(err.Error(), "foo", "should be correct error") + assert.Nil(returned, "should be no hook") +} + func TestEnvironment_SettingUi(t *testing.T) { assert := asserts.NewTestingAsserts(t, true) diff --git a/packer/hook_test.go b/packer/hook_test.go new file mode 100644 index 000000000..b180884ad --- /dev/null +++ b/packer/hook_test.go @@ -0,0 +1,9 @@ +package packer + +type TestHook struct { + runCalled bool +} + +func (t *TestHook) Run(string, interface{}) { + t.runCalled = true +}