diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index d63e847f2..e745b8db0 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -92,6 +92,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Flavor: b.config.Flavor, SourceImage: b.config.SourceImage, }, + &StepAllocateIp{ + FloatingIpPool: b.config.FloatingIpPool, + FloatingIp: b.config.FloatingIp, + }, &common.StepConnectSSH{ SSHAddress: SSHAddress(csp, b.config.SSHPort), SSHConfig: SSHConfig(b.config.SSHUsername), diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index 688d681a5..96388e254 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -16,6 +16,9 @@ type RunConfig struct { SSHUsername string `mapstructure:"ssh_username"` SSHPort int `mapstructure:"ssh_port"` OpenstackProvider string `mapstructure:"openstack_provider"` + UseFloatingIp bool `mapstructure:"use_floating_ip"` + FloatingIpPool string `mapstructure:"floating_ip_pool"` + FloatingIp string `mapstructure:"floating_ip"` // Unexported fields that are calculated from others sshTimeout time.Duration @@ -43,6 +46,10 @@ func (c *RunConfig) Prepare(t *packer.ConfigTemplate) []error { c.RawSSHTimeout = "5m" } + if c.UseFloatingIp && c.FloatingIpPool == "" { + c.FloatingIpPool = "public" + } + // Validation var err error errs := make([]error, 0) diff --git a/builder/openstack/ssh.go b/builder/openstack/ssh.go index a27a7f3c4..bdb3d75d6 100644 --- a/builder/openstack/ssh.go +++ b/builder/openstack/ssh.go @@ -13,24 +13,35 @@ import ( // for determining the SSH address based on the server AccessIPv4 setting.. func SSHAddress(csp gophercloud.CloudServersProvider, port int) func(multistep.StateBag) (string, error) { return func(state multistep.StateBag) (string, error) { - for j := 0; j < 2; j++ { - s := state.Get("server").(*gophercloud.Server) - if s.AccessIPv4 != "" { - return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil - } - if s.AccessIPv6 != "" { - return fmt.Sprintf("[%s]:%d", s.AccessIPv6, port), nil - } - serverState, err := csp.ServerById(s.Id) + s := state.Get("server").(*gophercloud.Server) - if err != nil { - return "", err - } - - state.Put("server", serverState) - time.Sleep(1 * time.Second) + if ip := state.Get("access_ip").(gophercloud.FloatingIp); ip.Ip != "" { + return fmt.Sprintf("%s:%d", ip.Ip, port), nil } + ip_pools, err := s.AllAddressPools() + if err != nil { + return "", errors.New("Error parsing SSH addresses") + } + for pool, addresses := range ip_pools { + if pool != "" { + for _, address := range addresses { + if address.Addr != "" { + return fmt.Sprintf("%s:%d", address.Addr, port), nil + } + } + } + } + + serverState, err := csp.ServerById(s.Id) + + if err != nil { + return "", err + } + + state.Put("server", serverState) + 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 new file mode 100644 index 000000000..28e97b4d5 --- /dev/null +++ b/builder/openstack/step_allocate_ip.go @@ -0,0 +1,66 @@ +package openstack + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/rackspace/gophercloud" +) + +type StepAllocateIp struct { + FloatingIpPool string + FloatingIp string +} + +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) + + var instanceIp gophercloud.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 + } else if s.FloatingIpPool != "" { + newIp, err := csp.CreateFloatingIp(s.FloatingIpPool) + 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)) + } + + if instanceIp.Ip != "" { + if err := csp.AssociateFloatingIp(server.Id, instanceIp); err != nil { + err := fmt.Errorf("Error associating floating IP %s with instance.", instanceIp.Ip) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } else { + ui.Say(fmt.Sprintf("Added floating IP %s to instance...", instanceIp.Ip)) + } + } + + state.Put("access_ip", instanceIp) + + return multistep.ActionContinue +} + +func (s *StepAllocateIp) Cleanup(state multistep.StateBag) { + 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)) + return + } + ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.Ip)) + } +}