Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create image using backup action #89

Merged
merged 6 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions builder/scaleway/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@ import (
"github.com/scaleway/scaleway-sdk-go/scw"
)

type ArtifactSnapshot struct {
// The ID of the snapshot
ID string
// The name of the snapshot
Name string
}

func (snap ArtifactSnapshot) String() string {
return fmt.Sprintf("(%s: %s)", snap.Name, snap.ID)
}

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
// Snapshots used by the generated image
snapshots []ArtifactSnapshot

// The name of the zone
zoneName string
Expand All @@ -47,8 +55,8 @@ func (a *Artifact) Id() string {
}

func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: '%v' (ID: %v) in zone '%v' based on snapshot '%v' (ID: %v)",
a.imageName, a.imageID, a.zoneName, a.snapshotName, a.snapshotID)
return fmt.Sprintf("An image was created: '%v' (ID: %v) in zone '%v' based on snapshots %v",
a.imageName, a.imageID, a.zoneName, a.snapshots)
}

func (a *Artifact) State(name string) interface{} {
Expand Down Expand Up @@ -78,12 +86,14 @@ func (a *Artifact) Destroy() error {
return err
}

log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName)
err = instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{
SnapshotID: a.snapshotID,
})
if err != nil {
return err
log.Printf("Destroying snapshots: %v", a.snapshots)
for _, snapshot := range a.snapshots {
err = instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{
SnapshotID: snapshot.ID,
})
if err != nil {
return err
}
}

return nil
Expand Down
32 changes: 28 additions & 4 deletions builder/scaleway/artifact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ func TestArtifact_Impl(t *testing.T) {

func TestArtifactId(t *testing.T) {
generatedData := make(map[string]interface{})
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil, generatedData}
a := &Artifact{
"packer-foobar-image",
"cc586e45-5156-4f71-b223-cf406b10dd1d",
[]ArtifactSnapshot{{
"packer-foobar-snapshot",
"cc586e45-5156-4f71-b223-cf406b10dd1c",
}},
"ams1",
nil,
generatedData}
expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1d"

if a.Id() != expected {
Expand All @@ -26,11 +35,26 @@ func TestArtifactId(t *testing.T) {

func TestArtifactString(t *testing.T) {
generatedData := make(map[string]interface{})
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil, generatedData}
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in zone 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
a := &Artifact{
"packer-foobar-image",
"cc586e45-5156-4f71-b223-cf406b10dd1d",
[]ArtifactSnapshot{
{
"cc586e45-5156-4f71-b223-cf406b10dd1c",
"packer-foobar-snapshot",
},
{
"cc586e45-5156-4f71-b223-cf406b10dd1e",
"packer-foobar-snapshot2",
},
},
"ams1",
nil,
generatedData}
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in zone 'ams1' based on snapshots [(packer-foobar-snapshot: cc586e45-5156-4f71-b223-cf406b10dd1c) (packer-foobar-snapshot2: cc586e45-5156-4f71-b223-cf406b10dd1e)]"

if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
t.Fatalf("artifact string (%v) should match: %v", a.String(), expected)
}
}

Expand Down
18 changes: 8 additions & 10 deletions builder/scaleway/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
new(stepWaitUserData),
new(stepCleanupMachineData),
new(stepShutdown),
new(stepSnapshot),
new(stepImage),
new(stepBackup),
}

if *acceptanceTests {
Expand All @@ -118,18 +117,17 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
return nil, errors.New("Build was halted.")
}

if _, ok := state.GetOk("snapshot_name"); !ok {
if _, ok := state.GetOk("snapshots"); !ok {
return nil, errors.New("Cannot find snapshot_name in state.")
}

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),
zoneName: b.config.Zone,
client: client,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
imageName: state.Get("image_name").(string),
imageID: state.Get("image_id").(string),
snapshots: state.Get("snapshots").([]ArtifactSnapshot),
zoneName: b.config.Zone,
client: client,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}

return artifact, nil
Expand Down
106 changes: 106 additions & 0 deletions builder/scaleway/step_backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package scaleway

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

type stepBackup struct{}

func (s *stepBackup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packersdk.Ui)
c := state.Get("config").(*Config)
server := state.Get("server").(*instance.Server)

ui.Say(fmt.Sprintf("Backing up server to image: %v", c.ImageName))

actionResp, err := instanceAPI.ServerAction(&instance.ServerActionRequest{
ServerID: server.ID,
Action: instance.ServerActionBackup,
Name: &c.ImageName,
Volumes: backupVolumesFromServer(server),
Zone: scw.Zone(c.Zone),
}, scw.WithContext(ctx))
if err != nil {
err := fmt.Errorf("failed to backup server: %w", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

imageID, err := imageIDFromBackupResult(actionResp.Task.HrefResult)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

image, err := instanceAPI.WaitForImage(&instance.WaitForImageRequest{
ImageID: imageID,
Zone: scw.Zone(c.Zone),
Timeout: &c.ImageCreationTimeout,
}, scw.WithContext(ctx))
if err != nil {
err := fmt.Errorf("failed to fetch generated image: %w", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

state.Put("snapshots", artifactSnapshotFromImage(image))
state.Put("image_id", image.ID)
state.Put("image_name", c.ImageName)
state.Put("region", c.Zone) // Deprecated
state.Put("zone", c.Zone)

return multistep.ActionContinue
}

func (s *stepBackup) Cleanup(_ multistep.StateBag) {
// no cleanup
}

func artifactSnapshotFromImage(image *instance.Image) []ArtifactSnapshot {
snapshots := []ArtifactSnapshot{
{
ID: image.RootVolume.ID,
Name: image.RootVolume.Name,
},
}
for _, extraVolume := range image.ExtraVolumes {
snapshots = append(snapshots, ArtifactSnapshot{
ID: extraVolume.ID,
Name: extraVolume.Name,
})
}
return snapshots
}

func backupVolumesFromServer(server *instance.Server) map[string]*instance.ServerActionRequestVolumeBackupTemplate {
backupVolumes := map[string]*instance.ServerActionRequestVolumeBackupTemplate{}

for _, volume := range server.Volumes {
backupVolumes[volume.ID] = &instance.ServerActionRequestVolumeBackupTemplate{
VolumeType: instance.SnapshotVolumeTypeUnified,
}
}
return backupVolumes
}

func imageIDFromBackupResult(hrefResult string) (string, error) {
// HrefResult format is /images/<uuid>
hrefSplit := strings.Split(hrefResult, "/")
if len(hrefSplit) != 3 {
return "", fmt.Errorf("failed to parse backup request response (%s)", hrefResult)
}
imageID := hrefSplit[2]

return imageID, nil
}
107 changes: 0 additions & 107 deletions builder/scaleway/step_create_image.go

This file was deleted.

1 change: 1 addition & 0 deletions builder/scaleway/step_server_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (s *stepServerInfo) Run(ctx context.Context, state multistep.StateBag) mult

state.Put("server_ip", instanceResp.PublicIP.Address.String())
state.Put("root_volume_id", instanceResp.Volumes["0"].ID)
state.Put("server", instanceResp)

return multistep.ActionContinue
}
Expand Down
Loading