Merge pull request #5538 from stack72/triton-image-data-source

builder/triton: Add a data source for source_machine_image
This commit is contained in:
James Nugent 2017-11-02 15:33:22 -05:00 committed by GitHub
commit fdd9b952d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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

@ -9,13 +9,29 @@ import (
// 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"`
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"`
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",