diff --git a/builder/osc/chroot/builder.go b/builder/osc/chroot/builder.go index 23ea5d64f..d4340ce44 100644 --- a/builder/osc/chroot/builder.go +++ b/builder/osc/chroot/builder.go @@ -262,6 +262,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &StepMountExtra{}, &StepCopyFiles{}, &StepChrootProvision{}, + &StepEarlyCleanup{}, + &StepSnapshot{}, ) // Run! diff --git a/builder/osc/chroot/step_early_cleanup.go b/builder/osc/chroot/step_early_cleanup.go new file mode 100644 index 000000000..34e7817df --- /dev/null +++ b/builder/osc/chroot/step_early_cleanup.go @@ -0,0 +1,39 @@ +package chroot + +import ( + "context" + "fmt" + "log" + + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +// StepEarlyCleanup performs some of the cleanup steps early in order to +// prepare for snapshotting and creating an AMI. +type StepEarlyCleanup struct{} + +func (s *StepEarlyCleanup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + cleanupKeys := []string{ + "copy_files_cleanup", + "mount_extra_cleanup", + "mount_device_cleanup", + "attach_cleanup", + } + + for _, key := range cleanupKeys { + c := state.Get(key).(Cleanup) + log.Printf("Running cleanup func: %s", key) + if err := c.CleanupFunc(state); err != nil { + err := fmt.Errorf("Error cleaning up: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *StepEarlyCleanup) Cleanup(state multistep.StateBag) {} diff --git a/builder/osc/chroot/step_snapshot.go b/builder/osc/chroot/step_snapshot.go new file mode 100644 index 000000000..3df493728 --- /dev/null +++ b/builder/osc/chroot/step_snapshot.go @@ -0,0 +1,81 @@ +package chroot + +import ( + "context" + "fmt" + "time" + + osccommon "github.com/hashicorp/packer/builder/osc/common" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/outscale/osc-go/oapi" +) + +// StepSnapshot creates a snapshot of the created volume. +// +// Produces: +// snapshot_id string - ID of the created snapshot +type StepSnapshot struct { + snapshotId string +} + +func (s *StepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + volumeId := state.Get("volume_id").(string) + + ui.Say("Creating snapshot...") + description := fmt.Sprintf("Packer: %s", time.Now().String()) + + createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{ + VolumeId: volumeId, + Description: description, + }) + if err != nil { + err := fmt.Errorf("Error creating snapshot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the snapshot ID so we can delete it later + s.snapshotId = createSnapResp.OK.Snapshot.SnapshotId + ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId)) + + // Wait for the snapshot to be ready + err = osccommon.WaitUntilSnapshotDone(oapiconn, s.snapshotId) + if err != nil { + err := fmt.Errorf("Error waiting for snapshot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state.Put("snapshot_id", s.snapshotId) + + snapshots := map[string][]string{ + oapiconn.GetConfig().Region: {s.snapshotId}, + } + state.Put("snapshots", snapshots) + + return multistep.ActionContinue +} + +func (s *StepSnapshot) Cleanup(state multistep.StateBag) { + if s.snapshotId == "" { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + ui.Say("Removing snapshot since we cancelled or halted...") + _, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: s.snapshotId}) + if err != nil { + ui.Error(fmt.Sprintf("Error: %s", err)) + } + } +} diff --git a/builder/osc/common/state.go b/builder/osc/common/state.go index b211f882a..2cc475123 100644 --- a/builder/osc/common/state.go +++ b/builder/osc/common/state.go @@ -66,6 +66,12 @@ func WaitUntilVolumeIsUnlinked(conn *oapi.Client, volumeID string) error { return <-errCh } +func WaitUntilSnapshotDone(conn *oapi.Client, snapshotID string) error { + errCh := make(chan error, 1) + go waitForState(errCh, "completed", waitUntilSnapshotDoneStateFunc(conn, snapshotID)) + return <-errCh +} + func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error { err := common.Retry(2, 2, 0, func(_ uint) (bool, error) { state, err := refresh() @@ -254,6 +260,37 @@ func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc { } } +func waitUntilSnapshotDoneStateFunc(conn *oapi.Client, id string) stateRefreshFunc { + return func() (string, error) { + log.Printf("[Debug] Check if Snapshot with id %s exists", id) + resp, err := conn.POST_ReadSnapshots(oapi.ReadSnapshotsRequest{ + Filters: oapi.FiltersSnapshot{ + SnapshotIds: []string{id}, + }, + }) + + log.Printf("[Debug] Read Response %+v", resp.OK) + + if err != nil { + return "", err + } + + if resp.OK == nil { + return "", fmt.Errorf("Snapshot with ID %s. Not Found", id) + } + + if len(resp.OK.Snapshots) == 0 { + return "", fmt.Errorf("Snapshot with ID %s. Not Found", id) + } + + if resp.OK.Snapshots[0].State == "error" { + return resp.OK.Snapshots[0].State, fmt.Errorf("Snapshot (%s) creation is failed", id) + } + + return resp.OK.Snapshots[0].State, nil + } +} + func volumeWaitFunc(conn *oapi.Client, id string) stateRefreshFunc { return func() (string, error) { log.Printf("[Debug] Check if SvolumeG with id %s exists", id)