builder/openstack-new
This commit is contained in:
parent
17555ff21a
commit
c903579aaa
|
@ -0,0 +1,109 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/template/interpolate"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessConfig is for common configuration related to openstack access
|
||||||
|
type AccessConfig struct {
|
||||||
|
Username string `mapstructure:"username"`
|
||||||
|
UserID string `mapstructure:"user_id"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
APIKey string `mapstructure:"api_key"`
|
||||||
|
IdentityEndpoint string `mapstructure:"identity_endpoint"`
|
||||||
|
TenantID string `mapstructure:"tenant_id"`
|
||||||
|
TenantName string `mapstructure:"tenant_name"`
|
||||||
|
DomainID string `mapstructure:"domain_id"`
|
||||||
|
DomainName string `mapstructure:"domain_name"`
|
||||||
|
Insecure bool `mapstructure:"insecure"`
|
||||||
|
Region string `mapstructure:"region"`
|
||||||
|
EndpointType string `mapstructure:"endpoint_type"`
|
||||||
|
|
||||||
|
osClient *gophercloud.ProviderClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
|
if c.EndpointType != "internal" && c.EndpointType != "internalURL" &&
|
||||||
|
c.EndpointType != "admin" && c.EndpointType != "adminURL" &&
|
||||||
|
c.EndpointType != "public" && c.EndpointType != "publicURL" &&
|
||||||
|
c.EndpointType != "" {
|
||||||
|
return []error{fmt.Errorf("Invalid endpoint type provided")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Region == "" {
|
||||||
|
c.Region = os.Getenv("OS_REGION_NAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get as much as possible from the end
|
||||||
|
ao, err := openstack.AuthOptionsFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override values if we have them in our config
|
||||||
|
overrides := []struct {
|
||||||
|
From, To *string
|
||||||
|
}{
|
||||||
|
{&c.Username, &ao.Username},
|
||||||
|
{&c.UserID, &ao.UserID},
|
||||||
|
{&c.Password, &ao.Password},
|
||||||
|
{&c.APIKey, &ao.APIKey},
|
||||||
|
{&c.IdentityEndpoint, &ao.IdentityEndpoint},
|
||||||
|
{&c.TenantID, &ao.TenantID},
|
||||||
|
{&c.TenantName, &ao.TenantName},
|
||||||
|
{&c.DomainID, &ao.DomainID},
|
||||||
|
{&c.DomainName, &ao.DomainName},
|
||||||
|
}
|
||||||
|
for _, s := range overrides {
|
||||||
|
if *s.From != "" {
|
||||||
|
*s.To = *s.From
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the client itself
|
||||||
|
client, err := openstack.NewClient(ao.IdentityEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have insecure set, then create a custom HTTP client that
|
||||||
|
// ignores SSL errors.
|
||||||
|
if c.Insecure {
|
||||||
|
config := &tls.Config{InsecureSkipVerify: true}
|
||||||
|
transport := &http.Transport{TLSClientConfig: config}
|
||||||
|
client.HTTPClient.Transport = transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
err = openstack.Authenticate(client, ao)
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.osClient = client
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) {
|
||||||
|
return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{
|
||||||
|
Region: c.Region,
|
||||||
|
Availability: c.getEndpointType(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessConfig) getEndpointType() gophercloud.Availability {
|
||||||
|
if c.EndpointType == "internal" || c.EndpointType == "internalURL" {
|
||||||
|
return gophercloud.AvailabilityInternal
|
||||||
|
}
|
||||||
|
if c.EndpointType == "admin" || c.EndpointType == "adminURL" {
|
||||||
|
return gophercloud.AvailabilityAdmin
|
||||||
|
}
|
||||||
|
return gophercloud.AvailabilityPublic
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Artifact is an artifact implementation that contains built images.
|
||||||
|
type Artifact struct {
|
||||||
|
// ImageId of built image
|
||||||
|
ImageId string
|
||||||
|
|
||||||
|
// BuilderId is the unique ID for the builder that created this image
|
||||||
|
BuilderIdValue string
|
||||||
|
|
||||||
|
// OpenStack connection for performing API stuff.
|
||||||
|
Client *gophercloud.ServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) BuilderId() string {
|
||||||
|
return a.BuilderIdValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Artifact) Files() []string {
|
||||||
|
// We have no files
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Id() string {
|
||||||
|
return a.ImageId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) String() string {
|
||||||
|
return fmt.Sprintf("An image was created: %v", a.ImageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) State(name string) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Destroy() error {
|
||||||
|
log.Printf("Destroying image: %s", a.ImageId)
|
||||||
|
return images.Delete(a.Client, a.ImageId).ExtractErr()
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArtifact_Impl(t *testing.T) {
|
||||||
|
var _ packer.Artifact = new(Artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactId(t *testing.T) {
|
||||||
|
expected := `b8cdf55b-c916-40bd-b190-389ec144c4ed`
|
||||||
|
|
||||||
|
a := &Artifact{
|
||||||
|
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := a.Id()
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("bad: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactString(t *testing.T) {
|
||||||
|
expected := "An image was created: b8cdf55b-c916-40bd-b190-389ec144c4ed"
|
||||||
|
|
||||||
|
a := &Artifact{
|
||||||
|
ImageId: "b8cdf55b-c916-40bd-b190-389ec144c4ed",
|
||||||
|
}
|
||||||
|
result := a.String()
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("bad: %s", result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// The openstack package contains a packer.Builder implementation that
|
||||||
|
// builds Images for openstack.
|
||||||
|
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/helper/config"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/mitchellh/packer/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The unique ID for this builder
|
||||||
|
const BuilderId = "mitchellh.openstack"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
AccessConfig `mapstructure:",squash"`
|
||||||
|
ImageConfig `mapstructure:",squash"`
|
||||||
|
RunConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
|
ctx interpolate.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config Config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||||
|
Interpolate: true,
|
||||||
|
}, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate any errors
|
||||||
|
var errs *packer.MultiError
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.ImageConfig.Prepare(&b.config.ctx)...)
|
||||||
|
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(common.ScrubConfig(b.config, b.config.Password))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
computeClient, err := b.config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error initializing compute client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the state bag and initial state for the steps
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("config", b.config)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
// Build the steps
|
||||||
|
steps := []multistep.Step{
|
||||||
|
&StepKeyPair{
|
||||||
|
Debug: b.config.PackerDebug,
|
||||||
|
DebugKeyPath: fmt.Sprintf("os_%s.pem", b.config.PackerBuildName),
|
||||||
|
},
|
||||||
|
&StepRunSourceServer{
|
||||||
|
Name: b.config.ImageName,
|
||||||
|
Flavor: b.config.Flavor,
|
||||||
|
SourceImage: b.config.SourceImage,
|
||||||
|
SecurityGroups: b.config.SecurityGroups,
|
||||||
|
Networks: b.config.Networks,
|
||||||
|
},
|
||||||
|
&StepWaitForRackConnect{
|
||||||
|
Wait: b.config.RackconnectWait,
|
||||||
|
},
|
||||||
|
&StepAllocateIp{
|
||||||
|
FloatingIpPool: b.config.FloatingIpPool,
|
||||||
|
FloatingIp: b.config.FloatingIp,
|
||||||
|
},
|
||||||
|
&common.StepConnectSSH{
|
||||||
|
SSHAddress: SSHAddress(computeClient, b.config.SSHInterface, b.config.SSHPort),
|
||||||
|
SSHConfig: SSHConfig(b.config.SSHUsername),
|
||||||
|
SSHWaitTimeout: b.config.SSHTimeout(),
|
||||||
|
},
|
||||||
|
&common.StepProvision{},
|
||||||
|
&stepCreateImage{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run!
|
||||||
|
if b.config.PackerDebug {
|
||||||
|
b.runner = &multistep.DebugRunner{
|
||||||
|
Steps: steps,
|
||||||
|
PauseFn: common.MultistepDebugFn(ui),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no images, then just return
|
||||||
|
if _, ok := state.GetOk("image"); !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the artifact and return it
|
||||||
|
artifact := &Artifact{
|
||||||
|
ImageId: state.Get("image").(string),
|
||||||
|
BuilderIdValue: BuilderId,
|
||||||
|
Client: computeClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifact, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"username": "foo",
|
||||||
|
"password": "bar",
|
||||||
|
"provider": "foo",
|
||||||
|
"region": "DFW",
|
||||||
|
"image_name": "foo",
|
||||||
|
"source_image": "foo",
|
||||||
|
"flavor": "foo",
|
||||||
|
"ssh_username": "root",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Builder{}
|
||||||
|
if _, ok := raw.(packer.Builder); !ok {
|
||||||
|
t.Fatalf("Builder should be a builder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
c := map[string]interface{}{
|
||||||
|
"password": []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
warns, err := b.Prepare(c)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("prepare should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ImageName(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Test good
|
||||||
|
config["image_name"] = "foo"
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
config["image_name"] = "foo {{"
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test bad
|
||||||
|
delete(config, "image_name")
|
||||||
|
b = Builder{}
|
||||||
|
warns, err = b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
// Add a random key
|
||||||
|
config["i_should_not_be_valid"] = true
|
||||||
|
warns, err := b.Prepare(config)
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageConfig is for common configuration related to creating Images.
|
||||||
|
type ImageConfig struct {
|
||||||
|
ImageName string `mapstructure:"image_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
if c.ImageName == "" {
|
||||||
|
errs = append(errs, fmt.Errorf("An image_name must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testImageConfig() *ImageConfig {
|
||||||
|
return &ImageConfig{
|
||||||
|
ImageName: "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageConfigPrepare_Region(t *testing.T) {
|
||||||
|
c := testImageConfig()
|
||||||
|
if err := c.Prepare(nil); err != nil {
|
||||||
|
t.Fatalf("shouldn't have err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ImageName = ""
|
||||||
|
if err := c.Prepare(nil); err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/packer/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunConfig contains configuration for running an instance from a source
|
||||||
|
// image and details on how to access that launched image.
|
||||||
|
type RunConfig struct {
|
||||||
|
SourceImage string `mapstructure:"source_image"`
|
||||||
|
Flavor string `mapstructure:"flavor"`
|
||||||
|
RawSSHTimeout string `mapstructure:"ssh_timeout"`
|
||||||
|
SSHUsername string `mapstructure:"ssh_username"`
|
||||||
|
SSHPort int `mapstructure:"ssh_port"`
|
||||||
|
SSHInterface string `mapstructure:"ssh_interface"`
|
||||||
|
OpenstackProvider string `mapstructure:"openstack_provider"`
|
||||||
|
UseFloatingIp bool `mapstructure:"use_floating_ip"`
|
||||||
|
RackconnectWait bool `mapstructure:"rackconnect_wait"`
|
||||||
|
FloatingIpPool string `mapstructure:"floating_ip_pool"`
|
||||||
|
FloatingIp string `mapstructure:"floating_ip"`
|
||||||
|
SecurityGroups []string `mapstructure:"security_groups"`
|
||||||
|
Networks []string `mapstructure:"networks"`
|
||||||
|
|
||||||
|
// Unexported fields that are calculated from others
|
||||||
|
sshTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
|
// Defaults
|
||||||
|
if c.SSHUsername == "" {
|
||||||
|
c.SSHUsername = "root"
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPort == 0 {
|
||||||
|
c.SSHPort = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.RawSSHTimeout == "" {
|
||||||
|
c.RawSSHTimeout = "5m"
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UseFloatingIp && c.FloatingIpPool == "" {
|
||||||
|
c.FloatingIpPool = "public"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
var err error
|
||||||
|
errs := make([]error, 0)
|
||||||
|
if c.SourceImage == "" {
|
||||||
|
errs = append(errs, errors.New("A source_image must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Flavor == "" {
|
||||||
|
errs = append(errs, errors.New("A flavor must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHUsername == "" {
|
||||||
|
errs = append(errs, errors.New("An ssh_username must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.sshTimeout, err = time.ParseDuration(c.RawSSHTimeout)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RunConfig) SSHTimeout() time.Duration {
|
||||||
|
return c.sshTimeout
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Clear out the openstack env vars so they don't
|
||||||
|
// affect our tests.
|
||||||
|
os.Setenv("SDK_USERNAME", "")
|
||||||
|
os.Setenv("SDK_PASSWORD", "")
|
||||||
|
os.Setenv("SDK_PROVIDER", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRunConfig() *RunConfig {
|
||||||
|
return &RunConfig{
|
||||||
|
SourceImage: "abcd",
|
||||||
|
Flavor: "m1.small",
|
||||||
|
SSHUsername: "root",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare(t *testing.T) {
|
||||||
|
c := testRunConfig()
|
||||||
|
err := c.Prepare(nil)
|
||||||
|
if len(err) > 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_InstanceType(t *testing.T) {
|
||||||
|
c := testRunConfig()
|
||||||
|
c.Flavor = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SourceImage(t *testing.T) {
|
||||||
|
c := testRunConfig()
|
||||||
|
c.SourceImage = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SSHPort(t *testing.T) {
|
||||||
|
c := testRunConfig()
|
||||||
|
c.SSHPort = 0
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPort != 22 {
|
||||||
|
t.Fatalf("invalid value: %d", c.SSHPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SSHPort = 44
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPort != 44 {
|
||||||
|
t.Fatalf("invalid value: %d", c.SSHPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SSHTimeout(t *testing.T) {
|
||||||
|
c := testRunConfig()
|
||||||
|
c.RawSSHTimeout = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RawSSHTimeout = "bad"
|
||||||
|
if err := c.Prepare(nil); len(err) != 1 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunConfigPrepare_SSHUsername(t *testing.T) {
|
||||||
|
c := testRunConfig()
|
||||||
|
c.SSHUsername = ""
|
||||||
|
if err := c.Prepare(nil); len(err) != 0 {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateRefreshFunc is a function type used for StateChangeConf that is
|
||||||
|
// responsible for refreshing the item being watched for a state change.
|
||||||
|
//
|
||||||
|
// It returns three results. `result` is any object that will be returned
|
||||||
|
// as the final object after waiting for state change. This allows you to
|
||||||
|
// return the final updated object, for example an openstack instance after
|
||||||
|
// refreshing it.
|
||||||
|
//
|
||||||
|
// `state` is the latest state of that object. And `err` is any error that
|
||||||
|
// may have happened while refreshing the state.
|
||||||
|
type StateRefreshFunc func() (result interface{}, state string, progress int, err error)
|
||||||
|
|
||||||
|
// StateChangeConf is the configuration struct used for `WaitForState`.
|
||||||
|
type StateChangeConf struct {
|
||||||
|
Pending []string
|
||||||
|
Refresh StateRefreshFunc
|
||||||
|
StepState multistep.StateBag
|
||||||
|
Target string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerStateRefreshFunc returns a StateRefreshFunc that is used to watch
|
||||||
|
// an openstack server.
|
||||||
|
func ServerStateRefreshFunc(
|
||||||
|
client *gophercloud.ServiceClient, s *servers.Server) StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, int, error) {
|
||||||
|
var serverNew *servers.Server
|
||||||
|
result := servers.Get(client, s.ID)
|
||||||
|
err := result.Err
|
||||||
|
if err == nil {
|
||||||
|
serverNew, err = result.Extract()
|
||||||
|
}
|
||||||
|
if result.Err != nil {
|
||||||
|
errCode, ok := result.Err.(*gophercloud.UnexpectedResponseCodeError)
|
||||||
|
if ok && errCode.Actual == 404 {
|
||||||
|
log.Printf("[INFO] 404 on ServerStateRefresh, returning DELETED")
|
||||||
|
return nil, "DELETED", 0, nil
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] Error on ServerStateRefresh: %s", result.Err)
|
||||||
|
return nil, "", 0, result.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverNew, serverNew.Status, serverNew.Progress, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForState watches an object and waits for it to achieve a certain
|
||||||
|
// state.
|
||||||
|
func WaitForState(conf *StateChangeConf) (i interface{}, err error) {
|
||||||
|
log.Printf("Waiting for state to become: %s", conf.Target)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var currentProgress int
|
||||||
|
var currentState string
|
||||||
|
i, currentState, currentProgress, err = conf.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentState == conf.Target {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.StepState != nil {
|
||||||
|
if _, ok := conf.StepState.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return nil, errors.New("interrupted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, allowed := range conf.Pending {
|
||||||
|
if currentState == allowed {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("unexpected state '%s', wanted target '%s'", currentState, conf.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Waiting for state to become: %s currently %s (%d%%)", conf.Target, currentState, currentProgress)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHAddress returns a function that can be given to the SSH communicator
|
||||||
|
// for determining the SSH address based on the server AccessIPv4 setting..
|
||||||
|
func SSHAddress(
|
||||||
|
client *gophercloud.ServiceClient,
|
||||||
|
sshinterface string, port int) func(multistep.StateBag) (string, error) {
|
||||||
|
return func(state multistep.StateBag) (string, error) {
|
||||||
|
s := state.Get("server").(*servers.Server)
|
||||||
|
|
||||||
|
// If we have a floating IP, use that
|
||||||
|
if ip := state.Get("access_ip").(*floatingip.FloatingIP); ip.FixedIP != "" {
|
||||||
|
return fmt.Sprintf("%s:%d", ip.FixedIP, port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AccessIPv4 != "" {
|
||||||
|
return fmt.Sprintf("%s:%d", s.AccessIPv4, port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the addresses associated with this server
|
||||||
|
/*
|
||||||
|
ip_pools, err := s.AllAddressPools()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("Error parsing SSH addresses")
|
||||||
|
}
|
||||||
|
for pool, addresses := range ip_pools {
|
||||||
|
if sshinterface != "" {
|
||||||
|
if pool != sshinterface {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pool != "" {
|
||||||
|
for _, address := range addresses {
|
||||||
|
if address.Addr != "" && address.Version == 4 {
|
||||||
|
return fmt.Sprintf("%s:%d", address.Addr, port), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
result := servers.Get(client, s.ID)
|
||||||
|
err := result.Err
|
||||||
|
if err == nil {
|
||||||
|
s, err = result.Extract()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put("server", s)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
return "", errors.New("couldn't determine IP address for server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHConfig returns a function that can be used for the SSH communicator
|
||||||
|
// config for connecting to the instance created over SSH using the generated
|
||||||
|
// private key.
|
||||||
|
func SSHConfig(username string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||||
|
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||||
|
privateKey := state.Get("privateKey").(string)
|
||||||
|
|
||||||
|
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ssh.ClientConfig{
|
||||||
|
User: username,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.PublicKeys(signer),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepAllocateIp struct {
|
||||||
|
FloatingIpPool string
|
||||||
|
FloatingIp string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
server := state.Get("server").(*servers.Server)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
client, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
var instanceIp *floatingip.FloatingIP
|
||||||
|
// This is here in case we error out before putting instanceIp into the
|
||||||
|
// statebag below, because it is requested by Cleanup()
|
||||||
|
state.Put("access_ip", instanceIp)
|
||||||
|
|
||||||
|
if s.FloatingIp != "" {
|
||||||
|
instanceIp.FixedIP = s.FloatingIp
|
||||||
|
} else if s.FloatingIpPool != "" {
|
||||||
|
newIp, err := floatingip.Create(client, floatingip.CreateOpts{
|
||||||
|
Pool: s.FloatingIpPool,
|
||||||
|
}).Extract()
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceIp = newIp
|
||||||
|
ui.Say(fmt.Sprintf("Created temporary floating IP %s...", instanceIp.FixedIP))
|
||||||
|
}
|
||||||
|
|
||||||
|
if instanceIp.FixedIP != "" {
|
||||||
|
err := floatingip.Associate(client, server.ID, instanceIp.FixedIP).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"Error associating floating IP %s with instance.",
|
||||||
|
instanceIp.FixedIP)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf(
|
||||||
|
"Added floating IP %s to instance...", instanceIp.FixedIP))
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put("access_ip", instanceIp)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
instanceIp := state.Get("access_ip").(*floatingip.FloatingIP)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
client, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error deleting temporary floating IP %s", instanceIp.FixedIP))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.FloatingIpPool != "" && instanceIp.ID != "" {
|
||||||
|
if err := floatingip.Delete(client, instanceIp.ID).ExtractErr(); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error deleting temporary floating IP %s", instanceIp.FixedIP))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.FixedIP))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/rackspace/gophercloud"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepCreateImage struct{}
|
||||||
|
|
||||||
|
func (s *stepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
server := state.Get("server").(*servers.Server)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
client, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the image
|
||||||
|
ui.Say(fmt.Sprintf("Creating the image: %s", config.ImageName))
|
||||||
|
imageId, err := servers.CreateImage(client, server.ID, servers.CreateImageOpts{
|
||||||
|
Name: config.ImageName,
|
||||||
|
}).ExtractImageID()
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating image: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Image ID in the state
|
||||||
|
ui.Say(fmt.Sprintf("Image: %s", imageId))
|
||||||
|
state.Put("image", imageId)
|
||||||
|
|
||||||
|
// Wait for the image to become ready
|
||||||
|
ui.Say("Waiting for image to become ready...")
|
||||||
|
if err := WaitForImage(client, imageId); err != nil {
|
||||||
|
err := fmt.Errorf("Error waiting for image: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateImage) Cleanup(multistep.StateBag) {
|
||||||
|
// No cleanup...
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForImage waits for the given Image ID to become ready.
|
||||||
|
func WaitForImage(client *gophercloud.ServiceClient, imageId string) error {
|
||||||
|
for {
|
||||||
|
var image *images.Image
|
||||||
|
result := images.Get(client, imageId)
|
||||||
|
err := result.Err
|
||||||
|
if err == nil {
|
||||||
|
image, err = result.Extract()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if image.Status == "ACTIVE" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/common/uuid"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepKeyPair struct {
|
||||||
|
Debug bool
|
||||||
|
DebugKeyPath string
|
||||||
|
keyName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepKeyPair) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
computeClient, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating temporary keypair for this instance...")
|
||||||
|
keyName := fmt.Sprintf("packer %s", uuid.TimeOrderedUUID())
|
||||||
|
keypair, err := keypairs.Create(computeClient, keypairs.CreateOpts{
|
||||||
|
Name: keyName,
|
||||||
|
}).Extract()
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
if keypair.PrivateKey == "" {
|
||||||
|
state.Put("error", fmt.Errorf("The temporary keypair returned was blank"))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're in debug mode, output the private key to the working
|
||||||
|
// directory.
|
||||||
|
if s.Debug {
|
||||||
|
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||||
|
f, err := os.Create(s.DebugKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Write the key out
|
||||||
|
if _, err := f.Write([]byte(keypair.PrivateKey)); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod it so that it is SSH ready
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if err := f.Chmod(0600); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the keyname so we know to delete it later
|
||||||
|
s.keyName = keyName
|
||||||
|
|
||||||
|
// Set some state data for use in future steps
|
||||||
|
state.Put("keyPair", keyName)
|
||||||
|
state.Put("privateKey", keypair.PrivateKey)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||||
|
// If no key name is set, then we never created it, so just return
|
||||||
|
if s.keyName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
computeClient, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Deleting temporary keypair...")
|
||||||
|
err = keypairs.Delete(computeClient, s.keyName).ExtractErr()
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepRunSourceServer struct {
|
||||||
|
Flavor string
|
||||||
|
Name string
|
||||||
|
SourceImage string
|
||||||
|
SecurityGroups []string
|
||||||
|
Networks []string
|
||||||
|
|
||||||
|
server *servers.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
keyName := state.Get("keyPair").(string)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
computeClient, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
networks := make([]servers.Network, len(s.Networks))
|
||||||
|
for i, networkUuid := range s.Networks {
|
||||||
|
networks[i].UUID = networkUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
s.server, err = servers.Create(computeClient, keypairs.CreateOptsExt{
|
||||||
|
CreateOptsBuilder: servers.CreateOpts{
|
||||||
|
Name: s.Name,
|
||||||
|
ImageRef: s.SourceImage,
|
||||||
|
FlavorRef: s.Flavor,
|
||||||
|
SecurityGroups: s.SecurityGroups,
|
||||||
|
Networks: networks,
|
||||||
|
},
|
||||||
|
|
||||||
|
KeyName: keyName,
|
||||||
|
}).Extract()
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error launching source server: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("server id: %s", s.server.ID)
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Waiting for server (%s) to become ready...", s.server.ID))
|
||||||
|
stateChange := StateChangeConf{
|
||||||
|
Pending: []string{"BUILD"},
|
||||||
|
Target: "ACTIVE",
|
||||||
|
Refresh: ServerStateRefreshFunc(computeClient, s.server),
|
||||||
|
StepState: state,
|
||||||
|
}
|
||||||
|
latestServer, err := WaitForState(&stateChange)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error waiting for server (%s) to become ready: %s", s.server.ID, err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.server = latestServer.(*servers.Server)
|
||||||
|
state.Put("server", s.server)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepRunSourceServer) Cleanup(state multistep.StateBag) {
|
||||||
|
if s.server == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
computeClient, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Terminating the source server...")
|
||||||
|
if err := servers.Delete(computeClient, s.server.ID).ExtractErr(); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error terminating server, may still be around: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stateChange := StateChangeConf{
|
||||||
|
Pending: []string{"ACTIVE", "BUILD", "REBUILD", "SUSPENDED"},
|
||||||
|
Refresh: ServerStateRefreshFunc(computeClient, s.server),
|
||||||
|
Target: "DELETED",
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForState(&stateChange)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package openstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StepWaitForRackConnect struct {
|
||||||
|
Wait bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWaitForRackConnect) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
if !s.Wait {
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
server := state.Get("server").(*servers.Server)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// We need the v2 compute client
|
||||||
|
computeClient, err := config.computeV2Client()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf(
|
||||||
|
"Waiting for server (%s) to become RackConnect ready...", server.ID))
|
||||||
|
for {
|
||||||
|
server, err = servers.Get(computeClient, server.ID).Extract()
|
||||||
|
if err != nil {
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Metadata["rackconnect_automation_status"] == "DEPLOYED" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWaitForRackConnect) Cleanup(state multistep.StateBag) {
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/builder/openstack-new"
|
||||||
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server, err := plugin.Server()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
server.RegisterBuilder(new(openstack.Builder))
|
||||||
|
server.Serve()
|
||||||
|
}
|
Loading…
Reference in New Issue