diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index 69ab6a016..137409dad 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -69,6 +69,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Build the steps steps := []multistep.Step{ + &StepLoadExtensions{}, &StepLoadFlavor{ Flavor: b.config.Flavor, }, @@ -98,6 +99,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHConfig: SSHConfig(b.config.RunConfig.Comm.SSHUsername), }, &common.StepProvision{}, + &StepStopServer{}, &stepCreateImage{}, } diff --git a/builder/openstack/server.go b/builder/openstack/server.go index de8c9d103..482657c03 100644 --- a/builder/openstack/server.go +++ b/builder/openstack/server.go @@ -28,7 +28,7 @@ type StateChangeConf struct { Pending []string Refresh StateRefreshFunc StepState multistep.StateBag - Target string + Target []string } // ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch @@ -65,8 +65,10 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) { return } - if currentState == conf.Target { - return + for _, t := range conf.Target { + if currentState == t { + return + } } if conf.StepState != nil { diff --git a/builder/openstack/step_load_extensions.go b/builder/openstack/step_load_extensions.go new file mode 100644 index 000000000..095863612 --- /dev/null +++ b/builder/openstack/step_load_extensions.go @@ -0,0 +1,58 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions" + "github.com/rackspace/gophercloud/pagination" +) + +// StepLoadExtensions gets the FlavorRef from a Flavor. It first assumes +// that the Flavor is a ref and verifies it. Otherwise, it tries to find +// the flavor by name. +type StepLoadExtensions struct{} + +func (s *StepLoadExtensions) Run(state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(Config) + ui := state.Get("ui").(packer.Ui) + + // We need the v2 compute client + client, err := config.computeV2Client() + if err != nil { + err = fmt.Errorf("Error initializing compute client: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + ui.Say("Discovering enabled extensions...") + result := make(map[string]struct{}, 15) + pager := extensions.List(client) + err = pager.EachPage(func(p pagination.Page) (bool, error) { + // Extract the extensions from this page + exts, err := extensions.ExtractExtensions(p) + if err != nil { + return false, err + } + + for _, ext := range exts { + log.Printf("[DEBUG] Discovered extension: %s", ext.Alias) + result[ext.Alias] = struct{}{} + } + + return true, nil + }) + if err != nil { + err = fmt.Errorf("Error loading extensions: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + state.Put("extensions", result) + return multistep.ActionContinue +} + +func (s *StepLoadExtensions) Cleanup(state multistep.StateBag) { +} diff --git a/builder/openstack/step_run_source_server.go b/builder/openstack/step_run_source_server.go index 4014f1d95..596348def 100644 --- a/builder/openstack/step_run_source_server.go +++ b/builder/openstack/step_run_source_server.go @@ -65,7 +65,7 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction ui.Say("Waiting for server to become ready...") stateChange := StateChangeConf{ Pending: []string{"BUILD"}, - Target: "ACTIVE", + Target: []string{"ACTIVE"}, Refresh: ServerStateRefreshFunc(computeClient, s.server), StepState: state, } @@ -105,9 +105,9 @@ func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) { } stateChange := StateChangeConf{ - Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"}, + Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED", "SHUTOFF", "STOPPED"}, Refresh: ServerStateRefreshFunc(computeClient, s.server), - Target: "DELETED", + Target: []string{"DELETED"}, } WaitForState(&stateChange) diff --git a/builder/openstack/step_stop_server.go b/builder/openstack/step_stop_server.go new file mode 100644 index 000000000..298d0bc0a --- /dev/null +++ b/builder/openstack/step_stop_server.go @@ -0,0 +1,58 @@ +package openstack + +import ( + "fmt" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" +) + +type StepStopServer struct{} + +func (s *StepStopServer) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + config := state.Get("config").(Config) + extensions := state.Get("extensions").(map[string]struct{}) + server := state.Get("server").(*servers.Server) + + // Verify we have the extension + if _, ok := extensions["os-server-start-stop"]; !ok { + ui.Say("OpenStack cluster doesn't support stop, skipping...") + return multistep.ActionContinue + } + + // We need the v2 compute client + client, err := config.computeV2Client() + if err != nil { + err = fmt.Errorf("Error initializing compute client: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + ui.Say("Stopping server...") + if err := startstop.Stop(client, server.ID).ExtractErr(); err != nil { + err = fmt.Errorf("Error stopping server: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + + ui.Message("Waiting for server to stop...") + stateChange := StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"SHUTOFF", "STOPPED"}, + Refresh: ServerStateRefreshFunc(client, server), + StepState: state, + } + if _, err := WaitForState(&stateChange); err != nil { + err := fmt.Errorf("Error waiting for server (%s) to stop: %s", server.ID, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepStopServer) Cleanup(state multistep.StateBag) {}