Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
# SoftLayer Builder (for packer.io)
# SoftLayer/IBM Bluemix Builder (for packer.io)

The softlayer builder is able to create new images for use with SoftLayer. The builder takes a source image (identified by it's global ID or reference name), runs any provisioning necessary on the image after launching it, then snapshots it into a reusable image. This reusable image can then be used as the foundation of new servers that are launched within SoftLayer.
The softlayer/IBM bluemix builder is able to create new images for use with SoftLayer and/or IBM Bluemix. The builder takes a source image (identified by it's global ID or reference name), runs any provisioning necessary on the image after launching it, then snapshots it into a reusable image. This reusable image can then be used as the foundation of new servers that are launched within SoftLayer.

The builder does not manage images. Once it creates an image, it is up to you to use it or delete it.

## Install

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 into the same folder as the packer binaries:
Next, clone this repository into `$GOPATH/src/github.com/leonidlm/packer-builder-softlayer`.
Then install vendor dependencies using [govendor](https://github.com/kardianos/govendor). Last
build the packer-softlayer-builder binary into the same folder as the packer binaries:

```
mkdir -p ~/.packer-id/plugins
go get -u github.com/leonidlm/packer-builder-softlayer
go get github.com/kardianos/govendor
cd $GOPATH/src/github.com/leonidlm/packer-builder-softlayer
go build -o /usr/local/packer/packer-builder-softlayer main.go
govendor sync
govendor build -o ~/.packer.d/plugins/packer-builder-softlayer main.go
```

Packer should automatically detect the plugin.
Expand Down
19 changes: 11 additions & 8 deletions builder/softlayer/builder.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package softlayer

import (
"bytes"
"errors"
"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"

"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)

// The unique ID for this builder.
Expand Down Expand Up @@ -154,7 +156,7 @@ 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.Comm.SSHPrivateKey == "" {
if self.config.BaseImageId != "" && bytes.Compare(self.config.Comm.SSHPrivateKey, []byte("")) == 0 {
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"))
Expand All @@ -167,7 +169,8 @@ func (self *Builder) Prepare(raws ...interface{}) (parms []string, retErr error)
}
self.config.StateTimeout = stateTimeout

log.Println(common.ScrubConfig(self.config, self.config.APIKey, self.config.Username))
packer.LogSecretFilter.Set(self.config.APIKey, self.config.Username)
log.Println(self.config)

if len(errs.Errors) > 0 {
retErr = errors.New(errs.Error())
Expand Down
11 changes: 5 additions & 6 deletions builder/softlayer/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ func (self SoftlayerClient) doHttpRequest(path string, requestType string, reque
return nil, err
}

return []interface{} {v,}, nil
return []interface{}{v}, nil

case nil:
return []interface{} {nil,}, nil
return []interface{}{nil}, nil
default:
return nil, errors.New("Unexpected type in HTTP response")
}
Expand Down Expand Up @@ -329,7 +329,7 @@ func (self SoftlayerClient) getBlockDevices(instanceId string) ([]interface{}, e
return data, nil
}

func (self SoftlayerClient) findNonSwapBlockDeviceIds(blockDevices []interface{}) ([]int64) {
func (self SoftlayerClient) findNonSwapAndMetadataBlockDeviceIds(blockDevices []interface{}) []int64 {
blockDeviceIds := make([]int64, len(blockDevices))
deviceCount := 0

Expand All @@ -339,7 +339,7 @@ func (self SoftlayerClient) findNonSwapBlockDeviceIds(blockDevices []interface{}
name := diskImage["name"].(string)
id := int64(blockDevice["id"].(float64))

if !strings.Contains(name, "SWAP") {
if !strings.Contains(name, "SWAP") && !strings.Contains(name, "METADATA") {
blockDeviceIds[deviceCount] = id
deviceCount++
}
Expand Down Expand Up @@ -378,10 +378,9 @@ func (self SoftlayerClient) findImageIdByName(imageName string) (string, error)
return "", err
}

return imageId, nil;
return imageId, nil
}


func (self SoftlayerClient) captureStandardImage(instanceId string, imageName string, imageDescription string, blockDeviceIds []int64) (map[string]interface{}, error) {
blockDevices := make([]*BlockDevice, len(blockDeviceIds))
for i, id := range blockDeviceIds {
Expand Down
59 changes: 27 additions & 32 deletions builder/softlayer/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ func TestClient_FindNonSwapBlockDeviceIds(t *testing.T) {
client := SoftlayerClient{}

result := client.findNonSwapBlockDeviceIds(
[]interface{} {
map[string]interface{} {
[]interface{}{
map[string]interface{}{
"device": "0",
"id": 11.,
"diskImage": map[string]interface{} {
"id": 11.,
"diskImage": map[string]interface{}{
"name": "root-device",
"id": 12.,
"id": 12.,
},
},
map[string]interface{} {
map[string]interface{}{
"device": "1",
"id": 21.,
"diskImage": map[string]interface{} {
"id": 21.,
"diskImage": map[string]interface{}{
"name": "SWAP-device",
"id": 22.,
"id": 22.,
},
},
})
Expand All @@ -33,49 +33,45 @@ func TestClient_FindNonSwapBlockDeviceIds(t *testing.T) {
t.Fatalf("Expected device id 11 but got %d", result[0])
}



result = client.findNonSwapBlockDeviceIds(
[]interface{} {
map[string]interface{} {
[]interface{}{
map[string]interface{}{
"device": "0",
"id": 11.,
"diskImage": map[string]interface{} {
"id": 11.,
"diskImage": map[string]interface{}{
"name": "first-SWAP-device",
"id": 12.,
"id": 12.,
},
},
map[string]interface{} {
map[string]interface{}{
"device": "1",
"id": 21.,
"diskImage": map[string]interface{} {
"id": 21.,
"diskImage": map[string]interface{}{
"name": "SWAP-device",
"id": 22.,
"id": 22.,
},
},
})
if len(result) != 0 {
t.Fatalf("Expected no devices but got '%v'", result)
}



result = client.findNonSwapBlockDeviceIds(
[]interface{} {
map[string]interface{} {
[]interface{}{
map[string]interface{}{
"device": "0",
"id": 11.,
"diskImage": map[string]interface{} {
"id": 11.,
"diskImage": map[string]interface{}{
"name": "first-device",
"id": 12.,
"id": 12.,
},
},
map[string]interface{} {
map[string]interface{}{
"device": "1",
"id": 21.,
"diskImage": map[string]interface{} {
"id": 21.,
"diskImage": map[string]interface{}{
"name": "second-device",
"id": 22.,
"id": 22.,
},
},
})
Expand All @@ -86,4 +82,3 @@ func TestClient_FindNonSwapBlockDeviceIds(t *testing.T) {
t.Fatalf("Expected devices 11 and 21 but got %v", result)
}
}

4 changes: 3 additions & 1 deletion builder/softlayer/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package softlayer
import (
"errors"
"fmt"
"github.com/mitchellh/multistep"

"github.com/hashicorp/packer/helper/multistep"
"golang.org/x/crypto/ssh"
)

Expand Down Expand Up @@ -34,5 +35,6 @@ func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}, nil
}
11 changes: 7 additions & 4 deletions builder/softlayer/step_capture_image.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package softlayer

import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"

"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)

type stepCaptureImage struct{}

func (self *stepCaptureImage) Run(state multistep.StateBag) multistep.StepAction {
func (self *stepCaptureImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*SoftlayerClient)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance_data").(map[string]interface{})
Expand All @@ -29,7 +31,8 @@ func (self *stepCaptureImage) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionHalt
}

blockDeviceIds := client.findNonSwapBlockDeviceIds(blockDevices)
blockDeviceIds := client.findNonSwapAndMetadataBlockDeviceIds(blockDevices)

ui.Say(fmt.Sprintf("Will capture standard image using these block devices: %v", blockDeviceIds))

_, err = client.captureStandardImage(instanceId, config.ImageName, config.ImageDescription, blockDeviceIds)
Expand Down
8 changes: 5 additions & 3 deletions builder/softlayer/step_create_instance.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package softlayer

import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"

"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)

type stepCreateInstance struct {
instanceId string
}

func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction {
func (self *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*SoftlayerClient)
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)
Expand Down
19 changes: 11 additions & 8 deletions builder/softlayer/step_create_ssh_key.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
package softlayer

import (
"code.google.com/p/gosshold/ssh"
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"strings"

"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"golang.org/x/crypto/ssh"
)

type stepCreateSshKey struct {
keyId int64
PrivateKeyFile string
PrivateKeyFile []byte
}

func (self *stepCreateSshKey) Run(state multistep.StateBag) multistep.StepAction {
func (self *stepCreateSshKey) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if self.PrivateKeyFile != "" {
if bytes.Compare(self.PrivateKeyFile, []byte("")) != 0 {
ui.Say(fmt.Sprintf("Reading private key file (%s)...", self.PrivateKeyFile))

privateKeyBytes, err := ioutil.ReadFile(self.PrivateKeyFile)
privateKeyBytes, err := ioutil.ReadFile(string(self.PrivateKeyFile))
if err != nil {
state.Put("error", fmt.Errorf("Error loading configured private key file: %s", err))
return multistep.ActionHalt
Expand Down
8 changes: 5 additions & 3 deletions builder/softlayer/step_waitfor_instance.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package softlayer

import (
"context"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"

"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)

type stepWaitforInstance struct{}

func (self *stepWaitforInstance) Run(state multistep.StateBag) multistep.StepAction {
func (self *stepWaitforInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*SoftlayerClient)
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package main

import (
"github.com/hashicorp/packer/packer/plugin"
"github.com/leonidlm/packer-builder-softlayer/builder/softlayer"
"github.com/mitchellh/packer/packer/plugin"
)

func main() {
Expand Down
Loading