Commit 1428e6df authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

Merge pull request #2218 from mitchellh/f-openstack

OpenStack uses official OpenStack client
parents 1da56993 92b6b5c3
...@@ -4,99 +4,120 @@ import ( ...@@ -4,99 +4,120 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"os" "os"
"strings"
"github.com/mitchellh/gophercloud-fork-40444fb"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
"github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack"
) )
// AccessConfig is for common configuration related to openstack access // AccessConfig is for common configuration related to openstack access
type AccessConfig struct { type AccessConfig struct {
Username string `mapstructure:"username"` Username string `mapstructure:"username"`
UserID string `mapstructure:"user_id"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
ApiKey string `mapstructure:"api_key"` APIKey string `mapstructure:"api_key"`
Project string `mapstructure:"project"` IdentityEndpoint string `mapstructure:"identity_endpoint"`
Provider string `mapstructure:"provider"` TenantID string `mapstructure:"tenant_id"`
RawRegion string `mapstructure:"region"` TenantName string `mapstructure:"tenant_name"`
ProxyUrl string `mapstructure:"proxy_url"` DomainID string `mapstructure:"domain_id"`
TenantId string `mapstructure:"tenant_id"` DomainName string `mapstructure:"domain_name"`
Insecure bool `mapstructure:"insecure"` 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 osClient *gophercloud.ProviderClient
// 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 func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
// /tokens to the authentication URL. This ensures it is present when if c.EndpointType != "internal" && c.EndpointType != "internalURL" &&
// specifying the URL. c.EndpointType != "admin" && c.EndpointType != "adminURL" &&
if strings.Contains(c.Provider, "://") && !strings.HasSuffix(c.Provider, "/tokens") { c.EndpointType != "public" && c.EndpointType != "publicURL" &&
c.Provider += "/tokens" c.EndpointType != "" {
return []error{fmt.Errorf("Invalid endpoint type provided")}
} }
authoptions := gophercloud.AuthOptions{ if c.Region == "" {
AllowReauth: true, c.Region = os.Getenv("OS_REGION_NAME")
ApiKey: c.ApiKey,
TenantId: c.TenantId,
TenantName: c.Project,
Username: c.Username,
Password: c.Password,
} }
default_transport := &http.Transport{} // Legacy RackSpace stuff. We're keeping this around to keep things BC.
if c.APIKey == "" {
c.APIKey = os.Getenv("SDK_API_KEY")
}
if c.Password == "" {
c.Password = os.Getenv("SDK_PASSWORD")
}
if c.Region == "" {
c.Region = os.Getenv("SDK_REGION")
}
if c.TenantName == "" {
c.TenantName = os.Getenv("SDK_PROJECT")
}
if c.Username == "" {
c.Username = os.Getenv("SDK_USERNAME")
}
if c.Insecure { // Get as much as possible from the end
cfg := new(tls.Config) ao, _ := openstack.AuthOptionsFromEnv()
cfg.InsecureSkipVerify = true
default_transport.TLSClientConfig = cfg // 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
}
} }
// For corporate networks it may be the case where we want our API calls // Build the client itself
// to be sent through a separate HTTP proxy than external traffic. client, err := openstack.NewClient(ao.IdentityEndpoint)
if c.ProxyUrl != "" {
url, err := url.Parse(c.ProxyUrl)
if err != nil { if err != nil {
return nil, err return []error{err}
} }
// The gophercloud.Context has a UseCustomClient method which // If we have insecure set, then create a custom HTTP client that
// would allow us to override with a new instance of http.Client. // ignores SSL errors.
default_transport.Proxy = http.ProxyURL(url) if c.Insecure {
config := &tls.Config{InsecureSkipVerify: true}
transport := &http.Transport{TLSClientConfig: config}
client.HTTPClient.Transport = transport
} }
if c.Insecure || c.ProxyUrl != "" { // Auth
http.DefaultTransport = default_transport err = openstack.Authenticate(client, ao)
if err != nil {
return []error{err}
} }
return gophercloud.Authenticate(c.Provider, authoptions) c.osClient = client
return nil
} }
func (c *AccessConfig) Region() string { func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) {
return common.ChooseString(c.RawRegion, os.Getenv("SDK_REGION"), os.Getenv("OS_REGION_NAME")) return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{
Region: c.Region,
Availability: c.getEndpointType(),
})
} }
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { func (c *AccessConfig) getEndpointType() gophercloud.Availability {
errs := make([]error, 0) if c.EndpointType == "internal" || c.EndpointType == "internalURL" {
if strings.HasPrefix(c.Provider, "rackspace") { return gophercloud.AvailabilityInternal
if c.Region() == "" {
errs = append(errs, fmt.Errorf("region must be specified when using rackspace"))
}
} }
if c.EndpointType == "admin" || c.EndpointType == "adminURL" {
if len(errs) > 0 { return gophercloud.AvailabilityAdmin
return errs
} }
return gophercloud.AvailabilityPublic
return nil
} }
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())
}
}
...@@ -4,7 +4,8 @@ import ( ...@@ -4,7 +4,8 @@ import (
"fmt" "fmt"
"log" "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. // Artifact is an artifact implementation that contains built images.
...@@ -16,7 +17,7 @@ type Artifact struct { ...@@ -16,7 +17,7 @@ type Artifact struct {
BuilderIdValue string BuilderIdValue string
// OpenStack connection for performing API stuff. // OpenStack connection for performing API stuff.
Conn gophercloud.CloudServersProvider Client *gophercloud.ServiceClient
} }
func (a *Artifact) BuilderId() string { func (a *Artifact) BuilderId() string {
...@@ -42,5 +43,5 @@ func (a *Artifact) State(name string) interface{} { ...@@ -42,5 +43,5 @@ func (a *Artifact) State(name string) interface{} {
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %s", a.ImageId) log.Printf("Destroying image: %s", a.ImageId)
return a.Conn.DeleteImageById(a.ImageId) return images.Delete(a.Client, a.ImageId).ExtractErr()
} }
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/mitchellh/packer/common" "github.com/mitchellh/packer/common"
"log" "log"
"github.com/mitchellh/gophercloud-fork-40444fb"
"github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate" "github.com/mitchellh/packer/template/interpolate"
...@@ -55,40 +54,28 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { ...@@ -55,40 +54,28 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
} }
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, 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 { if err != nil {
return nil, err return nil, fmt.Errorf("Error initializing compute client: %s", 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
} }
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
state.Put("config", b.config) state.Put("config", b.config)
state.Put("csp", csp)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
// Build the steps // Build the steps
steps := []multistep.Step{ steps := []multistep.Step{
&StepLoadFlavor{
Flavor: b.config.Flavor,
},
&StepKeyPair{ &StepKeyPair{
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName), DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName),
}, },
&StepRunSourceServer{ &StepRunSourceServer{
Name: b.config.ImageName, Name: b.config.ImageName,
Flavor: b.config.Flavor,
SourceImage: b.config.SourceImage, SourceImage: b.config.SourceImage,
SecurityGroups: b.config.SecurityGroups, SecurityGroups: b.config.SecurityGroups,
Networks: b.config.Networks, Networks: b.config.Networks,
...@@ -101,7 +88,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -101,7 +88,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
FloatingIp: b.config.FloatingIp, FloatingIp: b.config.FloatingIp,
}, },
&common.StepConnectSSH{ &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), SSHConfig: SSHConfig(b.config.SSHUsername),
SSHWaitTimeout: b.config.SSHTimeout(), SSHWaitTimeout: b.config.SSHTimeout(),
}, },
...@@ -135,7 +122,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe ...@@ -135,7 +122,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
artifact := &Artifact{ artifact := &Artifact{
ImageId: state.Get("image").(string), ImageId: state.Get("image").(string),
BuilderIdValue: BuilderId, BuilderIdValue: BuilderId,
Conn: csp, Client: computeClient,
} }
return artifact, nil return artifact, nil
......
...@@ -9,7 +9,6 @@ func testConfig() map[string]interface{} { ...@@ -9,7 +9,6 @@ func testConfig() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"username": "foo", "username": "foo",
"password": "bar", "password": "bar",
"provider": "foo",
"region": "DFW", "region": "DFW",
"image_name": "foo", "image_name": "foo",
"source_image": "foo", "source_image": "foo",
...@@ -40,55 +39,3 @@ func TestBuilder_Prepare_BadType(t *testing.T) { ...@@ -40,55 +39,3 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
t.Fatalf("prepare should fail") 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")
}
}
...@@ -17,14 +17,16 @@ type RunConfig struct { ...@@ -17,14 +17,16 @@ type RunConfig struct {
SSHUsername string `mapstructure:"ssh_username"` SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"` SSHPort int `mapstructure:"ssh_port"`
SSHInterface string `mapstructure:"ssh_interface"` SSHInterface string `mapstructure:"ssh_interface"`
OpenstackProvider string `mapstructure:"openstack_provider"`
UseFloatingIp bool `mapstructure:"use_floating_ip"`
RackconnectWait bool `mapstructure:"rackconnect_wait"` RackconnectWait bool `mapstructure:"rackconnect_wait"`
FloatingIpPool string `mapstructure:"floating_ip_pool"` FloatingIpPool string `mapstructure:"floating_ip_pool"`
FloatingIp string `mapstructure:"floating_ip"` FloatingIp string `mapstructure:"floating_ip"`
SecurityGroups []string `mapstructure:"security_groups"` SecurityGroups []string `mapstructure:"security_groups"`
Networks []string `mapstructure:"networks"` Networks []string `mapstructure:"networks"`
// Not really used, but here for BC
OpenstackProvider string `mapstructure:"openstack_provider"`
UseFloatingIp bool `mapstructure:"use_floating_ip"`
// Unexported fields that are calculated from others // Unexported fields that are calculated from others
sshTimeout time.Duration sshTimeout time.Duration
} }
......
...@@ -3,12 +3,12 @@ package openstack ...@@ -3,12 +3,12 @@ package openstack
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/racker/perigee"
"log" "log"
"time" "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 // StateRefreshFunc is a function type used for StateChangeConf that is
...@@ -33,21 +33,22 @@ type StateChangeConf struct { ...@@ -33,21 +33,22 @@ type StateChangeConf struct {
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch // ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
// an openstack server. // 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) { return func() (interface{}, string, int, error) {
resp, err := csp.ServerById(s.Id) serverNew, err := servers.Get(client, s.ID).Extract()
if err != nil { if err != nil {
urce, ok := err.(*perigee.UnexpectedResponseCodeError) errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
if ok && (urce.Actual == 404) { if ok && errCode.Actual == 404 {
log.Printf("404 on ServerStateRefresh, returning DELETED") log.Printf("[INFO] 404 on ServerStateRefresh, returning DELETED")
return nil, "DELETED", 0, nil return nil, "DELETED", 0, nil
} else { } else {
log.Printf("Error on ServerStateRefresh: %s", err) log.Printf("[ERROR] Error on ServerStateRefresh: %s", err)
return nil, "", 0, err return nil, "", 0, err
} }
} }
return resp, resp.Status, resp.Progress, nil
return serverNew, serverNew.Status, serverNew.Progress, nil
} }
} }
......
...@@ -3,49 +3,67 @@ package openstack ...@@ -3,49 +3,67 @@ package openstack
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/mitchellh/multistep" "log"
"golang.org/x/crypto/ssh"
"time" "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 // 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, 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) { 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 != "" { // If we have a floating IP, use that
return fmt.Sprintf("%s:%d", ip.Ip, port), nil ip := state.Get("access_ip").(*floatingip.FloatingIP)
if ip != nil && ip.IP != "" {
return fmt.Sprintf("%s:%d", ip.IP, port), nil
} }
ip_pools, err := s.AllAddressPools() if s.AccessIPv4 != "" {
if err != nil { return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil
return "", errors.New("Error parsing SSH addresses")
} }
for pool, addresses := range ip_pools {
if sshinterface != "" { // Get all the addresses associated with this server. This
if pool != sshinterface { // 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 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 pool != "" {
for _, address := range addresses {
if address.Addr != "" && address.Version == 4 {
return fmt.Sprintf("%s:%d", address.Addr, port), nil
} }
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 { if err != nil {
return "", err return "", err
} }
state.Put("server", serverState) state.Put("server", s)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
return "", errors.New("couldn't determine IP address for server") return "", errors.New("couldn't determine IP address for server")
......
...@@ -2,10 +2,11 @@ package openstack ...@@ -2,10 +2,11 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
"github.com/mitchellh/gophercloud-fork-40444fb" "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
) )
type StepAllocateIp struct { type StepAllocateIp struct {
...@@ -15,53 +16,79 @@ type StepAllocateIp struct { ...@@ -15,53 +16,79 @@ type StepAllocateIp struct {
func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction { func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
csp := state.Get("csp").(gophercloud.CloudServersProvider) config := state.Get("config").(Config)
server := state.Get("server").(*gophercloud.Server) 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
var instanceIp gophercloud.FloatingIp
// This is here in case we error out before putting instanceIp into the // This is here in case we error out before putting instanceIp into the
// statebag below, because it is requested by Cleanup() // statebag below, because it is requested by Cleanup()
state.Put("access_ip", instanceIp) state.Put("access_ip", &instanceIp)
if s.FloatingIp != "" { if s.FloatingIp != "" {
instanceIp.Ip = s.FloatingIp instanceIp.IP = s.FloatingIp
} else if s.FloatingIpPool != "" { } else if s.FloatingIpPool != "" {
newIp, err := csp.CreateFloatingIp(s.FloatingIpPool) newIp, err := floatingip.Create(client, floatingip.CreateOpts{
Pool: s.FloatingIpPool,
}).Extract()
if err != nil { if err != nil {
err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool) err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt 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.IP))
} }
if instanceIp.Ip != "" { if instanceIp.IP != "" {
if err := csp.AssociateFloatingIp(server.Id, instanceIp); err != nil { err := floatingip.Associate(client, server.ID, instanceIp.IP).ExtractErr()
err := fmt.Errorf("Error associating floating IP %s with instance.", instanceIp.Ip) if err != nil {
err := fmt.Errorf(
"Error associating floating IP %s with instance.",
instanceIp.IP)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} else {
ui.Say(fmt.Sprintf("Added floating IP %s to instance...", instanceIp.Ip))
}
} }
state.Put("access_ip", instanceIp) ui.Say(fmt.Sprintf(
"Added floating IP %s to instance...", instanceIp.IP))
}
state.Put("access_ip", &instanceIp)
return multistep.ActionContinue return multistep.ActionContinue
} }
func (s *StepAllocateIp) Cleanup(state multistep.StateBag) { func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
csp := state.Get("csp").(gophercloud.CloudServersProvider) instanceIp := state.Get("access_ip").(*floatingip.FloatingIP)
instanceIp := state.Get("access_ip").(gophercloud.FloatingIp)
if s.FloatingIpPool != "" && instanceIp.Id != 0 { // We need the v2 compute client
if err := csp.DeleteFloatingIp(instanceIp); err != nil { client, err := config.computeV2Client()
ui.Error(fmt.Sprintf("Error deleting temporary floating IP %s", instanceIp.Ip)) if err != nil {
ui.Error(fmt.Sprintf(
"Error deleting temporary floating IP %s", instanceIp.IP))
return return
} }
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.Ip))
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.IP))
return
}
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.IP))
} }
} }
...@@ -2,28 +2,36 @@ package openstack ...@@ -2,28 +2,36 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "log"
"time" "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{} type stepCreateImage struct{}
func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction { func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider)
config := state.Get("config").(Config) config := state.Get("config").(Config)
server := state.Get("server").(*gophercloud.Server) server := state.Get("server").(*servers.Server)
ui := state.Get("ui").(packer.Ui) 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 // Create the image
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName)) 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, Name: config.ImageName,
} }).ExtractImageID()
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.Put("error", err) state.Put("error", err)
...@@ -32,12 +40,12 @@ func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction { ...@@ -32,12 +40,12 @@ func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
} }
// Set the Image ID in the state // 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) 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(client, imageId); err != nil {
err := fmt.Errorf("Error waiting for image: %s", err) err := fmt.Errorf("Error waiting for image: %s", err)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
...@@ -52,10 +60,17 @@ func (s *stepCreateImage) Cleanup(multistep.StateBag) { ...@@ -52,10 +60,17 @@ func (s *stepCreateImage) Cleanup(multistep.StateBag) {
} }
// WaitForImage waits for the given Image ID to become ready. // 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 { for {
image, err := csp.ImageById(imageId) image, err := images.Get(client, imageId).Extract()
if err != nil { 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 return err
} }
......
...@@ -2,14 +2,13 @@ package openstack ...@@ -2,14 +2,13 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"log"
"os" "os"
"runtime" "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 { type StepKeyPair struct {
...@@ -19,18 +18,28 @@ type StepKeyPair struct { ...@@ -19,18 +18,28 @@ type StepKeyPair struct {
} }
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { 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) 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...") ui.Say("Creating temporary keypair for this instance...")
keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID()) keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID())
log.Printf("temporary keypair name: %s", keyName) keypair, err := keypairs.Create(computeClient, keypairs.CreateOpts{
keyResp, err := csp.CreateKeyPair(gophercloud.NewKeyPair{Name: keyName}) Name: keyName,
}).Extract()
if err != nil { if err != nil {
state.Put("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
} }
if keyResp.PrivateKey == "" {
if keypair.PrivateKey == "" {
state.Put("error", fmt.Errorf("The temporary keypair returned was blank")) state.Put("error", fmt.Errorf("The temporary keypair returned was blank"))
return multistep.ActionHalt return multistep.ActionHalt
} }
...@@ -47,7 +56,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ...@@ -47,7 +56,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
defer f.Close() defer f.Close()
// Write the key out // 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)) state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt return multistep.ActionHalt
} }
...@@ -66,7 +75,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction { ...@@ -66,7 +75,7 @@ func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
// Set some state data for use in future steps // Set some state data for use in future steps
state.Put("keyPair", keyName) state.Put("keyPair", keyName)
state.Put("privateKey", keyResp.PrivateKey) state.Put("privateKey", keypair.PrivateKey)
return multistep.ActionContinue return multistep.ActionContinue
} }
...@@ -77,11 +86,19 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) { ...@@ -77,11 +86,19 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
return return
} }
csp := state.Get("csp").(gophercloud.CloudServersProvider) config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui) 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...") ui.Say("Deleting temporary keypair...")
err := csp.DeleteKeyPair(s.keyName) err = keypairs.Delete(computeClient, s.keyName).ExtractErr()
if err != nil { if err != nil {
ui.Error(fmt.Sprintf( ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) "Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
......
package openstack
import (
"fmt"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
)
// StepLoadFlavor 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 StepLoadFlavor struct {
Flavor string
}
func (s *StepLoadFlavor) 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(fmt.Sprintf("Loading flavor: %s", s.Flavor))
log.Printf("[INFO] Loading flavor by ID: %s", s.Flavor)
flavor, err := flavors.Get(client, s.Flavor).Extract()
if err != nil {
log.Printf("[ERROR] Failed to find flavor by ID: %s", err)
geterr := err
log.Printf("[INFO] Loading flavor by name: %s", s.Flavor)
id, err := flavors.IDFromName(client, s.Flavor)
if err != nil {
log.Printf("[ERROR] Failed to find flavor by name: %s", err)
err = fmt.Errorf(
"Unable to find specified flavor by ID or name!\n\n"+
"Error from ID lookup: %s\n\n"+
"Error from name lookup: %s",
geterr,
err)
state.Put("error", err)
return multistep.ActionHalt
}
flavor = &flavors.Flavor{ID: id}
}
ui.Message(fmt.Sprintf("Verified flavor. ID: %s", flavor.ID))
state.Put("flavor_id", flavor.ID)
return multistep.ActionContinue
}
func (s *StepLoadFlavor) Cleanup(state multistep.StateBag) {
}
...@@ -2,51 +2,54 @@ package openstack ...@@ -2,51 +2,54 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log" "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 { type StepRunSourceServer struct {
Flavor string
Name string Name string
SourceImage string SourceImage string
SecurityGroups []string SecurityGroups []string
Networks []string Networks []string
server *gophercloud.Server server *servers.Server
} }
func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction { func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction {
csp := state.Get("csp").(gophercloud.CloudServersProvider) config := state.Get("config").(Config)
flavor := state.Get("flavor_id").(string)
keyName := state.Get("keyPair").(string) keyName := state.Get("keyPair").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
// XXX - validate image and flavor is available // We need the v2 compute client
computeClient, err := config.computeV2Client()
securityGroups := make([]map[string]interface{}, len(s.SecurityGroups)) if err != nil {
for i, groupName := range s.SecurityGroups { err = fmt.Errorf("Error initializing compute client: %s", err)
securityGroups[i] = make(map[string]interface{}) state.Put("error", err)
securityGroups[i]["name"] = groupName return multistep.ActionHalt
} }
networks := make([]gophercloud.NetworkConfig, len(s.Networks)) networks := make([]servers.Network, len(s.Networks))
for i, networkUuid := range s.Networks { for i, networkUuid := range s.Networks {
networks[i].Uuid = networkUuid networks[i].UUID = networkUuid
} }
server := gophercloud.NewServer{ ui.Say("Launching server...")
s.server, err = servers.Create(computeClient, keypairs.CreateOptsExt{
CreateOptsBuilder: servers.CreateOpts{
Name: s.Name, Name: s.Name,
ImageRef: s.SourceImage, ImageRef: s.SourceImage,
FlavorRef: s.Flavor, FlavorRef: flavor,
KeyPairName: keyName, SecurityGroups: s.SecurityGroups,
SecurityGroup: securityGroups,
Networks: networks, Networks: networks,
} },
serverResp, err := csp.CreateServer(server) KeyName: keyName,
}).Extract()
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.Put("error", err) state.Put("error", err)
...@@ -54,25 +57,25 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction ...@@ -54,25 +57,25 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionHalt return multistep.ActionHalt
} }
s.server, err = csp.ServerById(serverResp.Id) ui.Message(fmt.Sprintf("Server ID: %s", s.server.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("Waiting for server to become ready...")
stateChange := StateChangeConf{ stateChange := StateChangeConf{
Pending: []string{"BUILD"}, Pending: []string{"BUILD"},
Target: "ACTIVE", Target: "ACTIVE",
Refresh: ServerStateRefreshFunc(csp, s.server), Refresh: ServerStateRefreshFunc(computeClient, s.server),
StepState: state, StepState: state,
} }
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.Put("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.(*servers.Server)
state.Put("server", s.server) state.Put("server", s.server)
return multistep.ActionContinue return multistep.ActionContinue
...@@ -83,18 +86,25 @@ func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) { ...@@ -83,18 +86,25 @@ func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) {
return return
} }
csp := state.Get("csp").(gophercloud.CloudServersProvider) config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui) 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...") 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)) ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
return return
} }
stateChange := StateChangeConf{ stateChange := StateChangeConf{
Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"}, Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"},
Refresh: ServerStateRefreshFunc(csp, s.server), Refresh: ServerStateRefreshFunc(computeClient, s.server),
Target: "DELETED", Target: "DELETED",
} }
......
...@@ -2,11 +2,11 @@ package openstack ...@@ -2,11 +2,11 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time" "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 { type StepWaitForRackConnect struct {
...@@ -18,14 +18,22 @@ func (s *StepWaitForRackConnect) Run(state multistep.StateBag) multistep.StepAct ...@@ -18,14 +18,22 @@ func (s *StepWaitForRackConnect) Run(state multistep.StateBag) multistep.StepAct
return multistep.ActionContinue return multistep.ActionContinue
} }
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) 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 { for {
server, err := csp.ServerById(server.Id) server, err = servers.Get(computeClient, server.ID).Extract()
if err != nil { if err != nil {
return multistep.ActionHalt return multistep.ActionHalt
} }
......
...@@ -29,28 +29,26 @@ each category, the available configuration keys are alphabetized. ...@@ -29,28 +29,26 @@ each category, the available configuration keys are alphabetized.
### Required: ### Required:
* `flavor` (string) - The ID or full URL for the desired flavor for the * `flavor` (string) - The ID, name, or full URL for the desired flavor for the
server to be created. server to be created.
* `image_name` (string) - The name of the resulting image. * `image_name` (string) - The name of the resulting image.
* `password` (string) - The password used to connect to the OpenStack service.
If not specified, Packer will use the environment variables
`SDK_PASSWORD` or `OS_PASSWORD` (in that order), if set.
* `source_image` (string) - The ID or full URL to the base image to use. * `source_image` (string) - The ID or full URL to the base image to use.
This is the image that will be used to launch a new server and provision it. This is the image that will be used to launch a new server and provision it.
* `username` (string) - The username used to connect to the OpenStack service. * `username` (string) - The username used to connect to the OpenStack service.
If not specified, Packer will use the environment variable
`OS_USERNAME`, if set.
* `password` (string) - The password used to connect to the OpenStack service.
If not specified, Packer will use the environment variables If not specified, Packer will use the environment variables
`SDK_USERNAME` or `OS_USERNAME` (in that order), if set. `OS_PASSWORD`, if set.
### Optional: ### Optional:
* `api_key` (string) - The API key used to access OpenStack. Some OpenStack * `api_key` (string) - The API key used to access OpenStack. Some OpenStack
installations require this. installations require this.
If not specified, Packer will use the environment variables
`SDK_API_KEY`, if set.
* `floating_ip` (string) - A specific floating IP to assign to this instance. * `floating_ip` (string) - A specific floating IP to assign to this instance.
`use_floating_ip` must also be set to true for this to have an affect. `use_floating_ip` must also be set to true for this to have an affect.
...@@ -65,32 +63,18 @@ each category, the available configuration keys are alphabetized. ...@@ -65,32 +63,18 @@ each category, the available configuration keys are alphabetized.
* `networks` (array of strings) - A list of networks by UUID to attach * `networks` (array of strings) - A list of networks by UUID to attach
to this instance. to this instance.
* `openstack_provider` (string) - A name of a provider that has a slightly * `tenant_id` or `tenant_name` (string) - The tenant ID or name to boot the
different API model. Currently supported values are "openstack" (default), instance into. Some OpenStack installations require this.
and "rackspace". If not specified, Packer will use the environment variable
`OS_TENANT_NAME`, if set.
* `project` (string) - The project name to boot the instance into. Some
OpenStack installations require this.
If not specified, Packer will use the environment variables
`SDK_PROJECT` or `OS_TENANT_NAME` (in that order), if set.
* `provider` (string) - The provider used to connect to the OpenStack service.
If not specified, Packer will use the environment variables `SDK_PROVIDER`
or `OS_AUTH_URL` (in that order), if set.
For Rackspace this should be `rackspace-us` or `rackspace-uk`.
* `proxy_url` (string)
* `security_groups` (array of strings) - A list of security groups by name * `security_groups` (array of strings) - A list of security groups by name
to add to this instance. to add to this instance.
* `region` (string) - The name of the region, such as "DFW", in which * `region` (string) - The name of the region, such as "DFW", in which
to launch the server to create the AMI. to launch the server to create the AMI.
If not specified, Packer will use the environment variables If not specified, Packer will use the environment variable
`SDK_REGION` or `OS_REGION_NAME` (in that order), if set. `OS_REGION_NAME`, if set.
For a `provider` of "rackspace", it is required to specify a region,
either using this option or with an environment variable. For other
providers, including a private cloud, specifying a region is optional.
* `ssh_port` (integer) - The port that SSH will be available on. Defaults to port * `ssh_port` (integer) - The port that SSH will be available on. Defaults to port
22. 22.
...@@ -106,9 +90,6 @@ each category, the available configuration keys are alphabetized. ...@@ -106,9 +90,6 @@ each category, the available configuration keys are alphabetized.
useful for Rackspace are "public" or "private", and the default behavior is useful for Rackspace are "public" or "private", and the default behavior is
to connect via whichever is returned first from the OpenStack API. to connect via whichever is returned first from the OpenStack API.
* `tenant_id` (string) - Tenant ID for accessing OpenStack if your
installation requires this.
* `use_floating_ip` (boolean) - Whether or not to use a floating IP for * `use_floating_ip` (boolean) - Whether or not to use a floating IP for
the instance. Defaults to false. the instance. Defaults to false.
...@@ -124,10 +105,8 @@ Ubuntu 12.04 LTS (Precise Pangolin) on Rackspace OpenStack cloud offering. ...@@ -124,10 +105,8 @@ Ubuntu 12.04 LTS (Precise Pangolin) on Rackspace OpenStack cloud offering.
```javascript ```javascript
{ {
"type": "openstack", "type": "openstack",
"username": "", "username": "foo",
"api_key": "", "password": "foo",
"openstack_provider": "rackspace",
"provider": "rackspace-us",
"region": "DFW", "region": "DFW",
"ssh_username": "root", "ssh_username": "root",
"image_name": "Test image", "image_name": "Test image",
...@@ -160,13 +139,3 @@ script is setting environment variables like: ...@@ -160,13 +139,3 @@ script is setting environment variables like:
* `OS_TENANT_ID` * `OS_TENANT_ID`
* `OS_USERNAME` * `OS_USERNAME`
* `OS_PASSWORD` * `OS_PASSWORD`
## Troubleshooting
*I get the error "Missing or incorrect provider"*
* Verify your "username", "password" and "provider" settings.
*I get the error "Missing endpoint, or insufficient privileges to access endpoint"*
* Verify your "region" setting.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment