From a0d41fcd14f2525df351d1764e3401d1f016ff53 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 12 Jun 2015 00:16:43 -0400 Subject: [PATCH] builder/openstack --- builder/openstack-new/access_config.go | 109 ------------ builder/openstack-new/artifact.go | 47 ----- builder/openstack-new/artifact_test.go | 35 ---- builder/openstack-new/builder.go | 134 -------------- builder/openstack-new/builder_test.go | 94 ---------- builder/openstack-new/image_config.go | 25 --- builder/openstack-new/image_config_test.go | 23 --- builder/openstack-new/run_config.go | 75 -------- builder/openstack-new/run_config_test.go | 88 ---------- builder/openstack-new/server.go | 95 ---------- builder/openstack-new/ssh.go | 92 ---------- builder/openstack-new/step_allocate_ip.go | 93 ---------- builder/openstack-new/step_create_image.go | 84 --------- builder/openstack-new/step_key_pair.go | 106 ------------ .../openstack-new/step_run_source_server.go | 110 ------------ .../step_wait_for_rackconnect.go | 52 ------ builder/openstack/access_config.go | 163 +++++++++--------- builder/openstack/access_config_test.go | 77 --------- builder/openstack/artifact.go | 7 +- builder/openstack/builder.go | 23 +-- builder/openstack/server.go | 23 +-- builder/openstack/ssh.go | 62 ++++--- builder/openstack/step_allocate_ip.go | 68 +++++--- builder/openstack/step_create_image.go | 39 +++-- builder/openstack/step_key_pair.go | 43 +++-- builder/openstack/step_run_source_server.go | 70 ++++---- .../openstack/step_wait_for_rackconnect.go | 22 ++- 27 files changed, 303 insertions(+), 1556 deletions(-) delete mode 100644 builder/openstack-new/access_config.go delete mode 100644 builder/openstack-new/artifact.go delete mode 100644 builder/openstack-new/artifact_test.go delete mode 100644 builder/openstack-new/builder.go delete mode 100644 builder/openstack-new/builder_test.go delete mode 100644 builder/openstack-new/image_config.go delete mode 100644 builder/openstack-new/image_config_test.go delete mode 100644 builder/openstack-new/run_config.go delete mode 100644 builder/openstack-new/run_config_test.go delete mode 100644 builder/openstack-new/server.go delete mode 100644 builder/openstack-new/ssh.go delete mode 100644 builder/openstack-new/step_allocate_ip.go delete mode 100644 builder/openstack-new/step_create_image.go delete mode 100644 builder/openstack-new/step_key_pair.go delete mode 100644 builder/openstack-new/step_run_source_server.go delete mode 100644 builder/openstack-new/step_wait_for_rackconnect.go delete mode 100644 builder/openstack/access_config_test.go diff --git a/builder/openstack-new/access_config.go b/builder/openstack-new/access_config.go deleted file mode 100644 index e0f962c50..000000000 --- a/builder/openstack-new/access_config.go +++ /dev/null @@ -1,109 +0,0 @@ -package openstack - -import ( - "crypto/tls" - "fmt" - "net/http" - "os" - - "github.com/mitchellh/packer/template/interpolate" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" -) - -// AccessConfig is for common configuration related to openstack access -type AccessConfig struct { - Username string `mapstructure:"username"` - UserID string `mapstructure:"user_id"` - Password string `mapstructure:"password"` - APIKey string `mapstructure:"api_key"` - IdentityEndpoint string `mapstructure:"identity_endpoint"` - TenantID string `mapstructure:"tenant_id"` - TenantName string `mapstructure:"tenant_name"` - DomainID string `mapstructure:"domain_id"` - DomainName string `mapstructure:"domain_name"` - Insecure bool `mapstructure:"insecure"` - Region string `mapstructure:"region"` - EndpointType string `mapstructure:"endpoint_type"` - - osClient *gophercloud.ProviderClient -} - -func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { - if c.EndpointType != "internal" && c.EndpointType != "internalURL" && - c.EndpointType != "admin" && c.EndpointType != "adminURL" && - c.EndpointType != "public" && c.EndpointType != "publicURL" && - c.EndpointType != "" { - return []error{fmt.Errorf("Invalid endpoint type provided")} - } - - if c.Region == "" { - c.Region = os.Getenv("OS_REGION_NAME") - } - - // Get as much as possible from the end - ao, err := openstack.AuthOptionsFromEnv() - if err != nil { - return []error{err} - } - - // Override values if we have them in our config - overrides := []struct { - From, To *string - }{ - {&c.Username, &ao.Username}, - {&c.UserID, &ao.UserID}, - {&c.Password, &ao.Password}, - {&c.APIKey, &ao.APIKey}, - {&c.IdentityEndpoint, &ao.IdentityEndpoint}, - {&c.TenantID, &ao.TenantID}, - {&c.TenantName, &ao.TenantName}, - {&c.DomainID, &ao.DomainID}, - {&c.DomainName, &ao.DomainName}, - } - for _, s := range overrides { - if *s.From != "" { - *s.To = *s.From - } - } - - // Build the client itself - client, err := openstack.NewClient(ao.IdentityEndpoint) - if err != nil { - return []error{err} - } - - // If we have insecure set, then create a custom HTTP client that - // ignores SSL errors. - if c.Insecure { - config := &tls.Config{InsecureSkipVerify: true} - transport := &http.Transport{TLSClientConfig: config} - client.HTTPClient.Transport = transport - } - - // Auth - err = openstack.Authenticate(client, ao) - if err != nil { - return []error{err} - } - - c.osClient = client - return nil -} - -func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) { - return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{ - Region: c.Region, - Availability: c.getEndpointType(), - }) -} - -func (c *AccessConfig) getEndpointType() gophercloud.Availability { - if c.EndpointType == "internal" || c.EndpointType == "internalURL" { - return gophercloud.AvailabilityInternal - } - if c.EndpointType == "admin" || c.EndpointType == "adminURL" { - return gophercloud.AvailabilityAdmin - } - return gophercloud.AvailabilityPublic -} diff --git a/builder/openstack-new/artifact.go b/builder/openstack-new/artifact.go deleted file mode 100644 index aa60d2641..000000000 --- a/builder/openstack-new/artifact.go +++ /dev/null @@ -1,47 +0,0 @@ -package openstack - -import ( - "fmt" - "log" - - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack/compute/v2/images" -) - -// Artifact is an artifact implementation that contains built images. -type Artifact struct { - // ImageId of built image - ImageId string - - // BuilderId is the unique ID for the builder that created this image - BuilderIdValue string - - // OpenStack connection for performing API stuff. - Client *gophercloud.ServiceClient -} - -func (a *Artifact) BuilderId() string { - return a.BuilderIdValue -} - -func (*Artifact) Files() []string { - // We have no files - return nil -} - -func (a *Artifact) Id() string { - return a.ImageId -} - -func (a *Artifact) String() string { - return fmt.Sprintf("An image was created: %v", a.ImageId) -} - -func (a *Artifact) State(name string) interface{} { - return nil -} - -func (a *Artifact) Destroy() error { - log.Printf("Destroying image: %s", a.ImageId) - return images.Delete(a.Client, a.ImageId).ExtractErr() -} diff --git a/builder/openstack-new/artifact_test.go b/builder/openstack-new/artifact_test.go deleted file mode 100644 index 313fea7cf..000000000 --- a/builder/openstack-new/artifact_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package openstack - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func TestArtifact_Impl(t *testing.T) { - var _ packer.Artifact = new(Artifact) -} - -func TestArtifactId(t *testing.T) { - expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed` - - a := &Artifact{ - ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed", - } - - result := a.Id() - if result != expected { - t.Fatalf("bad: %s", result) - } -} - -func TestArtifactString(t *testing.T) { - expected := "An image was created: b8cdf55b-c916-40bd-b190-389ec144c4ed" - - a := &Artifact{ - ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed", - } - result := a.String() - if result != expected { - t.Fatalf("bad: %s", result) - } -} diff --git a/builder/openstack-new/builder.go b/builder/openstack-new/builder.go deleted file mode 100644 index bebb28452..000000000 --- a/builder/openstack-new/builder.go +++ /dev/null @@ -1,134 +0,0 @@ -// The openstack package contains a packer.Builder implementation that -// builds Images for openstack. - -package openstack - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common" - "log" - - "github.com/mitchellh/packer/helper/config" - "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/template/interpolate" -) - -// The unique ID for this builder -const BuilderId = "mitchellh.openstack" - -type Config struct { - common.PackerConfig `mapstructure:",squash"` - AccessConfig `mapstructure:",squash"` - ImageConfig `mapstructure:",squash"` - RunConfig `mapstructure:",squash"` - - ctx interpolate.Context -} - -type Builder struct { - config Config - runner multistep.Runner -} - -func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - err := config.Decode(&b.config, &config.DecodeOpts{ - Interpolate: true, - }, raws...) - if err != nil { - return nil, err - } - - // Accumulate any errors - var errs *packer.MultiError - errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) - errs = packer.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(&b.config.ctx)...) - errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) - - if errs != nil && len(errs.Errors) > 0 { - return nil, errs - } - - log.Println(common.ScrubConfig(b.config, b.config.Password)) - return nil, nil -} - -func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - computeClient, err := b.config.computeV2Client() - if err != nil { - return nil, fmt.Errorf("Error initializing compute client: %s", err) - } - - // Setup the state bag and initial state for the steps - state := new(multistep.BasicStateBag) - state.Put("config", b.config) - state.Put("hook", hook) - state.Put("ui", ui) - - // Build the steps - steps := []multistep.Step{ - &StepKeyPair{ - Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName), - }, - &StepRunSourceServer{ - Name: b.config.ImageName, - Flavor: b.config.Flavor, - SourceImage: b.config.SourceImage, - SecurityGroups: b.config.SecurityGroups, - Networks: b.config.Networks, - }, - &StepWaitForRackConnect{ - Wait: b.config.RackconnectWait, - }, - &StepAllocateIp{ - FloatingIpPool: b.config.FloatingIpPool, - FloatingIp: b.config.FloatingIp, - }, - &common.StepConnectSSH{ - SSHAddress: SSHAddress(computeClient, b.config.SSHInterface, b.config.SSHPort), - SSHConfig: SSHConfig(b.config.SSHUsername), - SSHWaitTimeout: b.config.SSHTimeout(), - }, - &common.StepProvision{}, - &stepCreateImage{}, - } - - // Run! - if b.config.PackerDebug { - b.runner = &multistep.DebugRunner{ - Steps: steps, - PauseFn: common.MultistepDebugFn(ui), - } - } else { - b.runner = &multistep.BasicRunner{Steps: steps} - } - - b.runner.Run(state) - - // If there was an error, return that - if rawErr, ok := state.GetOk("error"); ok { - return nil, rawErr.(error) - } - - // If there are no images, then just return - if _, ok := state.GetOk("image"); !ok { - return nil, nil - } - - // Build the artifact and return it - artifact := &Artifact{ - ImageId: state.Get("image").(string), - BuilderIdValue: BuilderId, - Client: computeClient, - } - - return artifact, nil -} - -func (b *Builder) Cancel() { - if b.runner != nil { - log.Println("Cancelling the step runner...") - b.runner.Cancel() - } -} diff --git a/builder/openstack-new/builder_test.go b/builder/openstack-new/builder_test.go deleted file mode 100644 index badf9784d..000000000 --- a/builder/openstack-new/builder_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package openstack - -import ( - "github.com/mitchellh/packer/packer" - "testing" -) - -func testConfig() map[string]interface{} { - return map[string]interface{}{ - "username": "foo", - "password": "bar", - "provider": "foo", - "region": "DFW", - "image_name": "foo", - "source_image": "foo", - "flavor": "foo", - "ssh_username": "root", - } -} - -func TestBuilder_ImplementsBuilder(t *testing.T) { - var raw interface{} - raw = &Builder{} - if _, ok := raw.(packer.Builder); !ok { - t.Fatalf("Builder should be a builder") - } -} - -func TestBuilder_Prepare_BadType(t *testing.T) { - b := &Builder{} - c := map[string]interface{}{ - "password": []string{}, - } - - warns, err := b.Prepare(c) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatalf("prepare should fail") - } -} - -func TestBuilderPrepare_ImageName(t *testing.T) { - var b Builder - config := testConfig() - - // Test good - config["image_name"] = "foo" - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - // Test bad - config["image_name"] = "foo {{" - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } - - // Test bad - delete(config, "image_name") - b = Builder{} - warns, err = b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } -} - -func TestBuilderPrepare_InvalidKey(t *testing.T) { - var b Builder - config := testConfig() - - // Add a random key - config["i_should_not_be_valid"] = true - warns, err := b.Prepare(config) - if len(warns) > 0 { - t.Fatalf("bad: %#v", warns) - } - if err == nil { - t.Fatal("should have error") - } -} diff --git a/builder/openstack-new/image_config.go b/builder/openstack-new/image_config.go deleted file mode 100644 index 124449eab..000000000 --- a/builder/openstack-new/image_config.go +++ /dev/null @@ -1,25 +0,0 @@ -package openstack - -import ( - "fmt" - - "github.com/mitchellh/packer/template/interpolate" -) - -// ImageConfig is for common configuration related to creating Images. -type ImageConfig struct { - ImageName string `mapstructure:"image_name"` -} - -func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error { - errs := make([]error, 0) - if c.ImageName == "" { - errs = append(errs, fmt.Errorf("An image_name must be specified")) - } - - if len(errs) > 0 { - return errs - } - - return nil -} diff --git a/builder/openstack-new/image_config_test.go b/builder/openstack-new/image_config_test.go deleted file mode 100644 index 4d81ecd94..000000000 --- a/builder/openstack-new/image_config_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package openstack - -import ( - "testing" -) - -func testImageConfig() *ImageConfig { - return &ImageConfig{ - ImageName: "foo", - } -} - -func TestImageConfigPrepare_Region(t *testing.T) { - c := testImageConfig() - if err := c.Prepare(nil); err != nil { - t.Fatalf("shouldn't have err: %s", err) - } - - c.ImageName = "" - if err := c.Prepare(nil); err == nil { - t.Fatal("should have error") - } -} diff --git a/builder/openstack-new/run_config.go b/builder/openstack-new/run_config.go deleted file mode 100644 index e5d73c9c1..000000000 --- a/builder/openstack-new/run_config.go +++ /dev/null @@ -1,75 +0,0 @@ -package openstack - -import ( - "errors" - "fmt" - "time" - - "github.com/mitchellh/packer/template/interpolate" -) - -// RunConfig contains configuration for running an instance from a source -// image and details on how to access that launched image. -type RunConfig struct { - SourceImage string `mapstructure:"source_image"` - Flavor string `mapstructure:"flavor"` - RawSSHTimeout string `mapstructure:"ssh_timeout"` - SSHUsername string `mapstructure:"ssh_username"` - SSHPort int `mapstructure:"ssh_port"` - SSHInterface string `mapstructure:"ssh_interface"` - OpenstackProvider string `mapstructure:"openstack_provider"` - UseFloatingIp bool `mapstructure:"use_floating_ip"` - RackconnectWait bool `mapstructure:"rackconnect_wait"` - FloatingIpPool string `mapstructure:"floating_ip_pool"` - FloatingIp string `mapstructure:"floating_ip"` - SecurityGroups []string `mapstructure:"security_groups"` - Networks []string `mapstructure:"networks"` - - // Unexported fields that are calculated from others - sshTimeout time.Duration -} - -func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { - // Defaults - if c.SSHUsername == "" { - c.SSHUsername = "root" - } - - if c.SSHPort == 0 { - c.SSHPort = 22 - } - - if c.RawSSHTimeout == "" { - c.RawSSHTimeout = "5m" - } - - if c.UseFloatingIp && c.FloatingIpPool == "" { - c.FloatingIpPool = "public" - } - - // Validation - var err error - errs := make([]error, 0) - if c.SourceImage == "" { - errs = append(errs, errors.New("A source_image must be specified")) - } - - if c.Flavor == "" { - errs = append(errs, errors.New("A flavor must be specified")) - } - - if c.SSHUsername == "" { - errs = append(errs, errors.New("An ssh_username must be specified")) - } - - c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout) - if err != nil { - errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err)) - } - - return errs -} - -func (c *RunConfig) SSHTimeout() time.Duration { - return c.sshTimeout -} diff --git a/builder/openstack-new/run_config_test.go b/builder/openstack-new/run_config_test.go deleted file mode 100644 index 16b89b352..000000000 --- a/builder/openstack-new/run_config_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package openstack - -import ( - "os" - "testing" -) - -func init() { - // Clear out the openstack env vars so they don't - // affect our tests. - os.Setenv("SDK_USERNAME", "") - os.Setenv("SDK_PASSWORD", "") - os.Setenv("SDK_PROVIDER", "") -} - -func testRunConfig() *RunConfig { - return &RunConfig{ - SourceImage: "abcd", - Flavor: "m1.small", - SSHUsername: "root", - } -} - -func TestRunConfigPrepare(t *testing.T) { - c := testRunConfig() - err := c.Prepare(nil) - if len(err) > 0 { - t.Fatalf("err: %s", err) - } -} - -func TestRunConfigPrepare_InstanceType(t *testing.T) { - c := testRunConfig() - c.Flavor = "" - if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) - } -} - -func TestRunConfigPrepare_SourceImage(t *testing.T) { - c := testRunConfig() - c.SourceImage = "" - if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) - } -} - -func TestRunConfigPrepare_SSHPort(t *testing.T) { - c := testRunConfig() - c.SSHPort = 0 - if err := c.Prepare(nil); len(err) != 0 { - t.Fatalf("err: %s", err) - } - - if c.SSHPort != 22 { - t.Fatalf("invalid value: %d", c.SSHPort) - } - - c.SSHPort = 44 - if err := c.Prepare(nil); len(err) != 0 { - t.Fatalf("err: %s", err) - } - - if c.SSHPort != 44 { - t.Fatalf("invalid value: %d", c.SSHPort) - } -} - -func TestRunConfigPrepare_SSHTimeout(t *testing.T) { - c := testRunConfig() - c.RawSSHTimeout = "" - if err := c.Prepare(nil); len(err) != 0 { - t.Fatalf("err: %s", err) - } - - c.RawSSHTimeout = "bad" - if err := c.Prepare(nil); len(err) != 1 { - t.Fatalf("err: %s", err) - } -} - -func TestRunConfigPrepare_SSHUsername(t *testing.T) { - c := testRunConfig() - c.SSHUsername = "" - if err := c.Prepare(nil); len(err) != 0 { - t.Fatalf("err: %s", err) - } -} diff --git a/builder/openstack-new/server.go b/builder/openstack-new/server.go deleted file mode 100644 index de8c9d103..000000000 --- a/builder/openstack-new/server.go +++ /dev/null @@ -1,95 +0,0 @@ -package openstack - -import ( - "errors" - "fmt" - "log" - "time" - - "github.com/mitchellh/multistep" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack/compute/v2/servers" -) - -// StateRefreshFunc is a function type used for StateChangeConf that is -// responsible for refreshing the item being watched for a state change. -// -// It returns three results. `result` is any object that will be returned -// as the final object after waiting for state change. This allows you to -// return the final updated object, for example an openstack instance after -// refreshing it. -// -// `state` is the latest state of that object. And `err` is any error that -// may have happened while refreshing the state. -type StateRefreshFunc func() (result interface{}, state string, progress int, err error) - -// StateChangeConf is the configuration struct used for `WaitForState`. -type StateChangeConf struct { - Pending []string - Refresh StateRefreshFunc - StepState multistep.StateBag - Target string -} - -// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch -// an openstack server. -func ServerStateRefreshFunc( - client *gophercloud.ServiceClient, s *servers.Server) StateRefreshFunc { - return func() (interface{}, string, int, error) { - serverNew, err := servers.Get(client, s.ID).Extract() - if err != nil { - errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) - if ok && errCode.Actual == 404 { - log.Printf("[INFO] 404 on ServerStateRefresh, returning DELETED") - return nil, "DELETED", 0, nil - } else { - log.Printf("[ERROR] Error on ServerStateRefresh: %s", err) - return nil, "", 0, err - } - } - - return serverNew, serverNew.Status, serverNew.Progress, nil - } -} - -// WaitForState watches an object and waits for it to achieve a certain -// state. -func WaitForState(conf *StateChangeConf) (i interface{}, err error) { - log.Printf("Waiting for state to become: %s", conf.Target) - - for { - var currentProgress int - var currentState string - i, currentState, currentProgress, err = conf.Refresh() - if err != nil { - return - } - - if currentState == conf.Target { - return - } - - if conf.StepState != nil { - if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok { - return nil, errors.New("interrupted") - } - } - - found := false - for _, allowed := range conf.Pending { - if currentState == allowed { - found = true - break - } - } - - if !found { - return nil, fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target) - } - - log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress) - time.Sleep(2 * time.Second) - } - - return -} diff --git a/builder/openstack-new/ssh.go b/builder/openstack-new/ssh.go deleted file mode 100644 index 7b0510f98..000000000 --- a/builder/openstack-new/ssh.go +++ /dev/null @@ -1,92 +0,0 @@ -package openstack - -import ( - "errors" - "fmt" - "log" - "time" - - "github.com/mitchellh/multistep" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" - "github.com/rackspace/gophercloud/openstack/compute/v2/servers" - "golang.org/x/crypto/ssh" -) - -// SSHAddress returns a function that can be given to the SSH communicator -// for determining the SSH address based on the server AccessIPv4 setting.. -func SSHAddress( - client *gophercloud.ServiceClient, - sshinterface string, port int) func(multistep.StateBag) (string, error) { - return func(state multistep.StateBag) (string, error) { - s := state.Get("server").(*servers.Server) - - // If we have a floating IP, use that - ip := state.Get("access_ip").(*floatingip.FloatingIP) - if ip != nil && ip.FixedIP != "" { - return fmt.Sprintf("%s:%d", ip.FixedIP, port), nil - } - - if s.AccessIPv4 != "" { - return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil - } - - // Get all the addresses associated with this server. This - // was taken directly from Terraform. - for _, networkAddresses := range s.Addresses { - elements, ok := networkAddresses.([]interface{}) - if !ok { - log.Printf( - "[ERROR] Unknown return type for address field: %#v", - networkAddresses) - continue - } - - for _, element := range elements { - var addr string - address := element.(map[string]interface{}) - if address["OS-EXT-IPS:type"] == "floating" { - addr = address["addr"].(string) - } else { - if address["version"].(float64) == 4 { - addr = address["addr"].(string) - } - } - if addr != "" { - return fmt.Sprintf("%s:%d", addr, port), nil - } - } - } - - s, err := servers.Get(client, s.ID).Extract() - if err != nil { - return "", err - } - - state.Put("server", s) - time.Sleep(1 * time.Second) - - return "", errors.New("couldn't determine IP address for server") - } -} - -// SSHConfig returns a function that can be used for the SSH communicator -// config for connecting to the instance created over SSH using the generated -// private key. -func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) { - return func(state multistep.StateBag) (*ssh.ClientConfig, error) { - privateKey := state.Get("privateKey").(string) - - signer, err := ssh.ParsePrivateKey([]byte(privateKey)) - if err != nil { - return nil, fmt.Errorf("Error setting up SSH config: %s", err) - } - - return &ssh.ClientConfig{ - User: username, - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(signer), - }, - }, nil - } -} diff --git a/builder/openstack-new/step_allocate_ip.go b/builder/openstack-new/step_allocate_ip.go deleted file mode 100644 index 16efe8d38..000000000 --- a/builder/openstack-new/step_allocate_ip.go +++ /dev/null @@ -1,93 +0,0 @@ -package openstack - -import ( - "fmt" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" - "github.com/rackspace/gophercloud/openstack/compute/v2/servers" -) - -type StepAllocateIp struct { - FloatingIpPool string - FloatingIp string -} - -func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - config := state.Get("config").(Config) - server := state.Get("server").(*servers.Server) - - // 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 - } - - var instanceIp *floatingip.FloatingIP - // This is here in case we error out before putting instanceIp into the - // statebag below, because it is requested by Cleanup() - state.Put("access_ip", instanceIp) - - if s.FloatingIp != "" { - *instanceIp = floatingip.FloatingIP{FixedIP: s.FloatingIp} - } else if s.FloatingIpPool != "" { - newIp, err := floatingip.Create(client, floatingip.CreateOpts{ - Pool: s.FloatingIpPool, - }).Extract() - if err != nil { - err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - *instanceIp = *newIp - ui.Say(fmt.Sprintf("Created temporary floating IP %s...", instanceIp.FixedIP)) - } - - if instanceIp != nil && instanceIp.FixedIP != "" { - err := floatingip.Associate(client, server.ID, instanceIp.FixedIP).ExtractErr() - if err != nil { - err := fmt.Errorf( - "Error associating floating IP %s with instance.", - instanceIp.FixedIP) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say(fmt.Sprintf( - "Added floating IP %s to instance...", instanceIp.FixedIP)) - } - - state.Put("access_ip", instanceIp) - return multistep.ActionContinue -} - -func (s *StepAllocateIp) Cleanup(state multistep.StateBag) { - config := state.Get("config").(Config) - ui := state.Get("ui").(packer.Ui) - instanceIp := state.Get("access_ip").(*floatingip.FloatingIP) - - // We need the v2 compute client - client, err := config.computeV2Client() - if err != nil { - ui.Error(fmt.Sprintf( - "Error deleting temporary floating IP %s", instanceIp.FixedIP)) - return - } - - if s.FloatingIpPool != "" && instanceIp.ID != "" { - if err := floatingip.Delete(client, instanceIp.ID).ExtractErr(); err != nil { - ui.Error(fmt.Sprintf( - "Error deleting temporary floating IP %s", instanceIp.FixedIP)) - return - } - - ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.FixedIP)) - } -} diff --git a/builder/openstack-new/step_create_image.go b/builder/openstack-new/step_create_image.go deleted file mode 100644 index b777e8b0b..000000000 --- a/builder/openstack-new/step_create_image.go +++ /dev/null @@ -1,84 +0,0 @@ -package openstack - -import ( - "fmt" - "log" - "time" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack/compute/v2/images" - "github.com/rackspace/gophercloud/openstack/compute/v2/servers" -) - -type stepCreateImage struct{} - -func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(Config) - server := state.Get("server").(*servers.Server) - 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 - } - - // Create the image - ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName)) - imageId, err := servers.CreateImage(client, server.ID, servers.CreateImageOpts{ - Name: config.ImageName, - }).ExtractImageID() - if err != nil { - err := fmt.Errorf("Error creating image: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - // Set the Image ID in the state - ui.Message(fmt.Sprintf("Image: %s", imageId)) - state.Put("image", imageId) - - // Wait for the image to become ready - ui.Say("Waiting for image to become ready...") - if err := WaitForImage(client, imageId); err != nil { - err := fmt.Errorf("Error waiting for image: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -func (s *stepCreateImage) Cleanup(multistep.StateBag) { - // No cleanup... -} - -// WaitForImage waits for the given Image ID to become ready. -func WaitForImage(client *gophercloud.ServiceClient, imageId string) error { - for { - image, err := images.Get(client, imageId).Extract() - if err != nil { - errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) - if ok && errCode.Actual == 500 { - log.Printf("[ERROR] 500 error received, will ignore and retry: %s", err) - time.Sleep(2 * time.Second) - continue - } - - return err - } - - if image.Status == "ACTIVE" { - return nil - } - - log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress) - time.Sleep(2 * time.Second) - } -} diff --git a/builder/openstack-new/step_key_pair.go b/builder/openstack-new/step_key_pair.go deleted file mode 100644 index 06bcbf9ea..000000000 --- a/builder/openstack-new/step_key_pair.go +++ /dev/null @@ -1,106 +0,0 @@ -package openstack - -import ( - "fmt" - "os" - "runtime" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common/uuid" - "github.com/mitchellh/packer/packer" - "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" -) - -type StepKeyPair struct { - Debug bool - DebugKeyPath string - keyName string -} - -func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(Config) - ui := state.Get("ui").(packer.Ui) - - // We need the v2 compute client - computeClient, err := config.computeV2Client() - if err != nil { - err = fmt.Errorf("Error initializing compute client: %s", err) - state.Put("error", err) - return multistep.ActionHalt - } - - ui.Say("Creating temporary keypair for this instance...") - keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID()) - keypair, err := keypairs.Create(computeClient, keypairs.CreateOpts{ - Name: keyName, - }).Extract() - if err != nil { - state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) - return multistep.ActionHalt - } - - if keypair.PrivateKey == "" { - state.Put("error", fmt.Errorf("The temporary keypair returned was blank")) - return multistep.ActionHalt - } - - // If we're in debug mode, output the private key to the working - // directory. - if s.Debug { - ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath)) - f, err := os.Create(s.DebugKeyPath) - if err != nil { - state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) - return multistep.ActionHalt - } - defer f.Close() - - // Write the key out - if _, err := f.Write([]byte(keypair.PrivateKey)); err != nil { - state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) - return multistep.ActionHalt - } - - // Chmod it so that it is SSH ready - if runtime.GOOS != "windows" { - if err := f.Chmod(0600); err != nil { - state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err)) - return multistep.ActionHalt - } - } - } - - // Set the keyname so we know to delete it later - s.keyName = keyName - - // Set some state data for use in future steps - state.Put("keyPair", keyName) - state.Put("privateKey", keypair.PrivateKey) - - return multistep.ActionContinue -} - -func (s *StepKeyPair) Cleanup(state multistep.StateBag) { - // If no key name is set, then we never created it, so just return - if s.keyName == "" { - return - } - - config := state.Get("config").(Config) - ui := state.Get("ui").(packer.Ui) - - // We need the v2 compute client - computeClient, err := config.computeV2Client() - if err != nil { - ui.Error(fmt.Sprintf( - "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) - return - } - - ui.Say("Deleting temporary keypair...") - err = keypairs.Delete(computeClient, s.keyName).ExtractErr() - if err != nil { - ui.Error(fmt.Sprintf( - "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) - } -} diff --git a/builder/openstack-new/step_run_source_server.go b/builder/openstack-new/step_run_source_server.go deleted file mode 100644 index 4432d5860..000000000 --- a/builder/openstack-new/step_run_source_server.go +++ /dev/null @@ -1,110 +0,0 @@ -package openstack - -import ( - "fmt" - "log" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" - "github.com/rackspace/gophercloud/openstack/compute/v2/servers" -) - -type StepRunSourceServer struct { - Flavor string - Name string - SourceImage string - SecurityGroups []string - Networks []string - - server *servers.Server -} - -func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction { - config := state.Get("config").(Config) - keyName := state.Get("keyPair").(string) - ui := state.Get("ui").(packer.Ui) - - // We need the v2 compute client - computeClient, err := config.computeV2Client() - if err != nil { - err = fmt.Errorf("Error initializing compute client: %s", err) - state.Put("error", err) - return multistep.ActionHalt - } - - networks := make([]servers.Network, len(s.Networks)) - for i, networkUuid := range s.Networks { - networks[i].UUID = networkUuid - } - - s.server, err = servers.Create(computeClient, keypairs.CreateOptsExt{ - CreateOptsBuilder: servers.CreateOpts{ - Name: s.Name, - ImageRef: s.SourceImage, - FlavorName: s.Flavor, - SecurityGroups: s.SecurityGroups, - Networks: networks, - }, - - KeyName: keyName, - }).Extract() - if err != nil { - err := fmt.Errorf("Error launching source server: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Printf("server id: %s", s.server.ID) - - ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.ID)) - stateChange := StateChangeConf{ - Pending: []string{"BUILD"}, - Target: "ACTIVE", - Refresh: ServerStateRefreshFunc(computeClient, s.server), - StepState: state, - } - latestServer, err := WaitForState(&stateChange) - if err != nil { - err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.ID, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - s.server = latestServer.(*servers.Server) - state.Put("server", s.server) - - return multistep.ActionContinue -} - -func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) { - if s.server == nil { - return - } - - config := state.Get("config").(Config) - ui := state.Get("ui").(packer.Ui) - - // We need the v2 compute client - computeClient, err := config.computeV2Client() - if err != nil { - ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err)) - return - } - - ui.Say("Terminating the source server...") - if err := servers.Delete(computeClient, s.server.ID).ExtractErr(); err != nil { - ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err)) - return - } - - stateChange := StateChangeConf{ - Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"}, - Refresh: ServerStateRefreshFunc(computeClient, s.server), - Target: "DELETED", - } - - WaitForState(&stateChange) -} diff --git a/builder/openstack-new/step_wait_for_rackconnect.go b/builder/openstack-new/step_wait_for_rackconnect.go deleted file mode 100644 index 6263bd17d..000000000 --- a/builder/openstack-new/step_wait_for_rackconnect.go +++ /dev/null @@ -1,52 +0,0 @@ -package openstack - -import ( - "fmt" - "time" - - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "github.com/rackspace/gophercloud/openstack/compute/v2/servers" -) - -type StepWaitForRackConnect struct { - Wait bool -} - -func (s *StepWaitForRackConnect) Run(state multistep.StateBag) multistep.StepAction { - if !s.Wait { - return multistep.ActionContinue - } - - config := state.Get("config").(Config) - server := state.Get("server").(*servers.Server) - ui := state.Get("ui").(packer.Ui) - - // We need the v2 compute client - computeClient, err := config.computeV2Client() - if err != nil { - err = fmt.Errorf("Error initializing compute client: %s", err) - state.Put("error", err) - return multistep.ActionHalt - } - - ui.Say(fmt.Sprintf( - "Waiting for server (%s) to become RackConnect ready...", server.ID)) - for { - server, err = servers.Get(computeClient, server.ID).Extract() - if err != nil { - return multistep.ActionHalt - } - - if server.Metadata["rackconnect_automation_status"] == "DEPLOYED" { - break - } - - time.Sleep(2 * time.Second) - } - - return multistep.ActionContinue -} - -func (s *StepWaitForRackConnect) Cleanup(state multistep.StateBag) { -} diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index cb1c9d7bd..e0f962c50 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -4,99 +4,106 @@ import ( "crypto/tls" "fmt" "net/http" - "net/url" "os" - "strings" - "github.com/mitchellh/gophercloud-fork-40444fb" - "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/template/interpolate" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" ) // AccessConfig is for common configuration related to openstack access type AccessConfig struct { - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - ApiKey string `mapstructure:"api_key"` - Project string `mapstructure:"project"` - Provider string `mapstructure:"provider"` - RawRegion string `mapstructure:"region"` - ProxyUrl string `mapstructure:"proxy_url"` - TenantId string `mapstructure:"tenant_id"` - Insecure bool `mapstructure:"insecure"` -} + Username string `mapstructure:"username"` + UserID string `mapstructure:"user_id"` + Password string `mapstructure:"password"` + APIKey string `mapstructure:"api_key"` + IdentityEndpoint string `mapstructure:"identity_endpoint"` + TenantID string `mapstructure:"tenant_id"` + TenantName string `mapstructure:"tenant_name"` + DomainID string `mapstructure:"domain_id"` + DomainName string `mapstructure:"domain_name"` + Insecure bool `mapstructure:"insecure"` + Region string `mapstructure:"region"` + EndpointType string `mapstructure:"endpoint_type"` -// Auth returns a valid Auth object for access to openstack services, or -// an error if the authentication couldn't be resolved. -func (c *AccessConfig) Auth() (gophercloud.AccessProvider, error) { - c.Username = common.ChooseString(c.Username, os.Getenv("SDK_USERNAME"), os.Getenv("OS_USERNAME")) - c.Password = common.ChooseString(c.Password, os.Getenv("SDK_PASSWORD"), os.Getenv("OS_PASSWORD")) - c.ApiKey = common.ChooseString(c.ApiKey, os.Getenv("SDK_API_KEY")) - c.Project = common.ChooseString(c.Project, os.Getenv("SDK_PROJECT"), os.Getenv("OS_TENANT_NAME")) - c.Provider = common.ChooseString(c.Provider, os.Getenv("SDK_PROVIDER"), os.Getenv("OS_AUTH_URL")) - c.RawRegion = common.ChooseString(c.RawRegion, os.Getenv("SDK_REGION"), os.Getenv("OS_REGION_NAME")) - c.TenantId = common.ChooseString(c.TenantId, os.Getenv("OS_TENANT_ID")) - - // OpenStack's auto-generated openrc.sh files do not append the suffix - // /tokens to the authentication URL. This ensures it is present when - // specifying the URL. - if strings.Contains(c.Provider, "://") && !strings.HasSuffix(c.Provider, "/tokens") { - c.Provider += "/tokens" - } - - authoptions := gophercloud.AuthOptions{ - AllowReauth: true, - - ApiKey: c.ApiKey, - TenantId: c.TenantId, - TenantName: c.Project, - Username: c.Username, - Password: c.Password, - } - - default_transport := &http.Transport{} - - if c.Insecure { - cfg := new(tls.Config) - cfg.InsecureSkipVerify = true - default_transport.TLSClientConfig = cfg - } - - // For corporate networks it may be the case where we want our API calls - // to be sent through a separate HTTP proxy than external traffic. - if c.ProxyUrl != "" { - url, err := url.Parse(c.ProxyUrl) - if err != nil { - return nil, err - } - - // The gophercloud.Context has a UseCustomClient method which - // would allow us to override with a new instance of http.Client. - default_transport.Proxy = http.ProxyURL(url) - } - - if c.Insecure || c.ProxyUrl != "" { - http.DefaultTransport = default_transport - } - - return gophercloud.Authenticate(c.Provider, authoptions) -} - -func (c *AccessConfig) Region() string { - return common.ChooseString(c.RawRegion, os.Getenv("SDK_REGION"), os.Getenv("OS_REGION_NAME")) + osClient *gophercloud.ProviderClient } func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { - errs := make([]error, 0) - if strings.HasPrefix(c.Provider, "rackspace") { - if c.Region() == "" { - errs = append(errs, fmt.Errorf("region must be specified when using rackspace")) + if c.EndpointType != "internal" && c.EndpointType != "internalURL" && + c.EndpointType != "admin" && c.EndpointType != "adminURL" && + c.EndpointType != "public" && c.EndpointType != "publicURL" && + c.EndpointType != "" { + return []error{fmt.Errorf("Invalid endpoint type provided")} + } + + if c.Region == "" { + c.Region = os.Getenv("OS_REGION_NAME") + } + + // Get as much as possible from the end + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return []error{err} + } + + // Override values if we have them in our config + overrides := []struct { + From, To *string + }{ + {&c.Username, &ao.Username}, + {&c.UserID, &ao.UserID}, + {&c.Password, &ao.Password}, + {&c.APIKey, &ao.APIKey}, + {&c.IdentityEndpoint, &ao.IdentityEndpoint}, + {&c.TenantID, &ao.TenantID}, + {&c.TenantName, &ao.TenantName}, + {&c.DomainID, &ao.DomainID}, + {&c.DomainName, &ao.DomainName}, + } + for _, s := range overrides { + if *s.From != "" { + *s.To = *s.From } } - if len(errs) > 0 { - return errs + // Build the client itself + client, err := openstack.NewClient(ao.IdentityEndpoint) + if err != nil { + return []error{err} } + // If we have insecure set, then create a custom HTTP client that + // ignores SSL errors. + if c.Insecure { + config := &tls.Config{InsecureSkipVerify: true} + transport := &http.Transport{TLSClientConfig: config} + client.HTTPClient.Transport = transport + } + + // Auth + err = openstack.Authenticate(client, ao) + if err != nil { + return []error{err} + } + + c.osClient = client return nil } + +func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) { + return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{ + Region: c.Region, + Availability: c.getEndpointType(), + }) +} + +func (c *AccessConfig) getEndpointType() gophercloud.Availability { + if c.EndpointType == "internal" || c.EndpointType == "internalURL" { + return gophercloud.AvailabilityInternal + } + if c.EndpointType == "admin" || c.EndpointType == "adminURL" { + return gophercloud.AvailabilityAdmin + } + return gophercloud.AvailabilityPublic +} diff --git a/builder/openstack/access_config_test.go b/builder/openstack/access_config_test.go deleted file mode 100644 index cf37448cc..000000000 --- a/builder/openstack/access_config_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package openstack - -import ( - "os" - "testing" -) - -func init() { - // Clear out the openstack env vars so they don't - // affect our tests. - os.Setenv("SDK_REGION", "") - os.Setenv("OS_REGION_NAME", "") -} - -func testAccessConfig() *AccessConfig { - return &AccessConfig{} -} - -func TestAccessConfigPrepare_NoRegion_Rackspace(t *testing.T) { - c := testAccessConfig() - c.Provider = "rackspace-us" - if err := c.Prepare(nil); err == nil { - t.Fatalf("shouldn't have err: %s", err) - } -} - -func TestAccessConfigRegionWithEmptyEnv(t *testing.T) { - c := testAccessConfig() - c.Prepare(nil) - if c.Region() != "" { - t.Fatalf("Region should be empty") - } -} - -func TestAccessConfigRegionWithSdkRegionEnv(t *testing.T) { - c := testAccessConfig() - c.Prepare(nil) - - expectedRegion := "sdk_region" - os.Setenv("SDK_REGION", expectedRegion) - os.Setenv("OS_REGION_NAME", "") - if c.Region() != expectedRegion { - t.Fatalf("Region should be: %s", expectedRegion) - } -} - -func TestAccessConfigRegionWithOsRegionNameEnv(t *testing.T) { - c := testAccessConfig() - c.Prepare(nil) - - expectedRegion := "os_region_name" - os.Setenv("SDK_REGION", "") - os.Setenv("OS_REGION_NAME", expectedRegion) - if c.Region() != expectedRegion { - t.Fatalf("Region should be: %s", expectedRegion) - } -} - -func TestAccessConfigPrepare_NoRegion_PrivateCloud(t *testing.T) { - c := testAccessConfig() - c.Provider = "http://some-keystone-server:5000/v2.0" - if err := c.Prepare(nil); err != nil { - t.Fatalf("shouldn't have err: %s", err) - } -} - -func TestAccessConfigPrepare_Region(t *testing.T) { - dfw := "DFW" - c := testAccessConfig() - c.RawRegion = dfw - if err := c.Prepare(nil); err != nil { - t.Fatalf("shouldn't have err: %s", err) - } - if dfw != c.Region() { - t.Fatalf("Regions do not match: %s %s", dfw, c.Region()) - } -} diff --git a/builder/openstack/artifact.go b/builder/openstack/artifact.go index 6e75fad3e..aa60d2641 100644 --- a/builder/openstack/artifact.go +++ b/builder/openstack/artifact.go @@ -4,7 +4,8 @@ import ( "fmt" "log" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/images" ) // Artifact is an artifact implementation that contains built images. @@ -16,7 +17,7 @@ type Artifact struct { BuilderIdValue string // OpenStack connection for performing API stuff. - Conn gophercloud.CloudServersProvider + Client *gophercloud.ServiceClient } func (a *Artifact) BuilderId() string { @@ -42,5 +43,5 @@ func (a *Artifact) State(name string) interface{} { func (a *Artifact) Destroy() error { log.Printf("Destroying image: %s", a.ImageId) - return a.Conn.DeleteImageById(a.ImageId) + return images.Delete(a.Client, a.ImageId).ExtractErr() } diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index e6e5c6675..bebb28452 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -9,7 +9,6 @@ import ( "github.com/mitchellh/packer/common" "log" - "github.com/mitchellh/gophercloud-fork-40444fb" "github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" @@ -55,28 +54,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { - auth, err := b.config.AccessConfig.Auth() + computeClient, err := b.config.computeV2Client() if err != nil { - return nil, err - } - //fetches the api requisites from gophercloud for the appropriate - //openstack variant - api, err := gophercloud.PopulateApi(b.config.RunConfig.OpenstackProvider) - if err != nil { - return nil, err - } - api.Region = b.config.AccessConfig.Region() - - csp, err := gophercloud.ServersApi(auth, api) - if err != nil { - log.Printf("Region: %s", b.config.AccessConfig.Region()) - return nil, err + return nil, fmt.Errorf("Error initializing compute client: %s", err) } // Setup the state bag and initial state for the steps state := new(multistep.BasicStateBag) state.Put("config", b.config) - state.Put("csp", csp) state.Put("hook", hook) state.Put("ui", ui) @@ -101,7 +86,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe FloatingIp: b.config.FloatingIp, }, &common.StepConnectSSH{ - SSHAddress: SSHAddress(csp, b.config.SSHInterface, b.config.SSHPort), + SSHAddress: SSHAddress(computeClient, b.config.SSHInterface, b.config.SSHPort), SSHConfig: SSHConfig(b.config.SSHUsername), SSHWaitTimeout: b.config.SSHTimeout(), }, @@ -135,7 +120,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe artifact := &Artifact{ ImageId: state.Get("image").(string), BuilderIdValue: BuilderId, - Conn: csp, + Client: computeClient, } return artifact, nil diff --git a/builder/openstack/server.go b/builder/openstack/server.go index ba22dd3e2..de8c9d103 100644 --- a/builder/openstack/server.go +++ b/builder/openstack/server.go @@ -3,12 +3,12 @@ package openstack import ( "errors" "fmt" - "github.com/mitchellh/multistep" - "github.com/racker/perigee" "log" "time" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/mitchellh/multistep" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) // StateRefreshFunc is a function type used for StateChangeConf that is @@ -33,21 +33,22 @@ type StateChangeConf struct { // ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch // an openstack server. -func ServerStateRefreshFunc(csp gophercloud.CloudServersProvider, s *gophercloud.Server) StateRefreshFunc { +func ServerStateRefreshFunc( + client *gophercloud.ServiceClient, s *servers.Server) StateRefreshFunc { return func() (interface{}, string, int, error) { - resp, err := csp.ServerById(s.Id) + serverNew, err := servers.Get(client, s.ID).Extract() if err != nil { - urce, ok := err.(*perigee.UnexpectedResponseCodeError) - if ok && (urce.Actual == 404) { - log.Printf("404 on ServerStateRefresh, returning DELETED") - + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if ok && errCode.Actual == 404 { + log.Printf("[INFO] 404 on ServerStateRefresh, returning DELETED") return nil, "DELETED", 0, nil } else { - log.Printf("Error on ServerStateRefresh: %s", err) + log.Printf("[ERROR] Error on ServerStateRefresh: %s", err) return nil, "", 0, err } } - return resp, resp.Status, resp.Progress, nil + + return serverNew, serverNew.Status, serverNew.Progress, nil } } diff --git a/builder/openstack/ssh.go b/builder/openstack/ssh.go index d20f24170..7b0510f98 100644 --- a/builder/openstack/ssh.go +++ b/builder/openstack/ssh.go @@ -3,49 +3,67 @@ package openstack import ( "errors" "fmt" - "github.com/mitchellh/multistep" - "golang.org/x/crypto/ssh" + "log" "time" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/mitchellh/multistep" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "golang.org/x/crypto/ssh" ) // SSHAddress returns a function that can be given to the SSH communicator // for determining the SSH address based on the server AccessIPv4 setting.. -func SSHAddress(csp gophercloud.CloudServersProvider, sshinterface string, port int) func(multistep.StateBag) (string, error) { +func SSHAddress( + client *gophercloud.ServiceClient, + sshinterface string, port int) func(multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { - s := state.Get("server").(*gophercloud.Server) + s := state.Get("server").(*servers.Server) - if ip := state.Get("access_ip").(gophercloud.FloatingIp); ip.Ip != "" { - return fmt.Sprintf("%s:%d", ip.Ip, port), nil + // If we have a floating IP, use that + ip := state.Get("access_ip").(*floatingip.FloatingIP) + if ip != nil && ip.FixedIP != "" { + return fmt.Sprintf("%s:%d", ip.FixedIP, port), nil } - ip_pools, err := s.AllAddressPools() - if err != nil { - return "", errors.New("Error parsing SSH addresses") + if s.AccessIPv4 != "" { + return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil } - for pool, addresses := range ip_pools { - if sshinterface != "" { - if pool != sshinterface { - continue - } + + // Get all the addresses associated with this server. This + // was taken directly from Terraform. + for _, networkAddresses := range s.Addresses { + elements, ok := networkAddresses.([]interface{}) + if !ok { + log.Printf( + "[ERROR] Unknown return type for address field: %#v", + networkAddresses) + continue } - if pool != "" { - for _, address := range addresses { - if address.Addr != "" && address.Version == 4 { - return fmt.Sprintf("%s:%d", address.Addr, port), nil + + for _, element := range elements { + var addr string + address := element.(map[string]interface{}) + if address["OS-EXT-IPS:type"] == "floating" { + addr = address["addr"].(string) + } else { + if address["version"].(float64) == 4 { + addr = address["addr"].(string) } } + if addr != "" { + return fmt.Sprintf("%s:%d", addr, port), nil + } } } - serverState, err := csp.ServerById(s.Id) - + s, err := servers.Get(client, s.ID).Extract() if err != nil { return "", err } - state.Put("server", serverState) + state.Put("server", s) time.Sleep(1 * time.Second) return "", errors.New("couldn't determine IP address for server") diff --git a/builder/openstack/step_allocate_ip.go b/builder/openstack/step_allocate_ip.go index b64f8b617..16efe8d38 100644 --- a/builder/openstack/step_allocate_ip.go +++ b/builder/openstack/step_allocate_ip.go @@ -2,10 +2,11 @@ package openstack import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) type StepAllocateIp struct { @@ -15,53 +16,78 @@ type StepAllocateIp struct { func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) - csp := state.Get("csp").(gophercloud.CloudServersProvider) - server := state.Get("server").(*gophercloud.Server) + config := state.Get("config").(Config) + server := state.Get("server").(*servers.Server) - var instanceIp gophercloud.FloatingIp + // 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 + } + + var instanceIp *floatingip.FloatingIP // This is here in case we error out before putting instanceIp into the // statebag below, because it is requested by Cleanup() state.Put("access_ip", instanceIp) if s.FloatingIp != "" { - instanceIp.Ip = s.FloatingIp + *instanceIp = floatingip.FloatingIP{FixedIP: s.FloatingIp} } else if s.FloatingIpPool != "" { - newIp, err := csp.CreateFloatingIp(s.FloatingIpPool) + newIp, err := floatingip.Create(client, floatingip.CreateOpts{ + Pool: s.FloatingIpPool, + }).Extract() if err != nil { err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - instanceIp = newIp - ui.Say(fmt.Sprintf("Created temporary floating IP %s...", instanceIp.Ip)) + + *instanceIp = *newIp + ui.Say(fmt.Sprintf("Created temporary floating IP %s...", instanceIp.FixedIP)) } - if instanceIp.Ip != "" { - if err := csp.AssociateFloatingIp(server.Id, instanceIp); err != nil { - err := fmt.Errorf("Error associating floating IP %s with instance.", instanceIp.Ip) + if instanceIp != nil && instanceIp.FixedIP != "" { + err := floatingip.Associate(client, server.ID, instanceIp.FixedIP).ExtractErr() + if err != nil { + err := fmt.Errorf( + "Error associating floating IP %s with instance.", + instanceIp.FixedIP) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt - } else { - ui.Say(fmt.Sprintf("Added floating IP %s to instance...", instanceIp.Ip)) } + + ui.Say(fmt.Sprintf( + "Added floating IP %s to instance...", instanceIp.FixedIP)) } state.Put("access_ip", instanceIp) - return multistep.ActionContinue } func (s *StepAllocateIp) Cleanup(state multistep.StateBag) { + config := state.Get("config").(Config) ui := state.Get("ui").(packer.Ui) - csp := state.Get("csp").(gophercloud.CloudServersProvider) - instanceIp := state.Get("access_ip").(gophercloud.FloatingIp) - if s.FloatingIpPool != "" && instanceIp.Id != 0 { - if err := csp.DeleteFloatingIp(instanceIp); err != nil { - ui.Error(fmt.Sprintf("Error deleting temporary floating IP %s", instanceIp.Ip)) + instanceIp := state.Get("access_ip").(*floatingip.FloatingIP) + + // We need the v2 compute client + client, err := config.computeV2Client() + if err != nil { + ui.Error(fmt.Sprintf( + "Error deleting temporary floating IP %s", instanceIp.FixedIP)) + return + } + + if s.FloatingIpPool != "" && instanceIp.ID != "" { + if err := floatingip.Delete(client, instanceIp.ID).ExtractErr(); err != nil { + ui.Error(fmt.Sprintf( + "Error deleting temporary floating IP %s", instanceIp.FixedIP)) return } - ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.Ip)) + + ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.FixedIP)) } } diff --git a/builder/openstack/step_create_image.go b/builder/openstack/step_create_image.go index 52a2ec4d1..b777e8b0b 100644 --- a/builder/openstack/step_create_image.go +++ b/builder/openstack/step_create_image.go @@ -2,28 +2,36 @@ package openstack import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" "log" "time" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/images" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) type stepCreateImage struct{} func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction { - csp := state.Get("csp").(gophercloud.CloudServersProvider) config := state.Get("config").(Config) - server := state.Get("server").(*gophercloud.Server) + server := state.Get("server").(*servers.Server) 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 + } + // Create the image ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName)) - createOpts := gophercloud.CreateImage{ + imageId, err := servers.CreateImage(client, server.ID, servers.CreateImageOpts{ Name: config.ImageName, - } - imageId, err := csp.CreateImage(server.Id, createOpts) + }).ExtractImageID() if err != nil { err := fmt.Errorf("Error creating image: %s", err) state.Put("error", err) @@ -32,12 +40,12 @@ func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction { } // Set the Image ID in the state - ui.Say(fmt.Sprintf("Image: %s", imageId)) + ui.Message(fmt.Sprintf("Image: %s", imageId)) state.Put("image", imageId) // Wait for the image to become ready ui.Say("Waiting for image to become ready...") - if err := WaitForImage(csp, imageId); err != nil { + if err := WaitForImage(client, imageId); err != nil { err := fmt.Errorf("Error waiting for image: %s", err) state.Put("error", err) ui.Error(err.Error()) @@ -52,10 +60,17 @@ func (s *stepCreateImage) Cleanup(multistep.StateBag) { } // WaitForImage waits for the given Image ID to become ready. -func WaitForImage(csp gophercloud.CloudServersProvider, imageId string) error { +func WaitForImage(client *gophercloud.ServiceClient, imageId string) error { for { - image, err := csp.ImageById(imageId) + image, err := images.Get(client, imageId).Extract() if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if ok && errCode.Actual == 500 { + log.Printf("[ERROR] 500 error received, will ignore and retry: %s", err) + time.Sleep(2 * time.Second) + continue + } + return err } diff --git a/builder/openstack/step_key_pair.go b/builder/openstack/step_key_pair.go index 9c46b4377..06bcbf9ea 100644 --- a/builder/openstack/step_key_pair.go +++ b/builder/openstack/step_key_pair.go @@ -2,14 +2,13 @@ package openstack import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/common/uuid" - "github.com/mitchellh/packer/packer" - "log" "os" "runtime" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/common/uuid" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" ) type StepKeyPair struct { @@ -19,18 +18,28 @@ type StepKeyPair struct { } func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { - csp := state.Get("csp").(gophercloud.CloudServersProvider) + config := state.Get("config").(Config) ui := state.Get("ui").(packer.Ui) + // We need the v2 compute client + computeClient, err := config.computeV2Client() + if err != nil { + err = fmt.Errorf("Error initializing compute client: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + ui.Say("Creating temporary keypair for this instance...") keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID()) - log.Printf("temporary keypair name: %s", keyName) - keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName}) + keypair, err := keypairs.Create(computeClient, keypairs.CreateOpts{ + Name: keyName, + }).Extract() if err != nil { state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) return multistep.ActionHalt } - if keyResp.PrivateKey == "" { + + if keypair.PrivateKey == "" { state.Put("error", fmt.Errorf("The temporary keypair returned was blank")) return multistep.ActionHalt } @@ -47,7 +56,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { defer f.Close() // Write the key out - if _, err := f.Write([]byte(keyResp.PrivateKey)); err != nil { + if _, err := f.Write([]byte(keypair.PrivateKey)); err != nil { state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) return multistep.ActionHalt } @@ -66,7 +75,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { // Set some state data for use in future steps state.Put("keyPair", keyName) - state.Put("privateKey", keyResp.PrivateKey) + state.Put("privateKey", keypair.PrivateKey) return multistep.ActionContinue } @@ -77,11 +86,19 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) { return } - csp := state.Get("csp").(gophercloud.CloudServersProvider) + config := state.Get("config").(Config) ui := state.Get("ui").(packer.Ui) + // We need the v2 compute client + computeClient, err := config.computeV2Client() + if err != nil { + ui.Error(fmt.Sprintf( + "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) + return + } + ui.Say("Deleting temporary keypair...") - err := csp.DeleteKeyPair(s.keyName) + err = keypairs.Delete(computeClient, s.keyName).ExtractErr() if err != nil { ui.Error(fmt.Sprintf( "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) diff --git a/builder/openstack/step_run_source_server.go b/builder/openstack/step_run_source_server.go index 19e7f024d..4432d5860 100644 --- a/builder/openstack/step_run_source_server.go +++ b/builder/openstack/step_run_source_server.go @@ -2,11 +2,12 @@ package openstack import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" "log" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) type StepRunSourceServer struct { @@ -16,37 +17,38 @@ type StepRunSourceServer struct { SecurityGroups []string Networks []string - server *gophercloud.Server + server *servers.Server } func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction { - csp := state.Get("csp").(gophercloud.CloudServersProvider) + config := state.Get("config").(Config) keyName := state.Get("keyPair").(string) ui := state.Get("ui").(packer.Ui) - // XXX - validate image and flavor is available - - securityGroups := make([]map[string]interface{}, len(s.SecurityGroups)) - for i, groupName := range s.SecurityGroups { - securityGroups[i] = make(map[string]interface{}) - securityGroups[i]["name"] = groupName + // We need the v2 compute client + computeClient, err := config.computeV2Client() + if err != nil { + err = fmt.Errorf("Error initializing compute client: %s", err) + state.Put("error", err) + return multistep.ActionHalt } - networks := make([]gophercloud.NetworkConfig, len(s.Networks)) + networks := make([]servers.Network, len(s.Networks)) for i, networkUuid := range s.Networks { - networks[i].Uuid = networkUuid + networks[i].UUID = networkUuid } - server := gophercloud.NewServer{ - Name: s.Name, - ImageRef: s.SourceImage, - FlavorRef: s.Flavor, - KeyPairName: keyName, - SecurityGroup: securityGroups, - Networks: networks, - } + s.server, err = servers.Create(computeClient, keypairs.CreateOptsExt{ + CreateOptsBuilder: servers.CreateOpts{ + Name: s.Name, + ImageRef: s.SourceImage, + FlavorName: s.Flavor, + SecurityGroups: s.SecurityGroups, + Networks: networks, + }, - serverResp, err := csp.CreateServer(server) + KeyName: keyName, + }).Extract() if err != nil { err := fmt.Errorf("Error launching source server: %s", err) state.Put("error", err) @@ -54,25 +56,24 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction return multistep.ActionHalt } - s.server, err = csp.ServerById(serverResp.Id) - log.Printf("server id: %s", s.server.Id) + log.Printf("server id: %s", s.server.ID) - ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.Id)) + ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.ID)) stateChange := StateChangeConf{ Pending: []string{"BUILD"}, Target: "ACTIVE", - Refresh: ServerStateRefreshFunc(csp, s.server), + Refresh: ServerStateRefreshFunc(computeClient, s.server), StepState: state, } latestServer, err := WaitForState(&stateChange) 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.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - s.server = latestServer.(*gophercloud.Server) + s.server = latestServer.(*servers.Server) state.Put("server", s.server) return multistep.ActionContinue @@ -83,18 +84,25 @@ func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) { return } - csp := state.Get("csp").(gophercloud.CloudServersProvider) + config := state.Get("config").(Config) ui := state.Get("ui").(packer.Ui) + // We need the v2 compute client + computeClient, err := config.computeV2Client() + if err != nil { + ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err)) + return + } + ui.Say("Terminating the source server...") - if err := csp.DeleteServerById(s.server.Id); err != nil { + if err := servers.Delete(computeClient, s.server.ID).ExtractErr(); err != nil { ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err)) return } stateChange := StateChangeConf{ Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"}, - Refresh: ServerStateRefreshFunc(csp, s.server), + Refresh: ServerStateRefreshFunc(computeClient, s.server), Target: "DELETED", } diff --git a/builder/openstack/step_wait_for_rackconnect.go b/builder/openstack/step_wait_for_rackconnect.go index ee6ee6138..6263bd17d 100644 --- a/builder/openstack/step_wait_for_rackconnect.go +++ b/builder/openstack/step_wait_for_rackconnect.go @@ -2,11 +2,11 @@ package openstack import ( "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" "time" - "github.com/mitchellh/gophercloud-fork-40444fb" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) type StepWaitForRackConnect struct { @@ -18,14 +18,22 @@ func (s *StepWaitForRackConnect) Run(state multistep.StateBag) multistep.StepAct return multistep.ActionContinue } - csp := state.Get("csp").(gophercloud.CloudServersProvider) - server := state.Get("server").(*gophercloud.Server) + config := state.Get("config").(Config) + server := state.Get("server").(*servers.Server) ui := state.Get("ui").(packer.Ui) - ui.Say(fmt.Sprintf("Waiting for server (%s) to become RackConnect ready...", server.Id)) + // We need the v2 compute client + computeClient, err := config.computeV2Client() + if err != nil { + err = fmt.Errorf("Error initializing compute client: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + ui.Say(fmt.Sprintf( + "Waiting for server (%s) to become RackConnect ready...", server.ID)) for { - server, err := csp.ServerById(server.Id) + server, err = servers.Get(computeClient, server.ID).Extract() if err != nil { return multistep.ActionHalt }