Add packer plugin which support alicloud ecs images

This commit is contained in:
zhuzhih2017 2017-03-03 16:56:17 +08:00
parent 33aa372835
commit fadfb696dd
39 changed files with 3095 additions and 1 deletions

View File

@ -0,0 +1,81 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/packer/template/interpolate"
"os"
)
// Config of alicloud
type AlicloudAccessConfig struct {
AlicloudAccessKey string `mapstructure:"access_key"`
AlicloudSecretKey string `mapstructure:"secret_key"`
AlicloudRegion string `mapstructure:"region"`
AlicloudSkipValidation bool `mapstructure:"skip_region_validation"`
}
// Client for AlicloudClient
func (c *AlicloudAccessConfig) Client() (*ecs.Client, error) {
if err := c.loadAndValidate(); err != nil {
return nil, err
}
client := ecs.NewClient(c.AlicloudAccessKey, c.AlicloudSecretKey)
if _, err := client.DescribeRegions(); err != nil {
return nil, err
}
return client, nil
}
func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if err := c.Config(); err != nil {
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 len(errs) > 0 {
return errs
}
return nil
}
func (c *AlicloudAccessConfig) Config() error {
if c.AlicloudAccessKey == "" {
c.AlicloudAccessKey = os.Getenv("ALICLOUD_ACCESS_KEY")
}
if c.AlicloudSecretKey == "" {
c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY")
}
if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" {
return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must set in template file or environment variables")
}
return nil
}
func (c *AlicloudAccessConfig) loadAndValidate() error {
if err := c.validateRegion(); err != nil {
return err
}
return nil
}
func (c *AlicloudAccessConfig) validateRegion() error {
for _, valid := range common.ValidRegions {
if c.AlicloudRegion == string(valid) {
return nil
}
}
return fmt.Errorf("Not a valid alicloud region: %s", c.AlicloudRegion)
}

View File

@ -0,0 +1,44 @@
package ecs
import (
"testing"
)
func testAlicloudAccessConfig() *AlicloudAccessConfig {
return &AlicloudAccessConfig{
AlicloudAccessKey: "",
AlicloudSecretKey: "",
}
}
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"
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
c.AlicloudRegion = "cn-beijing"
if err := c.Prepare(nil); err != nil {
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
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudSkipValidation = false
}

View File

@ -0,0 +1,134 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/packer/packer"
"log"
"sort"
"strings"
)
type Artifact struct {
// A map of regions to alicloud image IDs.
AlicloudImages map[string]string
// BuilderId is the unique ID for the builder that created this alicloud image
BuilderIdValue string
// Alcloud connection for performing API stuff.
Client *ecs.Client
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
parts := make([]string, 0, len(a.AlicloudImages))
for region, ecsImageId := range a.AlicloudImages {
parts = append(parts, fmt.Sprintf("%s:%s", region, ecsImageId))
}
sort.Strings(parts)
return strings.Join(parts, ",")
}
func (a *Artifact) String() string {
alicloudImageStrings := make([]string, 0, len(a.AlicloudImages))
for region, id := range a.AlicloudImages {
single := fmt.Sprintf("%s: %s", region, id)
alicloudImageStrings = append(alicloudImageStrings, single)
}
sort.Strings(alicloudImageStrings)
return fmt.Sprintf("Alicloud images were created:\n\n%s", strings.Join(alicloudImageStrings, "\n"))
}
func (a *Artifact) State(name string) interface{} {
switch name {
case "atlas.artifact.metadata":
return a.stateAtlasMetadata()
default:
return nil
}
}
func (a *Artifact) Destroy() error {
errors := make([]error, 0)
for region, imageId := range a.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})
if err != nil {
errors = append(errors, err)
}
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 {
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,
})
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 {
if len(errors) == 1 {
return errors[0]
} else {
return &packer.MultiError{Errors: errors}
}
}
return nil
}
func (a *Artifact) stateAtlasMetadata() interface{} {
metadata := make(map[string]string)
for region, imageId := range a.AlicloudImages {
k := fmt.Sprintf("region.%s", region)
metadata[k] = imageId
}
return metadata
}

View File

@ -0,0 +1,47 @@
package ecs
import (
"reflect"
"testing"
"github.com/mitchellh/packer/packer"
)
func TestArtifact_Impl(t *testing.T) {
var _ packer.Artifact = new(Artifact)
}
func TestArtifactId(t *testing.T) {
expected := `east:foo,west:bar`
ecsImages := make(map[string]string)
ecsImages["east"] = "foo"
ecsImages["west"] = "bar"
a := &Artifact{
AlicloudImages: ecsImages,
}
result := a.Id()
if result != expected {
t.Fatalf("bad: %s", result)
}
}
func TestArtifactState_atlasMetadata(t *testing.T) {
a := &Artifact{
AlicloudImages: map[string]string{
"east": "foo",
"west": "bar",
},
}
actual := a.State("atlas.artifact.metadata")
expected := map[string]string{
"region.east": "foo",
"region.west": "bar",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}

View File

@ -0,0 +1,87 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/mitchellh/packer/template/interpolate"
)
type AlicloudDiskDevice struct {
DiskName string `mapstructure:"disk_name"`
DiskCategory string `mapstructure:"disk_category"`
DiskSize int `mapstructure:"disk_size"`
SnapshotId string `mapstructure:"disk_snapshot_id"`
Description string `mapstructure:"disk_description"`
DeleteWithInstance bool `mapstructure:"disk_delete_with_instance"`
Device string `mapstructure:"disk_device"`
}
type AlicloudDiskDevices struct {
ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"`
}
type AlicloudImageConfig struct {
AlicloudImageName string `mapstructure:"image_name"`
AlicloudImageVersion string `mapstructure:"image_version"`
AlicloudImageDescription string `mapstructure:"image_description"`
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"`
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"`
AlicloudImageForceDetele bool `mapstructure:"image_force_delete"`
AlicloudImageForceDeteleSnapshots bool `mapstructure:"image_force_delete_snapshots"`
AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"`
AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"`
AlicloudDiskDevices `mapstructure:",squash"`
}
func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.AlicloudImageName == "" {
errs = append(errs, fmt.Errorf("image_name must be specified"))
}
if len(c.AlicloudImageDestinationRegions) > 0 {
regionSet := make(map[string]struct{})
regions := make([]string, 0, len(c.AlicloudImageDestinationRegions))
for _, region := range c.AlicloudImageDestinationRegions {
// If we already saw the region, then don't look again
if _, ok := regionSet[region]; ok {
continue
}
// Mark that we saw the region
regionSet[region] = struct{}{}
if !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)
}
c.AlicloudImageDestinationRegions = regions
}
if len(errs) > 0 {
return errs
}
return nil
}
func validateRegion(region string) error {
for _, valid := range common.ValidRegions {
if region == string(valid) {
return nil
}
}
return fmt.Errorf("Not a valid alicloud region: %s", region)
}

View File

@ -0,0 +1,63 @@
package ecs
import (
"github.com/denverdino/aliyungo/common"
"testing"
)
func testAlicloudImageConfig() *AlicloudImageConfig {
return &AlicloudImageConfig{
AlicloudImageName: "foo",
}
}
func TestECSImageConfigPrepare_name(t *testing.T) {
c := testAlicloudImageConfig()
if err := c.Prepare(nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AlicloudImageName = ""
if err := c.Prepare(nil); err == nil {
t.Fatal("should have error")
}
}
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAlicloudImageConfig()
c.AlicloudImageDestinationRegions = nil
if err := c.Prepare(nil); err != nil {
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.AlicloudImageSkipRegionValidation = true
if err := c.Prepare(nil); err != nil {
t.Fatal("shouldn't have error")
}
c.AlicloudImageSkipRegionValidation = false
}
func regionsToString() []string {
var regions []string
for _, region := range common.ValidRegions {
regions = append(regions, string(region))
}
return regions
}

View File

@ -0,0 +1,21 @@
package ecs
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
func message(state multistep.StateBag, module string) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
ui := state.Get("ui").(packer.Ui)
if cancelled || halted {
ui.Say(fmt.Sprintf("Delete the %s because cancelation or error...", module))
} else {
ui.Say(fmt.Sprintf("Clean the created %s", module))
}
}

View File

@ -0,0 +1,85 @@
package ecs
import (
"errors"
"fmt"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/template/interpolate"
"os"
)
const (
ssh_time_out = 60000000000
default_port = 22
default_comm_type = "ssh"
)
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
ZoneId string `mapstructure:"zone_id"`
IOOptimized bool `mapstructure:"io_optimized"`
InstanceType string `mapstructure:"instance_type"`
Description string `mapstructure:"description"`
AlicloudSourceImage string `mapstructure:"source_image"`
ForceStopInstance bool `mapstructure:"force_stop_instance"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupName string `mapstructure:"security_group_name"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcId string `mapstructure:"vpc_id"`
VpcName string `mapstructure:"vpc_name"`
CidrBlock string `mapstructure:"vpc_cidr_block"`
VSwitchId string `mapstructure:"vswitch_id"`
VSwitchName string `mapstructure:"vswitch_id"`
InstanceName string `mapstructure:"instance_name"`
InternetChargeType string `mapstructure:"internet_charge_type"`
InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwith_out"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
PublicKey string `mapstructure:"ssh_private_key_file"`
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
if c.SSHKeyPairName == "" && c.TemporaryKeyPairName == "" &&
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" {
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
if c.Comm.Type == "" {
c.Comm.Type = default_comm_type
}
if c.Comm.SSHTimeout == 0 {
c.Comm.SSHTimeout = ssh_time_out
}
if c.Comm.SSHPort == 0 {
c.Comm.SSHPort = default_port
}
// Validation
errs := c.Comm.Prepare(ctx)
if c.AlicloudSourceImage == "" {
errs = append(errs, errors.New("A source_image must be specified"))
}
if c.InstanceType == "" {
errs = append(errs, errors.New("An aliclod_instance_type must be specified"))
}
if c.UserData != "" && c.UserDataFile != "" {
errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if c.UserDataFile != "" {
if _, err := os.Stat(c.UserDataFile); err != nil {
errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile))
}
}
return errs
}

View File

@ -0,0 +1,122 @@
package ecs
import (
"io/ioutil"
"testing"
"github.com/mitchellh/packer/helper/communicator"
)
func testConfig() *RunConfig {
return &RunConfig{
AlicloudSourceImage: "alicloud_images",
InstanceType: "ecs.n1.tiny",
Comm: communicator.Config{
SSHUsername: "alicloud",
},
}
}
func TestRunConfigPrepare(t *testing.T) {
c := testConfig()
err := c.Prepare(nil)
if len(err) > 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_InstanceType(t *testing.T) {
c := testConfig()
c.InstanceType = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SourceECSImage(t *testing.T) {
c := testConfig()
c.AlicloudSourceImage = ""
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_SSHPort(t *testing.T) {
c := testConfig()
c.Comm.SSHPort = 0
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 22 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
c.Comm.SSHPort = 44
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.Comm.SSHPort != 44 {
t.Fatalf("invalid value: %d", c.Comm.SSHPort)
}
}
func TestRunConfigPrepare_UserData(t *testing.T) {
c := testConfig()
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
c.UserData = "foo"
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_UserDataFile(t *testing.T) {
c := testConfig()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
c.UserDataFile = "idontexistidontthink"
if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("err: %s", err)
}
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer tf.Close()
c.UserDataFile = tf.Name()
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
}
func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
c := testConfig()
c.TemporaryKeyPairName = ""
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.TemporaryKeyPairName == "" {
t.Fatal("keypair name is empty")
}
c.TemporaryKeyPairName = "ssh-key-123"
if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err)
}
if c.TemporaryKeyPairName != "ssh-key-123" {
t.Fatal("keypair name does not match")
}
}

View File

@ -0,0 +1,47 @@
package ecs
import (
"fmt"
"github.com/mitchellh/multistep"
packerssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh"
)
type alicloudSSHHelper interface {
}
// SSHHost returns a function that can be given to the SSH communicator
func SSHHost(e alicloudSSHHelper, private bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
ipAddress := state.Get("ipaddress").(string)
return ipAddress, nil
}
}
func SSHConfig(useAgent bool, username, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
privateKey, hasKey := state.GetOk("privateKey")
if hasKey {
signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}, nil
} else {
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password),
ssh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(password)),
}}, nil
}
}
}

View File

@ -0,0 +1,46 @@
package ecs
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
gossh "golang.org/x/crypto/ssh"
)
var (
ErrKeyGeneration = errors.New("Unable to generate key")
ErrValidation = errors.New("Unable to validate key")
ErrPublicKey = errors.New("Unable to convert public key")
)
type KeyPair struct {
PrivateKey string
PublicKey string
}
// NewKeyPair generates a new SSH keypair
// This will return a private & public key encoded as DER.
func NewKeyPair() (keyPair *KeyPair, err error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, ErrKeyGeneration
}
if err := priv.Validate(); err != nil {
return nil, ErrValidation
}
privDer := x509.MarshalPKCS1PrivateKey(priv)
pubSSH, err := gossh.NewPublicKey(&priv.PublicKey)
if err != nil {
return nil, ErrPublicKey
}
return &KeyPair{
PrivateKey: string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: privDer})),
PublicKey: string(gossh.MarshalAuthorizedKey(pubSSH)),
}, nil
}

View File

@ -0,0 +1,277 @@
// The alicloud contains a packer.Builder implementation that
// builds ecs images for alicloud.
package ecs
import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
"log"
)
// The unique ID for this builder
const BuilderId = "alibaba.alicloud"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
AlicloudAccessConfig `mapstructure:",squash"`
AlicloudImageConfig `mapstructure:",squash"`
RunConfig `mapstructure:",squash"`
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
type InstanceNetWork string
const (
ClassicNet = InstanceNetWork("classic")
VpcNet = InstanceNetWork("vpc")
ALICLOUD_DEFAULT_SHORT_TIMEOUT = 180
ALICLOUD_DEFAULT_TIMEOUT = 1800
ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600
)
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_command",
},
},
}, raws...)
b.config.ctx.EnableEnv = true
if err != nil {
return nil, err
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AlicloudAccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AlicloudImageConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
log.Println(common.ScrubConfig(b.config, b.config.AlicloudAccessKey, b.config.AlicloudSecretKey))
return nil, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
client, err := b.config.Client()
if err != nil {
return nil, err
}
state := new(multistep.BasicStateBag)
state.Put("config", b.config)
state.Put("client", client)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("networktype", b.chooseNetworkType())
var steps []multistep.Step
if b.chooseNetworkType() == VpcNet {
// Build the steps
steps = []multistep.Step{
&stepPreValidate{
AlicloudDestImageName: b.config.AlicloudImageName,
ForceDelete: b.config.AlicloudImageForceDetele,
},
&stepCheckAlicloudSourceImage{
SourceECSImageId: b.config.AlicloudSourceImage,
},
&StepConfigAlicloudKeyPair{
Debug: b.config.PackerDebug,
KeyPairName: b.config.SSHKeyPairName,
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
PublicKeyFile: b.config.PublicKey,
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
},
&stepConfigAlicloudVPC{
VpcId: b.config.VpcId,
CidrBlock: b.config.CidrBlock,
VpcName: b.config.VpcName,
},
&stepConfigAlicloudVSwithc{
VSwitchId: b.config.VSwitchId,
ZoneId: b.config.ZoneId,
CidrBlock: b.config.CidrBlock,
VSwitchName: b.config.VSwitchName,
},
&stepConfigAlicloudSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SecurityGroupName: b.config.SecurityGroupId,
RegionId: b.config.AlicloudRegion,
},
&stepCreateAlicloudInstance{
IOOptimized: b.config.IOOptimized,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
RegionId: b.config.AlicloudRegion,
InternetChargeType: b.config.InternetChargeType,
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
InstnaceName: b.config.InstanceName,
ZoneId: b.config.ZoneId,
},
&setpConfigAlicloudEIP{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
RegionId: b.config.AlicloudRegion,
},
&stepRunAlicloudInstance{},
&stepMountAlicloudDisk{},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: SSHHost(
client,
b.config.SSHPrivateIp),
SSHConfig: SSHConfig(
b.config.RunConfig.Comm.SSHAgentAuth,
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
},
&common.StepProvision{},
&stepStopAlicloudInstance{
ForceStop: b.config.ForceStopInstance,
},
&stepDeleteAlicloudImageSnapshots{
AlicloudImageForceDeteleSnapshots: b.config.AlicloudImageForceDeteleSnapshots,
AlicloudImageForceDetele: b.config.AlicloudImageForceDetele,
AlicloudImageName: b.config.AlicloudImageName,
},
&stepCreateAlicloudImage{},
&setpRegionCopyAlicloudImage{
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
RegionId: b.config.AlicloudRegion,
},
&setpShareAlicloudImage{
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
RegionId: b.config.AlicloudRegion,
},
}
} else {
// Build the steps
steps = []multistep.Step{
&stepPreValidate{
AlicloudDestImageName: b.config.AlicloudImageName,
ForceDelete: b.config.AlicloudImageForceDetele,
},
&stepCheckAlicloudSourceImage{
SourceECSImageId: b.config.AlicloudSourceImage,
},
&StepConfigAlicloudKeyPair{
Debug: b.config.PackerDebug,
KeyPairName: b.config.SSHKeyPairName,
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
PublicKeyFile: b.config.PublicKey,
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
},
&stepConfigAlicloudSecurityGroup{
SecurityGroupId: b.config.SecurityGroupId,
SecurityGroupName: b.config.SecurityGroupId,
RegionId: b.config.AlicloudRegion,
},
&stepCreateAlicloudInstance{
IOOptimized: b.config.IOOptimized,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
RegionId: b.config.AlicloudRegion,
InternetChargeType: b.config.InternetChargeType,
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
InstnaceName: b.config.InstanceName,
ZoneId: b.config.ZoneId,
},
&stepConfigAlicloudPublicIP{
RegionId: b.config.AlicloudRegion,
},
&stepRunAlicloudInstance{},
&stepMountAlicloudDisk{},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: SSHHost(
client,
b.config.SSHPrivateIp),
SSHConfig: SSHConfig(
b.config.RunConfig.Comm.SSHAgentAuth,
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
},
&common.StepProvision{},
&stepStopAlicloudInstance{
ForceStop: b.config.ForceStopInstance,
},
&stepDeleteAlicloudImageSnapshots{
AlicloudImageForceDeteleSnapshots: b.config.AlicloudImageForceDeteleSnapshots,
AlicloudImageForceDetele: b.config.AlicloudImageForceDetele,
AlicloudImageName: b.config.AlicloudImageName,
},
&stepCreateAlicloudImage{},
&setpRegionCopyAlicloudImage{
AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions,
AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames,
RegionId: b.config.AlicloudRegion,
},
&setpShareAlicloudImage{
AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts,
AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts,
RegionId: b.config.AlicloudRegion,
},
}
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If there are no ECS images, then just return
if _, ok := state.GetOk("alicloudimages"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
AlicloudImages: state.Get("alicloudimages").(map[string]string),
BuilderIdValue: BuilderId,
Client: client,
}
return artifact, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
func (b *Builder) chooseNetworkType() InstanceNetWork {
//Alicloud userdata require vpc network and public key require userdata, so besides user specific vpc network,
//choose vpc networks in those cases
if b.config.RunConfig.Comm.SSHPrivateKey != "" || b.config.UserData != "" || b.config.UserDataFile != "" || b.config.VpcId != "" || b.config.VSwitchId != "" || b.config.TemporaryKeyPairName != "" {
return VpcNet
} else {
return ClassicNet
}
}

View File

@ -0,0 +1,325 @@
package ecs
import (
"os"
"testing"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
builderT "github.com/mitchellh/packer/helper/builder/testing"
"github.com/mitchellh/packer/packer"
)
func TestBuilderAcc_basic(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccBasic,
})
}
func TestBuilderAcc_windows(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccWindows,
})
}
func TestBuilderAcc_regionCopy(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccRegionCopy,
Check: checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}),
})
}
func TestBuilderAcc_forceDelete(t *testing.T) {
// Build the same alicloud image twice, with ecs_image_force_delete on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeregisterConfig("false", "delete"),
SkipArtifactTeardown: true,
})
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeregisterConfig("true", "delete"),
})
}
func TestBuilderAcc_ECSImageSharing(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: testBuilderAccSharing,
Check: checkECSImageSharing("1309208528360047"),
})
}
func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) {
destImageName := "delete"
// Build the same alicloud image name twice, with force_delete_snapshot on the second run
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("false", destImageName),
SkipArtifactTeardown: true,
})
// Get image data by image image name
client, _ := testAliyunClient()
images, _, _ := client.DescribeImages(&ecs.DescribeImagesArgs{
ImageName: "packer-test-" + destImageName,
RegionId: common.Region("cn-beijing")})
image := images[0]
// Get snapshot ids for image
snapshotIds := []string{}
for _, device := range image.DiskDeviceMappings.DiskDeviceMapping {
if device.Device != "" && device.SnapshotId != "" {
snapshotIds = append(snapshotIds, device.SnapshotId)
}
}
builderT.Test(t, builderT.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Builder: &Builder{},
Template: buildForceDeleteSnapshotConfig("true", destImageName),
Check: checkSnapshotsDeleted(snapshotIds),
})
}
func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
// Verify the snapshots are gone
client, _ := testAliyunClient()
snapshotResp, _, err := client.DescribeSnapshots(
&ecs.DescribeSnapshotsArgs{RegionId: common.Region("cn-beijing"), SnapshotIds: snapshotIds},
)
if err != nil {
return fmt.Errorf("Query snapshot failed %v", err)
}
if len(snapshotResp) > 0 {
return fmt.Errorf("Snapshots weren't successfully deleted by `ecs_image_force_delete_snapshots`")
}
return nil
}
}
func checkECSImageSharing(uid string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// describe the image, get block devices with a snapshot
client, _ := testAliyunClient()
imageSharePermissionResponse, err := client.DescribeImageSharePermission(&ecs.ModifyImageSharePermissionArgs{
RegionId: "cn-beijing",
ImageId: artifact.AlicloudImages["cn-beijing"],
})
if err != nil {
return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) in ECS Image Sharing Test: %s", artifact, err)
}
if len(imageSharePermissionResponse.Accounts.Account) != 1 && imageSharePermissionResponse.Accounts.Account[0].AliyunId != uid {
return fmt.Errorf("share account is incorrect %d", len(imageSharePermissionResponse.Accounts.Account))
}
return nil
}
}
func checkRegionCopy(regions []string) builderT.TestCheckFunc {
return func(artifacts []packer.Artifact) error {
if len(artifacts) > 1 {
return fmt.Errorf("more than 1 artifact")
}
// Get the actual *Artifact pointer so we can access the AMIs directly
artifactRaw := artifacts[0]
artifact, ok := artifactRaw.(*Artifact)
if !ok {
return fmt.Errorf("unknown artifact: %#v", artifactRaw)
}
// Verify that we copied to only the regions given
regionSet := make(map[string]struct{})
for _, r := range regions {
regionSet[r] = struct{}{}
}
for r := range artifact.AlicloudImages {
if r == "cn-beijing" {
delete(regionSet, r)
continue
}
if _, ok := regionSet[r]; !ok {
return fmt.Errorf("unknown region: %s", r)
}
delete(regionSet, r)
}
if len(regionSet) > 0 {
return fmt.Errorf("didn't copy to: %#v", regionSet)
}
client, _ := testAliyunClient()
for key, value := range artifact.AlicloudImages {
client.WaitForImageReady(common.Region(key), value, 1800)
}
return nil
}
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" {
t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests")
}
if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" {
t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests")
}
}
func testAliyunClient() (*ecs.Client, error) {
access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"}
err := access.Config()
if err != nil {
return nil, err
}
client, err := access.Client()
if err != nil {
return nil, err
}
return client, nil
}
const testBuilderAccBasic = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"ssh_username": "ubuntu",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test_{{timestamp}}"
}]
}`
const testBuilderAccRegionCopy = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test_{{timestamp}}",
"image_copy_regions": ["cn-hangzhou", "cn-shenzhen"]
}]
}
`
const testBuilderAccForceDelete = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_force_delete": "%s",
"image_name": "packer-test_%s"
}]
}
`
const testBuilderAccForceDeleteSnapshot = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_force_delete_snapshots": "%s",
"image_force_delete": "%s",
"image_name": "packer-test-%s"
}]
}
`
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"io_optimized":"true",
"ssh_username":"root",
"image_name": "packer-test_{{timestamp}}",
"image_share_account":["1309208528360047"]
}]
}
`
func buildForceDeregisterConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDelete, val, name)
}
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
const testBuilderAccWindows = `
{ "builders": [{
"type": "test",
"region": "cn-beijing",
"instance_type": "ecs.n1.tiny",
"source_image":"win2008_64_ent_r2_zh-cn_40G_alibase_20170118.vhd",
"io_optimized":"true",
"image_force_delete":"true",
"communicator": "winrm",
"winrm_port": 5985,
"winrm_username": "Administrator",
"winrm_password": "Test1234",
"image_name": "packer-test_{{timestamp}}"
}]
}`

View File

@ -0,0 +1,95 @@
package ecs
import (
"testing"
"github.com/mitchellh/packer/packer"
)
func testBuilderConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_image": "foo",
"instance_type": "ecs.n1.tiny",
"region": "cn-beijing",
"ssh_username": "root",
"image_name": "foo",
"io_optimized": true,
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatalf("prepare should fail")
}
}
func TestBuilderPrepare_ECSImageName(t *testing.T) {
var b Builder
config := testBuilderConfig()
// Test good
config["image_name"] = "ecs.n1.tiny"
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["ecs_image_name"] = "foo {{"
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
// Test bad
delete(config, "image_name")
b = Builder{}
warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testBuilderConfig()
// 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")
}
}

View File

@ -0,0 +1,41 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepCheckAlicloudSourceImage struct {
SourceECSImageId string
}
func (s *stepCheckAlicloudSourceImage) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(Config)
ui := state.Get("ui").(packer.Ui)
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{RegionId: common.Region(config.AlicloudRegion), ImageId: config.AlicloudSourceImage})
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("No alicloud image was found matching filters: %v", config.AlicloudSourceImage)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Found image ID: %s", images[0].ImageId))
state.Put("source_image", &images[0])
return multistep.ActionContinue
}
func (s *stepCheckAlicloudSourceImage) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,75 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type setpConfigAlicloudEIP struct {
AssociatePublicIpAddress bool
RegionId string
allocatedId string
}
func (s *setpConfigAlicloudEIP) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
ui.Say("Start allocated alicloud eip")
ipaddress, allocateId, err := client.AllocateEipAddress(&ecs.AllocateEipAddressArgs{
RegionId: common.Region(s.RegionId),
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error allocate eip: %s", err))
return multistep.ActionHalt
}
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 allocate alicloud eip: %s", err))
return multistep.ActionHalt
}
if err = client.AssociateEipAddress(allocateId, instance.InstanceId); err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error binding alicloud eip: %s", err))
return multistep.ActionHalt
}
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 alicloud eip: %s", err))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Allocated alicloud eip %s", ipaddress))
state.Put("ipaddress", ipaddress)
return multistep.ActionContinue
}
func (s *setpConfigAlicloudEIP) Cleanup(state multistep.StateBag) {
if len(s.allocatedId) == 0 {
return
}
client := state.Get("client").(*ecs.Client)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
ui := state.Get("ui").(packer.Ui)
message(state, "EIP")
if err := client.UnassociateEipAddress(s.allocatedId, instance.InstanceId); err != nil {
ui.Say(fmt.Sprintf("Unassociate alicloud eip failed "))
}
if err := client.WaitForEip(common.Region(s.RegionId), s.allocatedId, ecs.EipStatusAvailable, ALICLOUD_DEFAULT_SHORT_TIMEOUT); err != nil {
ui.Say(fmt.Sprintf("Unassociate alicloud eip timeout "))
}
if err := client.ReleaseEipAddress(s.allocatedId); err != nil {
ui.Say(fmt.Sprintf("Release alicloud eip failed "))
}
}

View File

@ -0,0 +1,65 @@
package ecs
import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
)
type StepConfigAlicloudKeyPair struct {
Debug bool
TemporaryKeyPairName string
KeyPairName string
PrivateKeyFile string
PublicKeyFile string
keyName string
}
func (s *StepConfigAlicloudKeyPair) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.PrivateKeyFile != "" {
ui.Say("Using existing SSH private key")
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
if err != nil {
state.Put("error", fmt.Errorf(
"Error loading configured private key file: %s", err))
return multistep.ActionHalt
}
if s.PublicKeyFile == "" {
s.PublicKeyFile = s.PrivateKeyFile + ".pub"
}
publicKeyBytes, err := ioutil.ReadFile(s.PublicKeyFile)
state.Put("keyPair", s.KeyPairName)
state.Put("privateKey", string(privateKeyBytes))
state.Put("publickKey", string(publicKeyBytes))
return multistep.ActionContinue
}
if s.TemporaryKeyPairName == "" {
ui.Say("Not using temporary keypair")
state.Put("keyPair", "")
return multistep.ActionContinue
}
keyPair, err := NewKeyPair()
if err != nil {
ui.Say("create temporary keypair failed")
return multistep.ActionHalt
}
state.Put("keyPair", s.TemporaryKeyPairName)
if err != nil {
state.Put("error", fmt.Errorf(
"Error loading configured private key file: %s", err))
return multistep.ActionHalt
}
state.Put("privateKey", keyPair.PrivateKey)
state.Put("publickKey", keyPair.PublicKey)
return multistep.ActionContinue
}
func (s *StepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,34 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepConfigAlicloudPublicIP struct {
publicIPAdress string
RegionId string
}
func (s *stepConfigAlicloudPublicIP) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
ipaddress, err := client.AllocatePublicIpAddress(instance.InstanceId)
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Error allocate public ip: %s", err))
return multistep.ActionHalt
}
s.publicIPAdress = ipaddress
ui.Say(fmt.Sprintf("allocated public ip address %s", ipaddress))
state.Put("ipaddress", ipaddress)
return multistep.ActionContinue
}
func (s *stepConfigAlicloudPublicIP) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,136 @@
package ecs
import (
"errors"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
type stepConfigAlicloudSecurityGroup struct {
SecurityGroupId string
SecurityGroupName string
Description string
VpcId string
RegionId string
isCreate bool
}
func (s *stepConfigAlicloudSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
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 {
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),
})
}
if err != nil {
ui.Say(fmt.Sprintf("Query alicloud security grouip failed: %s", err))
state.Put("error", err)
return multistep.ActionHalt
}
for _, securityGroupItem := range securityGroupItems {
if securityGroupItem.SecurityGroupId == s.SecurityGroupId {
state.Put("securitygroupid", s.SecurityGroupId)
s.isCreate = false
return multistep.ActionContinue
}
}
s.isCreate = false
message := fmt.Sprintf("The specific security group {%s} isn't exist.", s.SecurityGroupId)
state.Put("error", errors.New(message))
ui.Say(message)
return multistep.ActionHalt
}
var securityGroupId string
ui.Say("Start 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,
})
}
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Create security group failed %v", err))
return multistep.ActionHalt
}
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("authorzie security group failed %v", err))
return multistep.ActionHalt
}
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("authorzie security group failed %v", err))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
if !s.isCreate {
return
}
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
message(state, "security group")
start := time.Now().Add(10 * 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(start) {
time.Sleep(1 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Error delete security group failed, may still be around: %s", err))
return
}
break
}
}

View File

@ -0,0 +1,93 @@
package ecs
import (
"errors"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
type stepConfigAlicloudVPC struct {
VpcId string
CidrBlock string //192.168.0.0/16 or 172.16.0.0/16 (default)
VpcName string
isCreate bool
}
func (s *stepConfigAlicloudVPC) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(Config)
client := state.Get("client").(*ecs.Client)
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),
})
if err != nil {
ui.Say(fmt.Sprintf("Query vpcs failed: %s", err))
state.Put("error", err)
return multistep.ActionHalt
}
if len(vpcs) > 0 {
vpc := vpcs[0]
state.Put("vpcid", vpc.VpcId)
s.isCreate = false
return multistep.ActionContinue
}
message := fmt.Sprintf("The specific vpc {%s} isn't exist.", s.VpcId)
state.Put("error", errors.New(message))
ui.Say(message)
return multistep.ActionHalt
}
ui.Say("Start create alicloud vpc")
vpc, err := client.CreateVpc(&ecs.CreateVpcArgs{
RegionId: common.Region(config.AlicloudRegion),
CidrBlock: s.CidrBlock,
VpcName: s.VpcName,
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Create vps failed %v", 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("Waiting vps avabalibe failed %v", err))
return multistep.ActionHalt
}
state.Put("vpcid", vpc.VpcId)
s.isCreate = true
s.VpcId = vpc.VpcId
return multistep.ActionContinue
}
func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
if !s.isCreate {
return
}
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
message(state, "VPC")
start := time.Now().Add(10 * 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") && time.Now().Before(start) {
time.Sleep(1 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Error delete vpc, may still be around: %s", err))
return
}
break
}
}

View File

@ -0,0 +1,145 @@
package ecs
import (
"errors"
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
type stepConfigAlicloudVSwithc struct {
VSwitchId string
ZoneId string
isCreate bool
CidrBlock string
VSwitchName string
}
func (s *stepConfigAlicloudVSwithc) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
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,
})
if err != nil {
ui.Say(fmt.Sprintf("Query vswitch failed: %s", err))
state.Put("error", err)
return multistep.ActionHalt
}
if len(vswitchs) > 0 {
vswitch := vswitchs[0]
state.Put("vswitchid", vswitch.VSwitchId)
s.isCreate = false
return multistep.ActionContinue
}
s.isCreate = false
message := fmt.Sprintf("The specific vswitch {%s} isn't exist.", s.VSwitchId)
state.Put("error", errors.New(message))
ui.Say(message)
return multistep.ActionHalt
}
if s.ZoneId == "" {
zones, err := client.DescribeZones(common.Region(config.AlicloudRegion))
if err != nil {
ui.Say(fmt.Sprintf("Query avaiable zone failed: %s", err))
state.Put("error", err)
return multistep.ActionHalt
}
var instanceTypes []string
for _, zone := range zones {
isVSwitchSupported := false
for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes {
if resourceType == ecs.ResourceTypeVSwitch {
isVSwitchSupported = true
}
}
if isVSwitchSupported {
for _, instanceType := range zone.AvailableInstanceTypes.InstanceTypes {
if instanceType == config.InstanceType {
s.ZoneId = zone.ZoneId
break
}
instanceTypes = append(instanceTypes, instanceType)
}
}
}
if s.ZoneId == "" {
if len(instanceTypes) > 0 {
ui.Say(fmt.Sprintf("The instance Type %s isn't avaiable in this Region."+
"\n You can either change the instance one of following %v \n"+
"or choose another region", config.InstanceType, instanceTypes))
state.Put("error", fmt.Errorf("The instance Type %s isn't avaiable in this Region."+
"\n You can either change the instance one of following %v \n"+
"or choose another region", config.InstanceType, instanceTypes))
return multistep.ActionHalt
} else {
ui.Say(fmt.Sprintf("The instance Type %s isn't avaiable in this Region."+
"\n You can change to other ragions \n", config.InstanceType))
state.Put("error", fmt.Errorf("The instance Type %s isn't avaiable in this Region."+
"\n You can change to other ragions \n", config.InstanceType))
return multistep.ActionHalt
}
}
}
if config.CidrBlock == "" {
s.CidrBlock = "172.16.0.0/24" //use the default CirdBlock
}
ui.Say("Start creating vswitch...")
vswitchId, err := client.CreateVSwitch(&ecs.CreateVSwitchArgs{
CidrBlock: s.CidrBlock,
ZoneId: s.ZoneId,
VpcId: vpcId,
VSwitchName: s.VSwitchName,
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Create vswitch failed %v", err))
return multistep.ActionHalt
}
if err := client.WaitForVSwitchAvailable(vpcId, s.VSwitchId, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
state.Put("error", err)
ui.Error(fmt.Sprintf("Error waiting vswitch avaiable timeout: %v", err))
return multistep.ActionHalt
}
state.Put("vswitchid", vswitchId)
s.isCreate = true
s.VSwitchId = vswitchId
return multistep.ActionContinue
}
func (s *stepConfigAlicloudVSwithc) Cleanup(state multistep.StateBag) {
if !s.isCreate {
return
}
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
message(state, "vSwitch")
start := 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 == "IncorretRouteEntryStatus") && time.Now().Before(start) {
time.Sleep(1 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Error delete vswitch, may still be around: %s", err))
return
}
break
}
}

View File

@ -0,0 +1,93 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepCreateAlicloudImage struct {
image *ecs.ImageType
}
func (s *stepCreateAlicloudImage) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(Config)
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
// Create the alicloud image
ui.Say(fmt.Sprintf("Creating alicloud images: %s", config.AlicloudImageName))
var imageId string
var err error
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})
if err != nil {
err := fmt.Errorf("Error create alicloud images: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
err = client.WaitForImageReady(common.Region(config.AlicloudRegion),
imageId, ALICLOUD_DEFAULT_LONG_TIMEOUT)
if err != nil {
err := fmt.Errorf("Waiting alicloud image creating timeout: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(config.AlicloudRegion),
ImageId: imageId})
if err != nil {
err := fmt.Errorf("Query created alicloud image failed: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(images) == 0 {
err := fmt.Errorf("Query created alicloud image failed: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.image = &images[0]
state.Put("alicloudimage", imageId)
alicloudImages := make(map[string]string)
alicloudImages[config.AlicloudRegion] = images[0].ImageId
state.Put("alicloudimages", alicloudImages)
return multistep.ActionContinue
}
func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) {
if s.image == nil {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if !cancelled && !halted {
return
}
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(Config)
ui.Say("Delete the alicloud image because cancelation or error...")
if err := client.DeleteImage(common.Region(config.AlicloudRegion), s.image.ImageId); err != nil {
ui.Error(fmt.Sprintf("Error delete alicloud image, may still be around: %s", err))
return
}
}

View File

@ -0,0 +1,198 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"io/ioutil"
"log"
"strings"
)
const (
shebang = "#!/bin/sh\n"
key_function = `
export ssh_directory="/root/.ssh"
export authorized_keys=${ssh_directory}/authorized_keys
if [ -f "${authorized_keys}" ] ; then
isKeyExist=$(egrep -v "${user_public_key}" "${authorized_keys}")
if [ ! "${isKeyExist}" ] ; then
echo ${user_public_key} >> ${ssh_directory}/authorized_keys
fi
else
if [ ! -d "${ssh_directory}" ] ; then
mkdir -p ${ssh_directory}
fi
echo ${user_public_key} >> ${ssh_directory}/authorized_keys
fi
`
)
type stepCreateAlicloudInstance struct {
IOOptimized bool
InstanceType string
UserData string
UserDataFile string
instanceId string
RegionId string
InternetChargeType string
InternetMaxBandwidthOut int
InstnaceName string
ZoneId string
instance *ecs.InstanceAttributesType
}
func (s *stepCreateAlicloudInstance) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(Config)
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("Start creating alicloud 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.InstnaceName,
Password: password,
ZoneId: s.ZoneId,
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
})
if err != nil {
err := fmt.Errorf("Error create alicloud 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.InstnaceName,
Password: password,
ZoneId: s.ZoneId,
DataDisk: diskDeviceToDiskType(config.AlicloudImageConfig.ECSImagesDiskMappings),
})
if err != nil {
err := fmt.Errorf("Error create instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
err = client.WaitForInstance(instanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
if err != nil {
err := fmt.Errorf("Error creating instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instance, err := client.DescribeInstanceAttribute(instanceId)
if err != nil {
ui.Say(fmt.Sprint(err))
return multistep.ActionHalt
}
s.instance = instance
state.Put("instance", instance)
return multistep.ActionContinue
}
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("Clean instance %s failed ", s.instance.InstanceId))
}
}
func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (string, error) {
config := state.Get("config").(Config)
publicKey := ""
if publickey_temp, ok := state.GetOk("publickKey"); ok {
publicKey = publickey_temp.(string)
publicKey = strings.TrimRight(publicKey, "\n")
}
userData := s.UserData
if s.UserDataFile != "" {
data, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
return "", err
}
userData = string(data)
}
if config.Comm.SSHPrivateKey != "" || config.TemporaryKeyPairName != "" {
if userData == "" {
userData = shebang
} else {
userData = userData + "\n\n"
}
userData = userData + "export user_public_key=\"" + publicKey + "\""
userData = userData + key_function
}
log.Printf(userData)
return userData, nil
}
func diskDeviceToDiskType(diskDevices []AlicloudDiskDevice) []ecs.DataDiskType {
result := make([]ecs.DataDiskType, len(diskDevices))
for _, diskDevice := range diskDevices {
result = append(result, ecs.DataDiskType{
DiskName: diskDevice.DiskName,
Category: ecs.DiskCategory(diskDevice.DiskCategory),
Size: diskDevice.DiskSize,
SnapshotId: diskDevice.SnapshotId,
Description: diskDevice.Description,
DeleteWithInstance: diskDevice.DeleteWithInstance,
Device: diskDevice.Device,
})
}
return result
}

View File

@ -0,0 +1,62 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type stepDeleteAlicloudImageSnapshots struct {
AlicloudImageForceDetele bool
AlicloudImageForceDeteleSnapshots bool
AlicloudImageName string
}
func (s *stepDeleteAlicloudImageSnapshots) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(Config)
ui.Say("Start delete alicloud image snapshots")
// Check for force delete
if s.AlicloudImageForceDetele {
images, _, err := client.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: common.Region(config.AlicloudRegion),
ImageName: s.AlicloudImageName,
})
if len(images) < 1 {
return multistep.ActionContinue
}
for _, image := range images {
if image.ImageOwnerAlias != string(ecs.ImageOwnerSelf) {
log.Printf("Only can delete the instance based on customized images %s ", image.ImageId)
continue
}
err = client.DeleteImage(common.Region(config.AlicloudRegion), image.ImageId)
if err != nil {
err := fmt.Errorf("Delete alicloud image failed: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.AlicloudImageForceDeteleSnapshots {
for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping {
if err := client.DeleteSnapshot(diskDevice.SnapshotId); err != nil {
err := fmt.Errorf("Delete ECS snapshots failed: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
}
}
return multistep.ActionContinue
}
func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) {
}

View File

@ -0,0 +1,69 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepMountAlicloudDisk struct {
}
func (s *stepMountAlicloudDisk) Run(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("Start mounting Disks")
disks, _, err := client.DescribeDisks(&ecs.DescribeDisksArgs{InstanceId: instance.InstanceId, RegionId: instance.RegionId})
if err != nil {
err := fmt.Errorf("Error query alicloud disks failed: %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 mount alicloud disks failed: %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("Error waiting mount alicloud disks failed: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
ui.Say("Finish mounting Disks")
return multistep.ActionContinue
}
func (s *stepMountAlicloudDisk) Cleanup(state multistep.StateBag) {
}
func getDevice(disk *ecs.DiskItemType, diskDevices []AlicloudDiskDevice) string {
if disk.Device != "" {
return disk.Device
}
for _, alicloudDiskDevice := range diskDevices {
if alicloudDiskDevice.DiskName == disk.DiskName || alicloudDiskDevice.SnapshotId == disk.SourceSnapshotId {
return alicloudDiskDevice.Device
}
}
return ""
}

View File

@ -0,0 +1,47 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepPreValidate struct {
AlicloudDestImageName string
ForceDelete bool
}
func (s *stepPreValidate) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.ForceDelete {
ui.Say("Force delete flag found, skipping prevalidating alicloud image name")
return multistep.ActionContinue
}
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(Config)
ui.Say("Prevalidating alicloud 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: name conflicts with an existing alicloud image: %s", images[0].ImageId)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}

View File

@ -0,0 +1,73 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type setpRegionCopyAlicloudImage struct {
AlicloudImageDestinationRegions []string
AlicloudImageDestinationNames []string
RegionId string
}
func (s *setpRegionCopyAlicloudImage) Run(state multistep.StateBag) multistep.StepAction {
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)
numberOfName := len(s.AlicloudImageDestinationNames)
for index, destinationRegion := range s.AlicloudImageDestinationRegions {
if destinationRegion == s.RegionId {
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 copy alicloud images: %s", err))
return multistep.ActionHalt
}
alicloudImages[destinationRegion] = imageId
}
return multistep.ActionContinue
}
func (s *setpRegionCopyAlicloudImage) 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("Cancel copy image because cancelation or error..."))
for copyedRegionId, copyedImageId := range alicloudImages {
if copyedRegionId == s.RegionId {
continue
}
if err := client.CancelCopyImage(common.Region(copyedRegionId), copyedImageId); err != nil {
ui.Say(fmt.Sprintf("Cancel copy image has error %v", err))
}
}
}
}

View File

@ -0,0 +1,54 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepRunAlicloudInstance struct {
}
func (s *stepRunAlicloudInstance) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
err := client.StartInstance(instance.InstanceId)
if err != nil {
err := fmt.Errorf("Error start alicloud instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Alcoud instance starting")
err = client.WaitForInstance(instance.InstanceId, ecs.Running, ALICLOUD_DEFAULT_TIMEOUT)
if err != nil {
err := fmt.Errorf("Starting alicloud instance timeout: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
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)
instanceAttrubite, _ := client.DescribeInstanceAttribute(instance.InstanceId)
if instanceAttrubite.Status == ecs.Starting || instanceAttrubite.Status == ecs.Running {
if err := client.StopInstance(instance.InstanceId, true); err != nil {
ui.Say(fmt.Sprintf("Stop alicloud instance %s failed %v", instance.InstanceId, err))
return
}
err := client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
ui.Say(fmt.Sprintf("Stop alicloud instance %s failed %v", instance.InstanceId, err))
}
}
}

View File

@ -0,0 +1,59 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type setpShareAlicloudImage struct {
AlicloudImageShareAccounts []string
AlicloudImageUNShareAccounts []string
RegionId string
}
func (s *setpShareAlicloudImage) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
alicloudImages := state.Get("alicloudimages").(map[string]string)
for copyedRegion, copyedImageId := range alicloudImages {
err := client.ModifyImageSharePermission(
&ecs.ModifyImageSharePermissionArgs{
RegionId: common.Region(copyedRegion),
ImageId: copyedImageId,
AddAccount: s.AlicloudImageShareAccounts,
RemoveAccount: s.AlicloudImageUNShareAccounts,
})
if err != nil {
state.Put("error", err)
ui.Say(fmt.Sprintf("Modify alicloud image share permission failed: %s", err))
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *setpShareAlicloudImage) 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("Restore alicloud image share permission because cancelations or error...")
for copyedRegion, copyedImageId := range alicloudImages {
err := client.ModifyImageSharePermission(
&ecs.ModifyImageSharePermissionArgs{
RegionId: common.Region(copyedRegion),
ImageId: copyedImageId,
AddAccount: s.AlicloudImageUNShareAccounts,
RemoveAccount: s.AlicloudImageShareAccounts,
})
if err != nil {
ui.Say(fmt.Sprintf("Restore alicloud image share permission failed: %s", err))
}
}
}
}

View File

@ -0,0 +1,42 @@
package ecs
import (
"fmt"
"github.com/denverdino/aliyungo/ecs"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type stepStopAlicloudInstance struct {
ForceStop bool
}
func (s *stepStopAlicloudInstance) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
ui := state.Get("ui").(packer.Ui)
err := client.StopInstance(instance.InstanceId, s.ForceStop)
if err != nil {
if err != nil {
err := fmt.Errorf("Error stopping alicloud instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
err = client.WaitForInstance(instance.InstanceId, ecs.Stopped, ALICLOUD_DEFAULT_TIMEOUT)
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 multistep.ActionContinue
}
func (s *stepStopAlicloudInstance) Cleanup(multistep.StateBag) {
// No cleanup...
}

View File

@ -13,6 +13,7 @@ import (
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/packer/plugin"
alicloudecsbuilder "github.com/mitchellh/packer/builder/alicloud/ecs"
amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot"
amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs"
amazonebssurrogatebuilder "github.com/mitchellh/packer/builder/amazon/ebssurrogate"
@ -73,10 +74,11 @@ type PluginCommand struct {
}
var Builders = map[string]packer.Builder{
"alicloud-ecs": new(alicloudecsbuilder.Builder),
"amazon-chroot": new(amazonchrootbuilder.Builder),
"amazon-ebs": new(amazonebsbuilder.Builder),
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
"amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder),
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
"amazon-instance": new(amazoninstancebuilder.Builder),
"azure-arm": new(azurearmbuilder.Builder),
"cloudstack": new(cloudstackbuilder.Builder),

View File

@ -0,0 +1,24 @@
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_basic",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true"
}],
"provisioners": [{
"type": "shell",
"inline": [
"sleep 30",
"yum install redis.x86_64 -y"
]
}]
}

View File

@ -0,0 +1,25 @@
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_test",
"source_image":"win2008_64_ent_r2_zh-cn_40G_alibase_20170118.vhd",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
"image_force_delete":"true",
"communicator": "winrm",
"winrm_port": 5985,
"winrm_username": "Administrator",
"winrm_password": "Test1234"
}],
"provisioners": [{
"type": "powershell",
"inline": ["dir c:\\"]
}]
}

View File

@ -0,0 +1,25 @@
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_with_data_disk",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
"image_disk_mappings":[{"disk_name":"data1","disk_size":20},{"disk_name":"data1","disk_size":20,"disk_device":"/dev/xvdz"}]
}],
"provisioners": [{
"type": "shell",
"inline": [
"sleep 30",
"yum install redis.x86_64 -y"
]
}]
}

View File

@ -0,0 +1,33 @@
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_chef2",
"source_image":"ubuntu_14_0405_64_40G_base_20170222.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.medium",
"io_optimized":"true",
"image_force_delete":"true",
"ssh_password":"Test1234",
"user_data_file":"examples/alicloud/chef/user_data.sh"
}],
"provisioners": [{
"type": "file",
"source": "examples/alicloud/chef/chef.sh",
"destination": "/root/"
},{
"type": "shell",
"inline": [
"cd /root/",
"chmod 755 chef.sh",
"./chef.sh",
"chef-server-ctl reconfigure"
]
}]
}

View File

@ -0,0 +1,46 @@
#!/bin/sh
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
if [ not $HOSTNAME ] ; then
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
fi
CHEF_SERVER_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-server-core_12.8.0-1_amd64.deb'
CHEF_CONSOLE_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-manage_2.4.3-1_amd64.deb'
CHEF_SERVER_ADMIN='admin'
CHEF_SERVER_ADMIN_PASSWORD='vmADMIN123'
ORGANIZATION='aliyun'
ORGANIZATION_FULL_NAME='Aliyun, Inc'
#specify hostname
hostname $HOSTNAME
mkdir ~/.pemfile
#install chef server
wget $CHEF_SERVER_URL
sudo dpkg -i chef-server-core_*.deb
sudo chef-server-ctl reconfigure
#create admin user
sudo chef-server-ctl user-create $CHEF_SERVER_ADMIN $CHEF_SERVER_ADMIN $CHEF_SERVER_ADMIN 641002259@qq.com $CHEF_SERVER_ADMIN_PASSWORD -f ~/.pemfile/admin.pem
#create aliyun organization
sudo chef-server-ctl org-create $ORGANIZATION $ORGANIZATION_FULL_NAME --association_user $CHEF_SERVER_ADMIN -f ~/.pemfile/aliyun-validator.pem
#install chef management console
wget $CHEF_CONSOLE_URL
sudo dpkg -i chef-manage_*.deb
sudo chef-server-ctl reconfigure
type expect >/dev/null 2>&1 || { echo >&2 "Install Expect..."; apt-get -y install expect; }
echo "spawn sudo chef-manage-ctl reconfigure" >> chef-manage-confirm.exp
echo "expect \"*Press any key to continue\"" >> chef-manage-confirm.exp
echo "send \"a\\\n\"" >> chef-manage-confirm.exp
echo "expect \".*chef-manage 2.4.3 license: \\\"Chef-MLSA\\\".*\"" >> chef-manage-confirm.exp
echo "send \"q\"" >> chef-manage-confirm.exp
echo "expect \".*Type 'yes' to accept the software license agreement, or anything else to cancel.\"" >> chef-manage-confirm.exp
echo "send \"yes\\\n\"" >> chef-manage-confirm.exp
echo "interact" >> chef-manage-confirm.exp
expect chef-manage-confirm.exp
rm -f chef-manage-confirm.exp
#clean
rm -rf chef-manage_2.4.3-1_amd64.deb
rm -rf chef-server-core_12.8.0-1_amd64.deb

View File

@ -0,0 +1,6 @@
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
if [ not $HOSTNAME ] ; then
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
fi
hostname $HOSTNAME
chef-server-ctl reconfigure

View File

@ -0,0 +1,172 @@
---
description: |
layout: docs
page_title: Alicloud Image Builder
...
# Alicloud Image Builder
Type: `alicloud-ecs`
Alicloud Packer builder plugin provide the capability to build customized images based on the existing base images.
## Configuration Reference
The following configuration options are available for building Alicloud images. In addition to the options listed here, a
[communicator](/docs/templates/communicator.html) can be configured for this
builder.
### Required:
* `access_key` - (string) This is the Alicloud access key. It must be provided, but it can also be sourced from the `ALICLOUD_ACCESS_KEY` environment variable.
* `secret_key` - (string) This is the Alicloud secret key. It must be provided, but it can also be sourced from the `ALICLOUD_SECRET_KEY` environment variable.
* `region` - (string) This is the Alicloud region. It must be provided, but it can also be sourced from the `ALICLOUD_REGION` environment variables.
* `instance_type` - (string) Type of the instance. For values, see [Instance Type Table](). You can also obtain the latest instance type table by invoking the [Querying Instance Type Table](https://intl.aliyun.com/help/doc-detail/25620.htm?spm=a3c0i.o25499en.a3.6.Dr1bik) interface..
* `image_name` - (string) The name of the user-defined image, [2, 128] English or Chinese characters. It must begin with an uppercase/lowercase letter or a Chinese character, and may contain numbers, "_" or "-".It cannot begin with http:// or https://.
* `source_image` - (string) This is the base image id which you want to create your customized images.
### Optional:
- `skip_region_validation` (bool) The region validation can be skipped if this value is true, the default value is false.
- `image_description` (string) The description of the image, with a length limit of 0 to 256 characters. Leaving it blank means null, which is the default value.It cannot begin with http:// or https://.
- `image_version` (string) The version number of the image, with a length limit of 1 to 40 English characters.
- `image_share_account` (array of string) The IDs of to-be-added Aliyun accounts to which the image is shared. The number of accounts is 1 to 10. If number of accounts is greater than 10, this parameter is ignored.
- `image_copy_regions` (array of string) Copy to the destination regionIds.
- `image_copy_names` (array of string) The name of the destination image, [2, 128] English or Chinese characters. It must begin with an uppercase/lowercase letter or a Chinese character, and may contain numbers, "_" or "-".It cannot begin with http:// or https://.
- `image_force_delete` (bool) If this value is true, when the target image name is duplicated with an existing image, it will delete the existing image and then create the target image, otherwise, the creation will fail. The default value is false.
- `image_force_delete_snapshots` (bool) If this value is true, when delete the duplicated existing image, the source snapshot of this image will be delete either.
- `disk_name` (string) The value of disk name is blank by default. [2, 128] English or Chinese characters, must begin with an uppercase/lowercase letter or Chinese character. Can contain numbers, “.”, “_“ and “-“. The disk name will appear on the console.It cannot begin with http:// or https://.
- `disk_category` (string) Category of the data disk. Optional values are:
<ul>
<li>cloud - general cloud disk
<li>cloud_efficiency - efficiency cloud disk
<li>cloud_ssd - cloud SSD
</ul>
Default value:cloud.
- `disk_size` (int) Size of the system disk, in GB, values range:
<ul>
<li>cloud - 5 ~ 2000
<li>cloud_efficiency - 20 ~ 2048
<li>cloud_ssd - 20 ~ 2048
</ul>
The value should be equal to or greater than the size of the specific SnapshotId.
- `disk_snapshot_id` (string) Snapshots are used to create the data disk
After this parameter is specified, Size is ignored. The actual size of the created disk is the size of the specified snapshot
<br/>Snapshots from on or before July 15, 2013 cannot be used to create a disk
- `disk_description` (string) The value of disk description is blank by default. [2, 256] characters. The disk description will appear on the console.It cannot begin with http:// or https://.
- `disk_delete_with_instance` (string) Whether or not the disk is released along with the instance:
<ul>
<li>True indicates that when the instance is released, this disk will be released with it
<li>False indicates that when the instance is released, this disk will be retained.
</ul>
- `disk_device` (string) Device information of the related instance: such as /dev/xvdb
It is null unless the Status is In_use.
- `zone_id` (string) ID of the zone to which the disk belongs.
- `io_optimized` (string) I/O optimized. Optional values are:
<ul>
<li>none: none I/O Optimized
<li>optimized: I/O Optimized
</ul>
Default value: none for Generation I instances; optimized for other instances..
- `force_stop_instance` (bool) Whether to force shutdown upon device restart. Value:
<ul>
<li>true
<li>false
</ul>
The default value is “false”.<br>
If it is set to “false”, the system is shut down normally; if it is set to “true”, the system is forced to shut down..
- `security_group_id` (string) ID of the security group to which a newly created instance belongs. Mutual access is allowed between instances in one security group. If not specified, the newly created instance will be added to the default security group. If the default group doesnt exist, or the number of instances in it has reached the maximum limit, a new security group will be created automatically.
- `security_group_name` (string) The security group name. The default value is blank. [2, 128] English or Chinese characters, must begin with an uppercase/lowercase letter or Chinese character. Can contain numbers, ".", "_" or "-". It cannot begin with http:// or https://.
- `user_data` (string) The UserData of an instance must be encoded in Base64 format, and the maximum size of the raw data is 16 KB.
- `user_data_file` (string) The file name of the userdata.
- `vpc_id` (string) VPC ID allocated by the system.
- `vpc_name` (string) The VPC name. The default value is blank. [2, 128] English or Chinese characters, must begin with an uppercase/lowercase letter or Chinese character. Can contain numbers, "_" and "-". The disk description will appear on the console. Cannot begin with http:// or https://.
- `vpc_cidr_block` (string) Value options: 192.168.0.0/16 and 172.16.0.0/16. When not specified, the default value is 172.16.0.0/16.
- `vswitch_id` (string) The ID of the VSwitch to be used.
- `instance_name` (string) Display name of the instance, which is a string of 2 to 128 Chinese or English characters. It must begin with an uppercase/lowercase letter or a Chinese character and can contain numerals, “.”, “_“, or “-“. The instance name is displayed on the Alibaba Cloud console. If this parameter is not specified, the default value is InstanceId of the instance. It cannot begin with http:// or https://.
- `internet_charge_type` (string) Internet charge type, which can be PayByTraffic or PayByBandwidth.Optional values:
<ul>
<li>PayByBandwidth
<li>PayByTraffic
</ul>
If this parameter is not specified, the default value is PayByBandwidth.
- `internet_max_bandwith_out` (string) Maximum outgoing bandwidth to the public network, measured in Mbps (Mega bit per second).
<br>Value range:
<br>PayByBandwidth: [0, 100]. If this parameter is not specified, API automatically sets it to 0 Mbps.
<br>PayByTraffic: [1, 100]. If this parameter is not specified, an error is returned.
- `temporary_key_pair_name` (string) The name of the temporary key pair to generate. By default, Packer generates a name that looks like packer_<UUID>, where <UUID> is a 36 character unique identifier.
## Basic Example
Here is a basic example for Alicloud.
``` {.javascript}
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_test2",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
"image_force_delete":"true"
}],
"provisioners": [{
"type": "shell",
"inline": [
"sleep 30",
"yum install redis.x86_64 -y"
]
}]
}
```
See the [examples/alicloud](https://github.com/mitchellh/packer/tree/master/examples/alicloud) folder in the packer project
for more examples.

View File

@ -35,6 +35,7 @@
<li>
<h4>Builders</h4>
</li>
<li><a href="/docs/builders/alicloud.html">Alicloud Image</a></li>
<li><a href="/docs/builders/amazon.html">Amazon EC2 (AMI)</a></li>
<li><a href="/docs/builders/azure-arm.html">Azure Resource Manager</a></li>
<li><a href="/docs/builders/cloudstack.html">CloudStack</a></li>