From 1fb13cc23eeebf7e3c57f47657d663dc6a13e21c Mon Sep 17 00:00:00 2001 From: Edouard BONLIEU Date: Tue, 11 Apr 2017 12:19:28 +0200 Subject: [PATCH] Add image creation from snapshot Rename organization_id / access_key Update test / doc --- builder/scaleway/artifact.go | 25 ++++++--- builder/scaleway/artifact_test.go | 8 +-- builder/scaleway/builder.go | 5 +- builder/scaleway/builder_test.go | 12 ++--- builder/scaleway/config.go | 16 ++++-- builder/scaleway/step_create_image.go | 53 +++++++++++++++++++ builder/scaleway/step_create_server.go | 11 ++-- builder/scaleway/step_shutdown.go | 6 +-- builder/scaleway/step_snapshot.go | 13 +---- builder/scaleway/step_terminate.go | 4 +- website/source/docs/builders/scaleway.html.md | 15 ++++-- 11 files changed, 122 insertions(+), 46 deletions(-) create mode 100644 builder/scaleway/step_create_image.go diff --git a/builder/scaleway/artifact.go b/builder/scaleway/artifact.go index fe1efb96f..b5aea88da 100644 --- a/builder/scaleway/artifact.go +++ b/builder/scaleway/artifact.go @@ -8,11 +8,17 @@ import ( ) type Artifact struct { + // The name of the image + imageName string + + // The ID of the image + imageID string + // The name of the snapshot snapshotName string // The ID of the snapshot - snapshotId string + snapshotID string // The name of the region regionName string @@ -31,11 +37,12 @@ func (*Artifact) Files() []string { } func (a *Artifact) Id() string { - return fmt.Sprintf("%s:%s", a.regionName, a.snapshotId) + return fmt.Sprintf("%s:%s", a.regionName, a.imageID) } func (a *Artifact) String() string { - return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in region '%v'", a.snapshotName, a.snapshotId, a.regionName) + return fmt.Sprintf("An image was created: '%v' (ID: %v) in region '%v' based on snapshot '%v' (ID: %v)", + a.imageName, a.imageID, a.regionName, a.snapshotName, a.snapshotID) } func (a *Artifact) State(name string) interface{} { @@ -43,7 +50,13 @@ func (a *Artifact) State(name string) interface{} { } func (a *Artifact) Destroy() error { - log.Printf("Destroying image: %s (%s)", a.snapshotId, a.snapshotName) - err := a.client.DeleteSnapshot(a.snapshotId) - return err + log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName) + if err := a.client.DeleteImage(a.imageID); err != nil { + return err + } + log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName) + if err := a.client.DeleteSnapshot(a.snapshotID); err != nil { + return err + } + return nil } diff --git a/builder/scaleway/artifact_test.go b/builder/scaleway/artifact_test.go index 8805ad686..8f158ef5f 100644 --- a/builder/scaleway/artifact_test.go +++ b/builder/scaleway/artifact_test.go @@ -15,8 +15,8 @@ func TestArtifact_Impl(t *testing.T) { } func TestArtifactId(t *testing.T) { - a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil} - expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1c" + a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil} + expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1d" if a.Id() != expected { t.Fatalf("artifact ID should match: %v", expected) @@ -24,8 +24,8 @@ func TestArtifactId(t *testing.T) { } func TestArtifactString(t *testing.T) { - a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil} - expected := "A snapshot was created: 'packer-foobar' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c) in region 'ams1'" + a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil} + expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in region 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)" if a.String() != expected { t.Fatalf("artifact string should match: %v", expected) diff --git a/builder/scaleway/builder.go b/builder/scaleway/builder.go index 2c92e31b6..e6265395a 100644 --- a/builder/scaleway/builder.go +++ b/builder/scaleway/builder.go @@ -56,6 +56,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(common.StepProvision), new(stepShutdown), new(stepSnapshot), + new(stepImage), new(stepTerminate), } @@ -72,8 +73,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe } artifact := &Artifact{ + imageName: state.Get("image_name").(string), + imageID: state.Get("image_id").(string), snapshotName: state.Get("snapshot_name").(string), - snapshotId: state.Get("snapshot_id").(string), + snapshotID: state.Get("snapshot_id").(string), regionName: state.Get("region").(string), client: client, } diff --git a/builder/scaleway/builder_test.go b/builder/scaleway/builder_test.go index ff83e1556..230c9c0c6 100644 --- a/builder/scaleway/builder_test.go +++ b/builder/scaleway/builder_test.go @@ -9,12 +9,12 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "api_organization": "foo", - "api_token": "bar", - "region": "ams1", - "commercial_type": "VC1S", - "ssh_username": "root", - "image": "image-uuid", + "api_access_key": "foo", + "api_token": "bar", + "region": "ams1", + "commercial_type": "VC1S", + "ssh_username": "root", + "image": "image-uuid", } } diff --git a/builder/scaleway/config.go b/builder/scaleway/config.go index 040b39891..f965e1ce0 100644 --- a/builder/scaleway/config.go +++ b/builder/scaleway/config.go @@ -19,13 +19,14 @@ type Config struct { Comm communicator.Config `mapstructure:",squash"` Token string `mapstructure:"api_token"` - Organization string `mapstructure:"api_organization"` + Organization string `mapstructure:"api_access_key"` Region string `mapstructure:"region"` Image string `mapstructure:"image"` CommercialType string `mapstructure:"commercial_type"` SnapshotName string `mapstructure:"snapshot_name"` + ImageName string `mapstructure:"image_name"` ServerName string `mapstructure:"server_name"` UserAgent string @@ -53,7 +54,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.UserAgent = "Packer - Scaleway builder" if c.Organization == "" { - c.Organization = os.Getenv("SCALEWAY_API_ORGANIZATION") + c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY") } if c.Token == "" { @@ -61,7 +62,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { } if c.SnapshotName == "" { - def, err := interpolate.Render("packer-{{timestamp}}", nil) + def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil) if err != nil { panic(err) } @@ -69,6 +70,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { c.SnapshotName = def } + if c.ImageName == "" { + def, err := interpolate.Render("image-packer-{{timestamp}}", nil) + if err != nil { + panic(err) + } + + c.ImageName = def + } + if c.ServerName == "" { // Default to packer-[time-ordered-uuid] c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) diff --git a/builder/scaleway/step_create_image.go b/builder/scaleway/step_create_image.go new file mode 100644 index 000000000..313f2e421 --- /dev/null +++ b/builder/scaleway/step_create_image.go @@ -0,0 +1,53 @@ +package scaleway + +import ( + "fmt" + "log" + + "github.com/hashicorp/packer/packer" + "github.com/mitchellh/multistep" + "github.com/scaleway/scaleway-cli/pkg/api" +) + +type stepImage struct{} + +func (s *stepImage) Run(state multistep.StateBag) multistep.StepAction { + client := state.Get("client").(*api.ScalewayAPI) + ui := state.Get("ui").(packer.Ui) + c := state.Get("config").(Config) + snapshotID := state.Get("snapshot_id").(string) + bootscriptID := "" + + ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName)) + + image, err := client.GetImage(c.Image) + if err != nil { + err := fmt.Errorf("Error getting initial image info: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if image.DefaultBootscript != nil { + bootscriptID = image.DefaultBootscript.Identifier + } + + imageID, err := client.PostImage(snapshotID, c.ImageName, bootscriptID, image.Arch) + if err != nil { + err := fmt.Errorf("Error creating image: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Printf("Image ID: %s", imageID) + state.Put("image_id", imageID) + state.Put("image_name", c.ImageName) + state.Put("region", c.Region) + + return multistep.ActionContinue +} + +func (s *stepImage) Cleanup(state multistep.StateBag) { + // no cleanup +} diff --git a/builder/scaleway/step_create_server.go b/builder/scaleway/step_create_server.go index c961b6110..e1e8b88c6 100644 --- a/builder/scaleway/step_create_server.go +++ b/builder/scaleway/step_create_server.go @@ -2,14 +2,15 @@ package scaleway import ( "fmt" + "strings" + "github.com/hashicorp/packer/packer" "github.com/mitchellh/multistep" "github.com/scaleway/scaleway-cli/pkg/api" - "strings" ) type stepCreateServer struct { - serverId string + serverID string } func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction { @@ -37,7 +38,7 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - s.serverId = server + s.serverID = server state.Put("server_id", server) @@ -45,7 +46,7 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction { } func (s *stepCreateServer) Cleanup(state multistep.StateBag) { - if s.serverId != "" { + if s.serverID != "" { return } @@ -53,7 +54,7 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) ui.Say("Destroying server...") - err := client.PostServerAction(s.serverId, "terminate") + err := client.PostServerAction(s.serverID, "terminate") if err != nil { ui.Error(fmt.Sprintf( "Error destroying server. Please destroy it manually: %s", err)) diff --git a/builder/scaleway/step_shutdown.go b/builder/scaleway/step_shutdown.go index 8d246974d..b8e45be51 100644 --- a/builder/scaleway/step_shutdown.go +++ b/builder/scaleway/step_shutdown.go @@ -13,11 +13,11 @@ type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*api.ScalewayAPI) ui := state.Get("ui").(packer.Ui) - serverId := state.Get("server_id").(string) + serverID := state.Get("server_id").(string) ui.Say("Shutting down server...") - err := client.PostServerAction(serverId, "poweroff") + err := client.PostServerAction(serverID, "poweroff") if err != nil { err := fmt.Errorf("Error stopping server: %s", err) @@ -26,7 +26,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - _, err = api.WaitForServerState(client, serverId, "stopped") + _, err = api.WaitForServerState(client, serverID, "stopped") if err != nil { err := fmt.Errorf("Error shutting down server: %s", err) diff --git a/builder/scaleway/step_snapshot.go b/builder/scaleway/step_snapshot.go index 26bfa6934..20301a444 100644 --- a/builder/scaleway/step_snapshot.go +++ b/builder/scaleway/step_snapshot.go @@ -15,10 +15,10 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*api.ScalewayAPI) ui := state.Get("ui").(packer.Ui) c := state.Get("config").(Config) - volumeId := state.Get("root_volume_id").(string) + volumeID := state.Get("root_volume_id").(string) ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName)) - snapshot, err := client.PostSnapshot(volumeId, c.SnapshotName) + snapshot, err := client.PostSnapshot(volumeID, c.SnapshotName) if err != nil { err := fmt.Errorf("Error creating snapshot: %s", err) state.Put("error", err) @@ -26,15 +26,6 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - log.Printf("Looking up snapshot ID for snapshot: %s", c.SnapshotName) - _, err = client.GetSnapshot(snapshot) - if err != nil { - err := fmt.Errorf("Error looking up snapshot ID: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - log.Printf("Snapshot ID: %s", snapshot) state.Put("snapshot_id", snapshot) state.Put("snapshot_name", c.SnapshotName) diff --git a/builder/scaleway/step_terminate.go b/builder/scaleway/step_terminate.go index 0951b6a94..154750931 100644 --- a/builder/scaleway/step_terminate.go +++ b/builder/scaleway/step_terminate.go @@ -13,11 +13,11 @@ type stepTerminate struct{} func (s *stepTerminate) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*api.ScalewayAPI) ui := state.Get("ui").(packer.Ui) - serverId := state.Get("server_id").(string) + serverID := state.Get("server_id").(string) ui.Say("Terminating server...") - err := client.DeleteServerForce(serverId) + err := client.DeleteServerForce(serverID) if err != nil { err := fmt.Errorf("Error terminating server: %s", err) diff --git a/website/source/docs/builders/scaleway.html.md b/website/source/docs/builders/scaleway.html.md index 1df9dd6ec..4e0a513fc 100644 --- a/website/source/docs/builders/scaleway.html.md +++ b/website/source/docs/builders/scaleway.html.md @@ -3,7 +3,7 @@ layout: docs sidebar_current: docs-builders-scaleway page_title: Scaleway - Builders description: |- - The Scaleway Packer builder is able to create new snapshots for use with + The Scaleway Packer builder is able to create new images for use with Scaleway BareMetal and Virtual cloud server. The builder takes a source image, runs any provisioning necessary on the image after launching it, then snapshots it into a reusable image. This reusable image can then be used as the foundation of new servers @@ -15,7 +15,7 @@ description: |- Type: `scaleway` -The `scaleway` Packer builder is able to create new snapshots for use with +The `scaleway` Packer builder is able to create new images for use with [Scaleway](https://www.scaleway.com). The builder takes a source image, runs any provisioning necessary on the image after launching it, then snapshots it into a reusable image. This reusable image can then be used as the foundation @@ -36,13 +36,15 @@ builder. ### Required: -- `api_organization` (string) - The organization ID to use to access your account. +- `api_access_key` (string) - The api_access_key to use to access your account. It can also be specified via - environment variable `SCALEWAY_API_ORGANIZATION`. + environment variable `SCALEWAY_API_ACCESS_KEY`. + Your access key is available in the ["Credentials" section](https://cloud.scaleway.com/#/credentials) of the control panel. - `api_token` (string) - The organization TOKEN to use to access your account. It can also be specified via environment variable `SCALEWAY_API_TOKEN`. + Your tokens are available in the ["Credentials" section](https://cloud.scaleway.com/#/credentials) of the control panel. - `image` (string) - The UUID of the base image to use. This is the image that will be used to launch a new server and provision it. See @@ -60,6 +62,9 @@ builder. - `server_name` (string) - The name assigned to the server. +- `image_name` (string) - The name of the resulting image that will + appear in your account. + - `snapshot_name` (string) - The name of the resulting snapshot that will appear in your account. @@ -71,7 +76,7 @@ access tokens: ```json { "type": "scaleway", - "api_organization": "YOUR ORGANIZATION KEY", + "api_access_key": "YOUR API ACCESS KEY", "api_token": "YOUR TOKEN", "image": "f01f8a48-c026-48ac-9771-a70eaac0890e", "region": "par1",