builder/openstack: new multistep API

This commit is contained in:
Mitchell Hashimoto 2013-08-31 12:37:07 -07:00
parent 94b76036fc
commit 77f7f24990
6 changed files with 45 additions and 44 deletions

View File

@ -72,11 +72,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
state := make(map[string]interface{}) state := new(multistep.BasicStateBag)
state["config"] = b.config state.Put("config", b.config)
state["csp"] = csp state.Put("csp", csp)
state["hook"] = hook state.Put("hook", hook)
state["ui"] = ui state.Put("ui", ui)
// Build the steps // Build the steps
steps := []multistep.Step{ steps := []multistep.Step{
@ -108,13 +108,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b.runner.Run(state) b.runner.Run(state)
// If there was an error, return that // If there was an error, return that
if rawErr, ok := state["error"]; ok { if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error) return nil, rawErr.(error)
} }
// Build the artifact and return it // Build the artifact and return it
artifact := &Artifact{ artifact := &Artifact{
ImageId: state["image"].(string), ImageId: state.Get("image").(string),
BuilderIdValue: BuilderId, BuilderIdValue: BuilderId,
Conn: csp, Conn: csp,
} }

View File

@ -25,7 +25,7 @@ type StateRefreshFunc func() (result interface{}, state string, progress int, er
type StateChangeConf struct { type StateChangeConf struct {
Pending []string Pending []string
Refresh StateRefreshFunc Refresh StateRefreshFunc
StepState map[string]interface{} StepState multistep.StateBag
Target string Target string
} }
@ -61,7 +61,7 @@ func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
} }
if conf.StepState != nil { if conf.StepState != nil {
if _, ok := conf.StepState[multistep.StateCancelled]; ok { if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("interrupted") return nil, errors.New("interrupted")
} }
} }

View File

@ -4,6 +4,7 @@ import (
gossh "code.google.com/p/go.crypto/ssh" gossh "code.google.com/p/go.crypto/ssh"
"errors" "errors"
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/communicator/ssh" "github.com/mitchellh/packer/communicator/ssh"
"github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud"
"time" "time"
@ -11,10 +12,10 @@ import (
// SSHAddress returns a function that can be given to the SSH communicator // SSHAddress returns a function that can be given to the SSH communicator
// for determining the SSH address based on the server AccessIPv4 setting.. // for determining the SSH address based on the server AccessIPv4 setting..
func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(map[string]interface{}) (string, error) { func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(multistep.StateBag) (string, error) {
return func(state map[string]interface{}) (string, error) { return func(state multistep.StateBag) (string, error) {
for j := 0; j < 2; j++ { for j := 0; j < 2; j++ {
s := state["server"].(*gophercloud.Server) s := state.Get("server").(*gophercloud.Server)
if s.AccessIPv4 != "" { if s.AccessIPv4 != "" {
return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil
} }
@ -24,7 +25,7 @@ func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(map[string]
return "", err return "", err
} }
state["server"] = serverState state.Put("server", serverState)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
@ -35,9 +36,9 @@ func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(map[string]
// SSHConfig returns a function that can be used for the SSH communicator // SSHConfig returns a function that can be used for the SSH communicator
// config for connecting to the instance created over SSH using the generated // config for connecting to the instance created over SSH using the generated
// private key. // private key.
func SSHConfig(username string) func(map[string]interface{}) (*gossh.ClientConfig, error) { func SSHConfig(username string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
return func(state map[string]interface{}) (*gossh.ClientConfig, error) { return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
privateKey := state["privateKey"].(string) privateKey := state.Get("privateKey").(string)
keyring := new(ssh.SimpleKeychain) keyring := new(ssh.SimpleKeychain)
if err := keyring.AddPEMKey(privateKey); err != nil { if err := keyring.AddPEMKey(privateKey); err != nil {

View File

@ -11,11 +11,11 @@ import (
type stepCreateImage struct{} type stepCreateImage struct{}
func (s *stepCreateImage) Run(state map[string]interface{}) multistep.StepAction { func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
csp := state["csp"].(gophercloud.CloudServersProvider) csp := state.Get("csp").(gophercloud.CloudServersProvider)
config := state["config"].(config) config := state.Get("config").(config)
server := state["server"].(*gophercloud.Server) server := state.Get("server").(*gophercloud.Server)
ui := state["ui"].(packer.Ui) ui := state.Get("ui").(packer.Ui)
// Create the image // Create the image
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName)) ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
@ -25,20 +25,20 @@ func (s *stepCreateImage) Run(state map[string]interface{}) multistep.StepAction
imageId, err := csp.CreateImage(server.Id, createOpts) imageId, err := csp.CreateImage(server.Id, createOpts)
if err != nil { if err != nil {
err := fmt.Errorf("Error creating image: %s", err) err := fmt.Errorf("Error creating image: %s", err)
state["error"] = err state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
// Set the Image ID in the state // Set the Image ID in the state
ui.Say(fmt.Sprintf("Image: %s", imageId)) ui.Say(fmt.Sprintf("Image: %s", imageId))
state["image"] = imageId state.Put("image", imageId)
// Wait for the image to become ready // Wait for the image to become ready
ui.Say("Waiting for image to become ready...") ui.Say("Waiting for image to become ready...")
if err := WaitForImage(csp, imageId); err != nil { if err := WaitForImage(csp, imageId); err != nil {
err := fmt.Errorf("Error waiting for image: %s", err) err := fmt.Errorf("Error waiting for image: %s", err)
state["error"] = err state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
@ -46,7 +46,7 @@ func (s *stepCreateImage) Run(state map[string]interface{}) multistep.StepAction
return multistep.ActionContinue return multistep.ActionContinue
} }
func (s *stepCreateImage) Cleanup(map[string]interface{}) { func (s *stepCreateImage) Cleanup(multistep.StateBag) {
// No cleanup... // No cleanup...
} }

View File

@ -14,16 +14,16 @@ type StepKeyPair struct {
keyName string keyName string
} }
func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction { func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
csp := state["csp"].(gophercloud.CloudServersProvider) csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state["ui"].(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Creating temporary keypair for this instance...") ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw())) keyName := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw()))
log.Printf("temporary keypair name: %s", keyName) log.Printf("temporary keypair name: %s", keyName)
keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName}) keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName})
if err != nil { if err != nil {
state["error"] = fmt.Errorf("Error creating temporary keypair: %s", err) state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt return multistep.ActionHalt
} }
@ -31,20 +31,20 @@ func (s *StepKeyPair) Run(state map[string]interface{}) multistep.StepAction {
s.keyName = keyName s.keyName = keyName
// Set some state data for use in future steps // Set some state data for use in future steps
state["keyPair"] = keyName state.Put("keyPair", keyName)
state["privateKey"] = keyResp.PrivateKey state.Put("privateKey", keyResp.PrivateKey)
return multistep.ActionContinue return multistep.ActionContinue
} }
func (s *StepKeyPair) Cleanup(state map[string]interface{}) { func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return // If no key name is set, then we never created it, so just return
if s.keyName == "" { if s.keyName == "" {
return return
} }
csp := state["csp"].(gophercloud.CloudServersProvider) csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state["ui"].(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary keypair...") ui.Say("Deleting temporary keypair...")
err := csp.DeleteKeyPair(s.keyName) err := csp.DeleteKeyPair(s.keyName)

View File

@ -16,10 +16,10 @@ type StepRunSourceServer struct {
server *gophercloud.Server server *gophercloud.Server
} }
func (s *StepRunSourceServer) Run(state map[string]interface{}) multistep.StepAction { func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction {
csp := state["csp"].(gophercloud.CloudServersProvider) csp := state.Get("csp").(gophercloud.CloudServersProvider)
keyName := state["keyPair"].(string) keyName := state.Get("keyPair").(string)
ui := state["ui"].(packer.Ui) ui := state.Get("ui").(packer.Ui)
// XXX - validate image and flavor is available // XXX - validate image and flavor is available
@ -33,7 +33,7 @@ func (s *StepRunSourceServer) Run(state map[string]interface{}) multistep.StepAc
serverResp, err := csp.CreateServer(server) serverResp, err := csp.CreateServer(server)
if err != nil { if err != nil {
err := fmt.Errorf("Error launching source server: %s", err) err := fmt.Errorf("Error launching source server: %s", err)
state["error"] = err state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
@ -51,24 +51,24 @@ func (s *StepRunSourceServer) Run(state map[string]interface{}) multistep.StepAc
latestServer, err := WaitForState(&stateChange) latestServer, err := WaitForState(&stateChange)
if err != nil { if err != nil {
err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.Id, err) err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.Id, err)
state["error"] = err state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
s.server = latestServer.(*gophercloud.Server) s.server = latestServer.(*gophercloud.Server)
state["server"] = s.server state.Put("server", s.server)
return multistep.ActionContinue return multistep.ActionContinue
} }
func (s *StepRunSourceServer) Cleanup(state map[string]interface{}) { func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) {
if s.server == nil { if s.server == nil {
return return
} }
csp := state["csp"].(gophercloud.CloudServersProvider) csp := state.Get("csp").(gophercloud.CloudServersProvider)
ui := state["ui"].(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Terminating the source server...") ui.Say("Terminating the source server...")
if err := csp.DeleteServerById(s.server.Id); err != nil { if err := csp.DeleteServerById(s.server.Id); err != nil {