Skip to content
This repository has been archived by the owner on Oct 4, 2021. It is now read-only.

Commit

Permalink
Added multiple zones support
Browse files Browse the repository at this point in the history
  • Loading branch information
maxfrei committed Feb 17, 2021
1 parent 20b53a2 commit ab3c525
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 81 deletions.
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
default: build

test:
PACKER_ACC=1 go test -count 1 -v ./... -timeout=120m

build:
go build -v

install: build
mkdir -p ~/.packer.d/plugins
install ./upcloud-packer ~/.packer.d/plugins/packer-builder-upcloud

.PHONY: default test build install
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ This section describes the available configuration options for the builder. Plea
* `storage_size` (int) The storage size in gigabytes. Defaults to `25`. Changing this value is useful if you aim to build a template for larger server configurations where the preconfigured server disk is larger than 25 GB. The operating system disk can also be later extended if needed. Note that Windows templates require large storage size, than default 25 Gb.
* `state_timeout_duration` (string) The amount of time to wait for resource state changes. Defaults to `5m`.
* `template_prefix` (string) The prefix to use for the generated template title. Defaults to an empty string, meaning the prefix will be the storage title. You can use this option to easily differentiate between different templates.
* `clone_zones` ([]string) The array of extra zones (locations) where created templates should be cloned. Note that default `state_timeout_duration` is not enough for cloning, better to increase a value depending on storage size.

## License

Expand Down
23 changes: 17 additions & 6 deletions builder/upcloud/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package upcloud

import (
"fmt"
"strings"

"github.com/UpCloudLtd/upcloud-go-api/upcloud"
)

// packersdk.Artifact implementation
type Artifact struct {
config *Config
driver Driver
Template *upcloud.Storage
config *Config
driver Driver
Templates []*upcloud.Storage

// StateData should store data such as GeneratedData
// to be shared with post-processors
Expand All @@ -26,17 +27,27 @@ func (a *Artifact) Files() []string {
}

func (a *Artifact) Id() string {
return a.Template.UUID
result := []string{}
for _, t := range a.Templates {
result = append(result, t.UUID)
}
return strings.Join(result, ",")
}

func (a *Artifact) String() string {
return fmt.Sprintf("Storage template created, UUID: %q, Title: %q", a.Template.UUID, a.Template.Title)
return fmt.Sprintf("Storage template created, UUID: %s", a.Id())
}

func (a *Artifact) State(name string) interface{} {
return a.StateData[name]
}

func (a *Artifact) Destroy() error {
return a.driver.DeleteTemplate(a.Template.UUID)
for _, t := range a.Templates {
err := a.driver.DeleteTemplate(t.UUID)
if err != nil {
return err
}
}
return nil
}
27 changes: 16 additions & 11 deletions builder/upcloud/artifact_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package upcloud

import (
"fmt"
"testing"

"github.com/UpCloudLtd/upcloud-go-api/upcloud"
Expand All @@ -12,11 +13,15 @@ func TestArtifact_impl(t *testing.T) {
}

func TestArtifact_Id(t *testing.T) {
expected := "some-uuid"
template := &upcloud.Storage{
UUID: expected,
}
a := &Artifact{Template: template}
uuid1 := "some-uuid-1"
uuid2 := "some-uuid-2"
expected := fmt.Sprintf("%s,%s", uuid1, uuid2)

templates := []*upcloud.Storage{}
templates = append(templates, &upcloud.Storage{UUID: uuid1})
templates = append(templates, &upcloud.Storage{UUID: uuid2})

a := &Artifact{Templates: templates}
result := a.Id()

if result != expected {
Expand All @@ -25,12 +30,12 @@ func TestArtifact_Id(t *testing.T) {
}

func TestArtifact_String(t *testing.T) {
expected := `Storage template created, UUID: "some-uuid", Title: "some-title"`
template := &upcloud.Storage{
UUID: "some-uuid",
Title: "some-title",
}
a := &Artifact{Template: template}
expected := `Storage template created, UUID: some-uuid`

templates := []*upcloud.Storage{}
templates = append(templates, &upcloud.Storage{UUID: "some-uuid"})

a := &Artifact{Templates: templates}
result := a.String()

if result != expected {
Expand Down
20 changes: 15 additions & 5 deletions builder/upcloud/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ func (b *Builder) Prepare(raws ...interface{}) (generatedVars []string, warnings

func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
// Setup the state bag and initial state for the steps
b.driver = NewDriver(&b.config)
b.driver = NewDriver(&DriverConfig{
Username: b.config.Username,
Password: b.config.Password,
Timeout: b.config.Timeout,
SSHUsername: b.config.Comm.SSHUsername,
})

state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
Expand All @@ -59,11 +64,12 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
DebugKeyPath: fmt.Sprintf("ssh_key-%s.pem", b.config.PackerBuildName),
},
&StepCreateServer{
Config: &b.config,
GeneratedData: generatedData,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: sshHostCallback,
Host: SshHostCallback,
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&commonsteps.StepProvision{},
Expand All @@ -72,6 +78,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&StepTeardownServer{},
&StepCreateTemplate{
Config: &b.config,
GeneratedData: generatedData,
},
}
Expand All @@ -85,16 +92,19 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
return nil, err.(error)
}

template, ok := state.GetOk("template")
templates, ok := state.GetOk("templates")
if !ok {
return nil, fmt.Errorf("No template found in state, the build was probably cancelled")
}

artifact := &Artifact{
Template: template.(*upcloud.Storage),
Templates: templates.([]*upcloud.Storage),
config: &b.config,
driver: b.driver,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
StateData: map[string]interface{}{
"generated_data": state.Get("generated_data"),
"template_prefix": b.config.TemplatePrefix,
},
}
return artifact, nil
}
8 changes: 4 additions & 4 deletions builder/upcloud/builder_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ func checkTemplateDefaultSettings() builderT.TestCheckFunc {
expectedSize := 25
expectedTitle := "custom-image"

if artifact.Template.Size != expectedSize {
return fmt.Errorf("Wrong size. Expected %d, got %d", expectedSize, artifact.Template.Size)
if artifact.Templates[0].Size != expectedSize {
return fmt.Errorf("Wrong size. Expected %d, got %d", expectedSize, artifact.Templates[0].Size)
}

if !strings.HasPrefix(artifact.Template.Title, expectedTitle) {
return fmt.Errorf("Wrong title prefix. Expected %q, got %q", expectedTitle, artifact.Template.Title)
if !strings.HasPrefix(artifact.Templates[0].Title, expectedTitle) {
return fmt.Errorf("Wrong title prefix. Expected %q, got %q", expectedTitle, artifact.Templates[0].Title)
}
return nil
}
Expand Down
1 change: 1 addition & 0 deletions builder/upcloud/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {
TemplatePrefix string `mapstructure:"template_prefix"`
StorageSize int `mapstructure:"storage_size"`
Timeout time.Duration `mapstructure:"state_timeout_duration"`
CloneZones []string `mapstructure:"clone_zones"`

ctx interpolate.Context
}
Expand Down
2 changes: 2 additions & 0 deletions builder/upcloud/config.hcl2spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type FlatConfig struct {
TemplatePrefix *string `mapstructure:"template_prefix" cty:"template_prefix"`
StorageSize *int `mapstructure:"storage_size" cty:"storage_size"`
Timeout *string `mapstructure:"state_timeout_duration" cty:"state_timeout_duration"`
CloneZones []string `mapstructure:"clone_zones" cty:"clone_zones"`
}

// FlatMapstructure returns a new FlatConfig.
Expand Down Expand Up @@ -152,6 +153,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"template_prefix": &hcldec.AttrSpec{Name: "template_prefix", Type: cty.String, Required: false},
"storage_size": &hcldec.AttrSpec{Name: "storage_size", Type: cty.Number, Required: false},
"state_timeout_duration": &hcldec.AttrSpec{Name: "state_timeout_duration", Type: cty.String, Required: false},
"clone_zones": &hcldec.AttrSpec{Name: "clone_zones", Type: cty.List(cty.String), Required: false},
}
return s
}
83 changes: 50 additions & 33 deletions builder/upcloud/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package upcloud
import (
"fmt"
"strings"
"time"

"github.com/UpCloudLtd/upcloud-go-api/upcloud"
"github.com/UpCloudLtd/upcloud-go-api/upcloud/client"
Expand All @@ -16,21 +17,30 @@ const (

type (
Driver interface {
CreateServer(string, string) (*upcloud.ServerDetails, error)
CreateServer(string, string, string, string, int) (*upcloud.ServerDetails, error)
DeleteServer(string) error
StopServer(string) error
GetStorage() (*upcloud.Storage, error)
CreateTemplate(string) (*upcloud.Storage, error)
GetStorage(string, string) (*upcloud.Storage, error)
GetServerStorage(string) (*upcloud.ServerStorageDevice, error)
CloneStorage(string, string, string) (*upcloud.Storage, error)
CreateTemplate(string, string) (*upcloud.Storage, error)
DeleteTemplate(string) error
}

driver struct {
svc *service.Service
config *Config
config *DriverConfig
}

DriverConfig struct {
Username string
Password string
Timeout time.Duration
SSHUsername string
}
)

func NewDriver(c *Config) Driver {
func NewDriver(c *DriverConfig) Driver {
client := client.New(c.Username, c.Password)
svc := service.New(client)
return &driver{
Expand All @@ -39,9 +49,9 @@ func NewDriver(c *Config) Driver {
}
}

func (d *driver) CreateServer(storageUuid, sshKeyPublic string) (*upcloud.ServerDetails, error) {
func (d *driver) CreateServer(storageUuid, zone, prefix, sshKeyPublic string, storageSize int) (*upcloud.ServerDetails, error) {
// Create server
request := d.prepareCreateRequest(storageUuid, sshKeyPublic)
request := d.prepareCreateRequest(storageUuid, zone, prefix, sshKeyPublic, storageSize)
response, err := d.svc.CreateServer(request)
if err != nil {
return nil, fmt.Errorf("Error creating server: %s", err)
Expand Down Expand Up @@ -94,26 +104,22 @@ func (d *driver) StopServer(serverUuid string) error {
return nil
}

func (d *driver) CreateTemplate(serverUuid string) (*upcloud.Storage, error) {
// get storage details
storage, err := d.getServerStorage(serverUuid)
if err != nil {
return nil, err
}

func (d *driver) CreateTemplate(serverStorageUuid, prefix string) (*upcloud.Storage, error) {
// create image
templateTitle := fmt.Sprintf("%s-%s", d.config.TemplatePrefix, GetNowString())
templateTitle := fmt.Sprintf("%s-%s", prefix, GetNowString())
response, err := d.svc.TemplatizeStorage(&request.TemplatizeStorageRequest{
UUID: storage.UUID,
UUID: serverStorageUuid,
Title: templateTitle,
})
if err != nil {
return nil, fmt.Errorf("Error creating image: %s", err)
}
return d.waitStorageOnline(response.UUID)
}

// wait for online state
func (d *driver) waitStorageOnline(storageUuid string) (*upcloud.Storage, error) {
template, err := d.svc.WaitForStorageState(&request.WaitForStorageStateRequest{
UUID: response.UUID,
UUID: storageUuid,
DesiredState: upcloud.StorageStateOnline,
Timeout: d.config.Timeout,
})
Expand All @@ -123,10 +129,8 @@ func (d *driver) CreateTemplate(serverUuid string) (*upcloud.Storage, error) {
return &template.Storage, nil
}

func (d *driver) GetStorage() (*upcloud.Storage, error) {
storageUuid := d.config.StorageUUID
storageName := d.config.StorageName

// fetch storage by uuid or name
func (d *driver) GetStorage(storageUuid, storageName string) (*upcloud.Storage, error) {
if storageUuid != "" {
storage, err := d.getStorageByUuid(storageUuid)
if err != nil {
Expand All @@ -152,6 +156,18 @@ func (d *driver) DeleteTemplate(templateUuid string) error {
})
}

func (d *driver) CloneStorage(storageUuid string, zone string, title string) (*upcloud.Storage, error) {
response, err := d.svc.CloneStorage(&request.CloneStorageRequest{
UUID: storageUuid,
Zone: zone,
Title: title,
})
if err != nil {
return nil, err
}
return d.waitStorageOnline(response.UUID)
}

func (d *driver) getStorageByUuid(storageUuid string) (*upcloud.Storage, error) {
response, err := d.svc.GetStorageDetails(&request.GetStorageDetailsRequest{
UUID: storageUuid,
Expand Down Expand Up @@ -222,7 +238,7 @@ func (d *driver) getServerDetails(serverUuid string) (*upcloud.ServerDetails, er
return response, nil
}

func (d *driver) getServerStorage(serverUuid string) (*upcloud.ServerStorageDevice, error) {
func (d *driver) GetServerStorage(serverUuid string) (*upcloud.ServerStorageDevice, error) {
details, err := d.getServerDetails(serverUuid)
if err != nil {
return nil, err
Expand All @@ -243,23 +259,23 @@ func (d *driver) getServerStorage(serverUuid string) (*upcloud.ServerStorageDevi
return &storage, nil
}

func (d *driver) prepareCreateRequest(storageUuid, sshKeyPublic string) *request.CreateServerRequest {
title := fmt.Sprintf("packer-%s-%s", d.config.TemplatePrefix, GetNowString())
hostname := d.config.TemplatePrefix
func (d *driver) prepareCreateRequest(storageUuid, zone, prefix, sshKeyPublic string, storageSize int) *request.CreateServerRequest {
title := fmt.Sprintf("packer-%s-%s", prefix, GetNowString())
hostname := prefix
titleDisk := fmt.Sprintf("%s-disk1", title)

return &request.CreateServerRequest{
request := request.CreateServerRequest{
Title: title,
Hostname: hostname,
Zone: d.config.Zone,
Zone: zone,
PasswordDelivery: request.PasswordDeliveryNone,
Plan: DefaultPlan,
StorageDevices: []request.CreateServerStorageDevice{
{
Action: request.CreateServerStorageDeviceActionClone,
Storage: storageUuid,
Title: titleDisk,
Size: d.config.StorageSize,
Size: storageSize,
Tier: upcloud.StorageTierMaxIOPS,
},
},
Expand All @@ -277,10 +293,11 @@ func (d *driver) prepareCreateRequest(storageUuid, sshKeyPublic string) *request
},
LoginUser: &request.LoginUser{
CreatePassword: "no",
Username: d.config.Comm.SSHUsername,
SSHKeys: []string{
sshKeyPublic,
},
},
}
if sshKeyPublic != "" {
request.LoginUser.Username = d.config.SSHUsername
request.LoginUser.SSHKeys = []string{sshKeyPublic}
}
return &request
}
Loading

0 comments on commit ab3c525

Please sign in to comment.