Merge pull request #2218 from mitchellh/f-openstack
OpenStack uses official OpenStack client
This commit is contained in:
commit
1428e6dfbe
|
@ -4,99 +4,120 @@ 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")
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// Get as much as possible from the end
|
||||
ao, _ := openstack.AuthOptionsFromEnv()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,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) {
|
||||
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)
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&StepLoadFlavor{
|
||||
Flavor: b.config.Flavor,
|
||||
},
|
||||
&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,
|
||||
|
@ -101,7 +88,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 +122,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
|
||||
|
|
|
@ -9,7 +9,6 @@ func testConfig() map[string]interface{} {
|
|||
return map[string]interface{}{
|
||||
"username": "foo",
|
||||
"password": "bar",
|
||||
"provider": "foo",
|
||||
"region": "DFW",
|
||||
"image_name": "foo",
|
||||
"source_image": "foo",
|
||||
|
@ -40,55 +39,3 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
|
|||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,19 +11,21 @@ import (
|
|||
// 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"`
|
||||
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"`
|
||||
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"`
|
||||
|
||||
// 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
|
||||
sshTimeout time.Duration
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.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")
|
||||
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")
|
||||
|
|
|
@ -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,79 @@ 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)
|
||||
|
||||
// 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
|
||||
// statebag below, because it is requested by Cleanup()
|
||||
state.Put("access_ip", instanceIp)
|
||||
state.Put("access_ip", &instanceIp)
|
||||
|
||||
if s.FloatingIp != "" {
|
||||
instanceIp.Ip = s.FloatingIp
|
||||
instanceIp.IP = 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.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)
|
||||
if instanceIp.IP != "" {
|
||||
err := floatingip.Associate(client, server.ID, instanceIp.IP).ExtractErr()
|
||||
if 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))
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf(
|
||||
"Added floating IP %s to instance...", instanceIp.IP))
|
||||
}
|
||||
|
||||
state.Put("access_ip", instanceIp)
|
||||
|
||||
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.IP))
|
||||
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.IP))
|
||||
return
|
||||
}
|
||||
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.Ip))
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.IP))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
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
|
|||
|
||||
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 {
|
||||
Flavor string
|
||||
Name string
|
||||
SourceImage string
|
||||
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)
|
||||
flavor := state.Get("flavor_id").(string)
|
||||
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,
|
||||
}
|
||||
ui.Say("Launching server...")
|
||||
s.server, err = servers.Create(computeClient, keypairs.CreateOptsExt{
|
||||
CreateOptsBuilder: servers.CreateOpts{
|
||||
Name: s.Name,
|
||||
ImageRef: s.SourceImage,
|
||||
FlavorRef: 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 +57,25 @@ 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)
|
||||
ui.Message(fmt.Sprintf("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{
|
||||
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 +86,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",
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -29,28 +29,26 @@ each category, the available configuration keys are alphabetized.
|
|||
|
||||
### 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.
|
||||
|
||||
* `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.
|
||||
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.
|
||||
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
|
||||
`SDK_USERNAME` or `OS_USERNAME` (in that order), if set.
|
||||
`OS_PASSWORD`, if set.
|
||||
|
||||
### Optional:
|
||||
|
||||
* `api_key` (string) - The API key used to access OpenStack. Some OpenStack
|
||||
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.
|
||||
`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.
|
|||
* `networks` (array of strings) - A list of networks by UUID to attach
|
||||
to this instance.
|
||||
|
||||
* `openstack_provider` (string) - A name of a provider that has a slightly
|
||||
different API model. Currently supported values are "openstack" (default),
|
||||
and "rackspace".
|
||||
|
||||
* `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)
|
||||
* `tenant_id` or `tenant_name` (string) - The tenant ID or name to boot the
|
||||
instance into. Some OpenStack installations require this.
|
||||
If not specified, Packer will use the environment variable
|
||||
`OS_TENANT_NAME`, if set.
|
||||
|
||||
* `security_groups` (array of strings) - A list of security groups by name
|
||||
to add to this instance.
|
||||
|
||||
* `region` (string) - The name of the region, such as "DFW", in which
|
||||
to launch the server to create the AMI.
|
||||
If not specified, Packer will use the environment variables
|
||||
`SDK_REGION` or `OS_REGION_NAME` (in that order), 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.
|
||||
If not specified, Packer will use the environment variable
|
||||
`OS_REGION_NAME`, if set.
|
||||
|
||||
* `ssh_port` (integer) - The port that SSH will be available on. Defaults to port
|
||||
22.
|
||||
|
@ -106,9 +90,6 @@ each category, the available configuration keys are alphabetized.
|
|||
useful for Rackspace are "public" or "private", and the default behavior is
|
||||
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
|
||||
the instance. Defaults to false.
|
||||
|
||||
|
@ -124,10 +105,8 @@ Ubuntu 12.04 LTS (Precise Pangolin) on Rackspace OpenStack cloud offering.
|
|||
```javascript
|
||||
{
|
||||
"type": "openstack",
|
||||
"username": "",
|
||||
"api_key": "",
|
||||
"openstack_provider": "rackspace",
|
||||
"provider": "rackspace-us",
|
||||
"username": "foo",
|
||||
"password": "foo",
|
||||
"region": "DFW",
|
||||
"ssh_username": "root",
|
||||
"image_name": "Test image",
|
||||
|
@ -160,13 +139,3 @@ script is setting environment variables like:
|
|||
* `OS_TENANT_ID`
|
||||
* `OS_USERNAME`
|
||||
* `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.
|
||||
|
|
Loading…
Reference in New Issue