Add image creation from snapshot

Rename organization_id / access_key
Update test / doc
This commit is contained in:
Edouard BONLIEU 2017-04-11 12:19:28 +02:00 committed by Matthew Hooker
parent 9b611af7e6
commit 1fb13cc23e
No known key found for this signature in database
GPG Key ID: 7B5F933D9CE8C6A1
11 changed files with 122 additions and 46 deletions

View File

@ -8,11 +8,17 @@ import (
) )
type Artifact struct { type Artifact struct {
// The name of the image
imageName string
// The ID of the image
imageID string
// The name of the snapshot // The name of the snapshot
snapshotName string snapshotName string
// The ID of the snapshot // The ID of the snapshot
snapshotId string snapshotID string
// The name of the region // The name of the region
regionName string regionName string
@ -31,11 +37,12 @@ func (*Artifact) Files() []string {
} }
func (a *Artifact) Id() 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 { 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{} { func (a *Artifact) State(name string) interface{} {
@ -43,7 +50,13 @@ func (a *Artifact) State(name string) interface{} {
} }
func (a *Artifact) Destroy() error { func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %s (%s)", a.snapshotId, a.snapshotName) log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName)
err := a.client.DeleteSnapshot(a.snapshotId) if err := a.client.DeleteImage(a.imageID); err != nil {
return err 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
}

View File

@ -15,8 +15,8 @@ func TestArtifact_Impl(t *testing.T) {
} }
func TestArtifactId(t *testing.T) { func TestArtifactId(t *testing.T) {
a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil} 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-cf406b10dd1c" expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1d"
if a.Id() != expected { if a.Id() != expected {
t.Fatalf("artifact ID should match: %v", expected) t.Fatalf("artifact ID should match: %v", expected)
@ -24,8 +24,8 @@ func TestArtifactId(t *testing.T) {
} }
func TestArtifactString(t *testing.T) { func TestArtifactString(t *testing.T) {
a := &Artifact{"packer-foobar", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil} a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
expected := "A snapshot was created: 'packer-foobar' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c) in region 'ams1'" 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 { if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected) t.Fatalf("artifact string should match: %v", expected)

View File

@ -56,6 +56,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(common.StepProvision), new(common.StepProvision),
new(stepShutdown), new(stepShutdown),
new(stepSnapshot), new(stepSnapshot),
new(stepImage),
new(stepTerminate), new(stepTerminate),
} }
@ -72,8 +73,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} }
artifact := &Artifact{ artifact := &Artifact{
imageName: state.Get("image_name").(string),
imageID: state.Get("image_id").(string),
snapshotName: state.Get("snapshot_name").(string), snapshotName: state.Get("snapshot_name").(string),
snapshotId: state.Get("snapshot_id").(string), snapshotID: state.Get("snapshot_id").(string),
regionName: state.Get("region").(string), regionName: state.Get("region").(string),
client: client, client: client,
} }

View File

@ -9,7 +9,7 @@ import (
func testConfig() map[string]interface{} { func testConfig() map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"api_organization": "foo", "api_access_key": "foo",
"api_token": "bar", "api_token": "bar",
"region": "ams1", "region": "ams1",
"commercial_type": "VC1S", "commercial_type": "VC1S",

View File

@ -19,13 +19,14 @@ type Config struct {
Comm communicator.Config `mapstructure:",squash"` Comm communicator.Config `mapstructure:",squash"`
Token string `mapstructure:"api_token"` Token string `mapstructure:"api_token"`
Organization string `mapstructure:"api_organization"` Organization string `mapstructure:"api_access_key"`
Region string `mapstructure:"region"` Region string `mapstructure:"region"`
Image string `mapstructure:"image"` Image string `mapstructure:"image"`
CommercialType string `mapstructure:"commercial_type"` CommercialType string `mapstructure:"commercial_type"`
SnapshotName string `mapstructure:"snapshot_name"` SnapshotName string `mapstructure:"snapshot_name"`
ImageName string `mapstructure:"image_name"`
ServerName string `mapstructure:"server_name"` ServerName string `mapstructure:"server_name"`
UserAgent string UserAgent string
@ -53,7 +54,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
c.UserAgent = "Packer - Scaleway builder" c.UserAgent = "Packer - Scaleway builder"
if c.Organization == "" { if c.Organization == "" {
c.Organization = os.Getenv("SCALEWAY_API_ORGANIZATION") c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY")
} }
if c.Token == "" { if c.Token == "" {
@ -61,7 +62,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
} }
if c.SnapshotName == "" { if c.SnapshotName == "" {
def, err := interpolate.Render("packer-{{timestamp}}", nil) def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -69,6 +70,15 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
c.SnapshotName = def 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 == "" { if c.ServerName == "" {
// Default to packer-[time-ordered-uuid] // Default to packer-[time-ordered-uuid]
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())

View File

@ -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
}

View File

@ -2,14 +2,15 @@ package scaleway
import ( import (
"fmt" "fmt"
"strings"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/scaleway/scaleway-cli/pkg/api" "github.com/scaleway/scaleway-cli/pkg/api"
"strings"
) )
type stepCreateServer struct { type stepCreateServer struct {
serverId string serverID string
} }
func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction { 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 return multistep.ActionHalt
} }
s.serverId = server s.serverID = server
state.Put("server_id", 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) { func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
if s.serverId != "" { if s.serverID != "" {
return return
} }
@ -53,7 +54,7 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Destroying server...") ui.Say("Destroying server...")
err := client.PostServerAction(s.serverId, "terminate") err := client.PostServerAction(s.serverID, "terminate")
if err != nil { if err != nil {
ui.Error(fmt.Sprintf( ui.Error(fmt.Sprintf(
"Error destroying server. Please destroy it manually: %s", err)) "Error destroying server. Please destroy it manually: %s", err))

View File

@ -13,11 +13,11 @@ type stepShutdown struct{}
func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI) client := state.Get("client").(*api.ScalewayAPI)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
serverId := state.Get("server_id").(string) serverID := state.Get("server_id").(string)
ui.Say("Shutting down server...") ui.Say("Shutting down server...")
err := client.PostServerAction(serverId, "poweroff") err := client.PostServerAction(serverID, "poweroff")
if err != nil { if err != nil {
err := fmt.Errorf("Error stopping server: %s", err) err := fmt.Errorf("Error stopping server: %s", err)
@ -26,7 +26,7 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
_, err = api.WaitForServerState(client, serverId, "stopped") _, err = api.WaitForServerState(client, serverID, "stopped")
if err != nil { if err != nil {
err := fmt.Errorf("Error shutting down server: %s", err) err := fmt.Errorf("Error shutting down server: %s", err)

View File

@ -15,10 +15,10 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI) client := state.Get("client").(*api.ScalewayAPI)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(Config) 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)) 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 { if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err) err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err) state.Put("error", err)
@ -26,15 +26,6 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt 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) log.Printf("Snapshot ID: %s", snapshot)
state.Put("snapshot_id", snapshot) state.Put("snapshot_id", snapshot)
state.Put("snapshot_name", c.SnapshotName) state.Put("snapshot_name", c.SnapshotName)

View File

@ -13,11 +13,11 @@ type stepTerminate struct{}
func (s *stepTerminate) Run(state multistep.StateBag) multistep.StepAction { func (s *stepTerminate) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI) client := state.Get("client").(*api.ScalewayAPI)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
serverId := state.Get("server_id").(string) serverID := state.Get("server_id").(string)
ui.Say("Terminating server...") ui.Say("Terminating server...")
err := client.DeleteServerForce(serverId) err := client.DeleteServerForce(serverID)
if err != nil { if err != nil {
err := fmt.Errorf("Error terminating server: %s", err) err := fmt.Errorf("Error terminating server: %s", err)

View File

@ -3,7 +3,7 @@ layout: docs
sidebar_current: docs-builders-scaleway sidebar_current: docs-builders-scaleway
page_title: Scaleway - Builders page_title: Scaleway - Builders
description: |- 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 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 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 image. This reusable image can then be used as the foundation of new servers
@ -15,7 +15,7 @@ description: |-
Type: `scaleway` 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, [Scaleway](https://www.scaleway.com). The builder takes a source image,
runs any provisioning necessary on the image after launching it, then snapshots 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 it into a reusable image. This reusable image can then be used as the foundation
@ -36,13 +36,15 @@ builder.
### Required: ### 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 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. - `api_token` (string) - The organization TOKEN to use to access your account.
It can also be specified via It can also be specified via
environment variable `SCALEWAY_API_TOKEN`. 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` (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 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. - `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 - `snapshot_name` (string) - The name of the resulting snapshot that will
appear in your account. appear in your account.
@ -71,7 +76,7 @@ access tokens:
```json ```json
{ {
"type": "scaleway", "type": "scaleway",
"api_organization": "YOUR ORGANIZATION KEY", "api_access_key": "YOUR API ACCESS KEY",
"api_token": "YOUR TOKEN", "api_token": "YOUR TOKEN",
"image": "f01f8a48-c026-48ac-9771-a70eaac0890e", "image": "f01f8a48-c026-48ac-9771-a70eaac0890e",
"region": "par1", "region": "par1",