Merge remote-tracking branch 'origin/master' into pr/7501

This commit is contained in:
Adrien Delorme 2019-05-02 11:47:43 +02:00
commit ae71a81c1e
1780 changed files with 267039 additions and 25129 deletions

View File

@ -1,26 +1,60 @@
defaults: &golang
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/hashicorp/packer
steps:
- checkout
- run: go build -o pkg/packer .
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/hashicorp/packer
<<: *golang
steps:
- checkout
- run: make ci
check-vendor-vs-mod:
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/hashicorp/packer
<<: *golang
steps:
- checkout
- run: GO111MODULE=on go run . --help
- run: make check-vendor-vs-mod
build_linux:
<<: *golang
environment:
GOOS: linux
build_windows:
<<: *golang
environment:
GOOS: windows
build_darwin:
<<: *golang
environment:
GOOS: darwin
build_freebsd:
<<: *golang
environment:
GOOS: freebsd
build_solaris:
<<: *golang
environment:
GOOS: solaris
build_openbsd:
<<: *golang
environment:
GOOS: openbsd
workflows:
version: 2
build_and_check_vendor_vs_module:
jobs:
- build
- check-vendor-vs-mod
- check-vendor-vs-mod
- build_linux
- build_darwin
- build_windows
- build_freebsd
- build_openbsd

View File

@ -53,7 +53,7 @@ can quickly merge or address your contributions.
If you have never worked with Go before, you will have to install its
runtime in order to build packer.
1. [Install go](https://golang.org/doc/install#install)
1. This project always releases from the latest version of golang. [Install go](https://golang.org/doc/install#install)
## Setting up Packer for dev

View File

@ -7,6 +7,7 @@
/builder/amazon/ebsvolume/ @jen20
/builder/azure/ @paulmey
/builder/hyperv/ @taliesins
/builder/linode/ @displague @ctreatma @stvnjacobs
/builder/lxc/ @ChrisLundquist
/builder/lxd/ @ChrisLundquist
/builder/oneandone/ @jasmingacic
@ -17,6 +18,7 @@
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hcloud/ @LKaemmerling
/builder/hyperone @m110 @gregorybrzeski @ad-m
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
# provisioners
@ -24,6 +26,7 @@
/provisioner/converge/ @stevendborrelli
# post-processors
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
/post-processor/checksum/ v.tolstov@selfip.ru
/post-processor/googlecompute-export/ crunkleton@google.com

View File

@ -3,10 +3,11 @@ package ecs
import (
"fmt"
"os"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/template/interpolate"
"github.com/hashicorp/packer/version"
)
// Config of alicloud
@ -16,24 +17,33 @@ type AlicloudAccessConfig struct {
AlicloudRegion string `mapstructure:"region"`
AlicloudSkipValidation bool `mapstructure:"skip_region_validation"`
SecurityToken string `mapstructure:"security_token"`
client *ClientWrapper
}
const Packer = "HashiCorp-Packer"
const DefaultRequestReadTimeout = 10 * time.Second
// Client for AlicloudClient
func (c *AlicloudAccessConfig) Client() (*ecs.Client, error) {
if err := c.loadAndValidate(); err != nil {
return nil, err
func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) {
if c.client != nil {
return c.client, nil
}
if c.SecurityToken == "" {
c.SecurityToken = os.Getenv("SECURITY_TOKEN")
}
client := ecs.NewECSClientWithSecurityToken(c.AlicloudAccessKey, c.AlicloudSecretKey,
c.SecurityToken, common.Region(c.AlicloudRegion))
client.SetBusinessInfo("Packer")
if _, err := client.DescribeRegions(); err != nil {
client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey,
c.AlicloudSecretKey, c.SecurityToken)
if err != nil {
return nil, err
}
return client, nil
client.AppendUserAgent(Packer, version.FormattedVersion())
client.SetReadTimeout(DefaultRequestReadTimeout)
c.client = &ClientWrapper{client}
return c.client, nil
}
func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
@ -42,10 +52,12 @@ func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, err)
}
if c.AlicloudRegion != "" && !c.AlicloudSkipValidation {
if c.validateRegion() != nil {
errs = append(errs, fmt.Errorf("Unknown alicloud region: %s", c.AlicloudRegion))
}
if c.AlicloudRegion == "" {
c.AlicloudRegion = os.Getenv("ALICLOUD_REGION")
}
if c.AlicloudRegion == "" {
errs = append(errs, fmt.Errorf("region option or ALICLOUD_REGION must be provided in template file or environment variables."))
}
if len(errs) > 0 {
@ -69,21 +81,38 @@ func (c *AlicloudAccessConfig) Config() error {
}
func (c *AlicloudAccessConfig) loadAndValidate() error {
if err := c.validateRegion(); err != nil {
func (c *AlicloudAccessConfig) ValidateRegion(region string) error {
supportedRegions, err := c.getSupportedRegions()
if err != nil {
return err
}
return nil
}
func (c *AlicloudAccessConfig) validateRegion() error {
for _, valid := range common.ValidRegions {
if c.AlicloudRegion == string(valid) {
for _, supportedRegion := range supportedRegions {
if region == supportedRegion {
return nil
}
}
return fmt.Errorf("Not a valid alicloud region: %s", c.AlicloudRegion)
return fmt.Errorf("Not a valid alicloud region: %s", region)
}
func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) {
client, err := c.Client()
if err != nil {
return nil, err
}
regionsRequest := ecs.CreateDescribeRegionsRequest()
regionsResponse, err := client.DescribeRegions(regionsRequest)
if err != nil {
return nil, err
}
validRegions := make([]string, len(regionsResponse.Regions.Region))
for _, valid := range regionsResponse.Regions.Region {
validRegions = append(validRegions, valid.RegionId)
}
return validRegions, nil
}

View File

@ -1,6 +1,7 @@
package ecs
import (
"os"
"testing"
)
@ -14,14 +15,10 @@ func testAlicloudAccessConfig() *AlicloudAccessConfig {
func TestAlicloudAccessConfigPrepareRegion(t *testing.T) {
c := testAlicloudAccessConfig()
c.AlicloudRegion = ""
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudRegion = "cn-beijing-3"
c.AlicloudRegion = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
t.Fatalf("should have err")
}
c.AlicloudRegion = "cn-beijing"
@ -29,16 +26,11 @@ func TestAlicloudAccessConfigPrepareRegion(t *testing.T) {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudRegion = "unknown"
if err := c.Prepare(nil); err == nil {
t.Fatalf("should have err")
}
c.AlicloudRegion = "unknown"
c.AlicloudSkipValidation = true
os.Setenv("ALICLOUD_REGION", "cn-hangzhou")
c.AlicloudRegion = ""
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudSkipValidation = false
c.AlicloudSkipValidation = false
}

View File

@ -6,8 +6,7 @@ import (
"sort"
"strings"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/packer"
)
@ -19,7 +18,7 @@ type Artifact struct {
BuilderIdValue string
// Alcloud connection for performing API stuff.
Client *ecs.Client
Client *ClientWrapper
}
func (a *Artifact) BuilderId() string {
@ -64,53 +63,73 @@ func (a *Artifact) State(name string) interface{} {
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
for region, imageId := range a.AlicloudImages {
log.Printf("Delete alicloud image ID (%s) from region (%s)", imageId, region)
// Get alicloud image metadata
images, _, err := a.Client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(region),
ImageId: imageId})
copyingImages := make(map[string]string, len(a.AlicloudImages))
sourceImage := make(map[string]*ecs.Image, 1)
for regionId, imageId := range a.AlicloudImages {
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = regionId
describeImagesRequest.ImageId = imageId
describeImagesRequest.Status = ImageStatusQueried
imagesResponse, err := a.Client.DescribeImages(describeImagesRequest)
if err != nil {
errors = append(errors, err)
}
images := imagesResponse.Images.Image
if len(images) == 0 {
err := fmt.Errorf("Error retrieving details for alicloud image(%s), no alicloud images found", imageId)
errors = append(errors, err)
continue
}
//Unshared the shared account before destroy
sharePermissions, err := a.Client.DescribeImageSharePermission(&ecs.ModifyImageSharePermissionArgs{RegionId: common.Region(region), ImageId: imageId})
if err != nil {
if images[0].IsCopied && images[0].Status != ImageStatusAvailable {
copyingImages[regionId] = imageId
} else {
sourceImage[regionId] = &images[0]
}
}
for regionId, imageId := range copyingImages {
log.Printf("Cancel copying alicloud image (%s) from region (%s)", imageId, regionId)
errs := a.unsharedAccountsOnImages(regionId, imageId)
if errs != nil {
errors = append(errors, errs...)
}
cancelImageCopyRequest := ecs.CreateCancelCopyImageRequest()
cancelImageCopyRequest.RegionId = regionId
cancelImageCopyRequest.ImageId = imageId
if _, err := a.Client.CancelCopyImage(cancelImageCopyRequest); err != nil {
errors = append(errors, err)
}
accountsNumber := len(sharePermissions.Accounts.Account)
if accountsNumber > 0 {
accounts := make([]string, accountsNumber)
for index, account := range sharePermissions.Accounts.Account {
accounts[index] = account.AliyunId
}
err := a.Client.ModifyImageSharePermission(&ecs.ModifyImageSharePermissionArgs{
}
RegionId: common.Region(region),
ImageId: imageId,
RemoveAccount: accounts,
})
for regionId, image := range sourceImage {
imageId := image.ImageId
log.Printf("Delete alicloud image (%s) from region (%s)", imageId, regionId)
errs := a.unsharedAccountsOnImages(regionId, imageId)
if errs != nil {
errors = append(errors, errs...)
}
deleteImageRequest := ecs.CreateDeleteImageRequest()
deleteImageRequest.RegionId = regionId
deleteImageRequest.ImageId = imageId
if _, err := a.Client.DeleteImage(deleteImageRequest); err != nil {
errors = append(errors, err)
}
//Delete the snapshot of this images
for _, diskDevices := range image.DiskDeviceMappings.DiskDeviceMapping {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId
_, err := a.Client.DeleteSnapshot(deleteSnapshotRequest)
if err != nil {
errors = append(errors, err)
}
}
// Delete alicloud images
if err := a.Client.DeleteImage(common.Region(region), imageId); err != nil {
errors = append(errors, err)
}
//Delete the snapshot of this images
for _, diskDevices := range images[0].DiskDeviceMappings.DiskDeviceMapping {
if err := a.Client.DeleteSnapshot(diskDevices.SnapshotId); err != nil {
errors = append(errors, err)
}
}
}
if len(errors) > 0 {
@ -124,6 +143,38 @@ func (a *Artifact) Destroy() error {
return nil
}
func (a *Artifact) unsharedAccountsOnImages(regionId string, imageId string) []error {
var errors []error
describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest()
describeImageShareRequest.RegionId = regionId
describeImageShareRequest.ImageId = imageId
imageShareResponse, err := a.Client.DescribeImageSharePermission(describeImageShareRequest)
if err != nil {
errors = append(errors, err)
return errors
}
accountsNumber := len(imageShareResponse.Accounts.Account)
if accountsNumber > 0 {
accounts := make([]string, accountsNumber)
for index, account := range imageShareResponse.Accounts.Account {
accounts[index] = account.AliyunId
}
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
modifyImageShareRequest.RegionId = regionId
modifyImageShareRequest.ImageId = imageId
modifyImageShareRequest.RemoveAccount = &accounts
_, err := a.Client.ModifyImageSharePermission(modifyImageShareRequest)
if err != nil {
errors = append(errors, err)
}
}
return errors
}
func (a *Artifact) stateAtlasMetadata() interface{} {
metadata := make(map[string]string)
for region, imageId := range a.AlicloudImages {

View File

@ -34,8 +34,6 @@ type Builder struct {
type InstanceNetWork string
const (
ClassicNet = InstanceNetWork("classic")
VpcNet = InstanceNetWork("vpc")
ALICLOUD_DEFAULT_SHORT_TIMEOUT = 180
ALICLOUD_DEFAULT_TIMEOUT = 1800
ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600
@ -105,7 +103,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
RegionId: b.config.AlicloudRegion,
},
}
if b.chooseNetworkType() == VpcNet {
if b.chooseNetworkType() == InstanceNetworkVpc {
steps = append(steps,
&stepConfigAlicloudVPC{
VpcId: b.config.VpcId,
@ -136,7 +134,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
InstanceName: b.config.InstanceName,
ZoneId: b.config.ZoneId,
})
if b.chooseNetworkType() == VpcNet {
if b.chooseNetworkType() == InstanceNetworkVpc {
steps = append(steps, &stepConfigAlicloudEIP{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
RegionId: b.config.AlicloudRegion,
@ -153,7 +151,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
steps = append(steps,
&stepAttachKeyPair{},
&stepRunAlicloudInstance{},
&stepMountAlicloudDisk{},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: SSHHost(
@ -228,9 +225,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
func (b *Builder) chooseNetworkType() InstanceNetWork {
if b.isVpcNetRequired() {
return VpcNet
return InstanceNetworkVpc
} else {
return ClassicNet
return InstanceNetworkClassic
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,278 @@
package ecs
import (
"fmt"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
)
type ClientWrapper struct {
*ecs.Client
}
const (
InstanceStatusRunning = "Running"
InstanceStatusStarting = "Starting"
InstanceStatusStopped = "Stopped"
InstanceStatusStopping = "Stopping"
)
const (
ImageStatusWaiting = "Waiting"
ImageStatusCreating = "Creating"
ImageStatusCreateFailed = "CreateFailed"
ImageStatusAvailable = "Available"
)
var ImageStatusQueried = fmt.Sprintf("%s,%s,%s,%s", ImageStatusWaiting, ImageStatusCreating, ImageStatusCreateFailed, ImageStatusAvailable)
const (
SnapshotStatusAll = "all"
SnapshotStatusProgressing = "progressing"
SnapshotStatusAccomplished = "accomplished"
SnapshotStatusFailed = "failed"
)
const (
DiskStatusInUse = "In_use"
DiskStatusAvailable = "Available"
DiskStatusAttaching = "Attaching"
DiskStatusDetaching = "Detaching"
DiskStatusCreating = "Creating"
DiskStatusReIniting = "ReIniting"
)
const (
VpcStatusPending = "Pending"
VpcStatusAvailable = "Available"
)
const (
VSwitchStatusPending = "Pending"
VSwitchStatusAvailable = "Available"
)
const (
EipStatusAssociating = "Associating"
EipStatusUnassociating = "Unassociating"
EipStatusInUse = "InUse"
EipStatusAvailable = "Available"
)
const (
ImageOwnerSystem = "system"
ImageOwnerSelf = "self"
ImageOwnerOthers = "others"
ImageOwnerMarketplace = "marketplace"
)
const (
IOOptimizedNone = "none"
IOOptimizedOptimized = "optimized"
)
const (
InstanceNetworkClassic = "classic"
InstanceNetworkVpc = "vpc"
)
const (
DiskTypeSystem = "system"
DiskTypeData = "data"
)
const (
TagResourceImage = "image"
TagResourceInstance = "instance"
TagResourceSnapshot = "snapshot"
TagResourceDisk = "disk"
)
const (
IpProtocolAll = "all"
IpProtocolTCP = "tcp"
IpProtocolUDP = "udp"
IpProtocolICMP = "icmp"
IpProtocolGRE = "gre"
)
const (
NicTypeInternet = "internet"
NicTypeIntranet = "intranet"
)
const (
DefaultPortRange = "-1/-1"
DefaultCidrIp = "0.0.0.0/0"
DefaultCidrBlock = "172.16.0.0/24"
)
const (
defaultRetryInterval = 5 * time.Second
defaultRetryTimes = 12
shortRetryTimes = 36
mediumRetryTimes = 360
longRetryTimes = 720
)
type WaitForExpectEvalResult struct {
evalPass bool
stopRetry bool
}
var (
WaitForExpectSuccess = WaitForExpectEvalResult{
evalPass: true,
stopRetry: true,
}
WaitForExpectToRetry = WaitForExpectEvalResult{
evalPass: false,
stopRetry: false,
}
WaitForExpectFailToStop = WaitForExpectEvalResult{
evalPass: false,
stopRetry: true,
}
)
type WaitForExpectArgs struct {
RequestFunc func() (responses.AcsResponse, error)
EvalFunc func(response responses.AcsResponse, err error) WaitForExpectEvalResult
RetryInterval time.Duration
RetryTimes int
RetryTimeout time.Duration
}
func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsResponse, error) {
if args.RetryInterval <= 0 {
args.RetryInterval = defaultRetryInterval
}
if args.RetryTimes <= 0 {
args.RetryTimes = defaultRetryTimes
}
var timeoutPoint time.Time
if args.RetryTimeout > 0 {
timeoutPoint = time.Now().Add(args.RetryTimeout)
}
var lastError error
for i := 0; ; i++ {
if args.RetryTimeout > 0 && time.Now().After(timeoutPoint) {
break
}
if args.RetryTimeout <= 0 && i >= args.RetryTimes {
break
}
response, err := args.RequestFunc()
lastError = err
evalResult := args.EvalFunc(response, err)
if evalResult.evalPass {
return response, nil
}
if evalResult.stopRetry {
return nil, err
}
time.Sleep(args.RetryInterval)
}
if args.RetryTimeout > 0 {
return nil, fmt.Errorf("evaluate failed after %d seconds timeout with %d seconds retry interval: %s", int(args.RetryTimeout.Seconds()), int(args.RetryInterval.Seconds()), lastError)
}
return nil, fmt.Errorf("evaluate failed after %d times retry with %d seconds retry interval: %s", args.RetryTimes, int(args.RetryInterval.Seconds()), lastError)
}
func (c *ClientWrapper) WaitForInstanceStatus(regionId string, instanceId string, expectedStatus string) (responses.AcsResponse, error) {
return c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeInstancesRequest()
request.RegionId = regionId
request.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId)
return c.DescribeInstances(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
instancesResponse := response.(*ecs.DescribeInstancesResponse)
instances := instancesResponse.Instances.Instance
for _, instance := range instances {
if instance.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimes: mediumRetryTimes,
})
}
func (c *ClientWrapper) WaitForImageStatus(regionId string, imageId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) {
return c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeImagesRequest()
request.RegionId = regionId
request.ImageId = imageId
request.Status = ImageStatusQueried
return c.DescribeImages(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
imagesResponse := response.(*ecs.DescribeImagesResponse)
images := imagesResponse.Images.Image
for _, image := range images {
if image.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimeout: timeout,
})
}
type EvalErrorType bool
const (
EvalRetryErrorType = EvalErrorType(true)
EvalNotRetryErrorType = EvalErrorType(false)
)
func (c *ClientWrapper) EvalCouldRetryResponse(evalErrors []string, evalErrorType EvalErrorType) func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
return func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err == nil {
return WaitForExpectSuccess
}
e, ok := err.(errors.Error)
if !ok {
return WaitForExpectToRetry
}
if evalErrorType == EvalRetryErrorType && !ContainsInArray(evalErrors, e.ErrorCode()) {
return WaitForExpectFailToStop
}
if evalErrorType == EvalNotRetryErrorType && ContainsInArray(evalErrors, e.ErrorCode()) {
return WaitForExpectFailToStop
}
return WaitForExpectToRetry
}
}

View File

@ -0,0 +1,83 @@
package ecs
import (
"fmt"
"testing"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
)
func TestWaitForExpectedExceedRetryTimes(t *testing.T) {
c := ClientWrapper{}
iter := 0
waitDone := make(chan bool, 1)
go func() {
_, _ = c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
iter++
return nil, fmt.Errorf("test: let iteration %d failed", iter)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
fmt.Printf("need retry: %s\n", err)
return WaitForExpectToRetry
}
return WaitForExpectSuccess
},
})
waitDone <- true
}()
timeTolerance := 1 * time.Second
select {
case <-waitDone:
if iter != defaultRetryTimes {
t.Fatalf("WaitForExpected should terminate at the %d iterations", defaultRetryTimes)
}
case <-time.After(defaultRetryTimes*defaultRetryInterval + timeTolerance):
t.Fatalf("WaitForExpected should terminate within %f seconds", (defaultRetryTimes*defaultRetryInterval + timeTolerance).Seconds())
}
}
func TestWaitForExpectedExceedRetryTimeout(t *testing.T) {
c := ClientWrapper{}
expectTimeout := 10 * time.Second
iter := 0
waitDone := make(chan bool, 1)
go func() {
_, _ = c.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
iter++
return nil, fmt.Errorf("test: let iteration %d failed", iter)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
fmt.Printf("need retry: %s\n", err)
return WaitForExpectToRetry
}
return WaitForExpectSuccess
},
RetryTimeout: expectTimeout,
})
waitDone <- true
}()
timeTolerance := 1 * time.Second
select {
case <-waitDone:
if iter > int(expectTimeout/defaultRetryInterval) {
t.Fatalf("WaitForExpected should terminate before the %d iterations", int(expectTimeout/defaultRetryInterval))
}
case <-time.After(expectTimeout + timeTolerance):
t.Fatalf("WaitForExpected should terminate within %f seconds", (expectTimeout + timeTolerance).Seconds())
}
}

View File

@ -2,11 +2,9 @@ package ecs
import (
"fmt"
"regexp"
"strings"
"github.com/denverdino/aliyungo/common"
"github.com/hashicorp/packer/template/interpolate"
)
@ -18,6 +16,7 @@ type AlicloudDiskDevice struct {
Description string `mapstructure:"disk_description"`
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance"`
Device string `mapstructure:"disk_device"`
Encrypted *bool `mapstructure:"disk_encrypted"`
}
type AlicloudDiskDevices struct {
@ -33,6 +32,7 @@ type AlicloudImageConfig struct {
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
ImageEncrypted *bool `mapstructure:"image_encrypted"`
AlicloudImageForceDelete bool `mapstructure:"image_force_delete"`
AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"`
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
@ -69,15 +69,6 @@ func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
// Mark that we saw the region
regionSet[region] = struct{}{}
if !c.AlicloudImageSkipRegionValidation {
// Verify the region is real
if valid := validateRegion(region); valid != nil {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
}
regions = append(regions, region)
}
@ -90,14 +81,3 @@ func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
return nil
}
func validateRegion(region string) error {
for _, valid := range common.ValidRegions {
if region == string(valid) {
return nil
}
}
return fmt.Errorf("Not a valid alicloud region: %s", region)
}

View File

@ -2,8 +2,6 @@ package ecs
import (
"testing"
"github.com/denverdino/aliyungo/common"
)
func testAlicloudImageConfig() *AlicloudImageConfig {
@ -31,28 +29,17 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudImageDestinationRegions = regionsToString()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudImageDestinationRegions = []string{"foo"}
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
c.AlicloudImageDestinationRegions = []string{"cn-beijing", "cn-hangzhou", "eu-central-1"}
if err := c.Prepare(nil); err != nil {
t.Fatalf("bad: %s", err)
}
c.AlicloudImageDestinationRegions = []string{"unknow"}
c.AlicloudImageDestinationRegions = nil
c.AlicloudImageSkipRegionValidation = true
if err := c.Prepare(nil); err != nil {
t.Fatal("shouldn't have error")
}
c.AlicloudImageSkipRegionValidation = false
}
func TestECSImageConfigPrepare_imageTags(t *testing.T) {
@ -72,11 +59,3 @@ func TestECSImageConfigPrepare_imageTags(t *testing.T) {
}, c.AlicloudImageTags)
}
}
func regionsToString() []string {
var regions []string
for _, region := range common.ValidRegions {
regions = append(regions, string(region))
}
return regions
}

View File

@ -2,12 +2,13 @@ package ecs
import (
"fmt"
"strconv"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func message(state multistep.StateBag, module string) {
func cleanUpMessage(state multistep.StateBag, module string) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
@ -18,7 +19,6 @@ func message(state multistep.StateBag, module string) {
} else {
ui.Say(fmt.Sprintf("Cleaning up '%s'", module))
}
}
func halt(state multistep.StateBag, err error, prefix string) multistep.StepAction {
@ -32,3 +32,21 @@ func halt(state multistep.StateBag, err error, prefix string) multistep.StepActi
ui.Error(err.Error())
return multistep.ActionHalt
}
func convertNumber(value int) string {
if value <= 0 {
return ""
}
return strconv.Itoa(value)
}
func ContainsInArray(arr []string, value string) bool {
for _, item := range arr {
if item == value {
return true
}
}
return false
}

View File

@ -4,10 +4,8 @@ import (
"context"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -15,53 +13,57 @@ import (
type stepAttachKeyPair struct {
}
var attachKeyPairNotRetryErrors = []string{
"MissingParameter",
"DependencyViolation.WindowsInstance",
"InvalidKeyPairName.NotFound",
"InvalidRegionId.NotFound",
}
func (s *stepAttachKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
timeoutPoint := time.Now().Add(120 * time.Second)
instance := state.Get("instance").(*ecs.Instance)
keyPairName := config.Comm.SSHKeyPairName
if keyPairName == "" {
return multistep.ActionContinue
}
for {
err := client.AttachKeyPair(&ecs.AttachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
if err != nil {
e, _ := err.(*common.Error)
if (!(e.Code == "MissingParameter" || e.Code == "DependencyViolation.WindowsInstance" ||
e.Code == "InvalidKeyPairName.NotFound" || e.Code == "InvalidRegionId.NotFound")) &&
time.Now().Before(timeoutPoint) {
time.Sleep(5 * time.Second)
continue
}
err := fmt.Errorf("Error attaching keypair %s to instance %s : %s",
keyPairName, instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
break
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateAttachKeyPairRequest()
request.RegionId = config.AlicloudRegion
request.KeyPairName = keyPairName
request.InstanceIds = "[\"" + instance.InstanceId + "\"]"
return client.AttachKeyPair(request)
},
EvalFunc: client.EvalCouldRetryResponse(attachKeyPairNotRetryErrors, EvalNotRetryErrorType),
})
if err != nil {
return halt(state, err, fmt.Sprintf("Error attaching keypair %s to instance %s", keyPairName, instance.InstanceId))
}
ui.Message(fmt.Sprintf("Attach keypair %s to instance: %s", keyPairName, instance.InstanceId))
return multistep.ActionContinue
}
func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
instance := state.Get("instance").(*ecs.Instance)
keyPairName := config.Comm.SSHKeyPairName
if keyPairName == "" {
return
}
err := client.DetachKeyPair(&ecs.DetachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
detachKeyPairRequest := ecs.CreateDetachKeyPairRequest()
detachKeyPairRequest.RegionId = config.AlicloudRegion
detachKeyPairRequest.KeyPairName = keyPairName
detachKeyPairRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId)
_, err := client.DetachKeyPair(detachKeyPairRequest)
if err != nil {
err := fmt.Errorf("Error Detaching keypair %s to instance %s : %s", keyPairName,
instance.InstanceId, err)

View File

@ -4,8 +4,7 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -15,40 +14,35 @@ type stepCheckAlicloudSourceImage struct {
}
func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
args := &ecs.DescribeImagesArgs{
RegionId: common.Region(config.AlicloudRegion),
ImageId: config.AlicloudSourceImage,
}
args.PageSize = 50
images, _, err := client.DescribeImages(args)
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = config.AlicloudRegion
describeImagesRequest.ImageId = config.AlicloudSourceImage
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
err := fmt.Errorf("Error querying alicloud image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return halt(state, err, "Error querying alicloud image")
}
images := imagesResponse.Images.Image
// Describe markerplace image
args.ImageOwnerAlias = ecs.ImageOwnerMarketplace
imageMarkets, _, err := client.DescribeImages(args)
describeImagesRequest.ImageOwnerAlias = "marketplace"
marketImagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
err := fmt.Errorf("Error querying alicloud marketplace image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return halt(state, err, "Error querying alicloud marketplace image")
}
if len(imageMarkets) > 0 {
images = append(images, imageMarkets...)
marketImages := marketImagesResponse.Images.Image
if len(marketImages) > 0 {
images = append(images, marketImages...)
}
if len(images) == 0 {
err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return halt(state, err, "")
}
ui.Message(fmt.Sprintf("Found image ID: %s", images[0].ImageId))

View File

@ -4,8 +4,11 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
"github.com/hashicorp/packer/common/uuid"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -19,10 +22,14 @@ type stepConfigAlicloudEIP struct {
SSHPrivateIp bool
}
var allocateEipAddressRetryErrors = []string{
"LastTokenProcessing",
}
func (s *stepConfigAlicloudEIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
instance := state.Get("instance").(*ecs.Instance)
if s.SSHPrivateIp {
ipaddress := instance.VpcAttributes.PrivateIpAddress.IpAddress
@ -34,37 +41,48 @@ func (s *stepConfigAlicloudEIP) Run(ctx context.Context, state multistep.StateBa
return multistep.ActionContinue
}
ui.Say("Allocating eip")
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
RegionId: common.Region(s.RegionId), InternetChargeType: common.InternetChargeType(s.InternetChargeType),
Bandwidth: s.InternetMaxBandwidthOut,
ui.Say("Allocating eip...")
allocateEipAddressRequest := s.buildAllocateEipAddressRequest(state)
allocateEipAddressResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.AllocateEipAddress(allocateEipAddressRequest)
},
EvalFunc: client.EvalCouldRetryResponse(allocateEipAddressRetryErrors, EvalRetryErrorType),
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error allocating eip: %s", err))
return multistep.ActionHalt
return halt(state, err, "Error allocating eip")
}
ipaddress := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).EipAddress
ui.Message(fmt.Sprintf("Allocated eip: %s", ipaddress))
allocateId := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).AllocationId
s.allocatedId = allocateId
if err = client.WaitForEip(common.Region(s.RegionId), allocateId,
ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error allocating eip: %s", err))
return multistep.ActionHalt
err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable)
if err != nil {
return halt(state, err, "Error wait eip available timeout")
}
if err = client.AssociateEipAddress(allocateId, instance.InstanceId); err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error binding eip: %s", err))
return multistep.ActionHalt
associateEipAddressRequest := ecs.CreateAssociateEipAddressRequest()
associateEipAddressRequest.AllocationId = allocateId
associateEipAddressRequest.InstanceId = instance.InstanceId
if _, err := client.AssociateEipAddress(associateEipAddressRequest); err != nil {
e, ok := err.(errors.Error)
if !ok || e.ErrorCode() != "TaskConflict" {
return halt(state, err, "Error associating eip")
}
ui.Error(fmt.Sprintf("Error associate eip: %s", err))
}
if err = client.WaitForEip(common.Region(s.RegionId), allocateId,
ecs.EipStatusInUse, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error associating eip: %s", err))
return multistep.ActionHalt
err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusInUse)
if err != nil {
return halt(state, err, "Error wait eip associated timeout")
}
ui.Say(fmt.Sprintf("Allocated eip %s", ipaddress))
state.Put("ipaddress", ipaddress)
return multistep.ActionContinue
}
@ -74,21 +92,74 @@ func (s *stepConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*ecs.Client)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
cleanUpMessage(state, "EIP")
client := state.Get("client").(*ClientWrapper)
instance := state.Get("instance").(*ecs.Instance)
ui := state.Get("ui").(packer.Ui)
message(state, "EIP")
if err := client.UnassociateEipAddress(s.allocatedId, instance.InstanceId); err != nil {
ui.Say(fmt.Sprintf("Failed to unassociate eip."))
unassociateEipAddressRequest := ecs.CreateUnassociateEipAddressRequest()
unassociateEipAddressRequest.AllocationId = s.allocatedId
unassociateEipAddressRequest.InstanceId = instance.InstanceId
if _, err := client.UnassociateEipAddress(unassociateEipAddressRequest); err != nil {
ui.Say(fmt.Sprintf("Failed to unassociate eip: %s", err))
}
if err := client.WaitForEip(common.Region(s.RegionId), s.allocatedId, ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
ui.Say(fmt.Sprintf("Timeout while unassociating eip."))
}
if err := client.ReleaseEipAddress(s.allocatedId); err != nil {
ui.Say(fmt.Sprintf("Failed to release eip."))
if err := s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable); err != nil {
ui.Say(fmt.Sprintf("Timeout while unassociating eip: %s", err))
}
releaseEipAddressRequest := ecs.CreateReleaseEipAddressRequest()
releaseEipAddressRequest.AllocationId = s.allocatedId
if _, err := client.ReleaseEipAddress(releaseEipAddressRequest); err != nil {
ui.Say(fmt.Sprintf("Failed to release eip: %s", err))
}
}
func (s *stepConfigAlicloudEIP) waitForEipStatus(client *ClientWrapper, regionId string, allocationId string, expectedStatus string) error {
describeEipAddressesRequest := ecs.CreateDescribeEipAddressesRequest()
describeEipAddressesRequest.RegionId = regionId
describeEipAddressesRequest.AllocationId = s.allocatedId
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
response, err := client.DescribeEipAddresses(describeEipAddressesRequest)
if err == nil && len(response.EipAddresses.EipAddress) == 0 {
err = fmt.Errorf("eip allocated is not find")
}
return response, err
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
eipAddressesResponse := response.(*ecs.DescribeEipAddressesResponse)
eipAddresses := eipAddressesResponse.EipAddresses.EipAddress
for _, eipAddress := range eipAddresses {
if eipAddress.Status == expectedStatus {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimes: shortRetryTimes,
})
return err
}
func (s *stepConfigAlicloudEIP) buildAllocateEipAddressRequest(state multistep.StateBag) *ecs.AllocateEipAddressRequest {
instance := state.Get("instance").(*ecs.Instance)
request := ecs.CreateAllocateEipAddressRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = instance.RegionId
request.InternetChargeType = s.InternetChargeType
request.Bandwidth = string(convertNumber(s.InternetMaxBandwidthOut))
return request
}

View File

@ -6,8 +6,7 @@ import (
"os"
"runtime"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
@ -34,7 +33,6 @@ func (s *stepConfigAlicloudKeyPair) Run(ctx context.Context, state multistep.Sta
}
s.Comm.SSHPrivateKey = privateKeyBytes
return multistep.ActionContinue
}
@ -54,16 +52,15 @@ func (s *stepConfigAlicloudKeyPair) Run(ctx context.Context, state multistep.Sta
return multistep.ActionContinue
}
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
keyResp, err := client.CreateKeyPair(&ecs.CreateKeyPairArgs{
KeyPairName: s.Comm.SSHTemporaryKeyPairName,
RegionId: common.Region(s.RegionId),
})
createKeyPairRequest := ecs.CreateCreateKeyPairRequest()
createKeyPairRequest.RegionId = s.RegionId
createKeyPairRequest.KeyPairName = s.Comm.SSHTemporaryKeyPairName
keyResp, err := client.CreateKeyPair(createKeyPairRequest)
if err != nil {
state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err))
return multistep.ActionHalt
return halt(state, err, "Error creating temporary keypair")
}
// Set the keyname so we know to delete it later
@ -110,15 +107,16 @@ func (s *stepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
// Remove the keypair
ui.Say("Deleting temporary keypair...")
err := client.DeleteKeyPairs(&ecs.DeleteKeyPairsArgs{
RegionId: common.Region(s.RegionId),
KeyPairNames: "[\"" + s.keyName + "\"]",
})
deleteKeyPairsRequest := ecs.CreateDeleteKeyPairsRequest()
deleteKeyPairsRequest.RegionId = s.RegionId
deleteKeyPairsRequest.KeyPairNames = fmt.Sprintf("[\"%s\"]", s.keyName)
_, err := client.DeleteKeyPairs(deleteKeyPairsRequest)
if err != nil {
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.keyName))

View File

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -16,9 +16,9 @@ type stepConfigAlicloudPublicIP struct {
}
func (s *stepConfigAlicloudPublicIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
instance := state.Get("instance").(*ecs.Instance)
if s.SSHPrivateIp {
ipaddress := instance.InnerIpAddress.IpAddress
@ -30,15 +30,16 @@ func (s *stepConfigAlicloudPublicIP) Run(ctx context.Context, state multistep.St
return multistep.ActionContinue
}
ipaddress, err := client.AllocatePublicIpAddress(instance.InstanceId)
allocatePublicIpAddressRequest := ecs.CreateAllocatePublicIpAddressRequest()
allocatePublicIpAddressRequest.InstanceId = instance.InstanceId
ipaddress, err := client.AllocatePublicIpAddress(allocatePublicIpAddressRequest)
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error allocating public ip: %s", err))
return multistep.ActionHalt
return halt(state, err, "Error allocating public ip")
}
s.publicIPAddress = ipaddress
ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress))
state.Put("ipaddress", ipaddress)
s.publicIPAddress = ipaddress.IpAddress
ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress.IpAddress))
state.Put("ipaddress", ipaddress.IpAddress)
return multistep.ActionContinue
}

View File

@ -2,12 +2,11 @@ package ecs
import (
"context"
"errors"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -21,31 +20,34 @@ type stepConfigAlicloudSecurityGroup struct {
isCreate bool
}
var createSecurityGroupRetryErrors = []string{
"IdempotentProcessing",
}
var deleteSecurityGroupRetryErrors = []string{
"DependencyViolation",
}
func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
networkType := state.Get("networktype").(InstanceNetWork)
var securityGroupItems []ecs.SecurityGroupItemType
var err error
if len(s.SecurityGroupId) != 0 {
if networkType == VpcNet {
describeSecurityGroupsRequest := ecs.CreateDescribeSecurityGroupsRequest()
describeSecurityGroupsRequest.RegionId = s.RegionId
if networkType == InstanceNetworkVpc {
vpcId := state.Get("vpcid").(string)
securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
VpcId: vpcId,
RegionId: common.Region(s.RegionId),
})
} else {
securityGroupItems, _, err = client.DescribeSecurityGroups(&ecs.DescribeSecurityGroupsArgs{
RegionId: common.Region(s.RegionId),
})
describeSecurityGroupsRequest.VpcId = vpcId
}
securityGroupsResponse, err := client.DescribeSecurityGroups(describeSecurityGroupsRequest)
if err != nil {
ui.Say(fmt.Sprintf("Failed querying security group: %s", err))
state.Put("error", err)
return multistep.ActionHalt
return halt(state, err, "Failed querying security group")
}
securityGroupItems := securityGroupsResponse.SecurityGroups.SecurityGroup
for _, securityGroupItem := range securityGroupItems {
if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
state.Put("securitygroupid", s.SecurityGroupId)
@ -53,61 +55,55 @@ func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multist
return multistep.ActionContinue
}
}
s.isCreate = false
message := fmt.Sprintf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
state.Put("error", errors.New(message))
ui.Say(message)
return multistep.ActionHalt
s.isCreate = false
err = fmt.Errorf("The specified security group {%s} doesn't exist.", s.SecurityGroupId)
return halt(state, err, "")
}
var securityGroupId string
ui.Say("Creating security groups...")
if networkType == VpcNet {
vpcId := state.Get("vpcid").(string)
securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
RegionId: common.Region(s.RegionId),
SecurityGroupName: s.SecurityGroupName,
VpcId: vpcId,
})
} else {
securityGroupId, err = client.CreateSecurityGroup(&ecs.CreateSecurityGroupArgs{
RegionId: common.Region(s.RegionId),
SecurityGroupName: s.SecurityGroupName,
})
}
ui.Say("Creating security group...")
createSecurityGroupRequest := s.buildCreateSecurityGroupRequest(state)
securityGroupResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateSecurityGroup(createSecurityGroupRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createSecurityGroupRetryErrors, EvalRetryErrorType),
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Failed creating security group %s.", err))
return multistep.ActionHalt
return halt(state, err, "Failed creating security group")
}
securityGroupId := securityGroupResponse.(*ecs.CreateSecurityGroupResponse).SecurityGroupId
ui.Message(fmt.Sprintf("Created security group: %s", securityGroupId))
state.Put("securitygroupid", securityGroupId)
s.isCreate = true
s.SecurityGroupId = securityGroupId
err = client.AuthorizeSecurityGroupEgress(&ecs.AuthorizeSecurityGroupEgressArgs{
SecurityGroupId: securityGroupId,
RegionId: common.Region(s.RegionId),
IpProtocol: ecs.IpProtocolAll,
PortRange: "-1/-1",
NicType: ecs.NicTypeInternet,
DestCidrIp: "0.0.0.0/0", //The input parameter "DestGroupId" or "DestCidrIp" cannot be both blank.
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
return multistep.ActionHalt
authorizeSecurityGroupEgressRequest := ecs.CreateAuthorizeSecurityGroupEgressRequest()
authorizeSecurityGroupEgressRequest.SecurityGroupId = securityGroupId
authorizeSecurityGroupEgressRequest.RegionId = s.RegionId
authorizeSecurityGroupEgressRequest.IpProtocol = IpProtocolAll
authorizeSecurityGroupEgressRequest.PortRange = DefaultPortRange
authorizeSecurityGroupEgressRequest.NicType = NicTypeInternet
authorizeSecurityGroupEgressRequest.DestCidrIp = DefaultCidrIp
if _, err := client.AuthorizeSecurityGroupEgress(authorizeSecurityGroupEgressRequest); err != nil {
return halt(state, err, "Failed authorizing security group")
}
err = client.AuthorizeSecurityGroup(&ecs.AuthorizeSecurityGroupArgs{
SecurityGroupId: securityGroupId,
RegionId: common.Region(s.RegionId),
IpProtocol: ecs.IpProtocolAll,
PortRange: "-1/-1",
NicType: ecs.NicTypeInternet,
SourceCidrIp: "0.0.0.0/0", //The input parameter "SourceGroupId" or "SourceCidrIp" cannot be both blank.
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Failed authorizing security group: %s", err))
return multistep.ActionHalt
authorizeSecurityGroupRequest := ecs.CreateAuthorizeSecurityGroupRequest()
authorizeSecurityGroupRequest.SecurityGroupId = securityGroupId
authorizeSecurityGroupRequest.RegionId = s.RegionId
authorizeSecurityGroupRequest.IpProtocol = IpProtocolAll
authorizeSecurityGroupRequest.PortRange = DefaultPortRange
authorizeSecurityGroupRequest.NicType = NicTypeInternet
authorizeSecurityGroupRequest.SourceCidrIp = DefaultCidrIp
if _, err := client.AuthorizeSecurityGroup(authorizeSecurityGroupRequest); err != nil {
return halt(state, err, "Failed authorizing security group")
}
return multistep.ActionContinue
@ -118,21 +114,39 @@ func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*ecs.Client)
cleanUpMessage(state, "security group")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
message(state, "security group")
timeoutPoint := time.Now().Add(120 * time.Second)
for {
if err := client.DeleteSecurityGroup(common.Region(s.RegionId), s.SecurityGroupId); err != nil {
e, _ := err.(*common.Error)
if e.Code == "DependencyViolation" && time.Now().Before(timeoutPoint) {
time.Sleep(5 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
return
}
break
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteSecurityGroupRequest()
request.RegionId = s.RegionId
request.SecurityGroupId = s.SecurityGroupId
return client.DeleteSecurityGroup(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteSecurityGroupRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))
}
}
func (s *stepConfigAlicloudSecurityGroup) buildCreateSecurityGroupRequest(state multistep.StateBag) *ecs.CreateSecurityGroupRequest {
networkType := state.Get("networktype").(InstanceNetWork)
request := ecs.CreateCreateSecurityGroupRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = s.RegionId
request.SecurityGroupName = s.SecurityGroupName
if networkType == InstanceNetworkVpc {
vpcId := state.Get("vpcid").(string)
request.VpcId = vpcId
}
return request
}

View File

@ -2,12 +2,12 @@ package ecs
import (
"context"
"errors"
errorsNew "errors"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -19,54 +19,94 @@ type stepConfigAlicloudVPC struct {
isCreate bool
}
var createVpcRetryErrors = []string{
"TOKEN_PROCESSING",
}
var deleteVpcRetryErrors = []string{
"DependencyViolation.Instance",
"DependencyViolation.RouteEntry",
"DependencyViolation.VSwitch",
"DependencyViolation.SecurityGroup",
"Forbbiden",
"TaskConflict",
}
func (s *stepConfigAlicloudVPC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
if len(s.VpcId) != 0 {
vpcs, _, err := client.DescribeVpcs(&ecs.DescribeVpcsArgs{
VpcId: s.VpcId,
RegionId: common.Region(config.AlicloudRegion),
})
describeVpcsRequest := ecs.CreateDescribeVpcsRequest()
describeVpcsRequest.VpcId = s.VpcId
describeVpcsRequest.RegionId = config.AlicloudRegion
vpcsResponse, err := client.DescribeVpcs(describeVpcsRequest)
if err != nil {
ui.Say(fmt.Sprintf("Failed querying vpcs: %s", err))
state.Put("error", err)
return multistep.ActionHalt
return halt(state, err, "Failed querying vpcs")
}
vpcs := vpcsResponse.Vpcs.Vpc
if len(vpcs) > 0 {
vpc := vpcs[0]
state.Put("vpcid", vpc.VpcId)
state.Put("vpcid", vpcs[0].VpcId)
s.isCreate = false
return multistep.ActionContinue
}
message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId)
state.Put("error", errors.New(message))
ui.Say(message)
return multistep.ActionHalt
message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId)
return halt(state, errorsNew.New(message), "")
}
ui.Say("Creating vpc")
vpc, err := client.CreateVpc(&ecs.CreateVpcArgs{
RegionId: common.Region(config.AlicloudRegion),
CidrBlock: s.CidrBlock,
VpcName: s.VpcName,
ui.Say("Creating vpc...")
createVpcRequest := s.buildCreateVpcRequest(state)
createVpcResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateVpc(createVpcRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createVpcRetryErrors, EvalRetryErrorType),
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Failed creating vpc: %s", err))
return multistep.ActionHalt
}
err = client.WaitForVpcAvailable(common.Region(config.AlicloudRegion), vpc.VpcId, ALICLOUD_DEFAULT_SHORT_TIMEOUT)
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Failed waiting for vpc to become available: %s", err))
return multistep.ActionHalt
return halt(state, err, "Failed creating vpc")
}
state.Put("vpcid", vpc.VpcId)
vpcId := createVpcResponse.(*ecs.CreateVpcResponse).VpcId
_, err = client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeVpcsRequest()
request.RegionId = config.AlicloudRegion
request.VpcId = vpcId
return client.DescribeVpcs(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
vpcsResponse := response.(*ecs.DescribeVpcsResponse)
vpcs := vpcsResponse.Vpcs.Vpc
if len(vpcs) > 0 {
for _, vpc := range vpcs {
if vpc.Status == VpcStatusAvailable {
return WaitForExpectSuccess
}
}
}
return WaitForExpectToRetry
},
RetryTimes: shortRetryTimes,
})
if err != nil {
return halt(state, err, "Failed waiting for vpc to become available")
}
ui.Message(fmt.Sprintf("Created vpc: %s", vpcId))
state.Put("vpcid", vpcId)
s.isCreate = true
s.VpcId = vpc.VpcId
s.VpcId = vpcId
return multistep.ActionContinue
}
@ -75,24 +115,34 @@ func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*ecs.Client)
cleanUpMessage(state, "VPC")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
message(state, "VPC")
timeoutPoint := time.Now().Add(60 * time.Second)
for {
if err := client.DeleteVpc(s.VpcId); err != nil {
e, _ := err.(*common.Error)
if (e.Code == "DependencyViolation.Instance" || e.Code == "DependencyViolation.RouteEntry" ||
e.Code == "DependencyViolation.VSwitch" ||
e.Code == "DependencyViolation.SecurityGroup" ||
e.Code == "Forbbiden") && time.Now().Before(timeoutPoint) {
time.Sleep(1 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err))
return
}
break
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteVpcRequest()
request.VpcId = s.VpcId
return client.DeleteVpc(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteVpcRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err))
}
}
func (s *stepConfigAlicloudVPC) buildCreateVpcRequest(state multistep.StateBag) *ecs.CreateVpcRequest {
config := state.Get("config").(*Config)
request := ecs.CreateCreateVpcRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = config.AlicloudRegion
request.CidrBlock = s.CidrBlock
request.VpcName = s.VpcName
return request
}

View File

@ -2,12 +2,11 @@ package ecs
import (
"context"
"errors"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -20,52 +19,65 @@ type stepConfigAlicloudVSwitch struct {
VSwitchName string
}
var createVSwitchRetryErrors = []string{
"TOKEN_PROCESSING",
}
var deleteVSwitchRetryErrors = []string{
"IncorrectVSwitchStatus",
"DependencyViolation",
"DependencyViolation.HaVip",
"IncorrectRouteEntryStatus",
"TaskConflict",
}
func (s *stepConfigAlicloudVSwitch) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
vpcId := state.Get("vpcid").(string)
config := state.Get("config").(*Config)
if len(s.VSwitchId) != 0 {
vswitchs, _, err := client.DescribeVSwitches(&ecs.DescribeVSwitchesArgs{
VpcId: vpcId,
VSwitchId: s.VSwitchId,
ZoneId: s.ZoneId,
})
describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest()
describeVSwitchesRequest.VpcId = vpcId
describeVSwitchesRequest.VSwitchId = s.VSwitchId
describeVSwitchesRequest.ZoneId = s.ZoneId
vswitchesResponse, err := client.DescribeVSwitches(describeVSwitchesRequest)
if err != nil {
ui.Say(fmt.Sprintf("Failed querying vswitch: %s", err))
state.Put("error", err)
return multistep.ActionHalt
return halt(state, err, "Failed querying vswitch")
}
if len(vswitchs) > 0 {
vswitch := vswitchs[0]
state.Put("vswitchid", vswitch.VSwitchId)
vswitch := vswitchesResponse.VSwitches.VSwitch
if len(vswitch) > 0 {
state.Put("vswitchid", vswitch[0].VSwitchId)
s.isCreate = false
return multistep.ActionContinue
}
s.isCreate = false
message := fmt.Sprintf("The specified vswitch {%s} doesn't exist.", s.VSwitchId)
state.Put("error", errors.New(message))
ui.Say(message)
return multistep.ActionHalt
return halt(state, fmt.Errorf("The specified vswitch {%s} doesn't exist.", s.VSwitchId), "")
}
if s.ZoneId == "" {
zones, err := client.DescribeZones(common.Region(config.AlicloudRegion))
if s.ZoneId == "" {
describeZonesRequest := ecs.CreateDescribeZonesRequest()
describeZonesRequest.RegionId = config.AlicloudRegion
zonesResponse, err := client.DescribeZones(describeZonesRequest)
if err != nil {
ui.Say(fmt.Sprintf("Query for available zones failed: %s", err))
state.Put("error", err)
return multistep.ActionHalt
return halt(state, err, "Query for available zones failed")
}
var instanceTypes []string
zones := zonesResponse.Zones.Zone
for _, zone := range zones {
isVSwitchSupported := false
for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes {
if resourceType == ecs.ResourceTypeVSwitch {
if resourceType == "VSwitch" {
isVSwitchSupported = true
}
}
if isVSwitchSupported {
for _, instanceType := range zone.AvailableInstanceTypes.InstanceTypes {
if instanceType == config.InstanceType {
@ -97,29 +109,62 @@ func (s *stepConfigAlicloudVSwitch) Run(ctx context.Context, state multistep.Sta
}
}
}
if config.CidrBlock == "" {
s.CidrBlock = "172.16.0.0/24" //use the default CirdBlock
s.CidrBlock = DefaultCidrBlock //use the default CirdBlock
}
ui.Say("Creating vswitch...")
vswitchId, err := client.CreateVSwitch(&ecs.CreateVSwitchArgs{
CidrBlock: s.CidrBlock,
ZoneId: s.ZoneId,
VpcId: vpcId,
VSwitchName: s.VSwitchName,
createVSwitchRequest := s.buildCreateVSwitchRequest(state)
createVSwitchResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateVSwitch(createVSwitchRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createVSwitchRetryErrors, EvalRetryErrorType),
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Create vswitch failed %v", err))
return multistep.ActionHalt
return halt(state, err, "Error Creating vswitch")
}
if err := client.WaitForVSwitchAvailable(vpcId, s.VSwitchId, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
state.Put("error", err)
ui.Error(fmt.Sprintf("Timeout waiting for vswitch to become available: %v", err))
return multistep.ActionHalt
vSwitchId := createVSwitchResponse.(*ecs.CreateVSwitchResponse).VSwitchId
describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest()
describeVSwitchesRequest.VpcId = vpcId
describeVSwitchesRequest.VSwitchId = vSwitchId
_, err = client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.DescribeVSwitches(describeVSwitchesRequest)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
vSwitchesResponse := response.(*ecs.DescribeVSwitchesResponse)
vSwitches := vSwitchesResponse.VSwitches.VSwitch
if len(vSwitches) > 0 {
for _, vSwitch := range vSwitches {
if vSwitch.Status == VSwitchStatusAvailable {
return WaitForExpectSuccess
}
}
}
return WaitForExpectToRetry
},
RetryTimes: shortRetryTimes,
})
if err != nil {
return halt(state, err, "Timeout waiting for vswitch to become available")
}
state.Put("vswitchid", vswitchId)
ui.Message(fmt.Sprintf("Created vswitch: %s", vSwitchId))
state.Put("vswitchid", vSwitchId)
s.isCreate = true
s.VSwitchId = vswitchId
s.VSwitchId = vSwitchId
return multistep.ActionContinue
}
@ -128,22 +173,35 @@ func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*ecs.Client)
cleanUpMessage(state, "vSwitch")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
message(state, "vSwitch")
timeoutPoint := time.Now().Add(10 * time.Second)
for {
if err := client.DeleteVSwitch(s.VSwitchId); err != nil {
e, _ := err.(*common.Error)
if (e.Code == "IncorrectVSwitchStatus" || e.Code == "DependencyViolation" ||
e.Code == "DependencyViolation.HaVip" ||
e.Code == "IncorrectRouteEntryStatus") && time.Now().Before(timeoutPoint) {
time.Sleep(1 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err))
return
}
break
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteVSwitchRequest()
request.VSwitchId = s.VSwitchId
return client.DeleteVSwitch(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteVSwitchRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err))
}
}
func (s *stepConfigAlicloudVSwitch) buildCreateVSwitchRequest(state multistep.StateBag) *ecs.CreateVSwitchRequest {
vpcId := state.Get("vpcid").(string)
request := ecs.CreateCreateVSwitchRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.CidrBlock = s.CidrBlock
request.ZoneId = s.ZoneId
request.VpcId = vpcId
request.VSwitchName = s.VSwitchName
return request
}

View File

@ -3,9 +3,13 @@ package ecs
import (
"context"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/packer/common/random"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -13,59 +17,53 @@ import (
type stepCreateAlicloudImage struct {
AlicloudImageIgnoreDataDisks bool
WaitSnapshotReadyTimeout int
image *ecs.ImageType
image *ecs.Image
}
var createImageRetryErrors = []string{
"IdempotentProcessing",
}
func (s *stepCreateAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
// Create the alicloud image
ui.Say(fmt.Sprintf("Creating image: %s", config.AlicloudImageName))
var imageId string
var err error
if s.AlicloudImageIgnoreDataDisks {
snapshotId := state.Get("alicloudsnapshot").(string)
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
RegionId: common.Region(config.AlicloudRegion),
SnapshotId: snapshotId,
ImageName: config.AlicloudImageName,
ImageVersion: config.AlicloudImageVersion,
Description: config.AlicloudImageDescription})
tempImageName := config.AlicloudImageName
if config.ImageEncrypted != nil && *config.ImageEncrypted {
tempImageName = fmt.Sprintf("packer_%s", random.AlphaNum(7))
ui.Say(fmt.Sprintf("Creating temporary image for encryption: %s", tempImageName))
} else {
instance := state.Get("instance").(*ecs.InstanceAttributesType)
imageId, err = client.CreateImage(&ecs.CreateImageArgs{
RegionId: common.Region(config.AlicloudRegion),
InstanceId: instance.InstanceId,
ImageName: config.AlicloudImageName,
ImageVersion: config.AlicloudImageVersion,
Description: config.AlicloudImageDescription})
ui.Say(fmt.Sprintf("Creating image: %s", tempImageName))
}
createImageRequest := s.buildCreateImageRequest(state, tempImageName)
createImageResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateImage(createImageRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createImageRetryErrors, EvalRetryErrorType),
})
if err != nil {
return halt(state, err, "Error creating image")
}
err = client.WaitForImageReady(common.Region(config.AlicloudRegion), imageId, s.WaitSnapshotReadyTimeout)
imageId := createImageResponse.(*ecs.CreateImageResponse).ImageId
imagesResponse, err := client.WaitForImageStatus(config.AlicloudRegion, imageId, ImageStatusAvailable, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second)
if err != nil {
return halt(state, err, "Timeout waiting for image to be created")
}
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(config.AlicloudRegion),
ImageId: imageId})
if err != nil {
return halt(state, err, "Error querying created imaged")
}
images := imagesResponse.(*ecs.DescribeImagesResponse).Images.Image
if len(images) == 0 {
return halt(state, err, "Unable to find created image")
}
s.image = &images[0]
var snapshotIds = []string{}
var snapshotIds []string
for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping {
snapshotIds = append(snapshotIds, device.SnapshotId)
}
@ -84,19 +82,62 @@ func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
if s.image == nil {
return
}
config := state.Get("config").(*Config)
encryptedSet := config.ImageEncrypted != nil && *config.ImageEncrypted
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
if !cancelled && !halted && !encryptedSet {
return
}
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
ui.Say("Deleting the image because of cancellation or error...")
if err := client.DeleteImage(common.Region(config.AlicloudRegion), s.image.ImageId); err != nil {
if !cancelled && !halted && encryptedSet {
ui.Say(fmt.Sprintf("Deleting temporary image %s(%s) and related snapshots after finishing encryption...", s.image.ImageId, s.image.ImageName))
} else {
ui.Say("Deleting the image and related snapshots because of cancellation or error...")
}
deleteImageRequest := ecs.CreateDeleteImageRequest()
deleteImageRequest.RegionId = config.AlicloudRegion
deleteImageRequest.ImageId = s.image.ImageId
if _, err := client.DeleteImage(deleteImageRequest); err != nil {
ui.Error(fmt.Sprintf("Error deleting image, it may still be around: %s", err))
return
}
//Delete the snapshot of this image
for _, diskDevices := range s.image.DiskDeviceMappings.DiskDeviceMapping {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
return
}
}
}
func (s *stepCreateAlicloudImage) buildCreateImageRequest(state multistep.StateBag, imageName string) *ecs.CreateImageRequest {
config := state.Get("config").(*Config)
request := ecs.CreateCreateImageRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = config.AlicloudRegion
request.ImageName = imageName
request.ImageVersion = config.AlicloudImageVersion
request.Description = config.AlicloudImageDescription
if s.AlicloudImageIgnoreDataDisks {
snapshotId := state.Get("alicloudsnapshot").(string)
request.SnapshotId = snapshotId
} else {
instance := state.Get("instance").(*ecs.Instance)
request.InstanceId = instance.InstanceId
}
return request
}

View File

@ -2,12 +2,16 @@ package ecs
import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"strconv"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/packer/common/uuid"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -23,99 +27,55 @@ type stepCreateAlicloudInstance struct {
InternetMaxBandwidthOut int
InstanceName string
ZoneId string
instance *ecs.InstanceAttributesType
instance *ecs.Instance
}
var createInstanceRetryErrors = []string{
"IdempotentProcessing",
}
var deleteInstanceRetryErrors = []string{
"IncorrectInstanceStatus.Initializing",
}
func (s *stepCreateAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(*Config)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
source_image := state.Get("source_image").(*ecs.ImageType)
network_type := state.Get("networktype").(InstanceNetWork)
securityGroupId := state.Get("securitygroupid").(string)
var instanceId string
var err error
ioOptimized := ecs.IoOptimizedNone
if s.IOOptimized {
ioOptimized = ecs.IoOptimizedOptimized
}
password := config.Comm.SSHPassword
if password == "" && config.Comm.WinRMPassword != "" {
password = config.Comm.WinRMPassword
}
ui.Say("Creating instance.")
if network_type == VpcNet {
userData, err := s.getUserData(state)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
vswitchId := state.Get("vswitchid").(string)
instanceId, err = client.CreateInstance(&ecs.CreateInstanceArgs{
RegionId: common.Region(s.RegionId),
ImageId: source_image.ImageId,
InstanceType: s.InstanceType,
InternetChargeType: common.InternetChargeType(s.InternetChargeType), //"PayByTraffic",
InternetMaxBandwidthOut: s.InternetMaxBandwidthOut,
UserData: userData,
IoOptimized: ioOptimized,
VSwitchId: vswitchId,
SecurityGroupId: securityGroupId,
InstanceName: s.InstanceName,
Password: password,
ZoneId: s.ZoneId,
SystemDisk: systemDeviceToDiskType(config.AlicloudImageConfig.ECSSystemDiskMapping),
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
})
if err != nil {
err := fmt.Errorf("Error creating instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
} else {
if s.InstanceType == "" {
s.InstanceType = "PayByTraffic"
}
if s.InternetMaxBandwidthOut == 0 {
s.InternetMaxBandwidthOut = 5
}
instanceId, err = client.CreateInstance(&ecs.CreateInstanceArgs{
RegionId: common.Region(s.RegionId),
ImageId: source_image.ImageId,
InstanceType: s.InstanceType,
InternetChargeType: common.InternetChargeType(s.InternetChargeType), //"PayByTraffic",
InternetMaxBandwidthOut: s.InternetMaxBandwidthOut,
IoOptimized: ioOptimized,
SecurityGroupId: securityGroupId,
InstanceName: s.InstanceName,
Password: password,
ZoneId: s.ZoneId,
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
})
if err != nil {
err := fmt.Errorf("Error creating instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
err = client.WaitForInstance(instanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
ui.Say("Creating instance...")
createInstanceRequest, err := s.buildCreateInstanceRequest(state)
if err != nil {
err := fmt.Errorf("Error creating instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return halt(state, err, "")
}
instance, err := client.DescribeInstanceAttribute(instanceId)
createInstanceResponse, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
return client.CreateInstance(createInstanceRequest)
},
EvalFunc: client.EvalCouldRetryResponse(createInstanceRetryErrors, EvalRetryErrorType),
})
if err != nil {
ui.Say(err.Error())
return multistep.ActionHalt
return halt(state, err, "Error creating instance")
}
s.instance = instance
state.Put("instance", instance)
instanceId := createInstanceResponse.(*ecs.CreateInstanceResponse).InstanceId
_, err = client.WaitForInstanceStatus(s.RegionId, instanceId, InstanceStatusStopped)
if err != nil {
return halt(state, err, "Error waiting create instance")
}
describeInstancesRequest := ecs.CreateDescribeInstancesRequest()
describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId)
instances, err := client.DescribeInstances(describeInstancesRequest)
if err != nil {
return halt(state, err, "")
}
ui.Message(fmt.Sprintf("Created instance: %s", instanceId))
s.instance = &instances.Instances.Instance[0]
state.Put("instance", s.instance)
return multistep.ActionContinue
}
@ -124,51 +84,121 @@ func (s *stepCreateAlicloudInstance) Cleanup(state multistep.StateBag) {
if s.instance == nil {
return
}
message(state, "instance")
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
err := client.DeleteInstance(s.instance.InstanceId)
if err != nil {
ui.Say(fmt.Sprintf("Failed to clean up instance %s: %v", s.instance.InstanceId, err.Error()))
}
cleanUpMessage(state, "instance")
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
_, err := client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDeleteInstanceRequest()
request.InstanceId = s.instance.InstanceId
request.Force = requests.NewBoolean(true)
return client.DeleteInstance(request)
},
EvalFunc: client.EvalCouldRetryResponse(deleteInstanceRetryErrors, EvalRetryErrorType),
RetryTimes: shortRetryTimes,
})
if err != nil {
ui.Say(fmt.Sprintf("Failed to clean up instance %s: %s", s.instance.InstanceId, err))
}
}
func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.StateBag) (*ecs.CreateInstanceRequest, error) {
request := ecs.CreateCreateInstanceRequest()
request.ClientToken = uuid.TimeOrderedUUID()
request.RegionId = s.RegionId
request.InstanceType = s.InstanceType
request.InstanceName = s.InstanceName
request.ZoneId = s.ZoneId
sourceImage := state.Get("source_image").(*ecs.Image)
request.ImageId = sourceImage.ImageId
securityGroupId := state.Get("securitygroupid").(string)
request.SecurityGroupId = securityGroupId
networkType := state.Get("networktype").(InstanceNetWork)
if networkType == InstanceNetworkVpc {
vswitchId := state.Get("vswitchid").(string)
request.VSwitchId = vswitchId
userData, err := s.getUserData(state)
if err != nil {
return nil, err
}
request.UserData = userData
} else {
if s.InternetChargeType == "" {
s.InternetChargeType = "PayByTraffic"
}
if s.InternetMaxBandwidthOut == 0 {
s.InternetMaxBandwidthOut = 5
}
}
request.InternetChargeType = s.InternetChargeType
request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut))
ioOptimized := IOOptimizedNone
if s.IOOptimized {
ioOptimized = IOOptimizedOptimized
}
request.IoOptimized = ioOptimized
config := state.Get("config").(*Config)
password := config.Comm.SSHPassword
if password == "" && config.Comm.WinRMPassword != "" {
password = config.Comm.WinRMPassword
}
request.Password = password
systemDisk := config.AlicloudImageConfig.ECSSystemDiskMapping
request.SystemDiskDiskName = systemDisk.DiskName
request.SystemDiskCategory = systemDisk.DiskCategory
request.SystemDiskSize = requests.Integer(convertNumber(systemDisk.DiskSize))
request.SystemDiskDescription = systemDisk.Description
imageDisks := config.AlicloudImageConfig.ECSImagesDiskMappings
var dataDisks []ecs.CreateInstanceDataDisk
for _, imageDisk := range imageDisks {
var dataDisk ecs.CreateInstanceDataDisk
dataDisk.DiskName = imageDisk.DiskName
dataDisk.Category = imageDisk.DiskCategory
dataDisk.Size = string(convertNumber(imageDisk.DiskSize))
dataDisk.SnapshotId = imageDisk.SnapshotId
dataDisk.Description = imageDisk.Description
dataDisk.DeleteWithInstance = strconv.FormatBool(imageDisk.DeleteWithInstance)
dataDisk.Device = imageDisk.Device
if imageDisk.Encrypted != nil {
dataDisk.Encrypted = strconv.FormatBool(*imageDisk.Encrypted)
}
dataDisks = append(dataDisks, dataDisk)
}
request.DataDisk = &dataDisks
return request, nil
}
func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (string, error) {
userData := s.UserData
if s.UserDataFile != "" {
data, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
return "", err
}
userData = string(data)
}
log.Printf(userData)
if userData != "" {
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
return userData, nil
}
func systemDeviceToDiskType(systemDisk AlicloudDiskDevice) ecs.SystemDiskType {
return ecs.SystemDiskType{
DiskName: systemDisk.DiskName,
Category: ecs.DiskCategory(systemDisk.DiskCategory),
Size: systemDisk.DiskSize,
Description: systemDisk.Description,
}
}
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
result := make([]ecs.DataDiskType, len(diskDevices))
for _, diskDevice := range diskDevices {
result = append(result, ecs.DataDiskType{
DiskName: diskDevice.DiskName,
Category: ecs.DiskCategory(diskDevice.DiskCategory),
Size: diskDevice.DiskSize,
SnapshotId: diskDevice.SnapshotId,
Description: diskDevice.Description,
DeleteWithInstance: diskDevice.DeleteWithInstance,
Device: diskDevice.Device,
})
}
return result
}

View File

@ -3,33 +3,35 @@ package ecs
import (
"context"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepCreateAlicloudSnapshot struct {
snapshot *ecs.SnapshotType
snapshot *ecs.Snapshot
WaitSnapshotReadyTimeout int
}
func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.Instance)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{
RegionId: common.Region(config.AlicloudRegion),
InstanceId: instance.InstanceId,
DiskType: ecs.DiskTypeAllSystem,
})
describeDisksRequest := ecs.CreateDescribeDisksRequest()
describeDisksRequest.RegionId = config.AlicloudRegion
describeDisksRequest.InstanceId = instance.InstanceId
describeDisksRequest.DiskType = DiskTypeSystem
disksResponse, err := client.DescribeDisks(describeDisksRequest)
if err != nil {
return halt(state, err, "Error describe disks")
}
disks := disksResponse.Disks.Disk
if len(disks) == 0 {
return halt(state, err, "Unable to find system disk of instance")
}
@ -37,33 +39,57 @@ func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.St
// Create the alicloud snapshot
ui.Say(fmt.Sprintf("Creating snapshot from system disk: %s", disks[0].DiskId))
snapshotId, err := client.CreateSnapshot(&ecs.CreateSnapshotArgs{
DiskId: disks[0].DiskId,
})
createSnapshotRequest := ecs.CreateCreateSnapshotRequest()
createSnapshotRequest.DiskId = disks[0].DiskId
snapshot, err := client.CreateSnapshot(createSnapshotRequest)
if err != nil {
return halt(state, err, "Error creating snapshot")
}
err = client.WaitForSnapShotReady(common.Region(config.AlicloudRegion), snapshotId, s.WaitSnapshotReadyTimeout)
_, err = client.WaitForExpected(&WaitForExpectArgs{
RequestFunc: func() (responses.AcsResponse, error) {
request := ecs.CreateDescribeSnapshotsRequest()
request.RegionId = config.AlicloudRegion
request.SnapshotIds = snapshot.SnapshotId
return client.DescribeSnapshots(request)
},
EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult {
if err != nil {
return WaitForExpectToRetry
}
snapshotsResponse := response.(*ecs.DescribeSnapshotsResponse)
snapshots := snapshotsResponse.Snapshots.Snapshot
for _, snapshot := range snapshots {
if snapshot.Status == SnapshotStatusAccomplished {
return WaitForExpectSuccess
}
}
return WaitForExpectToRetry
},
RetryTimeout: time.Duration(s.WaitSnapshotReadyTimeout) * time.Second,
})
if err != nil {
return halt(state, err, "Timeout waiting for snapshot to be created")
}
snapshots, _, err := client.DescribeSnapshots(&ecs.DescribeSnapshotsArgs{
RegionId: common.Region(config.AlicloudRegion),
SnapshotIds: []string{snapshotId},
})
describeSnapshotsRequest := ecs.CreateDescribeSnapshotsRequest()
describeSnapshotsRequest.RegionId = config.AlicloudRegion
describeSnapshotsRequest.SnapshotIds = snapshot.SnapshotId
snapshotsResponse, err := client.DescribeSnapshots(describeSnapshotsRequest)
if err != nil {
return halt(state, err, "Error querying created snapshot")
}
snapshots := snapshotsResponse.Snapshots.Snapshot
if len(snapshots) == 0 {
return halt(state, err, "Unable to find created snapshot")
}
s.snapshot = &snapshots[0]
state.Put("alicloudsnapshot", snapshotId)
s.snapshot = &snapshots[0]
state.Put("alicloudsnapshot", snapshot.SnapshotId)
return multistep.ActionContinue
}
@ -77,11 +103,14 @@ func (s *stepCreateAlicloudSnapshot) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting the snapshot because of cancellation or error...")
if err := client.DeleteSnapshot(s.snapshot.SnapshotId); err != nil {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = s.snapshot.SnapshotId
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err))
return
}

View File

@ -4,8 +4,7 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -16,7 +15,7 @@ type stepCreateTags struct {
func (s *stepCreateTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
imageId := state.Get("alicloudimage").(string)
snapshotIds := state.Get("alicloudsnapshots").([]string)
@ -24,26 +23,37 @@ func (s *stepCreateTags) Run(ctx context.Context, state multistep.StateBag) mult
if len(s.Tags) == 0 {
return multistep.ActionContinue
}
ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId))
err := client.AddTags(&ecs.AddTagsArgs{
ResourceId: imageId,
ResourceType: ecs.TagResourceImage,
RegionId: common.Region(config.AlicloudRegion),
Tag: s.Tags,
})
if err != nil {
var tags []ecs.AddTagsTag
for key, value := range s.Tags {
var tag ecs.AddTagsTag
tag.Key = key
tag.Value = value
tags = append(tags, tag)
}
addTagsRequest := ecs.CreateAddTagsRequest()
addTagsRequest.RegionId = config.AlicloudRegion
addTagsRequest.ResourceId = imageId
addTagsRequest.ResourceType = TagResourceImage
addTagsRequest.Tag = &tags
if _, err := client.AddTags(addTagsRequest); err != nil {
return halt(state, err, "Error Adding tags to image")
}
for _, snapshotId := range snapshotIds {
ui.Say(fmt.Sprintf("Adding tags(%s) to snapshot: %s", s.Tags, snapshotId))
err = client.AddTags(&ecs.AddTagsArgs{
ResourceId: snapshotId,
ResourceType: ecs.TagResourceSnapshot,
RegionId: common.Region(config.AlicloudRegion),
Tag: s.Tags,
})
if err != nil {
addTagsRequest := ecs.CreateAddTagsRequest()
addTagsRequest.RegionId = config.AlicloudRegion
addTagsRequest.ResourceId = snapshotId
addTagsRequest.ResourceType = TagResourceSnapshot
addTagsRequest.Tag = &tags
if _, err := client.AddTags(addTagsRequest); err != nil {
return halt(state, err, "Error Adding tags to snapshot")
}
}

View File

@ -5,8 +5,7 @@ import (
"fmt"
"log"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -54,13 +53,15 @@ func (s *stepDeleteAlicloudImageSnapshots) Run(ctx context.Context, state multis
}
func (s *stepDeleteAlicloudImageSnapshots) deleteImageAndSnapshots(state multistep.StateBag, imageName string, region string) error {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(region),
ImageName: imageName,
})
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = region
describeImagesRequest.ImageName = imageName
describeImagesRequest.Status = ImageStatusQueried
imageResponse, _ := client.DescribeImages(describeImagesRequest)
images := imageResponse.Images.Image
if len(images) < 1 {
return nil
}
@ -68,20 +69,24 @@ func (s *stepDeleteAlicloudImageSnapshots) deleteImageAndSnapshots(state multist
ui.Say(fmt.Sprintf("Deleting duplicated image and snapshot in %s: %s", region, imageName))
for _, image := range images {
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
if image.ImageOwnerAlias != ImageOwnerSelf {
log.Printf("You can not delete non-customized images: %s ", image.ImageId)
continue
}
err = client.DeleteImage(common.Region(region), image.ImageId)
if err != nil {
deleteImageRequest := ecs.CreateDeleteImageRequest()
deleteImageRequest.RegionId = region
deleteImageRequest.ImageId = image.ImageId
if _, err := client.DeleteImage(deleteImageRequest); err != nil {
err := fmt.Errorf("Failed to delete image: %s", err)
return err
}
if s.AlicloudImageForceDeleteSnapshots {
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
if err := client.DeleteSnapshot(diskDevice.SnapshotId); err != nil {
deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest()
deleteSnapshotRequest.SnapshotId = diskDevice.SnapshotId
if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil {
err := fmt.Errorf("Deleting ECS snapshot failed: %s", err)
return err
}

View File

@ -1,72 +0,0 @@
package ecs
import (
"context"
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type stepMountAlicloudDisk struct {
}
func (s *stepMountAlicloudDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
alicloudDiskDevices := config.ECSImagesDiskMappings
if len(config.ECSImagesDiskMappings) == 0 {
return multistep.ActionContinue
}
ui.Say("Mounting disks.")
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{InstanceId: instance.InstanceId,
RegionId: instance.RegionId})
if err != nil {
err := fmt.Errorf("Error querying disks: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, disk := range disks {
if disk.Status == ecs.DiskStatusAvailable {
if err := client.AttachDisk(&ecs.AttachDiskArgs{DiskId: disk.DiskId,
InstanceId: instance.InstanceId,
Device: getDevice(&disk, alicloudDiskDevices),
}); err != nil {
err := fmt.Errorf("Error mounting disks: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
for _, disk := range disks {
if err := client.WaitForDisk(instance.RegionId, disk.DiskId, ecs.DiskStatusInUse, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
err := fmt.Errorf("Timeout waiting for mount: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Finished mounting disks.")
return multistep.ActionContinue
}
func (s *stepMountAlicloudDisk) Cleanup(state multistep.StateBag) {
}
func getDevice(disk *ecs.DiskItemType, diskDevices []AlicloudDiskDevice) string {
if disk.Device != "" {
return disk.Device
}
for _, alicloudDiskDevice := range diskDevices {
if alicloudDiskDevice.DiskName == disk.DiskName || alicloudDiskDevice.SnapshotId == disk.SourceSnapshotId {
return alicloudDiskDevice.Device
}
}
return ""
}

View File

@ -4,8 +4,7 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -16,34 +15,73 @@ type stepPreValidate struct {
}
func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.ForceDelete {
ui.Say("Force delete flag found, skipping prevalidating image name.")
return multistep.ActionContinue
if err := s.validateRegions(state); err != nil {
return halt(state, err, "")
}
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(*Config)
ui.Say("Prevalidating image name...")
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
ImageName: s.AlicloudDestImageName,
RegionId: common.Region(config.AlicloudRegion)})
if err != nil {
err := fmt.Errorf("Error querying alicloud image: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(images) > 0 {
err := fmt.Errorf("Error: Image Name: '%s' is used by an existing alicloud image: %s", images[0].ImageName, images[0].ImageId)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
if err := s.validateDestImageName(state); err != nil {
return halt(state, err, "")
}
return multistep.ActionContinue
}
func (s *stepPreValidate) validateRegions(state multistep.StateBag) error {
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
if config.AlicloudSkipValidation {
ui.Say("Skip region validation flag found, skipping prevalidating source region and copied regions.")
return nil
}
ui.Say("Prevalidating source region and copied regions...")
var errs *packer.MultiError
if err := config.ValidateRegion(config.AlicloudRegion); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
for _, region := range config.AlicloudImageDestinationRegions {
if err := config.ValidateRegion(region); err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (s *stepPreValidate) validateDestImageName(state multistep.StateBag) error {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
config := state.Get("config").(*Config)
if s.ForceDelete {
ui.Say("Force delete flag found, skipping prevalidating image name.")
return nil
}
ui.Say("Prevalidating image name...")
describeImagesRequest := ecs.CreateDescribeImagesRequest()
describeImagesRequest.RegionId = config.AlicloudRegion
describeImagesRequest.ImageName = s.AlicloudDestImageName
describeImagesRequest.Status = ImageStatusQueried
imagesResponse, err := client.DescribeImages(describeImagesRequest)
if err != nil {
return fmt.Errorf("Error querying alicloud image: %s", err)
}
images := imagesResponse.Images.Image
if len(images) > 0 {
return fmt.Errorf("Error: Image Name: '%s' is used by an existing alicloud image: %s", images[0].ImageName, images[0].ImageId)
}
return nil
}
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}

View File

@ -3,9 +3,10 @@ package ecs
import (
"context"
"fmt"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -17,56 +18,88 @@ type stepRegionCopyAlicloudImage struct {
}
func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
if config.ImageEncrypted != nil {
s.AlicloudImageDestinationRegions = append(s.AlicloudImageDestinationRegions, s.RegionId)
s.AlicloudImageDestinationNames = append(s.AlicloudImageDestinationNames, config.AlicloudImageName)
}
if len(s.AlicloudImageDestinationRegions) == 0 {
return multistep.ActionContinue
}
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
imageId := state.Get("alicloudimage").(string)
alicloudImages := state.Get("alicloudimages").(map[string]string)
region := common.Region(s.RegionId)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
srcImageId := state.Get("alicloudimage").(string)
alicloudImages := state.Get("alicloudimages").(map[string]string)
numberOfName := len(s.AlicloudImageDestinationNames)
ui.Say(fmt.Sprintf("Coping image %s from %s...", srcImageId, s.RegionId))
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
if destinationRegion == s.RegionId {
if destinationRegion == s.RegionId && config.ImageEncrypted == nil {
continue
}
ecsImageName := ""
if numberOfName > 0 && index < numberOfName {
ecsImageName = s.AlicloudImageDestinationNames[index]
}
imageId, err := client.CopyImage(
&ecs.CopyImageArgs{
RegionId: region,
ImageId: imageId,
DestinationRegionId: common.Region(destinationRegion),
DestinationImageName: ecsImageName,
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error copying images: %s", err))
return multistep.ActionHalt
copyImageRequest := ecs.CreateCopyImageRequest()
copyImageRequest.RegionId = s.RegionId
copyImageRequest.ImageId = srcImageId
copyImageRequest.DestinationRegionId = destinationRegion
copyImageRequest.DestinationImageName = ecsImageName
if config.ImageEncrypted != nil {
copyImageRequest.Encrypted = requests.NewBoolean(*config.ImageEncrypted)
}
alicloudImages[destinationRegion] = imageId
imageResponse, err := client.CopyImage(copyImageRequest)
if err != nil {
return halt(state, err, "Error copying images")
}
alicloudImages[destinationRegion] = imageResponse.ImageId
ui.Message(fmt.Sprintf("Copy image from %s(%s) to %s(%s)", s.RegionId, srcImageId, destinationRegion, imageResponse.ImageId))
}
if config.ImageEncrypted != nil {
if _, err := client.WaitForImageStatus(s.RegionId, alicloudImages[s.RegionId], ImageStatusAvailable, time.Duration(ALICLOUD_DEFAULT_LONG_TIMEOUT)*time.Second); err != nil {
return halt(state, err, fmt.Sprintf("Timeout waiting image %s finish copying", alicloudImages[s.RegionId]))
}
}
return multistep.ActionContinue
}
func (s *stepRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ecs.Client)
alicloudImages := state.Get("alicloudimages").(map[string]string)
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
for copiedRegionId, copiedImageId := range alicloudImages {
if copiedRegionId == s.RegionId {
continue
}
if err := client.CancelCopyImage(common.Region(copiedRegionId), copiedImageId); err != nil {
ui.Say(fmt.Sprintf("Error cancelling copy image: %v", err))
}
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
client := state.Get("client").(*ClientWrapper)
alicloudImages := state.Get("alicloudimages").(map[string]string)
srcImageId := state.Get("alicloudimage").(string)
for copiedRegionId, copiedImageId := range alicloudImages {
if copiedImageId == srcImageId {
continue
}
cancelCopyImageRequest := ecs.CreateCancelCopyImageRequest()
cancelCopyImageRequest.RegionId = copiedRegionId
cancelCopyImageRequest.ImageId = copiedImageId
if _, err := client.CancelCopyImage(cancelCopyImageRequest); err != nil {
ui.Error(fmt.Sprintf("Error cancelling copy image: %v", err))
}
}
}

View File

@ -4,7 +4,8 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -13,26 +14,21 @@ type stepRunAlicloudInstance struct {
}
func (s *stepRunAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
client := state.Get("client").(*ClientWrapper)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
instance := state.Get("instance").(*ecs.Instance)
err := client.StartInstance(instance.InstanceId)
if err != nil {
err := fmt.Errorf("Error starting instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
startInstanceRequest := ecs.CreateStartInstanceRequest()
startInstanceRequest.InstanceId = instance.InstanceId
if _, err := client.StartInstance(startInstanceRequest); err != nil {
return halt(state, err, "Error starting instance")
}
ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId))
err = client.WaitForInstance(instance.InstanceId, ecs.Running, ALICLOUD_DEFAULT_TIMEOUT)
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusRunning)
if err != nil {
err := fmt.Errorf("Timeout waiting for instance to start: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return halt(state, err, "Timeout waiting for instance to start")
}
return multistep.ActionContinue
@ -41,19 +37,36 @@ func (s *stepRunAlicloudInstance) Run(ctx context.Context, state multistep.State
func (s *stepRunAlicloudInstance) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ecs.Client)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
instanceAttribute, _ := client.DescribeInstanceAttribute(instance.InstanceId)
if instanceAttribute.Status == ecs.Starting || instanceAttribute.Status == ecs.Running {
if err := client.StopInstance(instance.InstanceId, true); err != nil {
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
return
}
if err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
}
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
instance := state.Get("instance").(*ecs.Instance)
describeInstancesRequest := ecs.CreateDescribeInstancesRequest()
describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId)
instancesResponse, _ := client.DescribeInstances(describeInstancesRequest)
if len(instancesResponse.Instances.Instance) == 0 {
return
}
instanceAttribute := instancesResponse.Instances.Instance[0]
if instanceAttribute.Status == InstanceStatusStarting || instanceAttribute.Status == InstanceStatusRunning {
stopInstanceRequest := ecs.CreateStopInstanceRequest()
stopInstanceRequest.InstanceId = instance.InstanceId
stopInstanceRequest.ForceStop = requests.NewBoolean(true)
if _, err := client.StopInstance(stopInstanceRequest); err != nil {
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
return
}
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped)
if err != nil {
ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err))
}
}
}

View File

@ -4,8 +4,7 @@ import (
"context"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -17,21 +16,18 @@ type stepShareAlicloudImage struct {
}
func (s *stepShareAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
alicloudImages := state.Get("alicloudimages").(map[string]string)
for copiedRegion, copiedImageId := range alicloudImages {
err := client.ModifyImageSharePermission(
&ecs.ModifyImageSharePermissionArgs{
RegionId: common.Region(copiedRegion),
ImageId: copiedImageId,
AddAccount: s.AlicloudImageShareAccounts,
RemoveAccount: s.AlicloudImageUNShareAccounts,
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Failed modifying image share permissions: %s", err))
return multistep.ActionHalt
for regionId, imageId := range alicloudImages {
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
modifyImageShareRequest.RegionId = regionId
modifyImageShareRequest.ImageId = imageId
modifyImageShareRequest.AddAccount = &s.AlicloudImageShareAccounts
modifyImageShareRequest.RemoveAccount = &s.AlicloudImageUNShareAccounts
if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil {
return halt(state, err, "Failed modifying image share permissions")
}
}
return multistep.ActionContinue
@ -40,22 +36,25 @@ func (s *stepShareAlicloudImage) Run(ctx context.Context, state multistep.StateB
func (s *stepShareAlicloudImage) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ecs.Client)
alicloudImages := state.Get("alicloudimages").(map[string]string)
ui.Say("Restoring image share permission because cancellations or error...")
for copiedRegion, copiedImageId := range alicloudImages {
err := client.ModifyImageSharePermission(
&ecs.ModifyImageSharePermissionArgs{
RegionId: common.Region(copiedRegion),
ImageId: copiedImageId,
AddAccount: s.AlicloudImageUNShareAccounts,
RemoveAccount: s.AlicloudImageShareAccounts,
})
if err != nil {
ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err))
}
if !cancelled && !halted {
return
}
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*ClientWrapper)
alicloudImages := state.Get("alicloudimages").(map[string]string)
ui.Say("Restoring image share permission because cancellations or error...")
for regionId, imageId := range alicloudImages {
modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest()
modifyImageShareRequest.RegionId = regionId
modifyImageShareRequest.ImageId = imageId
modifyImageShareRequest.AddAccount = &s.AlicloudImageUNShareAccounts
modifyImageShareRequest.RemoveAccount = &s.AlicloudImageShareAccounts
if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil {
ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err))
}
}
}

View File

@ -3,8 +3,11 @@ package ecs
import (
"context"
"fmt"
"strconv"
"github.com/denverdino/aliyungo/ecs"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -15,29 +18,26 @@ type stepStopAlicloudInstance struct {
}
func (s *stepStopAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
client := state.Get("client").(*ClientWrapper)
instance := state.Get("instance").(*ecs.Instance)
ui := state.Get("ui").(packer.Ui)
if !s.DisableStop {
ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId))
err := client.StopInstance(instance.InstanceId, s.ForceStop)
if err != nil {
err := fmt.Errorf("Error stopping alicloud instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
stopInstanceRequest := ecs.CreateStopInstanceRequest()
stopInstanceRequest.InstanceId = instance.InstanceId
stopInstanceRequest.ForceStop = requests.Boolean(strconv.FormatBool(s.ForceStop))
if _, err := client.StopInstance(stopInstanceRequest); err != nil {
return halt(state, err, "Error stopping alicloud instance")
}
}
ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId))
err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
_, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped)
if err != nil {
err := fmt.Errorf("Error waiting for alicloud instance to stop: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
return halt(state, err, "Error waiting for alicloud instance to stop")
}
return multistep.ActionContinue

View File

@ -280,6 +280,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,

View File

@ -14,6 +14,7 @@ import (
type StepAMIRegionCopy struct {
AccessConfig *AccessConfig
Regions []string
AMIKmsKeyId string
RegionKeyIds map[string]string
EncryptBootVolume *bool // nil means preserve
Name string
@ -31,6 +32,10 @@ func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) m
// AMI with required encryption setting.
// temp image was created by stepCreateAMI.
s.Regions = append(s.Regions, *ec2conn.Config.Region)
if s.RegionKeyIds == nil {
s.RegionKeyIds = make(map[string]string)
}
s.RegionKeyIds[*ec2conn.Config.Region] = s.AMIKmsKeyId
}
if len(s.Regions) == 0 {

View File

@ -223,6 +223,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common/random"
@ -106,10 +107,49 @@ func (s *stepCreateAMI) Cleanup(state multistep.StateBag) {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Deregistering the AMI because cancellation, error or it was temporary (encrypt_boot was set)...")
deregisterOpts := &ec2.DeregisterImageInput{ImageId: s.image.ImageId}
if _, err := ec2conn.DeregisterImage(deregisterOpts); err != nil {
ui.Error(fmt.Sprintf("Error deregistering AMI, may still be around: %s", err))
ui.Say("Deregistering the AMI and deleting associated snapshots because " +
"of cancellation, error or it was temporary (encrypt_boot was set)...")
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
ImageIds: []*string{s.image.ImageId},
})
if err != nil {
err := fmt.Errorf("Error describing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
// Deregister image by name.
for _, i := range resp.Images {
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: i.ImageId,
})
if err != nil {
err := fmt.Errorf("Error deregistering existing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deregistered AMI id: %s", *i.ImageId))
// Delete snapshot(s) by image
for _, b := range i.BlockDeviceMappings {
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
SnapshotId: b.Ebs.SnapshotId,
})
if err != nil {
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return
}
ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId))
}
}
}
}

View File

@ -248,6 +248,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,

View File

@ -299,6 +299,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
AMIKmsKeyId: b.config.AMIKmsKeyId,
RegionKeyIds: b.config.AMIRegionKMSKeyIDs,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,

View File

@ -233,6 +233,11 @@ func (c *Communicator) Download(src string, dst io.Writer) error {
return fmt.Errorf("Failed to open pipe: %s", err)
}
stderrP, err := localCmd.StderrPipe()
if err != nil {
return fmt.Errorf("Failed to open stderr pipe: %s", err)
}
if err = localCmd.Start(); err != nil {
return fmt.Errorf("Failed to start download: %s", err)
}
@ -240,6 +245,16 @@ func (c *Communicator) Download(src string, dst io.Writer) error {
// When you use - to send docker cp to stdout it is streamed as a tar; this
// enables it to work with directories. We don't actually support
// directories in Download() but we still need to handle the tar format.
stderrOut, err := ioutil.ReadAll(stderrP)
if err != nil {
return err
}
if string(stderrOut) != "" {
return fmt.Errorf("Error downloading file: %s", string(stderrOut))
}
archive := tar.NewReader(pipe)
_, err = archive.Next()
if err != nil {

View File

@ -31,6 +31,7 @@ type StepCloneVM struct {
SecureBootTemplate string
EnableVirtualizationExtensions bool
MacAddress string
KeepRegistered bool
}
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -147,6 +148,12 @@ func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if s.KeepRegistered {
ui.Say("keep_registered set. Skipping unregister/deletion of VM.")
return
}
ui.Say("Unregistering and deleting virtual machine...")
err := driver.DeleteVirtualMachine(s.VMName)

View File

@ -35,6 +35,7 @@ type StepCreateVM struct {
MacAddress string
FixedVHD bool
Version string
KeepRegistered bool
}
func (s *StepCreateVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -168,6 +169,12 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
if s.KeepRegistered {
ui.Say("keep_registered set. Skipping unregister/deletion of VM.")
return
}
ui.Say("Unregistering and deleting virtual machine...")
err := driver.DeleteVirtualMachine(s.VMName)

View File

@ -98,6 +98,7 @@ type Config struct {
EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"`
TempPath string `mapstructure:"temp_path"`
Version string `mapstructure:"configuration_version"`
KeepRegistered bool `mapstructure:"keep_registered"`
Communicator string `mapstructure:"communicator"`
@ -420,6 +421,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
MacAddress: b.config.MacAddress,
FixedVHD: b.config.FixedVHD,
Version: b.config.Version,
KeepRegistered: b.config.KeepRegistered,
},
&hypervcommon.StepEnableIntegrationService{},

View File

@ -95,6 +95,7 @@ type Config struct {
EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"`
TempPath string `mapstructure:"temp_path"`
Version string `mapstructure:"configuration_version"`
KeepRegistered bool `mapstructure:"keep_registered"`
Communicator string `mapstructure:"communicator"`
@ -441,6 +442,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SecureBootTemplate: b.config.SecureBootTemplate,
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
MacAddress: b.config.MacAddress,
KeepRegistered: b.config.KeepRegistered,
},
&hypervcommon.StepEnableIntegrationService{},

View File

@ -0,0 +1,32 @@
package linode
import (
"context"
"fmt"
"log"
"github.com/linode/linodego"
)
type Artifact struct {
ImageID string
ImageLabel string
Driver *linodego.Client
}
func (a Artifact) BuilderId() string { return BuilderID }
func (a Artifact) Files() []string { return nil }
func (a Artifact) Id() string { return a.ImageID }
func (a Artifact) String() string {
return fmt.Sprintf("Linode image: %s (%s)", a.ImageLabel, a.ImageID)
}
func (a Artifact) State(name string) interface{} { return nil }
func (a Artifact) Destroy() error {
log.Printf("Destroying image: %s (%s)", a.ImageID, a.ImageLabel)
err := a.Driver.DeleteImage(context.TODO(), a.ImageID)
return err
}

View File

@ -0,0 +1,33 @@
package linode
import (
"testing"
"github.com/hashicorp/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
var raw interface{}
raw = &Artifact{}
if _, ok := raw.(packer.Artifact); !ok {
t.Fatalf("Artifact should be artifact")
}
}
func TestArtifactId(t *testing.T) {
a := &Artifact{"private/42", "packer-foobar", nil}
expected := "private/42"
if a.Id() != expected {
t.Fatalf("artifact ID should match: %v", expected)
}
}
func TestArtifactString(t *testing.T) {
a := &Artifact{"private/42", "packer-foobar", nil}
expected := "Linode image: packer-foobar (private/42)"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
}
}

98
builder/linode/builder.go Normal file
View File

@ -0,0 +1,98 @@
// The linode package contains a packer.Builder implementation
// that builds Linode images.
package linode
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/packer/common"
"github.com/linode/linodego"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// The unique ID for this builder.
const BuilderID = "packer.linode"
// Builder represents a Packer Builder.
type Builder struct {
config *Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
return nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (ret packer.Artifact, err error) {
ui.Say("Running builder ...")
client := newLinodeClient(b.config.PersonalAccessToken)
if err != nil {
ui.Error(err.Error())
return nil, err
}
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("hook", hook)
state.Put("ui", ui)
steps := []multistep.Step{
&StepCreateSSHKey{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("linode_%s.pem", b.config.PackerBuildName),
},
&stepCreateLinode{client},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: commHost,
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.Comm,
},
&stepShutdownLinode{client},
&stepCreateImage{client},
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
if _, ok := state.GetOk("image"); !ok {
return nil, errors.New("Cannot find image in state.")
}
image := state.Get("image").(*linodego.Image)
artifact := Artifact{
ImageLabel: image.Label,
ImageID: image.ID,
Driver: &client,
}
return artifact, nil
}

View File

@ -0,0 +1,34 @@
package linode
import (
"os"
"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,
})
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("LINODE_TOKEN"); v == "" {
t.Fatal("LINODE_TOKEN must be set for acceptance tests")
}
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"region": "us-east",
"instance_type": "g6-nanode-1",
"image": "linode/alpine3.9",
"ssh_username": "root"
}]
}
`

View File

@ -0,0 +1,287 @@
package linode
import (
"strconv"
"testing"
"github.com/hashicorp/packer/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"linode_token": "bar",
"region": "us-east",
"instance_type": "g6-nanode-1",
"ssh_username": "root",
"image": "linode/alpine3.9",
}
}
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{}{
"linode_token": []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_Region(t *testing.T) {
var b Builder
config := testConfig()
// Test default
delete(config, "region")
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should error")
}
expected := "us-east"
// Test set
config["region"] = expected
b = Builder{}
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)
}
if b.config.Region != expected {
t.Errorf("found %s, expected %s", b.config.Region, expected)
}
}
func TestBuilderPrepare_Size(t *testing.T) {
var b Builder
config := testConfig()
// Test default
delete(config, "instance_type")
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("should error")
}
expected := "g6-nanode-1"
// Test set
config["instance_type"] = expected
b = Builder{}
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)
}
if b.config.InstanceType != expected {
t.Errorf("found %s, expected %s", b.config.InstanceType, expected)
}
}
func TestBuilderPrepare_Image(t *testing.T) {
var b Builder
config := testConfig()
// Test default
delete(config, "image")
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should error")
}
expected := "linode/alpine3.9"
// Test set
config["image"] = expected
b = Builder{}
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)
}
if b.config.Image != expected {
t.Errorf("found %s, expected %s", b.config.Image, expected)
}
}
func TestBuilderPrepare_StateTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test default
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 set
config["state_timeout"] = "5m"
b = Builder{}
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["state_timeout"] = "tubes"
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_ImageLabel(t *testing.T) {
var b Builder
config := testConfig()
// Test default
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)
}
if b.config.ImageLabel == "" {
t.Errorf("invalid: %s", b.config.ImageLabel)
}
// Test set
config["image_label"] = "foobarbaz"
b = Builder{}
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 set with template
config["image_label"] = "{{timestamp}}"
b = Builder{}
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)
}
_, err = strconv.ParseInt(b.config.ImageLabel, 0, 0)
if err != nil {
t.Fatalf("failed to parse int in template: %s", err)
}
}
func TestBuilderPrepare_Label(t *testing.T) {
var b Builder
config := testConfig()
// Test default
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)
}
if b.config.Label == "" {
t.Errorf("invalid: %s", b.config.Label)
}
// Test normal set
config["instance_label"] = "foobar"
b = Builder{}
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 with template
config["instance_label"] = "foobar-{{timestamp}}"
b = Builder{}
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 with bad template
config["instance_label"] = "foobar-{{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}

156
builder/linode/config.go Normal file
View File

@ -0,0 +1,156 @@
package linode
import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"os"
"regexp"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ctx interpolate.Context
Comm communicator.Config `mapstructure:",squash"`
PersonalAccessToken string `mapstructure:"linode_token"`
Region string `mapstructure:"region"`
InstanceType string `mapstructure:"instance_type"`
Label string `mapstructure:"instance_label"`
Tags []string `mapstructure:"instance_tags"`
Image string `mapstructure:"image"`
SwapSize int `mapstructure:"swap_size"`
RootPass string `mapstructure:"root_pass"`
RootSSHKey string `mapstructure:"root_ssh_key"`
ImageLabel string `mapstructure:"image_label"`
Description string `mapstructure:"image_description"`
RawStateTimeout string `mapstructure:"state_timeout"`
stateTimeout time.Duration
interCtx interpolate.Context
}
func createRandomRootPassword() (string, error) {
rawRootPass := make([]byte, 50)
_, err := rand.Read(rawRootPass)
if err != nil {
return "", fmt.Errorf("Failed to generate random password")
}
rootPass := base64.StdEncoding.EncodeToString(rawRootPass)
return rootPass, nil
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
if err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
},
},
}, raws...); err != nil {
return nil, nil, err
}
var errs *packer.MultiError
// Defaults
if c.PersonalAccessToken == "" {
// Default to environment variable for linode_token, if it exists
c.PersonalAccessToken = os.Getenv("LINODE_TOKEN")
}
if c.ImageLabel == "" {
if def, err := interpolate.Render("packer-{{timestamp}}", nil); err == nil {
c.ImageLabel = def
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Unable to render image name: %s", err))
}
}
if c.Label == "" {
// Default to packer-[time-ordered-uuid]
if def, err := interpolate.Render("packer-{{timestamp}}", nil); err == nil {
c.Label = def
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Unable to render Linode label: %s", err))
}
}
if c.RootPass == "" {
var err error
c.RootPass, err = createRandomRootPassword()
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Unable to generate root_pass: %s", err))
}
}
if c.RawStateTimeout == "" {
c.stateTimeout = 5 * time.Minute
} else {
if stateTimeout, err := time.ParseDuration(c.RawStateTimeout); err == nil {
c.stateTimeout = stateTimeout
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Unable to parse state timeout: %s", err))
}
}
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
c.Comm.SSHPassword = c.RootPass
if c.PersonalAccessToken == "" {
// Required configurations that will display errors if not set
errs = packer.MultiErrorAppend(
errs, errors.New("linode_token is required"))
}
if c.Region == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("region is required"))
}
if c.InstanceType == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("instance_type is required"))
}
if c.Image == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("image is required"))
}
if c.Tags == nil {
c.Tags = make([]string, 0)
}
tagRe := regexp.MustCompile("^[[:alnum:]:_-]{1,255}$")
for _, t := range c.Tags {
if !tagRe.MatchString(t) {
errs = packer.MultiErrorAppend(errs, errors.New(fmt.Sprintf("invalid tag: %s", t)))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
packer.LogSecretFilter.Set(c.PersonalAccessToken)
return c, nil, nil
}

30
builder/linode/linode.go Normal file
View File

@ -0,0 +1,30 @@
package linode
import (
"fmt"
"net/http"
"github.com/hashicorp/packer/version"
"github.com/linode/linodego"
"golang.org/x/oauth2"
)
func newLinodeClient(pat string) linodego.Client {
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: pat})
oauthTransport := &oauth2.Transport{
Source: tokenSource,
}
oauth2Client := &http.Client{
Transport: oauthTransport,
}
client := linodego.NewClient(oauth2Client)
projectURL := "https://www.packer.io"
userAgent := fmt.Sprintf("Packer/%s (+%s) linodego/%s",
version.FormattedVersion(), projectURL, linodego.Version)
client.SetUserAgent(userAgent)
return client
}

27
builder/linode/ssh.go Normal file
View File

@ -0,0 +1,27 @@
package linode
import (
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/linode/linodego"
"golang.org/x/crypto/ssh"
)
func commHost(state multistep.StateBag) (string, error) {
instance := state.Get("instance").(*linodego.Instance)
if len(instance.IPv4) == 0 {
return "", fmt.Errorf("Linode instance %d has no IPv4 addresses!", instance.ID)
}
return instance.IPv4[0].String(), nil
}
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
return &ssh.ClientConfig{
User: "root",
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.Password(state.Get("root_pass").(string)),
},
}, nil
}

View File

@ -0,0 +1,48 @@
package linode
import (
"context"
"errors"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/linode/linodego"
)
type stepCreateImage struct {
client linodego.Client
}
func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
c := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
disk := state.Get("disk").(*linodego.InstanceDisk)
instance := state.Get("instance").(*linodego.Instance)
ui.Say("Creating image...")
image, err := s.client.CreateImage(ctx, linodego.ImageCreateOptions{
DiskID: disk.ID,
Label: c.ImageLabel,
Description: c.Description,
})
if err == nil {
_, err = s.client.WaitForInstanceDiskStatus(ctx, instance.ID, disk.ID, linodego.DiskReady, 600)
}
if err == nil {
image, err = s.client.GetImage(ctx, image.ID)
}
if err != nil {
err = errors.New("Error creating image: " + err.Error())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("image", image)
return multistep.ActionContinue
}
func (s *stepCreateImage) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,94 @@
package linode
import (
"context"
"errors"
"time"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/linode/linodego"
)
type stepCreateLinode struct {
client linodego.Client
}
func (s *stepCreateLinode) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
c := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating Linode...")
createOpts := linodego.InstanceCreateOptions{
RootPass: c.Comm.Password(),
AuthorizedKeys: []string{string(c.Comm.SSHPublicKey)},
Region: c.Region,
Type: c.InstanceType,
Label: c.Label,
Image: c.Image,
SwapSize: &c.SwapSize,
}
instance, err := s.client.CreateInstance(ctx, createOpts)
if err != nil {
err = errors.New("Error creating Linode: " + err.Error())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("instance", instance)
// wait until instance is running
for instance.Status != linodego.InstanceRunning {
time.Sleep(2 * time.Second)
if instance, err = s.client.GetInstance(ctx, instance.ID); err != nil {
err = errors.New("Error creating Linode: " + err.Error())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("instance", instance)
}
disk, err := s.findDisk(ctx, instance.ID)
if err != nil {
err = errors.New("Error creating Linode: " + err.Error())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else if disk == nil {
err := errors.New("Error creating Linode: no suitable disk was found")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("disk", disk)
return multistep.ActionContinue
}
func (s *stepCreateLinode) findDisk(ctx context.Context, instanceID int) (*linodego.InstanceDisk, error) {
disks, err := s.client.ListInstanceDisks(ctx, instanceID, nil)
if err != nil {
return nil, err
}
for _, disk := range disks {
if disk.Filesystem != linodego.FilesystemSwap {
return &disk, nil
}
}
return nil, nil
}
func (s *stepCreateLinode) Cleanup(state multistep.StateBag) {
instance, ok := state.GetOk("instance")
if !ok {
return
}
ui := state.Get("ui").(packer.Ui)
if err := s.client.DeleteInstance(context.Background(), instance.(*linodego.Instance).ID); err != nil {
ui.Error("Error cleaning up Linode: " + err.Error())
}
}

View File

@ -0,0 +1,95 @@
package linode
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"golang.org/x/crypto/ssh"
)
// StepCreateSSHKey represents a Packer build step that generates SSH key pairs.
type StepCreateSSHKey struct {
Debug bool
DebugKeyPath string
}
// Run executes the Packer build step that generates SSH key pairs.
// The key pairs are added to the ssh config
func (s *StepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
if config.Comm.SSHPrivateKeyFile != "" {
ui.Say("Using existing SSH private key")
privateKeyBytes, err := ioutil.ReadFile(config.Comm.SSHPrivateKeyFile)
if err != nil {
state.Put("error", fmt.Errorf(
"Error loading configured private key file: %s", err))
return multistep.ActionHalt
}
config.Comm.SSHPrivateKey = privateKeyBytes
config.Comm.SSHPublicKey = nil
return multistep.ActionContinue
}
ui.Say("Creating temporary SSH key for instance...")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
priv_blk := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(priv),
}
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
err := fmt.Errorf("Error creating temporary ssh key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
config.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
config.Comm.SSHPublicKey = ssh.MarshalAuthorizedKey(pub)
// Linode has a serious issue with the newline that the ssh package appends to the end of the key.
if config.Comm.SSHPublicKey[len(config.Comm.SSHPublicKey)-1] == '\n' {
config.Comm.SSHPublicKey = config.Comm.SSHPublicKey[:len(config.Comm.SSHPublicKey)-1]
}
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
}
// Write out the key
err = pem.Encode(f, &priv_blk)
f.Close()
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
// Nothing to clean up. SSH keys are associated with a single Linode instance.
func (s *StepCreateSSHKey) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,39 @@
package linode
import (
"context"
"errors"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/linode/linodego"
)
type stepShutdownLinode struct {
client linodego.Client
}
func (s *stepShutdownLinode) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*linodego.Instance)
ui.Say("Shutting down Linode...")
if err := s.client.ShutdownInstance(ctx, instance.ID); err != nil {
err = errors.New("Error shutting down Linode: " + err.Error())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, err := s.client.WaitForInstanceStatus(ctx, instance.ID, linodego.InstanceOffline, 120)
if err != nil {
err = errors.New("Error shutting down Linode: " + err.Error())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepShutdownLinode) Cleanup(state multistep.StateBag) {}

View File

@ -71,7 +71,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: getVMIP,
Host: commHost(b.config.Comm.Host()),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
&common.StepProvision{},
@ -98,6 +98,19 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return artifact, nil
}
// Returns ssh_host or winrm_host (see communicator.Config.Host) config
// parameter when set, otherwise gets the host IP from running VM
func commHost(host string) func(state multistep.StateBag) (string, error) {
if host != "" {
return func(state multistep.StateBag) (string, error) {
return host, nil
}
}
return getVMIP
}
// Reads the first non-loopback interface's IP address from the VM.
// qemu-guest-agent package must be installed on the VM
func getVMIP(state multistep.StateBag) (string, error) {
c := state.Get("proxmoxClient").(*proxmox.Client)
vmRef := state.Get("vmRef").(*proxmox.VmRef)

View File

@ -43,6 +43,7 @@ type Config struct {
NICs []nicConfig `mapstructure:"network_adapters"`
Disks []diskConfig `mapstructure:"disks"`
ISOFile string `mapstructure:"iso_file"`
Agent bool `mapstructure:"qemu_agent"`
TemplateName string `mapstructure:"template_name"`
TemplateDescription string `mapstructure:"template_description"`
@ -68,6 +69,8 @@ type diskConfig struct {
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
// Agent defaults to true
c.Agent = true
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{

View File

@ -93,6 +93,7 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) {
// OS not set, using default 'other'
// NIC 0 model not set, using default 'e1000'
// Disk 0 cache mode not set, using default 'none'
// Agent not set, default is true
if b.config.Memory != 512 {
t.Errorf("Expected Memory to be 512, got %d", b.config.Memory)
@ -112,4 +113,40 @@ func TestBasicExampleFromDocsIsValid(t *testing.T) {
if b.config.Disks[0].CacheMode != "none" {
t.Errorf("Expected disk cache mode to be 'none', got %s", b.config.Disks[0].CacheMode)
}
if b.config.Agent != true {
t.Errorf("Expected Agent to be true, got %t", b.config.Agent)
}
}
func TestAgentSetToFalse(t *testing.T) {
// only the mandatory attributes are specified
const config = `{
"builders": [
{
"type": "proxmox",
"proxmox_url": "https://my-proxmox.my-domain:8006/api2/json",
"username": "apiuser@pve",
"password": "supersecret",
"iso_file": "local:iso/Fedora-Server-dvd-x86_64-29-1.2.iso",
"ssh_username": "root",
"node": "my-proxmox",
"qemu_agent": false
}
]
}`
tpl, err := template.Parse(strings.NewReader(config))
if err != nil {
t.Fatal(err)
}
b := &Builder{}
warn, err := b.Prepare(tpl.Builders["proxmox"].Config)
if err != nil {
t.Fatal(err, warn)
}
if b.config.Agent != false {
t.Errorf("Expected Agent to be false, got %t", b.config.Agent)
}
}

View File

@ -21,10 +21,15 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
client := state.Get("proxmoxClient").(*proxmox.Client)
c := state.Get("config").(*Config)
agent := "1"
if c.Agent == false {
agent = "0"
}
ui.Say("Creating VM")
config := proxmox.ConfigQemu{
Name: c.VMName,
Agent: "1",
Agent: agent,
Description: "Packer ephemeral build VM",
Memory: c.Memory,
QemuCores: c.Cores,

View File

@ -26,7 +26,7 @@ type stepTypeBootCommand struct {
type bootCommandTemplateData struct {
HTTPIP string
HTTPPort uint
HTTPPort int
}
type commandTyper interface {
@ -66,7 +66,7 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
common.SetHTTPIP(httpIP)
s.Ctx.Data = &bootCommandTemplateData{
HTTPIP: httpIP,
HTTPPort: state.Get("http_port").(uint),
HTTPPort: state.Get("http_port").(int),
}
ui.Say("Typing the boot command")

View File

@ -106,7 +106,7 @@ func TestTypeBootCommand(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("config", c.builderConfig)
state.Put("http_port", uint(0))
state.Put("http_port", int(0))
state.Put("vmRef", proxmox.NewVmRef(1))
state.Put("proxmoxClient", typer)

View File

@ -106,7 +106,7 @@ func (s *StepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
vncPassword := VNCPassword(s.VNCDisablePassword)
log.Printf("Found available VNC port: %v", s.l)
log.Printf("Found available VNC port: %s:%d", vncBindAddress, vncPort)
vncFinder.UpdateVMX(vncBindAddress, vncPassword, vncPort, vmxData)
@ -117,8 +117,8 @@ func (s *StepConfigureVNC) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
state.Put("vnc_port", s.l.Port)
state.Put("vnc_ip", s.l.Address)
state.Put("vnc_port", vncPort)
state.Put("vnc_ip", vncBindAddress)
state.Put("vnc_password", vncPassword)
return multistep.ActionContinue

View File

@ -3,6 +3,7 @@ package common
import (
"context"
"fmt"
"log"
"time"
"github.com/hashicorp/packer/helper/multistep"
@ -66,12 +67,19 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) {
ui.Error(fmt.Sprintf("Error destroying VM: %s", err))
}
// Wait for the machine to actually destroy
start := time.Now()
for {
destroyed, _ := remoteDriver.IsDestroyed()
destroyed, err := remoteDriver.IsDestroyed()
if destroyed {
break
}
log.Printf("error destroying vm: %s", err)
time.Sleep(1 * time.Second)
if time.Since(start) >= time.Duration(30*time.Minute) {
ui.Error("Error unregistering VM; timed out. You may " +
"need to manually clean up your machine")
break
}
}
}
}

View File

@ -0,0 +1,46 @@
package yandex
import (
"fmt"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
)
type Artifact struct {
config *Config
driver Driver
image *compute.Image
}
//revive:disable:var-naming
func (*Artifact) BuilderId() string {
return BuilderID
}
func (a *Artifact) Id() string {
return a.image.Id
}
func (*Artifact) Files() []string {
return nil
}
//revive:enable:var-naming
func (a *Artifact) String() string {
return fmt.Sprintf("A disk image was created: %v (id: %v) with family name %v", a.image.Name, a.image.Id, a.image.Family)
}
func (a *Artifact) State(name string) interface{} {
switch name {
case "ImageID":
return a.image.Id
case "FolderID":
return a.image.FolderId
}
return nil
}
func (a *Artifact) Destroy() error {
return a.driver.DeleteImage(a.image.Id)
}

View File

@ -0,0 +1,43 @@
package yandex
import (
"testing"
"github.com/hashicorp/packer/packer"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
)
func TestArtifact_impl(t *testing.T) {
var _ packer.Artifact = new(Artifact)
}
func TestArtifact_Id(t *testing.T) {
i := &compute.Image{
Id: "test-id-value",
FolderId: "test-folder-id",
}
a := &Artifact{
image: i}
expected := "test-id-value"
if a.Id() != expected {
t.Fatalf("artifact ID should match: %v", expected)
}
}
func TestArtifact_String(t *testing.T) {
i := &compute.Image{
Id: "test-id-value",
FolderId: "test-folder-id",
Name: "test-name",
Family: "test-family",
}
a := &Artifact{
image: i}
expected := "A disk image was created: test-name (id: test-id-value) with family name test-family"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
}
}

94
builder/yandex/builder.go Normal file
View File

@ -0,0 +1,94 @@
package yandex
import (
"context"
"fmt"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
)
// The unique ID for this builder.
const BuilderID = "packer.yandex"
// Builder represents a Packer Builder.
type Builder struct {
config *Config
runner multistep.Runner
}
// Prepare processes the build configuration parameters.
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
c, warnings, errs := NewConfig(raws...)
if errs != nil {
return warnings, errs
}
b.config = c
return warnings, nil
}
// Run executes a yandex Packer build and returns a packer.Artifact
// representing a Yandex.Cloud compute image.
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
driver, err := NewDriverYC(ui, b.config)
if err != nil {
return nil, err
}
// Set up the state
state := &multistep.BasicStateBag{}
state.Put("config", b.config)
state.Put("driver", driver)
state.Put("sdk", driver.SDK())
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&stepCreateSSHKey{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("yc_%s.pem", b.config.PackerBuildName),
},
&stepCreateInstance{
Debug: b.config.PackerDebug,
SerialLogFile: b.config.SerialLogFile,
},
&stepInstanceInfo{},
&communicator.StepConnect{
Config: &b.config.Communicator,
Host: commHost,
SSHConfig: b.config.Communicator.SSHConfigFunc(),
},
&common.StepProvision{},
&common.StepCleanupTempKeys{
Comm: &b.config.Communicator,
},
&stepTeardownInstance{},
&stepCreateImage{},
}
// Run the steps
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// Report any errors
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
image, ok := state.GetOk("image")
if !ok {
return nil, fmt.Errorf("Failed to find 'image' in state. Bug?")
}
artifact := &Artifact{
image: image.(*compute.Image),
config: b.config,
}
return artifact, nil
}

View File

@ -0,0 +1,36 @@
package yandex
import (
"os"
"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,
})
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("YC_TOKEN"); v == "" {
t.Fatal("YC_TOKEN must be set for acceptance tests")
}
if v := os.Getenv("YC_FOLDER_ID"); v == "" {
t.Fatal("YC_FOLDER_ID must be set for acceptance tests")
}
}
const testBuilderAccBasic = `
{
"builders": [{
"type": "test",
"source_image_family": "ubuntu-1804-lts",
"use_ipv4_nat": "true",
"ssh_username": "ubuntu"
}]
}
`

View File

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

205
builder/yandex/config.go Normal file
View File

@ -0,0 +1,205 @@
package yandex
import (
"errors"
"fmt"
"os"
"regexp"
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/yandex-cloud/go-sdk/iamkey"
)
const defaultEndpoint = "api.cloud.yandex.net:443"
const defaultZone = "ru-central1-a"
var reImageFamily = regexp.MustCompile(`^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$`)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Communicator communicator.Config `mapstructure:",squash"`
Endpoint string `mapstructure:"endpoint"`
FolderID string `mapstructure:"folder_id"`
ServiceAccountKeyFile string `mapstructure:"service_account_key_file"`
Token string `mapstructure:"token"`
DiskName string `mapstructure:"disk_name"`
DiskSizeGb int `mapstructure:"disk_size_gb"`
DiskType string `mapstructure:"disk_type"`
ImageDescription string `mapstructure:"image_description"`
ImageFamily string `mapstructure:"image_family"`
ImageLabels map[string]string `mapstructure:"image_labels"`
ImageName string `mapstructure:"image_name"`
ImageProductIDs []string `mapstructure:"image_product_ids"`
InstanceCores int `mapstructure:"instance_cores"`
InstanceMemory int `mapstructure:"instance_mem_gb"`
InstanceName string `mapstructure:"instance_name"`
Labels map[string]string `mapstructure:"labels"`
PlatformID string `mapstructure:"platform_id"`
Metadata map[string]string `mapstructure:"metadata"`
SerialLogFile string `mapstructure:"serial_log_file"`
SourceImageFamily string `mapstructure:"source_image_family"`
SourceImageFolderID string `mapstructure:"source_image_folder_id"`
SourceImageID string `mapstructure:"source_image_id"`
SubnetID string `mapstructure:"subnet_id"`
UseIPv4Nat bool `mapstructure:"use_ipv4_nat"`
UseIPv6 bool `mapstructure:"use_ipv6"`
UseInternalIP bool `mapstructure:"use_internal_ip"`
Zone string `mapstructure:"zone"`
ctx interpolate.Context
StateTimeout time.Duration `mapstructure:"state_timeout"`
}
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := &Config{}
c.ctx.Funcs = TemplateFuncs
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
}, raws...)
if err != nil {
return nil, nil, err
}
var errs *packer.MultiError
if c.SerialLogFile != "" {
if _, err := os.Stat(c.SerialLogFile); os.IsExist(err) {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Serial log file %s already exist", c.SerialLogFile))
}
}
if c.InstanceCores == 0 {
c.InstanceCores = 2
}
if c.InstanceMemory == 0 {
c.InstanceMemory = 4
}
if c.DiskSizeGb == 0 {
c.DiskSizeGb = 10
}
if c.DiskType == "" {
c.DiskType = "network-hdd"
}
if c.ImageDescription == "" {
c.ImageDescription = "Created by Packer"
}
if c.ImageName == "" {
img, err := interpolate.Render("packer-{{timestamp}}", nil)
if err != nil {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Unable to render default image name: %s ", err))
} else {
c.ImageName = img
}
}
if len(c.ImageFamily) > 63 {
errs = packer.MultiErrorAppend(errs,
errors.New("Invalid image family: Must not be longer than 63 characters"))
}
if c.ImageFamily != "" {
if !reImageFamily.MatchString(c.ImageFamily) {
errs = packer.MultiErrorAppend(errs,
errors.New("Invalid image family: The first character must be a "+
"lowercase letter, and all following characters must be a dash, "+
"lowercase letter, or digit, except the last character, which cannot be a dash"))
}
}
if c.InstanceName == "" {
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
if c.DiskName == "" {
c.DiskName = c.InstanceName + "-disk"
}
if c.PlatformID == "" {
c.PlatformID = "standard-v1"
}
if es := c.Communicator.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
// Process required parameters.
if c.SourceImageID == "" && c.SourceImageFamily == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a source_image_id or source_image_family must be specified"))
}
if c.Endpoint == "" {
c.Endpoint = defaultEndpoint
}
if c.Zone == "" {
c.Zone = defaultZone
}
// provision config by OS environment variables
if c.Token == "" {
c.Token = os.Getenv("YC_TOKEN")
}
if c.ServiceAccountKeyFile == "" {
c.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE")
}
if c.FolderID == "" {
c.FolderID = os.Getenv("YC_FOLDER_ID")
}
if c.Token == "" && c.ServiceAccountKeyFile == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a token or service account key file must be specified"))
}
if c.Token != "" && c.ServiceAccountKeyFile != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("one of token or service account key file must be specified, not both"))
}
if c.Token != "" {
packer.LogSecretFilter.Set(c.Token)
}
if c.ServiceAccountKeyFile != "" {
if _, err := iamkey.ReadFromJSONFile(c.ServiceAccountKeyFile); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("fail to read service account key file: %s", err))
}
}
if c.FolderID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("a folder_id must be specified"))
}
if c.StateTimeout == 0 {
c.StateTimeout = 5 * time.Minute
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {
return nil, nil, errs
}
return c, nil, nil
}

View File

@ -0,0 +1,231 @@
package yandex
import (
"io/ioutil"
"os"
"strings"
"testing"
)
const TestServiceAccountKeyFile = "./test_data/fake-sa-key.json"
func TestConfigPrepare(t *testing.T) {
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(tf.Name())
tf.Close()
cases := []struct {
Key string
Value interface{}
Err bool
}{
{
"unknown_key",
"bad",
true,
},
{
"service_account_key_file",
"/tmp/i/should/not/exist",
true,
},
{
"service_account_key_file",
tf.Name(),
true,
},
{
"service_account_key_file",
TestServiceAccountKeyFile,
false,
},
{
"folder_id",
nil,
true,
},
{
"folder_id",
"foo",
false,
},
{
"source_image_id",
nil,
true,
},
{
"source_image_id",
"foo",
false,
},
{
"source_image_family",
nil,
false,
},
{
"source_image_family",
"foo",
false,
},
{
"zone",
nil,
false,
},
{
"zone",
"foo",
false,
},
{
"ssh_timeout",
"SO BAD",
true,
},
{
"ssh_timeout",
"5s",
false,
},
{
"image_family",
nil,
false,
},
{
"image_family",
"",
false,
},
{
"image_family",
"foo-bar",
false,
},
{
"image_family",
"foo bar",
true,
},
}
for _, tc := range cases {
raw := testConfig(t)
if tc.Value == nil {
delete(raw, tc.Key)
} else {
raw[tc.Key] = tc.Value
}
if tc.Key == "service_account_key_file" {
delete(raw, "token")
}
_, warns, errs := NewConfig(raw)
if tc.Err {
testConfigErr(t, warns, errs, tc.Key)
} else {
testConfigOk(t, warns, errs)
}
}
}
func TestConfigDefaults(t *testing.T) {
cases := []struct {
Read func(c *Config) interface{}
Value interface{}
}{
{
func(c *Config) interface{} { return c.Communicator.Type },
"ssh",
},
{
func(c *Config) interface{} { return c.Communicator.SSHPort },
22,
},
}
for _, tc := range cases {
raw := testConfig(t)
c, warns, errs := NewConfig(raw)
testConfigOk(t, warns, errs)
actual := tc.Read(c)
if actual != tc.Value {
t.Fatalf("bad: %#v", actual)
}
}
}
func TestImageName(t *testing.T) {
raw := testConfig(t)
c, _, _ := NewConfig(raw)
if !strings.HasPrefix(c.ImageName, "packer-") {
t.Fatalf("ImageName should have 'packer-' prefix, found %s", c.ImageName)
}
if strings.Contains(c.ImageName, "{{timestamp}}") {
t.Errorf("ImageName should be interpolated; found %s", c.ImageName)
}
}
func TestZone(t *testing.T) {
raw := testConfig(t)
c, _, _ := NewConfig(raw)
if c.Zone != "ru-central1-a" {
t.Fatalf("Zone should be 'ru-central1-a' given, but is '%s'", c.Zone)
}
}
// Helper stuff below
func testConfig(t *testing.T) (config map[string]interface{}) {
config = map[string]interface{}{
"token": "test_token",
"folder_id": "hashicorp",
"source_image_id": "foo",
"ssh_username": "root",
"image_family": "bar",
"image_product_ids": []string{
"test-license",
},
"zone": "ru-central1-a",
}
return config
}
func testConfigErr(t *testing.T, warns []string, err error, extra string) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatalf("should error: %s", extra)
}
}
func testConfigOk(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad: %s", err)
}
}

18
builder/yandex/driver.go Normal file
View File

@ -0,0 +1,18 @@
package yandex
import (
"context"
ycsdk "github.com/yandex-cloud/go-sdk"
)
type Driver interface {
DeleteImage(id string) error
SDK() *ycsdk.SDK
GetImage(imageID string) (*Image, error)
GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error)
DeleteDisk(ctx context.Context, diskID string) error
DeleteInstance(ctx context.Context, instanceID string) error
DeleteSubnet(ctx context.Context, subnetID string) error
DeleteNetwork(ctx context.Context, networkID string) error
}

203
builder/yandex/driver_yc.go Normal file
View File

@ -0,0 +1,203 @@
package yandex
import (
"context"
"log"
"github.com/hashicorp/packer/helper/useragent"
"github.com/hashicorp/packer/packer"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
"github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint"
"github.com/yandex-cloud/go-genproto/yandex/cloud/vpc/v1"
ycsdk "github.com/yandex-cloud/go-sdk"
"github.com/yandex-cloud/go-sdk/iamkey"
"github.com/yandex-cloud/go-sdk/pkg/requestid"
)
type driverYC struct {
sdk *ycsdk.SDK
ui packer.Ui
}
func NewDriverYC(ui packer.Ui, config *Config) (Driver, error) {
log.Printf("[INFO] Initialize Yandex.Cloud client...")
sdkConfig := ycsdk.Config{}
if config.Endpoint != "" {
sdkConfig.Endpoint = config.Endpoint
}
switch {
case config.Token != "":
sdkConfig.Credentials = ycsdk.OAuthToken(config.Token)
case config.ServiceAccountKeyFile != "":
key, err := iamkey.ReadFromJSONFile(config.ServiceAccountKeyFile)
if err != nil {
return nil, err
}
credentials, err := ycsdk.ServiceAccountKey(key)
if err != nil {
return nil, err
}
sdkConfig.Credentials = credentials
}
userAgentMD := metadata.Pairs("user-agent", useragent.String())
sdk, err := ycsdk.Build(context.Background(), sdkConfig,
grpc.WithDefaultCallOptions(grpc.Header(&userAgentMD)),
grpc.WithUnaryInterceptor(requestid.Interceptor()))
if err != nil {
return nil, err
}
if _, err = sdk.ApiEndpoint().ApiEndpoint().List(context.Background(), &endpoint.ListApiEndpointsRequest{}); err != nil {
return nil, err
}
return &driverYC{
sdk: sdk,
ui: ui,
}, nil
}
func (d *driverYC) GetImage(imageID string) (*Image, error) {
image, err := d.sdk.Compute().Image().Get(context.Background(), &compute.GetImageRequest{
ImageId: imageID,
})
if err != nil {
return nil, err
}
return &Image{
ID: image.Id,
Labels: image.Labels,
Licenses: image.ProductIds,
Name: image.Name,
FolderID: image.FolderId,
MinDiskSizeGb: toGigabytes(image.MinDiskSize),
SizeGb: toGigabytes(image.StorageSize),
}, nil
}
func (d *driverYC) GetImageFromFolder(ctx context.Context, folderID string, family string) (*Image, error) {
image, err := d.sdk.Compute().Image().GetLatestByFamily(ctx, &compute.GetImageLatestByFamilyRequest{
FolderId: folderID,
Family: family,
})
if err != nil {
return nil, err
}
return &Image{
ID: image.Id,
Labels: image.Labels,
Licenses: image.ProductIds,
Name: image.Name,
FolderID: image.FolderId,
Family: image.Family,
MinDiskSizeGb: toGigabytes(image.MinDiskSize),
SizeGb: toGigabytes(image.StorageSize),
}, nil
}
func (d *driverYC) DeleteImage(ID string) error {
ctx := context.TODO()
op, err := d.sdk.WrapOperation(d.sdk.Compute().Image().Delete(ctx, &compute.DeleteImageRequest{
ImageId: ID,
}))
if err != nil {
return err
}
err = op.Wait(ctx)
if err != nil {
return err
}
_, err = op.Response()
return err
}
func (d *driverYC) SDK() *ycsdk.SDK {
return d.sdk
}
func (d *driverYC) DeleteInstance(ctx context.Context, instanceID string) error {
op, err := d.sdk.WrapOperation(d.sdk.Compute().Instance().Delete(ctx, &compute.DeleteInstanceRequest{
InstanceId: instanceID,
}))
if err != nil {
return err
}
err = op.Wait(ctx)
if err != nil {
return err
}
_, err = op.Response()
return err
}
func (d *driverYC) DeleteSubnet(ctx context.Context, subnetID string) error {
op, err := d.sdk.WrapOperation(d.sdk.VPC().Subnet().Delete(ctx, &vpc.DeleteSubnetRequest{
SubnetId: subnetID,
}))
if err != nil {
return err
}
err = op.Wait(ctx)
if err != nil {
return err
}
_, err = op.Response()
return err
}
func (d *driverYC) DeleteNetwork(ctx context.Context, networkID string) error {
op, err := d.sdk.WrapOperation(d.sdk.VPC().Network().Delete(ctx, &vpc.DeleteNetworkRequest{
NetworkId: networkID,
}))
if err != nil {
return err
}
err = op.Wait(ctx)
if err != nil {
return err
}
_, err = op.Response()
return err
}
func (d *driverYC) DeleteDisk(ctx context.Context, diskID string) error {
op, err := d.sdk.WrapOperation(d.sdk.Compute().Disk().Delete(ctx, &compute.DeleteDiskRequest{
DiskId: diskID,
}))
if err != nil {
return err
}
err = op.Wait(ctx)
if err != nil {
return err
}
_, err = op.Response()
return err
}

12
builder/yandex/image.go Normal file
View File

@ -0,0 +1,12 @@
package yandex
type Image struct {
ID string
FolderID string
Labels map[string]string
Licenses []string
MinDiskSizeGb int
Name string
Family string
SizeGb int
}

10
builder/yandex/ssh.go Normal file
View File

@ -0,0 +1,10 @@
package yandex
import (
"github.com/hashicorp/packer/helper/multistep"
)
func commHost(state multistep.StateBag) (string, error) {
ipAddress := state.Get("instance_ip").(string)
return ipAddress, nil
}

View File

@ -0,0 +1,70 @@
package yandex
import (
"context"
"errors"
"fmt"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
ycsdk "github.com/yandex-cloud/go-sdk"
)
type stepCreateImage struct{}
func (stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
sdk := state.Get("sdk").(*ycsdk.SDK)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
diskID := state.Get("disk_id").(string)
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
ctx, cancel := context.WithTimeout(ctx, c.StateTimeout)
defer cancel()
op, err := sdk.WrapOperation(sdk.Compute().Image().Create(ctx, &compute.CreateImageRequest{
FolderId: c.FolderID,
Name: c.ImageName,
Family: c.ImageFamily,
Description: c.ImageDescription,
Labels: c.ImageLabels,
ProductIds: c.ImageProductIDs,
Source: &compute.CreateImageRequest_DiskId{
DiskId: diskID,
},
}))
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error creating image: %s", err))
}
ui.Say("Waiting for image to complete...")
if err := op.Wait(ctx); err != nil {
return stepHaltWithError(state, fmt.Errorf("Error waiting for image: %s", err))
}
resp, err := op.Response()
if err != nil {
return stepHaltWithError(state, err)
}
image, ok := resp.(*compute.Image)
if !ok {
return stepHaltWithError(state, errors.New("Response doesn't contain Image"))
}
log.Printf("Image ID: %s", image.Id)
log.Printf("Image Name: %s", image.Name)
log.Printf("Image Family: %s", image.Family)
log.Printf("Image Description: %s", image.Description)
log.Printf("Image Storage size: %d", image.StorageSize)
state.Put("image", image)
return multistep.ActionContinue
}
func (stepCreateImage) Cleanup(state multistep.StateBag) {
// no cleanup
}

View File

@ -0,0 +1,357 @@
package yandex
import (
"context"
"errors"
"fmt"
"io/ioutil"
"github.com/c2h5oh/datasize"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
"github.com/yandex-cloud/go-genproto/yandex/cloud/vpc/v1"
ycsdk "github.com/yandex-cloud/go-sdk"
)
const StandardImagesFolderID = "standard-images"
type stepCreateInstance struct {
Debug bool
SerialLogFile string
}
func createNetwork(ctx context.Context, c *Config, d Driver) (*vpc.Network, error) {
req := &vpc.CreateNetworkRequest{
FolderId: c.FolderID,
Name: fmt.Sprintf("packer-network-%s", uuid.TimeOrderedUUID()),
}
sdk := d.SDK()
op, err := sdk.WrapOperation(sdk.VPC().Network().Create(ctx, req))
if err != nil {
return nil, err
}
err = op.Wait(ctx)
if err != nil {
return nil, err
}
resp, err := op.Response()
if err != nil {
return nil, err
}
network, ok := resp.(*vpc.Network)
if !ok {
return nil, errors.New("network create operation response doesn't contain Network")
}
return network, nil
}
func createSubnet(ctx context.Context, c *Config, d Driver, networkID string) (*vpc.Subnet, error) {
req := &vpc.CreateSubnetRequest{
FolderId: c.FolderID,
NetworkId: networkID,
Name: fmt.Sprintf("packer-subnet-%s", uuid.TimeOrderedUUID()),
ZoneId: c.Zone,
V4CidrBlocks: []string{"192.168.111.0/24"},
}
sdk := d.SDK()
op, err := sdk.WrapOperation(sdk.VPC().Subnet().Create(ctx, req))
if err != nil {
return nil, err
}
err = op.Wait(ctx)
if err != nil {
return nil, err
}
resp, err := op.Response()
if err != nil {
return nil, err
}
subnet, ok := resp.(*vpc.Subnet)
if !ok {
return nil, errors.New("subnet create operation response doesn't contain Subnet")
}
return subnet, nil
}
func getImage(ctx context.Context, c *Config, d Driver) (*Image, error) {
if c.SourceImageID != "" {
return d.GetImage(c.SourceImageID)
}
familyName := c.SourceImageFamily
if c.SourceImageFolderID != "" {
return d.GetImageFromFolder(ctx, c.SourceImageFolderID, familyName)
}
return d.GetImageFromFolder(ctx, StandardImagesFolderID, familyName)
}
func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
sdk := state.Get("sdk").(*ycsdk.SDK)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ctx, cancel := context.WithTimeout(ctx, config.StateTimeout)
defer cancel()
sourceImage, err := getImage(ctx, config, driver)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error getting source image for instance creation: %s", err))
}
if sourceImage.MinDiskSizeGb > config.DiskSizeGb {
return stepHaltWithError(state, fmt.Errorf("Instance DiskSizeGb (%d) should be equal or greater "+
"than SourceImage disk requirement (%d)", config.DiskSizeGb, sourceImage.MinDiskSizeGb))
}
ui.Say(fmt.Sprintf("Using as source image: %s (name: %q, family: %q)", sourceImage.ID, sourceImage.Name, sourceImage.Family))
// create or reuse network configuration
instanceSubnetID := ""
if config.SubnetID == "" {
// create Network and Subnet
ui.Say("Creating network...")
network, err := createNetwork(ctx, config, driver)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error creating network: %s", err))
}
state.Put("network_id", network.Id)
ui.Say(fmt.Sprintf("Creating subnet in zone %q...", config.Zone))
subnet, err := createSubnet(ctx, config, driver, network.Id)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error creating subnet: %s", err))
}
instanceSubnetID = subnet.Id
// save for cleanup
state.Put("subnet_id", subnet.Id)
} else {
ui.Say("Use provided subnet id " + config.SubnetID)
instanceSubnetID = config.SubnetID
}
// Create an instance based on the configuration
ui.Say("Creating instance...")
instanceMetadata := config.createInstanceMetadata(string(config.Communicator.SSHPublicKey))
// TODO make part metadata prepare process
if config.UseIPv6 {
// this ugly hack will replace user provided 'user-data'
userData := `#cloud-config
runcmd:
- [ sh, -c, '/sbin/dhclient -6 -D LL -nw -pf /run/dhclient_ipv6.eth0.pid -lf /var/lib/dhcp/dhclient_ipv6.eth0.leases eth0' ]
`
instanceMetadata["user-data"] = userData
}
req := &compute.CreateInstanceRequest{
FolderId: config.FolderID,
Name: config.InstanceName,
Labels: config.Labels,
ZoneId: config.Zone,
PlatformId: config.PlatformID,
ResourcesSpec: &compute.ResourcesSpec{
Memory: toBytes(config.InstanceMemory),
Cores: int64(config.InstanceCores),
},
Metadata: instanceMetadata,
BootDiskSpec: &compute.AttachedDiskSpec{
AutoDelete: false,
Disk: &compute.AttachedDiskSpec_DiskSpec_{
DiskSpec: &compute.AttachedDiskSpec_DiskSpec{
Name: config.DiskName,
TypeId: config.DiskType,
Size: int64((datasize.ByteSize(config.DiskSizeGb) * datasize.GB).Bytes()),
Source: &compute.AttachedDiskSpec_DiskSpec_ImageId{
ImageId: sourceImage.ID,
},
},
},
},
NetworkInterfaceSpecs: []*compute.NetworkInterfaceSpec{
{
SubnetId: instanceSubnetID,
PrimaryV4AddressSpec: &compute.PrimaryAddressSpec{},
},
},
}
if config.UseIPv6 {
req.NetworkInterfaceSpecs[0].PrimaryV6AddressSpec = &compute.PrimaryAddressSpec{}
}
if config.UseIPv4Nat {
req.NetworkInterfaceSpecs[0].PrimaryV4AddressSpec = &compute.PrimaryAddressSpec{
OneToOneNatSpec: &compute.OneToOneNatSpec{
IpVersion: compute.IpVersion_IPV4,
},
}
}
op, err := sdk.WrapOperation(sdk.Compute().Instance().Create(ctx, req))
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error create instance: %s", err))
}
opMetadata, err := op.Metadata()
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error get create operation metadata: %s", err))
}
if cimd, ok := opMetadata.(*compute.CreateInstanceMetadata); ok {
state.Put("instance_id", cimd.InstanceId)
} else {
return stepHaltWithError(state, fmt.Errorf("could not get Instance ID from operation metadata"))
}
err = op.Wait(ctx)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error create instance: %s", err))
}
resp, err := op.Response()
if err != nil {
return stepHaltWithError(state, err)
}
instance, ok := resp.(*compute.Instance)
if !ok {
return stepHaltWithError(state, fmt.Errorf("response doesn't contain Instance"))
}
state.Put("disk_id", instance.BootDisk.DiskId)
if s.Debug {
ui.Message(fmt.Sprintf("Instance ID %s started. Current instance status %s", instance.Id, instance.Status))
ui.Message(fmt.Sprintf("Disk ID %s. ", instance.BootDisk.DiskId))
}
return multistep.ActionContinue
}
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ctx, cancel := context.WithTimeout(context.Background(), config.StateTimeout)
defer cancel()
if s.SerialLogFile != "" {
ui.Say("Current state 'cancelled' or 'halted'...")
err := s.writeSerialLogFile(ctx, state)
if err != nil {
ui.Error(err.Error())
}
}
instanceIDRaw, ok := state.GetOk("instance_id")
if ok {
instanceID := instanceIDRaw.(string)
if instanceID != "" {
ui.Say("Destroying instance...")
err := driver.DeleteInstance(ctx, instanceID)
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying instance (id: %s). Please destroy it manually: %s", instanceID, err))
}
ui.Message("Instance has been destroyed!")
}
}
subnetIDRaw, ok := state.GetOk("subnet_id")
if ok {
subnetID := subnetIDRaw.(string)
if subnetID != "" {
// Destroy the subnet we just created
ui.Say("Destroying subnet...")
err := driver.DeleteSubnet(ctx, subnetID)
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying subnet (id: %s). Please destroy it manually: %s", subnetID, err))
}
ui.Message("Subnet has been deleted!")
}
}
// Destroy the network we just created
networkIDRaw, ok := state.GetOk("network_id")
if ok {
networkID := networkIDRaw.(string)
if networkID != "" {
// Destroy the network we just created
ui.Say("Destroying network...")
err := driver.DeleteNetwork(ctx, networkID)
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying network (id: %s). Please destroy it manually: %s", networkID, err))
}
ui.Message("Network has been deleted!")
}
}
diskIDRaw, ok := state.GetOk("disk_id")
if ok {
ui.Say("Destroying boot disk...")
diskID := diskIDRaw.(string)
err := driver.DeleteDisk(ctx, diskID)
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying boot disk (id: %s). Please destroy it manually: %s", diskID, err))
}
ui.Message("Disk has been deleted!")
}
}
func (s *stepCreateInstance) writeSerialLogFile(ctx context.Context, state multistep.StateBag) error {
sdk := state.Get("sdk").(*ycsdk.SDK)
ui := state.Get("ui").(packer.Ui)
instanceID := state.Get("instance_id").(string)
ui.Say("Try get instance's serial port output and write to file " + s.SerialLogFile)
serialOutput, err := sdk.Compute().Instance().GetSerialPortOutput(ctx, &compute.GetInstanceSerialPortOutputRequest{
InstanceId: instanceID,
})
if err != nil {
return fmt.Errorf("Failed to get serial port output for instance (id: %s): %s", instanceID, err)
}
if err := ioutil.WriteFile(s.SerialLogFile, []byte(serialOutput.Contents), 0600); err != nil {
return fmt.Errorf("Failed to write serial port output to file: %s", err)
}
ui.Message("Serial port output has been successfully written")
return nil
}
func (c *Config) createInstanceMetadata(sshPublicKey string) map[string]string {
instanceMetadata := make(map[string]string)
// Copy metadata from config.
for k, v := range c.Metadata {
instanceMetadata[k] = v
}
if sshPublicKey != "" {
sshMetaKey := "ssh-keys"
sshKeys := fmt.Sprintf("%s:%s", c.Communicator.SSHUsername, sshPublicKey)
if confSSHKeys, exists := instanceMetadata[sshMetaKey]; exists {
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSSHKeys)
}
instanceMetadata[sshMetaKey] = sshKeys
}
return instanceMetadata
}

View File

@ -0,0 +1,100 @@
package yandex
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"golang.org/x/crypto/ssh"
)
type stepCreateSSHKey struct {
Debug bool
DebugKeyPath string
}
func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
if config.Communicator.SSHPrivateKeyFile != "" {
ui.Say("Using existing SSH private key")
privateKeyBytes, err := config.Communicator.ReadSSHPrivateKeyFile()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
key, err := ssh.ParsePrivateKey(privateKeyBytes)
if err != nil {
err = fmt.Errorf("Error parsing 'ssh_private_key_file': %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
config.Communicator.SSHPublicKey = ssh.MarshalAuthorizedKey(key.PublicKey())
config.Communicator.SSHPrivateKey = privateKeyBytes
return multistep.ActionContinue
}
ui.Say("Creating temporary ssh key for instance...")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error generating temporary SSH key: %s", err))
}
// ASN.1 DER encoded form
privDer := x509.MarshalPKCS1PrivateKey(priv)
privBlk := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privDer,
}
// Marshal the public key into SSH compatible format
pub, err := ssh.NewPublicKey(&priv.PublicKey)
if err != nil {
err = fmt.Errorf("Error creating public ssh key: %s", err)
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
pubSSHFormat := string(ssh.MarshalAuthorizedKey(pub))
hashMD5 := ssh.FingerprintLegacyMD5(pub)
hashSHA256 := ssh.FingerprintSHA256(pub)
log.Printf("[INFO] md5 hash of ssh pub key: %s", hashMD5)
log.Printf("[INFO] sha256 hash of ssh pub key: %s", hashSHA256)
// Remember some state for the future
state.Put("ssh_key_public", pubSSHFormat)
// Set the private key in the config for later
config.Communicator.SSHPrivateKey = pem.EncodeToMemory(&privBlk)
config.Communicator.SSHPublicKey = ssh.MarshalAuthorizedKey(pub)
// 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))
err := ioutil.WriteFile(s.DebugKeyPath, config.Communicator.SSHPrivateKey, 0600)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error saving debug key: %s", err))
}
}
return multistep.ActionContinue
}
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,111 @@
package yandex
import (
"context"
"errors"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
ycsdk "github.com/yandex-cloud/go-sdk"
)
type stepInstanceInfo struct{}
func (s *stepInstanceInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
sdk := state.Get("sdk").(*ycsdk.SDK)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
instanceID := state.Get("instance_id").(string)
ui.Say("Waiting for instance to become active...")
ctx, cancel := context.WithTimeout(ctx, c.StateTimeout)
defer cancel()
instance, err := sdk.Compute().Instance().Get(ctx, &compute.GetInstanceRequest{
InstanceId: instanceID,
View: compute.InstanceView_FULL,
})
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error retrieving instance data: %s", err))
}
instanceIP, err := getInstanceIPAddress(c, instance)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Failed to find instance ip address: %s", err))
}
state.Put("instance_ip", instanceIP)
ui.Message(fmt.Sprintf("Detected instance IP: %s", instanceIP))
return multistep.ActionContinue
}
func getInstanceIPAddress(c *Config, instance *compute.Instance) (address string, err error) {
// Instance could have several network interfaces with different configuration each
// Get all possible addresses for instance
addrIPV4Internal, addrIPV4External, addrIPV6Addr, err := instanceAddresses(instance)
if err != nil {
return "", err
}
if c.UseIPv6 {
if addrIPV6Addr != "" {
return "[" + addrIPV6Addr + "]", nil
}
return "", errors.New("instance has no one IPv6 address")
}
if c.UseInternalIP {
if addrIPV4Internal != "" {
return addrIPV4Internal, nil
}
return "", errors.New("instance has no one IPv4 internal address")
}
if addrIPV4External != "" {
return addrIPV4External, nil
}
return "", errors.New("instance has no one IPv4 external address")
}
func instanceAddresses(instance *compute.Instance) (ipV4Int, ipV4Ext, ipV6 string, err error) {
if len(instance.NetworkInterfaces) == 0 {
return "", "", "", errors.New("No one network interface found for an instance")
}
var ipV4IntFound, ipV4ExtFound, ipV6Found bool
for _, iface := range instance.NetworkInterfaces {
if !ipV6Found && iface.PrimaryV6Address != nil {
ipV6 = iface.PrimaryV6Address.Address
ipV6Found = true
}
if !ipV4IntFound && iface.PrimaryV4Address != nil {
ipV4Int = iface.PrimaryV4Address.Address
ipV4IntFound = true
if !ipV4ExtFound && iface.PrimaryV4Address.OneToOneNat != nil {
ipV4Ext = iface.PrimaryV4Address.OneToOneNat.Address
ipV4ExtFound = true
}
}
if ipV6Found && ipV4IntFound && ipV4ExtFound {
break
}
}
if !ipV4IntFound {
// internal ipV4 address always should present
return "", "", "", errors.New("No IPv4 internal address found. Bug?")
}
return
}
func (s *stepInstanceInfo) Cleanup(state multistep.StateBag) {
// no cleanup
}

View File

@ -0,0 +1,46 @@
package yandex
import (
"context"
"fmt"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/yandex-cloud/go-genproto/yandex/cloud/compute/v1"
ycsdk "github.com/yandex-cloud/go-sdk"
)
type stepTeardownInstance struct{}
func (s *stepTeardownInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
sdk := state.Get("sdk").(*ycsdk.SDK)
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
instanceID := state.Get("instance_id").(string)
ui.Say("Deleting instance...")
ctx, cancel := context.WithTimeout(ctx, c.StateTimeout)
defer cancel()
op, err := sdk.WrapOperation(sdk.Compute().Instance().Delete(ctx, &compute.DeleteInstanceRequest{
InstanceId: instanceID,
}))
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error deleting instance: %s", err))
}
err = op.Wait(ctx)
if err != nil {
return stepHaltWithError(state, fmt.Errorf("Error deleting instance: %s", err))
}
ui.Message("Instance has been deleted!")
state.Put("instance_id", "")
return multistep.ActionContinue
}
func (s *stepTeardownInstance) Cleanup(state multistep.StateBag) {
// no cleanup
}

View File

@ -0,0 +1,36 @@
package yandex
import "strings"
import "text/template"
func isalphanumeric(b byte) bool {
if '0' <= b && b <= '9' {
return true
}
if 'a' <= b && b <= 'z' {
return true
}
return false
}
// Clean up image name by replacing invalid characters with "-"
// and converting upper cases to lower cases
func templateCleanImageName(s string) string {
if reImageFamily.MatchString(s) {
return s
}
b := []byte(strings.ToLower(s))
newb := make([]byte, len(b))
for i := range newb {
if isalphanumeric(b[i]) {
newb[i] = b[i]
} else {
newb[i] = '-'
}
}
return string(newb)
}
var TemplateFuncs = template.FuncMap{
"clean_image_name": templateCleanImageName,
}

View File

@ -0,0 +1,9 @@
{
"id": "ajeboa0du6edu6m43c3t",
"service_account_id": "ajeq7dsmihqple6761c5",
"created_at": "2018-11-19T13:38:09Z",
"description": "description",
"key_algorithm": "RSA_4096",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIICCgKCAgEAo/s1lN5vFpFNJvS/l+yRilQHAPDeC3JqBwpLstbqJXW4kAUaKKoe\nxkIuJuPUKOUcd/JE3LXOEt/LOFb9mkCRdpjaIW7Jd5Fw0kTHIZ5rDoq7DZx0LV9b\nGJNskdccd6M6stb1GEqVuGpVcyXMCH8tMSG3c85DkcAg0cxXgyrirAzHMPiWSTpj\nJjICkxXRVj01Xq7dIDqL2LSMrZ2kLda5m+CnfscUbwnGRPPoEg20jLiEgBM2o43e\nhpWko1NStRR5fMQcQSUBbdtvbfPracjZz2/fq4fZfqlnObgq3WpYpdGynniLH3i5\nbxPM3ufYL3HY2w5aIOY6KIwMKLf3WYlug90ieviMYAvCukrCASwyqBQlt3MKCHlN\nIcebZXJDQ1VSBuEs+4qXYlhG1p+5C07zahzigNNTm6rEo47FFfClF04mv2uJN42F\nfWlEPR+V9JHBcfcBCdvyhiGzftl/vDo2NdO751ETIhyNKzxM/Ve2PR9h/qcuEatC\nLlXUA+40epNNHbSxAauxcngyrtkn7FZAEhdjyTtx46sELyb90Z56WgnbNUUGnsS/\nHBnBy5z8RyCmI5MjTC2NtplVqtAWkG+x59mU3GoCeuI8EaNtu2YPXhl1ovRkS4NB\n1G0F4c5FiJ27/E2MbNKlV5iw9ICcDforATYTeqiXbkkEKqIIiZYZWOsCAwEAAQ==\n-----END PUBLIC KEY-----\n",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIJKQIBAAKCAgEAo/s1lN5vFpFNJvS/l+yRilQHAPDeC3JqBwpLstbqJXW4kAUa\nKKoexkIuJuPUKOUcd/JE3LXOEt/LOFb9mkCRdpjaIW7Jd5Fw0kTHIZ5rDoq7DZx0\nLV9bGJNskdccd6M6stb1GEqVuGpVcyXMCH8tMSG3c85DkcAg0cxXgyrirAzHMPiW\nSTpjJjICkxXRVj01Xq7dIDqL2LSMrZ2kLda5m+CnfscUbwnGRPPoEg20jLiEgBM2\no43ehpWko1NStRR5fMQcQSUBbdtvbfPracjZz2/fq4fZfqlnObgq3WpYpdGynniL\nH3i5bxPM3ufYL3HY2w5aIOY6KIwMKLf3WYlug90ieviMYAvCukrCASwyqBQlt3MK\nCHlNIcebZXJDQ1VSBuEs+4qXYlhG1p+5C07zahzigNNTm6rEo47FFfClF04mv2uJ\nN42FfWlEPR+V9JHBcfcBCdvyhiGzftl/vDo2NdO751ETIhyNKzxM/Ve2PR9h/qcu\nEatCLlXUA+40epNNHbSxAauxcngyrtkn7FZAEhdjyTtx46sELyb90Z56WgnbNUUG\nnsS/HBnBy5z8RyCmI5MjTC2NtplVqtAWkG+x59mU3GoCeuI8EaNtu2YPXhl1ovRk\nS4NB1G0F4c5FiJ27/E2MbNKlV5iw9ICcDforATYTeqiXbkkEKqIIiZYZWOsCAwEA\nAQKCAgEAihT1L6CGhshf4VfjJfktLQBIzYAGWjlEEx2WVMgobtbMTWoedvOZ6nS8\nDD943d7ftBkr53aoSrhslcqazpNkaiuYMuLpf2fXSxhjXmnZ2Gr1zCZcpgBP40fw\n+nXbINswiHv98zCLFrljrwy63MTKtz6fDkM4HrlcaY3aezdXnG0+JnyNgKhL6VPf\nWx/aIPZ1xH8W8RabwCV4+JFwOLFBpoLsSBM3n7DpZhLE7r7ftEeEO5zyO5MxOL81\n3dpCIP1Wt7sj169jnrBTCpGFQJTC5Kxd+kDw4nmf1LjCT6RHdYo5ELyM2jl8XI6d\ny24LWxhQ9VUGjAGSI6aabodLH/hcOBB2wG1tnO+n5y85GnKKOJgxCxaj1yR/LAcT\nFvZgbDGwAMd7h7+fU46Yj5BILk6mRvBNL6Mk2VAlBzUatGduU+Xxha3JkGxIJY4G\np1qPLNiP7as90mXXMgNEtsP2zXtyi+9q7XBOBnfL3ftHWQmu7MKQCHIKcNRchFJ4\nS1LtndjXtNchzDhbXru2qsRiASmL9u4CgZn/lM3kDHs+v2JI+V8cPk5XZhoPrrpP\nZ0SPeoLZEJ5/TtlTWAXXqP6F24rziBqnEJgpNCkeBnQYx2Rs9OKVsrlDk8cf3KkL\nH8qQ/86HYz9cEtFnVKAYOV5GtQsJRyzipMy7R/cegdtWJ8ScuiECggEBANOT7lBX\nRYw+k53TRpkk7NlWuQogKKEQx4PEf8A6HQj3SseH8u+tt3HfTFJktzWs/9EQerLS\nJky9bSPxBvDq0Zfj+IPamiY+c2w5a9WbLxk8UHCaUHcSUeWoWQwmCZqzXeUNj9f5\nQOfF+ajsqhaXE68/HuIj+dgOOn/XYyqNkxlidXa9U3gUanuftwRSephsGcsaEGTe\nep2My4Jj3hPH/9Qoith0X18atRru6RanK63bDl0FqAU/1uUycQr+h0hEwQHWoRiq\nNVXI1uxfi5/2pxK0w1MOzZLitwEQ/veCv6CZwNPf1SW1U8j70SvKVR8Z7gGDIPjS\n8klW2Z9g6gxPQ1MCggEBAMZpBFa4mEnsmt+paEFCGUtoeBapjZF94PBtdxII/T5t\ne5z4Iz7RMl+ixLhNepQu+0t+v1iDVJgDJuUjCsSF69jEca7gzmsWhs9d+gDU5Knm\n18ChbQyeaDvmqINCs2t45pA/mVIQHbA8L8n/ToI5P63ZELDUFVzZo9kerZu1ALNB\nRoG0PhIHrGkZKwL8oE72nrZmWtfjROsZBhu7FqJ0i7va/6fgNMuMtBC/abOC7yVT\nir5XP+ZGF8XNyIZ3Ic0X8xc+XqagYsf+XobHGmbSct/ZaDP3g1z4B/7JZcbYjuTZ\nMJ3s5T+6l/qo0dfDuaVBJFJrnw8YfahX/Bn4OQ2TuQkCggEBALfhs5dDogA3Spg6\nTPtAalCh3IP+WxFQwfW1S8pHN4DZW7Z6YxsHgY2IIo7hVZFi35pVli3gEsVTRI2e\nJwgvLSWzTgNac+qVED+Y0C1/h7mI/+g9VX2HAIJ2g53ZWTOIfCxcUw3DZTOKjmbP\n+StU9hiy5SZpWfT6uMDu8xLCpHvFZI1kEi0koT78GlW5US8zlF8+Mc1YxnwzJ5QV\nM6dBhQhgi/t/eHvxfEECLrYvZ/jbj2otRk/5oczkv/ZsLCsVBiGQ5cXH+D6sJI6e\no3zNI3tQewmurd/hBmf4239FtUHhHwOFX3w8Uas1oB9M5Bn5sS7DRl67BzPSNaUc\n140HPl0CggEAX1+13TXoxog8vkzBt7TdUdlK+KHSUmCvEwObnAjEKxEXvZGt55FJ\n5JzqcSmVRcv7sgOgWRzwOg4x0S1yDJvPjiiH+SdJMkLm1KF4/pNXw7AagBdYwxsW\nQc0Trd0PQBcixa48tizXCJM16aSXCZQZXykbk9Su3C4mS8UqcNGmH4S+LrUErUgR\nAYg+m7XyHWMBUe6LtoEh7Nzfic76B2d8j/WqtPjaiAn/uJk6ZzcGW+v3op1wMvH4\nlXXg8XosvljH2qF5gCFSuo40xBbLQyfgXmg0Zd6Rv8velAQdr2MD9U/NxexNGsBI\nNA6YqF4GTECvBAuFrwz3wkdhAN7IFhWveQKCAQBdfdHB3D+m+b/hZoEIv0nPcgQf\ncCOPPNO/ufObjWed2jTL3RjoDT337Mp3mYkoP4GE9n6cl7mjlcrf7KQeRG8k35fv\n3nMoMOp21qj9J66UgGf1/RHsV/+ljcu87ggYDCVKd8uGzkspRIQIsD77He/TwZNa\nyWL4fa1EvRU6STwi7CZFfhWhMF3rBGAPshABoyJZh6Z14cioAKSR0Sl6XZ5dcB9B\naoJM8sISSlOqMIJyNnyMtdE55Ag+P7LyMe2grxlwVTv3h0o5mHSzWnjSHVYvN4q5\n6h5UUopLtyVMGCwOJz+zNT7zFqi4XIGU8a8Lg1iiKtfjgHB2X8ZWZuXBdrTj\n-----END PRIVATE KEY-----\n"
}

22
builder/yandex/util.go Normal file
View File

@ -0,0 +1,22 @@
package yandex
import (
"github.com/c2h5oh/datasize"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func stepHaltWithError(state multistep.StateBag, err error) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
func toGigabytes(bytesCount int64) int {
return int((datasize.ByteSize(bytesCount) * datasize.B).GBytes())
}
func toBytes(gigabytesCount int) int64 {
return int64((datasize.ByteSize(gigabytesCount) * datasize.GB).Bytes())
}

View File

@ -29,6 +29,7 @@ import (
hyperonebuilder "github.com/hashicorp/packer/builder/hyperone"
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
linodebuilder "github.com/hashicorp/packer/builder/linode"
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
lxdbuilder "github.com/hashicorp/packer/builder/lxd"
ncloudbuilder "github.com/hashicorp/packer/builder/ncloud"
@ -50,6 +51,7 @@ import (
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
vmwarevmxbuilder "github.com/hashicorp/packer/builder/vmware/vmx"
yandexbuilder "github.com/hashicorp/packer/builder/yandex"
alicloudimportpostprocessor "github.com/hashicorp/packer/post-processor/alicloud-import"
amazonimportpostprocessor "github.com/hashicorp/packer/post-processor/amazon-import"
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
@ -108,6 +110,7 @@ var Builders = map[string]packer.Builder{
"hyperone": new(hyperonebuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
"linode": new(linodebuilder.Builder),
"lxc": new(lxcbuilder.Builder),
"lxd": new(lxdbuilder.Builder),
"ncloud": new(ncloudbuilder.Builder),
@ -129,6 +132,7 @@ var Builders = map[string]packer.Builder{
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
"vmware-iso": new(vmwareisobuilder.Builder),
"vmware-vmx": new(vmwarevmxbuilder.Builder),
"yandex": new(yandexbuilder.Builder),
}
var Provisioners = map[string]packer.Provisioner{

View File

@ -0,0 +1,11 @@
// +build !solaris
package filelock
import "github.com/gofrs/flock"
type Flock = flock.Flock
func New(path string) *Flock {
return flock.New(path)
}

View File

@ -0,0 +1,11 @@
// build solaris
package filelock
// Flock is a noop on solaris for now.
// TODO(azr): PR github.com/gofrs/flock for this.
type Flock = Noop
func New(string) *Flock {
return &Flock{}
}

8
common/filelock/noop.go Normal file
View File

@ -0,0 +1,8 @@
package filelock
// this lock does nothing
type Noop struct{}
func (_ *Noop) Lock() (bool, error) { return true, nil }
func (_ *Noop) TryLock() (bool, error) { return true, nil }
func (_ *Noop) Unlock() error { return nil }

View File

@ -7,9 +7,10 @@ import (
"math/rand"
"net"
"strconv"
"time"
"github.com/gofrs/flock"
"github.com/hashicorp/packer/common/filelock"
"github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/packer"
)
@ -26,7 +27,7 @@ type Listener struct {
net.Listener
Port int
Address string
lock *flock.Flock
lock *filelock.Flock
}
func (l *Listener) Close() error {
@ -40,7 +41,7 @@ func (l *Listener) Close() error {
// ListenRangeConfig contains options for listening to a free address [Min,Max)
// range. ListenRangeConfig wraps a net.ListenConfig.
type ListenRangeConfig struct {
// tcp", "udp"
// like "tcp" or "udp". defaults to "tcp".
Network string
Addr string
Min, Max int
@ -51,49 +52,72 @@ type ListenRangeConfig struct {
// until ctx is cancelled.
// Listen uses net.ListenConfig.Listen internally.
func (lc ListenRangeConfig) Listen(ctx context.Context) (*Listener, error) {
if lc.Network == "" {
lc.Network = "tcp"
}
portRange := lc.Max - lc.Min
for {
if err := ctx.Err(); err != nil {
return nil, err
}
var listener *Listener
err := retry.Config{
RetryDelay: func() time.Duration { return 1 * time.Millisecond },
}.Run(ctx, func(context.Context) error {
port := lc.Min
if portRange > 0 {
port += rand.Intn(portRange)
}
log.Printf("Trying port: %d", port)
lockFilePath, err := packer.CachePath("port", strconv.Itoa(port))
if err != nil {
return nil, err
return err
}
lock := flock.New(lockFilePath)
lock := filelock.New(lockFilePath)
locked, err := lock.TryLock()
if err != nil {
return nil, err
return err
}
if !locked {
continue // this port seems to be locked by another packer goroutine
return ErrPortFileLocked(port)
}
l, err := lc.ListenConfig.Listen(ctx, lc.Network, fmt.Sprintf("%s:%d", lc.Addr, port))
if err != nil {
if err := lock.Unlock(); err != nil {
log.Printf("Could not unlock file lock for port %d: %v", port, err)
log.Fatalf("Could not unlock file lock for port %d: %v", port, err)
}
return &ErrPortBusy{
Port: port,
Err: err,
}
continue // this port is most likely already open
}
log.Printf("Found available port: %d on IP: %s", port, lc.Addr)
return &Listener{
listener = &Listener{
Address: lc.Addr,
Port: port,
Listener: l,
lock: lock,
}, err
}
}
return nil
})
return listener, err
}
type ErrPortFileLocked int
func (port ErrPortFileLocked) Error() string {
return fmt.Sprintf("Port %d is file locked", port)
}
type ErrPortBusy struct {
Port int
Err error
}
func (err *ErrPortBusy) Error() string {
if err == nil {
return "<nil>"
}
return fmt.Sprintf("port %d cannot be opened: %v", err.Port, err.Err)
}

View File

@ -0,0 +1,167 @@
package net
import (
"context"
"net"
"testing"
"time"
)
func TestListenRangeConfig_Listen(t *testing.T) {
topCtx, cancel := context.WithCancel(context.Background())
defer cancel()
var err error
var lockedListener *Listener
{ // open a random port in range
ctx, cancel := context.WithTimeout(topCtx, time.Second*5)
lockedListener, err = ListenRangeConfig{
Min: 800,
Max: 10000,
Addr: "localhost",
}.Listen(ctx)
if err != nil {
t.Fatalf("could not open first port")
}
cancel()
defer lockedListener.Close() // in case
}
{ // open a second random port in range
ctx, cancel := context.WithTimeout(topCtx, time.Second*5)
listener, err := ListenRangeConfig{
Min: 800,
Max: 10000,
Addr: "localhost",
}.Listen(ctx)
if err != nil {
t.Fatalf("could not open first port")
}
cancel()
if err := listener.Close(); err != nil { // in case
t.Fatal("failed to close second random port")
}
}
{ // test that opened port cannot be openned using min/max
ctx, cancel := context.WithTimeout(topCtx, 250*time.Millisecond)
l, err := ListenRangeConfig{
Min: lockedListener.Port,
Max: lockedListener.Port,
}.Listen(ctx)
if err == nil {
l.Close()
t.Fatal("port should be taken, this should fail")
}
if p := int(err.(ErrPortFileLocked)); p != lockedListener.Port {
t.Fatalf("wrong fileport: %d", p)
}
cancel()
}
{ // test that opened port cannot be openned using min only
ctx, cancel := context.WithTimeout(topCtx, 250*time.Millisecond)
l, err := ListenRangeConfig{
Min: lockedListener.Port,
}.Listen(ctx)
if err == nil {
l.Close()
t.Fatalf("port should be taken, this should timeout.")
}
if p := int(err.(ErrPortFileLocked)); p != lockedListener.Port {
t.Fatalf("wrong fileport: %d", p)
}
cancel()
}
err = lockedListener.Close() // close port and release lock file
if err != nil {
t.Fatalf("could not release lockfile or port: %v", err)
}
{ // test that closed port can be reopenned.
ctx, cancel := context.WithTimeout(topCtx, 250*time.Millisecond)
lockedListener, err = ListenRangeConfig{
Min: lockedListener.Port,
}.Listen(ctx)
if err != nil {
t.Fatalf("port should have been freed: %v", err)
}
cancel()
defer lockedListener.Close() // in case
}
err = lockedListener.Listener.Close() // close listener, keep lockfile only
if err != nil {
t.Fatalf("could not release lockfile or port: %v", err)
}
{ // test that file locked port cannot be opened
ctx, cancel := context.WithTimeout(topCtx, 250*time.Millisecond)
l, err := ListenRangeConfig{
Min: lockedListener.Port,
}.Listen(ctx)
if err == nil {
l.Close()
t.Fatalf("port should be file locked, this should timeout")
}
cancel()
}
var netListener net.Listener
{ // test that the closed network port can be reopened using net.Listen
netListener, err = net.Listen("tcp", lockedListener.Addr().String())
if err != nil {
t.Fatalf("listen on freed port failed: %v", err)
}
}
if err := lockedListener.lock.Unlock(); err != nil {
t.Fatalf("error closing port: %v", err)
}
{ // test that busy port cannot be opened
ctx, cancel := context.WithTimeout(topCtx, 250*time.Millisecond)
l, err := ListenRangeConfig{
Min: lockedListener.Port,
}.Listen(ctx)
if err == nil {
l.Close()
t.Fatalf("port should be file locked, this should timeout")
}
busyErr := err.(*ErrPortBusy)
if busyErr.Port != lockedListener.Port {
t.Fatal("wrong port")
}
// error types vary depending on OS and it might get quickly
// complicated to test for the error we want.
cancel()
}
if err := netListener.Close(); err != nil { // free port
t.Fatalf("close failed: %v", err)
}
{ // test that freed port can be opened
ctx, cancel := context.WithTimeout(topCtx, 250*time.Minute)
lockedListener, err = ListenRangeConfig{
Min: lockedListener.Port,
}.Listen(ctx)
if err != nil {
t.Fatalf("port should have been freed: %v", err)
}
cancel()
defer lockedListener.Close() // in case
}
}

View File

@ -25,6 +25,17 @@ type Config struct {
ShouldRetry func(error) bool
}
type RetryExhaustedError struct {
Err error
}
func (err *RetryExhaustedError) Error() string {
if err == nil || err.Err == nil {
return "<nil>"
}
return fmt.Sprintf("retry count exhausted. Last err: %s", err.Err)
}
// Run fn until context is cancelled up until StartTimeout time has passed.
func (cfg Config) Run(ctx context.Context, fn func(context.Context) error) error {
retryDelay := func() time.Duration { return 2 * time.Second }
@ -40,10 +51,10 @@ func (cfg Config) Run(ctx context.Context, fn func(context.Context) error) error
startTimeout = time.After(cfg.StartTimeout)
}
var err error
for try := 0; ; try++ {
var err error
if cfg.Tries != 0 && try == cfg.Tries {
return err
return &RetryExhaustedError{err}
}
if err = fn(ctx); err == nil {
return nil

View File

@ -5,6 +5,8 @@ import (
"errors"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
func success(context.Context) error { return nil }
@ -18,12 +20,23 @@ var failErr = errors.New("woops !")
func fail(context.Context) error { return failErr }
type failOnce bool
func (ran *failOnce) Run(context.Context) error {
if !*ran {
*ran = true
return failErr
}
return nil
}
func TestConfig_Run(t *testing.T) {
cancelledCtx, cancel := context.WithCancel(context.Background())
cancel()
type fields struct {
StartTimeout time.Duration
RetryDelay func() time.Duration
Tries int
}
type args struct {
ctx context.Context
@ -36,26 +49,37 @@ func TestConfig_Run(t *testing.T) {
wantErr error
}{
{"success",
fields{StartTimeout: time.Second, RetryDelay: nil},
fields{StartTimeout: time.Second},
args{context.Background(), success},
nil},
{"context cancelled",
fields{StartTimeout: time.Second, RetryDelay: nil},
fields{StartTimeout: time.Second},
args{cancelledCtx, wait},
context.Canceled},
{"timeout",
fields{StartTimeout: 20 * time.Millisecond, RetryDelay: func() time.Duration { return 10 * time.Millisecond }},
args{cancelledCtx, fail},
failErr},
{"success after one failure",
fields{Tries: 2, RetryDelay: func() time.Duration { return 0 }},
args{context.Background(), new(failOnce).Run},
nil},
{"fail after one failure",
fields{Tries: 1, RetryDelay: func() time.Duration { return 0 }},
args{context.Background(), new(failOnce).Run},
&RetryExhaustedError{failErr},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := Config{
StartTimeout: tt.fields.StartTimeout,
RetryDelay: tt.fields.RetryDelay,
Tries: tt.fields.Tries,
}
if err := cfg.Run(tt.args.ctx, tt.args.fn); err != tt.wantErr {
t.Fatalf("Config.Run() error = %v, wantErr %v", err, tt.wantErr)
err := cfg.Run(tt.args.ctx, tt.args.fn)
if diff := cmp.Diff(err, tt.wantErr, DeepAllowUnexported(RetryExhaustedError{}, errors.New(""))); diff != "" {
t.Fatalf("Config.Run() unexpected error: %s", diff)
}
})
}

View File

@ -0,0 +1,48 @@
package retry
import (
"reflect"
"github.com/google/go-cmp/cmp"
)
func DeepAllowUnexported(vs ...interface{}) cmp.Option {
m := make(map[reflect.Type]struct{})
for _, v := range vs {
structTypes(reflect.ValueOf(v), m)
}
var typs []interface{}
for t := range m {
typs = append(typs, reflect.New(t).Elem().Interface())
}
return cmp.AllowUnexported(typs...)
}
func structTypes(v reflect.Value, m map[reflect.Type]struct{}) {
if !v.IsValid() {
return
}
switch v.Kind() {
case reflect.Ptr:
if !v.IsNil() {
structTypes(v.Elem(), m)
}
case reflect.Interface:
if !v.IsNil() {
structTypes(v.Elem(), m)
}
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
structTypes(v.Index(i), m)
}
case reflect.Map:
for _, k := range v.MapKeys() {
structTypes(v.MapIndex(k), m)
}
case reflect.Struct:
m[v.Type()] = struct{}{}
for i := 0; i < v.NumField(); i++ {
structTypes(v.Field(i), m)
}
}
}

View File

@ -7,11 +7,12 @@ import (
"fmt"
"log"
"os"
"runtime"
"strings"
"github.com/gofrs/flock"
getter "github.com/hashicorp/go-getter"
urlhelper "github.com/hashicorp/go-getter/helper/url"
"github.com/hashicorp/packer/common/filelock"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
@ -82,10 +83,27 @@ func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multis
errs = append(errs, err)
}
state.Put("error", fmt.Errorf("Downloading file: %v", errs))
err := fmt.Errorf("error downloading %s: %v", s.Description, errs)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var (
getters = getter.Getters
)
func init() {
if runtime.GOOS == "windows" {
getters["file"] = &getter.FileGetter{
// always copy local files instead of symlinking to fix GH-7534. The
// longer term fix for this would be to change the go-getter so that it
// can leave the source file where it is & tell us where it is.
Copy: true,
}
}
}
func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string) (string, error) {
u, err := urlhelper.Parse(source)
if err != nil {
@ -103,8 +121,6 @@ func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string
q := u.Query()
q.Set("checksum", s.Checksum)
u.RawQuery = q.Encode()
} else if s.ChecksumType != "none" {
return "", fmt.Errorf("Empty checksum")
}
targetPath := s.TargetPath
@ -130,7 +146,7 @@ func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string
lockFile := targetPath + ".lock"
log.Printf("Acquiring lock for: %s (%s)", u.String(), lockFile)
lock := flock.New(lockFile)
lock := filelock.New(lockFile)
lock.Lock()
defer lock.Unlock()
@ -152,6 +168,7 @@ func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string
ProgressListener: ui,
Pwd: wd,
Dir: false,
Getters: getters,
}
switch err := gc.Get(); err.(type) {

View File

@ -63,10 +63,13 @@ func TestStepDownload_Run(t *testing.T) {
want multistep.StepAction
wantFiles []string
}{
{"not passing a checksum fails",
{"not passing a checksum passes",
fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}},
multistep.ActionHalt,
nil,
multistep.ActionContinue,
[]string{
toSha1(abs(t, "./test-fixtures/root/another.txt")),
toSha1(abs(t, "./test-fixtures/root/another.txt")) + ".lock",
},
},
{"none checksum works, without a checksum",
fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}, ChecksumType: "none"},

View File

@ -9,7 +9,7 @@
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_test",
"source_image":"win2008r2_64_ent_sp1_zh-cn_40G_alibase_20170915.vhd",
"source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
"internet_charge_type":"PayByTraffic",

View File

@ -14,7 +14,18 @@
"instance_type":"ecs.n1.tiny",
"internet_charge_type":"PayByTraffic",
"io_optimized":"true",
"image_disk_mappings":[{"disk_name":"data1","disk_size":20},{"disk_name":"data1","disk_size":20,"disk_device":"/dev/xvdz"}]
"image_disk_mappings":[
{
"disk_name":"data1",
"disk_size":20,
"disk_delete_with_instance": true
},{
"disk_name":"data2",
"disk_size":20,
"disk_device":"/dev/xvdz",
"disk_delete_with_instance": true
}
]
}],
"provisioners": [{
"type": "shell",

38
go.mod
View File

@ -2,13 +2,15 @@ module github.com/hashicorp/packer
require (
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1
github.com/Azure/azure-sdk-for-go v17.3.1+incompatible
github.com/Azure/azure-sdk-for-go v27.3.0+incompatible
github.com/Azure/go-autorest v10.12.0+incompatible
github.com/Azure/go-autorest/tracing v0.1.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 // indirect
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290
github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591
github.com/Telmate/proxmox-api-go v0.0.0-20190410200643-f08824d5082d
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/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd // indirect
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 // indirect
@ -17,9 +19,9 @@ require (
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/creack/goselect v0.0.0-20180210034346-528c74964609 // indirect
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978
github.com/creack/goselect v0.1.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/godo v1.11.1
github.com/dnaeon/go-vcr v1.0.0 // indirect
@ -28,7 +30,6 @@ require (
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08
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
@ -54,6 +55,7 @@ require (
github.com/hetznercloud/hcloud-go v1.12.0
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564
github.com/json-iterator/go v1.1.6 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77 // indirect
@ -62,27 +64,29 @@ require (
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
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
github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859
github.com/miekg/dns v1.1.1 // indirect
github.com/mitchellh/cli v1.0.0
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/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/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
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 // indirect
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/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/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a
github.com/pierrec/lz4 v2.0.5+incompatible
@ -95,7 +99,6 @@ require (
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/stretchr/testify v1.3.0
@ -104,15 +107,18 @@ require (
github.com/ulikunitz/xz v0.5.5
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311
github.com/xanzy/go-cloudstack v2.4.1+incompatible
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6
google.golang.org/api v0.1.0
google.golang.org/appengine v1.4.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
github.com/yandex-cloud/go-genproto v0.0.0-20190401174212-1db0ef3dce9b
github.com/yandex-cloud/go-sdk v0.0.0-20190402114215-3fc1d6947035
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
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
google.golang.org/api v0.3.1
google.golang.org/grpc v1.19.1
gopkg.in/h2non/gock.v1 v1.0.12 // indirect
gopkg.in/ini.v1 v1.42.0 // indirect
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

143
go.sum
View File

@ -1,7 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
@ -9,10 +12,12 @@ 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 v17.3.1+incompatible h1:9Nzge8xxnYm5lVRkvTpG1odiDN0fYDorQwVEaVfg1+g=
github.com/Azure/azure-sdk-for-go v17.3.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
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/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/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=
@ -20,10 +25,16 @@ 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/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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
@ -33,6 +44,7 @@ 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=
@ -45,21 +57,26 @@ github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3A
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=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3 h1:3b+p838vN4sc37brz9W2HDphtSwZFcXZwFLyzm5Vk28=
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae h1:2Zmk+8cNvAGuY8AyvZuWpUdpQUAXwfom4ReVMe/CTIo=
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
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/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.0.0-20180210034346-528c74964609 h1:FSxXMd2wCHj6GqgBdo4UtVA9R2aieIDvSniepqyOppU=
github.com/creack/goselect v0.0.0-20180210034346-528c74964609/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978 h1:oyfbRmu7YnytN4bXhvJPY1HPgUL52j5AxtgGDU0bMVs=
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/digitalocean/godo v1.11.1 h1:OsTh37YFKk+g6DnAOrkXJ9oDArTkRx5UTkBJ2EWAO38=
@ -74,18 +91,26 @@ 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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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/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=
@ -93,18 +118,23 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
@ -117,15 +147,21 @@ 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=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw=
github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8=
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=
@ -133,6 +169,7 @@ github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxB
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79 h1:RKu7yAXZTaQsxj1K9GDsh+QVw0+Wu1SWHxtbFN0n+hE=
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79/go.mod h1:09jT3Y/OIsjTjQ2+3bkVNPDKqWcGIYYvjB2BEKVUdvc=
@ -140,6 +177,7 @@ github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBA
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@ -147,10 +185,12 @@ 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.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
@ -174,8 +214,11 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564 h1:+HMa2xWQOm+9ebsl0+XsuLaPuFCxExv3sCXo5psVzYI=
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
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=
@ -191,6 +234,7 @@ 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=
@ -199,6 +243,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
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=
@ -210,24 +256,29 @@ github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zw
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007 h1:xjZxmVDmDZoEsl2gV0qD0pyBH+wXmJIZd27wsNFphJk=
github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859 h1:smQbSzmT3EHl4EUwtFwFGmGIpiYgIiiPeVv1uguIQEE=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7 h1:PXPMDtfqV+rZJshQHOiwUFqlqErXaAcuWy+/ZmyRfNc=
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
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/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=
github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc h1:5T6hzGUO5OrL6MdYXYoLQtRWJDDgjdlOVBn9mIqGY1g=
@ -238,10 +289,15 @@ 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/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=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef h1:E/seV1Rtsnr2juBw1Dfz4iDPT3/5s1H/BATx+ePmSyo=
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=
@ -256,13 +312,16 @@ 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/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/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=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca h1:k8gsErq3rkcbAyCnpOycQsbw88NjCHk7L3KfBZKhQDQ=
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
@ -273,17 +332,26 @@ 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=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e h1:lN+IKs+Jb9uwDOMO4VJZzH9vOjjist0THR5s9akp+Ss=
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70 h1:DaqC32ZwOuO4ctgg9qAdKnlQxwFPkKmCOEqwSNwYy7c=
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s=
@ -338,19 +406,30 @@ github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311 h1:s5pyxd5S6wRs2WpE
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/go-cloudstack v2.4.1+incompatible h1:Oc4xa2+I94h1g/QJ+nHoq597nJz2KXzxuQx/weOx0AU=
github.com/xanzy/go-cloudstack v2.4.1+incompatible/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
github.com/yandex-cloud/go-genproto v0.0.0-20190401174212-1db0ef3dce9b h1:WcWw17zHjM1QoM5dPD2XUK9S0wSKlyohlCMeb1rwr0g=
github.com/yandex-cloud/go-genproto v0.0.0-20190401174212-1db0ef3dce9b/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20190402114215-3fc1d6947035 h1:2ZLZeg6xp+kYYGR2iMWSZyTn6j8bphNguO3drw7S1l4=
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/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=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d h1:adrbvkTDn9rGnXg2IJDKozEpXXLZN89pdIA+Syt4/u0=
golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -360,17 +439,31 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
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=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -379,23 +472,37 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U=
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-20181218192612-074acd46bca6 h1:MXtOG7w2ND9qNCUZSDBGll/SpVIq7ftozR9I8/JGBHY=
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190425145619-16072639606e h1:4ktJgTV34+N3qOZUc5fAaG3Pb11qzMm3PkAoTAgUZ2I=
golang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1 h1:nsUiJHvm6yOoRozW9Tz0siNk9sHieLzR+w814Ihse3A=
golang.org/x/text v0.3.1/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -407,10 +514,16 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
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=
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=
@ -422,16 +535,22 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/h2non/gock.v1 v1.0.12 h1:o3JJqe+h7R9Ay6LtMeFrKz1WnokrJDrNpDQs9KGqVn8=
gopkg.in/h2non/gock.v1 v1.0.12/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516 h1:H6trpavCIuipdInWrab8l34Mf+GGVfphniHostMdMaQ=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw=
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

22
main.go
View File

@ -186,21 +186,19 @@ func wrappedMain() int {
return 1
}
} else {
var TTY packer.TTY
if !inPlugin {
var err error
TTY, err = tty.Open()
if err != nil {
fmt.Fprintf(os.Stderr, "No tty available: %s\n", err)
} else {
defer TTY.Close()
}
}
ui = &packer.BasicUi{
basicUi := &packer.BasicUi{
Reader: os.Stdin,
Writer: os.Stdout,
ErrorWriter: os.Stdout,
TTY: TTY,
}
ui = basicUi
if !inPlugin {
if TTY, err := tty.Open(); err != nil {
fmt.Fprintf(os.Stderr, "No tty available: %s\n", err)
} else {
basicUi.TTY = TTY
defer TTY.Close()
}
}
}
// Create the CLI meta

View File

@ -4,11 +4,11 @@ import (
"context"
"io"
"os"
"strings"
"sync"
"unicode"
"golang.org/x/sync/errgroup"
"github.com/hashicorp/packer/common/iochan"
"github.com/mitchellh/iochan"
)
// CmdDisconnect is a sentinel value to indicate a RemoteCmd
@ -115,30 +115,52 @@ func (r *RemoteCmd) RunWithUi(ctx context.Context, c Communicator, ui Ui) error
r.Stderr = io.MultiWriter(r.Stderr, stderr_w)
}
// Loop and get all our output until done.
printFn := func(in io.Reader, out func(string)) error {
for output := range iochan.LineReader(in) {
if output != "" {
out(output)
}
}
return nil
}
wg, ctx := errgroup.WithContext(ctx)
wg.Go(func() error { return printFn(stdout_r, ui.Message) })
wg.Go(func() error { return printFn(stderr_r, ui.Error) })
// Start the command
if err := c.Start(ctx, r); err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-r.exitCh:
return nil
// Create the channels we'll use for data
stdoutCh := iochan.DelimReader(stdout_r, '\n')
stderrCh := iochan.DelimReader(stderr_r, '\n')
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
r.Wait()
}()
// Loop and get all our output
OutputLoop:
for {
select {
case output := <-stderrCh:
if output != "" {
ui.Error(r.cleanOutputLine(output))
}
case output := <-stdoutCh:
if output != "" {
ui.Message(r.cleanOutputLine(output))
}
case <-r.exitCh:
break OutputLoop
case <-ctx.Done():
return ctx.Err()
}
}
// Make sure we finish off stdout/stderr because we may have gotten
// a message from the exit channel before finishing these first.
for output := range stdoutCh {
ui.Message(r.cleanOutputLine(output))
}
for output := range stderrCh {
ui.Error(r.cleanOutputLine(output))
}
return nil
}
// SetExited is a helper for setting that this process is exited. This
@ -174,3 +196,19 @@ func (r *RemoteCmd) initchan() {
}
})
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func (r *RemoteCmd) cleanOutputLine(line string) string {
// Trim surrounding whitespace
line = strings.TrimRightFunc(line, unicode.IsSpace)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
}

View File

@ -14,12 +14,11 @@ type PostProcessor interface {
Configure(...interface{}) error
// PostProcess takes a previously created Artifact and produces another
// Artifact. If an error occurs, it should return that error. If `keep`
// is true, then the previous artifact defaults to being kept if
// user has not given a value to keep_input_artifact. If forceOverride
// is true, then any user input for keep_input_artifact is ignored and
// the artifact is either kept or discarded according to the value set in
// `keep`.
// Artifact. If an error occurs, it should return that error. If `keep` is
// true, then the previous artifact defaults to being kept if user has not
// given a value to keep_input_artifact. If forceOverride is true, then any
// user input for keep_input_artifact is ignored and the artifact is either
// kept or discarded according to the value set in `keep`.
// PostProcess is cancellable using context
PostProcess(context.Context, Ui, Artifact) (a Artifact, keep bool, forceOverride bool, err error)
}

Some files were not shown because too many files have changed in this diff Show More