builder/triton: Add a data source for source_machine_image

fixes: #5476

Based on this new template addition:

```
{
    "variables": {
        "image_version": "",
        "triton_account": "",
        "triton_key_id": "",
        "triton_key_material": ""
    },
    "builders": [{
        "type": "triton",
        "triton_account": "{{user `triton_account`}}",
        "triton_key_id": "{{user `triton_key_id`}}",
        "triton_key_material": "{{user `triton_key_material`}}",
        "source_machine_package": "g4-highcpu-128M",
        "source_machine_image_filter": {
          "name": "ubuntu-16.04",
          "most_recent": "true"
        },
        "ssh_username": "root",
        "image_version": "{{user `image_version`}}",
        "image_name": "teamcity-server"
    }],
    "provisioners": [
        {
            "type": "shell",
            "start_retry_timeout": "10m",
            "inline": [
                "sudo apt-get update -y",
                "sudo apt-get install -y nginx"
            ]
        }
    ]
}

```

I got the following output from packer:

```
packer-testing % make image
packer build \
		-var "triton_account=stack72_joyent" \
		-var "triton_key_id=40:9d:d3:f9:0b:86:62:48:f4:2e:a5:8e:43:00:2a:9b" \
		-var "triton_key_material=""" \
		-var "image_version=1.0.0" \
		new-template.json
triton output will be in this color.

==> triton: Selecting an image based on search criteria
==> triton: Based, on given search criteria, Machine ID is: "7b5981c4-1889-11e7-b4c5-3f3bdfc9b88b"
==> triton: Waiting for source machine to become available...
==> triton: Waiting for SSH to become available...
==> triton: Connected to SSH!
==> triton: Provisioning with shell script: /var/folders/_p/2_zj9lqn4n11fx20qy787p7c0000gn/T/packer-shell797317310
    triton: Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]
    triton: Hit:2 http://archive.ubuntu.com/ubuntu xenial InRelease
```

I can verify from the triton cli tools that the id `7b5981c4` (from the packer output) is indeed the correct ID

```
terraform [master●] % triton images name=~ubuntu-16.04
SHORTID   NAME          VERSION   FLAGS  OS     TYPE        PUBDATE
49b22aec  ubuntu-16.04  20160427  P      linux  lx-dataset  2016-04-27
675834a0  ubuntu-16.04  20160505  P      linux  lx-dataset  2016-05-05
4edaa46a  ubuntu-16.04  20160516  P      linux  lx-dataset  2016-05-16
05140a7e  ubuntu-16.04  20160601  P      linux  lx-dataset  2016-06-01
e331b22a  ubuntu-16.04  20161004  P      linux  lx-dataset  2016-10-04
8879c758  ubuntu-16.04  20161213  P      linux  lx-dataset  2016-12-13
7b5981c4  ubuntu-16.04  20170403  P      linux  lx-dataset  2017-04-03 <------- THIS IS THE LATEST UBUNTU IMAGE
```
This commit is contained in:
stack72 2017-10-30 19:26:42 +02:00
parent a495948dc5
commit 7776bf596b
7 changed files with 125 additions and 20 deletions

View File

@ -5,6 +5,7 @@ import (
)
type Driver interface {
GetImage(config Config) (string, error)
CreateImageFromMachine(machineId string, config Config) (string, error)
CreateMachine(config Config) (string, error)
DeleteImage(imageId string) error

View File

@ -17,6 +17,9 @@ type DriverMock struct {
DeleteMachineId string
DeleteMachineErr error
GetImageId string
GetImageErr error
GetMachineErr error
StopMachineId string
@ -29,6 +32,14 @@ type DriverMock struct {
WaitForMachineStateErr error
}
func (d *DriverMock) GetImage(config Config) (string, error) {
if d.GetImageErr != nil {
return "", d.GetImageErr
}
return config.MachineImage, nil
}
func (d *DriverMock) CreateImageFromMachine(machineId string, config Config) (string, error) {
if d.CreateImageFromMachineErr != nil {
return "", d.CreateImageFromMachineErr

View File

@ -6,6 +6,8 @@ import (
"net/http"
"time"
"sort"
"github.com/hashicorp/packer/packer"
"github.com/joyent/triton-go/client"
"github.com/joyent/triton-go/compute"
@ -28,6 +30,36 @@ func NewDriverTriton(ui packer.Ui, config Config) (Driver, error) {
}, nil
}
func (d *driverTriton) GetImage(config Config) (string, error) {
computeClient, _ := d.client.Compute()
images, err := computeClient.Images().List(context.Background(), &compute.ListImagesInput{
Name: config.MachineImageFilters.Name,
OS: config.MachineImageFilters.OS,
Version: config.MachineImageFilters.Version,
Public: config.MachineImageFilters.Public,
Type: config.MachineImageFilters.Type,
State: config.MachineImageFilters.State,
Owner: config.MachineImageFilters.Owner,
})
if err != nil {
return "", err
}
if len(images) == 0 {
return "", errors.New("No images found in your search. Please refine your search criteria")
}
if len(images) > 1 {
if !config.MachineImageFilters.MostRecent {
return "", errors.New("More than 1 machine image was found in your search. Please refine your search criteria")
} else {
return mostRecentImages(images).ID, nil
}
} else {
return images[0].ID, nil
}
}
func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (string, error) {
computeClient, _ := d.client.Compute()
image, err := computeClient.Images().CreateFromMachine(context.Background(), &compute.CreateImageFromMachineInput{
@ -193,3 +225,29 @@ func waitFor(f func() (bool, error), every, timeout time.Duration) error {
return errors.New("Timed out while waiting for resource change")
}
func mostRecentImages(images []*compute.Image) *compute.Image {
return sortImages(images)[0]
}
type imageSort []*compute.Image
func sortImages(images []*compute.Image) []*compute.Image {
sortedImages := images
sort.Sort(sort.Reverse(imageSort(sortedImages)))
return sortedImages
}
func (a imageSort) Len() int {
return len(a)
}
func (a imageSort) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a imageSort) Less(i, j int) bool {
itime := a[i].PublishedAt
jtime := a[j].PublishedAt
return itime.Unix() < jtime.Unix()
}

View File

@ -16,6 +16,22 @@ type SourceMachineConfig struct {
MachineMetadata map[string]string `mapstructure:"source_machine_metadata"`
MachineTags map[string]string `mapstructure:"source_machine_tags"`
MachineFirewallEnabled bool `mapstructure:"source_machine_firewall_enabled"`
MachineImageFilters MachineImageFilter `mapstructure:"source_machine_image_filter"`
}
type MachineImageFilter struct {
MostRecent bool `mapstructure:"most_recent"`
Name string
OS string
Version string
Public bool
State string
Owner string
Type string
}
func (m *MachineImageFilter) Empty() bool {
return m.Name == "" && m.OS == "" && m.Version == "" && m.State == "" && m.Owner == "" && m.Type == ""
}
// Prepare performs basic validation on a SourceMachineConfig struct.
@ -26,8 +42,8 @@ func (c *SourceMachineConfig) Prepare(ctx *interpolate.Context) []error {
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.MachineImage != "" && c.MachineImageFilters.Name != "" {
errs = append(errs, fmt.Errorf("You cannot specify a Machine Image and also Machine Name filter"))
}
if c.MachineNetworks == nil {

View File

@ -24,13 +24,6 @@ func TestSourceMachineConfig_Prepare(t *testing.T) {
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 {

View File

@ -17,7 +17,16 @@ func (s *StepCreateSourceMachine) Run(state multistep.StateBag) multistep.StepAc
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating source machine...")
if !config.MachineImageFilters.Empty() {
ui.Say("Selecting an image based on search criteria")
imageId, err := driver.GetImage(config)
if err != nil {
state.Put("error", fmt.Errorf("Problem selecting an image based on an search criteria: %s", err))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Based, on given search criteria, Machine ID is: %q", imageId))
config.MachineImage = imageId
}
machineId, err := driver.CreateMachine(config)
if err != nil {
@ -33,7 +42,6 @@ func (s *StepCreateSourceMachine) Run(state multistep.StateBag) multistep.StepAc
}
state.Put("machine", machineId)
return multistep.ActionContinue
}

View File

@ -64,7 +64,8 @@ builder.
base image automatically decides the brand. On the Joyent public cloud a
valid `source_machine_image` could for example be
`70e3ae72-96b6-11e6-9056-9737fd4d0764` for version 16.3.1 of the 64bit
SmartOS base image (a 'joyent' brand image).
SmartOS base image (a 'joyent' brand image). `source_machine_image_filter` can
be used to populate this UUID.
- `source_machine_package` (string) - The Triton package to use while building
the image. Does not affect (and does not have to be the same) as the package
@ -133,6 +134,19 @@ builder.
information about the image. Maximum 128 characters.
- `image_tags` (object of key/value strings) - Tag applied to the image.
- `source_machine_image_filter` (object) - Filters used to populate the `source_machine_image` field.
Example:
``` json
{
"source_machine_image_filter": {
"name": "ubuntu-16.04",
"type": "lx-dataset",
"most_recent": true
}
}
```
## Basic Example
Below is a minimal example to create an joyent-brand image on the Joyent public
@ -149,7 +163,11 @@ cloud:
"source_machine_name": "image-builder",
"source_machine_package": "g4-highcpu-128M",
"source_machine_image": "f6acf198-2037-11e7-8863-8fdd4ce58b6a",
"source_machine_image_filter": {
"name": "ubuntu-16.04",
"type": "lx-dataset",
"most_recent": "true"
},
"ssh_username": "root",