Merge pull request #7459 from outscale/osc-develop

Outscale Packer Builder
This commit is contained in:
Megan Marsh 2019-07-18 10:24:13 -07:00 committed by GitHub
commit 86a3791aae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 27070 additions and 79 deletions

View File

@ -18,6 +18,7 @@
/builder/hyperone @m110 @gregorybrzeski @ad-m
/builder/ucloud/ @shawnmssu
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
/builder/osc @marinsalinas
# provisioners

219
builder/osc/bsu/builder.go Normal file
View File

@ -0,0 +1,219 @@
// Package bsu contains a packer.Builder implementation that
// builds OMIs for Outscale OAPI.
//
// In general, there are two types of OMIs that can be created: ebs-backed or
// instance-store. This builder _only_ builds ebs-backed images.
package bsu
import (
"context"
"crypto/tls"
"fmt"
"net/http"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// The unique ID for this builder
const BuilderId = "oapi.outscale.bsu"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
osccommon.AccessConfig `mapstructure:",squash"`
osccommon.OMIConfig `mapstructure:",squash"`
osccommon.BlockDevices `mapstructure:",squash"`
osccommon.RunConfig `mapstructure:",squash"`
VolumeRunTags osccommon.TagMap `mapstructure:"run_volume_tags"`
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = osccommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"omi_description",
"run_tags",
"run_volume_tags",
"spot_tags",
"snapshot_tags",
"tags",
},
},
}, raws...)
if err != nil {
return nil, err
}
if b.config.PackerConfig.PackerForce {
b.config.OMIForceDeregister = true
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.OMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("hook", hook)
state.Put("ui", ui)
steps := []multistep.Step{
&osccommon.StepPreValidate{
DestOmiName: b.config.OMIName,
ForceDeregister: b.config.OMIForceDeregister,
},
&osccommon.StepSourceOMIInfo{
SourceOmi: b.config.SourceOmi,
OmiFilters: b.config.SourceOmiFilter,
OMIVirtType: b.config.OMIVirtType, //TODO: Remove if it is not used
},
&osccommon.StepNetworkInfo{
NetId: b.config.NetId,
NetFilter: b.config.NetFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
SubregionName: b.config.Subregion,
},
&osccommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("oapi_%s", b.config.PackerBuildName),
},
&osccommon.StepSecurityGroup{
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&osccommon.StepCleanupVolumes{
BlockDevices: b.config.BlockDevices,
},
&osccommon.StepRunSourceVm{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
BlockDevices: b.config.BlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
Debug: b.config.PackerDebug,
BsuOptimized: b.config.BsuOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: osccommon.RunSourceVmBSUExpectedRootDevice,
IamVmProfile: b.config.IamVmProfile,
VmInitiatedShutdownBehavior: b.config.VmInitiatedShutdownBehavior,
VmType: b.config.VmType,
IsRestricted: false,
SourceOMI: b.config.SourceOmi,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
},
&osccommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
Timeout: b.config.WindowsPasswordTimeout,
BuildName: b.config.PackerBuildName,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.SSHHost(
oapiconn,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&osccommon.StepStopBSUBackedVm{
Skip: false,
DisableStopVm: b.config.DisableStopVm,
},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.OMIForceDeregister,
ForceDeleteSnapshot: b.config.OMIForceDeleteSnapshot,
OMIName: b.config.OMIName,
Regions: b.config.OMIRegions,
},
&stepCreateOMI{},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
SnapshotAccountIds: b.config.SnapshotAccountIDs,
Ctx: b.config.ctx,
},
&osccommon.StepCreateTags{
Tags: b.config.OMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
//Build the artifact
if omis, ok := state.GetOk("omis"); ok {
// Build the artifact and return it
artifact := &osccommon.Artifact{
Omis: omis.(map[string]string),
BuilderIdValue: BuilderId,
Config: clientConfig,
}
return artifact, nil
}
return nil, nil
}

View File

@ -0,0 +1,53 @@
//TODO: explain how to delete the image.
package bsu
import (
"crypto/tls"
"net/http"
"testing"
"github.com/hashicorp/packer/builder/osc/common"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/outscale/osc-go/oapi"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
SkipArtifactTeardown: true,
})
}
func testAccPreCheck(t *testing.T) {
}
func testOAPIConn() (*oapi.Client, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
clientConfig, err := access.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
return oapi.NewClient(clientConfig, skipClient), nil
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"omi_name": "packer-test {{timestamp}}"
}]
}
`

View File

@ -0,0 +1,131 @@
package bsu
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_omi": "foo",
"vm_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
"omi_name": "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_OMIName(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["omi_name"] = "foo"
config["skip_region_validation"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["omi_name"] = "foo {{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "omi_name")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["shutdown_behavior"] = "terminate"
config["skip_region_validation"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test good
config["shutdown_behavior"] = "stop"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["shutdown_behavior"] = "foobar"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}

View File

@ -0,0 +1,116 @@
package bsu
import (
"context"
"fmt"
"log"
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"
)
type stepCreateOMI struct {
image *oapi.Image
}
func (s *stepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
// Create the image
omiName := config.OMIName
ui.Say(fmt.Sprintf("Creating OMI %s from vm %s", omiName, vm.VmId))
createOpts := oapi.CreateImageRequest{
VmId: vm.VmId,
ImageName: omiName,
BlockDeviceMappings: config.BlockDevices.BuildOMIDevices(),
}
resp, err := oapiconn.POST_CreateImage(createOpts)
if err != nil || resp.OK == nil {
err := fmt.Errorf("Error creating OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := resp.OK.Image
// Set the OMI ID in the state
ui.Message(fmt.Sprintf("OMI: %s", image.ImageId))
omis := make(map[string]string)
omis[oapiconn.GetConfig().Region] = 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, image.ImageId); err != nil {
log.Printf("Error waiting for OMI: %s", err)
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{image.ImageId},
},
})
if err != nil {
log.Printf("Unable to determine reason waiting for OMI failed: %s", err)
err = fmt.Errorf("Unknown error waiting for OMI.")
} else {
stateReason := imagesResp.OK.Images[0].StateComment
err = fmt.Errorf("Error waiting for OMI. Reason: %s", stateReason)
}
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
imagesResp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{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 *stepCreateOMI) 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...")
DeleteOpts := oapi.DeleteImageRequest{ImageId: s.image.ImageId}
if _, err := oapiconn.POST_DeleteImage(DeleteOpts); err != nil {
ui.Error(fmt.Sprintf("Error Deleting OMI, may still be around: %s", err))
return
}
}

View File

@ -0,0 +1,249 @@
// Package bsusurrogate contains a packer.Builder implementation that
// builds a new EBS-backed OMI using an ephemeral instance.
package bsusurrogate
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
const BuilderId = "oapi.outscale.bsusurrogate"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
osccommon.AccessConfig `mapstructure:",squash"`
osccommon.RunConfig `mapstructure:",squash"`
osccommon.BlockDevices `mapstructure:",squash"`
osccommon.OMIConfig `mapstructure:",squash"`
RootDevice RootBlockDevice `mapstructure:"omi_root_device"`
VolumeRunTags osccommon.TagMap `mapstructure:"run_volume_tags"`
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = osccommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"omi_description",
"run_tags",
"run_volume_tags",
"snapshot_tags",
"spot_tags",
"tags",
},
},
}, raws...)
if err != nil {
return nil, err
}
if b.config.PackerConfig.PackerForce {
b.config.OMIForceDeregister = true
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.OMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RootDevice.Prepare(&b.config.ctx)...)
if b.config.OMIVirtType == "" {
errs = packer.MultiErrorAppend(errs, errors.New("omi_virtualization_type is required."))
}
foundRootVolume := false
for _, launchDevice := range b.config.BlockDevices.LaunchMappings {
if launchDevice.DeviceName == b.config.RootDevice.SourceDeviceName {
foundRootVolume = true
}
}
if !foundRootVolume {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("no volume with name '%s' is found", b.config.RootDevice.SourceDeviceName))
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("hook", hook)
state.Put("ui", ui)
//VMStep
omiDevices := b.config.BuildOMIDevices()
launchDevices := b.config.BuildLaunchDevices()
steps := []multistep.Step{
&osccommon.StepPreValidate{
DestOmiName: b.config.OMIName,
ForceDeregister: b.config.OMIForceDeregister,
},
&osccommon.StepSourceOMIInfo{
SourceOmi: b.config.SourceOmi,
OmiFilters: b.config.SourceOmiFilter,
OMIVirtType: b.config.OMIVirtType, //TODO: Remove if it is not used
},
&osccommon.StepNetworkInfo{
NetId: b.config.NetId,
NetFilter: b.config.NetFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
SubregionName: b.config.Subregion,
},
&osccommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("oapi_%s", b.config.PackerBuildName),
},
&osccommon.StepSecurityGroup{
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&osccommon.StepCleanupVolumes{
BlockDevices: b.config.BlockDevices,
},
&osccommon.StepRunSourceVm{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
BlockDevices: b.config.BlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
Debug: b.config.PackerDebug,
BsuOptimized: b.config.BsuOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: osccommon.RunSourceVmBSUExpectedRootDevice,
IamVmProfile: b.config.IamVmProfile,
VmInitiatedShutdownBehavior: b.config.VmInitiatedShutdownBehavior,
VmType: b.config.VmType,
IsRestricted: false,
SourceOMI: b.config.SourceOmi,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
VolumeTags: b.config.VolumeRunTags,
},
&osccommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
Timeout: b.config.WindowsPasswordTimeout,
BuildName: b.config.PackerBuildName,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.SSHHost(
oapiconn,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&osccommon.StepStopBSUBackedVm{
Skip: false,
DisableStopVm: b.config.DisableStopVm,
},
&StepSnapshotVolumes{
LaunchDevices: launchDevices,
},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.OMIForceDeregister,
ForceDeleteSnapshot: b.config.OMIForceDeleteSnapshot,
OMIName: b.config.OMIName,
Regions: b.config.OMIRegions,
},
&StepRegisterOMI{
RootDevice: b.config.RootDevice,
OMIDevices: omiDevices,
LaunchDevices: launchDevices,
},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
SnapshotAccountIds: b.config.SnapshotAccountIDs,
Ctx: b.config.ctx,
},
&osccommon.StepCreateTags{
Tags: b.config.OMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
//Build the artifact
if omis, ok := state.GetOk("omis"); ok {
// Build the artifact and return it
artifact := &osccommon.Artifact{
Omis: omis.(map[string]string),
BuilderIdValue: BuilderId,
Config: clientConfig,
}
return artifact, nil
}
return nil, nil
}

View File

@ -0,0 +1,51 @@
package bsusurrogate
import (
"testing"
builderT "github.com/hashicorp/packer/helper/builder/testing"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
SkipArtifactTeardown: true,
})
}
func testAccPreCheck(t *testing.T) {
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"omi_name": "packer-test {{timestamp}}",
"omi_virtualization_type": "hvm",
"subregion_name": "eu-west-2a",
"launch_block_device_mappings" : [
{
"volume_type" : "io1",
"device_name" : "/dev/xvdf",
"delete_on_vm_deletion" : false,
"volume_size" : 10,
"iops": 300
}
],
"omi_root_device":{
"source_device_name": "/dev/xvdf",
"device_name": "/dev/sda1",
"delete_on_vm_deletion": true,
"volume_size": 10,
"volume_type": "standard"
}
}]
}
`

View File

@ -0,0 +1,15 @@
package bsusurrogate
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatal("Builder should be a builder")
}
}

View File

@ -0,0 +1,46 @@
package bsusurrogate
import (
"errors"
"github.com/hashicorp/packer/template/interpolate"
)
type RootBlockDevice struct {
SourceDeviceName string `mapstructure:"source_device_name"`
DeviceName string `mapstructure:"device_name"`
DeleteOnVmDeletion bool `mapstructure:"delete_on_vm_deletion"`
IOPS int64 `mapstructure:"iops"`
VolumeType string `mapstructure:"volume_type"`
VolumeSize int64 `mapstructure:"volume_size"`
}
func (c *RootBlockDevice) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.SourceDeviceName == "" {
errs = append(errs, errors.New("source_device_name for the root_device must be specified"))
}
if c.DeviceName == "" {
errs = append(errs, errors.New("device_name for the root_device must be specified"))
}
if c.VolumeType == "gp2" && c.IOPS != 0 {
errs = append(errs, errors.New("iops may not be specified for a gp2 volume"))
}
if c.IOPS < 0 {
errs = append(errs, errors.New("iops must be greater than 0"))
}
if c.VolumeSize < 0 {
errs = append(errs, errors.New("volume_size must be greater than 0"))
}
if len(errs) > 0 {
return errs
}
return nil
}

View File

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

View File

@ -0,0 +1,107 @@
package bsusurrogate
import (
"context"
"fmt"
"sync"
"time"
multierror "github.com/hashicorp/go-multierror"
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"
)
// StepSnapshotVolumes creates snapshots of the created volumes.
//
// Produces:
// snapshot_ids map[string]string - IDs of the created snapshots
type StepSnapshotVolumes struct {
LaunchDevices []oapi.BlockDeviceMappingVmCreation
snapshotIds map[string]string
}
func (s *StepSnapshotVolumes) snapshotVolume(ctx context.Context, deviceName string, state multistep.StateBag) error {
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
vm := state.Get("vm").(oapi.Vm)
var volumeId string
for _, volume := range vm.BlockDeviceMappings {
if volume.DeviceName == deviceName {
volumeId = volume.Bsu.VolumeId
}
}
if volumeId == "" {
return fmt.Errorf("Volume ID for device %s not found", deviceName)
}
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", volumeId))
description := fmt.Sprintf("Packer: %s", time.Now().String())
createSnapResp, err := oapiconn.POST_CreateSnapshot(oapi.CreateSnapshotRequest{
VolumeId: volumeId,
Description: description,
})
if err != nil {
return err
}
// Set the snapshot ID so we can delete it later
s.snapshotIds[deviceName] = createSnapResp.OK.Snapshot.SnapshotId
// Wait for snapshot to be created
err = osccommon.WaitUntilSnapshotCompleted(oapiconn, createSnapResp.OK.Snapshot.SnapshotId)
return err
}
func (s *StepSnapshotVolumes) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
s.snapshotIds = map[string]string{}
var wg sync.WaitGroup
var errs *multierror.Error
for _, device := range s.LaunchDevices {
wg.Add(1)
go func(device oapi.BlockDeviceMappingVmCreation) {
defer wg.Done()
if err := s.snapshotVolume(ctx, device.DeviceName, state); err != nil {
errs = multierror.Append(errs, err)
}
}(device)
}
wg.Wait()
if errs != nil {
state.Put("error", errs)
ui.Error(errs.Error())
return multistep.ActionHalt
}
state.Put("snapshot_ids", s.snapshotIds)
return multistep.ActionContinue
}
func (s *StepSnapshotVolumes) Cleanup(state multistep.StateBag) {
if len(s.snapshotIds) == 0 {
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 snapshots since we cancelled or halted...")
for _, snapshotId := range s.snapshotIds {
_, err := oapiconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{SnapshotId: snapshotId})
if err != nil {
ui.Error(fmt.Sprintf("Error: %s", err))
}
}
}
}

View File

@ -0,0 +1,87 @@
package bsuvolume
import (
"fmt"
"log"
"sort"
"strings"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// map of region to list of volume IDs
type BsuVolumes map[string][]string
// Artifact is an artifact implementation that contains built AMIs.
type Artifact struct {
// A map of regions to EBS Volume IDs.
Volumes BsuVolumes
// BuilderId is the unique ID for the builder that created this AMI
BuilderIdValue string
// Client connection for performing API stuff.
Conn *oapi.Client
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
// returns a sorted list of region:ID pairs
func (a *Artifact) idList() []string {
parts := make([]string, 0, len(a.Volumes))
for region, volumeIDs := range a.Volumes {
for _, volumeID := range volumeIDs {
parts = append(parts, fmt.Sprintf("%s:%s", region, volumeID))
}
}
sort.Strings(parts)
return parts
}
func (a *Artifact) Id() string {
return strings.Join(a.idList(), ",")
}
func (a *Artifact) String() string {
return fmt.Sprintf("EBS Volumes were created:\n\n%s", strings.Join(a.idList(), "\n"))
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
for region, volumeIDs := range a.Volumes {
for _, volumeID := range volumeIDs {
log.Printf("Deregistering Volume ID (%s) from region (%s)", volumeID, region)
input := oapi.DeleteVolumeRequest{
VolumeId: volumeID,
}
if _, err := a.Conn.POST_DeleteVolume(input); err != nil {
errors = append(errors, err)
}
}
}
if len(errors) > 0 {
if len(errors) == 1 {
return errors[0]
} else {
return &packer.MultiError{Errors: errors}
}
}
return nil
}

View File

@ -0,0 +1,29 @@
package bsuvolume
import (
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/template/interpolate"
)
type BlockDevice struct {
osccommon.BlockDevice `mapstructure:"-,squash"`
Tags osccommon.TagMap `mapstructure:"tags"`
}
func commonBlockDevices(mappings []BlockDevice, ctx *interpolate.Context) (osccommon.BlockDevices, error) {
result := make([]osccommon.BlockDevice, len(mappings))
for i, mapping := range mappings {
interpolateBlockDev, err := interpolate.RenderInterface(&mapping.BlockDevice, ctx)
if err != nil {
return osccommon.BlockDevices{}, err
}
result[i] = *interpolateBlockDev.(*osccommon.BlockDevice)
}
return osccommon.BlockDevices{
LaunchBlockDevices: osccommon.LaunchBlockDevices{
LaunchMappings: result,
},
}, nil
}

View File

@ -0,0 +1,198 @@
// The ebsvolume package contains a packer.Builder implementation that
// builds EBS volumes for Outscale using an ephemeral instance,
package bsuvolume
import (
"context"
"crypto/tls"
"fmt"
"log"
"net/http"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
const BuilderId = "oapi.outscale.bsuvolume"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
osccommon.AccessConfig `mapstructure:",squash"`
osccommon.RunConfig `mapstructure:",squash"`
VolumeMappings []BlockDevice `mapstructure:"bsu_volumes"`
launchBlockDevices osccommon.BlockDevices
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
type EngineVarsTemplate struct {
BuildRegion string
SourceOMI string
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = osccommon.TemplateFuncs
// Create passthrough for {{ .BuildRegion }} and {{ .SourceOMI }} variables
// so we can fill them in later
b.config.ctx.Data = &EngineVarsTemplate{
BuildRegion: `{{ .BuildRegion }}`,
SourceOMI: `{{ .SourceOMI }} `,
}
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
}, raws...)
if err != nil {
return nil, err
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.launchBlockDevices.Prepare(&b.config.ctx)...)
for _, d := range b.config.VolumeMappings {
if err := d.Prepare(&b.config.ctx); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("OMIMapping: %s", err.Error()))
}
}
b.config.launchBlockDevices, err = commonBlockDevices(b.config.VolumeMappings, &b.config.ctx)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("hook", hook)
state.Put("ui", ui)
log.Printf("[DEBUG] launch block devices %#v", b.config.launchBlockDevices)
instanceStep := &osccommon.StepRunSourceVm{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
BlockDevices: b.config.launchBlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
Debug: b.config.PackerDebug,
BsuOptimized: b.config.BsuOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs",
IamVmProfile: b.config.IamVmProfile,
VmInitiatedShutdownBehavior: b.config.VmInitiatedShutdownBehavior,
VmType: b.config.VmType,
SourceOMI: b.config.SourceOmi,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
}
// Build the steps
steps := []multistep.Step{
&osccommon.StepSourceOMIInfo{
SourceOmi: b.config.SourceOmi,
OmiFilters: b.config.SourceOmiFilter,
},
&osccommon.StepNetworkInfo{
NetId: b.config.NetId,
NetFilter: b.config.NetFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
SubregionName: b.config.Subregion,
},
&osccommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("oapi_%s.pem", b.config.PackerBuildName),
},
&osccommon.StepSecurityGroup{
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
instanceStep,
&stepTagBSUVolumes{
VolumeMapping: b.config.VolumeMappings,
Ctx: b.config.ctx,
},
&osccommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
Timeout: b.config.WindowsPasswordTimeout,
BuildName: b.config.PackerBuildName,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: osccommon.SSHHost(
oapiconn,
b.config.SSHInterface),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.RunConfig.Comm,
},
&osccommon.StepStopBSUBackedVm{
Skip: b.config.IsSpotVm(),
DisableStopVm: b.config.DisableStopVm,
},
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// Build the artifact and return it
artifact := &Artifact{
Volumes: state.Get("bsuvolumes").(BsuVolumes),
BuilderIdValue: BuilderId,
Conn: oapiconn,
}
ui.Say(fmt.Sprintf("Created Volumes: %s", artifact))
return artifact, nil
}

View File

@ -0,0 +1,86 @@
//TODO: explain how to delete the image.
package bsuvolume
import (
"crypto/tls"
"net/http"
"testing"
"github.com/hashicorp/packer/builder/osc/common"
builderT "github.com/hashicorp/packer/helper/builder/testing"
"github.com/outscale/osc-go/oapi"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccBasic,
SkipArtifactTeardown: true,
})
}
func testAccPreCheck(t *testing.T) {
}
func testOAPIConn() (*oapi.Client, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
clientConfig, err := access.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
return oapi.NewClient(clientConfig, skipClient), nil
}
const testBuilderAccBasic = `
{
"builders": [
{
"type": "test",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"bsu_volumes": [
{
"volume_type": "gp2",
"device_name": "/dev/xvdf",
"delete_on_vm_deletion": false,
"tags": {
"zpool": "data",
"Name": "Data1"
},
"volume_size": 10
},
{
"volume_type": "gp2",
"device_name": "/dev/xvdg",
"tags": {
"zpool": "data",
"Name": "Data2"
},
"delete_on_vm_deletion": false,
"volume_size": 10
},
{
"volume_size": 10,
"tags": {
"Name": "Data3",
"zpool": "data"
},
"delete_on_vm_deletion": false,
"device_name": "/dev/xvdh",
"volume_type": "gp2"
}
]
}
]
}
`

View File

@ -0,0 +1,92 @@
package bsuvolume
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_omi": "foo",
"vm_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidShutdownBehavior(t *testing.T) {
var b Builder
config := testConfig()
// Test good
config["shutdown_behavior"] = "terminate"
config["skip_region_validation"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test good
config["shutdown_behavior"] = "stop"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
// Test bad
config["shutdown_behavior"] = "foobar"
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}

View File

@ -0,0 +1,81 @@
package bsuvolume
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
type stepTagBSUVolumes struct {
VolumeMapping []BlockDevice
Ctx interpolate.Context
}
func (s *stepTagBSUVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
volumes := make(BsuVolumes)
for _, instanceBlockDevices := range vm.BlockDeviceMappings {
for _, configVolumeMapping := range s.VolumeMapping {
if configVolumeMapping.DeviceName == instanceBlockDevices.DeviceName {
volumes[oapiconn.GetConfig().Region] = append(
volumes[oapiconn.GetConfig().Region],
instanceBlockDevices.Bsu.VolumeId)
}
}
}
state.Put("bsuvolumes", volumes)
if len(s.VolumeMapping) > 0 {
ui.Say("Tagging BSU volumes...")
toTag := map[string][]oapi.ResourceTag{}
for _, mapping := range s.VolumeMapping {
if len(mapping.Tags) == 0 {
ui.Say(fmt.Sprintf("No tags specified for volume on %s...", mapping.DeviceName))
continue
}
tags, err := mapping.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
tags.Report(ui)
for _, v := range vm.BlockDeviceMappings {
if v.DeviceName == mapping.DeviceName {
toTag[v.Bsu.VolumeId] = tags
}
}
}
for volumeId, tags := range toTag {
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: []string{volumeId},
Tags: tags,
})
if err != nil {
err := fmt.Errorf("Error tagging BSU Volume %s on %s: %s", volumeId, vm.VmId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *stepTagBSUVolumes) Cleanup(state multistep.StateBag) {
// No cleanup...
}

View File

@ -0,0 +1,309 @@
// Package chroot is able to create an Outscale OMI without requiring
// the launch of a new instance for every build. It does this by attaching
// and mounting the root volume of another OMI and chrooting into that
// directory. It then creates an OMI from that attached drive.
package chroot
import (
"context"
"crypto/tls"
"errors"
"net/http"
"runtime"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// The unique ID for this builder
const BuilderId = "oapi.outscale.chroot"
// Config is the configuration that is chained through the steps and
// settable from the template.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
osccommon.OMIBlockDevices `mapstructure:",squash"`
osccommon.OMIConfig `mapstructure:",squash"`
osccommon.AccessConfig `mapstructure:",squash"`
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
CommandWrapper string `mapstructure:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files"`
DevicePath string `mapstructure:"device_path"`
NVMEDevicePath string `mapstructure:"nvme_device_path"`
FromScratch bool `mapstructure:"from_scratch"`
MountOptions []string `mapstructure:"mount_options"`
MountPartition string `mapstructure:"mount_partition"`
MountPath string `mapstructure:"mount_path"`
PostMountCommands []string `mapstructure:"post_mount_commands"`
PreMountCommands []string `mapstructure:"pre_mount_commands"`
RootDeviceName string `mapstructure:"root_device_name"`
RootVolumeSize int64 `mapstructure:"root_volume_size"`
RootVolumeType string `mapstructure:"root_volume_type"`
SourceOMI string `mapstructure:"source_omi"`
SourceOMIFilter osccommon.OmiFilterOptions `mapstructure:"source_omi_filter"`
RootVolumeTags osccommon.TagMap `mapstructure:"root_volume_tags"`
ctx interpolate.Context
}
type wrappedCommandTemplate struct {
Command string
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = osccommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"omi_description",
"snapshot_tags",
"tags",
"root_volume_tags",
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
},
},
}, raws...)
if err != nil {
return nil, err
}
if b.config.PackerConfig.PackerForce {
b.config.OMIForceDeregister = true
}
// Defaults
if b.config.ChrootMounts == nil {
b.config.ChrootMounts = make([][]string, 0)
}
if len(b.config.ChrootMounts) == 0 {
b.config.ChrootMounts = [][]string{
{"proc", "proc", "/proc"},
{"sysfs", "sysfs", "/sys"},
{"bind", "/dev", "/dev"},
{"devpts", "devpts", "/dev/pts"},
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
}
}
// set default copy file if we're not giving our own
if b.config.CopyFiles == nil {
b.config.CopyFiles = make([]string, 0)
if !b.config.FromScratch {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
}
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
}
if b.config.MountPath == "" {
b.config.MountPath = "/mnt/packer-outscale-chroot-volumes/{{.Device}}"
}
if b.config.MountPartition == "" {
b.config.MountPartition = "1"
}
// Accumulate any errors or warnings
var errs *packer.MultiError
var warns []string
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.OMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
for _, mounts := range b.config.ChrootMounts {
if len(mounts) != 3 {
errs = packer.MultiErrorAppend(
errs, errors.New("Each chroot_mounts entry should be three elements."))
break
}
}
if b.config.FromScratch {
if b.config.SourceOMI != "" || !b.config.SourceOMIFilter.Empty() {
warns = append(warns, "source_omi and source_omi_filter are unused when from_scratch is true")
}
if b.config.RootVolumeSize == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("root_volume_size is required with from_scratch."))
}
if len(b.config.PreMountCommands) == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("pre_mount_commands is required with from_scratch."))
}
if b.config.OMIVirtType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("omi_virtualization_type is required with from_scratch."))
}
if b.config.RootDeviceName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("root_device_name is required with from_scratch."))
}
if len(b.config.OMIMappings) == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("omi_block_device_mappings is required with from_scratch."))
}
} else {
if b.config.SourceOMI == "" && b.config.SourceOMIFilter.Empty() {
errs = packer.MultiErrorAppend(
errs, errors.New("source_omi or source_omi_filter is required."))
}
if len(b.config.OMIMappings) != 0 {
warns = append(warns, "omi_block_device_mappings are unused when from_scratch is false")
}
if b.config.RootDeviceName != "" {
warns = append(warns, "root_device_name is unused when from_scratch is false")
}
}
if errs != nil && len(errs.Errors) > 0 {
return warns, errs
}
packer.LogSecretFilter.Set(b.config.AccessKey, b.config.SecretKey, b.config.Token)
return warns, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
if runtime.GOOS != "linux" {
return nil, errors.New("The outscale-chroot builder only works on Linux environments.")
}
clientConfig, err := b.config.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
oapiconn := oapi.NewClient(clientConfig, skipClient)
wrappedCommand := func(command string) (string, error) {
ctx := b.config.ctx
ctx.Data = &wrappedCommandTemplate{Command: command}
return interpolate.Render(b.config.CommandWrapper, &ctx)
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("oapi", oapiconn)
state.Put("clientConfig", clientConfig)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
// Build the steps
steps := []multistep.Step{
&osccommon.StepPreValidate{
DestOmiName: b.config.OMIName,
ForceDeregister: b.config.OMIForceDeregister,
},
&StepVmInfo{},
}
if !b.config.FromScratch {
steps = append(steps,
&osccommon.StepSourceOMIInfo{
SourceOmi: b.config.SourceOMI,
OmiFilters: b.config.SourceOMIFilter,
OMIVirtType: b.config.OMIVirtType,
},
&StepCheckRootDevice{},
)
}
steps = append(steps,
&StepFlock{},
&StepPrepareDevice{},
&StepCreateVolume{
RootVolumeType: b.config.RootVolumeType,
RootVolumeSize: b.config.RootVolumeSize,
RootVolumeTags: b.config.RootVolumeTags,
Ctx: b.config.ctx,
},
&StepLinkVolume{},
&StepEarlyUnflock{},
&StepPreMountCommands{
Commands: b.config.PreMountCommands,
},
&StepMountDevice{
MountOptions: b.config.MountOptions,
MountPartition: b.config.MountPartition,
},
&StepPostMountCommands{
Commands: b.config.PostMountCommands,
},
&StepMountExtra{},
&StepCopyFiles{},
&StepChrootProvision{},
&StepEarlyCleanup{},
&StepSnapshot{},
&osccommon.StepDeregisterOMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.OMIForceDeregister,
ForceDeleteSnapshot: b.config.OMIForceDeleteSnapshot,
OMIName: b.config.OMIName,
Regions: b.config.OMIRegions,
},
&StepCreateOMI{
RootVolumeSize: b.config.RootVolumeSize,
},
&osccommon.StepUpdateOMIAttributes{
AccountIds: b.config.OMIAccountIDs,
SnapshotAccountIds: b.config.SnapshotAccountIDs,
Ctx: b.config.ctx,
},
&osccommon.StepCreateTags{
Tags: b.config.OMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
)
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no OMIs, then just return
if _, ok := state.GetOk("omis"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &osccommon.Artifact{
Omis: state.Get("omis").(map[string]string),
BuilderIdValue: BuilderId,
Config: clientConfig,
}
return artifact, nil
}

View File

@ -0,0 +1,10 @@
package chroot
import (
"github.com/hashicorp/packer/helper/multistep"
)
// Cleanup is an interface that some steps implement for early cleanup.
type Cleanup interface {
CleanupFunc(multistep.StateBag) error
}

View File

@ -0,0 +1,15 @@
package chroot
import (
"os/exec"
)
// CommandWrapper is a type that given a command, will possibly modify that
// command in-flight. This might return an error.
type CommandWrapper func(string) (string, error)
// ShellCommand takes a command string and returns an *exec.Cmd to execute
// it within the context of a shell (/bin/sh).
func ShellCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}

View File

@ -0,0 +1,143 @@
package chroot
import (
"bytes"
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
)
// Communicator is a special communicator that works by executing
// commands locally but within a chroot.
type Communicator struct {
Chroot string
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
// need extra escapes for the command since we're wrapping it in quotes
cmd.Command = strconv.Quote(cmd.Command)
command, err := c.CmdWrapper(
fmt.Sprintf("chroot %s /bin/sh -c %s", c.Chroot, cmd.Command))
if err != nil {
return err
}
localCmd := ShellCommand(command)
localCmd.Stdin = cmd.Stdin
localCmd.Stdout = cmd.Stdout
localCmd.Stderr = cmd.Stderr
log.Printf("Executing: %s %#v", localCmd.Path, localCmd.Args)
if err := localCmd.Start(); err != nil {
return err
}
go func() {
exitStatus := 0
if err := localCmd.Wait(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
}
log.Printf(
"Chroot execution exited with '%d': '%s'",
exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
}()
return nil
}
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
dst = filepath.Join(c.Chroot, dst)
log.Printf("Uploading to chroot dir: %s", dst)
tf, err := tmp.File("packer-outscale-chroot")
if err != nil {
return fmt.Errorf("Error preparing shell script: %s", err)
}
defer os.Remove(tf.Name())
if _, err := io.Copy(tf, r); err != nil {
return err
}
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp %s %s", tf.Name(), dst))
if err != nil {
return err
}
return ShellCommand(cpCmd).Run()
}
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
// If src ends with a trailing "/", copy from "src/." so that
// directory contents (including hidden files) are copied, but the
// directory "src" is omitted. BSD does this automatically when
// the source contains a trailing slash, but linux does not.
if src[len(src)-1] == '/' {
src = src + "."
}
// TODO: remove any file copied if it appears in `exclude`
chrootDest := filepath.Join(c.Chroot, dst)
log.Printf("Uploading directory '%s' to '%s'", src, chrootDest)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R '%s' %s", src, chrootDest))
if err != nil {
return err
}
var stderr bytes.Buffer
cmd := ShellCommand(cpCmd)
cmd.Env = append(cmd.Env, "LANG=C")
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Stderr = &stderr
err = cmd.Run()
if err == nil {
return err
}
if strings.Contains(stderr.String(), "No such file") {
// This just means that the directory was empty. Just ignore it.
return nil
}
return err
}
func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error {
return fmt.Errorf("DownloadDir is not implemented for outscale-chroot")
}
func (c *Communicator) Download(src string, w io.Writer) error {
src = filepath.Join(c.Chroot, src)
log.Printf("Downloading from chroot dir: %s", src)
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(w, f); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,15 @@
package chroot
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
var raw interface{}
raw = &Communicator{}
if _, ok := raw.(packer.Communicator); !ok {
t.Fatalf("Communicator should be a communicator")
}
}

View File

@ -0,0 +1,74 @@
package chroot
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
// AvailableDevice finds an available device and returns it. Note that
// you should externally hold a flock or something in order to guarantee
// that this device is available across processes.
func AvailableDevice() (string, error) {
prefix, err := devicePrefix()
if err != nil {
return "", err
}
letters := "fghijklmnop"
for _, letter := range letters {
device := fmt.Sprintf("/dev/%s%c", prefix, letter)
// If the block device itself, i.e. /dev/sf, exists, then we
// can't use any of the numbers either.
if _, err := os.Stat(device); err == nil {
continue
}
// To be able to build both Paravirtual and HVM images, the unnumbered
// device and the first numbered one must be available.
// E.g. /dev/xvdf and /dev/xvdf1
numbered_device := fmt.Sprintf("%s%d", device, 1)
if _, err := os.Stat(numbered_device); err != nil {
return device, nil
}
}
return "", errors.New("available device could not be found")
}
// devicePrefix returns the prefix ("sd" or "xvd" or so on) of the devices
// on the system. The "vd" prefix appears on outscale images.
func devicePrefix() (string, error) {
available := []string{"sd", "xvd", "vd"}
f, err := os.Open("/sys/block")
if err != nil {
return "", err
}
defer f.Close()
dirs, err := f.Readdirnames(-1)
if len(dirs) > 0 {
for _, dir := range dirs {
dirBase := filepath.Base(dir)
for _, prefix := range available {
if strings.HasPrefix(dirBase, prefix) {
//for outscale.
if prefix != "xvd" {
prefix = "xvd"
}
return prefix, nil
}
}
}
}
if err != nil {
return "", err
}
return "", errors.New("device prefix could not be detected")
}

View File

@ -0,0 +1,16 @@
// +build windows
package chroot
import (
"errors"
"os"
)
func lockFile(*os.File) error {
return errors.New("not supported on Windows")
}
func unlockFile(f *os.File) error {
return nil
}

View File

@ -0,0 +1,27 @@
// +build !windows
package chroot
import (
"os"
"golang.org/x/sys/unix"
)
// See: http://linux.die.net/include/sys/file.h
const LOCK_EX = 2
const LOCK_NB = 4
const LOCK_UN = 8
func lockFile(f *os.File) error {
err := unix.Flock(int(f.Fd()), LOCK_EX)
if err != nil {
return err
}
return nil
}
func unlockFile(f *os.File) error {
return unix.Flock(int(f.Fd()), LOCK_UN)
}

View File

@ -0,0 +1,41 @@
package chroot
import (
"context"
"fmt"
sl "github.com/hashicorp/packer/common/shell-local"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
func RunLocalCommands(commands []string, wrappedCommand CommandWrapper, ictx interpolate.Context, ui packer.Ui) error {
ctx := context.TODO()
for _, rawCmd := range commands {
intCmd, err := interpolate.Render(rawCmd, &ictx)
if err != nil {
return fmt.Errorf("Error interpolating: %s", err)
}
command, err := wrappedCommand(intCmd)
if err != nil {
return fmt.Errorf("Error wrapping command: %s", err)
}
ui.Say(fmt.Sprintf("Executing command: %s", command))
comm := &sl.Communicator{
ExecuteCommand: []string{"sh", "-c", command},
}
cmd := &packer.RemoteCmd{Command: command}
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return fmt.Errorf("Error executing command: %s", err)
}
if cmd.ExitStatus() != 0 {
return fmt.Errorf(
"Received non-zero exit code %d from command: %s",
cmd.ExitStatus(),
command)
}
}
return nil
}

View File

@ -0,0 +1,32 @@
package chroot
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// StepCheckRootDevice makes sure the root device on the OMI is BSU-backed.
type StepCheckRootDevice struct{}
func (s *StepCheckRootDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
image := state.Get("source_image").(oapi.Image)
ui := state.Get("ui").(packer.Ui)
ui.Say("Checking the root device on source OMI...")
// It must be BSU-backed otherwise the build won't work
if image.RootDeviceType != "ebs" {
err := fmt.Errorf("The root device of the source OMI must be BSU-backed.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCheckRootDevice) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,37 @@
package chroot
import (
"context"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepChrootProvision provisions the instance within a chroot.
type StepChrootProvision struct {
}
func (s *StepChrootProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
hook := state.Get("hook").(packer.Hook)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
// Create our communicator
comm := &Communicator{
Chroot: mountPath,
CmdWrapper: wrappedCommand,
}
// Provision
log.Println("Running the provision hook")
if err := hook.Run(ctx, packer.HookProvision, ui, comm, nil); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepChrootProvision) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,91 @@
package chroot
import (
"bytes"
"context"
"fmt"
"log"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepCopyFiles copies some files from the host into the chroot environment.
//
// Produces:
// copy_files_cleanup CleanupFunc - A function to clean up the copied files
// early.
type StepCopyFiles struct {
files []string
}
func (s *StepCopyFiles) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
stderr := new(bytes.Buffer)
s.files = make([]string, 0, len(config.CopyFiles))
if len(config.CopyFiles) > 0 {
ui.Say("Copying files from host to chroot...")
for _, path := range config.CopyFiles {
ui.Message(path)
chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath)
cmdText, err := wrappedCommand(fmt.Sprintf("cp --remove-destination %s %s", path, chrootPath))
if err != nil {
err := fmt.Errorf("Error building copy command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
stderr.Reset()
cmd := ShellCommand(cmdText)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error copying file: %s\nnStderr: %s", err, stderr.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.files = append(s.files, chrootPath)
}
}
state.Put("copy_files_cleanup", s)
return multistep.ActionContinue
}
func (s *StepCopyFiles) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepCopyFiles) CleanupFunc(state multistep.StateBag) error {
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if s.files != nil {
for _, file := range s.files {
log.Printf("Removing: %s", file)
localCmdText, err := wrappedCommand(fmt.Sprintf("rm -f %s", file))
if err != nil {
return err
}
localCmd := ShellCommand(localCmdText)
if err := localCmd.Run(); err != nil {
return err
}
}
}
s.files = nil
return nil
}

View File

@ -0,0 +1,11 @@
package chroot
import "testing"
func TestCopyFilesCleanupFunc_ImplementsCleanupFunc(t *testing.T) {
var raw interface{}
raw = new(StepCopyFiles)
if _, ok := raw.(Cleanup); !ok {
t.Fatalf("cleanup func should be a CleanupFunc")
}
}

View File

@ -0,0 +1,112 @@
package chroot
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"
)
// StepCreateOMI creates the OMI.
type StepCreateOMI struct {
RootVolumeSize int64
}
func (s *StepCreateOMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating the OMI...")
var (
registerOpts oapi.CreateImageRequest
mappings []oapi.BlockDeviceMappingImage
image oapi.Image
rootDeviceName string
)
if config.FromScratch {
mappings = config.OMIBlockDevices.BuildOMIDevices()
rootDeviceName = config.RootDeviceName
} else {
image = state.Get("source_image").(oapi.Image)
mappings = image.BlockDeviceMappings
rootDeviceName = image.RootDeviceName
}
newMappings := make([]oapi.BlockDeviceMappingImage, len(mappings))
for i, device := range mappings {
newDevice := device
//FIX: Temporary fix
gibSize := newDevice.Bsu.VolumeSize / (1024 * 1024 * 1024)
newDevice.Bsu.VolumeSize = gibSize
if newDevice.DeviceName == rootDeviceName {
if newDevice.Bsu != (oapi.BsuToCreate{}) {
newDevice.Bsu.SnapshotId = snapshotId
} else {
newDevice.Bsu = oapi.BsuToCreate{SnapshotId: snapshotId}
}
if config.FromScratch || s.RootVolumeSize > newDevice.Bsu.VolumeSize {
newDevice.Bsu.VolumeSize = s.RootVolumeSize
}
}
newMappings[i] = newDevice
}
if config.FromScratch {
registerOpts = oapi.CreateImageRequest{
ImageName: config.OMIName,
Architecture: "x86_64",
RootDeviceName: rootDeviceName,
BlockDeviceMappings: newMappings,
}
} else {
registerOpts = buildRegisterOpts(config, image, newMappings)
}
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
}
imageID := registerResp.OK.Image.ImageId
// Set the OMI ID in the state
ui.Say(fmt.Sprintf("OMI: %s", imageID))
omis := make(map[string]string)
omis[oapiconn.GetConfig().Region] = imageID
state.Put("omis", omis)
ui.Say("Waiting for OMI to become ready...")
if err := osccommon.WaitUntilImageAvailable(oapiconn, imageID); err != nil {
err := fmt.Errorf("Error waiting for OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCreateOMI) Cleanup(state multistep.StateBag) {}
func buildRegisterOpts(config *Config, image oapi.Image, mappings []oapi.BlockDeviceMappingImage) oapi.CreateImageRequest {
registerOpts := oapi.CreateImageRequest{
ImageName: config.OMIName,
Architecture: image.Architecture,
RootDeviceName: image.RootDeviceName,
BlockDeviceMappings: mappings,
}
return registerOpts
}

View File

@ -0,0 +1,166 @@
package chroot
import (
"context"
"errors"
"fmt"
"log"
osccommon "github.com/hashicorp/packer/builder/osc/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// StepCreateVolume creates a new volume from the snapshot of the root
// device of the OMI.
//
// Produces:
// volume_id string - The ID of the created volume
type StepCreateVolume struct {
volumeId string
RootVolumeSize int64
RootVolumeType string
RootVolumeTags osccommon.TagMap
Ctx interpolate.Context
}
func (s *StepCreateVolume) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
var err error
volTags, err := s.RootVolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var createVolume *oapi.CreateVolumeRequest
if config.FromScratch {
rootVolumeType := osccommon.VolumeTypeGp2
if s.RootVolumeType == "io1" {
err := errors.New("Cannot use io1 volume when building from scratch")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else if s.RootVolumeType != "" {
rootVolumeType = s.RootVolumeType
}
createVolume = &oapi.CreateVolumeRequest{
SubregionName: vm.Placement.SubregionName,
Size: s.RootVolumeSize,
VolumeType: rootVolumeType,
}
} else {
// Determine the root device snapshot
image := state.Get("source_image").(oapi.Image)
log.Printf("Searching for root device of the image (%s)", image.RootDeviceName)
var rootDevice *oapi.BlockDeviceMappingImage
for _, device := range image.BlockDeviceMappings {
if device.DeviceName == image.RootDeviceName {
rootDevice = &device
break
}
}
ui.Say("Creating the root volume...")
createVolume, err = s.buildCreateVolumeInput(vm.Placement.SubregionName, rootDevice)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
log.Printf("Create args: %+v", createVolume)
createVolumeResp, err := oapiconn.POST_CreateVolume(*createVolume)
if err != nil {
err := fmt.Errorf("Error creating root volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the volume ID so we remember to delete it later
s.volumeId = createVolumeResp.OK.Volume.VolumeId
log.Printf("Volume ID: %s", s.volumeId)
//Create tags for volume
if len(volTags) > 0 {
if err := osccommon.CreateTags(oapiconn, s.volumeId, ui, volTags); err != nil {
err := fmt.Errorf("Error creating tags for volume: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Wait for the volume to become ready
err = osccommon.WaitUntilVolumeAvailable(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("volume_id", s.volumeId)
return multistep.ActionContinue
}
func (s *StepCreateVolume) Cleanup(state multistep.StateBag) {
if s.volumeId == "" {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting the created BSU volume...")
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: s.volumeId})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting BSU volume: %s", err))
}
}
func (s *StepCreateVolume) buildCreateVolumeInput(suregionName string, rootDevice *oapi.BlockDeviceMappingImage) (*oapi.CreateVolumeRequest, error) {
if rootDevice == nil {
return nil, fmt.Errorf("Couldn't find root device!")
}
//FIX: Temporary fix
gibSize := rootDevice.Bsu.VolumeSize / (1024 * 1024 * 1024)
createVolumeInput := &oapi.CreateVolumeRequest{
SubregionName: suregionName,
Size: gibSize,
SnapshotId: rootDevice.Bsu.SnapshotId,
VolumeType: rootDevice.Bsu.VolumeType,
Iops: rootDevice.Bsu.Iops,
}
if s.RootVolumeSize > rootDevice.Bsu.VolumeSize {
createVolumeInput.Size = s.RootVolumeSize
}
if s.RootVolumeType == "" || s.RootVolumeType == rootDevice.Bsu.VolumeType {
return createVolumeInput, nil
}
if s.RootVolumeType == "io1" {
return nil, fmt.Errorf("Root volume type cannot be io1, because existing root volume type was %s", rootDevice.Bsu.VolumeType)
}
createVolumeInput.VolumeType = s.RootVolumeType
// non io1 cannot set iops
createVolumeInput.Iops = 0
return createVolumeInput, nil
}

View File

@ -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(ctx 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) {}

View File

@ -0,0 +1,30 @@
package chroot
import (
"context"
"fmt"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepEarlyUnflock unlocks the flock.
type StepEarlyUnflock struct{}
func (s *StepEarlyUnflock) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
cleanup := state.Get("flock_cleanup").(Cleanup)
ui := state.Get("ui").(packer.Ui)
log.Println("Unlocking file lock...")
if err := cleanup.CleanupFunc(state); err != nil {
err := fmt.Errorf("Error unlocking file lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepEarlyUnflock) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,74 @@
package chroot
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepFlock provisions the instance within a chroot.
//
// Produces:
// flock_cleanup Cleanup - To perform early cleanup
type StepFlock struct {
fh *os.File
}
func (s *StepFlock) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
lockfile := "/var/lock/packer-chroot/lock"
if err := os.MkdirAll(filepath.Dir(lockfile), 0755); err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Obtaining lock: %s", lockfile)
f, err := os.Create(lockfile)
if err != nil {
err := fmt.Errorf("Error creating lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// LOCK!
if err := lockFile(f); err != nil {
err := fmt.Errorf("Error obtaining lock: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the file handle, we can't close it because we need to hold
// the lock.
s.fh = f
state.Put("flock_cleanup", s)
return multistep.ActionContinue
}
func (s *StepFlock) Cleanup(state multistep.StateBag) {
s.CleanupFunc(state)
}
func (s *StepFlock) CleanupFunc(state multistep.StateBag) error {
if s.fh == nil {
return nil
}
log.Printf("Unlocking: %s", s.fh.Name())
if err := unlockFile(s.fh); err != nil {
return err
}
s.fh = nil
return nil
}

View File

@ -0,0 +1,96 @@
package chroot
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"
)
// 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 := device
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
}

View File

@ -0,0 +1,183 @@
package chroot
import (
"bytes"
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
type mountPathData struct {
Device string
}
// StepMountDevice mounts the attached device.
//
// Produces:
// mount_path string - The location where the volume was mounted.
// mount_device_cleanup CleanupFunc - To perform early cleanup
type StepMountDevice struct {
MountOptions []string
MountPartition string
mountPath string
}
func (s *StepMountDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
if config.NVMEDevicePath != "" {
// customizable device path for mounting NVME block devices on c5 and m5 HVM
device = config.NVMEDevicePath
}
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
var virtualizationType string
if config.FromScratch {
virtualizationType = config.OMIVirtType
} else {
//image := state.Get("source_image").(oapi.Image)
//Is always hvm
virtualizationType = "hvm"
log.Printf("Source image virtualization type is: %s", virtualizationType)
}
ctx := config.ctx
ctx.Data = &mountPathData{Device: filepath.Base(device)}
mountPath, err := interpolate.Render(config.MountPath, &ctx)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
mountPath, err = filepath.Abs(mountPath)
if err != nil {
err := fmt.Errorf("Error preparing mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("[DEBUG] Device: %s", device)
log.Printf("[DEBUG] Mount path: %s", mountPath)
if err := os.MkdirAll(mountPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
//Check the symbolic link for the device to get the real device name
cmd := ShellCommand(fmt.Sprintf("lsblk -no pkname $(readlink -f %s)", device))
realDeviceName, err := cmd.Output()
if err != nil {
err := fmt.Errorf(
"Error retrieving the symlink of the device %s.\n", device)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("[DEBUG] RealDeviceName: %s", realDeviceName)
realDeviceNameSplitted := strings.Split(string(realDeviceName), "\n")
log.Printf("[DEBUG] RealDeviceName Splitted %+v", realDeviceNameSplitted)
log.Printf("[DEBUG] RealDeviceName Splitted Length %d", len(realDeviceNameSplitted))
log.Printf("[DEBUG] RealDeviceName Splitted [0] %s", realDeviceNameSplitted[0])
log.Printf("[DEBUG] RealDeviceName Splitted [1] %s", realDeviceNameSplitted[1])
realDeviceNameStr := realDeviceNameSplitted[0]
if realDeviceNameStr == "" {
realDeviceNameStr = realDeviceNameSplitted[1]
}
deviceMount := fmt.Sprintf("/dev/%s", strings.Replace(realDeviceNameStr, "\n", "", -1))
log.Printf("[DEBUG] s.MountPartition = %s", s.MountPartition)
log.Printf("[DEBUG ] DeviceMount: %s", deviceMount)
if virtualizationType == "hvm" && s.MountPartition != "0" {
deviceMount = fmt.Sprintf("%s%s", deviceMount, s.MountPartition)
}
state.Put("deviceMount", deviceMount)
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
// build mount options from mount_options config, useful for nouuid options
// or other specific device type settings for mount
opts := ""
if len(s.MountOptions) > 0 {
opts = "-o " + strings.Join(s.MountOptions, " -o ")
}
mountCommand, err := wrappedCommand(
fmt.Sprintf("mount %s %s %s", opts, deviceMount, mountPath))
if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("[DEBUG] (step mount) mount command is %s", mountCommand)
cmd = ShellCommand(mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting root volume: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the mount path so we remember to unmount it later
s.mountPath = mountPath
state.Put("mount_path", s.mountPath)
state.Put("mount_device_cleanup", s)
return multistep.ActionContinue
}
func (s *StepMountDevice) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
if s.mountPath == "" {
return nil
}
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
ui.Say("Unmounting the root device...")
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
cmd := ShellCommand(unmountCommand)
if err := cmd.Run(); err != nil {
return fmt.Errorf("Error unmounting root device: %s", err)
}
s.mountPath = ""
return nil
}

View File

@ -0,0 +1,137 @@
package chroot
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"syscall"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepMountExtra mounts the attached device.
//
// Produces:
// mount_extra_cleanup CleanupFunc - To perform early cleanup
type StepMountExtra struct {
mounts []string
}
func (s *StepMountExtra) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
s.mounts = make([]string, 0, len(config.ChrootMounts))
ui.Say("Mounting additional paths within the chroot...")
for _, mountInfo := range config.ChrootMounts {
innerPath := mountPath + mountInfo[2]
if err := os.MkdirAll(innerPath, 0755); err != nil {
err := fmt.Errorf("Error creating mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
flags := "-t " + mountInfo[0]
if mountInfo[0] == "bind" {
flags = "--bind"
}
ui.Message(fmt.Sprintf("Mounting: %s", mountInfo[2]))
stderr := new(bytes.Buffer)
mountCommand, err := wrappedCommand(fmt.Sprintf(
"mount %s %s %s",
flags,
mountInfo[1],
innerPath))
if err != nil {
err := fmt.Errorf("Error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
cmd := ShellCommand(mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"Error mounting: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.mounts = append(s.mounts, innerPath)
}
state.Put("mount_extra_cleanup", s)
return multistep.ActionContinue
}
func (s *StepMountExtra) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
return
}
}
func (s *StepMountExtra) CleanupFunc(state multistep.StateBag) error {
if s.mounts == nil {
return nil
}
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
for len(s.mounts) > 0 {
var path string
lastIndex := len(s.mounts) - 1
path, s.mounts = s.mounts[lastIndex], s.mounts[:lastIndex]
grepCommand, err := wrappedCommand(fmt.Sprintf("grep %s /proc/mounts", path))
if err != nil {
return fmt.Errorf("Error creating grep command: %s", err)
}
// Before attempting to unmount,
// check to see if path is already unmounted
stderr := new(bytes.Buffer)
cmd := ShellCommand(grepCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
exitStatus := status.ExitStatus()
if exitStatus == 1 {
// path has already been unmounted
// just skip this path
continue
}
}
}
}
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", path))
if err != nil {
return fmt.Errorf("Error creating unmount command: %s", err)
}
stderr = new(bytes.Buffer)
cmd = ShellCommand(unmountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf(
"Error unmounting device: %s\nStderr: %s", err, stderr.String())
}
}
s.mounts = nil
return nil
}

View File

@ -0,0 +1,47 @@
package chroot
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type postMountCommandsData struct {
Device string
MountPath string
}
// StepPostMountCommands allows running arbitrary commands after mounting the
// device, but prior to the bind mount and copy steps.
type StepPostMountCommands struct {
Commands []string
}
func (s *StepPostMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
device := state.Get("device").(string)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if len(s.Commands) == 0 {
return multistep.ActionContinue
}
ictx := config.ctx
ictx.Data = &postMountCommandsData{
Device: device,
MountPath: mountPath,
}
ui.Say("Running post-mount commands...")
if err := RunLocalCommands(s.Commands, wrappedCommand, ictx, ui); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPostMountCommands) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,41 @@
package chroot
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type preMountCommandsData struct {
Device string
}
// StepPreMountCommands sets up the a new block device when building from scratch
type StepPreMountCommands struct {
Commands []string
}
func (s *StepPreMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
device := state.Get("device").(string)
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
if len(s.Commands) == 0 {
return multistep.ActionContinue
}
ictx := config.ctx
ictx.Data = &preMountCommandsData{Device: device}
ui.Say("Running device setup commands...")
if err := RunLocalCommands(s.Commands, wrappedCommand, ictx, ui); err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPreMountCommands) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,46 @@
package chroot
import (
"context"
"fmt"
"log"
"os"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepPrepareDevice finds an available device and sets it.
type StepPrepareDevice struct {
}
func (s *StepPrepareDevice) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
device := config.DevicePath
if device == "" {
var err error
log.Println("Device path not specified, searching for available device...")
device, err = AvailableDevice()
if err != nil {
err := fmt.Errorf("Error finding available device: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if _, err := os.Stat(device); err == nil {
err := fmt.Errorf("Device is in use: %s", device)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Device: %s", device)
state.Put("device", device)
return multistep.ActionContinue
}
func (s *StepPrepareDevice) Cleanup(state multistep.StateBag) {}

View File

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

View File

@ -0,0 +1,64 @@
package chroot
import (
"context"
"fmt"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// StepVmInfo verifies that this builder is running on an Outscale vm.
type StepVmInfo struct{}
func (s *StepVmInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
//session := state.Get("clientConfig").(*session.Session)
ui := state.Get("ui").(packer.Ui)
// Get our own vm ID
ui.Say("Gathering information about this Outscale vm...")
cmd := ShellCommand("curl http://169.254.169.254/latest/meta-data/instance-id")
vmID, err := cmd.Output()
if err != nil {
err := fmt.Errorf(
"Error retrieving the ID of the vm Packer is running on.\n" +
"Please verify Packer is running on a proper Outscale vm.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("[Debug] VmID got: %s", string(vmID))
// Query the entire vm metadata
resp, err := oapiconn.POST_ReadVms(oapi.ReadVmsRequest{Filters: oapi.FiltersVm{
VmIds: []string{string(vmID)},
}})
if err != nil {
err := fmt.Errorf("Error getting vm data: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vmsResp := resp.OK
if len(vmsResp.Vms) == 0 {
err := fmt.Errorf("Error getting vm data: no vm found.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vm := vmsResp.Vms[0]
state.Put("vm", vm)
return multistep.ActionContinue
}
func (s *StepVmInfo) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,106 @@
package common
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"os"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// AccessConfig is for common configuration related to Outscale API access
type AccessConfig struct {
AccessKey string `mapstructure:"access_key"`
CustomEndpointOAPI string `mapstructure:"custom_endpoint_oapi"`
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"`
MFACode string `mapstructure:"mfa_code"`
ProfileName string `mapstructure:"profile"`
RawRegion string `mapstructure:"region"`
SecretKey string `mapstructure:"secret_key"`
SkipValidation bool `mapstructure:"skip_region_validation"`
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
Token string `mapstructure:"token"`
clientConfig *oapi.Config
getOAPIConnection func() oapi.OAPIClient
}
// Config returns a valid oapi.Config object for access to Outscale services, or
// an error if the authentication and region couldn't be resolved
func (c *AccessConfig) Config() (*oapi.Config, error) {
if c.clientConfig != nil {
return c.clientConfig, nil
}
//Check env variables if access configuration is not set.
if c.AccessKey == "" {
c.AccessKey = os.Getenv("OUTSCALE_ACCESSKEYID")
}
if c.SecretKey == "" {
c.SecretKey = os.Getenv("OUTSCALE_SECRETKEYID")
}
if c.RawRegion == "" {
c.RawRegion = os.Getenv("OUTSCALE_REGION")
}
if c.CustomEndpointOAPI == "" {
c.CustomEndpointOAPI = os.Getenv("OUTSCALE_OAPI_URL")
}
if c.CustomEndpointOAPI == "" {
c.CustomEndpointOAPI = "outscale.com/oapi/latest"
}
config := &oapi.Config{
AccessKey: c.AccessKey,
SecretKey: c.SecretKey,
Region: c.RawRegion,
URL: c.CustomEndpointOAPI,
Service: "api",
}
return config, nil
}
func (c *AccessConfig) NewOAPIConnection() (oapi.OAPIClient, error) {
if c.getOAPIConnection != nil {
return c.getOAPIConnection(), nil
}
oapicfg, err := c.Config()
if err != nil {
return nil, err
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.InsecureSkipTLSVerify},
},
}
oapiClient := oapi.NewClient(oapicfg, skipClient)
return oapiClient, nil
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.SkipMetadataApiCheck {
log.Println("(WARN) skip_metadata_api_check ignored.")
}
// Either both access and secret key must be set or neither of them should
// be.
if (len(c.AccessKey) > 0) != (len(c.SecretKey) > 0) {
errs = append(errs,
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
}
return errs
}

View File

@ -0,0 +1,66 @@
package common
import (
"testing"
"github.com/outscale/osc-go/oapi"
)
type mockOAPIClient struct {
oapi.OAPIClient
}
func testAccessConfig() *AccessConfig {
return &AccessConfig{
getOAPIConnection: func() oapi.OAPIClient {
return &mockOAPIClient{}
},
}
}
func (m *mockOAPIClient) POST_ReadRegions(oapi.ReadRegionsRequest) (*oapi.POST_ReadRegionsResponses, error) {
return &oapi.POST_ReadRegionsResponses{
OK: &oapi.ReadRegionsResponse{
Regions: []oapi.Region{
{RegionEndpoint: "us-west1", RegionName: "us-west1"},
{RegionEndpoint: "us-east-1", RegionName: "us-east-1"},
},
},
}, nil
}
func TestAccessConfigPrepare_Region(t *testing.T) {
c := testAccessConfig()
c.RawRegion = "us-east-12"
err := c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "us-east-1"
err = c.ValidateRegion(c.RawRegion)
if err != nil {
t.Fatalf("shouldn't have region validation err: %s", c.RawRegion)
}
c.RawRegion = "custom"
err = c.ValidateRegion(c.RawRegion)
if err == nil {
t.Fatalf("should have region validation err: %s", c.RawRegion)
}
c.RawRegion = "custom"
c.SkipValidation = true
// testing whole prepare func here; this is checking that validation is
// skipped, so we don't need a mock connection
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.SkipValidation = false
c.RawRegion = ""
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
}

View File

@ -0,0 +1,135 @@
package common
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"sort"
"strings"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// Artifact is an artifact implementation that contains built OMIs.
type Artifact struct {
// A map of regions to OMI IDs.
Omis map[string]string
// BuilderId is the unique ID for the builder that created this OMI
BuilderIdValue string
// OAPI connection for performing API stuff.
Config *oapi.Config
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
parts := make([]string, 0, len(a.Omis))
for region, amiId := range a.Omis {
parts = append(parts, fmt.Sprintf("%s:%s", region, amiId))
}
sort.Strings(parts)
return strings.Join(parts, ",")
}
func (a *Artifact) String() string {
amiStrings := make([]string, 0, len(a.Omis))
for region, id := range a.Omis {
single := fmt.Sprintf("%s: %s", region, id)
amiStrings = append(amiStrings, single)
}
sort.Strings(amiStrings)
return fmt.Sprintf("OMIs were created:\n%s\n", strings.Join(amiStrings, "\n"))
}
func (a *Artifact) State(name string) interface{} {
switch name {
case "atlas.artifact.metadata":
return a.stateAtlasMetadata()
default:
return nil
}
}
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
for region, imageId := range a.Omis {
log.Printf("Deregistering image ID (%s) from region (%s)", imageId, region)
newConfig := &oapi.Config{
UserAgent: a.Config.UserAgent,
AccessKey: a.Config.AccessKey,
SecretKey: a.Config.SecretKey,
Service: a.Config.Service,
Region: region, //New region
URL: a.Config.URL,
}
log.Printf("[DEBUG] New Client config %+v", newConfig)
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionConn := oapi.NewClient(newConfig, skipClient)
// Get image metadata
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: []string{imageId},
},
})
if err != nil {
errors = append(errors, err)
}
if len(imageResp.OK.Images) == 0 {
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", imageId)
errors = append(errors, err)
}
// Deregister ami
input := oapi.DeleteImageRequest{
ImageId: imageId,
}
if _, err := regionConn.POST_DeleteImage(input); err != nil {
errors = append(errors, err)
}
// TODO: Delete the snapshots associated with an OMI too
}
if len(errors) > 0 {
if len(errors) == 1 {
return errors[0]
} else {
return &packer.MultiError{Errors: errors}
}
}
return nil
}
func (a *Artifact) stateAtlasMetadata() interface{} {
metadata := make(map[string]string)
for region, imageId := range a.Omis {
k := fmt.Sprintf("region.%s", region)
metadata[k] = imageId
}
return metadata
}

View File

@ -0,0 +1,154 @@
package common
import (
"fmt"
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
// BlockDevice
type BlockDevice struct {
DeleteOnVmDeletion bool `mapstructure:"delete_on_vm_deletion"`
DeviceName string `mapstructure:"device_name"`
IOPS int64 `mapstructure:"iops"`
NoDevice bool `mapstructure:"no_device"`
SnapshotId string `mapstructure:"snapshot_id"`
VirtualName string `mapstructure:"virtual_name"`
VolumeType string `mapstructure:"volume_type"`
VolumeSize int64 `mapstructure:"volume_size"`
}
type BlockDevices struct {
OMIBlockDevices `mapstructure:",squash"`
LaunchBlockDevices `mapstructure:",squash"`
}
type OMIBlockDevices struct {
OMIMappings []BlockDevice `mapstructure:"omi_block_device_mappings"`
}
type LaunchBlockDevices struct {
LaunchMappings []BlockDevice `mapstructure:"launch_block_device_mappings"`
}
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: aws.Bool(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 {
log.Printf("[DEBUG] Launch Block Device %#v", b)
var blockDevices []oapi.BlockDeviceMappingVmCreation
for _, blockDevice := range b {
mapping := oapi.BlockDeviceMappingVmCreation{
DeviceName: blockDevice.DeviceName,
}
if blockDevice.NoDevice {
mapping.NoDevice = ""
} else if blockDevice.VirtualName != "" {
if strings.HasPrefix(blockDevice.VirtualName, "ephemeral") {
mapping.VirtualDeviceName = blockDevice.VirtualName
}
} else {
bsu := oapi.BsuToCreate{
DeleteOnVmDeletion: aws.Bool(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 (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
if b.DeviceName == "" {
return fmt.Errorf("The `device_name` must be specified " +
"for every device in the block device mapping.")
}
return nil
}
func (b *BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
for _, d := range b.OMIMappings {
if err := d.Prepare(ctx); err != nil {
errs = append(errs, fmt.Errorf("OMIMapping: %s", err.Error()))
}
}
for _, d := range b.LaunchMappings {
if err := d.Prepare(ctx); err != nil {
errs = append(errs, fmt.Errorf("LaunchMapping: %s", err.Error()))
}
}
return errs
}
func (b *OMIBlockDevices) BuildOMIDevices() []oapi.BlockDeviceMappingImage {
return buildBlockDevicesImage(b.OMIMappings)
}
func (b *LaunchBlockDevices) BuildLaunchDevices() []oapi.BlockDeviceMappingVmCreation {
return buildBlockDevicesVmCreation(b.LaunchMappings)
}

View File

@ -0,0 +1,300 @@
package common
import (
"reflect"
"testing"
"github.com/outscale/osc-go/oapi"
)
func TestBlockDevice_LaunchDevices(t *testing.T) {
tr := new(bool)
f := new(bool)
*tr = true
*f = false
cases := []struct {
Config *BlockDevice
Result oapi.BlockDeviceMappingVmCreation
}{
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeSize: 8,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeSize: 8,
DeleteOnVmDeletion: f,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: true,
IOPS: 1000,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
Iops: 1000,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "standard",
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "standard",
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VirtualName: "ephemeral0",
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
VirtualDeviceName: "ephemeral0",
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
NoDevice: true,
},
Result: oapi.BlockDeviceMappingVmCreation{
DeviceName: "/dev/sdb",
NoDevice: "",
},
},
}
for _, tc := range cases {
launchBlockDevices := LaunchBlockDevices{
LaunchMappings: []BlockDevice{*tc.Config},
}
expected := []oapi.BlockDeviceMappingVmCreation{tc.Result}
launchResults := launchBlockDevices.BuildLaunchDevices()
if !reflect.DeepEqual(expected, launchResults) {
t.Fatalf("Bad block device, \nexpected: %#v\n\ngot: %#v",
expected, launchResults)
}
}
}
func TestBlockDevice_OMI(t *testing.T) {
tr := new(bool)
f := new(bool)
*tr = true
*f = false
cases := []struct {
Config *BlockDevice
Result oapi.BlockDeviceMappingImage
}{
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
SnapshotId: "snap-1234",
VolumeType: "standard",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeSize: 8,
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeSize: 8,
DeleteOnVmDeletion: f,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: true,
IOPS: 1000,
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "io1",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
Iops: 1000,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "gp2",
VolumeSize: 8,
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "standard",
DeleteOnVmDeletion: true,
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
Bsu: oapi.BsuToCreate{
VolumeType: "standard",
DeleteOnVmDeletion: tr,
},
},
},
{
Config: &BlockDevice{
DeviceName: "/dev/sdb",
VirtualName: "ephemeral0",
},
Result: oapi.BlockDeviceMappingImage{
DeviceName: "/dev/sdb",
VirtualDeviceName: "ephemeral0",
},
},
}
for _, tc := range cases {
omiBlockDevices := OMIBlockDevices{
OMIMappings: []BlockDevice{*tc.Config},
}
expected := []oapi.BlockDeviceMappingImage{tc.Result}
omiResults := omiBlockDevices.BuildOMIDevices()
if !reflect.DeepEqual(expected, omiResults) {
t.Fatalf("Bad block device, \nexpected: %+#v\n\ngot: %+#v",
expected, omiResults)
}
}
}

View File

@ -0,0 +1,157 @@
package common
import (
"log"
"strconv"
"github.com/outscale/osc-go/oapi"
)
func buildNetFilters(input map[string]string) oapi.FiltersNet {
var filters oapi.FiltersNet
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "ip-range":
filters.IpRanges = filterValue
case "dhcp-options-set-id":
filters.DhcpOptionsSetIds = filterValue
case "is-default":
if isDefault, err := strconv.ParseBool(v); err == nil {
filters.IsDefault = isDefault
}
case "state":
filters.States = filterValue
case "tag-key":
filters.TagKeys = filterValue
case "tag-value":
filters.TagValues = filterValue
default:
log.Printf("[Debug] Unknown Filter Name: %s.", name)
}
}
return filters
}
func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet {
var filters oapi.FiltersSubnet
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "available-ips-counts":
if ipCount, err := strconv.Atoi(v); err == nil {
filters.AvailableIpsCounts = []int64{int64(ipCount)}
}
case "ip-ranges":
filters.IpRanges = filterValue
case "net-ids":
filters.NetIds = filterValue
case "states":
filters.States = filterValue
case "subnet-ids":
filters.SubnetIds = filterValue
case "sub-region-names":
filters.SubregionNames = filterValue
default:
log.Printf("[Debug] Unknown Filter Name: %s.", name)
}
}
return filters
}
func buildOMIFilters(input map[string]string) oapi.FiltersImage {
var filters oapi.FiltersImage
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "account-alias":
filters.AccountAliases = filterValue
case "account-id":
filters.AccountIds = filterValue
case "architecture":
filters.Architectures = filterValue
case "image-id":
filters.ImageIds = filterValue
case "image-name":
filters.ImageNames = filterValue
case "image-type":
filters.ImageTypes = filterValue
case "virtualization-type":
filters.VirtualizationTypes = filterValue
case "root-device-type":
filters.RootDeviceTypes = filterValue
case "block-device-mapping-volume-type":
filters.BlockDeviceMappingVolumeType = filterValue
//Some params are missing.
default:
log.Printf("[WARN] Unknown Filter Name: %s.", name)
}
}
return filters
}
func buildSecurityGroupFilters(input map[string]string) oapi.FiltersSecurityGroup {
var filters oapi.FiltersSecurityGroup
for k, v := range input {
filterValue := []string{v}
switch name := k; name {
case "account-ids":
filters.AccountIds = filterValue
case "descriptions":
filters.Descriptions = filterValue
case "inbound-rule-account-ids":
filters.InboundRuleAccountIds = filterValue
case "inbound-rule-from-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.InboundRuleFromPortRanges = []int64{int64(val)}
}
case "inbound-rule-ip-ranges":
filters.InboundRuleIpRanges = filterValue
case "inbound-rule-protocols":
filters.InboundRuleProtocols = filterValue
case "inbound-rule-security-group-ids":
filters.InboundRuleSecurityGroupIds = filterValue
case "inbound-rule-security-group-names":
filters.InboundRuleSecurityGroupNames = filterValue
case "inbound-rule-to-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.InboundRuleToPortRanges = []int64{int64(val)}
}
case "net-ids":
filters.NetIds = filterValue
case "outbound-rule-account-ids":
filters.OutboundRuleAccountIds = filterValue
case "outbound-rule-from-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.OutboundRuleFromPortRanges = []int64{int64(val)}
}
case "outbound-rule-ip-ranges":
filters.OutboundRuleIpRanges = filterValue
case "outbound-rule-protocols":
filters.OutboundRuleProtocols = filterValue
case "outbound-rule-security-group-ids":
filters.OutboundRuleSecurityGroupIds = filterValue
case "outbound-rule-security-group-names":
filters.OutboundRuleSecurityGroupNames = filterValue
case "outbound-rule-to-port-ranges":
if val, err := strconv.Atoi(v); err == nil {
filters.OutboundRuleToPortRanges = []int64{int64(val)}
}
case "security-group-ids":
filters.SecurityGroupIds = filterValue
case "security-group-names":
filters.SecurityGroupNames = filterValue
case "tags-keys":
filters.TagKeys = filterValue
case "tags-values":
filters.TagValues = filterValue
//Some params are missing.
default:
log.Printf("[Debug] Unknown Filter Name: %s.", name)
}
}
return filters
}

View File

@ -0,0 +1,35 @@
package common
import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-go/oapi"
)
type BuildInfoTemplate struct {
BuildRegion string
SourceOMI string
SourceOMIName string
SourceOMITags map[string]string
}
func extractBuildInfo(region string, state multistep.StateBag) *BuildInfoTemplate {
rawSourceOMI, hasSourceOMI := state.GetOk("source_image")
if !hasSourceOMI {
return &BuildInfoTemplate{
BuildRegion: region,
}
}
sourceOMI := rawSourceOMI.(oapi.Image)
sourceOMITags := make(map[string]string, len(sourceOMI.Tags))
for _, tag := range sourceOMI.Tags {
sourceOMITags[tag.Key] = tag.Value
}
return &BuildInfoTemplate{
BuildRegion: region,
SourceOMI: sourceOMI.ImageId,
SourceOMIName: sourceOMI.ImageName,
SourceOMITags: sourceOMITags,
}
}

View File

@ -0,0 +1,62 @@
package common
import (
"reflect"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-go/oapi"
)
func testImage() oapi.Image {
return oapi.Image{
ImageId: "ami-abcd1234",
ImageName: "ami_test_name",
Tags: []oapi.ResourceTag{
{
Key: "key-1",
Value: "value-1",
},
{
Key: "key-2",
Value: "value-2",
},
},
}
}
func testState() multistep.StateBag {
state := new(multistep.BasicStateBag)
return state
}
func TestInterpolateBuildInfo_extractBuildInfo_noSourceImage(t *testing.T) {
state := testState()
buildInfo := extractBuildInfo("foo", state)
expected := BuildInfoTemplate{
BuildRegion: "foo",
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}
func TestInterpolateBuildInfo_extractBuildInfo_withSourceImage(t *testing.T) {
state := testState()
state.Put("source_image", testImage())
buildInfo := extractBuildInfo("foo", state)
expected := BuildInfoTemplate{
BuildRegion: "foo",
SourceOMI: "ami-abcd1234",
SourceOMIName: "ami_test_name",
SourceOMITags: map[string]string{
"key-1": "value-1",
"key-2": "value-2",
},
}
if !reflect.DeepEqual(*buildInfo, expected) {
t.Fatalf("Unexpected BuildInfoTemplate: expected %#v got %#v\n", expected, *buildInfo)
}
}

View File

@ -0,0 +1,91 @@
package common
import (
"fmt"
"log"
"github.com/hashicorp/packer/template/interpolate"
)
// OMIConfig is for common configuration related to creating OMIs.
type OMIConfig struct {
OMIName string `mapstructure:"omi_name"`
OMIDescription string `mapstructure:"omi_description"`
OMIVirtType string `mapstructure:"omi_virtualization_type"`
OMIAccountIDs []string `mapstructure:"omi_account_ids"`
OMIGroups []string `mapstructure:"omi_groups"`
OMIProductCodes []string `mapstructure:"omi_product_codes"`
OMIRegions []string `mapstructure:"omi_regions"`
OMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
OMITags TagMap `mapstructure:"tags"`
OMIForceDeregister bool `mapstructure:"force_deregister"`
OMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
SnapshotTags TagMap `mapstructure:"snapshot_tags"`
SnapshotAccountIDs []string `mapstructure:"snapshot_account_ids"`
SnapshotGroups []string `mapstructure:"snapshot_groups"`
}
func stringInSlice(s []string, searchstr string) bool {
for _, item := range s {
if item == searchstr {
return true
}
}
return false
}
func (c *OMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context) []error {
var errs []error
if c.OMIName == "" {
errs = append(errs, fmt.Errorf("omi_name must be specified"))
}
errs = append(errs, c.prepareRegions(accessConfig)...)
if len(c.OMIName) < 3 || len(c.OMIName) > 128 {
errs = append(errs, fmt.Errorf("omi_name must be between 3 and 128 characters long"))
}
if c.OMIName != templateCleanResourceName(c.OMIName) {
errs = append(errs, fmt.Errorf("OMIName should only contain "+
"alphanumeric characters, parentheses (()), square brackets ([]), spaces "+
"( ), periods (.), slashes (/), dashes (-), single quotes ('), at-signs "+
"(@), or underscores(_). You can use the `clean_omi_name` template "+
"filter to automatically clean your omi name."))
}
if len(errs) > 0 {
return errs
}
return nil
}
func (c *OMIConfig) prepareRegions(accessConfig *AccessConfig) (errs []error) {
if len(c.OMIRegions) > 0 {
regionSet := make(map[string]struct{})
regions := make([]string, 0, len(c.OMIRegions))
for _, region := range c.OMIRegions {
// If we already saw the region, then don't look again
if _, ok := regionSet[region]; ok {
continue
}
// Mark that we saw the region
regionSet[region] = struct{}{}
if (accessConfig != nil) && (region == accessConfig.RawRegion) {
// make sure we don't try to copy to the region we originally
// create the OMI in.
log.Printf("Cannot copy OMI to OUTSCALE session region '%s', deleting it from `omi_regions`.", region)
continue
}
regions = append(regions, region)
}
c.OMIRegions = regions
}
return errs
}

View File

@ -0,0 +1,148 @@
package common
import (
"fmt"
"reflect"
"testing"
)
func testOMIConfig() *OMIConfig {
return &OMIConfig{
OMIName: "foo",
}
}
func getFakeAccessConfig(region string) *AccessConfig {
c := testAccessConfig()
c.RawRegion = region
return c
}
func TestOMIConfigPrepare_name(t *testing.T) {
c := testOMIConfig()
accessConf := testAccessConfig()
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.OMIName = ""
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("should have error")
}
}
func TestOMIConfigPrepare_regions(t *testing.T) {
c := testOMIConfig()
c.OMIRegions = nil
var errs []error
var err error
accessConf := testAccessConfig()
mockConn := &mockOAPIClient{}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
c.OMIRegions, err = listOAPIRegions(mockConn)
if err != nil {
t.Fatalf("shouldn't have err: %s", err.Error())
}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("shouldn't have err: %#v", errs)
}
errs = errs[:0]
c.OMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatalf("bad: %s", errs[0])
}
expected := []string{"us-east-1", "us-west-1"}
if !reflect.DeepEqual(c.OMIRegions, expected) {
t.Fatalf("bad: %#v", c.OMIRegions)
}
c.OMIRegions = []string{"custom"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("shouldn't have error")
}
c.OMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal(fmt.Sprintf("shouldn't have error: %s", errs[0]))
}
c.OMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
}
c.SnapshotAccountIDs = []string{"user-foo", "user-bar"}
c.OMIRegions = []string{"us-east-1", "us-east-2", "us-west-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have an error b/c can't use default KMS key if sharing")
}
c.OMIRegions = []string{"us-east-1", "us-west-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in the key map that isn't in omi_regions")
}
c.OMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
c.SnapshotAccountIDs = []string{"foo", "bar"}
c.OMIRegions = []string{"us-east-1", "us-west-1"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should have error b/c theres a region in in omi_regions that isn't in the key map")
}
// allow rawregion to exist in omi_regions list.
accessConf = getFakeAccessConfig("us-east-1")
c.OMIRegions = []string{"us-east-1", "us-west-1", "us-east-2"}
if errs = c.prepareRegions(accessConf); len(errs) > 0 {
t.Fatal("should allow user to have the raw region in omi_regions")
}
}
func TestOMINameValidation(t *testing.T) {
c := testOMIConfig()
accessConf := testAccessConfig()
c.OMIName = "aa"
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an omi name with less than 3 characters")
}
var longOmiName string
for i := 0; i < 129; i++ {
longOmiName += "a"
}
c.OMIName = longOmiName
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an omi name with great than 128 characters")
}
c.OMIName = "+aaa"
if err := c.Prepare(accessConf, nil); err == nil {
t.Fatal("shouldn't be able to have an omi name with invalid characters")
}
c.OMIName = "fooBAR1()[] ./-'@_"
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatal("should be able to use all of the allowed OMI characters")
}
c.OMIName = `xyz-base-2017-04-05-1934`
if err := c.Prepare(accessConf, nil); err != nil {
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
}
}

View File

@ -0,0 +1,56 @@
package common
import (
"fmt"
"github.com/outscale/osc-go/oapi"
)
func listOAPIRegions(oapiconn oapi.OAPIClient) ([]string, error) {
var regions []string
resp, err := oapiconn.POST_ReadRegions(oapi.ReadRegionsRequest{})
if resp.OK == nil || err != nil {
return []string{}, err
}
resultRegions := resp.OK
for _, region := range resultRegions.Regions {
regions = append(regions, region.RegionName)
}
return regions, nil
}
// ValidateRegion returns true if the supplied region is a valid Outscale
// region and false if it's not.
func (c *AccessConfig) ValidateRegion(regions ...string) error {
oapiconn, err := c.NewOAPIConnection()
if err != nil {
return err
}
validRegions, err := listOAPIRegions(oapiconn)
if err != nil {
return err
}
var invalidRegions []string
for _, region := range regions {
found := false
for _, validRegion := range validRegions {
if region == validRegion {
found = true
break
}
}
if !found {
invalidRegions = append(invalidRegions, region)
}
}
if len(invalidRegions) > 0 {
return fmt.Errorf("Invalid region(s): %v", invalidRegions)
}
return nil
}

View File

@ -0,0 +1,219 @@
package common
import (
"fmt"
"net"
"os"
"regexp"
"strings"
"time"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/template/interpolate"
)
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
type OmiFilterOptions struct {
Filters map[string]string
Owners []string
MostRecent bool `mapstructure:"most_recent"`
}
func (d *OmiFilterOptions) Empty() bool {
return len(d.Owners) == 0 && len(d.Filters) == 0
}
func (d *OmiFilterOptions) NoOwner() bool {
return len(d.Owners) == 0
}
type SubnetFilterOptions struct {
Filters map[string]string
MostFree bool `mapstructure:"most_free"`
Random bool `mapstructure:"random"`
}
func (d *SubnetFilterOptions) Empty() bool {
return len(d.Filters) == 0
}
type NetFilterOptions struct {
Filters map[string]string
}
func (d *NetFilterOptions) Empty() bool {
return len(d.Filters) == 0
}
type SecurityGroupFilterOptions struct {
Filters map[string]string
}
func (d *SecurityGroupFilterOptions) Empty() bool {
return len(d.Filters) == 0
}
// RunConfig contains configuration for running an vm from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
Subregion string `mapstructure:"subregion_name"`
BlockDurationMinutes int64 `mapstructure:"block_duration_minutes"`
DisableStopVm bool `mapstructure:"disable_stop_vm"`
BsuOptimized bool `mapstructure:"bsu_optimized"`
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"`
IamVmProfile string `mapstructure:"iam_vm_profile"`
VmInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
VmType string `mapstructure:"vm_type"`
SecurityGroupFilter SecurityGroupFilterOptions `mapstructure:"security_group_filter"`
RunTags map[string]string `mapstructure:"run_tags"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SourceOmi string `mapstructure:"source_omi"`
SourceOmiFilter OmiFilterOptions `mapstructure:"source_omi_filter"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SpotTags map[string]string `mapstructure:"spot_tags"`
SubnetFilter SubnetFilterOptions `mapstructure:"subnet_filter"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
NetFilter NetFilterOptions `mapstructure:"net_filter"`
NetId string `mapstructure:"net_id"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
SSHInterface string `mapstructure:"ssh_interface"`
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// If we are not given an explicit ssh_keypair_name or
// ssh_private_key_file, then create a temporary one, but only if the
// temporary_key_pair_name has not been provided and we are not using
// ssh_password.
if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" {
c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
if c.WindowsPasswordTimeout == 0 {
c.WindowsPasswordTimeout = 20 * time.Minute
}
if c.RunTags == nil {
c.RunTags = make(map[string]string)
}
// Validation
errs := c.Comm.Prepare(ctx)
// Validating ssh_interface
if c.SSHInterface != "public_ip" &&
c.SSHInterface != "private_ip" &&
c.SSHInterface != "public_dns" &&
c.SSHInterface != "private_dns" &&
c.SSHInterface != "" {
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.SSHInterface))
}
if c.Comm.SSHKeyPairName != "" {
if c.Comm.Type == "winrm" && c.Comm.WinRMPassword == "" && c.Comm.SSHPrivateKeyFile == "" {
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided to retrieve the winrm password when using ssh_keypair_name."))
} else if c.Comm.SSHPrivateKeyFile == "" && !c.Comm.SSHAgentAuth {
errs = append(errs, fmt.Errorf("ssh_private_key_file must be provided or ssh_agent_auth enabled when ssh_keypair_name is specified."))
}
}
if c.SourceOmi == "" && c.SourceOmiFilter.Empty() {
errs = append(errs, fmt.Errorf("A source_omi or source_omi_filter must be specified"))
}
if c.SourceOmi == "" && c.SourceOmiFilter.NoOwner() {
errs = append(errs, fmt.Errorf("For security reasons, your source AMI filter must declare an owner."))
}
if c.VmType == "" {
errs = append(errs, fmt.Errorf("An vm_type must be specified"))
}
if c.BlockDurationMinutes%60 != 0 {
errs = append(errs, fmt.Errorf(
"block_duration_minutes must be multiple of 60"))
}
if c.SpotPrice == "auto" {
if c.SpotPriceAutoProduct == "" {
errs = append(errs, fmt.Errorf(
"spot_price_auto_product must be specified when spot_price is auto"))
}
}
if c.SpotPriceAutoProduct != "" {
if c.SpotPrice != "auto" {
errs = append(errs, fmt.Errorf(
"spot_price should be set to auto when spot_price_auto_product is specified"))
}
}
if c.SpotTags != nil {
if c.SpotPrice == "" || c.SpotPrice == "0" {
errs = append(errs, fmt.Errorf(
"spot_tags should not be set when not requesting a spot vm"))
}
}
if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
if c.SecurityGroupId != "" {
if len(c.SecurityGroupIds) > 0 {
errs = append(errs, fmt.Errorf("Only one of security_group_id or security_group_ids can be specified."))
} else {
c.SecurityGroupIds = []string{c.SecurityGroupId}
c.SecurityGroupId = ""
}
}
if c.TemporarySGSourceCidr == "" {
c.TemporarySGSourceCidr = "0.0.0.0/0"
} else {
if _, _, err := net.ParseCIDR(c.TemporarySGSourceCidr); err != nil {
errs = append(errs, fmt.Errorf("Error parsing temporary_security_group_source_cidr: %s", err.Error()))
}
}
if c.VmInitiatedShutdownBehavior == "" {
c.VmInitiatedShutdownBehavior = "stop"
} else if !reShutdownBehavior.MatchString(c.VmInitiatedShutdownBehavior) {
errs = append(errs, fmt.Errorf("shutdown_behavior only accepts 'stop' or 'terminate' values."))
}
if c.EnableT2Unlimited {
if c.SpotPrice != "" {
errs = append(errs, fmt.Errorf("Error: T2 Unlimited cannot be used in conjuction with Spot Vms"))
}
firstDotIndex := strings.Index(c.VmType, ".")
if firstDotIndex == -1 {
errs = append(errs, fmt.Errorf("Error determining main Vm Type from: %s", c.VmType))
} else if c.VmType[0:firstDotIndex] != "t2" {
errs = append(errs, fmt.Errorf("Error: T2 Unlimited enabled with a non-T2 Vm Type: %s", c.VmType))
}
}
return errs
}
func (c *RunConfig) IsSpotVm() bool {
return c.SpotPrice != "" && c.SpotPrice != "0"
}

View File

@ -0,0 +1,232 @@
package common
import (
"io/ioutil"
"os"
"regexp"
"testing"
"github.com/hashicorp/packer/helper/communicator"
)
func init() {
// Clear out the OUTSCALE access key env vars so they don't
// affect our tests.
os.Setenv("OUTSCALE_ACCESS_KEY_ID", "")
os.Setenv("OUTSCALE_ACCESS_KEY", "")
os.Setenv("OUTSCALE_SECRET_ACCESS_KEY", "")
os.Setenv("OUTSCALE_SECRET_KEY", "")
}
func testConfig() *RunConfig {
return &RunConfig{
SourceOmi: "abcd",
VmType: "m1.small",
Comm: communicator.Config{
SSHUsername: "foo",
},
}
}
func testConfigFilter() *RunConfig {
config := testConfig()
config.SourceOmi = ""
config.SourceOmiFilter = OmiFilterOptions{}
return config
}
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_VmType(t *testing.T) {
c := testConfig()
c.VmType = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if an vm_type is not specified")
}
}
func TestRunConfigPrepare_SourceOmi(t *testing.T) {
c := testConfig()
c.SourceOmi = ""
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if a source_omi (or source_omi_filter) is not specified")
}
}
func TestRunConfigPrepare_SourceOmiFilterBlank(t *testing.T) {
c := testConfigFilter()
if err := c.Prepare(nil); len(err) != 2 {
t.Fatalf("Should error if source_ami_filter is empty or not specified (and source_ami is not specified)")
}
}
func TestRunConfigPrepare_SourceOmiFilterOwnersBlank(t *testing.T) {
c := testConfigFilter()
filter_key := "name"
filter_value := "foo"
c.SourceOmiFilter = OmiFilterOptions{Filters: map[string]string{filter_key: filter_value}}
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if Owners is not specified)")
}
}
func TestRunConfigPrepare_SourceOmiFilterGood(t *testing.T) {
c := testConfigFilter()
owner := "123"
filter_key := "name"
filter_value := "foo"
goodFilter := OmiFilterOptions{Owners: []string{owner}, Filters: map[string]string{filter_key: filter_value}}
c.SourceOmiFilter = goodFilter
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedGood(t *testing.T) {
c := testConfig()
// Must have a T2 vm type if T2 Unlimited is enabled
c.VmType = "t2.micro"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadVmType(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with vm types other than T2
c.VmType = "m5.large"
c.EnableT2Unlimited = true
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited is enabled with non-T2 vm_type")
}
}
func TestRunConfigPrepare_EnableT2UnlimitedBadWithSpotInstanceRequest(t *testing.T) {
c := testConfig()
// T2 Unlimited cannot be used with Spot Instances
c.VmType = "t2.micro"
c.EnableT2Unlimited = true
c.SpotPrice = "auto"
c.SpotPriceAutoProduct = "Linux/UNIX"
err := c.Prepare(nil)
if len(err) != 1 {
t.Fatalf("Should error if T2 Unlimited has been used in conjuntion with a Spot Price request")
}
}
func TestRunConfigPrepare_SpotAuto(t *testing.T) {
c := testConfig()
c.SpotPrice = "auto"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if spot_price_auto_product is not set and spot_price is set to auto")
}
// Good - SpotPrice and SpotPriceAutoProduct are correctly set
c.SpotPriceAutoProduct = "foo"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.SpotPrice = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if spot_price is not set to auto and spot_price_auto_product is set")
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig()
c.Comm.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
c.Comm.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if user_data string and user_data_file have both been specified")
}
}
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c := testConfig()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if the file specified by user_data_file does not exist")
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
defer tf.Close()
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.Comm.SSHTemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
// Match prefix and UUID, e.g. "packer_5790d491-a0b8-c84c-c9d2-2aea55086550".
r := regexp.MustCompile(`\Apacker_(?:(?i)[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)\z`)
if !r.MatchString(c.Comm.SSHTemporaryKeyPairName) {
t.Fatal("keypair name is not valid")
}
c.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
}
}

84
builder/osc/common/ssh.go Normal file
View File

@ -0,0 +1,84 @@
package common
import (
"errors"
"fmt"
"time"
"github.com/hashicorp/packer/helper/multistep"
"github.com/outscale/osc-go/oapi"
)
type oapiDescriber interface {
POST_ReadVms(oapi.ReadVmsRequest) (*oapi.POST_ReadVmsResponses, error)
}
var (
// modified in tests
sshHostSleepDuration = time.Second
)
// SSHHost returns a function that can be given to the SSH communicator
// for determining the SSH address based on the vm DNS name.
func SSHHost(e oapiDescriber, sshInterface string) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
const tries = 2
// <= with current structure to check result of describing `tries` times
for j := 0; j <= tries; j++ {
var host string
i := state.Get("vm").(oapi.Vm)
if sshInterface != "" {
switch sshInterface {
case "public_ip":
if i.PublicIp != "" {
host = i.PublicIp
}
case "private_ip":
if i.PrivateIp != "" {
host = i.PrivateIp
}
case "public_dns":
if i.PublicDnsName != "" {
host = i.PublicDnsName
}
case "private_dns":
if i.PrivateDnsName != "" {
host = i.PrivateDnsName
}
default:
panic(fmt.Sprintf("Unknown interface type: %s", sshInterface))
}
} else if i.NetId != "" {
if i.PublicIp != "" {
host = i.PublicIp
} else if i.PrivateIp != "" {
host = i.PrivateIp
}
} else if i.PublicDnsName != "" {
host = i.PublicDnsName
}
if host != "" {
return host, nil
}
r, err := e.POST_ReadVms(oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{i.VmId},
},
})
if err != nil {
return "", err
}
if len(r.OK.Vms) == 0 {
return "", fmt.Errorf("vm not found: %s", i.VmId)
}
state.Put("vm", r.OK.Vms[0])
time.Sleep(sshHostSleepDuration)
}
return "", errors.New("couldn't determine address for vm")
}
}

323
builder/osc/common/state.go Normal file
View File

@ -0,0 +1,323 @@
package common
import (
"fmt"
"log"
"github.com/hashicorp/packer/common"
"github.com/outscale/osc-go/oapi"
)
type stateRefreshFunc func() (string, error)
func waitForSecurityGroup(conn *oapi.Client, securityGroupID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "exists", securityGroupWaitFunc(conn, securityGroupID))
err := <-errCh
return err
}
func waitUntilForVmRunning(conn *oapi.Client, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "running", waitUntilVmStateFunc(conn, vmID))
err := <-errCh
return err
}
func waitUntilVmDeleted(conn *oapi.Client, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "terminated", waitUntilVmStateFunc(conn, vmID))
return <-errCh
}
func waitUntilVmStopped(conn *oapi.Client, vmID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "stopped", waitUntilVmStateFunc(conn, vmID))
return <-errCh
}
func WaitUntilSnapshotCompleted(conn *oapi.Client, id string) error {
errCh := make(chan error, 1)
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
}
func WaitUntilVolumeAvailable(conn *oapi.Client, volumeID string) error {
errCh := make(chan error, 1)
go waitForState(errCh, "available", volumeWaitFunc(conn, volumeID))
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 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()
if err != nil {
return false, err
} else if state == target {
return true, nil
}
return false, nil
})
errCh <- err
return err
}
func waitUntilVmStateFunc(conn *oapi.Client, id string) stateRefreshFunc {
return func() (string, error) {
log.Printf("[Debug] Check if SG with id %s exists", id)
resp, err := conn.POST_ReadVms(oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []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.Vms) == 0 {
return "pending", nil
}
return resp.OK.Vms[0].State, nil
}
}
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)
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("Vm with ID %s. Not Found", id)
}
if len(resp.OK.Snapshots) == 0 {
return "pending", nil
}
return resp.OK.Snapshots[0].State, nil
}
}
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)
resp, err := conn.POST_ReadSecurityGroups(oapi.ReadSecurityGroupsRequest{
Filters: oapi.FiltersSecurityGroup{
SecurityGroupIds: []string{id},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Security Group with ID %s. Not Found", id)
}
if len(resp.OK.SecurityGroups) == 0 {
return "waiting", nil
}
return "exists", nil
}
}
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)
resp, err := conn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: []string{id},
},
})
log.Printf("[Debug] Read Response %+v", resp.OK)
if err != nil {
return "", err
}
if resp.OK == nil {
return "", fmt.Errorf("Volume with ID %s. Not Found", id)
}
if len(resp.OK.Volumes) == 0 {
return "waiting", nil
}
if resp.OK.Volumes[0].State == "error" {
return resp.OK.Volumes[0].State, fmt.Errorf("Volume (%s) creation is failed", id)
}
return resp.OK.Volumes[0].State, nil
}
}

View File

@ -0,0 +1,96 @@
package common
import (
"context"
"fmt"
"reflect"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// stepCleanupVolumes cleans up any orphaned volumes that were not designated to
// remain after termination of the vm. These volumes are typically ones
// that are marked as "delete on terminate:false" in the source_ami of a build.
type StepCleanupVolumes struct {
BlockDevices BlockDevices
}
func (s *StepCleanupVolumes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// stepCleanupVolumes is for Cleanup only
return multistep.ActionContinue
}
func (s *StepCleanupVolumes) Cleanup(state multistep.StateBag) {
oapiconn := state.Get("oapi").(*oapi.Client)
vmRaw := state.Get("vm")
var vm oapi.Vm
if vmRaw != nil {
vm = vmRaw.(oapi.Vm)
}
ui := state.Get("ui").(packer.Ui)
if vm.VmId == "" {
ui.Say("No volumes to clean up, skipping")
return
}
ui.Say("Cleaning up any extra volumes...")
// Collect Volume information from the cached Vm as a map of volume-id
// to device name, to compare with save list below
var vl []string
volList := make(map[string]string)
for _, bdm := range vm.BlockDeviceMappings {
if !reflect.DeepEqual(bdm.Bsu, oapi.BsuCreated{}) {
vl = append(vl, bdm.Bsu.VolumeId)
volList[bdm.Bsu.VolumeId] = bdm.DeviceName
}
}
// Using the volume list from the cached Vm, check with Outscale for up to
// date information on them
resp, err := oapiconn.POST_ReadVolumes(oapi.ReadVolumesRequest{
Filters: oapi.FiltersVolume{
VolumeIds: vl,
},
})
if err != nil {
ui.Say(fmt.Sprintf("Error describing volumes: %s", err))
return
}
// If any of the returned volumes are in a "deleting" stage or otherwise not
// available, remove them from the list of volumes
for _, v := range resp.OK.Volumes {
if v.State != "" && v.State != "available" {
delete(volList, v.VolumeId)
}
}
if len(resp.OK.Volumes) == 0 {
ui.Say("No volumes to clean up, skipping")
return
}
// Filter out any devices created as part of the launch mappings, since
// we'll let outscale follow the `delete_on_vm_deletion` setting.
for _, b := range s.BlockDevices.LaunchMappings {
for volKey, volName := range volList {
if volName == b.DeviceName {
delete(volList, volKey)
}
}
}
// Destroy remaining volumes
for k := range volList {
ui.Say(fmt.Sprintf("Destroying volume (%s)...", k))
_, err := oapiconn.POST_DeleteVolume(oapi.DeleteVolumeRequest{VolumeId: k})
if err != nil {
ui.Say(fmt.Sprintf("Error deleting volume: %s", err))
}
}
}

View File

@ -0,0 +1,151 @@
package common
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/aws/aws-sdk-go/aws/awserr"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
type StepCreateTags struct {
Tags TagMap
SnapshotTags TagMap
Ctx interpolate.Context
}
func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
config := state.Get("clientConfig").(*oapi.Config)
ui := state.Get("ui").(packer.Ui)
omis := state.Get("omis").(map[string]string)
if !s.Tags.IsSet() && !s.SnapshotTags.IsSet() {
return multistep.ActionContinue
}
// Adds tags to OMIs and snapshots
for region, ami := range omis {
ui.Say(fmt.Sprintf("Adding tags to OMI (%s)...", ami))
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionConn := oapi.NewClient(newConfig, skipClient)
// Retrieve image list for given OMI
resourceIds := []string{ami}
imageResp, err := regionConn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageIds: resourceIds,
},
})
if err != nil {
err := fmt.Errorf("Error retrieving details for OMI (%s): %s", ami, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.OK.Images) == 0 {
err := fmt.Errorf("Error retrieving details for OMI (%s), no images found", ami)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := imageResp.OK.Images[0]
snapshotIds := []string{}
// Add only those with a Snapshot ID, i.e. not Ephemeral
for _, device := range image.BlockDeviceMappings {
if device.Bsu.SnapshotId != "" {
ui.Say(fmt.Sprintf("Tagging snapshot: %s", device.Bsu.SnapshotId))
resourceIds = append(resourceIds, device.Bsu.SnapshotId)
snapshotIds = append(snapshotIds, device.Bsu.SnapshotId)
}
}
// Convert tags to oapi.Tag format
ui.Say("Creating OMI tags")
amiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
amiTags.Report(ui)
ui.Say("Creating snapshot tags")
snapshotTags, err := s.SnapshotTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
snapshotTags.Report(ui)
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
// Tag images and snapshots
_, err := regionConn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: resourceIds,
Tags: amiTags,
})
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidOMIID.NotFound" ||
awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
// Override tags on snapshots
if len(snapshotTags) > 0 {
_, err = regionConn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: snapshotIds,
Tags: snapshotTags,
})
}
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidSnapshot.NotFound" {
return false, nil
}
}
return true, err
})
if err != nil {
err := fmt.Errorf("Error adding tags to Resources (%#v): %s", resourceIds, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
// No cleanup...
}

View File

@ -0,0 +1,110 @@
package common
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
type StepDeregisterOMI struct {
AccessConfig *AccessConfig
ForceDeregister bool
ForceDeleteSnapshot bool
OMIName string
Regions []string
}
func (s *StepDeregisterOMI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// Check for force deregister
if !s.ForceDeregister {
return multistep.ActionContinue
}
ui := state.Get("ui").(packer.Ui)
oapiconn := state.Get("oapi").(*oapi.Client)
// Add the session region to list of regions will will deregister OMIs in
regions := append(s.Regions, oapiconn.GetConfig().Region)
for _, region := range regions {
// get new connection for each region in which we need to deregister vms
config, err := s.AccessConfig.Config()
if err != nil {
return multistep.ActionHalt
}
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionconn := oapi.NewClient(newConfig, skipClient)
resp, err := regionconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{
ImageNames: []string{s.OMIName},
AccountAliases: []string{"self"},
},
})
if err != nil {
err := fmt.Errorf("Error describing OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Deregister image(s) by name
for _, i := range resp.OK.Images {
//We are supposing that DeleteImage does the same action as DeregisterImage
_, err := regionconn.POST_DeleteImage(oapi.DeleteImageRequest{
ImageId: i.ImageId,
})
if err != nil {
err := fmt.Errorf("Error deregistering existing OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deregistered OMI %s, id: %s", s.OMIName, i.ImageId))
// Delete snapshot(s) by image
if s.ForceDeleteSnapshot {
for _, b := range i.BlockDeviceMappings {
if b.Bsu.SnapshotId != "" {
_, err := regionconn.POST_DeleteSnapshot(oapi.DeleteSnapshotRequest{
SnapshotId: b.Bsu.SnapshotId,
})
if err != nil {
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deleted snapshot: %s", b.Bsu.SnapshotId))
}
}
}
}
}
return multistep.ActionContinue
}
func (s *StepDeregisterOMI) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,179 @@
package common
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"log"
"time"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// StepGetPassword reads the password from a Windows server and sets it
// on the WinRM config.
type StepGetPassword struct {
Debug bool
Comm *communicator.Config
Timeout time.Duration
BuildName string
}
func (s *StepGetPassword) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
// Skip if we're not using winrm
if s.Comm.Type != "winrm" {
log.Printf("[INFO] Not using winrm communicator, skipping get password...")
return multistep.ActionContinue
}
// If we already have a password, skip it
if s.Comm.WinRMPassword != "" {
ui.Say("Skipping waiting for password since WinRM password set...")
return multistep.ActionContinue
}
// Get the password
var password string
var err error
cancel := make(chan struct{})
waitDone := make(chan bool, 1)
go func() {
ui.Say("Waiting for auto-generated password for vm...")
ui.Message(
"It is normal for this process to take up to 15 minutes,\n" +
"but it usually takes around 5. Please wait.")
password, err = s.waitForPassword(state, cancel)
waitDone <- true
}()
timeout := time.After(s.Timeout)
WaitLoop:
for {
// Wait for either SSH to become available, a timeout to occur,
// or an interrupt to come through.
select {
case <-waitDone:
if err != nil {
ui.Error(fmt.Sprintf("Error waiting for password: %s", err))
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf(" \nPassword retrieved!"))
s.Comm.WinRMPassword = password
break WaitLoop
case <-timeout:
err := fmt.Errorf("Timeout waiting for password.")
state.Put("error", err)
ui.Error(err.Error())
close(cancel)
return multistep.ActionHalt
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
// The step sequence was cancelled, so cancel waiting for password
// and just start the halting process.
close(cancel)
log.Println("[WARN] Interrupt detected, quitting waiting for password.")
return multistep.ActionHalt
}
}
}
// In debug-mode, we output the password
if s.Debug {
ui.Message(fmt.Sprintf(
"Password (since debug is enabled): %s", s.Comm.WinRMPassword))
}
// store so that we can access this later during provisioning
err = commonhelper.SetSharedState("winrm_password", s.Comm.WinRMPassword, s.BuildName)
if err != nil {
log.Printf("[WARN] commonhelper.SetSharedState returned error: %s", err)
}
packer.LogSecretFilter.Set(s.Comm.WinRMPassword)
return multistep.ActionContinue
}
func (s *StepGetPassword) Cleanup(multistep.StateBag) {
commonhelper.RemoveSharedStateFile("winrm_password", s.BuildName)
}
func (s *StepGetPassword) waitForPassword(state multistep.StateBag, cancel <-chan struct{}) (string, error) {
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
privateKey := s.Comm.SSHPrivateKey
for {
select {
case <-cancel:
log.Println("[INFO] Retrieve password wait cancelled. Exiting loop.")
return "", errors.New("Retrieve password wait cancelled")
case <-time.After(5 * time.Second):
}
resp, err := oapiconn.POST_ReadAdminPassword(oapi.ReadAdminPasswordRequest{
VmId: vm.VmId,
})
if err != nil {
err := fmt.Errorf("Error retrieving auto-generated vm password: %s", err)
return "", err
}
if resp.OK.AdminPassword != "" {
decryptedPassword, err := decryptPasswordDataWithPrivateKey(
resp.OK.AdminPassword, []byte(privateKey))
if err != nil {
err := fmt.Errorf("Error decrypting auto-generated vm password: %s", err)
return "", err
}
return decryptedPassword, nil
}
log.Printf("[DEBUG] Password is blank, will retry...")
}
}
func decryptPasswordDataWithPrivateKey(passwordData string, pemBytes []byte) (string, error) {
encryptedPasswd, err := base64.StdEncoding.DecodeString(passwordData)
if err != nil {
return "", err
}
block, _ := pem.Decode(pemBytes)
var asn1Bytes []byte
if _, ok := block.Headers["DEK-Info"]; ok {
return "", errors.New("encrypted private key isn't yet supported")
/*
asn1Bytes, err = x509.DecryptPEMBlock(block, password)
if err != nil {
return "", err
}
*/
} else {
asn1Bytes = block.Bytes
}
key, err := x509.ParsePKCS1PrivateKey(asn1Bytes)
if err != nil {
return "", err
}
out, err := rsa.DecryptPKCS1v15(nil, key, encryptedPasswd)
if err != nil {
return "", err
}
return string(out), nil
}

View File

@ -0,0 +1,123 @@
package common
import (
"context"
"fmt"
"os"
"runtime"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
type StepKeyPair struct {
Debug bool
Comm *communicator.Config
DebugKeyPath string
doCleanup bool
}
func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.Comm.SSHPrivateKeyFile != "" {
ui.Say("Using existing SSH private key")
privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
s.Comm.SSHPrivateKey = privateKeyBytes
return multistep.ActionContinue
}
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
ui.Say("Using SSH Agent with key pair in Source OMI")
return multistep.ActionContinue
}
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
return multistep.ActionContinue
}
if s.Comm.SSHTemporaryKeyPairName == "" {
ui.Say("Not using temporary keypair")
s.Comm.SSHKeyPairName = ""
return multistep.ActionContinue
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
keyResp, err := oapiconn.POST_CreateKeypair(oapi.CreateKeypairRequest{
KeypairName: s.Comm.SSHTemporaryKeyPairName})
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
}
s.doCleanup = true
// Set some data for use in future steps
s.Comm.SSHKeyPairName = s.Comm.SSHTemporaryKeyPairName
s.Comm.SSHPrivateKey = []byte(keyResp.OK.Keypair.PrivateKey)
// If we're in debug mode, output the private key to the working
// directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keyResp.OK.Keypair.PrivateKey)); err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
if !s.doCleanup {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
// Remove the keypair
ui.Say("Deleting temporary keypair...")
_, err := oapiconn.POST_DeleteKeypair(oapi.DeleteKeypairRequest{KeypairName: s.Comm.SSHTemporaryKeyPairName})
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.Comm.SSHTemporaryKeyPairName))
}
// Also remove the physical key if we're debugging.
if s.Debug {
if err := os.Remove(s.DebugKeyPath); err != nil {
ui.Error(fmt.Sprintf(
"Error removing debug key '%s': %s", s.DebugKeyPath, err))
}
}
}

View File

@ -0,0 +1,158 @@
package common
import (
"context"
"fmt"
"log"
"math/rand"
"sort"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// StepNetworkInfo queries OUTSCALE for information about
// NET's and Subnets that is used throughout the OMI creation process.
//
// Produces (adding them to the state bag):
// vpc_id string - the NET ID
// subnet_id string - the Subnet ID
// availability_zone string - the Subregion name
type StepNetworkInfo struct {
NetId string
NetFilter NetFilterOptions
SubnetId string
SubnetFilter SubnetFilterOptions
SubregionName string
SecurityGroupIds []string
SecurityGroupFilter SecurityGroupFilterOptions
}
type subnetsSort []oapi.Subnet
func (a subnetsSort) Len() int { return len(a) }
func (a subnetsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a subnetsSort) Less(i, j int) bool {
return a[i].AvailableIpsCount < a[j].AvailableIpsCount
}
// Returns the most recent OMI out of a slice of images.
func mostFreeSubnet(subnets []oapi.Subnet) oapi.Subnet {
sortedSubnets := subnets
sort.Sort(subnetsSort(sortedSubnets))
return sortedSubnets[len(sortedSubnets)-1]
}
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
// NET
if s.NetId == "" && !s.NetFilter.Empty() {
params := oapi.ReadNetsRequest{}
params.Filters = buildNetFilters(s.NetFilter.Filters)
s.NetFilter.Filters["state"] = "available"
log.Printf("Using NET Filters %v", params)
vpcResp, err := oapiconn.POST_ReadNets(params)
if err != nil {
err := fmt.Errorf("Error querying NETs: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(vpcResp.OK.Nets) != 1 {
err := fmt.Errorf("Exactly one NET should match the filter, but %d NET's was found matching filters: %v", len(vpcResp.OK.Nets), params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.NetId = vpcResp.OK.Nets[0].NetId
ui.Message(fmt.Sprintf("Found NET ID: %s", s.NetId))
}
// Subnet
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
params := oapi.ReadSubnetsRequest{}
s.SubnetFilter.Filters["state"] = "available"
if s.NetId != "" {
s.SubnetFilter.Filters["vpc-id"] = s.NetId
}
if s.SubregionName != "" {
s.SubnetFilter.Filters["availability-zone"] = s.SubregionName
}
params.Filters = buildSubnetFilters(s.SubnetFilter.Filters)
log.Printf("Using Subnet Filters %v", params)
subnetsResp, err := oapiconn.POST_ReadSubnets(params)
if err != nil {
err := fmt.Errorf("Error querying Subnets: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(subnetsResp.OK.Subnets) == 0 {
err := fmt.Errorf("No Subnets was found matching filters: %v", params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(subnetsResp.OK.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
err := fmt.Errorf("Your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true.", len(subnetsResp.OK.Subnets))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var subnet oapi.Subnet
switch {
case s.SubnetFilter.MostFree:
subnet = mostFreeSubnet(subnetsResp.OK.Subnets)
case s.SubnetFilter.Random:
subnet = subnetsResp.OK.Subnets[rand.Intn(len(subnetsResp.OK.Subnets))]
default:
subnet = subnetsResp.OK.Subnets[0]
}
s.SubnetId = subnet.SubnetId
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
}
// Try to find Subregion and NET Id from Subnet if they are not yet found/given
if s.SubnetId != "" && (s.SubregionName == "" || s.NetId == "") {
log.Printf("[INFO] Finding Subregion and NetId for the given subnet '%s'", s.SubnetId)
resp, err := oapiconn.POST_ReadSubnets(
oapi.ReadSubnetsRequest{
Filters: oapi.FiltersSubnet{
SubnetIds: []string{s.SubnetId},
},
})
if err != nil {
err := fmt.Errorf("Describing the subnet: %s returned error: %s.", s.SubnetId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.SubregionName == "" {
s.SubregionName = resp.OK.Subnets[0].SubregionName
log.Printf("[INFO] SubregionName found: '%s'", s.SubregionName)
}
if s.NetId == "" {
s.NetId = resp.OK.Subnets[0].NetId
log.Printf("[INFO] NetId found: '%s'", s.NetId)
}
}
state.Put("net_id", s.NetId)
state.Put("subregion_name", s.SubregionName)
state.Put("subnet_id", s.SubnetId)
return multistep.ActionContinue
}
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,61 @@
package common
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// StepPreValidate provides an opportunity to pre-validate any configuration for
// the build before actually doing any time consuming work
//
type StepPreValidate struct {
DestOmiName string
ForceDeregister bool
}
func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.ForceDeregister {
ui.Say("Force Deregister flag found, skipping prevalidating OMI Name")
return multistep.ActionContinue
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui.Say(fmt.Sprintf("Prevalidating OMI Name: %s", s.DestOmiName))
resp, err := oapiconn.POST_ReadImages(oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{ImageNames: []string{s.DestOmiName}},
})
if err != nil {
err := fmt.Errorf("Error querying OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
//FIXME: Remove when the oAPI filters works
images := make([]oapi.Image, 0)
for _, omi := range resp.OK.Images {
if omi.ImageName == s.DestOmiName {
images = append(images, omi)
}
}
//if len(resp.OK.Images) > 0 {
if len(images) > 0 {
err := fmt.Errorf("Error: name conflicts with an existing OMI: %s", resp.OK.Images[0].ImageId)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPreValidate) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,342 @@
package common
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"reflect"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/outscale/osc-go/oapi"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
const (
RunSourceVmBSUExpectedRootDevice = "ebs"
)
type StepRunSourceVm struct {
AssociatePublicIpAddress bool
BlockDevices BlockDevices
Comm *communicator.Config
Ctx interpolate.Context
Debug bool
BsuOptimized bool
EnableT2Unlimited bool
ExpectedRootDevice string
IamVmProfile string
VmInitiatedShutdownBehavior string
VmType string
IsRestricted bool
SourceOMI string
Tags TagMap
UserData string
UserDataFile string
VolumeTags TagMap
vmId string
}
func (s *StepRunSourceVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
securityGroupIds := state.Get("securityGroupIds").([]string)
ui := state.Get("ui").(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
}
userData = string(contents)
}
// Test if it is encoded already, and if not, encode it
if _, err := base64.StdEncoding.DecodeString(userData); err != nil {
log.Printf("[DEBUG] base64 encoding user data...")
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
ui.Say("Launching a source OUTSCALE vm...")
image, ok := state.Get("source_image").(oapi.Image)
if !ok {
state.Put("error", fmt.Errorf("source_image type assertion failed"))
return multistep.ActionHalt
}
s.SourceOMI = image.ImageId
if s.ExpectedRootDevice != "" && image.RootDeviceType != s.ExpectedRootDevice {
state.Put("error", fmt.Errorf(
"The provided source OMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, image.RootDeviceType))
return multistep.ActionHalt
}
var vmId string
ui.Say("Adding tags to source vm")
if _, exists := s.Tags["Name"]; !exists {
s.Tags["Name"] = "Packer Builder"
}
oapiTags, err := s.Tags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging source vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
volTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging volumes: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
subregion := state.Get("subregion_name").(string)
runOpts := oapi.CreateVmsRequest{
ImageId: s.SourceOMI,
VmType: s.VmType,
UserData: userData,
MaxVmsCount: 1,
MinVmsCount: 1,
Placement: oapi.Placement{SubregionName: subregion},
BsuOptimized: s.BsuOptimized,
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
//IamVmProfile: oapi.IamVmProfileSpecification{Name: &s.IamVmProfile},
}
// if s.EnableT2Unlimited {
// creditOption := "unlimited"
// runOpts.CreditSpecification = &oapi.CreditSpecificationRequest{CpuCredits: &creditOption}
// }
// Collect tags for tagging on resource creation
// var tagSpecs []oapi.ResourceTag
// if len(oapiTags) > 0 {
// runTags := &oapi.ResourceTag{
// ResourceType: aws.String("vm"),
// Tags: oapiTags,
// }
// tagSpecs = append(tagSpecs, runTags)
// }
// if len(volTags) > 0 {
// runVolTags := &oapi.TagSpecification{
// ResourceType: aws.String("volume"),
// Tags: volTags,
// }
// tagSpecs = append(tagSpecs, runVolTags)
// }
// // If our region supports it, set tag specifications
// if len(tagSpecs) > 0 && !s.IsRestricted {
// runOpts.SetTagSpecifications(tagSpecs)
// oapiTags.Report(ui)
// volTags.Report(ui)
// }
if s.Comm.SSHKeyPairName != "" {
runOpts.KeypairName = s.Comm.SSHKeyPairName
}
subnetId := state.Get("subnet_id").(string)
if subnetId != "" && s.AssociatePublicIpAddress {
runOpts.Nics = []oapi.NicForVmCreation{
{
DeviceNumber: 0,
//AssociatePublicIpAddress: s.AssociatePublicIpAddress,
SubnetId: subnetId,
SecurityGroupIds: securityGroupIds,
DeleteOnVmDeletion: true,
},
}
} else {
runOpts.SubnetId = subnetId
runOpts.SecurityGroupIds = securityGroupIds
}
if s.ExpectedRootDevice == "bsu" {
runOpts.VmInitiatedShutdownBehavior = s.VmInitiatedShutdownBehavior
}
runResp, err := oapiconn.POST_CreateVms(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vmId = runResp.OK.Vms[0].VmId
volumeId := runResp.OK.Vms[0].BlockDeviceMappings[0].Bsu.VolumeId
// Set the vm ID so that the cleanup works properly
s.vmId = vmId
ui.Message(fmt.Sprintf("Vm ID: %s", vmId))
ui.Say(fmt.Sprintf("Waiting for vm (%v) to become ready...", vmId))
request := oapi.ReadVmsRequest{
Filters: oapi.FiltersVm{
VmIds: []string{vmId},
},
}
if err := waitUntilForVmRunning(oapiconn, vmId); err != nil {
err := fmt.Errorf("Error waiting for vm (%s) to become ready: %s", vmId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
//Set Vm tags and vollume tags
if len(oapiTags) > 0 {
if err := CreateTags(oapiconn, s.vmId, ui, oapiTags); err != nil {
err := fmt.Errorf("Error creating tags for vm (%s): %s", s.vmId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if len(volTags) > 0 {
if err := CreateTags(oapiconn, volumeId, ui, volTags); err != nil {
err := fmt.Errorf("Error creating tags for volume (%s): %s", volumeId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
//TODO: LinkPublicIp i
resp, err := oapiconn.POST_ReadVms(request)
r := resp.OK
if err != nil || len(r.Vms) == 0 {
err := fmt.Errorf("Error finding source vm.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vm := r.Vms[0]
if s.Debug {
if vm.PublicDnsName != "" {
ui.Message(fmt.Sprintf("Public DNS: %s", vm.PublicDnsName))
}
if vm.PublicIp != "" {
ui.Message(fmt.Sprintf("Public IP: %s", vm.PublicIp))
}
if vm.PrivateIp != "" {
ui.Message(fmt.Sprintf("Private IP: %s", vm.PublicIp))
}
}
state.Put("vm", vm)
// If we're in a region that doesn't support tagging on vm creation,
// do that now.
if s.IsRestricted {
oapiTags.Report(ui)
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
_, err := oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
Tags: oapiTags,
ResourceIds: []string{vmId},
})
if err == nil {
return true, nil
}
//TODO: improve error
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidVmID.NotFound" {
return false, nil
}
}
return true, err
})
if err != nil {
err := fmt.Errorf("Error tagging source vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Now tag volumes
volumeIds := make([]string, 0)
for _, v := range vm.BlockDeviceMappings {
if bsu := v.Bsu; !reflect.DeepEqual(bsu, oapi.BsuCreated{}) {
volumeIds = append(volumeIds, bsu.VolumeId)
}
}
if len(volumeIds) > 0 && s.VolumeTags.IsSet() {
ui.Say("Adding tags to source BSU Volumes")
volumeTags, err := s.VolumeTags.OAPITags(s.Ctx, oapiconn.GetConfig().Region, state)
if err != nil {
err := fmt.Errorf("Error tagging source BSU Volumes on %s: %s", vm.VmId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
volumeTags.Report(ui)
_, err = oapiconn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: volumeIds,
Tags: volumeTags,
})
if err != nil {
err := fmt.Errorf("Error tagging source BSU Volumes on %s: %s", vm.VmId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepRunSourceVm) Cleanup(state multistep.StateBag) {
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
// Terminate the source vm if it exists
if s.vmId != "" {
ui.Say("Terminating the source OUTSCALE vm...")
if _, err := oapiconn.POST_DeleteVms(oapi.DeleteVmsRequest{VmIds: []string{s.vmId}}); err != nil {
ui.Error(fmt.Sprintf("Error terminating vm, may still be around: %s", err))
return
}
if err := waitUntilVmDeleted(oapiconn, s.vmId); err != nil {
ui.Error(err.Error())
}
}
}

View File

@ -0,0 +1,175 @@
package common
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
type StepSecurityGroup struct {
CommConfig *communicator.Config
SecurityGroupFilter SecurityGroupFilterOptions
SecurityGroupIds []string
TemporarySGSourceCidr string
createdGroupId string
}
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
netId := state.Get("net_id").(string)
if len(s.SecurityGroupIds) > 0 {
resp, err := oapiconn.POST_ReadSecurityGroups(
oapi.ReadSecurityGroupsRequest{
Filters: oapi.FiltersSecurityGroup{
SecurityGroupIds: s.SecurityGroupIds,
},
},
)
if err != nil || resp.OK == nil || len(resp.OK.SecurityGroups) <= 0 {
err := fmt.Errorf("Couldn't find specified security group: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
log.Printf("Using specified security groups: %v", s.SecurityGroupIds)
state.Put("securityGroupIds", s.SecurityGroupIds)
return multistep.ActionContinue
}
if !s.SecurityGroupFilter.Empty() {
params := oapi.ReadSecurityGroupsRequest{}
if netId != "" {
s.SecurityGroupFilter.Filters["net-id"] = netId
}
params.Filters = buildSecurityGroupFilters(s.SecurityGroupFilter.Filters)
log.Printf("Using SecurityGroup Filters %v", params)
sgResp, err := oapiconn.POST_ReadSecurityGroups(params)
if err != nil || sgResp.OK == nil {
err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
securityGroupIds := []string{}
for _, sg := range sgResp.OK.SecurityGroups {
securityGroupIds = append(securityGroupIds, sg.SecurityGroupId)
}
ui.Message(fmt.Sprintf("Found Security Group(s): %s", strings.Join(securityGroupIds, ", ")))
state.Put("securityGroupIds", securityGroupIds)
return multistep.ActionContinue
}
port := s.CommConfig.Port()
if port == 0 {
if s.CommConfig.Type != "none" {
panic("port must be set to a non-zero value.")
}
}
// Create the group
groupName := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName))
group := oapi.CreateSecurityGroupRequest{
SecurityGroupName: groupName,
Description: "Temporary group for Packer",
}
group.NetId = netId
groupResp, err := oapiconn.POST_CreateSecurityGroup(group)
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
// Set the group ID so we can delete it later
s.createdGroupId = groupResp.OK.SecurityGroup.SecurityGroupId
// Wait for the security group become available for authorizing
log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId)
err = waitForSecurityGroup(oapiconn, s.createdGroupId)
if err == nil {
log.Printf("[DEBUG] Found security group %s", s.createdGroupId)
} else {
err := fmt.Errorf("Timed out waiting for security group %s: %s", s.createdGroupId, err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
// Authorize the SSH access for the security group
groupRules := oapi.CreateSecurityGroupRuleRequest{
SecurityGroupId: groupResp.OK.SecurityGroup.SecurityGroupId,
Flow: "Inbound",
Rules: []oapi.SecurityGroupRule{
{
FromPortRange: int64(port),
ToPortRange: int64(port),
IpRanges: []string{s.TemporarySGSourceCidr},
IpProtocol: "tcp",
},
},
}
ui.Say(fmt.Sprintf(
"Authorizing access to port %d from %s in the temporary security group...",
port, s.TemporarySGSourceCidr))
_, err = oapiconn.POST_CreateSecurityGroupRule(groupRules)
if err != nil {
err := fmt.Errorf("Error authorizing temporary security group: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set some state data for use in future steps
state.Put("securityGroupIds", []string{s.createdGroupId})
return multistep.ActionContinue
}
func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
if s.createdGroupId == "" {
return
}
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary security group...")
var err error
for i := 0; i < 5; i++ {
_, err = oapiconn.POST_DeleteSecurityGroup(oapi.DeleteSecurityGroupRequest{SecurityGroupId: s.createdGroupId})
if err == nil {
break
}
log.Printf("Error deleting security group: %s", err)
time.Sleep(5 * time.Second)
}
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId))
}
}

View File

@ -0,0 +1,100 @@
package common
import (
"context"
"fmt"
"log"
"sort"
"time"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
// StepSourceOMIInfo extracts critical information from the source OMI
// that is used throughout the OMI creation process.
//
// Produces:
// source_image *oapi.Image - the source OMI info
type StepSourceOMIInfo struct {
SourceOmi string
OMIVirtType string
OmiFilters OmiFilterOptions
}
type imageSort []oapi.Image
func (a imageSort) Len() int { return len(a) }
func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a imageSort) Less(i, j int) bool {
itime, _ := time.Parse(time.RFC3339, a[i].CreationDate)
jtime, _ := time.Parse(time.RFC3339, a[j].CreationDate)
return itime.Unix() < jtime.Unix()
}
// Returns the most recent OMI out of a slice of images.
func mostRecentOmi(images []oapi.Image) oapi.Image {
sortedImages := images
sort.Sort(imageSort(sortedImages))
return sortedImages[len(sortedImages)-1]
}
func (s *StepSourceOMIInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
ui := state.Get("ui").(packer.Ui)
params := oapi.ReadImagesRequest{
Filters: oapi.FiltersImage{},
}
if s.SourceOmi != "" {
params.Filters.ImageIds = []string{s.SourceOmi}
}
// We have filters to apply
if len(s.OmiFilters.Filters) > 0 {
params.Filters = buildOMIFilters(s.OmiFilters.Filters)
}
//TODO:Check if AccountIds correspond to Owners.
if len(s.OmiFilters.Owners) > 0 {
params.Filters.AccountIds = s.OmiFilters.Owners
}
log.Printf("Using OMI Filters %#v", params)
imageResp, err := oapiconn.POST_ReadImages(params)
if err != nil {
err := fmt.Errorf("Error querying OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.OK.Images) == 0 {
err := fmt.Errorf("No OMI was found matching filters: %#v", params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(imageResp.OK.Images) > 1 && !s.OmiFilters.MostRecent {
err := fmt.Errorf("Your query returned more than one result. Please try a more specific search, or set most_recent to true.")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var image oapi.Image
if s.OmiFilters.MostRecent {
image = mostRecentOmi(imageResp.OK.Images)
} else {
image = imageResp.OK.Images[0]
}
ui.Message(fmt.Sprintf("Found Image ID: %s", image.ImageId))
state.Put("source_image", image)
return multistep.ActionContinue
}
func (s *StepSourceOMIInfo) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,95 @@
package common
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/outscale/osc-go/oapi"
)
type StepStopBSUBackedVm struct {
Skip bool
DisableStopVm bool
}
func (s *StepStopBSUBackedVm) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
vm := state.Get("vm").(oapi.Vm)
ui := state.Get("ui").(packer.Ui)
// Skip when it is a spot vm
if s.Skip {
return multistep.ActionContinue
}
var err error
if !s.DisableStopVm {
// Stop the vm so we can create an AMI from it
ui.Say("Stopping the source vm...")
// Amazon EC2 API follows an eventual consistency model.
// This means that if you run a command to modify or describe a resource
// that you just created, its ID might not have propagated throughout
// the system, and you will get an error responding that the resource
// does not exist.
// Work around this by retrying a few times, up to about 5 minutes.
err := common.Retry(10, 60, 6, func(i uint) (bool, error) {
ui.Message(fmt.Sprintf("Stopping vm, attempt %d", i+1))
_, err = oapiconn.POST_StopVms(oapi.StopVmsRequest{
VmIds: []string{vm.VmId},
})
if err == nil {
// success
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidVmID.NotFound" {
ui.Message(fmt.Sprintf(
"Error stopping vm; will retry ..."+
"Error: %s", err))
// retry
return false, nil
}
}
// errored, but not in expected way. Don't want to retry
return true, err
})
if err != nil {
err := fmt.Errorf("Error stopping vm: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
ui.Say("Automatic vm stop disabled. Please stop vm manually.")
}
// Wait for the vm to actually stop
ui.Say("Waiting for the vm to stop...")
err = waitUntilVmStopped(oapiconn, vm.VmId)
if err != nil {
err := fmt.Errorf("Error waiting for vm to stop: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepStopBSUBackedVm) Cleanup(multistep.StateBag) {
// No cleanup...
}

View File

@ -0,0 +1,63 @@
package common
import (
"context"
"github.com/hashicorp/packer/helper/multistep"
)
type StepUpdateBSUBackedVm struct {
EnableAMIENASupport *bool
EnableAMISriovNetSupport bool
}
func (s *StepUpdateBSUBackedVm) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// oapiconn := state.Get("oapi").(*oapi.Client)
// vm := state.Get("vm").(*oapi.Vm)
// ui := state.Get("ui").(packer.Ui)
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
// if s.EnableAMISriovNetSupport {
// ui.Say("Enabling Enhanced Networking (SR-IOV)...")
// simple := "simple"
// _, err := oapiconn.POST_UpdateVm(oapi.UpdateVmRequest{
// VmId: vm.VmId,
// SriovNetSupport: &oapi.AttributeValue{Value: &simple},
// })
// if err != nil {
// err := fmt.Errorf("Error enabling Enhanced Networking (SR-IOV) on %s: %s", *vm.VmId, err)
// state.Put("error", err)
// ui.Error(err.Error())
// return multistep.ActionHalt
// }
// }
// Handle EnaSupport flag.
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
// if s.EnableAMIENASupport != nil {
// var prefix string
// if *s.EnableAMIENASupport {
// prefix = "En"
// } else {
// prefix = "Dis"
// }
// ui.Say(fmt.Sprintf("%sabling Enhanced Networking (ENA)...", prefix))
// _, err := oapiconn.UpdateVmAttribute(&oapi.UpdateVmAttributeInput{
// VmId: vm.VmId,
// EnaSupport: &oapi.AttributeBooleanValue{Value: aws.Bool(*s.EnableAMIENASupport)},
// })
// if err != nil {
// err := fmt.Errorf("Error %sabling Enhanced Networking (ENA) on %s: %s", strings.ToLower(prefix), *vm.VmId, err)
// state.Put("error", err)
// ui.Error(err.Error())
// return multistep.ActionHalt
// }
// }
return multistep.ActionContinue
}
func (s *StepUpdateBSUBackedVm) Cleanup(state multistep.StateBag) {
// No cleanup...
}

View File

@ -0,0 +1,127 @@
package common
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
type StepUpdateOMIAttributes struct {
AccountIds []string
SnapshotAccountIds []string
Ctx interpolate.Context
}
func (s *StepUpdateOMIAttributes) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
oapiconn := state.Get("oapi").(*oapi.Client)
config := state.Get("clientConfig").(*oapi.Config)
ui := state.Get("ui").(packer.Ui)
omis := state.Get("omis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
// Determine if there is any work to do.
valid := false
valid = valid || (s.AccountIds != nil && len(s.AccountIds) > 0)
valid = valid || (s.SnapshotAccountIds != nil && len(s.SnapshotAccountIds) > 0)
if !valid {
return multistep.ActionContinue
}
s.Ctx.Data = extractBuildInfo(oapiconn.GetConfig().Region, state)
updateSnapshoptRequest := oapi.UpdateSnapshotRequest{
PermissionsToCreateVolume: oapi.PermissionsOnResourceCreation{
Additions: oapi.PermissionsOnResource{
AccountIds: s.AccountIds,
GlobalPermission: false,
},
},
}
updateImageRequest := oapi.UpdateImageRequest{
PermissionsToLaunch: oapi.PermissionsOnResourceCreation{
Additions: oapi.PermissionsOnResource{
AccountIds: s.AccountIds,
GlobalPermission: false,
},
},
}
// Updating image attributes
for region, omi := range omis {
ui.Say(fmt.Sprintf("Updating attributes on OMI (%s)...", omi))
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionconn := oapi.NewClient(newConfig, skipClient)
ui.Message(fmt.Sprintf("Updating: %s", omi))
updateImageRequest.ImageId = omi
_, err := regionconn.POST_UpdateImage(updateImageRequest)
if err != nil {
err := fmt.Errorf("Error updating OMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Updating snapshot attributes
for region, region_snapshots := range snapshots {
for _, snapshot := range region_snapshots {
ui.Say(fmt.Sprintf("Updating attributes on snapshot (%s)...", snapshot))
newConfig := &oapi.Config{
UserAgent: config.UserAgent,
AccessKey: config.AccessKey,
SecretKey: config.SecretKey,
Service: config.Service,
Region: region, //New region
URL: config.URL,
}
skipClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
regionconn := oapi.NewClient(newConfig, skipClient)
ui.Message(fmt.Sprintf("Updating: %s", snapshot))
updateSnapshoptRequest.SnapshotId = snapshot
_, err := regionconn.POST_UpdateSnapshot(updateSnapshoptRequest)
if err != nil {
err := fmt.Errorf("Error updating snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepUpdateOMIAttributes) Cleanup(state multistep.StateBag) {
// No cleanup...
}

View File

@ -0,0 +1,56 @@
package common
import (
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/outscale/osc-go/oapi"
)
type TagMap map[string]string
type OAPITags []oapi.ResourceTag
func (t OAPITags) Report(ui packer.Ui) {
for _, tag := range t {
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
tag.Key, tag.Value))
}
}
func (t TagMap) IsSet() bool {
return len(t) > 0
}
func (t TagMap) OAPITags(ctx interpolate.Context, region string, state multistep.StateBag) (OAPITags, error) {
var oapiTags []oapi.ResourceTag
ctx.Data = extractBuildInfo(region, state)
for key, value := range t {
interpolatedKey, err := interpolate.Render(key, &ctx)
if err != nil {
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
interpolatedValue, err := interpolate.Render(value, &ctx)
if err != nil {
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
oapiTags = append(oapiTags, oapi.ResourceTag{
Key: interpolatedKey,
Value: interpolatedValue,
})
}
return oapiTags, nil
}
func CreateTags(conn *oapi.Client, resourceID string, ui packer.Ui, tags OAPITags) error {
tags.Report(ui)
_, err := conn.POST_CreateTags(oapi.CreateTagsRequest{
ResourceIds: []string{resourceID},
Tags: tags,
})
return err
}

View File

@ -0,0 +1,18 @@
package common
const (
// VolumeTypeStandard is a VolumeType enum value
VolumeTypeStandard = "standard"
// VolumeTypeIo1 is a VolumeType enum value
VolumeTypeIo1 = "io1"
// VolumeTypeGp2 is a VolumeType enum value
VolumeTypeGp2 = "gp2"
// VolumeTypeSc1 is a VolumeType enum value
VolumeTypeSc1 = "sc1"
// VolumeTypeSt1 is a VolumeType enum value
VolumeTypeSt1 = "st1"
)

View File

@ -0,0 +1,37 @@
package common
import (
"bytes"
"html/template"
)
func isalphanumeric(b byte) bool {
if '0' <= b && b <= '9' {
return true
}
if 'a' <= b && b <= 'z' {
return true
}
if 'A' <= b && b <= 'Z' {
return true
}
return false
}
func templateCleanResourceName(s string) string {
allowed := []byte{'(', ')', '[', ']', ' ', '.', '/', '-', '\'', '@', '_'}
b := []byte(s)
newb := make([]byte, len(b))
for i, c := range b {
if isalphanumeric(c) || bytes.IndexByte(allowed, c) != -1 {
newb[i] = c
} else {
newb[i] = '-'
}
}
return string(newb[:])
}
var TemplateFuncs = template.FuncMap{
"clean_resource_name": templateCleanResourceName,
}

View File

@ -38,6 +38,10 @@ import (
openstackbuilder "github.com/hashicorp/packer/builder/openstack"
oracleclassicbuilder "github.com/hashicorp/packer/builder/oracle/classic"
oracleocibuilder "github.com/hashicorp/packer/builder/oracle/oci"
oscbsubuilder "github.com/hashicorp/packer/builder/osc/bsu"
oscbsusurrogatebuilder "github.com/hashicorp/packer/builder/osc/bsusurrogate"
oscbsuvolumebuilder "github.com/hashicorp/packer/builder/osc/bsuvolume"
oscchrootbuilder "github.com/hashicorp/packer/builder/osc/chroot"
parallelsisobuilder "github.com/hashicorp/packer/builder/parallels/iso"
parallelspvmbuilder "github.com/hashicorp/packer/builder/parallels/pvm"
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
@ -121,6 +125,10 @@ var Builders = map[string]packer.Builder{
"openstack": new(openstackbuilder.Builder),
"oracle-classic": new(oracleclassicbuilder.Builder),
"oracle-oci": new(oracleocibuilder.Builder),
"osc-bsu": new(oscbsubuilder.Builder),
"osc-bsusurrogate": new(oscbsusurrogatebuilder.Builder),
"osc-bsuvolume": new(oscbsuvolumebuilder.Builder),
"osc-chroot": new(oscchrootbuilder.Builder),
"parallels-iso": new(parallelsisobuilder.Builder),
"parallels-pvm": new(parallelspvmbuilder.Builder),
"profitbricks": new(profitbricksbuilder.Builder),

14
go.mod
View File

@ -12,18 +12,18 @@ require (
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af // indirect
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f
github.com/alvaroloes/enumer v1.1.2 // indirect
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd // indirect
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 // indirect
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6 // indirect
github.com/apache/thrift v0.12.0 // indirect
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.16.24
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae
github.com/cheggaaa/pb v1.0.27
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/creack/goselect v0.1.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 // indirect
@ -36,6 +36,7 @@ require (
github.com/exoscale/egoscale v0.18.1
github.com/go-ini/ini v1.25.4
github.com/gofrs/flock v0.7.1
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/google/go-cmp v0.2.0
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
github.com/google/uuid v1.0.0
@ -71,7 +72,6 @@ require (
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 // indirect
github.com/linode/linodego v0.7.1
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c // indirect
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c // indirect
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939
@ -81,13 +81,11 @@ require (
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7
github.com/mitchellh/go-homedir v1.0.0
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/iochan v1.0.0
github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784
github.com/mitchellh/reflectwalk v1.0.0
github.com/mna/pigeon v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef // indirect
@ -95,15 +93,16 @@ require (
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b
github.com/openzipkin/zipkin-go v0.1.6 // indirect
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/oracle/oci-go-sdk v1.8.0
github.com/outscale/osc-go v0.0.1
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a
github.com/pierrec/lz4 v2.0.5+incompatible
github.com/pkg/errors v0.8.0
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca
github.com/posener/complete v1.1.1
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 // indirect
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 // indirect
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 // indirect
@ -126,7 +125,6 @@ require (
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20190425145619-16072639606e
golang.org/x/text v0.3.1 // indirect
golang.org/x/tools v0.0.0-20190619215442-4adf7a708c2d // indirect
google.golang.org/api v0.4.0
google.golang.org/grpc v1.20.1
gopkg.in/h2non/gock.v1 v1.0.12 // indirect

78
go.sum
View File

@ -3,8 +3,6 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ=
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
@ -14,16 +12,10 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 h1:RMTyvS5bjvSWiUcfqfr/E2pxHEMrALvU+E12n6biymg=
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
github.com/Azure/azure-sdk-for-go v27.3.0+incompatible h1:i+ROfG3CsZUPoVAnhK06T3R6PmBzKB9ds+lHBpN7Mzo=
github.com/Azure/azure-sdk-for-go v27.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v30.0.0+incompatible h1:6o1Yzl7wTBYg+xw0pY4qnalaPmEQolubEEdepo1/kmI=
github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v10.12.0+incompatible h1:6YphwUK+oXbzvCc1fd5VrnxCekwzDkpA7gUEbci2MvI=
github.com/Azure/go-autorest v10.12.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v12.0.0+incompatible h1:N+VqClcomLGD/sHb3smbSYYtNMgKpVV3Cd5r5i8z6bQ=
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/tracing v0.1.0 h1:TRBxC5Pj/fIuh4Qob0ZpkggbfT8RC0SubHbpV3p4/Vc=
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -31,22 +23,14 @@ github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 h1:K9I21XUHN
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591 h1:/P9HCl71+Eh6vDbKNyRu+rpIIR70UCZWNOGexVV3e6k=
github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591/go.mod h1:EHGzQGbwozJBj/4qj3WGrTJ0FqjgOTOxLQ0VNWvPn08=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d h1:igrCnHheXb+lZ1bW9Ths8JZZIjh9D4Vi/49JqiHE+cI=
github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4 h1:o//09WenT9BNcQypCYfOBfRe5gtLUvUfTPq0xQqPMEI=
github.com/Telmate/proxmox-api-go v0.0.0-20190614181158-26cd147831a4/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e h1:/8wOj52pewmIX/8d5eVO3t7Rr3astkBI/ruyg4WNqRo=
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f h1:jI4DIE5Vf4oRaHfthB0oRhU+yuYuoOTurDzwAlskP00=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/alvaroloes/enumer v1.1.2 h1:5khqHB33TZy1GWCO/lZwcroBFh7u+0j40T83VUbfAMY=
github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY4psQHhhbbahASuNVj52YIce7lI=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
@ -54,7 +38,6 @@ github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2pt
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6 h1:uZuxRZCz65cG1o6K/xUqImNcYKtmk9ylqaH0itMSvzA=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43 h1:ePCAQPf5tUc5IMcUvu6euhSGna7jzs7eiXtJXHig6Zc=
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43/go.mod h1:S6puKjZ9ZeqUPBv2hEBnMZGcM2J6mOsDRQcmxkMAND0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -64,6 +47,7 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.16.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.16.24 h1:I/A3Hwbgs3IEAP6v1bFpHKXiT7wZDoToX9cb00nxZnM=
github.com/aws/aws-sdk-go v1.16.24/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -80,8 +64,12 @@ github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXG
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
@ -107,9 +95,6 @@ github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURU
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY=
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI=
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
@ -122,15 +107,11 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
@ -167,8 +148,6 @@ github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@ -183,12 +162,6 @@ github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSyth
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v1.2.0 h1:E05bVPilzyh2yXgT6srn7WEkfMZaH+LuX9tDJw/4kaE=
github.com/hashicorp/go-getter v1.2.0/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-getter v1.3.1-0.20190627194702-0c4919d4eb92 h1:Tck3az71eOyEcxueXyZfM4YO5oy2Hq8AbTpvc9HrQN0=
github.com/hashicorp/go-getter v1.3.1-0.20190627194702-0c4919d4eb92/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-getter v1.3.1-0.20190627194820-3fc511e2f341 h1:v+hsBJMakfQLeYkPuv/Y/T86jKvDQrOU1TyzximcF2Y=
github.com/hashicorp/go-getter v1.3.1-0.20190627194820-3fc511e2f341/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e h1:6krcdHPiS+aIP9XKzJzSahfjD7jG7Z+4+opm0z39V1M=
github.com/hashicorp/go-getter v1.3.1-0.20190627223108-da0323b9545e/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
@ -211,7 +184,6 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
@ -247,7 +219,6 @@ github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwK
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -263,7 +234,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGi
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 h1:YUrU1/jxRqnt0PSrKj1Uj/wEjk/fjnE80QFfi2Zlj7Q=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -274,8 +244,6 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE=
github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY=
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c h1:N7uWGS2fTwH/4BwxbHiJZNAFTSJ5yPU0emHsQWvkxEY=
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c h1:FMUOnVGy8nWk1cvlMCAoftRItQGMxI0vzJ3dQjeZTCE=
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg=
@ -307,8 +275,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI=
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -320,8 +286,6 @@ github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784 h1:+DAetXqxv/
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mna/pigeon v1.0.0 h1:n46IoStjdzjaXuyBH53j9HZ8CVqGWpC7P5/v8dP4qEY=
github.com/mna/pigeon v1.0.0/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
@ -330,7 +294,6 @@ github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef h1:E/seV1Rtsnr2juBw1
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U=
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 h1:y6FZWUBBt1iPmJyGbGza3ncvVBMKzgd32oFChRZR7Do=
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@ -345,15 +308,14 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/oracle/oci-go-sdk v1.8.0 h1:4SO45bKV0I3/Mn1os3ANDZmV0eSE5z5CLdSUIkxtyzs=
github.com/oracle/oci-go-sdk v1.8.0/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/outscale/osc-go v0.0.1 h1:hvBtORyu7sWSKW1norGlfIP8C7c2aegI2Vkq75SRPCE=
github.com/outscale/osc-go v0.0.1/go.mod h1:hJLmXzqU/t07qQYh90I0TqZzu9s85Zs6FMrxk3ukiFM=
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a h1:A3QMuteviunoaY/8ex+RKFqwhcZJ/Cf3fCW3IwL2wx4=
github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 h1:/I3lTljEEDNYLho3/FUB7iD/oc2cEFgVmbHzV+O0PtU=
github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
@ -367,16 +329,9 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible h1:ZoVHH6voxW9Onzo6z2yLtocVoN6mBocyDoqoyAMHokE=
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible/go.mod h1:T3/WrziK7fYH3C8ilAFAHe99R452/IzIG3YYkqaOFeQ=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 h1:4qPms2txLWMLXKzqlnYSulKRS4cS9aYgPtAEpUelQok=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@ -415,8 +370,6 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@ -451,10 +404,6 @@ github.com/yandex-cloud/go-sdk v0.0.0-20190402114215-3fc1d6947035 h1:2ZLZeg6xp+k
github.com/yandex-cloud/go-sdk v0.0.0-20190402114215-3fc1d6947035/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854=
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.20.1 h1:pMEjRZ1M4ebWGikflH7nQpV6+Zr88KBMA2XJD3sbijw=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2 h1:NAfh7zF0/3/HqtMvJNZ/RFrSlCE6ZTlHmKfhL/Dm1Jk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
@ -484,7 +433,6 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsq
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
@ -516,8 +464,6 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuq
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -541,17 +487,10 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190530184349-ce1a3806b557 h1:WFdP1eIY3AwGUPgVua5UIX4C7BzCIK8TOwm6RA+0vAQ=
golang.org/x/tools v0.0.0-20190530184349-ce1a3806b557/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190619215442-4adf7a708c2d h1:LQ06Vbju+Kwbcd94hb+6CgDsWoj/e7GOLPcYzHrG+iI=
golang.org/x/tools v0.0.0-20190619215442-4adf7a708c2d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.3.1 h1:oJra/lMfmtm13/rgY/8i3MzjFWYXvQIAKjQ3HqofMk8=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -572,11 +511,8 @@ google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9M
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=

10477
vendor/github.com/outscale/osc-go/oapi/client.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1213
vendor/github.com/outscale/osc-go/oapi/interface.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

527
vendor/github.com/outscale/osc-go/oapi/provider.go generated vendored Normal file
View File

@ -0,0 +1,527 @@
// GENERATED FILE: DO NOT EDIT!
package oapi
// To create a server, first write a class that implements this interface.
// Then pass an instance of it to Initialize().
type Provider interface {
//
POST_AcceptNetPeering(parameters *POST_AcceptNetPeeringParameters, responses *POST_AcceptNetPeeringResponses) (err error)
//
POST_AuthenticateAccount(parameters *POST_AuthenticateAccountParameters, responses *POST_AuthenticateAccountResponses) (err error)
//
POST_CheckSignature(parameters *POST_CheckSignatureParameters, responses *POST_CheckSignatureResponses) (err error)
//
POST_CopyAccount(parameters *POST_CopyAccountParameters, responses *POST_CopyAccountResponses) (err error)
//
POST_CreateAccount(parameters *POST_CreateAccountParameters, responses *POST_CreateAccountResponses) (err error)
//
POST_CreateApiKey(parameters *POST_CreateApiKeyParameters, responses *POST_CreateApiKeyResponses) (err error)
//
POST_CreateClientGateway(parameters *POST_CreateClientGatewayParameters, responses *POST_CreateClientGatewayResponses) (err error)
//
POST_CreateDhcpOptions(parameters *POST_CreateDhcpOptionsParameters, responses *POST_CreateDhcpOptionsResponses) (err error)
//
POST_CreateDirectLink(parameters *POST_CreateDirectLinkParameters, responses *POST_CreateDirectLinkResponses) (err error)
//
POST_CreateDirectLinkInterface(parameters *POST_CreateDirectLinkInterfaceParameters, responses *POST_CreateDirectLinkInterfaceResponses) (err error)
//
POST_CreateImage(parameters *POST_CreateImageParameters, responses *POST_CreateImageResponses) (err error)
//
POST_CreateImageExportTask(parameters *POST_CreateImageExportTaskParameters, responses *POST_CreateImageExportTaskResponses) (err error)
//
POST_CreateInternetService(parameters *POST_CreateInternetServiceParameters, responses *POST_CreateInternetServiceResponses) (err error)
//
POST_CreateKeypair(parameters *POST_CreateKeypairParameters, responses *POST_CreateKeypairResponses) (err error)
//
POST_CreateListenerRule(parameters *POST_CreateListenerRuleParameters, responses *POST_CreateListenerRuleResponses) (err error)
//
POST_CreateLoadBalancer(parameters *POST_CreateLoadBalancerParameters, responses *POST_CreateLoadBalancerResponses) (err error)
//
POST_CreateLoadBalancerListeners(parameters *POST_CreateLoadBalancerListenersParameters, responses *POST_CreateLoadBalancerListenersResponses) (err error)
//
POST_CreateLoadBalancerPolicy(parameters *POST_CreateLoadBalancerPolicyParameters, responses *POST_CreateLoadBalancerPolicyResponses) (err error)
//
POST_CreateNatService(parameters *POST_CreateNatServiceParameters, responses *POST_CreateNatServiceResponses) (err error)
//
POST_CreateNet(parameters *POST_CreateNetParameters, responses *POST_CreateNetResponses) (err error)
//
POST_CreateNetAccessPoint(parameters *POST_CreateNetAccessPointParameters, responses *POST_CreateNetAccessPointResponses) (err error)
//
POST_CreateNetPeering(parameters *POST_CreateNetPeeringParameters, responses *POST_CreateNetPeeringResponses) (err error)
//
POST_CreateNic(parameters *POST_CreateNicParameters, responses *POST_CreateNicResponses) (err error)
//
POST_CreatePolicy(parameters *POST_CreatePolicyParameters, responses *POST_CreatePolicyResponses) (err error)
//
POST_CreatePublicIp(parameters *POST_CreatePublicIpParameters, responses *POST_CreatePublicIpResponses) (err error)
//
POST_CreateRoute(parameters *POST_CreateRouteParameters, responses *POST_CreateRouteResponses) (err error)
//
POST_CreateRouteTable(parameters *POST_CreateRouteTableParameters, responses *POST_CreateRouteTableResponses) (err error)
//
POST_CreateSecurityGroup(parameters *POST_CreateSecurityGroupParameters, responses *POST_CreateSecurityGroupResponses) (err error)
//
POST_CreateSecurityGroupRule(parameters *POST_CreateSecurityGroupRuleParameters, responses *POST_CreateSecurityGroupRuleResponses) (err error)
//
POST_CreateServerCertificate(parameters *POST_CreateServerCertificateParameters, responses *POST_CreateServerCertificateResponses) (err error)
//
POST_CreateSnapshot(parameters *POST_CreateSnapshotParameters, responses *POST_CreateSnapshotResponses) (err error)
//
POST_CreateSnapshotExportTask(parameters *POST_CreateSnapshotExportTaskParameters, responses *POST_CreateSnapshotExportTaskResponses) (err error)
//
POST_CreateSubnet(parameters *POST_CreateSubnetParameters, responses *POST_CreateSubnetResponses) (err error)
//
POST_CreateTags(parameters *POST_CreateTagsParameters, responses *POST_CreateTagsResponses) (err error)
//
POST_CreateUser(parameters *POST_CreateUserParameters, responses *POST_CreateUserResponses) (err error)
//
POST_CreateUserGroup(parameters *POST_CreateUserGroupParameters, responses *POST_CreateUserGroupResponses) (err error)
//
POST_CreateVirtualGateway(parameters *POST_CreateVirtualGatewayParameters, responses *POST_CreateVirtualGatewayResponses) (err error)
//
POST_CreateVms(parameters *POST_CreateVmsParameters, responses *POST_CreateVmsResponses) (err error)
//
POST_CreateVolume(parameters *POST_CreateVolumeParameters, responses *POST_CreateVolumeResponses) (err error)
//
POST_CreateVpnConnection(parameters *POST_CreateVpnConnectionParameters, responses *POST_CreateVpnConnectionResponses) (err error)
//
POST_CreateVpnConnectionRoute(parameters *POST_CreateVpnConnectionRouteParameters, responses *POST_CreateVpnConnectionRouteResponses) (err error)
//
POST_DeleteApiKey(parameters *POST_DeleteApiKeyParameters, responses *POST_DeleteApiKeyResponses) (err error)
//
POST_DeleteClientGateway(parameters *POST_DeleteClientGatewayParameters, responses *POST_DeleteClientGatewayResponses) (err error)
//
POST_DeleteDhcpOptions(parameters *POST_DeleteDhcpOptionsParameters, responses *POST_DeleteDhcpOptionsResponses) (err error)
//
POST_DeleteDirectLink(parameters *POST_DeleteDirectLinkParameters, responses *POST_DeleteDirectLinkResponses) (err error)
//
POST_DeleteDirectLinkInterface(parameters *POST_DeleteDirectLinkInterfaceParameters, responses *POST_DeleteDirectLinkInterfaceResponses) (err error)
//
POST_DeleteExportTask(parameters *POST_DeleteExportTaskParameters, responses *POST_DeleteExportTaskResponses) (err error)
//
POST_DeleteImage(parameters *POST_DeleteImageParameters, responses *POST_DeleteImageResponses) (err error)
//
POST_DeleteInternetService(parameters *POST_DeleteInternetServiceParameters, responses *POST_DeleteInternetServiceResponses) (err error)
//
POST_DeleteKeypair(parameters *POST_DeleteKeypairParameters, responses *POST_DeleteKeypairResponses) (err error)
//
POST_DeleteListenerRule(parameters *POST_DeleteListenerRuleParameters, responses *POST_DeleteListenerRuleResponses) (err error)
//
POST_DeleteLoadBalancer(parameters *POST_DeleteLoadBalancerParameters, responses *POST_DeleteLoadBalancerResponses) (err error)
//
POST_DeleteLoadBalancerListeners(parameters *POST_DeleteLoadBalancerListenersParameters, responses *POST_DeleteLoadBalancerListenersResponses) (err error)
//
POST_DeleteLoadBalancerPolicy(parameters *POST_DeleteLoadBalancerPolicyParameters, responses *POST_DeleteLoadBalancerPolicyResponses) (err error)
//
POST_DeleteNatService(parameters *POST_DeleteNatServiceParameters, responses *POST_DeleteNatServiceResponses) (err error)
//
POST_DeleteNet(parameters *POST_DeleteNetParameters, responses *POST_DeleteNetResponses) (err error)
//
POST_DeleteNetAccessPoints(parameters *POST_DeleteNetAccessPointsParameters, responses *POST_DeleteNetAccessPointsResponses) (err error)
//
POST_DeleteNetPeering(parameters *POST_DeleteNetPeeringParameters, responses *POST_DeleteNetPeeringResponses) (err error)
//
POST_DeleteNic(parameters *POST_DeleteNicParameters, responses *POST_DeleteNicResponses) (err error)
//
POST_DeletePolicy(parameters *POST_DeletePolicyParameters, responses *POST_DeletePolicyResponses) (err error)
//
POST_DeletePublicIp(parameters *POST_DeletePublicIpParameters, responses *POST_DeletePublicIpResponses) (err error)
//
POST_DeleteRoute(parameters *POST_DeleteRouteParameters, responses *POST_DeleteRouteResponses) (err error)
//
POST_DeleteRouteTable(parameters *POST_DeleteRouteTableParameters, responses *POST_DeleteRouteTableResponses) (err error)
//
POST_DeleteSecurityGroup(parameters *POST_DeleteSecurityGroupParameters, responses *POST_DeleteSecurityGroupResponses) (err error)
//
POST_DeleteSecurityGroupRule(parameters *POST_DeleteSecurityGroupRuleParameters, responses *POST_DeleteSecurityGroupRuleResponses) (err error)
//
POST_DeleteServerCertificate(parameters *POST_DeleteServerCertificateParameters, responses *POST_DeleteServerCertificateResponses) (err error)
//
POST_DeleteSnapshot(parameters *POST_DeleteSnapshotParameters, responses *POST_DeleteSnapshotResponses) (err error)
//
POST_DeleteSubnet(parameters *POST_DeleteSubnetParameters, responses *POST_DeleteSubnetResponses) (err error)
//
POST_DeleteTags(parameters *POST_DeleteTagsParameters, responses *POST_DeleteTagsResponses) (err error)
//
POST_DeleteUser(parameters *POST_DeleteUserParameters, responses *POST_DeleteUserResponses) (err error)
//
POST_DeleteUserGroup(parameters *POST_DeleteUserGroupParameters, responses *POST_DeleteUserGroupResponses) (err error)
//
POST_DeleteVirtualGateway(parameters *POST_DeleteVirtualGatewayParameters, responses *POST_DeleteVirtualGatewayResponses) (err error)
//
POST_DeleteVms(parameters *POST_DeleteVmsParameters, responses *POST_DeleteVmsResponses) (err error)
//
POST_DeleteVolume(parameters *POST_DeleteVolumeParameters, responses *POST_DeleteVolumeResponses) (err error)
//
POST_DeleteVpnConnection(parameters *POST_DeleteVpnConnectionParameters, responses *POST_DeleteVpnConnectionResponses) (err error)
//
POST_DeleteVpnConnectionRoute(parameters *POST_DeleteVpnConnectionRouteParameters, responses *POST_DeleteVpnConnectionRouteResponses) (err error)
//
POST_DeregisterUserInUserGroup(parameters *POST_DeregisterUserInUserGroupParameters, responses *POST_DeregisterUserInUserGroupResponses) (err error)
//
POST_DeregisterVmsInLoadBalancer(parameters *POST_DeregisterVmsInLoadBalancerParameters, responses *POST_DeregisterVmsInLoadBalancerResponses) (err error)
//
POST_LinkInternetService(parameters *POST_LinkInternetServiceParameters, responses *POST_LinkInternetServiceResponses) (err error)
//
POST_LinkNic(parameters *POST_LinkNicParameters, responses *POST_LinkNicResponses) (err error)
//
POST_LinkPolicy(parameters *POST_LinkPolicyParameters, responses *POST_LinkPolicyResponses) (err error)
//
POST_LinkPrivateIps(parameters *POST_LinkPrivateIpsParameters, responses *POST_LinkPrivateIpsResponses) (err error)
//
POST_LinkPublicIp(parameters *POST_LinkPublicIpParameters, responses *POST_LinkPublicIpResponses) (err error)
//
POST_LinkRouteTable(parameters *POST_LinkRouteTableParameters, responses *POST_LinkRouteTableResponses) (err error)
//
POST_LinkVirtualGateway(parameters *POST_LinkVirtualGatewayParameters, responses *POST_LinkVirtualGatewayResponses) (err error)
//
POST_LinkVolume(parameters *POST_LinkVolumeParameters, responses *POST_LinkVolumeResponses) (err error)
//
POST_PurchaseReservedVmsOffer(parameters *POST_PurchaseReservedVmsOfferParameters, responses *POST_PurchaseReservedVmsOfferResponses) (err error)
//
POST_ReadAccount(parameters *POST_ReadAccountParameters, responses *POST_ReadAccountResponses) (err error)
//
POST_ReadAccountConsumption(parameters *POST_ReadAccountConsumptionParameters, responses *POST_ReadAccountConsumptionResponses) (err error)
//
POST_ReadAdminPassword(parameters *POST_ReadAdminPasswordParameters, responses *POST_ReadAdminPasswordResponses) (err error)
//
POST_ReadApiKeys(parameters *POST_ReadApiKeysParameters, responses *POST_ReadApiKeysResponses) (err error)
//
POST_ReadApiLogs(parameters *POST_ReadApiLogsParameters, responses *POST_ReadApiLogsResponses) (err error)
//
POST_ReadBillableDigest(parameters *POST_ReadBillableDigestParameters, responses *POST_ReadBillableDigestResponses) (err error)
//
POST_ReadCatalog(parameters *POST_ReadCatalogParameters, responses *POST_ReadCatalogResponses) (err error)
//
POST_ReadClientGateways(parameters *POST_ReadClientGatewaysParameters, responses *POST_ReadClientGatewaysResponses) (err error)
//
POST_ReadConsoleOutput(parameters *POST_ReadConsoleOutputParameters, responses *POST_ReadConsoleOutputResponses) (err error)
//
POST_ReadDhcpOptions(parameters *POST_ReadDhcpOptionsParameters, responses *POST_ReadDhcpOptionsResponses) (err error)
//
POST_ReadDirectLinkInterfaces(parameters *POST_ReadDirectLinkInterfacesParameters, responses *POST_ReadDirectLinkInterfacesResponses) (err error)
//
POST_ReadDirectLinks(parameters *POST_ReadDirectLinksParameters, responses *POST_ReadDirectLinksResponses) (err error)
//
POST_ReadImageExportTasks(parameters *POST_ReadImageExportTasksParameters, responses *POST_ReadImageExportTasksResponses) (err error)
//
POST_ReadImages(parameters *POST_ReadImagesParameters, responses *POST_ReadImagesResponses) (err error)
//
POST_ReadInternetServices(parameters *POST_ReadInternetServicesParameters, responses *POST_ReadInternetServicesResponses) (err error)
//
POST_ReadKeypairs(parameters *POST_ReadKeypairsParameters, responses *POST_ReadKeypairsResponses) (err error)
//
POST_ReadListenerRules(parameters *POST_ReadListenerRulesParameters, responses *POST_ReadListenerRulesResponses) (err error)
//
POST_ReadLoadBalancers(parameters *POST_ReadLoadBalancersParameters, responses *POST_ReadLoadBalancersResponses) (err error)
//
POST_ReadLocations(parameters *POST_ReadLocationsParameters, responses *POST_ReadLocationsResponses) (err error)
//
POST_ReadNatServices(parameters *POST_ReadNatServicesParameters, responses *POST_ReadNatServicesResponses) (err error)
//
POST_ReadNetAccessPointServices(parameters *POST_ReadNetAccessPointServicesParameters, responses *POST_ReadNetAccessPointServicesResponses) (err error)
//
POST_ReadNetAccessPoints(parameters *POST_ReadNetAccessPointsParameters, responses *POST_ReadNetAccessPointsResponses) (err error)
//
POST_ReadNetPeerings(parameters *POST_ReadNetPeeringsParameters, responses *POST_ReadNetPeeringsResponses) (err error)
//
POST_ReadNets(parameters *POST_ReadNetsParameters, responses *POST_ReadNetsResponses) (err error)
//
POST_ReadNics(parameters *POST_ReadNicsParameters, responses *POST_ReadNicsResponses) (err error)
//
POST_ReadPolicies(parameters *POST_ReadPoliciesParameters, responses *POST_ReadPoliciesResponses) (err error)
//
POST_ReadPrefixLists(parameters *POST_ReadPrefixListsParameters, responses *POST_ReadPrefixListsResponses) (err error)
//
POST_ReadProductTypes(parameters *POST_ReadProductTypesParameters, responses *POST_ReadProductTypesResponses) (err error)
//
POST_ReadPublicCatalog(parameters *POST_ReadPublicCatalogParameters, responses *POST_ReadPublicCatalogResponses) (err error)
//
POST_ReadPublicIpRanges(parameters *POST_ReadPublicIpRangesParameters, responses *POST_ReadPublicIpRangesResponses) (err error)
//
POST_ReadPublicIps(parameters *POST_ReadPublicIpsParameters, responses *POST_ReadPublicIpsResponses) (err error)
//
POST_ReadQuotas(parameters *POST_ReadQuotasParameters, responses *POST_ReadQuotasResponses) (err error)
//
POST_ReadRegionConfig(parameters *POST_ReadRegionConfigParameters, responses *POST_ReadRegionConfigResponses) (err error)
//
POST_ReadRegions(parameters *POST_ReadRegionsParameters, responses *POST_ReadRegionsResponses) (err error)
//
POST_ReadReservedVmOffers(parameters *POST_ReadReservedVmOffersParameters, responses *POST_ReadReservedVmOffersResponses) (err error)
//
POST_ReadReservedVms(parameters *POST_ReadReservedVmsParameters, responses *POST_ReadReservedVmsResponses) (err error)
//
POST_ReadRouteTables(parameters *POST_ReadRouteTablesParameters, responses *POST_ReadRouteTablesResponses) (err error)
//
POST_ReadSecurityGroups(parameters *POST_ReadSecurityGroupsParameters, responses *POST_ReadSecurityGroupsResponses) (err error)
//
POST_ReadServerCertificates(parameters *POST_ReadServerCertificatesParameters, responses *POST_ReadServerCertificatesResponses) (err error)
//
POST_ReadSnapshotExportTasks(parameters *POST_ReadSnapshotExportTasksParameters, responses *POST_ReadSnapshotExportTasksResponses) (err error)
//
POST_ReadSnapshots(parameters *POST_ReadSnapshotsParameters, responses *POST_ReadSnapshotsResponses) (err error)
//
POST_ReadSubnets(parameters *POST_ReadSubnetsParameters, responses *POST_ReadSubnetsResponses) (err error)
//
POST_ReadSubregions(parameters *POST_ReadSubregionsParameters, responses *POST_ReadSubregionsResponses) (err error)
//
POST_ReadTags(parameters *POST_ReadTagsParameters, responses *POST_ReadTagsResponses) (err error)
//
POST_ReadUserGroups(parameters *POST_ReadUserGroupsParameters, responses *POST_ReadUserGroupsResponses) (err error)
//
POST_ReadUsers(parameters *POST_ReadUsersParameters, responses *POST_ReadUsersResponses) (err error)
//
POST_ReadVirtualGateways(parameters *POST_ReadVirtualGatewaysParameters, responses *POST_ReadVirtualGatewaysResponses) (err error)
//
POST_ReadVmTypes(parameters *POST_ReadVmTypesParameters, responses *POST_ReadVmTypesResponses) (err error)
//
POST_ReadVms(parameters *POST_ReadVmsParameters, responses *POST_ReadVmsResponses) (err error)
//
POST_ReadVmsHealth(parameters *POST_ReadVmsHealthParameters, responses *POST_ReadVmsHealthResponses) (err error)
//
POST_ReadVmsState(parameters *POST_ReadVmsStateParameters, responses *POST_ReadVmsStateResponses) (err error)
//
POST_ReadVolumes(parameters *POST_ReadVolumesParameters, responses *POST_ReadVolumesResponses) (err error)
//
POST_ReadVpnConnections(parameters *POST_ReadVpnConnectionsParameters, responses *POST_ReadVpnConnectionsResponses) (err error)
//
POST_RebootVms(parameters *POST_RebootVmsParameters, responses *POST_RebootVmsResponses) (err error)
//
POST_RegisterUserInUserGroup(parameters *POST_RegisterUserInUserGroupParameters, responses *POST_RegisterUserInUserGroupResponses) (err error)
//
POST_RegisterVmsInLoadBalancer(parameters *POST_RegisterVmsInLoadBalancerParameters, responses *POST_RegisterVmsInLoadBalancerResponses) (err error)
//
POST_RejectNetPeering(parameters *POST_RejectNetPeeringParameters, responses *POST_RejectNetPeeringResponses) (err error)
//
POST_ResetAccountPassword(parameters *POST_ResetAccountPasswordParameters, responses *POST_ResetAccountPasswordResponses) (err error)
//
POST_SendResetPasswordEmail(parameters *POST_SendResetPasswordEmailParameters, responses *POST_SendResetPasswordEmailResponses) (err error)
//
POST_StartVms(parameters *POST_StartVmsParameters, responses *POST_StartVmsResponses) (err error)
//
POST_StopVms(parameters *POST_StopVmsParameters, responses *POST_StopVmsResponses) (err error)
//
POST_UnlinkInternetService(parameters *POST_UnlinkInternetServiceParameters, responses *POST_UnlinkInternetServiceResponses) (err error)
//
POST_UnlinkNic(parameters *POST_UnlinkNicParameters, responses *POST_UnlinkNicResponses) (err error)
//
POST_UnlinkPolicy(parameters *POST_UnlinkPolicyParameters, responses *POST_UnlinkPolicyResponses) (err error)
//
POST_UnlinkPrivateIps(parameters *POST_UnlinkPrivateIpsParameters, responses *POST_UnlinkPrivateIpsResponses) (err error)
//
POST_UnlinkPublicIp(parameters *POST_UnlinkPublicIpParameters, responses *POST_UnlinkPublicIpResponses) (err error)
//
POST_UnlinkRouteTable(parameters *POST_UnlinkRouteTableParameters, responses *POST_UnlinkRouteTableResponses) (err error)
//
POST_UnlinkVirtualGateway(parameters *POST_UnlinkVirtualGatewayParameters, responses *POST_UnlinkVirtualGatewayResponses) (err error)
//
POST_UnlinkVolume(parameters *POST_UnlinkVolumeParameters, responses *POST_UnlinkVolumeResponses) (err error)
//
POST_UpdateAccount(parameters *POST_UpdateAccountParameters, responses *POST_UpdateAccountResponses) (err error)
//
POST_UpdateApiKey(parameters *POST_UpdateApiKeyParameters, responses *POST_UpdateApiKeyResponses) (err error)
//
POST_UpdateHealthCheck(parameters *POST_UpdateHealthCheckParameters, responses *POST_UpdateHealthCheckResponses) (err error)
//
POST_UpdateImage(parameters *POST_UpdateImageParameters, responses *POST_UpdateImageResponses) (err error)
//
POST_UpdateKeypair(parameters *POST_UpdateKeypairParameters, responses *POST_UpdateKeypairResponses) (err error)
//
POST_UpdateListenerRule(parameters *POST_UpdateListenerRuleParameters, responses *POST_UpdateListenerRuleResponses) (err error)
//
POST_UpdateLoadBalancer(parameters *POST_UpdateLoadBalancerParameters, responses *POST_UpdateLoadBalancerResponses) (err error)
//
POST_UpdateNet(parameters *POST_UpdateNetParameters, responses *POST_UpdateNetResponses) (err error)
//
POST_UpdateNetAccessPoint(parameters *POST_UpdateNetAccessPointParameters, responses *POST_UpdateNetAccessPointResponses) (err error)
//
POST_UpdateNic(parameters *POST_UpdateNicParameters, responses *POST_UpdateNicResponses) (err error)
//
POST_UpdateRoute(parameters *POST_UpdateRouteParameters, responses *POST_UpdateRouteResponses) (err error)
//
POST_UpdateRoutePropagation(parameters *POST_UpdateRoutePropagationParameters, responses *POST_UpdateRoutePropagationResponses) (err error)
//
POST_UpdateServerCertificate(parameters *POST_UpdateServerCertificateParameters, responses *POST_UpdateServerCertificateResponses) (err error)
//
POST_UpdateSnapshot(parameters *POST_UpdateSnapshotParameters, responses *POST_UpdateSnapshotResponses) (err error)
//
POST_UpdateUser(parameters *POST_UpdateUserParameters, responses *POST_UpdateUserResponses) (err error)
//
POST_UpdateUserGroup(parameters *POST_UpdateUserGroupParameters, responses *POST_UpdateUserGroupResponses) (err error)
//
POST_UpdateVm(parameters *POST_UpdateVmParameters, responses *POST_UpdateVmResponses) (err error)
}

5660
vendor/github.com/outscale/osc-go/oapi/types.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

27
vendor/github.com/outscale/osc-go/utils/utils.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package utils
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
)
// DebugRequest ...
func DebugRequest(req *http.Request) {
requestDump, err := httputil.DumpRequest(req, true)
if err != nil {
fmt.Println(err)
}
log.Printf("[DEBUG] Request\n%s", string(requestDump))
}
// DebugResponse ...
func DebugResponse(res *http.Response) {
responseDump, err := httputil.DumpResponse(res, true)
if err != nil {
fmt.Println(err)
}
log.Printf("[DEBUG] Response\n%s", string(responseDump))
}

3
vendor/modules.txt vendored
View File

@ -376,6 +376,9 @@ github.com/olekukonko/tablewriter
# github.com/oracle/oci-go-sdk v1.8.0
github.com/oracle/oci-go-sdk/common
github.com/oracle/oci-go-sdk/core
# github.com/outscale/osc-go v0.0.1
github.com/outscale/osc-go/oapi
github.com/outscale/osc-go/utils
# github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a
github.com/packer-community/winrmcp/winrmcp
# github.com/pierrec/lz4 v2.0.5+incompatible

View File

@ -0,0 +1,355 @@
---
description: |
The osc-bsu Packer builder is able to create Outscale OMIs backed by BSU volumes for use in Outscale. For more information on the difference between
BSU-backed VMs and VM-store backed VMs, see the storage for
the root device section in the Outscale documentation.
layout: docs
page_title: 'Outscale BSU - Builders'
sidebar_current: 'docs-builders-osc-bsubacked'
---
# OMI Builder (BSU backed)
Type: `osc-bsu`
The `osc-bsu` Packer builder is able to create Outscale OMIs backed by BSU
volumes for use in [Flexible Compute Unit](https://wiki.outscale.net/pages/viewpage.action?pageId=43060893). For more information on
the difference between BSU-backed VMs and VM-store backed
VMs, see the ["storage for the root device" section in the Outscale
documentation](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings).
This builder builds an OMI by launching an Outscale VM from a source OMI,
provisioning that running machine, and then creating an OMI from that machine.
This is all done in your own Outscale account. The builder will create temporary
keypairs, security group rules, etc. that provide it temporary access to the
VM while the image is being created. This simplifies configuration quite
a bit.
The builder does *not* manage OMIs. Once it creates an OMI and stores it in
your account, it is up to you to use, delete, etc. the OMI.
-&gt; **Note:** Temporary resources are, by default, all created with the
prefix `packer`. This can be useful if you want to restrict the security groups
and key pairs Packer is able to operate on.
## Configuration Reference
There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
In addition to the options listed here, a
[communicator](../templates/communicator.html) can be configured for this
builder.
### Required:
- `access_key` (string) - The access key used to communicate with OUTSCALE. [Learn how to set this](outscale.html#authentication)
- `omi_name` (string) - The name of the resulting OMIS that will appear when managing OMIs in the Outscale console or via APIs. This must be unique. To help make this unique, use a function like `timestamp` (see [template engine](../templates/engine.html) for more info).
- `vm_type` (string) - The Outscale VM type to use while building the OMI, such as `t2.small`.
- `region` (string) - The name of the region, such as `us-east-1`, in which to launch the Outscale VM to create the OMI.
- `secret_key` (string) - The secret key used to communicate with Outscale. [Learn how to set this](outscale.html#authentication)
- `source_omi` (string) - The initial OMI used as a base for the newly created machine. `source_omi_filter` may be used instead to populate this automatically.
### Optional:
- `omi_block_device_mappings` (array of block device mappings) - Add one or more [block device mappings](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) to the OMI. These will be attached when booting a new VM from your OMI. To add a block device during the Packer build see `launch_block_device_mappings` below. Your options here may vary depending on the type of VM you use. The block device mappings allow for the following configuration:
- `delete_on_vm_deletion` (boolean) - Indicates whether the BSU volume is deleted on VM termination. Default `false`. **NOTE**: If this value is not explicitly set to `true` and volumes are not cleaned up by an alternative method, additional volumes will accumulate after every build.
- `device_name` (string) - The device name exposed to the VM (for example, `/dev/sdh` or `xvdh`). Required for every device in the block device mapping.
- `iops` (number) - The number of I/O operations per second (IOPS) that the volume supports. See the documentation on
[IOPs](https://wiki.outscale.net/display/EN/About+Volumes#AboutVolumes-VolumeTypesVolumeTypesandIOPS)
for more information
- `no_device` (boolean) - Suppresses the specified device included in the
block device mapping of the OMI
- `snapshot_id` (string) - The ID of the snapshot
- `virtual_name` (string) - The virtual device name. See the documentation on [Block Device Mapping](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) for more information
- `volume_size` (number) - The size of the volume, in GiB. Required if not specifying a `snapshot_id`
- `volume_type` (string) - The volume type. `gp2` for General Purpose (SSD) volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard` for Magnetic volumes
- `omi_description` (string) - The description to set for the resulting OMI(s). By default this description is empty. This is a [template engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `omi_account_ids` (array of strings) - A list of account IDs that have access to launch the resulting OMI(s). By default no additional users other than the user creating the OMIS has permissions to launch it.
- `omi_virtualization_type` (string) - The type of virtualization for the OMI you are building. This option must match the supported virtualization type of `source_omi`. Can be `paravirtual` or `hvm`.
- `associate_public_ip_address` (boolean) - If using a non-default Net, public IP addresses are not provided by default. If this is toggled, your new VM will get a Public IP.
- `subregion_name` (string) - Destination subregion to launch VM in. Leave this empty to allow Outscale to auto-assign.
- `custom_endpoint_oapi` (string) - This option is useful if you use a cloud
provider whose API is compatible with Outscale OAPI. Specify another endpoint
like this `outscale.com/oapi/latest`.
- `disable_stop_vm` (boolean) - Packer normally stops the build
VM after all provisioners have run. For Windows VMs, it is
sometimes desirable to [run Sysprep](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc721940(v=ws.10)) which will stop the VM for you. If this is set to `true`, Packer
*will not* stop the VM but will assume that you will send the stop
signal yourself through your final provisioner. You can do this with a
[windows-shell provisioner](https://www.packer.io/docs/provisioners/windows-shell.html).
Note that Packer will still wait for the VM to be stopped, and
failing to send the stop signal yourself, when you have set this flag to
`true`, will cause a timeout.
- `bsu_optimized` (boolean) - If true, the VM is created with optimized BSU I/O.
- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots
associated with OMIs, which have been deregistered by `force_deregister`.
Default `false`.
- `force_deregister` (boolean) - Force Packer to first deregister an existing
OMIS if one with the same name already exists. Default `false`.
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS
verification of the OAPI endpoint. The default is `false`.
- `launch_block_device_mappings` (array of block device mappings) - Add one
or more block devices before the Packer build starts. If you add VM
store volumes or BSU volumes in addition to the root device volume, the
created OMIS will contain block device mapping information for those
volumes. Outscale creates snapshots of the source VM's root volume and
any other BSU volumes described here. When you launch an VM from this
new OMI, the VM automatically launches with these additional volumes,
and will restore them from snapshots taken from the source VM.
- `run_tags` (object of key/value strings) - Tags to apply to the VM
that is *launched* to create the OMI. These tags are *not* applied to the
resulting OMIS unless they're duplicated in `tags`. This is a [template
engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `run_volume_tags` (object of key/value strings) - Tags to apply to the
volumes that are *launched* to create the OMI. These tags are *not* applied
to the resulting OMIS unless they're duplicated in `tags`. This is a
[template engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `security_group_id` (string) - The ID (*not* the name) of the security
group to assign to the VM. By default this is not set and Packer will
automatically create a new temporary security group to allow SSH access.
Note that if this is specified, you must be sure the security group allows
access to the `ssh_port` given below.
- `security_group_ids` (array of strings) - A list of security groups as
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `shutdown_behavior` (string) - Automatically terminate VMs on
shutdown in case Packer exits ungracefully. Possible values are "stop" and
"terminate", default is `stop`.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Default `false`.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). `all` will make the snapshot
publicly accessible.
- `snapshot_users` (array of strings) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the OMIS has permissions to create
volumes from the backing snapshot(s).
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override OMIS tags if already applied to snapshot. This is a
[template engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `source_omi_filter` (object) - Filters used to populate the `source_omi` field.
- `filters` (map of strings) - filters used to select a `source_omi`.
- `owners` (array of strings) - Filters the images by their owner. You may specify one or more Outscale account IDs, "self" (which will use the account whose credentials you are using to run Packer). This option is required for security reasons.
Example:
``` json
{
"source_omi_filter": {
"filters": {
"virtualization-type": "hvm",
"image-name": "image-name-in-account",
"root-device-type": "ebs"
},
"owners": ["099720109477"],
}
}
```
This selects an Ubuntu 16.04 HVM BSU OMIS from Canonical. NOTE:
This will fail unless *exactly* one OMIS is returned. In the above example,
`most_recent` will cause this to succeed by selecting the newest image.
- `ssh_keypair_name` (string) - If specified, this is the key that will be used for SSH with the machine. The key must match a key pair name loaded up into Outscale. By default, this is blank, and Packer will generate a temporary keypair unless [`ssh_password`](../templates/communicator.html#ssh_password) is used. [`ssh_private_key_file`](../templates/communicator.html#ssh_private_key_file) or `ssh_agent_auth` must be specified when `ssh_keypair_name` is utilized.
- `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to authenticate connections to the source VM. No temporary keypair will be created, and the values of `ssh_password` and `ssh_private_key_file` will be ignored. To use this option with a key pair already configured in the source OMI, leave the `ssh_keypair_name` blank. To associate an existing key pair in Outscale with the source VM, set the `ssh_keypair_name` field to the name of the key pair.
- `ssh_interface` (string) - One of `public_ip`, `private_ip`, `public_dns`, or `private_dns`. If set, either the public IP address, private IP address, public DNS name or private DNS name will used as the host for SSH. The default behaviour if inside a Net is to use the public IP address if available, otherwise the private IP address will be used. If not in a Net the public DNS name will be used. Also works for WinRM.
Where Packer is configured for an outbound proxy but WinRM traffic should be direct, `ssh_interface` must be set to `private_dns` and `<region>.compute.internal` included in the `NO_PROXY` environment variable.
- `subnet_id` (string) - If using Net, the ID of the subnet, such as `subnet-12345def`, where Packer will launch the VM. This field is required if you are using an non-default Net.
- `tags` (object of key/value strings) - Tags applied to the OMIS and relevant snapshots. This is a [template engine](../templates/engine.html), see [Build template data](#build-template-data) for more information.
- `temporary_key_pair_name` (string) - The name of the temporary key pair to generate. By default, Packer generates a name that looks like `packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized access to the VM, when packer is creating a temporary security group. The default is `0.0.0.0/0` (i.e., allow any IPv4 source). This is only used when `security_group_id` or `security_group_ids` is not specified.
- `user_data` (string) - User data to apply when launching the VM. Note that you need to be careful about escaping characters due to the templates being JSON. It is often more convenient to use `user_data_file`, instead. Packer will not automatically wait for a user script to finish before shutting down the VM this must be handled in a provisioner.
- `user_data_file` (string) - Path to a file that will be used for the user data when launching the VM.
- `net_id` (string) - If launching into a Net subnet, Packer needs the Net ID in order to create a temporary security group within the Net. Requires `subnet_id` to be set. If this field is left blank, Packer will try to get the Net ID from the `subnet_id`.
- `net_filter` (object) - Filters used to populate the `net_id` field.
Example:
``` json
{
"net_filter": {
"filters": {
"is-default": "false",
"ip-range": "/24"
}
}
}
```
This selects the Net with a IPv4 CIDR block of `/24`. NOTE: This will fail unless *exactly* one Net is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`. NOTE: This will fail unless *exactly* one Net is returned.
`net_id` take precedence over this.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows password for Windows VMs. Defaults to 20 minutes. Example value: `10m`
## Basic Example
Here is a basic example. You will need to provide access keys, and may need to change the OMIS IDs according to what images exist at the time the template is run:
```json
{
"variables": {
"access_key": "{{env `OUTSCALE_ACCESSKEYID`}}",
"secret_key": "{{env `OUTSCALE_SECRETKEYID`}}"
},
"builders": [
{
"type": "osc-bsu",
"access_key": "{{user `access_key`}}",
"secret_key": "{{user `secret_key`}}",
"region": "us-east-1",
"source_omi": "ami-abcfd0283",
"vm_type": "t2.micro",
"ssh_username": "outscale",
"omi_name": "packer_osc {{timestamp}}"
}
]
}
```
-&gt; **Note:** Packer can also read the access key and secret access key from
environmental variables. See the configuration reference in the section above
for more information on what environmental variables Packer will look for.
Further information on locating OMIS IDs and their relationship to VM
types and regions can be found in the Outscale Documentation [reference](https://wiki.outscale.net/display/EN/Official+OMIs+Reference).
## Accessing the Instance to Debug
If you need to access the VM to debug for some reason, run the builder
with the `-debug` flag. In debug mode, the Outscale builder will save the private key in the current directory and will output the DNS or IP information as well.
You can use this information to access the VM as it is running.
## OMIS Block Device Mappings Example
Here is an example using the optional OMIS block device mappings. Our
configuration of `launch_block_device_mappings` will expand the root volume
(`/dev/sda`) to 40gb during the build (up from the default of 8gb). With
`ami_block_device_mappings` Outscale will attach additional volumes `/dev/sdb` and
`/dev/sdc` when we boot a new VM of our OMI.
``` json
{
"type": "osc-bsu",
"access_key": "YOUR KEY HERE",
"secret_key": "YOUR SECRET KEY HERE",
"region": "us-east-1",
"source_omi": "ami-fce3c696",
"vm_type": "t2.micro",
"ssh_username": "ubuntu",
"omi_name": "packer-quick-start {{timestamp}}",
"launch_block_device_mappings": [
{
"device_name": "/dev/sda1",
"volume_size": 40,
"volume_type": "gp2",
"delete_on_vm_deletion": true
}
],
"omi_block_device_mappings": [
{
"device_name": "/dev/sdb",
"virtual_name": "ephemeral0"
},
{
"device_name": "/dev/sdc",
"virtual_name": "ephemeral1"
}
]
}
```
## Build template data
In configuration directives marked as a template engine above, the following variables are available:
- `BuildRegion` - The region (for example `eu-west-2`) where Packer is building the OMI.
- `SourceOMI` - The source OMIS ID (for example `ami-a2412fcd`) used to build the OMI.
- `SourceOMIName` - The source OMIS Name (for example `ubutu-390`) used to build the OMI.
- `SourceOMITags` - The source OMIS Tags, as a `map[string]string` object.
## Tag Example
Here is an example using the optional OMIS tags. This will add the tags `OS_Version` and `Release` to the finished OMI. As before, you will need to provide your access keys, and may need to change the source OMIS ID based on what images exist when this template is run:
``` json
{
"type": "osc-bsu",
"access_key": "YOUR KEY HERE",
"secret_key": "YOUR SECRET KEY HERE",
"region": "us-east-1",
"source_omi": "ami-fce3c696",
"vm_type": "t2.micro",
"ssh_username": "ubuntu",
"omi_name": "packer-quick-start {{timestamp}}",
"tags": {
"OS_Version": "Ubuntu",
"Release": "Latest",
"Base_OMI_Name": "{{ .SourceOMIName }}",
"Extra": "{{ .SourceOMITags.TagName }}"
}
}
```
-&gt; **Note:** Packer uses pre-built OMIs as the source for building images.
These source OMIs may include volumes that are not flagged to be destroyed on
termination of the VM building the new image. Packer will attempt to
clean up all residual volumes that are not designated by the user to remain
after termination. If you need to preserve those source volumes, you can
overwrite the termination setting by specifying `delete_on_vm_deletion=false`
in the `launch_block_device_mappings` block for the device.

View File

@ -0,0 +1,309 @@
---
description: |
The osc-bsusurrogate Packer builder is like the chroot builder, but does not
require running inside an Outscale virtual machine.
layout: docs
page_title: 'Outacale BSU Surrogate - Builders'
sidebar_current: 'docs-builders-osc-bsusurrogate'
---
# BSU Surrogate Builder
Type: `osc-bsusurrogate`
The `osc-bsusurrogate` Packer builder is able to create Outscale OMIs by
running a source virtual machine with an attached volume, provisioning the attached
volume in such a way that it can be used as the root volume for the OMI, and
then snapshotting and creating the OMI from that volume.
This builder can therefore be used to bootstrap scratch-build images - for
example FreeBSD or Ubuntu using ZFS as the root file system.
This is all done in your own Outscale account. This builder will create temporary
key pairs, security group rules, etc., that provide it temporary access to the
virtual machine while the image is being created.
## Configuration Reference
There are many configuration options available for this builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
In addition to the options listed here, a
[communicator](/docs/templates/communicator.html) can be configured for this
builder.
### Required:
- `access_key` (string) - The access key used to communicate with OUTSCALE. [Learn how to set this](outscale.html#authentication)
- `omi_name` (string) - The name of the resulting OMIS that will appear when managing OMIs in the Outscale console or via APIs. This must be unique. To help make this unique, use a function like `timestamp` (see [template engine](../templates/engine.html) for more info).
- `vm_type` (string) - The Outscale VM type to use while building the OMI, such as `t2.small`.
- `region` (string) - The name of the region, such as `us-east-1`, in which to launch the Outscale VM to create the OMI.
- `secret_key` (string) - The secret key used to communicate with Outscale. [Learn how to set this](outscale.html#authentication)
- `source_omi` (string) - The initial OMI used as a base for the newly created machine. `source_omi_filter` may be used instead to populate this automatically.
- `omi_root_device` (block device mapping) - A block device mapping
describing the root device of the OMI. This looks like the mappings in
`omi_block_device_mapping`, except with an additional field:
- `source_device_name` (string) - The device name of the block device on
the source virtual machine to be used as the root device for the OMI. This
must correspond to a block device in `launch_block_device_mapping`.
### Optional:
- `omi_block_device_mappings` (array of block device mappings) - Add one or more [block device mappings](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) to the OMI. These will be attached when booting a new VM from your OMI. To add a block device during the Packer build see `launch_block_device_mappings` below. Your options here may vary depending on the type of VM you use. The block device mappings allow for the following configuration:
- `delete_on_vm_deletion` (boolean) - Indicates whether the BSU volume is deleted on VM termination. Default `false`. **NOTE**: If this value is not explicitly set to `true` and volumes are not cleaned up by an alternative method, additional volumes will accumulate after every build.
- `device_name` (string) - The device name exposed to the VM (for example, `/dev/sdh` or `xvdh`). Required for every device in the block device mapping.
- `iops` (number) - The number of I/O operations per second (IOPS) that the volume supports. See the documentation on
[IOPs](https://wiki.outscale.net/display/EN/About+Volumes#AboutVolumes-VolumeTypesVolumeTypesandIOPS)
for more information.
- `no_device` (boolean) - Suppresses the specified device included in the
block device mapping of the OMI.
- `snapshot_id` (string) - The ID of the snapshot
- `virtual_name` (string) - The virtual device name. See the documentation on [Block Device Mapping](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) for more information.
- `volume_size` (number) - The size of the volume, in GiB. Required if not specifying a `snapshot_id`
- `volume_type` (string) - The volume type. `gp2` for General Purpose (SSD) volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard` for Magnetic volumes
- `omi_description` (string) - The description to set for the resulting OMI(s). By default this description is empty. This is a [template engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `omi_account_ids` (array of strings) - A list of account IDs that have access to launch the resulting OMI(s). By default no additional users other than the user creating the OMIS has permissions to launch it.
- `omi_virtualization_type` (string) - The type of virtualization for the OMI you are building. This option must match the supported virtualization type of `source_omi`. Can be `paravirtual` or `hvm`.
- `associate_public_ip_address` (boolean) - If using a non-default Net, public IP addresses are not provided by default. If this is toggled, your new VM will get a Public IP.
- `subregion_name` (string) - Destination subregion to launch VM in. Leave this empty to allow Outscale to auto-assign.
- `custom_endpoint_oapi` (string) - This option is useful if you use a cloud
provider whose API is compatible with Outscale OAPI. Specify another endpoint
like this `outscale.com/oapi/latest`.
- `disable_stop_vm` (boolean) - Packer normally stops the build
VM after all provisioners have run. For Windows VMs, it is
sometimes desirable to [run Sysprep](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc721940(v=ws.10)) which will stop the VM for you. If this is set to `true`, Packer
*will not* stop the VM but will assume that you will send the stop
signal yourself through your final provisioner. You can do this with a
[windows-shell provisioner](https://www.packer.io/docs/provisioners/windows-shell.html).
Note that Packer will still wait for the VM to be stopped, and
failing to send the stop signal yourself, when you have set this flag to
`true`, will cause a timeout.
- `bsu_optimized` (boolean) - If true, the VM is created with optimized BSU I/O.
- `force_deregister` (boolean) - Force Packer to first deregister an existing
OMI if one with the same name already exists. Default `false`.
- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots
associated with OMIs, which have been deregistered by `force_deregister`.
Default `false`.
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS
verification of the OAPI endpoint. The default is `false`.
- `launch_block_device_mappings` (array of block device mappings) - Add one
or more block devices before the Packer build starts. If you add VM
store volumes or BSU volumes in addition to the root device volume, the
created OMIS will contain block device mapping information for those
volumes. Outscale creates snapshots of the source VM's root volume and
any other BSU volumes described here. When you launch an VM from this
new OMI, the VM automatically launches with these additional volumes,
and will restore them from snapshots taken from the source VM.
- `delete_on_vm_deletion` (boolean) - Indicates whether the BSU volume is deleted on VM termination. Default `false`. **NOTE**: If this value is not explicitly set to `true` and volumes are not cleaned up by an alternative method, additional volumes will accumulate after every build.
- `device_name` (string) - The device name exposed to the VM (for example, `/dev/sdh` or `xvdh`). Required for every device in the block device mapping.
- `iops` (number) - The number of I/O operations per second (IOPS) that the volume supports. See the documentation on
[IOPs](https://wiki.outscale.net/display/EN/About+Volumes#AboutVolumes-VolumeTypesVolumeTypesandIOPS)
for more information.
- `volume_size` (number) - The size of the volume, in GiB. Required if not specifying a `snapshot_id`
- `volume_type` (string) - The volume type. `gp2` for General Purpose (SSD) volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard` for Magnetic volumes
- `run_tags` (object of key/value strings) - Tags to apply to the VM
that is *launched* to create the OMI. These tags are *not* applied to the
resulting OMIS unless they're duplicated in `tags`. This is a [template
engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `run_volume_tags` (object of key/value strings) - Tags to apply to the
volumes that are *launched* to create the OMI. These tags are *not* applied
to the resulting OMIS unless they're duplicated in `tags`. This is a
[template engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `security_group_id` (string) - The ID (*not* the name) of the security
group to assign to the VM. By default this is not set and Packer will
automatically create a new temporary security group to allow SSH access.
Note that if this is specified, you must be sure the security group allows
access to the `ssh_port` given below.
- `security_group_ids` (array of strings) - A list of security groups as
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `shutdown_behavior` (string) - Automatically terminate VMs on
shutdown in case Packer exits ungracefully. Possible values are "stop" and
"terminate", default is `stop`.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Default `false`.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). `all` will make the snapshot
publicly accessible.
- `snapshot_users` (array of strings) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the OMIS has permissions to create
volumes from the backing snapshot(s).
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override OMIS tags if already applied to snapshot. This is a
[template engine](../templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `source_omi_filter` (object) - Filters used to populate the `source_omi` field.
- `filters` (map of strings) - filters used to select a `source_omi`.
- `owners` (array of strings) - Filters the images by their owner. You may specify one or more Outscale account IDs, "self" (which will use the account whose credentials you are using to run Packer). This option is required for security reasons.
Example:
``` json
{
"source_omi_filter": {
"filters": {
"virtualization-type": "hvm",
"image-name": "image-name",
"root-device-type": "ebs"
},
"owners": ["099720109477"],
}
}
```
This selects an Ubuntu 16.04 HVM BSU OMIS from Canonical. NOTE:
This will fail unless *exactly* one OMIS is returned. In the above example,
`most_recent` will cause this to succeed by selecting the newest image.
- `ssh_keypair_name` (string) - If specified, this is the key that will be used for SSH with the machine. The key must match a key pair name loaded up into Outscale. By default, this is blank, and Packer will generate a temporary keypair unless [`ssh_password`](../templates/communicator.html#ssh_password) is used. [`ssh_private_key_file`](../templates/communicator.html#ssh_private_key_file) or `ssh_agent_auth` must be specified when `ssh_keypair_name` is utilized.
- `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to authenticate connections to the source VM. No temporary keypair will be created, and the values of `ssh_password` and `ssh_private_key_file` will be ignored. To use this option with a key pair already configured in the source OMI, leave the `ssh_keypair_name` blank. To associate an existing key pair in Outscale with the source VM, set the `ssh_keypair_name` field to the name of the key pair.
- `ssh_interface` (string) - One of `public_ip`, `private_ip`, `public_dns`, or `private_dns`. If set, either the public IP address, private IP address, public DNS name or private DNS name will used as the host for SSH. The default behaviour if inside a Net is to use the public IP address if available, otherwise the private IP address will be used. If not in a Net the public DNS name will be used. Also works for WinRM.
Where Packer is configured for an outbound proxy but WinRM traffic should be direct, `ssh_interface` must be set to `private_dns` and `<region>.compute.internal` included in the `NO_PROXY` environment variable.
- `subnet_id` (string) - If using Net, the ID of the subnet, such as `subnet-12345def`, where Packer will launch the VM. This field is required if you are using an non-default Net.
- `tags` (object of key/value strings) - Tags applied to the OMIS and relevant snapshots. This is a [template engine](../templates/engine.html), see [Build template data](#build-template-data) for more information.
- `temporary_key_pair_name` (string) - The name of the temporary key pair to generate. By default, Packer generates a name that looks like `packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized access to the VM, when packer is creating a temporary security group. The default is `0.0.0.0/0` (i.e., allow any IPv4 source). This is only used when `security_group_id` or `security_group_ids` is not specified.
- `user_data` (string) - User data to apply when launching the VM. Note that you need to be careful about escaping characters due to the templates being JSON. It is often more convenient to use `user_data_file`, instead. Packer will not automatically wait for a user script to finish before shutting down the VM this must be handled in a provisioner.
- `user_data_file` (string) - Path to a file that will be used for the user data when launching the VM.
- `net_id` (string) - If launching into a Net subnet, Packer needs the Net ID in order to create a temporary security group within the Net. Requires `subnet_id` to be set. If this field is left blank, Packer will try to get the Net ID from the `subnet_id`.
- `net_filter` (object) - Filters used to populate the `net_id` field.
Example:
``` json
{
"net_filter": {
"filters": {
"is-default": "false",
"ip-range": "/24"
}
}
}
```
This selects the Net with a IPv4 CIDR block of `/24`. NOTE: This will fail unless *exactly* one Net is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`. NOTE: This will fail unless *exactly* one Net is returned.
`net_id` take precedence over this.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows password for Windows VMs. Defaults to 20 minutes. Example value: `10m`
## Basic Example
``` json
{
"type" : "osc-bsusurrogate",
"secret_key" : "YOUR SECRET KEY HERE",
"access_key" : "YOUR KEY HERE",
"region" : "eu-west-2",
"ssh_username" : "outscale",
"vm_type" : "t2.medium",
"source_omi" : "ami-bcfc34e0",
"subregion_name": "eu-west-2a",
"launch_block_device_mappings" : [
{
"volume_type" : "io1",
"device_name" : "/dev/xvdf",
"delete_on_vm_deletion" : false,
"volume_size" : 10,
"iops": 300
}
],
"omi_root_device":{
"source_device_name": "/dev/xvdf",
"device_name": "/dev/sda1",
"delete_on_vm_deletion": true,
"volume_size": 10,
"volume_type": "standard"
}
}
```
-&gt; **Note:** Packer can also read the access key and secret access key from
environmental variables. See the configuration reference in the section above
for more information on what environmental variables Packer will look for.
Further information on locating OMIS IDs and their relationship to VM
types and regions can be found in the Outscale Documentation [reference](https://wiki.outscale.net/display/EN/Official+OMIs+Reference).
## Accessing the Virtual Machine to Debug
If you need to access the virtual machine to debug for some reason, run this builder
with the `-debug` flag. In debug mode, the Outscale builder will save the private
key in the current directory and will output the DNS or IP information as well.
You can use this information to access the virtual machine as it is running.
## Build template data
In configuration directives marked as a template engine above, the following variables are available:
- `BuildRegion` - The region (for example `eu-west-2`) where Packer is building the OMI.
- `SourceOMI` - The source OMIS ID (for example ami-a2412fcd`) used to build the OMI.
- `SourceOMIName` - The source OMIS Name (for example `ubutu-390`) used to build the OMI.
- `SourceOMITags` - The source OMIS Tags, as a `map[string]string` object.
-&gt; **Note:** Packer uses pre-built OMIs as the source for building images.
These source OMIs may include volumes that are not flagged to be destroyed on
termination of the virtual machine building the new image. In addition to those
volumes created by this builder, any volumes inn the source OMI which are not
marked for deletion on termination will remain in your account.

View File

@ -0,0 +1,282 @@
---
description: |
The osc-bsuvolume Packer builder is like the BSU builder, but is intended to
create BSU volumes rather than a machine image.
layout: docs
page_title: 'Amazon BSU Volume - Builders'
sidebar_current: 'docs-builders-osc-bsuvolume'
---
# BSU Volume Builder
Type: `osc-bsuvolume`
The `osc-bsuvolume` Packer builder is able to create Ouscale Block Stogate Unit
volumes which are prepopulated with filesystems or data.
This builder builds BSU volumes by launching an Outscale VM from a source OMI,
provisioning that running machine, and then destroying the source machine,
keeping the volumes intact.
This is all done in your own Outscale account. The builder will create temporary key
pairs, security group rules, etc. that provide it temporary access to the
instance while the image is being created.
The builder does *not* manage BSU Volumes. Once it creates volumes and stores
it in your account, it is up to you to use, delete, etc. the volumes.
-&gt; **Note:** Temporary resources are, by default, all created with the
prefix `packer`. This can be useful if you want to restrict the security groups
and key pairs Packer is able to operate on.
## Configuration Reference
There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
In addition to the options listed here, a
[communicator](/docs/templates/communicator.html) can be configured for this
builder.
### Required:
- `access_key` (string) - The access key used to communicate with OUTSCALE. [Learn how to set this](outscale.html#authentication)
- `vm_type` (string) - The Outscale VM type to use while building the OMI, such as `t2.small`.
- `region` (string) - The name of the region, such as `us-east-1`, in which to launch the Outscale VM to create the OMI.
- `secret_key` (string) - The secret key used to communicate with Outscale. [Learn how to set this](outscale.html#authentication)
- `source_omi` (string) - The initial OMI used as a base for the newly created machine. `source_omi_filter` may be used instead to populate this automatically.
### Optional:
- `ebs_volumes` (array of block device mappings) - Add the block device
mappings to the OMI. The block device mappings allow for keys:
- `device_name` (string) - The device name exposed to the VM (for example, `/dev/sdh` or `xvdh`). Required for every device in the block device mapping.
- `delete_on_vm_deletion` (boolean) - Indicates whether the BSU volume is deleted on instance termination.
- `iops` (number) - The number of I/O operations per second (IOPS) that the volume supports. See the documentation on
[IOPs](https://wiki.outscale.net/display/EN/About+Volumes#AboutVolumes-VolumeTypesVolumeTypesandIOPS)
for more information.
- `no_device` (boolean) - Suppresses the specified device included in the block device mapping of the OMI.
- `snapshot_id` (string) - The ID of the snapshot
- `virtual_name` (string) - The virtual device name. See the documentation on [Block Device Mapping](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) for more information
- `volume_size` (number) - The size of the volume, in GiB. Required if not specifying a `snapshot_id`
- `volume_type` (string) - The volume type. `gp2` for General Purpose (SSD) volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard` for Magnetic volumes
- `tags` (map) - Tags to apply to the volume. These are retained after
the builder completes. This is a [template
engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `associate_public_ip_address` (boolean) - If using a non-default Net, public IP addresses are not provided by default. If this is toggled, your new VM will get a Public IP.
- `subregion_name` (string) - Destination subregion to launch VM in. Leave this empty to allow Outscale to auto-assign.
- `custom_endpoint_oapi` (string) - This option is useful if you use a cloud
provider whose API is compatible with Outscale OAPI. Specify another endpoint
like this `outscale.com/oapi/latest`.
- `disable_stop_vm` (boolean) - Packer normally stops the build
VM after all provisioners have run. For Windows VMs, it is
sometimes desirable to [run Sysprep](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc721940(v=ws.10)) which will stop the VM for you. If this is set to `true`, Packer
*will not* stop the VM but will assume that you will send the stop
signal yourself through your final provisioner. You can do this with a
[windows-shell provisioner](https://www.packer.io/docs/provisioners/windows-shell.html).
Note that Packer will still wait for the VM to be stopped, and
failing to send the stop signal yourself, when you have set this flag to
`true`, will cause a timeout.
- `bsu_optimized` (boolean) - If true, the VM is created with optimized BSU I/O.
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS
verification of the OAPI endpoint. The default is `false`.
- `run_tags` (object of key/value strings) - Tags to apply to the instance
that is *launched* to create the OMI. These tags are *not* applied to the
resulting OMI unless they're duplicated in `tags`. This is a [template
engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `security_group_id` (string) - The ID (*not* the name) of the security
group to assign to the VM. By default this is not set and Packer will
automatically create a new temporary security group to allow SSH access.
Note that if this is specified, you must be sure the security group allows
access to the `ssh_port` given below.
- `security_group_ids` (array of strings) - A list of security groups as
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `shutdown_behavior` (string) - Automatically terminate instances on
shutdown in case Packer exits ungracefully. Possible values are `stop` and
`terminate`. Defaults to `stop`.
- `shutdown_behavior` (string) - Automatically terminate VMs on
shutdown in case Packer exits ungracefully. Possible values are "stop" and
"terminate", default is `stop`.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). `all` will make the snapshot
publicly accessible.
- `snapshot_users` (array of strings) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the OMIS has permissions to create
volumes from the backing snapshot(s).
- `source_omi_filter` (object) - Filters used to populate the `source_omi` field.
- `filters` (map of strings) - filters used to select a `source_omi`.
- `owners` (array of strings) - Filters the images by their owner. You may specify one or more Outscale account IDs, "self" (which will use the account whose credentials you are using to run Packer). This option is required for security reasons.
Example:
``` json
{
"source_omi_filter": {
"filters": {
"virtualization-type": "hvm",
"image-name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
"root-device-type": "ebs"
},
"owners": ["099720109477"],
}
}
```
This selects an Ubuntu 16.04 HVM BSU OMIS from Canonical. NOTE:
This will fail unless *exactly* one OMIS is returned. In the above example,
`most_recent` will cause this to succeed by selecting the newest image.
- `ssh_keypair_name` (string) - If specified, this is the key that will be used for SSH with the machine. The key must match a key pair name loaded up into Outscale. By default, this is blank, and Packer will generate a temporary keypair unless [`ssh_password`](../templates/communicator.html#ssh_password) is used. [`ssh_private_key_file`](../templates/communicator.html#ssh_private_key_file) or `ssh_agent_auth` must be specified when `ssh_keypair_name` is utilized.
- `ssh_interface` (string) - One of `public_ip`, `private_ip`, `public_dns`, or `private_dns`. If set, either the public IP address, private IP address, public DNS name or private DNS name will used as the host for SSH. The default behaviour if inside a Net is to use the public IP address if available, otherwise the private IP address will be used. If not in a Net the public DNS name will be used. Also works for WinRM.
Where Packer is configured for an outbound proxy but WinRM traffic should be direct, `ssh_interface` must be set to `private_dns` and `<region>.compute.internal` included in the `NO_PROXY` environment variable.
- `subnet_id` (string) - If using Net, the ID of the subnet, such as `subnet-12345def`, where Packer will launch the VM. This field is required if you are using an non-default Net.
- `temporary_key_pair_name` (string) - The name of the temporary key pair to generate. By default, Packer generates a name that looks like `packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized access to the VM, when packer is creating a temporary security group. The default is `0.0.0.0/0` (i.e., allow any IPv4 source). This is only used when `security_group_id` or `security_group_ids` is not specified.
- `user_data` (string) - User data to apply when launching the VM. Note that you need to be careful about escaping characters due to the templates being JSON. It is often more convenient to use `user_data_file`, instead. Packer will not automatically wait for a user script to finish before shutting down the VM this must be handled in a provisioner.
- `user_data_file` (string) - Path to a file that will be used for the user data when launching the VM.
- `net_id` (string) - If launching into a Net subnet, Packer needs the Net ID in order to create a temporary security group within the Net. Requires `subnet_id` to be set. If this field is left blank, Packer will try to get the Net ID from the `subnet_id`.
- `net_filter` (object) - Filters used to populate the `net_id` field.
Example:
``` json
{
"net_filter": {
"filters": {
"is-default": "false",
"ip-range": "/24"
}
}
}
```
This selects the Net with a IPv4 CIDR block of `/24`. NOTE: This will fail unless *exactly* one Net is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`. NOTE: This will fail unless *exactly* one Net is returned.
`net_id` take precedence over this.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows password for Windows VMs. Defaults to 20 minutes. Example value: `10m`
## Basic Example
``` json
{
"builders": [
{
"type": "osc-bsuvolume",
"region": "eu-west-2",
"vm_type": "t2.micro",
"source_omi": "ami-65efcc11",
"ssh_username": "outscale",
"ebs_volumes": [
{
"volume_type": "gp2",
"device_name": "/dev/xvdf",
"delete_on_vm_deletion": false,
"tags": {
"zpool": "data",
"Name": "Data1"
},
"volume_size": 10
},
{
"volume_type": "gp2",
"device_name": "/dev/xvdg",
"tags": {
"zpool": "data",
"Name": "Data2"
},
"delete_on_vm_deletion": false,
"volume_size": 10
},
{
"volume_size": 10,
"tags": {
"Name": "Data3",
"zpool": "data"
},
"delete_on_vm_deletion": false,
"device_name": "/dev/xvdh",
"volume_type": "gp2"
}
]
}
]
}
```
-&gt; **Note:** Packer can also read the access key and secret access key from
environmental variables. See the configuration reference in the section above
for more information on what environmental variables Packer will look for.
Further information on locating OMIS IDs and their relationship to VM
types and regions can be found in the Outscale Documentation [reference](https://wiki.outscale.net/display/EN/Official+OMIs+Reference).
## Accessing the Instance to Debug
If you need to access the instance to debug for some reason, run the builder
with the `-debug` flag. In debug mode, the Amazon builder will save the private
key in the current directory and will output the DNS or IP information as well.
You can use this information to access the instance as it is running.
## Build template data
In configuration directives marked as a template engine above, the following
variables are available:
- `BuildRegion` - The region (for example `eu-west-2`) where Packer is
building the OMI.
- `SourceOMI` - The source OMI ID (for example `ami-a2412fcd`) used to build
the OMI.
- `SourceOMIName` - The source OMI Name (for example
`ubuntu/images/ebs-ssd/ubuntu-xenial-16.04-amd64-server-20180306`) used to
build the OMI.
- `SourceOMITags` - The source OMI Tags, as a `map[string]string` object.
-&gt; **Note:** Packer uses pre-built OMIs as the source for building images.
These source OMIs may include volumes that are not flagged to be destroyed on
termination of the instance building the new image. In addition to those
volumes created by this builder, any volumes inn the source OMI which are not
marked for deletion on termination will remain in your account.

View File

@ -0,0 +1,383 @@
---
description: |
The amazon-chroot Packer builder is able to create Outscale OMIs backed by an BSU
volume as the root device. For more information on the difference between
instance storage and BSU-backed instances, storage for the root device section
in the Outscale documentation.
layout: docs
page_title: 'Outscale chroot - Builders'
sidebar_current: 'docs-builders-osc-chroot'
---
# OMI Builder (chroot)
Type: `osc-chroot`
The `osc-chroot` Packer builder is able to create Outscale Machine Images (OMIs) backed by an
BSU volume as the root device. For more information on the difference between
instance storage and BSU-backed instances, see the ["storage for the root
device" section in the Outscale
documentation](https://wiki.outscale.net/display/EN/Home).
The difference between this builder and the `osc-bsu` builder is that this
builder is able to build an BSU-backed OMI without launching a new Outscale
VM. This can dramatically speed up OMI builds for organizations who need
the extra fast build.
~&gt; **This is an advanced builder** If you're just getting started with
Packer, we recommend starting with the [osc-bsu
builder](/docs/builders/osc-bsu.html), which is much easier to use.
The builder does *not* manage OMIs. Once it creates an OMI and stores it in
your account, it is up to you to use, delete, etc., the OMI.
## How Does it Work?
This builder works by creating a new BSU volume from an existing source OMI and
attaching it into an already-running Outscale VM. Once attached, a
[chroot](https://en.wikipedia.org/wiki/Chroot) is used to provision the system
within that volume. After provisioning, the volume is detached, snapshotted,
and an OMI is made.
Using this process, minutes can be shaved off the OMI creation process because
a new Outscale VM doesn't need to be launched.
There are some restrictions, however. The host Outscale instance where the volume is
attached to must be a similar system (generally the same OS version, kernel
versions, etc.) as the OMI being built. Additionally, this process is much more
expensive because the Outscale VM must be kept running persistently in order
to build OMIs, whereas the other OMI builders start VMs on-demand to
build OMIs as needed.
## Configuration Reference
There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
### Required:
- `access_key` (string) - The access key used to communicate with OUTSCALE. [Learn how to set this](outscale.html#authentication)
- `omi_name` (string) - The name of the resulting OMIS that will appear when managing OMIs in the Outscale console or via APIs. This must be unique. To help make this unique, use a function like `timestamp` (see [template engine](../templates/engine.html) for more info).
- `secret_key` (string) - The secret key used to communicate with Outscale. [Learn how to set this](outscale.html#authentication)
- `source_omi` (string) - The initial OMI used as a base for the newly created machine. `source_omi_filter` may be used instead to populate this automatically.
### Optional:
- `omi_description` (string) - The description to set for the resulting OMI(s).
By default this description is empty. This is a [template engine](../templates/engine.html),
see [Build template data](#build-template-data) for more information.
- `omi_account_ids` (array of strings) - A list of account IDs that have access to launch the resulting OMI(s). By default no additional users other than the user creating the OMIS has permissions to launch it.
- `omi_virtualization_type` (string) - The type of virtualization for the OMI you are building. This option must match the supported virtualization type of `source_omi`. Can be `paravirtual` or `hvm`.
- `chroot_mounts` (array of array of strings) - This is a list of devices to
mount into the chroot environment. This configuration parameter requires
some additional documentation which is in the [Chroot
Mounts](#chroot-mounts) section. Please read that section for more
information on how to use this.
- `command_wrapper` (string) - How to run shell commands. This defaults to
`{{.Command}}`. This may be useful to set if you want to set environmental
variables or perhaps run it with `sudo` or so on. This is a configuration
template where the `.Command` variable is replaced with the command to be
run. Defaults to `{{.Command}}`.
- `copy_files` (array of strings) - Paths to files on the running Outscale
VM that will be copied into the chroot environment prior to
provisioning. Defaults to `/etc/resolv.conf` so that DNS lookups work. Pass
an empty list to skip copying `/etc/resolv.conf`. You may need to do this
if you're building an image that uses systemd.
- `custom_endpoint_oapi` (string) - This option is useful if you use a cloud
provider whose API is compatible with Outscale OAPI. Specify another endpoint
like this `outscale.com/oapi/latest`.
- `device_path` (string) - The path to the device where the root volume of
the source OMI will be attached. This defaults to "" (empty string), which
forces Packer to find an open device automatically.
- `force_deregister` (boolean) - Force Packer to first deregister an existing
OMIS if one with the same name already exists. Default `false`.
- `force_delete_snapshot` (boolean) - Force Packer to delete snapshots
associated with OMIs, which have been deregistered by `force_deregister`.
Default `false`.
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS
verification of the OAPI endpoint. The default is `false`.
- `from_scratch` (boolean) - Build a new volume instead of starting from an
existing OMI root volume snapshot. Default `false`. If `true`, `source_omi`
is no longer used and the following options become required:
`omi_virtualization_type`, `pre_mount_commands` and `root_volume_size`. The
below options are also required in this mode only:
- `omi_block_device_mappings` (array of block device mappings) - Add one or more [block device mappings](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) to the OMI. These will be attached when booting a new VM from your OMI. To add a block device during the Packer build see `launch_block_device_mappings` below. Your options here may vary depending on the type of VM you use. The block device mappings allow for the following configuration:
- `delete_on_vm_deletion` (boolean) - Indicates whether the BSU volume is deleted on VM termination. Default `false`. **NOTE**: If this value is not explicitly set to `true` and volumes are not cleaned up by an alternative method, additional volumes will accumulate after every build.
- `device_name` (string) - The device name exposed to the VM (for example, `/dev/sdh` or `xvdh`). Required for every device in the block device mapping.
- `iops` (number) - The number of I/O operations per second (IOPS) that the volume supports. See the documentation on
[IOPs](https://wiki.outscale.net/display/EN/About+Volumes#AboutVolumes-VolumeTypesVolumeTypesandIOPS)
for more information
- `no_device` (boolean) - Suppresses the specified device included in the
block device mapping of the OMI
- `snapshot_id` (string) - The ID of the snapshot
- `virtual_name` (string) - The virtual device name. See the documentation on [Block Device Mapping](https://wiki.outscale.net/display/EN/Defining+Block+Device+Mappings) for more information
- `volume_size` (number) - The size of the volume, in GiB. Required if not specifying a `snapshot_id`
- `volume_type` (string) - The volume type. `gp2` for General Purpose (SSD) volumes, `io1` for Provisioned IOPS (SSD) volumes, and `standard` for Magnetic volumes
- `root_device_name` (string) - The root device name. For example, `xvda`.
- `mount_path` (string) - The path where the volume will be mounted. This is
where the chroot environment will be. This defaults to
`/mnt/packer-amazon-chroot-volumes/{{.Device}}`. This is a configuration
template where the `.Device` variable is replaced with the name of the
device where the volume is attached.
- `mount_partition` (string) - The partition number containing the /
partition. By default this is the first partition of the volume, (for
example, `xvdf1`) but you can designate the entire block device by setting
`"mount_partition": "0"` in your config, which will mount `xvdf` instead.
- `mount_options` (array of strings) - Options to supply the `mount` command
when mounting devices. Each option will be prefixed with `-o` and supplied
to the `mount` command ran by Packer. Because this command is ran in a
shell, user discretion is advised. See [this manual page for the mount
command](http://linuxcommand.org/man_pages/mount8.html) for valid file
system specific options.
- `nvme_device_path` (string) - When we call the mount command (by default
`mount -o device dir`), the string provided in `nvme_mount_path` will
replace `device` in that command. When this option is not set, `device` in
that command will be something like `/dev/sdf1`, mirroring the attached
device name. This assumption works for most instances but will fail with c5
and m5 instances. In order to use the chroot builder with c5 and m5
instances, you must manually set `nvme_device_path` and `device_path`.
- `pre_mount_commands` (array of strings) - A series of commands to execute
after attaching the root volume and before mounting the chroot. This is not
required unless using `from_scratch`. If so, this should include any
partitioning and filesystem creation commands. The path to the device is
provided by `{{.Device}}`.
- `post_mount_commands` (array of strings) - As `pre_mount_commands`, but the
commands are executed after mounting the root device and before the extra
mount and copy steps. The device and mount path are provided by
`{{.Device}}` and `{{.MountPath}}`.
- `root_volume_size` (number) - The size of the root volume in GB for the
chroot environment and the resulting OMI. Default size is the snapshot size
of the `source_omi` unless `from_scratch` is `true`, in which case this
field must be defined.
- `root_volume_type` (string) - The type of BSU volume for the chroot
environment and resulting OMI. The default value is the type of the
`source_omi`, unless `from_scratch` is `true`, in which case the default
value is `gp2`. You can only specify `io1` if building based on top of a
`source_omi` which is also `io1`.
- `root_volume_tags` (object of key/value strings) - Tags to apply to the
volumes that are *launched*. This is a [template
engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `skip_region_validation` (boolean) - Set to true if you want to skip
validation of the region configuration option. Default `false`.
- `snapshot_tags` (object of key/value strings) - Tags to apply to snapshot.
They will override OMI tags if already applied to snapshot. This is a
[template engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
- `snapshot_groups` (array of strings) - A list of groups that have access to
create volumes from the snapshot(s). By default no groups have permission
to create volumes from the snapshot(s). `all` will make the snapshot
publicly accessible.
- `snapshot_users` (array of strings) - A list of account IDs that have
access to create volumes from the snapshot(s). By default no additional
users other than the user creating the OMIS has permissions to create
volumes from the backing snapshot(s).
- `source_omi_filter` (object) - Filters used to populate the `source_omi` field.
- `filters` (map of strings) - filters used to select a `source_omi`.
- `owners` (array of strings) - Filters the images by their owner. You may specify one or more Outscale account IDs, "self" (which will use the account whose credentials you are using to run Packer). This option is required for security reasons.
Example:
``` json
{
"source_omi_filter": {
"filters": {
"virtualization-type": "hvm",
"image-name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
"root-device-type": "ebs"
},
"owners": ["099720109477"],
}
}
```
This selects an Ubuntu 16.04 HVM BSU OMIS from Canonical. NOTE:
This will fail unless *exactly* one OMIS is returned. In the above example,
`most_recent` will cause this to succeed by selecting the newest image.
You may set this in place of `source_omi` or in conjunction with it. If you
set this in conjunction with `source_omi`, the `source_omi` will be added
to the filter. The provided `source_omi` must meet all of the filtering
criteria provided in `source_omi_filter`; this pins the OMI returned by the
filter, but will cause Packer to fail if the `source_omi` does not exist.
- `tags` (object of key/value strings) - Tags applied to the OMI. This is a
[template engine](/docs/templates/engine.html), see [Build template
data](#build-template-data) for more information.
## Basic Example
Here is a basic example. It is completely valid except for the access keys:
``` json
{
"type": "osc-chroot",
"access_key": "YOUR KEY HERE",
"secret_key": "YOUR SECRET KEY HERE",
"source_omi": "ami-3e158364",
"omi_name": "packer-outscale-chroot {{timestamp}}"
}
```
## Chroot Mounts
The `chroot_mounts` configuration can be used to mount specific devices within
the chroot. By default, the following additional mounts are added into the
chroot by Packer:
- `/proc` (proc)
- `/sys` (sysfs)
- `/dev` (bind to real `/dev`)
- `/dev/pts` (devpts)
- `/proc/sys/fs/binfmt_misc` (binfmt\_misc)
These default mounts are usually good enough for anyone and are sane defaults.
However, if you want to change or add the mount points, you may using the
`chroot_mounts` configuration. Here is an example configuration which only
mounts `/proc` and `/dev`:
``` json
{
"chroot_mounts": [
["proc", "proc", "/proc"],
["bind", "/dev", "/dev"]
]
}
```
`chroot_mounts` is a list of a 3-tuples of strings. The three components of the
3-tuple, in order, are:
- The filesystem type. If this is "bind", then Packer will properly bind the
filesystem to another mount point.
- The source device.
- The mount directory.
## Parallelism
A quick note on parallelism: it is perfectly safe to run multiple *separate*
Packer processes with the `osc-chroot` builder on the same Outscale VM. In
fact, this is recommended as a way to push the most performance out of your OMI
builds.
Packer properly obtains a process lock for the parallelism-sensitive parts of
its internals such as finding an available device.
## Gotchas
### Unmounting the Filesystem
One of the difficulties with using the chroot builder is that your provisioning
scripts must not leave any processes running or packer will be unable to
unmount the filesystem.
For debian based distributions you can setup a
[policy-rc.d](http://people.debian.org/~hmh/invokerc.d-policyrc.d-specification.txt)
file which will prevent packages installed by your provisioners from starting
services:
``` json
{
"type": "shell",
"inline": [
"echo '#!/bin/sh' > /usr/sbin/policy-rc.d",
"echo 'exit 101' >> /usr/sbin/policy-rc.d",
"chmod a+x /usr/sbin/policy-rc.d"
]
},
// ...
{
"type": "shell",
"inline": [
"rm -f /usr/sbin/policy-rc.d"
]
}
```
### Ansible provisioner
Running ansible against `osc-chroot` requires changing the Ansible connection
to chroot and running Ansible as root/sudo.
## Building From Scratch
This example demonstrates the essentials of building an image from scratch. A
15G gp2 (SSD) device is created (overriding the default of standard/magnetic).
The device setup commands partition the device with one partition for use as an
HVM image and format it ext4. This builder block should be followed by
provisioning commands to install the os and bootloader.
``` json
{
"type": "osc-chroot",
"ami_name": "packer-from-scratch {{timestamp}}",
"from_scratch": true,
"ami_virtualization_type": "hvm",
"pre_mount_commands": [
"parted {{.Device}} mklabel msdos mkpart primary 1M 100% set 1 boot on print",
"mkfs.ext4 {{.Device}}1"
],
"root_volume_size": 15,
"root_device_name": "xvdf",
"ami_block_device_mappings": [
{
"device_name": "xvdf",
"delete_on_termination": true,
"volume_type": "gp2"
}
]
}
```
## Build template data
In configuration directives marked as a template engine above, the following
variables are available:
- `BuildRegion` - The region (for example `eu-west-2`) where Packer is building the OMI.
- `SourceOMI` - The source OMIS ID (for example `ami-a2412fcd`) used to build the OMI.
- `SourceOMIName` - The source OMIS Name (for example `ubuntu-390`) used to build the OMI.
- `SourceOMITags` - The source OMIS Tags, as a `map[string]string` object

View File

@ -0,0 +1,91 @@
---
description: |
Packer is able to create Outscale Machine Images (OMIs). To achieve this, Packer comes with
multiple builders depending on the strategy you want to use to build the OMI.
layout: docs
page_title: 'Outscale OMI - Builders'
sidebar_current: 'docs-builders-osc'
---
# Outscale OMI Builder
Packer is able to create Outscale OMIs. To achieve this, Packer comes with
multiple builders depending on the strategy you want to use to build the OMI.
Packer supports the following builders at the moment:
- [osc-bsu](/docs/builders/osc-bsu.html) - Create BSU-backed OMIs by
launching a source OMI and re-packaging it into a new OMI after
provisioning. If in doubt, use this builder, which is the easiest to get
started with.
- [osc-chroot](/docs/builders/osc-chroot.html) - Create EBS-backed OMIs
from an existing OUTSCALE VM by mounting the root device and using a
[Chroot](https://en.wikipedia.org/wiki/Chroot) environment to provision
that device. This is an **advanced builder and should not be used by
newcomers**. However, it is also the fastest way to build an EBS-backed OMI
since no new OUTSCALE VM needs to be launched.
- [osc-bsusurrogate](/docs/builders/osc-bsusurrogate.html) - Create BSU-backed OMIs from scratch. Works similarly to the `chroot` builder but does
not require running in Outscale VM. This is an **advanced builder and should not be
used by newcomers**.
-&gt; **Don't know which builder to use?** If in doubt, use the [osc-bsu
builder](/docs/builders/osc-bsu.html). It is much easier to use and Outscale generally recommends BSU-backed images nowadays.
# Outscale BSU Volume Builder
Packer is able to create Outscale BSU Volumes which are preinitialized with a filesystem and data.
- [osc-bsuvolume](/docs/builders/osc-bsuvolume.html) - Create EBS volumes by launching a source OMI with block devices mapped. Provision the VM, then destroy it, retaining the EBS volumes.
## Authentication
The OUTSCALE provider offers a flexible means of providing credentials for authentication. The following methods are supported, in this order, and explained below:
- Static credentials
- Environment variables
- Shared credentials file
- Outscale Role
### Static Credentials
Static credentials can be provided in the form of an access key id and secret.
These look like:
``` json
{
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"region": "us-east-1",
"type": "osc-bsu",
"oapi_custom_endpoint": "outscale.com/oapi/latest"
}
```
### Environment variables
You can provide your credentials via the `OUTSCALE_ACCESSKEYID` and
`OUTSCALE_SECRETKEYID`, environment variables, representing your Outscale Access
Key and Outscale Secret Key, respectively. The `OUTSCALE_REGION` and
`OUTSCALE_OAPI_URL` environment variables are also used, if applicable:
Usage:
$ export OUTSCALE_ACCESSKEYID="anaccesskey"
$ export OUTSCALE_SECRETKEYID="asecretkey"
$ export OUTSCALE_REGION="eu-west-2"
$ packer build packer.json
### Checking that system time is current
Outscale uses the current time as part of the [request signing
process](http://docs.aws.osc.com/general/latest/gr/sigv4_signing.html). If
your system clock is too skewed from the current time, your requests might
fail. If that's the case, you might see an error like this:
==> osc-bsu: Error querying OMI: AuthFailure: OUTSCALE was not able to validate the provided access credentials
If you suspect your system's date is wrong, you can compare it against
<http://www.time.gov/>. On Linux/OS X, you can run the `date` command to get
the current time. If you're on Linux, you can try setting the time with ntp by
running `sudo ntpd -q`.

View File

@ -151,6 +151,23 @@
</li>
</ul>
</li>
<li<%= sidebar_current("docs-builders-osc") %>>
<a href="/docs/builders/outscale.html">Outscale</a>
<ul class="nav">
<li<%= sidebar_current("docs-builders-osc-chroot") %>>
<a href="/docs/builders/osc-chroot.html">chroot</a>
</li>
<li<%= sidebar_current("docs-builders-osc-bsubacked") %>>
<a href="/docs/builders/osc-bsu.html">BSU</a>
</li>
<li<%= sidebar_current("docs-builders-osc-bsusurrogate") %>>
<a href="/docs/builders/osc-bsusurrogate.html">BSU Surrogate</a>
</li>
<li<%= sidebar_current("docs-builders-osc-bsuvolume") %>>
<a href="/docs/builders/osc-bsuvolume.html">BSU Volume</a>
</li>
</ul>
</li>
<li<%= sidebar_current("docs-builders-parallels") %>>
<a href="/docs/builders/parallels.html">Parallels</a>
<ul class="nav">