Skip to content

Commit

Permalink
Merge pull request #20 from picocandy/packer-0.8.6
Browse files Browse the repository at this point in the history
Updated to work with Packer 0.8.6
  • Loading branch information
cmluciano committed Jan 18, 2016
2 parents 3170c44 + 8c71d3c commit aaee056
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 101 deletions.
26 changes: 9 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,16 @@ The builder does not manage images. Once it creates an image, it is up to you to

## Install

Download and build Packer from source as described [here](https://github.com/mitchellh/packer#developing-packer).
Download the Packer binaries [here](https://www.packer.io/downloads.html) or build Packer from source as described [here](https://github.com/mitchellh/packer#developing-packer).

Next, clone this repository into `$GOPATH/src/github.com/leonidlm/packer-builder-softlayer`. Then build the packer-softlayer-builder binary:
Next, clone this repository into `$GOPATH/src/github.com/leonidlm/packer-builder-softlayer`. Then build the packer-softlayer-builder binary into the same folder as the packer binaries:

```
cd $GOPATH/src/github.com/leonidlm/packer-builder-softlayer
go build -o /usr/local/packer/packer-builder-softlayer main.go
```

Now [configure Packer](http://www.packer.io/docs/other/core-configuration.html) to pick up the new builder:

```
{
"builders": {
"softlayer": "/usr/local/packer/packer-builder-softlayer"
}
}
```
Packer should automatically detect the plugin.

## Basic Example

Expand Down Expand Up @@ -72,20 +64,20 @@ The reference of available configuration options is listed below.
* `username` (string) - The user name to use to access your account. If unspecified, the value is taken from the SOFTLAYER_USER_NAME environment variable.
* `api_key` (string) - The api key defined for the chosen user name. You can find what is your api key at the account->users tab of the SoftLayer web console. If unspecified, the value is taken from the SOFTLAYER_API_KEY environment variable.
* `image_name` (string) - The name of the resulting image that will appear in your account. This must be unique. To help make this unique, use a function like timestamp.
* `base_image_id` (string) - The ID of the base image to use (usually defined by the `globalIdentifier` or the `uuid` fields in SoftLayer API). This is the image that will be used for launching a new instance.
* `base_image_id` (string) - The ID of the base image to use (usually defined by the `globalIdentifier` or the `uuid` fields in SoftLayer API). This is the image that will be used for launching a new instance.
__NOTE__ that if you choose to use this option, you must specify a private key using `ssh_private_key_file` (described below).
To view all of your currently available images, run:

```SHELL
curl https://<username>:<api_key>@api.softlayer.com/rest/v3/SoftLayer_Account/getVirtualDiskImages.json
```

* `base_os_code` (string) - If you would like to start from a pre-installed SoftLayer OS image, you can specify it's reference code.
* `base_os_code` (string) - If you would like to start from a pre-installed SoftLayer OS image, you can specify it's reference code.
__NOTE__ that you can use only one of `base_image_id` or `base_os_code` per builder configuration.
To view all of the currently available pre-installed os images, run:

```SHELL
curl https://<username>:<api_key>@api.softlayer.com/rest/v3/SoftLayer_Account/getCreateObjectOptions.json | grep operatingSystemReferenceCode
curl https://<username>:<api_key>@api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/getCreateObjectOptions.json | grep operatingSystemReferenceCode
```

### Optional parameters:
Expand All @@ -106,17 +98,17 @@ The reference of available configuration options is listed below.
As already stated above, a good way of reviewing the available options is by inspecting the output of the following API call:

```SHELL
curl https://<username>:<api_key>@api.softlayer.com/rest/v3/SoftLayer_Account/getCreateObjectOptions.json
curl https://<username>:<api_key>@api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/getCreateObjectOptions.json
```

## Contribute

New contributors are always welcome!
New contributors are always welcome!
When in doubt please feel free to ask questions, just [Create an issue](https://github.com/leonidlm/packer-builder-softlayer/issues/new) with your enquiries.

### Development Environment

The Vagrantfile creates a development environment with Go and packer checked out and built. Type "vagrant up" to bring up the environment and then "vagrant ssh" to log in. The packer-builder-softlayer directory on the host is shared to the guest VM, and packer-builder-softalyer is built during "vagrant up". The SL_USERNAME and SL_API_KEY environment variables from your host machine are propagated to the VM.
The Vagrantfile creates a development environment with Go and packer checked out and built. Type "vagrant up" to bring up the environment and then "vagrant ssh" to log in. The packer-builder-softlayer directory on the host is shared to the guest VM, and packer-builder-softalyer is built during "vagrant up". The SL_USERNAME and SL_API_KEY environment variables from your host machine are propagated to the VM.

To run the unit tests, execute "go test ./..." from the root project directory.

Expand Down
95 changes: 24 additions & 71 deletions builder/softlayer/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
"fmt"
"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"
"github.com/mitchellh/packer/template/interpolate"
"log"
"os"
"time"
Expand All @@ -14,8 +17,9 @@ import (
// The unique ID for this builder.
const BuilderId = "packer.softlayer"

type config struct {
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`

Username string `mapstructure:"username"`
APIKey string `mapstructure:"api_key"`
Expand All @@ -32,47 +36,33 @@ type config struct {
InstanceMemory int64 `mapstructure:"instance_memory"`
InstanceNetworkSpeed int `mapstructure:"instance_network_speed"`
InstanceDiskCapacity int `mapstructure:"instance_disk_capacity"`
SshPort int64 `mapstructure:"ssh_port"`
SshUserName string `mapstructure:"ssh_username"`
SshPrivateKeyFile string `mapstructure:"ssh_private_key_file"`

RawSshTimeout string `mapstructure:"ssh_timeout"`
RawStateTimeout string `mapstructure:"instance_state_timeout"`
StateTimeout time.Duration

SshTimeout time.Duration
StateTimeout time.Duration

tpl *packer.ConfigTemplate
ctx interpolate.Context
}

// Image Types
const IMAGE_TYPE_FLEX = "flex"
const IMAGE_TYPE_FLEX = "flex"
const IMAGE_TYPE_STANDARD = "standard"

// Builder represents a Packer Builder.
type Builder struct {
config config
config Config
runner multistep.Runner
}

// Prepare processes the build configuration parameters.
func (self *Builder) Prepare(raws ...interface{}) (parms []string, retErr error) {
metadata, err := common.DecodeConfig(&self.config, raws...)
if err != nil {
return nil, err
}
err := config.Decode(&self.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &self.config.ctx,
}, raws...)

// Check that there aren't any unknown configuration keys defined
errs := common.CheckUnusedConfig(metadata)
if errs == nil {
errs = &packer.MultiError{}
}

self.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
self.config.tpl.UserVars = self.config.PackerUserVars

// Assign default values if possible
if self.config.APIKey == "" {
Expand Down Expand Up @@ -121,46 +111,17 @@ func (self *Builder) Prepare(raws ...interface{}) (parms []string, retErr error)
self.config.InstanceDiskCapacity = 25
}

if self.config.SshPort == 0 {
self.config.SshPort = 22
}

if self.config.SshUserName == "" {
self.config.SshUserName = "root"
}

if self.config.RawSshTimeout == "" {
self.config.RawSshTimeout = "5m"
if self.config.Comm.SSHUsername == "" {
self.config.Comm.SSHUsername = "root"
}

if self.config.RawStateTimeout == "" {
self.config.RawStateTimeout = "10m"
}

templates := map[string]*string{
"username": &self.config.Username,
"api_key": &self.config.APIKey,
"datacenter_name": &self.config.DatacenterName,
"base_image_id": &self.config.BaseImageId,
"image_name": &self.config.ImageName,
"image_description": &self.config.ImageDescription,
"image_type": &self.config.ImageType,
"base_os_code": &self.config.BaseOsCode,
"instance_name": &self.config.InstanceName,
"instance_domain": &self.config.InstanceDomain,
"ssh_timeout": &self.config.RawSshTimeout,
"instance_state_timeout": &self.config.RawStateTimeout,
"ssh_username": &self.config.SshUserName,
"ssh_private_key_file": &self.config.SshPrivateKeyFile,
}

for n, ptr := range templates {
var err error
*ptr, err = self.config.tpl.Process(*ptr, nil)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err))
}
}
// Validation
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, self.config.Comm.Prepare(&self.config.ctx)...)

// Check for required configurations that will display errors if not set
if self.config.APIKey == "" {
Expand Down Expand Up @@ -193,20 +154,12 @@ func (self *Builder) Prepare(raws ...interface{}) (parms []string, retErr error)
errs, errors.New("please specify only one of base_image_id or base_os_code"))
}

if self.config.BaseImageId != "" && self.config.SshPrivateKeyFile == "" {
if self.config.BaseImageId != "" && self.config.Comm.SSHPrivateKey == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("when using base_image_id, you must specify ssh_private_key_file "+
"since automatic ssh key config for custom images isn't supported by SoftLayer API"))
}

// Translate date configuration data from string to time format
sshTimeout, err := time.ParseDuration(self.config.RawSshTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
self.config.SshTimeout = sshTimeout

stateTimeout, err := time.ParseDuration(self.config.RawStateTimeout)
if err != nil {
errs = packer.MultiErrorAppend(
Expand Down Expand Up @@ -240,14 +193,14 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
// Build the steps
steps := []multistep.Step{
&stepCreateSshKey{
PrivateKeyFile: self.config.SshPrivateKeyFile,
PrivateKeyFile: self.config.Comm.SSHPrivateKey,
},
new(stepCreateInstance),
new(stepWaitforInstance),
&common.StepConnectSSH{
SSHAddress: sshAddress,
SSHConfig: sshConfig,
SSHWaitTimeout: self.config.SshTimeout,
&communicator.StepConnect{
Config: &self.config.Comm,
Host: commHost,
SSHConfig: sshConfig,
},
new(common.StepProvision),
new(stepCaptureImage),
Expand Down
5 changes: 2 additions & 3 deletions builder/softlayer/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

func testConfig() map[string]interface{} {
return map[string]interface{} {
return map[string]interface{}{
"username": "test",
"api_key": "testkey",
"image_name": "testimage",
Expand All @@ -28,7 +28,7 @@ func TestPrepare_ImageType(t *testing.T) {
}

// Verify standard images are supported
c["image_type"] = "standard"
c["image_type"] = "standard"
if _, err := b.Prepare(c); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
Expand All @@ -43,4 +43,3 @@ func TestPrepare_ImageType(t *testing.T) {
t.Fatal("Expected an error")
}
}

11 changes: 5 additions & 6 deletions builder/softlayer/ssh.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package softlayer

import (
"code.google.com/p/go.crypto/ssh"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"golang.org/x/crypto/ssh"
)

func sshAddress(state multistep.StateBag) (string, error) {
config := state.Get("config").(config)
func commHost(state multistep.StateBag) (string, error) {
client := state.Get("client").(*SoftlayerClient)
instance := state.Get("instance_data").(map[string]interface{})
instanceId := instance["globalIdentifier"].(string)
Expand All @@ -18,11 +17,11 @@ func sshAddress(state multistep.StateBag) (string, error) {
return "", err
}

return fmt.Sprintf("%s:%d", ipAddress, config.SshPort), nil
return ipAddress, nil
}

func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
config := state.Get("config").(config)
config := state.Get("config").(Config)
privateKey := state.Get("ssh_private_key").(string)

signer, err := ssh.ParsePrivateKey([]byte(privateKey))
Expand All @@ -31,7 +30,7 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
}

return &ssh.ClientConfig{
User: config.SshUserName,
User: config.Comm.SSHUsername,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
Expand Down
2 changes: 1 addition & 1 deletion builder/softlayer/step_capture_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func (self *stepCaptureImage) Run(state multistep.StateBag) multistep.StepAction
client := state.Get("client").(*SoftlayerClient)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance_data").(map[string]interface{})
config := state.Get("config").(config)
config := state.Get("config").(Config)
instanceId := instance["globalIdentifier"].(string)
var imageId string

Expand Down
4 changes: 2 additions & 2 deletions builder/softlayer/step_create_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type stepCreateInstance struct {

func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*SoftlayerClient)
config := state.Get("config").(config)
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)

// The ssh_key_id can be empty if the user specified a private key
Expand Down Expand Up @@ -55,7 +55,7 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi

func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*SoftlayerClient)
config := state.Get("config").(config)
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)

if self.instanceId == "" {
Expand Down
2 changes: 1 addition & 1 deletion builder/softlayer/step_waitfor_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type stepWaitforInstance struct{}

func (self *stepWaitforInstance) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*SoftlayerClient)
config := state.Get("config").(config)
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)

ui.Say("Waiting for the instance to become ACTIVE...")
Expand Down

0 comments on commit aaee056

Please sign in to comment.