diff --git a/builder/osc/chroot/builder.go b/builder/osc/chroot/builder.go index 9a3d1d54f..6f545ee98 100644 --- a/builder/osc/chroot/builder.go +++ b/builder/osc/chroot/builder.go @@ -247,6 +247,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe RootVolumeTags: b.config.RootVolumeTags, Ctx: b.config.ctx, }, + &StepLinkVolume{}, ) // Run! diff --git a/builder/osc/chroot/step_link_volume.go b/builder/osc/chroot/step_link_volume.go new file mode 100644 index 000000000..09f00f03a --- /dev/null +++ b/builder/osc/chroot/step_link_volume.go @@ -0,0 +1,97 @@ +package chroot + +import ( + "context" + "fmt" + "strings" + + 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" +) + +// StepLinkVolume attaches the previously created volume to an +// available device location. +// +// Produces: +// device string - The location where the volume was attached. +// attach_cleanup CleanupFunc +type StepLinkVolume struct { + attached bool + volumeId string +} + +func (s *StepLinkVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + oapiconn := state.Get("oapi").(*oapi.Client) + device := state.Get("device").(string) + vm := state.Get("vm").(oapi.Vm) + ui := state.Get("ui").(packer.Ui) + volumeId := state.Get("volume_id").(string) + + // For the API call, it expects "sd" prefixed devices. + //linkVolume := strings.Replace(device, "/xvd", "/sd", 1) + linkVolume := strings.Replace(device, "/vd", "/sd", 1) + + ui.Say(fmt.Sprintf("Attaching the root volume to %s", linkVolume)) + _, err := oapiconn.POST_LinkVolume(oapi.LinkVolumeRequest{ + VmId: vm.VmId, + VolumeId: volumeId, + DeviceName: linkVolume, + }) + + if err != nil { + err := fmt.Errorf("Error attaching volume: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Mark that we attached it so we can detach it later + s.attached = true + s.volumeId = volumeId + + // Wait for the volume to become attached + err = osccommon.WaitUntilVolumeIsLinked(oapiconn, s.volumeId) + if err != nil { + err := fmt.Errorf("Error waiting for volume: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + state.Put("attach_cleanup", s) + return multistep.ActionContinue +} + +func (s *StepLinkVolume) Cleanup(state multistep.StateBag) { + ui := state.Get("ui").(packer.Ui) + if err := s.CleanupFunc(state); err != nil { + ui.Error(err.Error()) + } +} + +func (s *StepLinkVolume) CleanupFunc(state multistep.StateBag) error { + if !s.attached { + return nil + } + + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Detaching BSU volume...") + _, err := oapiconn.POST_UnlinkVolume(oapi.UnlinkVolumeRequest{VolumeId: s.volumeId}) + if err != nil { + return fmt.Errorf("Error detaching BSU volume: %s", err) + } + + s.attached = false + + // Wait for the volume to detach + err = osccommon.WaitUntilVolumeIsUnlinked(oapiconn, s.volumeId) + if err != nil { + return fmt.Errorf("Error waiting for volume: %s", err) + } + + return nil +} diff --git a/builder/osc/common/state.go b/builder/osc/common/state.go index d0fd7a2db..b211f882a 100644 --- a/builder/osc/common/state.go +++ b/builder/osc/common/state.go @@ -54,6 +54,18 @@ func WaitUntilVolumeAvailable(conn *oapi.Client, volumeID string) error { return <-errCh } +func WaitUntilVolumeIsLinked(conn *oapi.Client, volumeID string) error { + errCh := make(chan error, 1) + go waitForState(errCh, "attached", waitUntilVolumeLinkedStateFunc(conn, volumeID)) + return <-errCh +} + +func WaitUntilVolumeIsUnlinked(conn *oapi.Client, volumeID string) error { + errCh := make(chan error, 1) + go waitForState(errCh, "dettached", waitUntilVolumeUnLinkedStateFunc(conn, volumeID)) + 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() @@ -95,6 +107,68 @@ func waitUntilVmStateFunc(conn *oapi.Client, id string) stateRefreshFunc { } } +func waitUntilVolumeLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc { + return func() (string, error) { + log.Printf("[Debug] Check if volume with id %s exists", id) + resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{ + Filters: oapi.FiltersVolume{ + VolumeIds: []string{id}, + }, + }) + + if err != nil { + return "", err + } + + if resp.OK == nil { + return "", fmt.Errorf("Vm with ID %s. Not Found", id) + } + + log.Printf("[Debug] Read Response %+v", resp.OK) + + if len(resp.OK.Volumes) == 0 { + return "pending", nil + } + + if len(resp.OK.Volumes[0].LinkedVolumes) == 0 { + return "pending", nil + } + + return resp.OK.Volumes[0].LinkedVolumes[0].State, nil + } +} + +func waitUntilVolumeUnLinkedStateFunc(conn *oapi.Client, id string) stateRefreshFunc { + return func() (string, error) { + log.Printf("[Debug] Check if volume with id %s exists", id) + resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{ + Filters: oapi.FiltersVolume{ + VolumeIds: []string{id}, + }, + }) + + if err != nil { + return "", err + } + + if resp.OK == nil { + return "", fmt.Errorf("Vm with ID %s. Not Found", id) + } + + log.Printf("[Debug] Read Response %+v", resp.OK) + + if len(resp.OK.Volumes) == 0 { + return "pending", nil + } + + if len(resp.OK.Volumes[0].LinkedVolumes) == 0 { + return "dettached", nil + } + + return "failed", nil + } +} + func waitUntilSnapshotStateFunc(conn *oapi.Client, id string) stateRefreshFunc { return func() (string, error) { log.Printf("[Debug] Check if Snapshot with id %s exists", id)