Merge pull request #7459 from outscale/osc-develop
Outscale Packer Builder
This commit is contained in:
commit
86a3791aae
|
@ -18,6 +18,7 @@
|
|||
/builder/hyperone @m110 @gregorybrzeski @ad-m
|
||||
/builder/ucloud/ @shawnmssu
|
||||
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
|
||||
/builder/osc @marinsalinas
|
||||
|
||||
# provisioners
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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}}"
|
||||
}]
|
||||
}
|
||||
`
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
}]
|
||||
}
|
||||
`
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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...
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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...
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
)
|
|
@ -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,
|
||||
}
|
|
@ -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
14
go.mod
|
@ -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
78
go.sum
|
@ -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=
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
-> **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 <UUID> 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}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
-> **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 }}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-> **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.
|
|
@ -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 <UUID> 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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-> **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.
|
||||
|
||||
-> **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.
|
|
@ -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.
|
||||
|
||||
-> **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 <UUID> 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
-> **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.
|
||||
|
||||
-> **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.
|
|
@ -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.
|
||||
|
||||
~> **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
|
|
@ -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**.
|
||||
|
||||
-> **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`.
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue