mirror of https://github.com/hashicorp/packer
This is a builder for Joyent's Triton system. It was originally at jen20/packer-builder-triton, and subsequently at joyent/packer-builder-triton on GitHub. The following commit vendors the dependencies.pull/4325/head
parent
f6fe8e8755
commit
e15be036d7
@ -0,0 +1,97 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joyent/gocommon/client"
|
||||
"github.com/joyent/gosdc/cloudapi"
|
||||
"github.com/joyent/gosign/auth"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to Triton access
|
||||
type AccessConfig struct {
|
||||
Endpoint string `mapstructure:"triton_url"`
|
||||
Account string `mapstructure:"triton_account"`
|
||||
KeyID string `mapstructure:"triton_key_id"`
|
||||
KeyMaterial string `mapstructure:"triton_key_material"`
|
||||
}
|
||||
|
||||
// Prepare performs basic validation on the AccessConfig
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.Endpoint == "" {
|
||||
// Use Joyent public cloud as the default endpoint if none is in environment
|
||||
c.Endpoint = "https://us-east-1.api.joyent.com"
|
||||
}
|
||||
|
||||
if c.Account == "" {
|
||||
errs = append(errs, fmt.Errorf("triton_account is required to use the triton builder"))
|
||||
}
|
||||
|
||||
if c.KeyID == "" {
|
||||
errs = append(errs, fmt.Errorf("triton_key_id is required to use the triton builder"))
|
||||
}
|
||||
|
||||
if c.KeyMaterial == "" {
|
||||
errs = append(errs, fmt.Errorf("triton_key_material is required to use the triton builder"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTritonClient returns an SDC client configured with the appropriate client credentials
|
||||
// or an error if creating the client fails.
|
||||
func (c *AccessConfig) CreateTritonClient() (*cloudapi.Client, error) {
|
||||
keyData, err := processKeyMaterial(c.KeyMaterial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userauth, err := auth.NewAuth(c.Account, string(keyData), "rsa-sha256")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
creds := &auth.Credentials{
|
||||
UserAuthentication: userauth,
|
||||
SdcKeyId: c.KeyID,
|
||||
SdcEndpoint: auth.Endpoint{URL: c.Endpoint},
|
||||
}
|
||||
|
||||
return cloudapi.New(client.NewClient(
|
||||
c.Endpoint,
|
||||
cloudapi.DefaultAPIVersion,
|
||||
creds,
|
||||
log.New(os.Stdout, "", log.Flags()),
|
||||
)), nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Comm() communicator.Config {
|
||||
return communicator.Config{}
|
||||
}
|
||||
|
||||
func processKeyMaterial(keyMaterial string) (string, error) {
|
||||
// Check for keyMaterial being a file path
|
||||
if _, err := os.Stat(keyMaterial); err != nil {
|
||||
// Not a valid file. Assume that keyMaterial is the key data
|
||||
return keyMaterial, nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(keyMaterial)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error reading key_material from path '%s': %s",
|
||||
keyMaterial, err)
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccessConfig_Prepare(t *testing.T) {
|
||||
ac := testAccessConfig(t)
|
||||
errs := ac.Prepare(nil)
|
||||
if errs != nil {
|
||||
t.Fatal("should not error")
|
||||
}
|
||||
|
||||
ac = testAccessConfig(t)
|
||||
ac.Account = ""
|
||||
errs = ac.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
ac = testAccessConfig(t)
|
||||
ac.KeyID = ""
|
||||
errs = ac.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
ac = testAccessConfig(t)
|
||||
ac.KeyMaterial = ""
|
||||
errs = ac.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func testAccessConfig(t *testing.T) AccessConfig {
|
||||
return AccessConfig{
|
||||
Endpoint: "test-endpoint",
|
||||
Account: "test-account",
|
||||
KeyID: "test-id",
|
||||
KeyMaterial: "test-private-key",
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built Triton images.
|
||||
type Artifact struct {
|
||||
// ImageID is the image ID of the artifact
|
||||
ImageID string
|
||||
|
||||
// BuilderIDValue is the unique ID for the builder that created this Image
|
||||
BuilderIDValue string
|
||||
|
||||
// SDC connection for cleanup etc
|
||||
Driver Driver
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.BuilderIDValue
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return a.ImageID
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("Image was created: %s", a.ImageID)
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
//TODO(jen20): Figure out how to make this work with Atlas
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Deleting image ID (%s)", a.ImageID)
|
||||
err := a.Driver.DeleteImage(a.ImageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/helper/config"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
const (
|
||||
BuilderId = "joyent.triton"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
errs := &multierror.Error{}
|
||||
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
if b.config.Comm.SSHUsername == "" {
|
||||
b.config.Comm.SSHUsername = "root"
|
||||
}
|
||||
|
||||
errs = multierror.Append(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = multierror.Append(errs, b.config.SourceMachineConfig.Prepare(&b.config.ctx)...)
|
||||
errs = multierror.Append(errs, b.config.Comm.Prepare(&b.config.ctx)...)
|
||||
errs = multierror.Append(errs, b.config.TargetImageConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if b.config.Comm.SSHPrivateKey == "" {
|
||||
b.config.Comm.SSHPrivateKey = b.config.KeyMaterial
|
||||
}
|
||||
return nil, errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
config := b.config
|
||||
|
||||
driver, err := NewDriverTriton(ui, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("driver", driver)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
steps := []multistep.Step{
|
||||
&StepCreateSourceMachine{},
|
||||
&communicator.StepConnect{
|
||||
Config: &config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: sshConfig,
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&StepStopMachine{},
|
||||
&StepCreateImageFromMachine{},
|
||||
&StepDeleteMachine{},
|
||||
}
|
||||
|
||||
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 is no image, just return
|
||||
if _, ok := state.GetOk("image"); !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
ImageID: state.Get("image").(string),
|
||||
BuilderIDValue: BuilderId,
|
||||
Driver: driver,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
// Cancel cancels a possibly running Builder. This should block until
|
||||
// the builder actually cancels and cleans up after itself.
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
AccessConfig `mapstructure:",squash"`
|
||||
SourceMachineConfig `mapstructure:",squash"`
|
||||
TargetImageConfig `mapstructure:",squash"`
|
||||
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig(t *testing.T) Config {
|
||||
return Config{
|
||||
AccessConfig: testAccessConfig(t),
|
||||
SourceMachineConfig: testSourceMachineConfig(t),
|
||||
TargetImageConfig: testTargetImageConfig(t),
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Driver interface {
|
||||
CreateImageFromMachine(machineId string, config Config) (string, error)
|
||||
CreateMachine(config Config) (string, error)
|
||||
DeleteImage(imageId string) error
|
||||
DeleteMachine(machineId string) error
|
||||
GetMachine(machineId string) (string, error)
|
||||
StopMachine(machineId string) error
|
||||
WaitForImageCreation(imageId string, timeout time.Duration) error
|
||||
WaitForMachineDeletion(machineId string, timeout time.Duration) error
|
||||
WaitForMachineState(machineId string, state string, timeout time.Duration) error
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type DriverMock struct {
|
||||
CreateImageFromMachineId string
|
||||
CreateImageFromMachineErr error
|
||||
|
||||
CreateMachineId string
|
||||
CreateMachineErr error
|
||||
|
||||
DeleteImageId string
|
||||
DeleteImageErr error
|
||||
|
||||
DeleteMachineId string
|
||||
DeleteMachineErr error
|
||||
|
||||
GetMachineErr error
|
||||
|
||||
StopMachineId string
|
||||
StopMachineErr error
|
||||
|
||||
WaitForImageCreationErr error
|
||||
|
||||
WaitForMachineDeletionErr error
|
||||
|
||||
WaitForMachineStateErr error
|
||||
}
|
||||
|
||||
func (d *DriverMock) CreateImageFromMachine(machineId string, config Config) (string, error) {
|
||||
if d.CreateImageFromMachineErr != nil {
|
||||
return "", d.CreateImageFromMachineErr
|
||||
}
|
||||
|
||||
d.CreateImageFromMachineId = config.ImageName
|
||||
|
||||
return d.CreateImageFromMachineId, nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) CreateMachine(config Config) (string, error) {
|
||||
if d.CreateMachineErr != nil {
|
||||
return "", d.CreateMachineErr
|
||||
}
|
||||
|
||||
d.CreateMachineId = config.MachineName
|
||||
|
||||
return d.CreateMachineId, nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) DeleteImage(imageId string) error {
|
||||
if d.DeleteImageErr != nil {
|
||||
return d.DeleteImageErr
|
||||
}
|
||||
|
||||
d.DeleteImageId = imageId
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) DeleteMachine(machineId string) error {
|
||||
if d.DeleteMachineErr != nil {
|
||||
return d.DeleteMachineErr
|
||||
}
|
||||
|
||||
d.DeleteMachineId = machineId
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) GetMachine(machineId string) (string, error) {
|
||||
if d.GetMachineErr != nil {
|
||||
return "", d.GetMachineErr
|
||||
}
|
||||
|
||||
return "ip", nil
|
||||
}
|
||||
|
||||
func (d *DriverMock) StopMachine(machineId string) error {
|
||||
d.StopMachineId = machineId
|
||||
|
||||
return d.StopMachineErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) WaitForImageCreation(machineId string, timeout time.Duration) error {
|
||||
return d.WaitForImageCreationErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) WaitForMachineDeletion(machineId string, timeout time.Duration) error {
|
||||
return d.WaitForMachineDeletionErr
|
||||
}
|
||||
|
||||
func (d *DriverMock) WaitForMachineState(machineId string, state string, timeout time.Duration) error {
|
||||
return d.WaitForMachineStateErr
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/joyent/gosdc/cloudapi"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type driverTriton struct {
|
||||
client *cloudapi.Client
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
func NewDriverTriton(ui packer.Ui, config Config) (Driver, error) {
|
||||
client, err := config.AccessConfig.CreateTritonClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &driverTriton{
|
||||
client: client,
|
||||
ui: ui,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (string, error) {
|
||||
opts := cloudapi.CreateImageFromMachineOpts{
|
||||
Machine: machineId,
|
||||
Name: config.ImageName,
|
||||
Version: config.ImageVersion,
|
||||
Description: config.ImageDescription,
|
||||
Homepage: config.ImageHomepage,
|
||||
EULA: config.ImageEULA,
|
||||
ACL: config.ImageACL,
|
||||
Tags: config.ImageTags,
|
||||
}
|
||||
|
||||
image, err := d.client.CreateImageFromMachine(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return image.Id, err
|
||||
}
|
||||
|
||||
func (d *driverTriton) CreateMachine(config Config) (string, error) {
|
||||
opts := cloudapi.CreateMachineOpts{
|
||||
Package: config.MachinePackage,
|
||||
Image: config.MachineImage,
|
||||
Networks: config.MachineNetworks,
|
||||
Metadata: config.MachineMetadata,
|
||||
Tags: config.MachineTags,
|
||||
FirewallEnabled: config.MachineFirewallEnabled,
|
||||
}
|
||||
|
||||
if config.MachineName != "" {
|
||||
opts.Name = config.MachineName
|
||||
}
|
||||
|
||||
machine, err := d.client.CreateMachine(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return machine.Id, nil
|
||||
}
|
||||
|
||||
func (d *driverTriton) DeleteImage(imageId string) error {
|
||||
return d.client.DeleteImage(imageId)
|
||||
}
|
||||
|
||||
func (d *driverTriton) DeleteMachine(machineId string) error {
|
||||
return d.client.DeleteMachine(machineId)
|
||||
}
|
||||
|
||||
func (d *driverTriton) GetMachine(machineId string) (string, error) {
|
||||
machine, err := d.client.GetMachine(machineId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return machine.PrimaryIP, nil
|
||||
}
|
||||
|
||||
func (d *driverTriton) StopMachine(machineId string) error {
|
||||
return d.client.StopMachine(machineId)
|
||||
}
|
||||
|
||||
// waitForMachineState uses the supplied client to wait for the state of
|
||||
// the machine with the given ID to reach the state described in state.
|
||||
// If timeout is reached before the machine reaches the required state, an
|
||||
// error is returned. If the machine reaches the target state within the
|
||||
// timeout, nil is returned.
|
||||
func (d *driverTriton) WaitForMachineState(machineId string, state string, timeout time.Duration) error {
|
||||
return waitFor(
|
||||
func() (bool, error) {
|
||||
machine, err := d.client.GetMachine(machineId)
|
||||
if machine == nil {
|
||||
return false, err
|
||||
}
|
||||
return machine.State == state, err
|
||||
},
|
||||
3*time.Second,
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
|
||||
// waitForMachineDeletion uses the supplied client to wait for the machine
|
||||
// with the given ID to be deleted. It is expected that the API call to delete
|
||||
// the machine has already been issued at this point.
|
||||
func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Duration) error {
|
||||
return waitFor(
|
||||
func() (bool, error) {
|
||||
machine, err := d.client.GetMachine(machineId)
|
||||
if machine != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
//TODO(jen20): is there a better way here than searching strings?
|
||||
if strings.Contains(err.Error(), "410") || strings.Contains(err.Error(), "404") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
},
|
||||
3*time.Second,
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
|
||||
func (d *driverTriton) WaitForImageCreation(imageId string, timeout time.Duration) error {
|
||||
return waitFor(
|
||||
func() (bool, error) {
|
||||
image, err := d.client.GetImage(imageId)
|
||||
if image == nil {
|
||||
return false, err
|
||||
}
|
||||
return image.OS != "", err
|
||||
},
|
||||
3*time.Second,
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
|
||||
func waitFor(f func() (bool, error), every, timeout time.Duration) error {
|
||||
start := time.Now()
|
||||
|
||||
for time.Since(start) <= timeout {
|
||||
stop, err := f()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stop {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(every)
|
||||
}
|
||||
|
||||
return errors.New("Timed out while waiting for resource change")
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// SourceMachineConfig represents the configuration to run a machine using
|
||||
// the SDC API in order for provisioning to take place.
|
||||
type SourceMachineConfig struct {
|
||||
MachineName string `mapstructure:"source_machine_name"`
|
||||
MachinePackage string `mapstructure:"source_machine_package"`
|
||||
MachineImage string `mapstructure:"source_machine_image"`
|
||||
MachineNetworks []string `mapstructure:"source_machine_networks"`
|
||||
MachineMetadata map[string]string `mapstructure:"source_machine_metadata"`
|
||||
MachineTags map[string]string `mapstructure:"source_machine_tags"`
|
||||
MachineFirewallEnabled bool `mapstructure:"source_machine_firewall_enabled"`
|
||||
}
|
||||
|
||||
// Prepare performs basic validation on a SourceMachineConfig struct.
|
||||
func (c *SourceMachineConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.MachineName == "" {
|
||||
errs = append(errs, fmt.Errorf("A source_machine_name must be specified"))
|
||||
}
|
||||
|
||||
if c.MachinePackage == "" {
|
||||
errs = append(errs, fmt.Errorf("A source_machine_package must be specified"))
|
||||
}
|
||||
|
||||
if c.MachineImage == "" {
|
||||
errs = append(errs, fmt.Errorf("A source_machine_image must be specified"))
|
||||
}
|
||||
|
||||
if c.MachineNetworks == nil {
|
||||
c.MachineNetworks = []string{}
|
||||
}
|
||||
|
||||
if c.MachineMetadata == nil {
|
||||
c.MachineMetadata = make(map[string]string)
|
||||
}
|
||||
|
||||
if c.MachineTags == nil {
|
||||
c.MachineTags = make(map[string]string)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSourceMachineConfig_Prepare(t *testing.T) {
|
||||
sc := testSourceMachineConfig(t)
|
||||
errs := sc.Prepare(nil)
|
||||
if errs != nil {
|
||||
t.Fatalf("should not error: %#v", sc)
|
||||
}
|
||||
|
||||
sc = testSourceMachineConfig(t)
|
||||
sc.MachineName = ""
|
||||
errs = sc.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatalf("should error: %#v", sc)
|
||||
}
|
||||
|
||||
sc = testSourceMachineConfig(t)
|
||||
sc.MachinePackage = ""
|
||||
errs = sc.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatalf("should error: %#v", sc)
|
||||
}
|
||||
|
||||
sc = testSourceMachineConfig(t)
|
||||
sc.MachineImage = ""
|
||||
errs = sc.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatalf("should error: %#v", sc)
|
||||
}
|
||||
}
|
||||
|
||||
func testSourceMachineConfig(t *testing.T) SourceMachineConfig {
|
||||
return SourceMachineConfig{
|
||||
MachineName: "test-machine",
|
||||
MachinePackage: "test-package",
|
||||
MachineImage: "test-image",
|
||||
MachineNetworks: []string{
|
||||
"test-network-1",
|
||||
"test-network-2",
|
||||
},
|
||||
MachineMetadata: map[string]string{
|
||||
"test-metadata-key1": "test-metadata-value1",
|
||||
"test-metadata-key2": "test-metadata-value2",
|
||||
"test-metadata-key3": "test-metadata-value3",
|
||||
},
|
||||
MachineTags: map[string]string{
|
||||
"test-tags-key1": "test-tags-value1",
|
||||
"test-tags-key2": "test-tags-value2",
|
||||
"test-tags-key3": "test-tags-value3",
|
||||
},
|
||||
MachineFirewallEnabled: false,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func commHost(state multistep.StateBag) (string, error) {
|
||||
driver := state.Get("driver").(Driver)
|
||||
machineID := state.Get("machine").(string)
|
||||
|
||||
machine, err := driver.GetMachine(machineID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return machine, nil
|
||||
}
|
||||
|
||||
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(config.Comm.SSHPrivateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: config.Comm.SSHUsername,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepCreateImageFromMachine creates an image with the specified attributes
|
||||
// from the machine with the given ID, and waits for the image to be created.
|
||||
// The machine must be in the "stopped" state prior to this step being run.
|
||||
type StepCreateImageFromMachine struct{}
|
||||
|
||||
func (s *StepCreateImageFromMachine) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
machineId := state.Get("machine").(string)
|
||||
|
||||
ui.Say("Creating image from source machine...")
|
||||
|
||||
imageId, err := driver.CreateImageFromMachine(machineId, config)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem creating image from machine: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Waiting for image to become available...")
|
||||
err = driver.WaitForImageCreation(imageId, 10*time.Minute)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem waiting for image to become available: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("image", imageId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateImageFromMachine) Cleanup(state multistep.StateBag) {
|
||||
// No cleanup
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateImageFromMachine(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateImageFromMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
state.Put("machine", "test-machine-id")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
_, ok := state.GetOk("image")
|
||||
if !ok {
|
||||
t.Fatalf("should have image")
|
||||
}
|
||||
|
||||
step.Cleanup(state)
|
||||
}
|
||||
|
||||
func TestStepCreateImageFromMachine_CreateImageFromMachineError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateImageFromMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
state.Put("machine", "test-machine-id")
|
||||
|
||||
driver.CreateImageFromMachineErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("image"); ok {
|
||||
t.Fatalf("should NOT have image")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateImageFromMachine_WaitForImageCreationError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateImageFromMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
state.Put("machine", "test-machine-id")
|
||||
|
||||
driver.WaitForImageCreationErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("image"); ok {
|
||||
t.Fatalf("should NOT have image")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepCreateSourceMachine creates an machine with the specified attributes
|
||||
// and waits for it to become available for provisioners.
|
||||
type StepCreateSourceMachine struct{}
|
||||
|
||||
func (s *StepCreateSourceMachine) Run(state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(Config)
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Creating source machine...")
|
||||
|
||||
machineId, err := driver.CreateMachine(config)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem creating source machine: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Waiting for source machine to become available...")
|
||||
err = driver.WaitForMachineState(machineId, "running", 10*time.Minute)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem waiting for source machine to become available: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("machine", machineId)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateSourceMachine) Cleanup(state multistep.StateBag) {
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
machineIdRaw, ok := state.GetOk("machine")
|
||||
if ok && machineIdRaw.(string) != "" {
|
||||
machineId := machineIdRaw.(string)
|
||||
ui.Say(fmt.Sprintf("Stopping source machine (%s)...", machineId))
|
||||
err := driver.StopMachine(machineId)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem stopping source machine: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for source machine to stop (%s)...", machineId))
|
||||
err = driver.WaitForMachineState(machineId, "stopped", 10*time.Minute)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem waiting for source machine to stop: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleting source machine (%s)...", machineId))
|
||||
err = driver.DeleteMachine(machineId)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem deleting source machine: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateSourceMachine(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSourceMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
machineIdRaw, ok := state.GetOk("machine")
|
||||
if !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
|
||||
step.Cleanup(state)
|
||||
|
||||
if driver.DeleteMachineId != machineIdRaw.(string) {
|
||||
t.Fatalf("should've deleted machine (%s != %s)", driver.DeleteMachineId, machineIdRaw.(string))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSourceMachine_CreateMachineError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSourceMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
driver.CreateMachineErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); ok {
|
||||
t.Fatalf("should NOT have machine")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSourceMachine_WaitForMachineStateError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSourceMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
driver.WaitForMachineStateErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); ok {
|
||||
t.Fatalf("should NOT have machine")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSourceMachine_StopMachineError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSourceMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
_, ok := state.GetOk("machine")
|
||||
if !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
|
||||
driver.StopMachineErr = errors.New("error")
|
||||
step.Cleanup(state)
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSourceMachine_WaitForMachineStoppedError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSourceMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
_, ok := state.GetOk("machine")
|
||||
if !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
|
||||
driver.WaitForMachineStateErr = errors.New("error")
|
||||
step.Cleanup(state)
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepCreateSourceMachine_DeleteMachineError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepCreateSourceMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
_, ok := state.GetOk("machine")
|
||||
if !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
|
||||
driver.DeleteMachineErr = errors.New("error")
|
||||
step.Cleanup(state)
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepDeleteMachine deletes the machine with the ID specified in state["machine"]
|
||||
type StepDeleteMachine struct{}
|
||||
|
||||
func (s *StepDeleteMachine) Run(state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
machineId := state.Get("machine").(string)
|
||||
|
||||
ui.Say("Deleting source machine...")
|
||||
err := driver.DeleteMachine(machineId)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem deleting source machine: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Waiting for source machine to be deleted...")
|
||||
err = driver.WaitForMachineDeletion(machineId, 10*time.Minute)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem waiting for source machine to be deleted: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
state.Put("machine", "")
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepDeleteMachine) Cleanup(state multistep.StateBag) {
|
||||
// No clean up to do here...
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepDeleteMachine(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepDeleteMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
machineId := "test-machine-id"
|
||||
state.Put("machine", machineId)
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
step.Cleanup(state)
|
||||
|
||||
if driver.DeleteMachineId != machineId {
|
||||
t.Fatalf("should've deleted machine (%s != %s)", driver.DeleteMachineId, machineId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteMachine_DeleteMachineError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepDeleteMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
machineId := "test-machine-id"
|
||||
state.Put("machine", machineId)
|
||||
|
||||
driver.DeleteMachineErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepDeleteMachine_WaitForMachineDeletionError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepDeleteMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
machineId := "test-machine-id"
|
||||
state.Put("machine", machineId)
|
||||
|
||||
driver.WaitForMachineDeletionErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("machine"); !ok {
|
||||
t.Fatalf("should have machine")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepStopMachine stops the machine with the given Machine ID, and waits
|
||||
// for it to reach the stopped state.
|
||||
type StepStopMachine struct{}
|
||||
|
||||
func (s *StepStopMachine) Run(state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
machineId := state.Get("machine").(string)
|
||||
|
||||
ui.Say(fmt.Sprintf("Stopping source machine (%s)...", machineId))
|
||||
err := driver.StopMachine(machineId)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem stopping source machine: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for source machine to stop (%s)...", machineId))
|
||||
err = driver.WaitForMachineState(machineId, "stopped", 10*time.Minute)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Problem waiting for source machine to stop: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepStopMachine) Cleanup(state multistep.StateBag) {
|
||||
// Explicitly don't clean up here as StepCreateSourceMachine will do it if necessary
|
||||
// and there is no real meaning to cleaning this up.
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
)
|
||||
|
||||
func TestStepStopMachine(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepStopMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
machineId := "test-machine-id"
|
||||
state.Put("machine", machineId)
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionContinue {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
step.Cleanup(state)
|
||||
|
||||
if driver.StopMachineId != machineId {
|
||||
t.Fatalf("should've stopped machine (%s != %s)", driver.StopMachineId, machineId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStopMachine_StopMachineError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepStopMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
machineId := "test-machine-id"
|
||||
state.Put("machine", machineId)
|
||||
|
||||
driver.StopMachineErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStepStopMachine_WaitForMachineStoppedError(t *testing.T) {
|
||||
state := testState(t)
|
||||
step := new(StepStopMachine)
|
||||
defer step.Cleanup(state)
|
||||
|
||||
driver := state.Get("driver").(*DriverMock)
|
||||
|
||||
machineId := "test-machine-id"
|
||||
state.Put("machine", machineId)
|
||||
|
||||
driver.WaitForMachineStateErr = errors.New("error")
|
||||
|
||||
if action := step.Run(state); action != multistep.ActionHalt {
|
||||
t.Fatalf("bad action: %#v", action)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("error"); !ok {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testState(t *testing.T) multistep.StateBag {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", testConfig(t))
|
||||
state.Put("driver", &DriverMock{})
|
||||
state.Put("hook", &packer.MockHook{})
|
||||
state.Put("ui", &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
return state
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
// StepWaitForStopNotToFail waits for 10 seconds before returning with continue
|
||||
// in order to prevent an observed issue where machines stopped immediately after
|
||||
// they are started never actually stop.
|
||||
type StepWaitForStopNotToFail struct{}
|
||||
|
||||
func (s *StepWaitForStopNotToFail) Run(state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("Waiting 10 seconds to avoid potential SDC bug...")
|
||||
time.Sleep(10 * time.Second)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepWaitForStopNotToFail) Cleanup(state multistep.StateBag) {
|
||||
// No clean up required...
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// TargetImageConfig represents the configuration for the image to be created
|
||||
// from the source machine.
|
||||
type TargetImageConfig struct {
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageVersion string `mapstructure:"image_version"`
|
||||
ImageDescription string `mapstructure:"image_description"`
|
||||
ImageHomepage string `mapstructure:"image_homepage"`
|
||||
ImageEULA string `mapstructure:"image_eula_url"`
|
||||
ImageACL []string `mapstructure:"image_acls"`
|
||||
ImageTags map[string]string `mapstructure:"image_tags"`
|
||||
}
|
||||
|
||||
// Prepare performs basic validation on a TargetImageConfig struct.
|
||||
func (c *TargetImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.ImageName == "" {
|
||||
errs = append(errs, fmt.Errorf("An image_name must be specified"))
|
||||
}
|
||||
|
||||
if c.ImageVersion == "" {
|
||||
errs = append(errs, fmt.Errorf("An image_version must be specified"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package triton
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTargetImageConfig_Prepare(t *testing.T) {
|
||||
tic := testTargetImageConfig(t)
|
||||
errs := tic.Prepare(nil)
|
||||
if errs != nil {
|
||||
t.Fatalf("should not error: %#v", tic)
|
||||
}
|
||||
|
||||
tic = testTargetImageConfig(t)
|
||||
tic.ImageName = ""
|
||||
errs = tic.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatalf("should error: %#v", tic)
|
||||
}
|
||||
|
||||
tic = testTargetImageConfig(t)
|
||||
tic.ImageVersion = ""
|
||||
errs = tic.Prepare(nil)
|
||||
if errs == nil {
|
||||
t.Fatalf("should error: %#v", tic)
|
||||
}
|
||||
}
|
||||
|
||||
func testTargetImageConfig(t *testing.T) TargetImageConfig {
|
||||
return TargetImageConfig{
|
||||
ImageName: "test-image",
|
||||
ImageVersion: "test-version",
|
||||
ImageDescription: "test-description",
|
||||
ImageHomepage: "test-homepage",
|
||||
ImageEULA: "test-eula",
|
||||
ImageACL: []string{
|
||||
"test-acl-1",
|
||||
"test-acl-2",
|
||||
},
|
||||
ImageTags: map[string]string{
|
||||
"test-tags-key1": "test-tags-value1",
|
||||
"test-tags-key2": "test-tags-value2",
|
||||
"test-tags-key3": "test-tags-value3",
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue