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:
parent
a495948dc5
commit
7776bf596b
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue