From cb1e30ef692cf4af56a9e2b27dfd47e276894ea1 Mon Sep 17 00:00:00 2001 From: Andre Hilsendeger Date: Mon, 5 Aug 2019 10:10:59 +0200 Subject: [PATCH] feat(builder/hcloud): allow selecting image based on filters --- builder/hcloud/config.go | 27 ++++++++++++---- builder/hcloud/step_create_server.go | 48 ++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/builder/hcloud/config.go b/builder/hcloud/config.go index 1fd5758f7..3a7d49fb5 100644 --- a/builder/hcloud/config.go +++ b/builder/hcloud/config.go @@ -25,10 +25,11 @@ type Config struct { Endpoint string `mapstructure:"endpoint"` PollInterval time.Duration `mapstructure:"poll_interval"` - ServerName string `mapstructure:"server_name"` - Location string `mapstructure:"location"` - ServerType string `mapstructure:"server_type"` - Image string `mapstructure:"image"` + ServerName string `mapstructure:"server_name"` + Location string `mapstructure:"location"` + ServerType string `mapstructure:"server_type"` + Image string `mapstructure:"image"` + ImageFilter *imageFilter `mapstructure:"image_filter"` SnapshotName string `mapstructure:"snapshot_name"` SnapshotLabels map[string]string `mapstructure:"snapshot_labels"` @@ -41,6 +42,11 @@ type Config struct { ctx interpolate.Context } +type imageFilter struct { + WithSelector []string `mapstructure:"with_selector"` + MostRecent bool `mapstructure:"most_recent"` +} + func NewConfig(raws ...interface{}) (*Config, []string, error) { c := new(Config) @@ -108,9 +114,18 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs, errors.New("server type is required")) } - if c.Image == "" { + if c.Image == "" && c.ImageFilter == nil { errs = packer.MultiErrorAppend( - errs, errors.New("image is required")) + errs, errors.New("image or image_filter is required")) + } + if c.ImageFilter != nil { + if len(c.ImageFilter.WithSelector) == 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("image_filter.with_selector is required when specifying filter")) + } else if c.Image != "" { + errs = packer.MultiErrorAppend( + errs, errors.New("only one of image or image_filter can be specified")) + } } if c.UserData != "" && c.UserDataFile != "" { diff --git a/builder/hcloud/step_create_server.go b/builder/hcloud/step_create_server.go index d3605e254..c5c38b9ad 100644 --- a/builder/hcloud/step_create_server.go +++ b/builder/hcloud/step_create_server.go @@ -3,8 +3,9 @@ package hcloud import ( "context" "fmt" - "io/ioutil" + "sort" + "strings" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" @@ -50,10 +51,24 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu sshKeys = append(sshKeys, sshKey) } + var image *hcloud.Image + if c.Image != "" { + image = &hcloud.Image{Name: c.Image} + } else { + var err error + image, err = getImageWithSelectors(ctx, client, c) + if err != nil { + ui.Error(err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } + ui.Message(fmt.Sprintf("Using image %s with ID %d", image.Description, image.ID)) + } + serverCreateResult, _, err := client.Server.Create(ctx, hcloud.ServerCreateOpts{ Name: c.ServerName, ServerType: &hcloud.ServerType{Name: c.ServerType}, - Image: &hcloud.Image{Name: c.Image}, + Image: image, SSHKeys: sshKeys, Location: &hcloud.Location{Name: c.Location}, UserData: userData, @@ -185,3 +200,32 @@ func waitForAction(ctx context.Context, client *hcloud.Client, action *hcloud.Ac } return nil } + +func getImageWithSelectors(ctx context.Context, client *hcloud.Client, c *Config) (*hcloud.Image, error) { + var allImages []*hcloud.Image + + var selector = strings.Join(c.ImageFilter.WithSelector, ",") + opts := hcloud.ImageListOpts{ + ListOpts: hcloud.ListOpts{LabelSelector: selector}, + Status: []hcloud.ImageStatus{hcloud.ImageStatusAvailable}, + } + + allImages, err := client.Image.AllWithOpts(ctx, opts) + if err != nil { + return nil, err + } + if len(allImages) == 0 { + return nil, fmt.Errorf("no image found for selector %q", selector) + } + if len(allImages) > 1 { + if !c.ImageFilter.MostRecent { + return nil, fmt.Errorf("more than one image found for selector %q", selector) + } + + sort.Slice(allImages, func(i, j int) bool { + return allImages[i].Created.After(allImages[j].Created) + }) + } + + return allImages[0], nil +}