packer: Add concept of hooks to Environment

This commit is contained in:
Mitchell Hashimoto 2013-05-10 23:15:13 -07:00
parent 5ac06e116e
commit 8ed313e7b5
5 changed files with 126 additions and 20 deletions

View File

@ -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 {

View File

@ -17,6 +17,7 @@ type Build interface {
type coreBuild struct {
name string
builder Builder
hooks map[string]Hook
rawConfig interface{}
prepareCalled bool

View File

@ -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 {

View File

@ -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)

9
packer/hook_test.go Normal file
View File

@ -0,0 +1,9 @@
package packer
type TestHook struct {
runCalled bool
}
func (t *TestHook) Run(string, interface{}) {
t.runCalled = true
}