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 { type Driver interface {
GetImage(config Config) (string, error)
CreateImageFromMachine(machineId string, config Config) (string, error) CreateImageFromMachine(machineId string, config Config) (string, error)
CreateMachine(config Config) (string, error) CreateMachine(config Config) (string, error)
DeleteImage(imageId string) error DeleteImage(imageId string) error

View File

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

View File

@ -6,6 +6,8 @@ import (
"net/http" "net/http"
"time" "time"
"sort"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/joyent/triton-go/client" "github.com/joyent/triton-go/client"
"github.com/joyent/triton-go/compute" "github.com/joyent/triton-go/compute"
@ -28,6 +30,36 @@ func NewDriverTriton(ui packer.Ui, config Config) (Driver, error) {
}, nil }, 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) { func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (string, error) {
computeClient, _ := d.client.Compute() computeClient, _ := d.client.Compute()
image, err := computeClient.Images().CreateFromMachine(context.Background(), &compute.CreateImageFromMachineInput{ 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") 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"` MachineMetadata map[string]string `mapstructure:"source_machine_metadata"`
MachineTags map[string]string `mapstructure:"source_machine_tags"` MachineTags map[string]string `mapstructure:"source_machine_tags"`
MachineFirewallEnabled bool `mapstructure:"source_machine_firewall_enabled"` 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. // 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")) errs = append(errs, fmt.Errorf("A source_machine_package must be specified"))
} }
if c.MachineImage == "" { if c.MachineImage != "" && c.MachineImageFilters.Name != "" {
errs = append(errs, fmt.Errorf("A source_machine_image must be specified")) errs = append(errs, fmt.Errorf("You cannot specify a Machine Image and also Machine Name filter"))
} }
if c.MachineNetworks == nil { if c.MachineNetworks == nil {

View File

@ -24,13 +24,6 @@ func TestSourceMachineConfig_Prepare(t *testing.T) {
if errs == nil { if errs == nil {
t.Fatalf("should error: %#v", sc) 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 { 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) driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui) 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) machineId, err := driver.CreateMachine(config)
if err != nil { if err != nil {
@ -33,7 +42,6 @@ func (s *StepCreateSourceMachine) Run(state multistep.StateBag) multistep.StepAc
} }
state.Put("machine", machineId) state.Put("machine", machineId)
return multistep.ActionContinue return multistep.ActionContinue
} }

View File

@ -64,7 +64,8 @@ builder.
base image automatically decides the brand. On the Joyent public cloud a base image automatically decides the brand. On the Joyent public cloud a
valid `source_machine_image` could for example be valid `source_machine_image` could for example be
`70e3ae72-96b6-11e6-9056-9737fd4d0764` for version 16.3.1 of the 64bit `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 - `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 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. information about the image. Maximum 128 characters.
- `image_tags` (object of key/value strings) - Tag applied to the image. - `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 ## Basic Example
Below is a minimal example to create an joyent-brand image on the Joyent public 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_name": "image-builder",
"source_machine_package": "g4-highcpu-128M", "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", "ssh_username": "root",