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 {
// 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)
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
}

View File

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

View File

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

View File

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

View File

@ -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())

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 (
"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))

View File

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

View File

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

View File

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

View File

@ -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",