Merge remote-tracking branch 'origin/master' into pr/7501
This commit is contained in:
commit
ae71a81c1e
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 ""
|
||||
}
|
|
@ -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) {}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{},
|
||||
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}]
|
||||
}
|
||||
`
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}]
|
||||
}
|
||||
`
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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{}
|
||||
}
|
|
@ -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 }
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
38
go.mod
|
@ -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
143
go.sum
|
@ -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
22
main.go
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue