diff --git a/builder/osc/bsusurrogate/builder.go b/builder/osc/bsusurrogate/builder.go index f97fc08f8..a0e16f00e 100644 --- a/builder/osc/bsusurrogate/builder.go +++ b/builder/osc/bsusurrogate/builder.go @@ -130,7 +130,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe //VMStep - //omiDevices := b.config.BuildOMIDevices() + omiDevices := b.config.BuildOMIDevices() launchDevices := b.config.BuildLaunchDevices() steps := []multistep.Step{ @@ -222,6 +222,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe OMIName: b.config.OMIName, Regions: b.config.OMIRegions, }, + &StepRegisterOMI{ + RootDevice: b.config.RootDevice, + OMIDevices: omiDevices, + LaunchDevices: launchDevices, + }, } b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) diff --git a/builder/osc/bsusurrogate/step_register_omi.go b/builder/osc/bsusurrogate/step_register_omi.go new file mode 100644 index 000000000..1dd4c1f00 --- /dev/null +++ b/builder/osc/bsusurrogate/step_register_omi.go @@ -0,0 +1,148 @@ +package bsusurrogate + +import ( + "context" + "fmt" + + 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" +) + +// StepRegisterOMI creates the OMI. +type StepRegisterOMI struct { + RootDevice RootBlockDevice + OMIDevices []oapi.BlockDeviceMappingImage + LaunchDevices []oapi.BlockDeviceMappingVmCreation + image *oapi.Image +} + +func (s *StepRegisterOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + config := state.Get("config").(*Config) + oapiconn := state.Get("oapi").(*oapi.Client) + snapshotIds := state.Get("snapshot_ids").(map[string]string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Registering the OMI...") + + blockDevices := s.combineDevices(snapshotIds) + + registerOpts := oapi.CreateImageRequest{ + ImageName: config.OMIName, + Architecture: "x86_64", + RootDeviceName: s.RootDevice.DeviceName, + BlockDeviceMappings: blockDevices, + } + + registerResp, err := oapiconn.POST_CreateImage(registerOpts) + if err != nil { + state.Put("error", fmt.Errorf("Error registering OMI: %s", err)) + ui.Error(state.Get("error").(error).Error()) + return multistep.ActionHalt + } + + // Set the OMI ID in the state + ui.Say(fmt.Sprintf("OMI: %s", registerResp.OK.Image.ImageId)) + omis := make(map[string]string) + omis[oapiconn.GetConfig().Region] = registerResp.OK.Image.ImageId + state.Put("omis", omis) + + // Wait for the image to become ready + ui.Say("Waiting for OMI to become ready...") + if err := osccommon.WaitUntilImageAvailable(oapiconn, registerResp.OK.Image.ImageId); err != nil { + err := fmt.Errorf("Error waiting for OMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{ + Filters: oapi.FiltersImage{ + ImageIds: []string{registerResp.OK.Image.ImageId}, + }, + }) + + if err != nil { + err := fmt.Errorf("Error searching for OMI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + s.image = &imagesResp.OK.Images[0] + + snapshots := make(map[string][]string) + for _, blockDeviceMapping := range imagesResp.OK.Images[0].BlockDeviceMappings { + if blockDeviceMapping.Bsu.SnapshotId != "" { + snapshots[oapiconn.GetConfig().Region] = append(snapshots[oapiconn.GetConfig().Region], blockDeviceMapping.Bsu.SnapshotId) + } + } + state.Put("snapshots", snapshots) + + return multistep.ActionContinue +} + +func (s *StepRegisterOMI) Cleanup(state multistep.StateBag) { + if s.image == nil { + return + } + + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + if !cancelled && !halted { + return + } + + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deregistering the OMI because cancellation or error...") + deregisterOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId} + if _, err := oapiconn.POST_DeleteImage(deregisterOpts); err != nil { + ui.Error(fmt.Sprintf("Error deregistering OMI, may still be around: %s", err)) + return + } +} + +func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []oapi.BlockDeviceMappingImage { + devices := map[string]oapi.BlockDeviceMappingImage{} + + for _, device := range s.OMIDevices { + devices[device.DeviceName] = device + } + + // Devices in launch_block_device_mappings override any with + // the same name in ami_block_device_mappings, except for the + // one designated as the root device in ami_root_device + for _, device := range s.LaunchDevices { + snapshotId, ok := snapshotIds[device.DeviceName] + if ok { + device.Bsu.SnapshotId = snapshotId + } + if device.DeviceName == s.RootDevice.SourceDeviceName { + device.DeviceName = s.RootDevice.DeviceName + } + devices[device.DeviceName] = copyToDeviceMappingImage(device) + } + + blockDevices := []oapi.BlockDeviceMappingImage{} + for _, device := range devices { + blockDevices = append(blockDevices, device) + } + return blockDevices +} + +func copyToDeviceMappingImage(device oapi.BlockDeviceMappingVmCreation) oapi.BlockDeviceMappingImage { + deviceImage := oapi.BlockDeviceMappingImage{ + DeviceName: device.DeviceName, + VirtualDeviceName: device.VirtualDeviceName, + Bsu: oapi.BsuToCreate{ + DeleteOnVmDeletion: device.Bsu.DeleteOnVmDeletion, + Iops: device.Bsu.Iops, + SnapshotId: device.Bsu.SnapshotId, + VolumeSize: device.Bsu.VolumeSize, + VolumeType: device.Bsu.VolumeType, + }, + } + return deviceImage +} diff --git a/builder/osc/common/block_device.go b/builder/osc/common/block_device.go index 8d6ce9535..420d112da 100644 --- a/builder/osc/common/block_device.go +++ b/builder/osc/common/block_device.go @@ -28,7 +28,7 @@ type BlockDevices struct { } type OMIBlockDevices struct { - OMIMappings []BlockDevice `mapstructure:"ami_block_device_mappings"` + OMIMappings []BlockDevice `mapstructure:"omi_block_device_mappings"` } type LaunchBlockDevices struct { @@ -83,6 +83,48 @@ func buildBlockDevices(b []BlockDevice) []*oapi.BlockDeviceMapping { return blockDevices } +func buildBlockDevicesImage(b []BlockDevice) []oapi.BlockDeviceMappingImage { + var blockDevices []oapi.BlockDeviceMappingImage + + for _, blockDevice := range b { + mapping := oapi.BlockDeviceMappingImage{ + DeviceName: blockDevice.DeviceName, + } + + if blockDevice.VirtualName != "" { + if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") { + mapping.VirtualDeviceName = blockDevice.VirtualName + } + } else { + bsu := oapi.BsuToCreate{ + DeleteOnVmDeletion: blockDevice.DeleteOnVmDeletion, + } + + if blockDevice.VolumeType != "" { + bsu.VolumeType = blockDevice.VolumeType + } + + if blockDevice.VolumeSize > 0 { + bsu.VolumeSize = blockDevice.VolumeSize + } + + // IOPS is only valid for io1 type + if blockDevice.VolumeType == "io1" { + bsu.Iops = blockDevice.IOPS + } + + if blockDevice.SnapshotId != "" { + bsu.SnapshotId = blockDevice.SnapshotId + } + + mapping.Bsu = bsu + } + + blockDevices = append(blockDevices, mapping) + } + return blockDevices +} + func buildBlockDevicesVmCreation(b []BlockDevice) []oapi.BlockDeviceMappingVmCreation { var blockDevices []oapi.BlockDeviceMappingVmCreation @@ -158,8 +200,8 @@ func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) { return errs } -func (b *OMIBlockDevices) BuildOMIDevices() []*oapi.BlockDeviceMapping { - return buildBlockDevices(b.OMIMappings) +func (b *OMIBlockDevices) BuildOMIDevices() []oapi.BlockDeviceMappingImage { + return buildBlockDevicesImage(b.OMIMappings) } func (b *LaunchBlockDevices) BuildLaunchDevices() []oapi.BlockDeviceMappingVmCreation { diff --git a/builder/osc/common/state.go b/builder/osc/common/state.go index 0f6c90986..ed8941268 100644 --- a/builder/osc/common/state.go +++ b/builder/osc/common/state.go @@ -36,9 +36,15 @@ func waitUntilVmStopped(conn *oapi.Client, vmID string) error { return <-errCh } -func WaitUntilSnapshotCompleted(conn *oapi.Client, vmID string) error { +func WaitUntilSnapshotCompleted(conn *oapi.Client, id string) error { errCh := make(chan error, 1) - go waitForState(errCh, "completed", waitUntilSnapshotStateFunc(conn, vmID)) + go waitForState(errCh, "completed", waitUntilSnapshotStateFunc(conn, id)) + return <-errCh +} + +func WaitUntilImageAvailable(conn *oapi.Client, imageID string) error { + errCh := make(chan error, 1) + go waitForState(errCh, "available", waitUntilImageStateFunc(conn, imageID)) return <-errCh } @@ -110,6 +116,37 @@ func waitUntilSnapshotStateFunc(conn *oapi.Client, id string) stateRefreshFunc { } } +func waitUntilImageStateFunc(conn *oapi.Client, id string) stateRefreshFunc { + return func() (string, error) { + log.Printf("[Debug] Check if Image with id %s exists", id) + resp, err := conn.POST_ReadImages(oapi.ReadImagesRequest{ + Filters: oapi.FiltersImage{ + ImageIds: []string{id}, + }, + }) + + log.Printf("[Debug] Read Response %+v", resp.OK) + + if err != nil { + return "", err + } + + if resp.OK == nil { + return "", fmt.Errorf("Vm with ID %s. Not Found", id) + } + + if len(resp.OK.Images) == 0 { + return "pending", nil + } + + if resp.OK.Images[0].State == "failed" { + return resp.OK.Images[0].State, fmt.Errorf("Image (%s) creation is failed", id) + } + + return resp.OK.Images[0].State, nil + } +} + func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc { return func() (string, error) { log.Printf("[Debug] Check if SG with id %s exists", id)