├── AUTHORS ├── .gitignore ├── examples └── alicloud │ ├── chef │ ├── user_data.sh │ ├── alicloud.json │ └── chef.sh │ ├── basic │ ├── alicloud.json │ ├── alicloud_with_data_disk.json │ ├── alicloud_windows.json │ └── winrm_enable_userdata.ps1 │ ├── jenkins │ ├── alicloud.json │ └── jenkins.sh │ └── local │ ├── http │ └── centos-6.9 │ │ └── ks.cfg │ └── centos.json ├── main.go ├── ecs ├── ssh_helper.go ├── access_config_test.go ├── artifact_test.go ├── packer_helper.go ├── step_config_public_ip.go ├── step_stop_instance.go ├── image_config_test.go ├── step_check_source_image.go ├── step_create_tags.go ├── step_share_image.go ├── client_test.go ├── step_pre_validate.go ├── step_attach_keypair.go ├── step_run_instance.go ├── run_config.go ├── step_create_snapshot.go ├── step_delete_images_snapshots.go ├── access_config.go ├── image_config.go ├── step_region_copy_image.go ├── step_config_key_pair.go ├── step_config_vpc.go ├── run_config_test.go ├── step_create_image.go ├── step_config_security_group.go ├── artifact.go ├── step_config_eip.go ├── step_create_instance.go ├── step_config_vswitch.go ├── builder_test.go ├── builder.go ├── client.go └── builder_acc_test.go ├── Makefile ├── CHANGELOG.md ├── README.md ├── LICENSE └── alicloud-import └── post-processor.go /AUTHORS: -------------------------------------------------------------------------------- 1 | # This file lists all individuals having contributed content to the repository. 2 | 3 | zhuzhih2017(dongxiao.zzh@alibaba-inc.com) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | bin/ 3 | *.bak 4 | /.gitignore.swp 5 | .DS_Store 6 | examples/.DS_Store 7 | examples/alicloud/.DS_Store 8 | release-tools 9 | release.sh 10 | .idea/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/alicloud/chef/user_data.sh: -------------------------------------------------------------------------------- 1 | HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1` 2 | if [ not $HOSTNAME ] ; then 3 | HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1` 4 | fi 5 | hostname $HOSTNAME 6 | chef-server-ctl reconfigure 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //Register alicloud packer builder plugin 4 | import ( 5 | alicloudimport "github.com/alibaba/packer-provider/alicloud-import" 6 | "github.com/alibaba/packer-provider/ecs" 7 | "github.com/hashicorp/packer/packer/plugin" 8 | ) 9 | 10 | func main() { 11 | server, err := plugin.Server() 12 | if err != nil { 13 | panic(err) 14 | } 15 | server.RegisterBuilder(new(ecs.Builder)) 16 | server.RegisterPostProcessor(new(alicloudimport.PostProcessor)) 17 | server.Serve() 18 | } 19 | -------------------------------------------------------------------------------- /ecs/ssh_helper.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/hashicorp/packer/helper/multistep" 7 | ) 8 | 9 | var ( 10 | // modified in tests 11 | sshHostSleepDuration = time.Second 12 | ) 13 | 14 | type alicloudSSHHelper interface { 15 | } 16 | 17 | // SSHHost returns a function that can be given to the SSH communicator 18 | func SSHHost(e alicloudSSHHelper, private bool) func(multistep.StateBag) (string, error) { 19 | return func(state multistep.StateBag) (string, error) { 20 | ipAddress := state.Get("ipaddress").(string) 21 | return ipAddress, nil 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/alicloud/basic/alicloud.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 4 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 5 | }, 6 | "builders": [{ 7 | "type":"alicloud-ecs", 8 | "access_key":"{{user `access_key`}}", 9 | "secret_key":"{{user `secret_key`}}", 10 | "region":"cn-beijing", 11 | "image_name":"packer_basic", 12 | "source_image":"centos_7_03_64_20G_alibase_20170818.vhd", 13 | "ssh_username":"root", 14 | "instance_type":"ecs.n1.tiny", 15 | "internet_charge_type":"PayByTraffic", 16 | "io_optimized":"true" 17 | }], 18 | "provisioners": [{ 19 | "type": "shell", 20 | "inline": [ 21 | "sleep 30", 22 | "yum install redis.x86_64 -y" 23 | ] 24 | }] 25 | } 26 | -------------------------------------------------------------------------------- /ecs/access_config_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func testAlicloudAccessConfig() *AlicloudAccessConfig { 9 | return &AlicloudAccessConfig{ 10 | AlicloudAccessKey: "ak", 11 | AlicloudSecretKey: "acs", 12 | } 13 | 14 | } 15 | 16 | func TestAlicloudAccessConfigPrepareRegion(t *testing.T) { 17 | c := testAlicloudAccessConfig() 18 | 19 | c.AlicloudRegion = "" 20 | if err := c.Prepare(nil); err == nil { 21 | t.Fatalf("should have err") 22 | } 23 | 24 | c.AlicloudRegion = "cn-beijing" 25 | if err := c.Prepare(nil); err != nil { 26 | t.Fatalf("shouldn't have err: %s", err) 27 | } 28 | 29 | os.Setenv("ALICLOUD_REGION", "cn-hangzhou") 30 | c.AlicloudRegion = "" 31 | if err := c.Prepare(nil); err != nil { 32 | t.Fatalf("shouldn't have err: %s", err) 33 | } 34 | 35 | c.AlicloudSkipValidation = false 36 | } 37 | -------------------------------------------------------------------------------- /examples/alicloud/basic/alicloud_with_data_disk.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 4 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 5 | }, 6 | "builders": [{ 7 | "type":"alicloud-ecs", 8 | "access_key":"{{user `access_key`}}", 9 | "secret_key":"{{user `secret_key`}}", 10 | "region":"cn-beijing", 11 | "image_name":"packer_with_data_disk", 12 | "source_image":"centos_7_03_64_20G_alibase_20170818.vhd", 13 | "ssh_username":"root", 14 | "instance_type":"ecs.n1.tiny", 15 | "internet_charge_type":"PayByTraffic", 16 | "io_optimized":"true", 17 | "image_disk_mappings":[{"disk_name":"data1","disk_size":20},{"disk_name":"data1","disk_size":20,"disk_device":"/dev/xvdz"}] 18 | }], 19 | "provisioners": [{ 20 | "type": "shell", 21 | "inline": [ 22 | "sleep 30", 23 | "yum install redis.x86_64 -y" 24 | ] 25 | }] 26 | } 27 | -------------------------------------------------------------------------------- /examples/alicloud/basic/alicloud_windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 4 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 5 | }, 6 | "builders": [{ 7 | "type":"alicloud-ecs", 8 | "access_key":"{{user `access_key`}}", 9 | "secret_key":"{{user `secret_key`}}", 10 | "region":"cn-beijing", 11 | "image_name":"packer_test", 12 | "source_image":"win2008r2_64_ent_sp1_zh-cn_40G_alibase_20170915.vhd", 13 | "instance_type":"ecs.n1.tiny", 14 | "io_optimized":"true", 15 | "internet_charge_type":"PayByTraffic", 16 | "image_force_delete":"true", 17 | "communicator": "winrm", 18 | "winrm_port": 5985, 19 | "winrm_username": "Administrator", 20 | "winrm_password": "Test1234", 21 | "user_data_file": "examples/alicloud/basic/winrm_enable_userdata.ps1" 22 | }], 23 | "provisioners": [{ 24 | "type": "powershell", 25 | "inline": ["dir c:\\"] 26 | }] 27 | } 28 | -------------------------------------------------------------------------------- /examples/alicloud/jenkins/alicloud.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 4 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 5 | }, 6 | "builders": [{ 7 | "type":"alicloud-ecs", 8 | "access_key":"{{user `access_key`}}", 9 | "secret_key":"{{user `secret_key`}}", 10 | "region":"cn-beijing", 11 | "image_name":"packer_jenkins", 12 | "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", 13 | "ssh_username":"root", 14 | "instance_type":"ecs.n1.medium", 15 | "io_optimized":"true", 16 | "internet_charge_type":"PayByTraffic", 17 | "image_force_delete":"true", 18 | "ssh_password":"Test12345" 19 | }], 20 | "provisioners": [{ 21 | "type": "file", 22 | "source": "examples/alicloud/jenkins/jenkins.sh", 23 | "destination": "/root/" 24 | },{ 25 | "type": "shell", 26 | "inline": [ 27 | "cd /root/", 28 | "chmod 755 jenkins.sh", 29 | "./jenkins.sh" 30 | ] 31 | }] 32 | } 33 | -------------------------------------------------------------------------------- /ecs/artifact_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/hashicorp/packer/packer" 8 | ) 9 | 10 | func TestArtifact_Impl(t *testing.T) { 11 | var _ packer.Artifact = new(Artifact) 12 | } 13 | 14 | func TestArtifactId(t *testing.T) { 15 | expected := `east:foo,west:bar` 16 | 17 | ecsImages := make(map[string]string) 18 | ecsImages["east"] = "foo" 19 | ecsImages["west"] = "bar" 20 | 21 | a := &Artifact{ 22 | AlicloudImages: ecsImages, 23 | } 24 | 25 | result := a.Id() 26 | if result != expected { 27 | t.Fatalf("bad: %s", result) 28 | } 29 | } 30 | 31 | func TestArtifactState_atlasMetadata(t *testing.T) { 32 | a := &Artifact{ 33 | AlicloudImages: map[string]string{ 34 | "east": "foo", 35 | "west": "bar", 36 | }, 37 | } 38 | 39 | actual := a.State("atlas.artifact.metadata") 40 | expected := map[string]string{ 41 | "region.east": "foo", 42 | "region.west": "bar", 43 | } 44 | if !reflect.DeepEqual(actual, expected) { 45 | t.Fatalf("bad: %#v", actual) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/alicloud/chef/alicloud.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 4 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 5 | }, 6 | "builders": [{ 7 | "type":"alicloud-ecs", 8 | "access_key":"{{user `access_key`}}", 9 | "secret_key":"{{user `secret_key`}}", 10 | "region":"cn-beijing", 11 | "image_name":"packer_chef2", 12 | "source_image":"ubuntu_18_04_64_20G_alibase_20190223.vhd", 13 | "ssh_username":"root", 14 | "instance_type":"ecs.n1.medium", 15 | "io_optimized":"true", 16 | "image_force_delete":"true", 17 | "internet_charge_type":"PayByTraffic", 18 | "ssh_password":"Test1234", 19 | "user_data_file":"examples/alicloud/chef/user_data.sh" 20 | }], 21 | "provisioners": [{ 22 | "type": "file", 23 | "source": "examples/alicloud/chef/chef.sh", 24 | "destination": "/root/" 25 | },{ 26 | "type": "shell", 27 | "inline": [ 28 | "cd /root/", 29 | "chmod 755 chef.sh", 30 | "./chef.sh", 31 | "chef-server-ctl reconfigure" 32 | ] 33 | }] 34 | } 35 | -------------------------------------------------------------------------------- /ecs/packer_helper.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/hashicorp/packer/helper/multistep" 8 | "github.com/hashicorp/packer/packer" 9 | ) 10 | 11 | func cleanUpMessage(state multistep.StateBag, module string) { 12 | _, cancelled := state.GetOk(multistep.StateCancelled) 13 | _, halted := state.GetOk(multistep.StateHalted) 14 | 15 | ui := state.Get("ui").(packer.Ui) 16 | 17 | if cancelled || halted { 18 | ui.Say(fmt.Sprintf("Deleting %s because of cancellation or error...", module)) 19 | } else { 20 | ui.Say(fmt.Sprintf("Cleaning up '%s'", module)) 21 | } 22 | } 23 | 24 | func halt(state multistep.StateBag, err error, prefix string) multistep.StepAction { 25 | ui := state.Get("ui").(packer.Ui) 26 | 27 | if prefix != "" { 28 | err = fmt.Errorf("%s: %s", prefix, err) 29 | } 30 | 31 | state.Put("error", err) 32 | ui.Error(err.Error()) 33 | return multistep.ActionHalt 34 | } 35 | 36 | func convertNumber(value int) string { 37 | if value <= 0 { 38 | return "" 39 | } 40 | 41 | return strconv.Itoa(value) 42 | } 43 | 44 | func ContainsInArray(arr []string, value string) bool { 45 | for _, item := range arr { 46 | if item == value { 47 | return true 48 | } 49 | } 50 | 51 | return false 52 | } 53 | -------------------------------------------------------------------------------- /examples/alicloud/basic/winrm_enable_userdata.ps1: -------------------------------------------------------------------------------- 1 | #powershell 2 | 3 | write-output "Running User Data Script" 4 | write-host "(host) Running User Data Script" 5 | 6 | Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore 7 | 8 | # Don't set this before Set-ExecutionPolicy as it throws an error 9 | $ErrorActionPreference = "stop" 10 | 11 | # Remove HTTP listener 12 | Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse 13 | 14 | # WinRM 15 | write-output "Setting up WinRM" 16 | write-host "(host) setting up WinRM" 17 | 18 | cmd.exe /c winrm quickconfig -q 19 | cmd.exe /c winrm quickconfig '-transport:http' 20 | cmd.exe /c winrm set "winrm/config" '@{MaxTimeoutms="1800000"}' 21 | cmd.exe /c winrm set "winrm/config/winrs" '@{MaxMemoryPerShellMB="10240"}' 22 | cmd.exe /c winrm set "winrm/config/service" '@{AllowUnencrypted="true"}' 23 | cmd.exe /c winrm set "winrm/config/client" '@{AllowUnencrypted="true"}' 24 | cmd.exe /c winrm set "winrm/config/service/auth" '@{Basic="true"}' 25 | cmd.exe /c winrm set "winrm/config/client/auth" '@{Basic="true"}' 26 | cmd.exe /c winrm set "winrm/config/service/auth" '@{CredSSP="true"}' 27 | cmd.exe /c winrm set "winrm/config/listener?Address=*+Transport=HTTP" '@{Port="5985"}' 28 | cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes 29 | cmd.exe /c netsh firewall add portopening TCP 5985 "Port 5985" 30 | cmd.exe /c net stop winrm 31 | cmd.exe /c sc config winrm start= auto 32 | cmd.exe /c net start winrm -------------------------------------------------------------------------------- /examples/alicloud/jenkins/jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | JENKINS_URL='http://mirrors.jenkins.io/war-stable/2.32.2/jenkins.war' 4 | 5 | TOMCAT_VERSION='7.0.77' 6 | TOMCAT_NAME="apache-tomcat-$TOMCAT_VERSION" 7 | TOMCAT_PACKAGE="$TOMCAT_NAME.tar.gz" 8 | TOMCAT_URL="http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v$TOMCAT_VERSION/bin/$TOMCAT_PACKAGE" 9 | TOMCAT_PATH="/opt/$TOMCAT_NAME" 10 | 11 | #install jdk 12 | if grep -Eqi "Ubuntu|Debian|Raspbian" /etc/issue || grep -Eq "Ubuntu|Debian|Raspbian" /etc/*-release; then 13 | sudo apt-get update -y 14 | sudo apt-get install -y openjdk-7-jdk 15 | elif grep -Eqi "CentOS|Fedora|Red Hat Enterprise Linux Server" /etc/issue || grep -Eq "CentOS|Fedora|Red Hat Enterprise Linux Server" /etc/*-release; then 16 | sudo yum update -y 17 | sudo yum install -y openjdk-7-jdk 18 | else 19 | echo "Unknown OS type." 20 | fi 21 | 22 | #install jenkins server 23 | mkdir ~/work 24 | cd ~/work 25 | 26 | #install tomcat 27 | wget $TOMCAT_URL 28 | tar -zxvf $TOMCAT_PACKAGE 29 | mv $TOMCAT_NAME /opt 30 | 31 | #install 32 | wget $JENKINS_URL 33 | mv jenkins.war $TOMCAT_PATH/webapps/ 34 | 35 | #set emvironment 36 | echo "TOMCAT_PATH=\"$TOMCAT_PATH\"">>/etc/profile 37 | echo "JENKINS_HOME=\"$TOMCAT_PATH/webapps/jenkins\"">>/etc/profile 38 | echo PATH="\"\$PATH:\$TOMCAT_PATH:\$JENKINS_HOME\"">>/etc/profile 39 | . /etc/profile 40 | 41 | #start tomcat & jenkins 42 | $TOMCAT_PATH/bin/startup.sh 43 | 44 | #set start on boot 45 | sed -i "/#!\/bin\/sh/a$TOMCAT_PATH/bin/startup.sh" /etc/rc.local 46 | 47 | #clean 48 | rm -rf ~/work 49 | -------------------------------------------------------------------------------- /ecs/step_config_public_ip.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 8 | "github.com/hashicorp/packer/helper/multistep" 9 | "github.com/hashicorp/packer/packer" 10 | ) 11 | 12 | type stepConfigAlicloudPublicIP struct { 13 | publicIPAddress string 14 | RegionId string 15 | SSHPrivateIp bool 16 | } 17 | 18 | func (s *stepConfigAlicloudPublicIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | client := state.Get("client").(*ClientWrapper) 20 | ui := state.Get("ui").(packer.Ui) 21 | instance := state.Get("instance").(*ecs.Instance) 22 | 23 | if s.SSHPrivateIp { 24 | ipaddress := instance.InnerIpAddress.IpAddress 25 | if len(ipaddress) == 0 { 26 | ui.Say("Failed to get private ip of instance") 27 | return multistep.ActionHalt 28 | } 29 | state.Put("ipaddress", ipaddress[0]) 30 | return multistep.ActionContinue 31 | } 32 | 33 | allocatePublicIpAddressRequest := ecs.CreateAllocatePublicIpAddressRequest() 34 | allocatePublicIpAddressRequest.InstanceId = instance.InstanceId 35 | ipaddress, err := client.AllocatePublicIpAddress(allocatePublicIpAddressRequest) 36 | if err != nil { 37 | return halt(state, err, "Error allocating public ip") 38 | } 39 | 40 | s.publicIPAddress = ipaddress.IpAddress 41 | ui.Say(fmt.Sprintf("Allocated public ip address %s.", ipaddress.IpAddress)) 42 | state.Put("ipaddress", ipaddress.IpAddress) 43 | return multistep.ActionContinue 44 | } 45 | 46 | func (s *stepConfigAlicloudPublicIP) Cleanup(state multistep.StateBag) { 47 | 48 | } 49 | -------------------------------------------------------------------------------- /examples/alicloud/local/http/centos-6.9/ks.cfg: -------------------------------------------------------------------------------- 1 | install 2 | cdrom 3 | lang en_US.UTF-8 4 | keyboard us 5 | network --bootproto=dhcp 6 | rootpw vagrant 7 | firewall --disabled 8 | selinux --permissive 9 | timezone UTC 10 | unsupported_hardware 11 | bootloader --location=mbr 12 | text 13 | skipx 14 | zerombr 15 | clearpart --all --initlabel 16 | autopart 17 | auth --enableshadow --passalgo=sha512 --kickstart 18 | firstboot --disabled 19 | reboot 20 | user --name=vagrant --plaintext --password vagrant 21 | key --skip 22 | 23 | %packages --nobase --ignoremissing --excludedocs 24 | # vagrant needs this to copy initial files via scp 25 | openssh-clients 26 | sudo 27 | kernel-headers 28 | kernel-devel 29 | gcc 30 | make 31 | perl 32 | wget 33 | nfs-utils 34 | -fprintd-pam 35 | -intltool 36 | 37 | # unnecessary firmware 38 | -aic94xx-firmware 39 | -atmel-firmware 40 | -b43-openfwwf 41 | -bfa-firmware 42 | -ipw2100-firmware 43 | -ipw2200-firmware 44 | -ivtv-firmware 45 | -iwl100-firmware 46 | -iwl1000-firmware 47 | -iwl3945-firmware 48 | -iwl4965-firmware 49 | -iwl5000-firmware 50 | -iwl5150-firmware 51 | -iwl6000-firmware 52 | -iwl6000g2a-firmware 53 | -iwl6050-firmware 54 | -libertas-usb8388-firmware 55 | -ql2100-firmware 56 | -ql2200-firmware 57 | -ql23xx-firmware 58 | -ql2400-firmware 59 | -ql2500-firmware 60 | -rt61pci-firmware 61 | -rt73usb-firmware 62 | -xorg-x11-drv-ati-firmware 63 | -zd1211-firmware 64 | 65 | %post 66 | # Force to set SELinux to a permissive mode 67 | sed -i -e 's/\(^SELINUX=\).*$/\1permissive/' /etc/selinux/config 68 | # sudo 69 | echo "%vagrant ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/vagrant 70 | -------------------------------------------------------------------------------- /ecs/step_stop_instance.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 9 | 10 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 11 | "github.com/hashicorp/packer/helper/multistep" 12 | "github.com/hashicorp/packer/packer" 13 | ) 14 | 15 | type stepStopAlicloudInstance struct { 16 | ForceStop bool 17 | DisableStop bool 18 | } 19 | 20 | func (s *stepStopAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 21 | client := state.Get("client").(*ClientWrapper) 22 | instance := state.Get("instance").(*ecs.Instance) 23 | ui := state.Get("ui").(packer.Ui) 24 | 25 | if !s.DisableStop { 26 | ui.Say(fmt.Sprintf("Stopping instance: %s", instance.InstanceId)) 27 | 28 | stopInstanceRequest := ecs.CreateStopInstanceRequest() 29 | stopInstanceRequest.InstanceId = instance.InstanceId 30 | stopInstanceRequest.ForceStop = requests.Boolean(strconv.FormatBool(s.ForceStop)) 31 | if _, err := client.StopInstance(stopInstanceRequest); err != nil { 32 | return halt(state, err, "Error stopping alicloud instance") 33 | } 34 | } 35 | 36 | ui.Say(fmt.Sprintf("Waiting instance stopped: %s", instance.InstanceId)) 37 | 38 | _, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped) 39 | if err != nil { 40 | return halt(state, err, "Error waiting for alicloud instance to stop") 41 | } 42 | 43 | return multistep.ActionContinue 44 | } 45 | 46 | func (s *stepStopAlicloudInstance) Cleanup(multistep.StateBag) { 47 | // No cleanup... 48 | } 49 | -------------------------------------------------------------------------------- /ecs/image_config_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func testAlicloudImageConfig() *AlicloudImageConfig { 8 | return &AlicloudImageConfig{ 9 | AlicloudImageName: "foo", 10 | } 11 | } 12 | 13 | func TestECSImageConfigPrepare_name(t *testing.T) { 14 | c := testAlicloudImageConfig() 15 | if err := c.Prepare(nil); err != nil { 16 | t.Fatalf("shouldn't have err: %s", err) 17 | } 18 | 19 | c.AlicloudImageName = "" 20 | if err := c.Prepare(nil); err == nil { 21 | t.Fatal("should have error") 22 | } 23 | } 24 | 25 | func TestAMIConfigPrepare_regions(t *testing.T) { 26 | c := testAlicloudImageConfig() 27 | c.AlicloudImageDestinationRegions = nil 28 | if err := c.Prepare(nil); err != nil { 29 | t.Fatalf("shouldn't have err: %s", err) 30 | } 31 | 32 | c.AlicloudImageDestinationRegions = []string{"cn-beijing", "cn-hangzhou", "eu-central-1"} 33 | if err := c.Prepare(nil); err != nil { 34 | t.Fatalf("bad: %s", err) 35 | } 36 | 37 | c.AlicloudImageDestinationRegions = nil 38 | c.AlicloudImageSkipRegionValidation = true 39 | if err := c.Prepare(nil); err != nil { 40 | t.Fatal("shouldn't have error") 41 | } 42 | c.AlicloudImageSkipRegionValidation = false 43 | } 44 | 45 | func TestECSImageConfigPrepare_imageTags(t *testing.T) { 46 | c := testAlicloudImageConfig() 47 | c.AlicloudImageTags = map[string]string{ 48 | "TagKey1": "TagValue1", 49 | "TagKey2": "TagValue2", 50 | } 51 | if err := c.Prepare(nil); len(err) != 0 { 52 | t.Fatalf("err: %s", err) 53 | } 54 | if len(c.AlicloudImageTags) != 2 || c.AlicloudImageTags["TagKey1"] != "TagValue1" || 55 | c.AlicloudImageTags["TagKey2"] != "TagValue2" { 56 | t.Fatalf("invalid value, expected: %s, actual: %s", map[string]string{ 57 | "TagKey1": "TagValue1", 58 | "TagKey2": "TagValue2", 59 | }, c.AlicloudImageTags) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ecs/step_check_source_image.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 8 | "github.com/hashicorp/packer/helper/multistep" 9 | "github.com/hashicorp/packer/packer" 10 | ) 11 | 12 | type stepCheckAlicloudSourceImage struct { 13 | SourceECSImageId string 14 | } 15 | 16 | func (s *stepCheckAlicloudSourceImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 17 | client := state.Get("client").(*ClientWrapper) 18 | config := state.Get("config").(*Config) 19 | ui := state.Get("ui").(packer.Ui) 20 | 21 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 22 | describeImagesRequest.RegionId = config.AlicloudRegion 23 | describeImagesRequest.ImageId = config.AlicloudSourceImage 24 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 25 | if err != nil { 26 | return halt(state, err, "Error querying alicloud image") 27 | } 28 | 29 | images := imagesResponse.Images.Image 30 | 31 | // Describe markerplace image 32 | describeImagesRequest.ImageOwnerAlias = "marketplace" 33 | marketImagesResponse, err := client.DescribeImages(describeImagesRequest) 34 | if err != nil { 35 | return halt(state, err, "Error querying alicloud marketplace image") 36 | } 37 | 38 | marketImages := marketImagesResponse.Images.Image 39 | if len(marketImages) > 0 { 40 | images = append(images, marketImages...) 41 | } 42 | 43 | if len(images) == 0 { 44 | err := fmt.Errorf("No alicloud image was found matching filters: %v", config.AlicloudSourceImage) 45 | return halt(state, err, "") 46 | } 47 | 48 | ui.Message(fmt.Sprintf("Found image ID: %s", images[0].ImageId)) 49 | 50 | state.Put("source_image", &images[0]) 51 | return multistep.ActionContinue 52 | } 53 | 54 | func (s *stepCheckAlicloudSourceImage) Cleanup(multistep.StateBag) {} 55 | -------------------------------------------------------------------------------- /ecs/step_create_tags.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 8 | "github.com/hashicorp/packer/helper/multistep" 9 | "github.com/hashicorp/packer/packer" 10 | ) 11 | 12 | type stepCreateTags struct { 13 | Tags map[string]string 14 | } 15 | 16 | func (s *stepCreateTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 17 | config := state.Get("config").(*Config) 18 | client := state.Get("client").(*ClientWrapper) 19 | ui := state.Get("ui").(packer.Ui) 20 | imageId := state.Get("alicloudimage").(string) 21 | snapshotIds := state.Get("alicloudsnapshots").([]string) 22 | 23 | if len(s.Tags) == 0 { 24 | return multistep.ActionContinue 25 | } 26 | 27 | ui.Say(fmt.Sprintf("Adding tags(%s) to image: %s", s.Tags, imageId)) 28 | 29 | var tags []ecs.AddTagsTag 30 | for key, value := range s.Tags { 31 | var tag ecs.AddTagsTag 32 | tag.Key = key 33 | tag.Value = value 34 | tags = append(tags, tag) 35 | } 36 | 37 | addTagsRequest := ecs.CreateAddTagsRequest() 38 | addTagsRequest.RegionId = config.AlicloudRegion 39 | addTagsRequest.ResourceId = imageId 40 | addTagsRequest.ResourceType = TagResourceImage 41 | addTagsRequest.Tag = &tags 42 | 43 | if _, err := client.AddTags(addTagsRequest); err != nil { 44 | return halt(state, err, "Error Adding tags to image") 45 | } 46 | 47 | for _, snapshotId := range snapshotIds { 48 | ui.Say(fmt.Sprintf("Adding tags(%s) to snapshot: %s", s.Tags, snapshotId)) 49 | addTagsRequest := ecs.CreateAddTagsRequest() 50 | 51 | addTagsRequest.RegionId = config.AlicloudRegion 52 | addTagsRequest.ResourceId = snapshotId 53 | addTagsRequest.ResourceType = TagResourceSnapshot 54 | addTagsRequest.Tag = &tags 55 | 56 | if _, err := client.AddTags(addTagsRequest); err != nil { 57 | return halt(state, err, "Error Adding tags to snapshot") 58 | } 59 | } 60 | 61 | return multistep.ActionContinue 62 | } 63 | func (s *stepCreateTags) Cleanup(state multistep.StateBag) { 64 | // Nothing need to do, tags will be cleaned when the resource is cleaned 65 | } 66 | -------------------------------------------------------------------------------- /examples/alicloud/chef/chef.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #if the related deb pkg not found, please replace with it other avaiable repository url 3 | HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1` 4 | if [ not $HOSTNAME ] ; then 5 | HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1` 6 | fi 7 | CHEF_SERVER_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-server-core_12.8.0-1_amd64.deb' 8 | CHEF_CONSOLE_URL='http://dubbo.oss-cn-shenzhen.aliyuncs.com/chef-manage_2.4.3-1_amd64.deb' 9 | CHEF_SERVER_ADMIN='admin' 10 | CHEF_SERVER_ADMIN_PASSWORD='vmADMIN123' 11 | ORGANIZATION='aliyun' 12 | ORGANIZATION_FULL_NAME='Aliyun, Inc' 13 | #specify hostname 14 | hostname $HOSTNAME 15 | 16 | mkdir ~/.pemfile 17 | #install chef server 18 | wget $CHEF_SERVER_URL 19 | sudo dpkg -i chef-server-core_*.deb 20 | sudo chef-server-ctl reconfigure 21 | 22 | #create admin user 23 | 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 24 | 25 | #create aliyun organization 26 | sudo chef-server-ctl org-create $ORGANIZATION $ORGANIZATION_FULL_NAME --association_user $CHEF_SERVER_ADMIN -f ~/.pemfile/aliyun-validator.pem 27 | 28 | #install chef management console 29 | wget $CHEF_CONSOLE_URL 30 | sudo dpkg -i chef-manage_*.deb 31 | sudo chef-server-ctl reconfigure 32 | 33 | type expect >/dev/null 2>&1 || { echo >&2 "Install Expect..."; apt-get -y install expect; } 34 | echo "spawn sudo chef-manage-ctl reconfigure" >> chef-manage-confirm.exp 35 | echo "expect \"*Press any key to continue\"" >> chef-manage-confirm.exp 36 | echo "send \"a\\\n\"" >> chef-manage-confirm.exp 37 | echo "expect \".*chef-manage 2.4.3 license: \\\"Chef-MLSA\\\".*\"" >> chef-manage-confirm.exp 38 | echo "send \"q\"" >> chef-manage-confirm.exp 39 | echo "expect \".*Type 'yes' to accept the software license agreement, or anything else to cancel.\"" >> chef-manage-confirm.exp 40 | echo "send \"yes\\\n\"" >> chef-manage-confirm.exp 41 | echo "interact" >> chef-manage-confirm.exp 42 | expect chef-manage-confirm.exp 43 | rm -f chef-manage-confirm.exp 44 | 45 | #clean 46 | rm -rf chef-manage_2.4.3-1_amd64.deb 47 | rm -rf chef-server-core_12.8.0-1_amd64.deb 48 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOFMT_FILES?=$$(find . -name '*.go' | grep -v vendor) 2 | 3 | all: clean fmt deps build 4 | 5 | build: mac windows linux 6 | 7 | dev: clean fmt deps mac move 8 | 9 | move: 10 | tar -xvf bin/packer-builder-alicloud-ecs_darwin-amd64.tgz && mv bin/packer-builder-alicloud-ecs $(shell dirname `which packer`) 11 | 12 | test: 13 | PACKER_ACC=1 go test -v ./ecs -timeout 120m 14 | 15 | vet: 16 | @echo "go tool vet $(VETARGS) ." 17 | @go tool vet $(VETARGS) $$(ls -d */ | grep -v vendor) ; if [ $$? -eq 1 ]; then \ 18 | echo ""; \ 19 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 20 | echo "and fix them if necessary before submitting the code for review."; \ 21 | exit 1; \ 22 | fi 23 | 24 | fmt: 25 | gofmt -w $(GOFMT_FILES) 26 | goimports -w $(GOFMT_FILES) 27 | 28 | fmtcheck: 29 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" 30 | 31 | 32 | deps: 33 | go get -u github.com/kardianos/govendor 34 | govendor sync 35 | go get golang.org/x/crypto/curve25519 36 | go get golang.org/x/crypto/ed25519 37 | 38 | 39 | mac: deps 40 | GOOS=darwin GOARCH=amd64 go build -o bin/packer-builder-alicloud-ecs 41 | GOOS=darwin GOARCH=amd64 go build -o bin/packer-post-processor-alicloud-import 42 | tar czvf bin/packer-builder-alicloud-ecs_darwin-amd64.tgz bin/packer-builder-alicloud-ecs bin/packer-post-processor-alicloud-import 43 | rm -rf bin/packer-provider-alicloud-ecs bin/packer-post-processor-alicloud-import 44 | 45 | windows: deps 46 | GOOS=windows GOARCH=amd64 go build -o bin/packer-builder-alicloud-ecs.exe 47 | GOOS=windows GOARCH=amd64 go build -o bin/packer-post-processor-alicloud-import.exe 48 | tar czvf bin/packer-builder-alicloud-ecs_windows-amd64.tgz bin/packer-builder-alicloud-ecs.exe bin/packer-post-processor-alicloud-import.exe 49 | rm -rf bin/packer-builder-alicloud-ecs.exe bin/packer-post-processor-alicloud-import.exe 50 | 51 | linux: deps 52 | GOOS=linux GOARCH=amd64 go build -o bin/packer-builder-alicloud-ecs 53 | GOOS=linux GOARCH=amd64 go build -o bin/packer-post-processor-alicloud-import 54 | tar czvf bin/packer-builder-alicloud-ecs_linux-amd64.tgz bin/packer-builder-alicloud-ecs bin/packer-post-processor-alicloud-import 55 | rm -rf bin/packer-builder-alicloud-ecs bin/packer-post-processor-alicloud-import 56 | 57 | clean: 58 | rm -rf bin/* -------------------------------------------------------------------------------- /ecs/step_share_image.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 8 | "github.com/hashicorp/packer/helper/multistep" 9 | "github.com/hashicorp/packer/packer" 10 | ) 11 | 12 | type stepShareAlicloudImage struct { 13 | AlicloudImageShareAccounts []string 14 | AlicloudImageUNShareAccounts []string 15 | RegionId string 16 | } 17 | 18 | func (s *stepShareAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 19 | client := state.Get("client").(*ClientWrapper) 20 | alicloudImages := state.Get("alicloudimages").(map[string]string) 21 | 22 | for regionId, imageId := range alicloudImages { 23 | modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest() 24 | modifyImageShareRequest.RegionId = regionId 25 | modifyImageShareRequest.ImageId = imageId 26 | modifyImageShareRequest.AddAccount = &s.AlicloudImageShareAccounts 27 | modifyImageShareRequest.RemoveAccount = &s.AlicloudImageUNShareAccounts 28 | 29 | if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil { 30 | return halt(state, err, "Failed modifying image share permissions") 31 | } 32 | } 33 | return multistep.ActionContinue 34 | } 35 | 36 | func (s *stepShareAlicloudImage) Cleanup(state multistep.StateBag) { 37 | _, cancelled := state.GetOk(multistep.StateCancelled) 38 | _, halted := state.GetOk(multistep.StateHalted) 39 | 40 | if !cancelled && !halted { 41 | return 42 | } 43 | 44 | ui := state.Get("ui").(packer.Ui) 45 | client := state.Get("client").(*ClientWrapper) 46 | alicloudImages := state.Get("alicloudimages").(map[string]string) 47 | 48 | ui.Say("Restoring image share permission because cancellations or error...") 49 | 50 | for regionId, imageId := range alicloudImages { 51 | modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest() 52 | modifyImageShareRequest.RegionId = regionId 53 | modifyImageShareRequest.ImageId = imageId 54 | modifyImageShareRequest.AddAccount = &s.AlicloudImageUNShareAccounts 55 | modifyImageShareRequest.RemoveAccount = &s.AlicloudImageShareAccounts 56 | if _, err := client.ModifyImageSharePermission(modifyImageShareRequest); err != nil { 57 | ui.Say(fmt.Sprintf("Restoring image share permission failed: %s", err)) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/alicloud/local/centos.json: -------------------------------------------------------------------------------- 1 | {"variables": { 2 | "box_basename": "centos-6.9", 3 | "build_timestamp": "{{isotime \"20060102150405\"}}", 4 | "cpus": "1", 5 | "disk_size": "4096", 6 | "git_revision": "__unknown_git_revision__", 7 | "headless": "", 8 | "http_proxy": "{{env `http_proxy`}}", 9 | "https_proxy": "{{env `https_proxy`}}", 10 | "iso_checksum_type": "md5", 11 | "iso_checksum": "af4a1640c0c6f348c6c41f1ea9e192a2", 12 | "iso_name": "CentOS-6.9-x86_64-minimal.iso", 13 | "ks_path": "centos-6.9/ks.cfg", 14 | "memory": "512", 15 | "metadata": "floppy/dummy_metadata.json", 16 | "mirror": "http://mirrors.aliyun.com/centos", 17 | "mirror_directory": "6.9/isos/x86_64", 18 | "name": "centos-6.9", 19 | "no_proxy": "{{env `no_proxy`}}", 20 | "template": "centos-6.9-x86_64", 21 | "version": "2.1.TIMESTAMP" 22 | }, 23 | "builders":[ 24 | { 25 | "boot_command": [ 26 | " text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/{{user `ks_path`}}" 27 | ], 28 | "boot_wait": "10s", 29 | "disk_size": "{{user `disk_size`}}", 30 | "headless": "{{ user `headless` }}", 31 | "http_directory": "http", 32 | "iso_checksum": "{{user `iso_checksum`}}", 33 | "iso_checksum_type": "{{user `iso_checksum_type`}}", 34 | "iso_url": "{{user `mirror`}}/{{user `mirror_directory`}}/{{user `iso_name`}}", 35 | "output_directory": "packer-{{user `template`}}-qemu", 36 | "shutdown_command": "echo 'vagrant'|sudo -S /sbin/halt -h -p", 37 | "ssh_password": "vagrant", 38 | "ssh_port": 22, 39 | "ssh_username": "root", 40 | "ssh_wait_timeout": "10000s", 41 | "type": "qemu", 42 | "vm_name": "{{ user `template` }}.raw", 43 | "net_device": "virtio-net", 44 | "disk_interface": "virtio", 45 | "format": "raw" 46 | } 47 | ], 48 | "provisioners": [{ 49 | "type": "shell", 50 | "inline": [ 51 | "sleep 30", 52 | "yum install cloud-util cloud-init -y" 53 | ] 54 | }], 55 | "post-processors":[ 56 | { 57 | "type":"alicloud-import", 58 | "oss_bucket_name": "packer", 59 | "image_name": "packer_import", 60 | "image_os_type": "linux", 61 | "image_platform": "CentOS", 62 | "image_architecture": "x86_64", 63 | "image_system_size": "40", 64 | "region":"cn-beijing" 65 | } 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /ecs/client_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 9 | ) 10 | 11 | func TestWaitForExpectedExceedRetryTimes(t *testing.T) { 12 | c := ClientWrapper{} 13 | 14 | iter := 0 15 | waitDone := make(chan bool, 1) 16 | 17 | go func() { 18 | _, _ = c.WaitForExpected(&WaitForExpectArgs{ 19 | RequestFunc: func() (responses.AcsResponse, error) { 20 | iter++ 21 | return nil, fmt.Errorf("test: let iteration %d failed", iter) 22 | }, 23 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 24 | if err != nil { 25 | fmt.Printf("need retry: %s\n", err) 26 | return WaitForExpectToRetry 27 | } 28 | 29 | return WaitForExpectSuccess 30 | }, 31 | }) 32 | 33 | waitDone <- true 34 | }() 35 | 36 | timeTolerance := 1 * time.Second 37 | select { 38 | case <-waitDone: 39 | if iter != defaultRetryTimes { 40 | t.Fatalf("WaitForExpected should terminate at the %d iterations", defaultRetryTimes) 41 | } 42 | case <-time.After(defaultRetryTimes*defaultRetryInterval + timeTolerance): 43 | t.Fatalf("WaitForExpected should terminate within %f seconds", (defaultRetryTimes*defaultRetryInterval + timeTolerance).Seconds()) 44 | } 45 | } 46 | 47 | func TestWaitForExpectedExceedRetryTimeout(t *testing.T) { 48 | c := ClientWrapper{} 49 | 50 | expectTimeout := 10 * time.Second 51 | iter := 0 52 | waitDone := make(chan bool, 1) 53 | 54 | go func() { 55 | _, _ = c.WaitForExpected(&WaitForExpectArgs{ 56 | RequestFunc: func() (responses.AcsResponse, error) { 57 | iter++ 58 | return nil, fmt.Errorf("test: let iteration %d failed", iter) 59 | }, 60 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 61 | if err != nil { 62 | fmt.Printf("need retry: %s\n", err) 63 | return WaitForExpectToRetry 64 | } 65 | 66 | return WaitForExpectSuccess 67 | }, 68 | RetryTimeout: expectTimeout, 69 | }) 70 | 71 | waitDone <- true 72 | }() 73 | 74 | timeTolerance := 1 * time.Second 75 | select { 76 | case <-waitDone: 77 | if iter > int(expectTimeout/defaultRetryInterval) { 78 | t.Fatalf("WaitForExpected should terminate before the %d iterations", int(expectTimeout/defaultRetryInterval)) 79 | } 80 | case <-time.After(expectTimeout + timeTolerance): 81 | t.Fatalf("WaitForExpected should terminate within %f seconds", (expectTimeout + timeTolerance).Seconds()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ecs/step_pre_validate.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 8 | "github.com/hashicorp/packer/helper/multistep" 9 | "github.com/hashicorp/packer/packer" 10 | ) 11 | 12 | type stepPreValidate struct { 13 | AlicloudDestImageName string 14 | ForceDelete bool 15 | } 16 | 17 | func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 18 | if err := s.validateRegions(state); err != nil { 19 | return halt(state, err, "") 20 | } 21 | 22 | if err := s.validateDestImageName(state); err != nil { 23 | return halt(state, err, "") 24 | } 25 | 26 | return multistep.ActionContinue 27 | } 28 | 29 | func (s *stepPreValidate) validateRegions(state multistep.StateBag) error { 30 | ui := state.Get("ui").(packer.Ui) 31 | config := state.Get("config").(*Config) 32 | 33 | if config.AlicloudSkipValidation { 34 | ui.Say("Skip region validation flag found, skipping prevalidating source region and copied regions.") 35 | return nil 36 | } 37 | 38 | ui.Say("Prevalidating source region and copied regions...") 39 | 40 | var errs *packer.MultiError 41 | if err := config.ValidateRegion(config.AlicloudRegion); err != nil { 42 | errs = packer.MultiErrorAppend(errs, err) 43 | } 44 | for _, region := range config.AlicloudImageDestinationRegions { 45 | if err := config.ValidateRegion(region); err != nil { 46 | errs = packer.MultiErrorAppend(errs, err) 47 | } 48 | } 49 | 50 | if errs != nil && len(errs.Errors) > 0 { 51 | return errs 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func (s *stepPreValidate) validateDestImageName(state multistep.StateBag) error { 58 | ui := state.Get("ui").(packer.Ui) 59 | client := state.Get("client").(*ClientWrapper) 60 | config := state.Get("config").(*Config) 61 | 62 | if s.ForceDelete { 63 | ui.Say("Force delete flag found, skipping prevalidating image name.") 64 | return nil 65 | } 66 | 67 | ui.Say("Prevalidating image name...") 68 | 69 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 70 | describeImagesRequest.RegionId = config.AlicloudRegion 71 | describeImagesRequest.ImageName = s.AlicloudDestImageName 72 | describeImagesRequest.Status = ImageStatusQueried 73 | 74 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 75 | if err != nil { 76 | return fmt.Errorf("Error querying alicloud image: %s", err) 77 | } 78 | 79 | images := imagesResponse.Images.Image 80 | if len(images) > 0 { 81 | return fmt.Errorf("Error: Image Name: '%s' is used by an existing alicloud image: %s", images[0].ImageName, images[0].ImageId) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func (s *stepPreValidate) Cleanup(multistep.StateBag) {} 88 | -------------------------------------------------------------------------------- /ecs/step_attach_keypair.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 8 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 9 | "github.com/hashicorp/packer/helper/multistep" 10 | "github.com/hashicorp/packer/packer" 11 | ) 12 | 13 | type stepAttachKeyPair struct { 14 | } 15 | 16 | var attachKeyPairNotRetryErrors = []string{ 17 | "MissingParameter", 18 | "DependencyViolation.WindowsInstance", 19 | "InvalidKeyPairName.NotFound", 20 | "InvalidRegionId.NotFound", 21 | } 22 | 23 | func (s *stepAttachKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 24 | ui := state.Get("ui").(packer.Ui) 25 | client := state.Get("client").(*ClientWrapper) 26 | config := state.Get("config").(*Config) 27 | instance := state.Get("instance").(*ecs.Instance) 28 | keyPairName := config.Comm.SSHKeyPairName 29 | if keyPairName == "" { 30 | return multistep.ActionContinue 31 | } 32 | 33 | _, err := client.WaitForExpected(&WaitForExpectArgs{ 34 | RequestFunc: func() (responses.AcsResponse, error) { 35 | request := ecs.CreateAttachKeyPairRequest() 36 | request.RegionId = config.AlicloudRegion 37 | request.KeyPairName = keyPairName 38 | request.InstanceIds = "[\"" + instance.InstanceId + "\"]" 39 | return client.AttachKeyPair(request) 40 | }, 41 | EvalFunc: client.EvalCouldRetryResponse(attachKeyPairNotRetryErrors, EvalNotRetryErrorType), 42 | }) 43 | 44 | if err != nil { 45 | return halt(state, err, fmt.Sprintf("Error attaching keypair %s to instance %s", keyPairName, instance.InstanceId)) 46 | } 47 | 48 | ui.Message(fmt.Sprintf("Attach keypair %s to instance: %s", keyPairName, instance.InstanceId)) 49 | return multistep.ActionContinue 50 | } 51 | 52 | func (s *stepAttachKeyPair) Cleanup(state multistep.StateBag) { 53 | client := state.Get("client").(*ClientWrapper) 54 | config := state.Get("config").(*Config) 55 | ui := state.Get("ui").(packer.Ui) 56 | instance := state.Get("instance").(*ecs.Instance) 57 | keyPairName := config.Comm.SSHKeyPairName 58 | if keyPairName == "" { 59 | return 60 | } 61 | 62 | detachKeyPairRequest := ecs.CreateDetachKeyPairRequest() 63 | detachKeyPairRequest.RegionId = config.AlicloudRegion 64 | detachKeyPairRequest.KeyPairName = keyPairName 65 | detachKeyPairRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId) 66 | _, err := client.DetachKeyPair(detachKeyPairRequest) 67 | if err != nil { 68 | err := fmt.Errorf("Error Detaching keypair %s to instance %s : %s", keyPairName, 69 | instance.InstanceId, err) 70 | state.Put("error", err) 71 | ui.Error(err.Error()) 72 | return 73 | } 74 | 75 | ui.Message(fmt.Sprintf("Detach keypair %s from instance: %s", keyPairName, instance.InstanceId)) 76 | 77 | } 78 | -------------------------------------------------------------------------------- /ecs/step_run_instance.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 8 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 9 | "github.com/hashicorp/packer/helper/multistep" 10 | "github.com/hashicorp/packer/packer" 11 | ) 12 | 13 | type stepRunAlicloudInstance struct { 14 | } 15 | 16 | func (s *stepRunAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 17 | client := state.Get("client").(*ClientWrapper) 18 | ui := state.Get("ui").(packer.Ui) 19 | instance := state.Get("instance").(*ecs.Instance) 20 | 21 | startInstanceRequest := ecs.CreateStartInstanceRequest() 22 | startInstanceRequest.InstanceId = instance.InstanceId 23 | if _, err := client.StartInstance(startInstanceRequest); err != nil { 24 | return halt(state, err, "Error starting instance") 25 | } 26 | 27 | ui.Say(fmt.Sprintf("Starting instance: %s", instance.InstanceId)) 28 | 29 | _, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusRunning) 30 | if err != nil { 31 | return halt(state, err, "Timeout waiting for instance to start") 32 | } 33 | 34 | return multistep.ActionContinue 35 | } 36 | 37 | func (s *stepRunAlicloudInstance) Cleanup(state multistep.StateBag) { 38 | _, cancelled := state.GetOk(multistep.StateCancelled) 39 | _, halted := state.GetOk(multistep.StateHalted) 40 | 41 | if !cancelled && !halted { 42 | return 43 | } 44 | 45 | ui := state.Get("ui").(packer.Ui) 46 | client := state.Get("client").(*ClientWrapper) 47 | instance := state.Get("instance").(*ecs.Instance) 48 | 49 | describeInstancesRequest := ecs.CreateDescribeInstancesRequest() 50 | describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instance.InstanceId) 51 | instancesResponse, _ := client.DescribeInstances(describeInstancesRequest) 52 | 53 | if len(instancesResponse.Instances.Instance) == 0 { 54 | return 55 | } 56 | 57 | instanceAttribute := instancesResponse.Instances.Instance[0] 58 | if instanceAttribute.Status == InstanceStatusStarting || instanceAttribute.Status == InstanceStatusRunning { 59 | stopInstanceRequest := ecs.CreateStopInstanceRequest() 60 | stopInstanceRequest.InstanceId = instance.InstanceId 61 | stopInstanceRequest.ForceStop = requests.NewBoolean(true) 62 | if _, err := client.StopInstance(stopInstanceRequest); err != nil { 63 | ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err)) 64 | return 65 | } 66 | 67 | _, err := client.WaitForInstanceStatus(instance.RegionId, instance.InstanceId, InstanceStatusStopped) 68 | if err != nil { 69 | ui.Say(fmt.Sprintf("Error stopping instance %s, it may still be around %s", instance.InstanceId, err)) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ecs/run_config.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/hashicorp/packer/common/uuid" 10 | "github.com/hashicorp/packer/helper/communicator" 11 | "github.com/hashicorp/packer/template/interpolate" 12 | ) 13 | 14 | type RunConfig struct { 15 | AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"` 16 | ZoneId string `mapstructure:"zone_id"` 17 | IOOptimized *bool `mapstructure:"io_optimized"` 18 | InstanceType string `mapstructure:"instance_type"` 19 | Description string `mapstructure:"description"` 20 | AlicloudSourceImage string `mapstructure:"source_image"` 21 | ForceStopInstance bool `mapstructure:"force_stop_instance"` 22 | DisableStopInstance bool `mapstructure:"disable_stop_instance"` 23 | SecurityGroupId string `mapstructure:"security_group_id"` 24 | SecurityGroupName string `mapstructure:"security_group_name"` 25 | UserData string `mapstructure:"user_data"` 26 | UserDataFile string `mapstructure:"user_data_file"` 27 | VpcId string `mapstructure:"vpc_id"` 28 | VpcName string `mapstructure:"vpc_name"` 29 | CidrBlock string `mapstructure:"vpc_cidr_block"` 30 | VSwitchId string `mapstructure:"vswitch_id"` 31 | VSwitchName string `mapstructure:"vswitch_id"` 32 | InstanceName string `mapstructure:"instance_name"` 33 | InternetChargeType string `mapstructure:"internet_charge_type"` 34 | InternetMaxBandwidthOut int `mapstructure:"internet_max_bandwidth_out"` 35 | WaitSnapshotReadyTimeout int `mapstructure:"wait_snapshot_ready_timeout"` 36 | 37 | // Communicator settings 38 | Comm communicator.Config `mapstructure:",squash"` 39 | SSHPrivateIp bool `mapstructure:"ssh_private_ip"` 40 | } 41 | 42 | func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { 43 | if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" && 44 | c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" && c.Comm.WinRMPassword == "" { 45 | 46 | c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) 47 | } 48 | 49 | // Validation 50 | errs := c.Comm.Prepare(ctx) 51 | if c.AlicloudSourceImage == "" { 52 | errs = append(errs, errors.New("A source_image must be specified")) 53 | } 54 | 55 | if strings.TrimSpace(c.AlicloudSourceImage) != c.AlicloudSourceImage { 56 | errs = append(errs, errors.New("The source_image can't include spaces")) 57 | } 58 | 59 | if c.InstanceType == "" { 60 | errs = append(errs, errors.New("An alicloud_instance_type must be specified")) 61 | } 62 | 63 | if c.UserData != "" && c.UserDataFile != "" { 64 | errs = append(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified.")) 65 | } else if c.UserDataFile != "" { 66 | if _, err := os.Stat(c.UserDataFile); err != nil { 67 | errs = append(errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile)) 68 | } 69 | } 70 | 71 | return errs 72 | } 73 | -------------------------------------------------------------------------------- /ecs/step_create_snapshot.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" 9 | 10 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 11 | "github.com/hashicorp/packer/helper/multistep" 12 | "github.com/hashicorp/packer/packer" 13 | ) 14 | 15 | type stepCreateAlicloudSnapshot struct { 16 | snapshot *ecs.Snapshot 17 | WaitSnapshotReadyTimeout int 18 | } 19 | 20 | func (s *stepCreateAlicloudSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 21 | config := state.Get("config").(*Config) 22 | client := state.Get("client").(*ClientWrapper) 23 | ui := state.Get("ui").(packer.Ui) 24 | instance := state.Get("instance").(*ecs.Instance) 25 | 26 | describeDisksRequest := ecs.CreateDescribeDisksRequest() 27 | describeDisksRequest.RegionId = config.AlicloudRegion 28 | describeDisksRequest.InstanceId = instance.InstanceId 29 | describeDisksRequest.DiskType = DiskTypeSystem 30 | disksResponse, err := client.DescribeDisks(describeDisksRequest) 31 | if err != nil { 32 | return halt(state, err, "Error describe disks") 33 | } 34 | 35 | disks := disksResponse.Disks.Disk 36 | if len(disks) == 0 { 37 | return halt(state, err, "Unable to find system disk of instance") 38 | } 39 | 40 | createSnapshotRequest := ecs.CreateCreateSnapshotRequest() 41 | createSnapshotRequest.DiskId = disks[0].DiskId 42 | snapshot, err := client.CreateSnapshot(createSnapshotRequest) 43 | if err != nil { 44 | return halt(state, err, "Error creating snapshot") 45 | } 46 | 47 | // Create the alicloud snapshot 48 | ui.Say(fmt.Sprintf("Creating snapshot from system disk %s: %s", disks[0].DiskId, snapshot.SnapshotId)) 49 | 50 | snapshotsResponse, err := client.WaitForSnapshotStatus(config.AlicloudRegion, snapshot.SnapshotId, SnapshotStatusAccomplished, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second) 51 | if err != nil { 52 | _, ok := err.(errors.Error) 53 | if ok { 54 | return halt(state, err, "Error querying created snapshot") 55 | } 56 | 57 | return halt(state, err, "Timeout waiting for snapshot to be created") 58 | } 59 | 60 | snapshots := snapshotsResponse.(*ecs.DescribeSnapshotsResponse).Snapshots.Snapshot 61 | if len(snapshots) == 0 { 62 | return halt(state, err, "Unable to find created snapshot") 63 | } 64 | 65 | s.snapshot = &snapshots[0] 66 | state.Put("alicloudsnapshot", snapshot.SnapshotId) 67 | return multistep.ActionContinue 68 | } 69 | 70 | func (s *stepCreateAlicloudSnapshot) Cleanup(state multistep.StateBag) { 71 | if s.snapshot == nil { 72 | return 73 | } 74 | _, cancelled := state.GetOk(multistep.StateCancelled) 75 | _, halted := state.GetOk(multistep.StateHalted) 76 | if !cancelled && !halted { 77 | return 78 | } 79 | 80 | client := state.Get("client").(*ClientWrapper) 81 | ui := state.Get("ui").(packer.Ui) 82 | 83 | ui.Say("Deleting the snapshot because of cancellation or error...") 84 | 85 | deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest() 86 | deleteSnapshotRequest.SnapshotId = s.snapshot.SnapshotId 87 | if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil { 88 | ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err)) 89 | return 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.1 (June 11, 2019) 2 | 3 | BUG FIXES: 4 | 5 | - fix describing snapshots issue when image_ignore_data_disks is provided ([#67](https://github.com/alibaba/packer-provider/pull/67)) 6 | 7 | ## 1.4.0 (April 25, 2019) 8 | 9 | IMPROVEMENTS: 10 | 11 | - update alicloud builder to use official SDK ([#62](https://github.com/alibaba/packer-provider/pull/62)) 12 | - support encryption with default service key ([#65](https://github.com/alibaba/packer-provider/pull/65)) 13 | 14 | ## 1.3.2 (April 11, 2019) 15 | 16 | IMPROVEMENTS: 17 | 18 | - update aliyun sdk to support eu-west-1 region ([#60](https://github.com/alibaba/packer-provider/pull/60)) 19 | 20 | ## 1.3.1 (December 14, 2018) 21 | 22 | IMPROVEMENTS: 23 | 24 | - Support `-force` option and copied images and snapshots if corresponding options are specified ([#57](https://github.com/alibaba/packer-provider/pull/57)) 25 | 26 | ## 1.3.0 (November 26, 2018) 27 | 28 | IMPROVEMENTS: 29 | 30 | - Support wait_snapshot_ready_timeout for much bigger disk ([#53](https://github.com/alibaba/packer-provider/pull/53)) 31 | - Apply tags to relevant snapshots ([#54](https://github.com/alibaba/packer-provider/pull/54)) 32 | - Update windows examples ([#55](https://github.com/alibaba/packer-provider/pull/55)) 33 | 34 | ## 1.2.5 (November 18, 2018) 35 | 36 | IMPROVEMENTS: 37 | 38 | - Support creating image without data disks ([#50](https://github.com/alibaba/packer-provider/pull/50)) 39 | 40 | ## 1.2.4 (October 31, 2018) 41 | 42 | IMPROVEMENTS: 43 | 44 | - add options for system disk properties ([#48](https://github.com/alibaba/packer-provider/pull/48)) 45 | 46 | ## 1.2.3 (September 28, 2018) 47 | 48 | IMPROVEMENTS: 49 | 50 | - Support disable_stop_instance option for some specific scenarios ([#45](https://github.com/alibaba/packer-provider/pull/45)) 51 | 52 | ## 1.2.2 (September 16, 2018) 53 | 54 | IMPROVEMENTS: 55 | 56 | - Support adding tags to image ([#43](https://github.com/alibaba/packer-provider/pull/43)) 57 | 58 | ## 1.2.1 (September 11, 2018) 59 | 60 | IMPROVEMENTS: 61 | 62 | - Support ssh with private ip address ([#42](https://github.com/alibaba/packer-provider/pull/42)) 63 | 64 | ## 1.2.0 (August 14, 2018) 65 | 66 | IMPROVEMENTS: 67 | 68 | - Support describing marketplace image ([#39](https://github.com/alibaba/packer-provider/pull/39)) 69 | - Sync with official packer ([#38](https://github.com/alibaba/packer-provider/pull/38)) 70 | 71 | ## 1.1.3 (August 3, 2017) 72 | 73 | IMPROVEMENTS: 74 | 75 | - add international site support ([#31](https://github.com/alibaba/packer-provider/pull/31)) 76 | 77 | ## 1.1.2 (July 20, 2017) 78 | 79 | IMPROVEMENTS: 80 | 81 | - Refactor the code and enhance the retry logic to reduce the timeout failure. 82 | 83 | ## 1.1.1 (May 24, 2017) 84 | 85 | BUG FIXES: 86 | 87 | - Fix the missing parameter paytype when allocate eip 88 | 89 | ## 1.1 (March 12, 2017) 90 | 91 | IMPROVEMENTS: 92 | 93 | - Add local image import function ([#16](https://github.com/alibaba/packer-provider/pull/16)) 94 | 95 | ## 1.0 (March 3, 2017) 96 | 97 | IMPROVEMENTS: 98 | 99 | - Add alicloud official announcement and orgnize the structure of samples ([#10](https://github.com/alibaba/packer-provider/pull/10)) 100 | -------------------------------------------------------------------------------- /ecs/step_delete_images_snapshots.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 9 | "github.com/hashicorp/packer/helper/multistep" 10 | "github.com/hashicorp/packer/packer" 11 | ) 12 | 13 | type stepDeleteAlicloudImageSnapshots struct { 14 | AlicloudImageForceDelete bool 15 | AlicloudImageForceDeleteSnapshots bool 16 | AlicloudImageName string 17 | AlicloudImageDestinationRegions []string 18 | AlicloudImageDestinationNames []string 19 | } 20 | 21 | func (s *stepDeleteAlicloudImageSnapshots) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 22 | config := state.Get("config").(*Config) 23 | 24 | // Check for force delete 25 | if s.AlicloudImageForceDelete { 26 | err := s.deleteImageAndSnapshots(state, s.AlicloudImageName, config.AlicloudRegion) 27 | if err != nil { 28 | return halt(state, err, "") 29 | } 30 | 31 | numberOfName := len(s.AlicloudImageDestinationNames) 32 | if numberOfName == 0 { 33 | return multistep.ActionContinue 34 | } 35 | 36 | for index, destinationRegion := range s.AlicloudImageDestinationRegions { 37 | if destinationRegion == config.AlicloudRegion { 38 | continue 39 | } 40 | 41 | if index < numberOfName { 42 | err = s.deleteImageAndSnapshots(state, s.AlicloudImageDestinationNames[index], destinationRegion) 43 | if err != nil { 44 | return halt(state, err, "") 45 | } 46 | } else { 47 | break 48 | } 49 | } 50 | } 51 | 52 | return multistep.ActionContinue 53 | } 54 | 55 | func (s *stepDeleteAlicloudImageSnapshots) deleteImageAndSnapshots(state multistep.StateBag, imageName string, region string) error { 56 | client := state.Get("client").(*ClientWrapper) 57 | ui := state.Get("ui").(packer.Ui) 58 | 59 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 60 | describeImagesRequest.RegionId = region 61 | describeImagesRequest.ImageName = imageName 62 | describeImagesRequest.Status = ImageStatusQueried 63 | imageResponse, _ := client.DescribeImages(describeImagesRequest) 64 | images := imageResponse.Images.Image 65 | if len(images) < 1 { 66 | return nil 67 | } 68 | 69 | ui.Say(fmt.Sprintf("Deleting duplicated image and snapshot in %s: %s", region, imageName)) 70 | 71 | for _, image := range images { 72 | if image.ImageOwnerAlias != ImageOwnerSelf { 73 | log.Printf("You can not delete non-customized images: %s ", image.ImageId) 74 | continue 75 | } 76 | 77 | deleteImageRequest := ecs.CreateDeleteImageRequest() 78 | deleteImageRequest.RegionId = region 79 | deleteImageRequest.ImageId = image.ImageId 80 | if _, err := client.DeleteImage(deleteImageRequest); err != nil { 81 | err := fmt.Errorf("Failed to delete image: %s", err) 82 | return err 83 | } 84 | 85 | if s.AlicloudImageForceDeleteSnapshots { 86 | for _, diskDevice := range image.DiskDeviceMappings.DiskDeviceMapping { 87 | deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest() 88 | deleteSnapshotRequest.SnapshotId = diskDevice.SnapshotId 89 | if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil { 90 | err := fmt.Errorf("Deleting ECS snapshot failed: %s", err) 91 | return err 92 | } 93 | } 94 | } 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func (s *stepDeleteAlicloudImageSnapshots) Cleanup(state multistep.StateBag) { 101 | } 102 | -------------------------------------------------------------------------------- /ecs/access_config.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 9 | "github.com/hashicorp/packer/template/interpolate" 10 | "github.com/hashicorp/packer/version" 11 | ) 12 | 13 | // Config of alicloud 14 | type AlicloudAccessConfig struct { 15 | AlicloudAccessKey string `mapstructure:"access_key"` 16 | AlicloudSecretKey string `mapstructure:"secret_key"` 17 | AlicloudRegion string `mapstructure:"region"` 18 | AlicloudSkipValidation bool `mapstructure:"skip_region_validation"` 19 | SecurityToken string `mapstructure:"security_token"` 20 | 21 | client *ClientWrapper 22 | } 23 | 24 | const Packer = "HashiCorp-Packer" 25 | const DefaultRequestReadTimeout = 10 * time.Second 26 | 27 | // Client for AlicloudClient 28 | func (c *AlicloudAccessConfig) Client() (*ClientWrapper, error) { 29 | if c.client != nil { 30 | return c.client, nil 31 | } 32 | if c.SecurityToken == "" { 33 | c.SecurityToken = os.Getenv("SECURITY_TOKEN") 34 | } 35 | 36 | client, err := ecs.NewClientWithStsToken(c.AlicloudRegion, c.AlicloudAccessKey, 37 | c.AlicloudSecretKey, c.SecurityToken) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | client.AppendUserAgent(Packer, version.FormattedVersion()) 43 | client.SetReadTimeout(DefaultRequestReadTimeout) 44 | c.client = &ClientWrapper{client} 45 | 46 | return c.client, nil 47 | } 48 | 49 | func (c *AlicloudAccessConfig) Prepare(ctx *interpolate.Context) []error { 50 | var errs []error 51 | if err := c.Config(); err != nil { 52 | errs = append(errs, err) 53 | } 54 | 55 | if c.AlicloudRegion == "" { 56 | c.AlicloudRegion = os.Getenv("ALICLOUD_REGION") 57 | } 58 | 59 | if c.AlicloudRegion == "" { 60 | errs = append(errs, fmt.Errorf("region option or ALICLOUD_REGION must be provided in template file or environment variables.")) 61 | } 62 | 63 | if len(errs) > 0 { 64 | return errs 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (c *AlicloudAccessConfig) Config() error { 71 | if c.AlicloudAccessKey == "" { 72 | c.AlicloudAccessKey = os.Getenv("ALICLOUD_ACCESS_KEY") 73 | } 74 | if c.AlicloudSecretKey == "" { 75 | c.AlicloudSecretKey = os.Getenv("ALICLOUD_SECRET_KEY") 76 | } 77 | if c.AlicloudAccessKey == "" || c.AlicloudSecretKey == "" { 78 | return fmt.Errorf("ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY must be set in template file or environment variables.") 79 | } 80 | return nil 81 | 82 | } 83 | 84 | func (c *AlicloudAccessConfig) ValidateRegion(region string) error { 85 | 86 | supportedRegions, err := c.getSupportedRegions() 87 | if err != nil { 88 | return err 89 | } 90 | 91 | for _, supportedRegion := range supportedRegions { 92 | if region == supportedRegion { 93 | return nil 94 | } 95 | } 96 | 97 | return fmt.Errorf("Not a valid alicloud region: %s", region) 98 | } 99 | 100 | func (c *AlicloudAccessConfig) getSupportedRegions() ([]string, error) { 101 | client, err := c.Client() 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | regionsRequest := ecs.CreateDescribeRegionsRequest() 107 | regionsResponse, err := client.DescribeRegions(regionsRequest) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | validRegions := make([]string, len(regionsResponse.Regions.Region)) 113 | for _, valid := range regionsResponse.Regions.Region { 114 | validRegions = append(validRegions, valid.RegionId) 115 | } 116 | 117 | return validRegions, nil 118 | } 119 | -------------------------------------------------------------------------------- /ecs/image_config.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/hashicorp/packer/template/interpolate" 9 | ) 10 | 11 | type AlicloudDiskDevice struct { 12 | DiskName string `mapstructure:"disk_name"` 13 | DiskCategory string `mapstructure:"disk_category"` 14 | DiskSize int `mapstructure:"disk_size"` 15 | SnapshotId string `mapstructure:"disk_snapshot_id"` 16 | Description string `mapstructure:"disk_description"` 17 | DeleteWithInstance bool `mapstructure:"disk_delete_with_instance"` 18 | Device string `mapstructure:"disk_device"` 19 | Encrypted *bool `mapstructure:"disk_encrypted"` 20 | } 21 | 22 | type AlicloudDiskDevices struct { 23 | ECSSystemDiskMapping AlicloudDiskDevice `mapstructure:"system_disk_mapping"` 24 | ECSImagesDiskMappings []AlicloudDiskDevice `mapstructure:"image_disk_mappings"` 25 | } 26 | 27 | type AlicloudImageConfig struct { 28 | AlicloudImageName string `mapstructure:"image_name"` 29 | AlicloudImageVersion string `mapstructure:"image_version"` 30 | AlicloudImageDescription string `mapstructure:"image_description"` 31 | AlicloudImageShareAccounts []string `mapstructure:"image_share_account"` 32 | AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account"` 33 | AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"` 34 | AlicloudImageDestinationNames []string `mapstructure:"image_copy_names"` 35 | ImageEncrypted *bool `mapstructure:"image_encrypted"` 36 | AlicloudImageForceDelete bool `mapstructure:"image_force_delete"` 37 | AlicloudImageForceDeleteSnapshots bool `mapstructure:"image_force_delete_snapshots"` 38 | AlicloudImageForceDeleteInstances bool `mapstructure:"image_force_delete_instances"` 39 | AlicloudImageIgnoreDataDisks bool `mapstructure:"image_ignore_data_disks"` 40 | AlicloudImageSkipRegionValidation bool `mapstructure:"skip_region_validation"` 41 | AlicloudImageTags map[string]string `mapstructure:"tags"` 42 | AlicloudDiskDevices `mapstructure:",squash"` 43 | } 44 | 45 | func (c *AlicloudImageConfig) Prepare(ctx *interpolate.Context) []error { 46 | var errs []error 47 | if c.AlicloudImageName == "" { 48 | errs = append(errs, fmt.Errorf("image_name must be specified")) 49 | } else if len(c.AlicloudImageName) < 2 || len(c.AlicloudImageName) > 128 { 50 | errs = append(errs, fmt.Errorf("image_name must less than 128 letters and more than 1 letters")) 51 | } else if strings.HasPrefix(c.AlicloudImageName, "http://") || 52 | strings.HasPrefix(c.AlicloudImageName, "https://") { 53 | errs = append(errs, fmt.Errorf("image_name can't start with 'http://' or 'https://'")) 54 | } 55 | reg := regexp.MustCompile("\\s+") 56 | if reg.FindString(c.AlicloudImageName) != "" { 57 | errs = append(errs, fmt.Errorf("image_name can't include spaces")) 58 | } 59 | 60 | if len(c.AlicloudImageDestinationRegions) > 0 { 61 | regionSet := make(map[string]struct{}) 62 | regions := make([]string, 0, len(c.AlicloudImageDestinationRegions)) 63 | 64 | for _, region := range c.AlicloudImageDestinationRegions { 65 | // If we already saw the region, then don't look again 66 | if _, ok := regionSet[region]; ok { 67 | continue 68 | } 69 | 70 | // Mark that we saw the region 71 | regionSet[region] = struct{}{} 72 | regions = append(regions, region) 73 | } 74 | 75 | c.AlicloudImageDestinationRegions = regions 76 | } 77 | 78 | if len(errs) > 0 { 79 | return errs 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /ecs/step_region_copy_image.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 9 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 10 | "github.com/hashicorp/packer/helper/multistep" 11 | "github.com/hashicorp/packer/packer" 12 | ) 13 | 14 | type stepRegionCopyAlicloudImage struct { 15 | AlicloudImageDestinationRegions []string 16 | AlicloudImageDestinationNames []string 17 | RegionId string 18 | } 19 | 20 | func (s *stepRegionCopyAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 21 | config := state.Get("config").(*Config) 22 | 23 | if config.ImageEncrypted != nil { 24 | s.AlicloudImageDestinationRegions = append(s.AlicloudImageDestinationRegions, s.RegionId) 25 | s.AlicloudImageDestinationNames = append(s.AlicloudImageDestinationNames, config.AlicloudImageName) 26 | } 27 | 28 | if len(s.AlicloudImageDestinationRegions) == 0 { 29 | return multistep.ActionContinue 30 | } 31 | 32 | client := state.Get("client").(*ClientWrapper) 33 | ui := state.Get("ui").(packer.Ui) 34 | 35 | srcImageId := state.Get("alicloudimage").(string) 36 | alicloudImages := state.Get("alicloudimages").(map[string]string) 37 | numberOfName := len(s.AlicloudImageDestinationNames) 38 | 39 | ui.Say(fmt.Sprintf("Coping image %s from %s...", srcImageId, s.RegionId)) 40 | for index, destinationRegion := range s.AlicloudImageDestinationRegions { 41 | if destinationRegion == s.RegionId && config.ImageEncrypted == nil { 42 | continue 43 | } 44 | 45 | ecsImageName := "" 46 | if numberOfName > 0 && index < numberOfName { 47 | ecsImageName = s.AlicloudImageDestinationNames[index] 48 | } 49 | 50 | copyImageRequest := ecs.CreateCopyImageRequest() 51 | copyImageRequest.RegionId = s.RegionId 52 | copyImageRequest.ImageId = srcImageId 53 | copyImageRequest.DestinationRegionId = destinationRegion 54 | copyImageRequest.DestinationImageName = ecsImageName 55 | if config.ImageEncrypted != nil { 56 | copyImageRequest.Encrypted = requests.NewBoolean(*config.ImageEncrypted) 57 | } 58 | 59 | imageResponse, err := client.CopyImage(copyImageRequest) 60 | if err != nil { 61 | return halt(state, err, "Error copying images") 62 | } 63 | 64 | alicloudImages[destinationRegion] = imageResponse.ImageId 65 | ui.Message(fmt.Sprintf("Copy image from %s(%s) to %s(%s)", s.RegionId, srcImageId, destinationRegion, imageResponse.ImageId)) 66 | } 67 | 68 | if config.ImageEncrypted != nil { 69 | if _, err := client.WaitForImageStatus(s.RegionId, alicloudImages[s.RegionId], ImageStatusAvailable, time.Duration(ALICLOUD_DEFAULT_LONG_TIMEOUT)*time.Second); err != nil { 70 | return halt(state, err, fmt.Sprintf("Timeout waiting image %s finish copying", alicloudImages[s.RegionId])) 71 | } 72 | } 73 | 74 | return multistep.ActionContinue 75 | } 76 | 77 | func (s *stepRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) { 78 | _, cancelled := state.GetOk(multistep.StateCancelled) 79 | _, halted := state.GetOk(multistep.StateHalted) 80 | 81 | if !cancelled && !halted { 82 | return 83 | } 84 | 85 | ui := state.Get("ui").(packer.Ui) 86 | ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error...")) 87 | 88 | client := state.Get("client").(*ClientWrapper) 89 | alicloudImages := state.Get("alicloudimages").(map[string]string) 90 | srcImageId := state.Get("alicloudimage").(string) 91 | 92 | for copiedRegionId, copiedImageId := range alicloudImages { 93 | if copiedImageId == srcImageId { 94 | continue 95 | } 96 | 97 | cancelCopyImageRequest := ecs.CreateCancelCopyImageRequest() 98 | cancelCopyImageRequest.RegionId = copiedRegionId 99 | cancelCopyImageRequest.ImageId = copiedImageId 100 | if _, err := client.CancelCopyImage(cancelCopyImageRequest); err != nil { 101 | 102 | ui.Error(fmt.Sprintf("Error cancelling copy image: %v", err)) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ecs/step_config_key_pair.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 10 | "github.com/hashicorp/packer/helper/communicator" 11 | "github.com/hashicorp/packer/helper/multistep" 12 | "github.com/hashicorp/packer/packer" 13 | ) 14 | 15 | type stepConfigAlicloudKeyPair struct { 16 | Debug bool 17 | Comm *communicator.Config 18 | DebugKeyPath string 19 | RegionId string 20 | 21 | keyName string 22 | } 23 | 24 | func (s *stepConfigAlicloudKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 25 | ui := state.Get("ui").(packer.Ui) 26 | 27 | if s.Comm.SSHPrivateKeyFile != "" { 28 | ui.Say("Using existing SSH private key") 29 | privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() 30 | if err != nil { 31 | state.Put("error", err) 32 | return multistep.ActionHalt 33 | } 34 | 35 | s.Comm.SSHPrivateKey = privateKeyBytes 36 | return multistep.ActionContinue 37 | } 38 | 39 | if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" { 40 | ui.Say("Using SSH Agent with key pair in source image") 41 | return multistep.ActionContinue 42 | } 43 | 44 | if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" { 45 | ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName)) 46 | return multistep.ActionContinue 47 | } 48 | 49 | if s.Comm.SSHTemporaryKeyPairName == "" { 50 | ui.Say("Not using temporary keypair") 51 | s.Comm.SSHKeyPairName = "" 52 | return multistep.ActionContinue 53 | } 54 | 55 | client := state.Get("client").(*ClientWrapper) 56 | ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName)) 57 | 58 | createKeyPairRequest := ecs.CreateCreateKeyPairRequest() 59 | createKeyPairRequest.RegionId = s.RegionId 60 | createKeyPairRequest.KeyPairName = s.Comm.SSHTemporaryKeyPairName 61 | keyResp, err := client.CreateKeyPair(createKeyPairRequest) 62 | if err != nil { 63 | return halt(state, err, "Error creating temporary keypair") 64 | } 65 | 66 | // Set the keyname so we know to delete it later 67 | s.keyName = s.Comm.SSHTemporaryKeyPairName 68 | 69 | // Set some state data for use in future steps 70 | s.Comm.SSHKeyPairName = s.keyName 71 | s.Comm.SSHPrivateKey = []byte(keyResp.PrivateKeyBody) 72 | 73 | // If we're in debug mode, output the private key to the working 74 | // directory. 75 | if s.Debug { 76 | ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath)) 77 | f, err := os.Create(s.DebugKeyPath) 78 | if err != nil { 79 | state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) 80 | return multistep.ActionHalt 81 | } 82 | defer f.Close() 83 | 84 | // Write the key out 85 | if _, err := f.Write([]byte(keyResp.PrivateKeyBody)); err != nil { 86 | state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) 87 | return multistep.ActionHalt 88 | } 89 | 90 | // Chmod it so that it is SSH ready 91 | if runtime.GOOS != "windows" { 92 | if err := f.Chmod(0600); err != nil { 93 | state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err)) 94 | return multistep.ActionHalt 95 | } 96 | } 97 | } 98 | 99 | return multistep.ActionContinue 100 | } 101 | 102 | func (s *stepConfigAlicloudKeyPair) Cleanup(state multistep.StateBag) { 103 | // If no key name is set, then we never created it, so just return 104 | // If we used an SSH private key file, do not go about deleting 105 | // keypairs 106 | if s.Comm.SSHPrivateKeyFile != "" || (s.Comm.SSHKeyPairName == "" && s.keyName == "") { 107 | return 108 | } 109 | 110 | client := state.Get("client").(*ClientWrapper) 111 | ui := state.Get("ui").(packer.Ui) 112 | 113 | // Remove the keypair 114 | ui.Say("Deleting temporary keypair...") 115 | 116 | deleteKeyPairsRequest := ecs.CreateDeleteKeyPairsRequest() 117 | deleteKeyPairsRequest.RegionId = s.RegionId 118 | deleteKeyPairsRequest.KeyPairNames = fmt.Sprintf("[\"%s\"]", s.keyName) 119 | _, err := client.DeleteKeyPairs(deleteKeyPairsRequest) 120 | if err != nil { 121 | ui.Error(fmt.Sprintf( 122 | "Error cleaning up keypair. Please delete the key manually: %s", s.keyName)) 123 | } 124 | 125 | // Also remove the physical key if we're debugging. 126 | if s.Debug { 127 | if err := os.Remove(s.DebugKeyPath); err != nil { 128 | ui.Error(fmt.Sprintf( 129 | "Error removing debug key '%s': %s", s.DebugKeyPath, err)) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ecs/step_config_vpc.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | errorsNew "errors" 6 | "fmt" 7 | 8 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 9 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 10 | "github.com/hashicorp/packer/common/uuid" 11 | "github.com/hashicorp/packer/helper/multistep" 12 | "github.com/hashicorp/packer/packer" 13 | ) 14 | 15 | type stepConfigAlicloudVPC struct { 16 | VpcId string 17 | CidrBlock string //192.168.0.0/16 or 172.16.0.0/16 (default) 18 | VpcName string 19 | isCreate bool 20 | } 21 | 22 | var createVpcRetryErrors = []string{ 23 | "TOKEN_PROCESSING", 24 | } 25 | 26 | var deleteVpcRetryErrors = []string{ 27 | "DependencyViolation.Instance", 28 | "DependencyViolation.RouteEntry", 29 | "DependencyViolation.VSwitch", 30 | "DependencyViolation.SecurityGroup", 31 | "Forbbiden", 32 | "TaskConflict", 33 | } 34 | 35 | func (s *stepConfigAlicloudVPC) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 36 | config := state.Get("config").(*Config) 37 | client := state.Get("client").(*ClientWrapper) 38 | ui := state.Get("ui").(packer.Ui) 39 | 40 | if len(s.VpcId) != 0 { 41 | describeVpcsRequest := ecs.CreateDescribeVpcsRequest() 42 | describeVpcsRequest.VpcId = s.VpcId 43 | describeVpcsRequest.RegionId = config.AlicloudRegion 44 | 45 | vpcsResponse, err := client.DescribeVpcs(describeVpcsRequest) 46 | if err != nil { 47 | return halt(state, err, "Failed querying vpcs") 48 | } 49 | 50 | vpcs := vpcsResponse.Vpcs.Vpc 51 | if len(vpcs) > 0 { 52 | state.Put("vpcid", vpcs[0].VpcId) 53 | s.isCreate = false 54 | return multistep.ActionContinue 55 | } 56 | 57 | message := fmt.Sprintf("The specified vpc {%s} doesn't exist.", s.VpcId) 58 | return halt(state, errorsNew.New(message), "") 59 | } 60 | 61 | ui.Say("Creating vpc...") 62 | 63 | createVpcRequest := s.buildCreateVpcRequest(state) 64 | createVpcResponse, err := client.WaitForExpected(&WaitForExpectArgs{ 65 | RequestFunc: func() (responses.AcsResponse, error) { 66 | return client.CreateVpc(createVpcRequest) 67 | }, 68 | EvalFunc: client.EvalCouldRetryResponse(createVpcRetryErrors, EvalRetryErrorType), 69 | }) 70 | if err != nil { 71 | return halt(state, err, "Failed creating vpc") 72 | } 73 | 74 | vpcId := createVpcResponse.(*ecs.CreateVpcResponse).VpcId 75 | _, err = client.WaitForExpected(&WaitForExpectArgs{ 76 | RequestFunc: func() (responses.AcsResponse, error) { 77 | request := ecs.CreateDescribeVpcsRequest() 78 | request.RegionId = config.AlicloudRegion 79 | request.VpcId = vpcId 80 | return client.DescribeVpcs(request) 81 | }, 82 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 83 | if err != nil { 84 | return WaitForExpectToRetry 85 | } 86 | 87 | vpcsResponse := response.(*ecs.DescribeVpcsResponse) 88 | vpcs := vpcsResponse.Vpcs.Vpc 89 | if len(vpcs) > 0 { 90 | for _, vpc := range vpcs { 91 | if vpc.Status == VpcStatusAvailable { 92 | return WaitForExpectSuccess 93 | } 94 | } 95 | } 96 | 97 | return WaitForExpectToRetry 98 | }, 99 | RetryTimes: shortRetryTimes, 100 | }) 101 | 102 | if err != nil { 103 | return halt(state, err, "Failed waiting for vpc to become available") 104 | } 105 | 106 | ui.Message(fmt.Sprintf("Created vpc: %s", vpcId)) 107 | state.Put("vpcid", vpcId) 108 | s.isCreate = true 109 | s.VpcId = vpcId 110 | return multistep.ActionContinue 111 | } 112 | 113 | func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) { 114 | if !s.isCreate { 115 | return 116 | } 117 | 118 | cleanUpMessage(state, "VPC") 119 | 120 | client := state.Get("client").(*ClientWrapper) 121 | ui := state.Get("ui").(packer.Ui) 122 | 123 | _, err := client.WaitForExpected(&WaitForExpectArgs{ 124 | RequestFunc: func() (responses.AcsResponse, error) { 125 | request := ecs.CreateDeleteVpcRequest() 126 | request.VpcId = s.VpcId 127 | return client.DeleteVpc(request) 128 | }, 129 | EvalFunc: client.EvalCouldRetryResponse(deleteVpcRetryErrors, EvalRetryErrorType), 130 | RetryTimes: shortRetryTimes, 131 | }) 132 | 133 | if err != nil { 134 | ui.Error(fmt.Sprintf("Error deleting vpc, it may still be around: %s", err)) 135 | } 136 | } 137 | 138 | func (s *stepConfigAlicloudVPC) buildCreateVpcRequest(state multistep.StateBag) *ecs.CreateVpcRequest { 139 | config := state.Get("config").(*Config) 140 | 141 | request := ecs.CreateCreateVpcRequest() 142 | request.ClientToken = uuid.TimeOrderedUUID() 143 | request.RegionId = config.AlicloudRegion 144 | request.CidrBlock = s.CidrBlock 145 | request.VpcName = s.VpcName 146 | 147 | return request 148 | } 149 | -------------------------------------------------------------------------------- /ecs/run_config_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/hashicorp/packer/helper/communicator" 9 | ) 10 | 11 | func testConfig() *RunConfig { 12 | return &RunConfig{ 13 | AlicloudSourceImage: "alicloud_images", 14 | InstanceType: "ecs.n1.tiny", 15 | Comm: communicator.Config{ 16 | SSHUsername: "alicloud", 17 | }, 18 | } 19 | } 20 | 21 | func TestRunConfigPrepare(t *testing.T) { 22 | c := testConfig() 23 | err := c.Prepare(nil) 24 | if len(err) > 0 { 25 | t.Fatalf("err: %s", err) 26 | } 27 | } 28 | 29 | func TestRunConfigPrepare_InstanceType(t *testing.T) { 30 | c := testConfig() 31 | c.InstanceType = "" 32 | if err := c.Prepare(nil); len(err) != 1 { 33 | t.Fatalf("err: %s", err) 34 | } 35 | } 36 | 37 | func TestRunConfigPrepare_SourceECSImage(t *testing.T) { 38 | c := testConfig() 39 | c.AlicloudSourceImage = "" 40 | if err := c.Prepare(nil); len(err) != 1 { 41 | t.Fatalf("err: %s", err) 42 | } 43 | } 44 | 45 | func TestRunConfigPrepare_SSHPort(t *testing.T) { 46 | c := testConfig() 47 | c.Comm.SSHPort = 0 48 | if err := c.Prepare(nil); len(err) != 0 { 49 | t.Fatalf("err: %s", err) 50 | } 51 | 52 | if c.Comm.SSHPort != 22 { 53 | t.Fatalf("invalid value: %d", c.Comm.SSHPort) 54 | } 55 | 56 | c.Comm.SSHPort = 44 57 | if err := c.Prepare(nil); len(err) != 0 { 58 | t.Fatalf("err: %s", err) 59 | } 60 | 61 | if c.Comm.SSHPort != 44 { 62 | t.Fatalf("invalid value: %d", c.Comm.SSHPort) 63 | } 64 | } 65 | 66 | func TestRunConfigPrepare_UserData(t *testing.T) { 67 | c := testConfig() 68 | tf, err := ioutil.TempFile("", "packer") 69 | if err != nil { 70 | t.Fatalf("err: %s", err) 71 | } 72 | defer os.Remove(tf.Name()) 73 | defer tf.Close() 74 | 75 | c.UserData = "foo" 76 | c.UserDataFile = tf.Name() 77 | if err := c.Prepare(nil); len(err) != 1 { 78 | t.Fatalf("err: %s", err) 79 | } 80 | } 81 | 82 | func TestRunConfigPrepare_UserDataFile(t *testing.T) { 83 | c := testConfig() 84 | if err := c.Prepare(nil); len(err) != 0 { 85 | t.Fatalf("err: %s", err) 86 | } 87 | 88 | c.UserDataFile = "idontexistidontthink" 89 | if err := c.Prepare(nil); len(err) != 1 { 90 | t.Fatalf("err: %s", err) 91 | } 92 | 93 | tf, err := ioutil.TempFile("", "packer") 94 | if err != nil { 95 | t.Fatalf("err: %s", err) 96 | } 97 | defer os.Remove(tf.Name()) 98 | defer tf.Close() 99 | 100 | c.UserDataFile = tf.Name() 101 | if err := c.Prepare(nil); len(err) != 0 { 102 | t.Fatalf("err: %s", err) 103 | } 104 | } 105 | 106 | func TestRunConfigPrepare_TemporaryKeyPairName(t *testing.T) { 107 | c := testConfig() 108 | c.Comm.SSHTemporaryKeyPairName = "" 109 | if err := c.Prepare(nil); len(err) != 0 { 110 | t.Fatalf("err: %s", err) 111 | } 112 | 113 | if c.Comm.SSHTemporaryKeyPairName == "" { 114 | t.Fatal("keypair name is empty") 115 | } 116 | 117 | c.Comm.SSHTemporaryKeyPairName = "ssh-key-123" 118 | if err := c.Prepare(nil); len(err) != 0 { 119 | t.Fatalf("err: %s", err) 120 | } 121 | 122 | if c.Comm.SSHTemporaryKeyPairName != "ssh-key-123" { 123 | t.Fatal("keypair name does not match") 124 | } 125 | } 126 | 127 | func TestRunConfigPrepare_SSHPrivateIp(t *testing.T) { 128 | c := testConfig() 129 | if err := c.Prepare(nil); len(err) != 0 { 130 | t.Fatalf("err: %s", err) 131 | } 132 | if c.SSHPrivateIp != false { 133 | t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp) 134 | } 135 | c.SSHPrivateIp = true 136 | if err := c.Prepare(nil); len(err) != 0 { 137 | t.Fatalf("err: %s", err) 138 | } 139 | if c.SSHPrivateIp != true { 140 | t.Fatalf("invalid value, expected: %t, actul: %t", true, c.SSHPrivateIp) 141 | } 142 | c.SSHPrivateIp = false 143 | if err := c.Prepare(nil); len(err) != 0 { 144 | t.Fatalf("err: %s", err) 145 | } 146 | if c.SSHPrivateIp != false { 147 | t.Fatalf("invalid value, expected: %t, actul: %t", false, c.SSHPrivateIp) 148 | } 149 | } 150 | 151 | func TestRunConfigPrepare_DisableStopInstance(t *testing.T) { 152 | c := testConfig() 153 | 154 | if err := c.Prepare(nil); len(err) != 0 { 155 | t.Fatalf("err: %s", err) 156 | } 157 | if c.DisableStopInstance != false { 158 | t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance) 159 | } 160 | 161 | c.DisableStopInstance = true 162 | if err := c.Prepare(nil); len(err) != 0 { 163 | t.Fatalf("err: %s", err) 164 | } 165 | if c.DisableStopInstance != true { 166 | t.Fatalf("invalid value, expected: %t, actul: %t", true, c.DisableStopInstance) 167 | } 168 | 169 | c.DisableStopInstance = false 170 | if err := c.Prepare(nil); len(err) != 0 { 171 | t.Fatalf("err: %s", err) 172 | } 173 | if c.DisableStopInstance != false { 174 | t.Fatalf("invalid value, expected: %t, actul: %t", false, c.DisableStopInstance) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /ecs/step_create_image.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/hashicorp/packer/common/random" 9 | 10 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 11 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 12 | "github.com/hashicorp/packer/common/uuid" 13 | "github.com/hashicorp/packer/helper/multistep" 14 | "github.com/hashicorp/packer/packer" 15 | ) 16 | 17 | type stepCreateAlicloudImage struct { 18 | AlicloudImageIgnoreDataDisks bool 19 | WaitSnapshotReadyTimeout int 20 | image *ecs.Image 21 | } 22 | 23 | var createImageRetryErrors = []string{ 24 | "IdempotentProcessing", 25 | } 26 | 27 | func (s *stepCreateAlicloudImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 28 | config := state.Get("config").(*Config) 29 | client := state.Get("client").(*ClientWrapper) 30 | ui := state.Get("ui").(packer.Ui) 31 | 32 | tempImageName := config.AlicloudImageName 33 | if config.ImageEncrypted != nil && *config.ImageEncrypted { 34 | tempImageName = fmt.Sprintf("packer_%s", random.AlphaNum(7)) 35 | ui.Say(fmt.Sprintf("Creating temporary image for encryption: %s", tempImageName)) 36 | } else { 37 | ui.Say(fmt.Sprintf("Creating image: %s", tempImageName)) 38 | } 39 | 40 | createImageRequest := s.buildCreateImageRequest(state, tempImageName) 41 | createImageResponse, err := client.WaitForExpected(&WaitForExpectArgs{ 42 | RequestFunc: func() (responses.AcsResponse, error) { 43 | return client.CreateImage(createImageRequest) 44 | }, 45 | EvalFunc: client.EvalCouldRetryResponse(createImageRetryErrors, EvalRetryErrorType), 46 | }) 47 | 48 | if err != nil { 49 | return halt(state, err, "Error creating image") 50 | } 51 | 52 | imageId := createImageResponse.(*ecs.CreateImageResponse).ImageId 53 | 54 | imagesResponse, err := client.WaitForImageStatus(config.AlicloudRegion, imageId, ImageStatusAvailable, time.Duration(s.WaitSnapshotReadyTimeout)*time.Second) 55 | 56 | // save image first for cleaning up if timeout 57 | images := imagesResponse.(*ecs.DescribeImagesResponse).Images.Image 58 | if len(images) == 0 { 59 | return halt(state, err, "Unable to find created image") 60 | } 61 | s.image = &images[0] 62 | 63 | if err != nil { 64 | return halt(state, err, "Timeout waiting for image to be created") 65 | } 66 | 67 | var snapshotIds []string 68 | for _, device := range images[0].DiskDeviceMappings.DiskDeviceMapping { 69 | snapshotIds = append(snapshotIds, device.SnapshotId) 70 | } 71 | 72 | state.Put("alicloudimage", imageId) 73 | state.Put("alicloudsnapshots", snapshotIds) 74 | 75 | alicloudImages := make(map[string]string) 76 | alicloudImages[config.AlicloudRegion] = images[0].ImageId 77 | state.Put("alicloudimages", alicloudImages) 78 | 79 | return multistep.ActionContinue 80 | } 81 | 82 | func (s *stepCreateAlicloudImage) Cleanup(state multistep.StateBag) { 83 | if s.image == nil { 84 | return 85 | } 86 | 87 | config := state.Get("config").(*Config) 88 | encryptedSet := config.ImageEncrypted != nil && *config.ImageEncrypted 89 | 90 | _, cancelled := state.GetOk(multistep.StateCancelled) 91 | _, halted := state.GetOk(multistep.StateHalted) 92 | 93 | if !cancelled && !halted && !encryptedSet { 94 | return 95 | } 96 | 97 | client := state.Get("client").(*ClientWrapper) 98 | ui := state.Get("ui").(packer.Ui) 99 | 100 | if !cancelled && !halted && encryptedSet { 101 | ui.Say(fmt.Sprintf("Deleting temporary image %s(%s) and related snapshots after finishing encryption...", s.image.ImageId, s.image.ImageName)) 102 | } else { 103 | ui.Say("Deleting the image and related snapshots because of cancellation or error...") 104 | } 105 | 106 | deleteImageRequest := ecs.CreateDeleteImageRequest() 107 | deleteImageRequest.RegionId = config.AlicloudRegion 108 | deleteImageRequest.ImageId = s.image.ImageId 109 | if _, err := client.DeleteImage(deleteImageRequest); err != nil { 110 | ui.Error(fmt.Sprintf("Error deleting image, it may still be around: %s", err)) 111 | return 112 | } 113 | 114 | //Delete the snapshot of this image 115 | for _, diskDevices := range s.image.DiskDeviceMappings.DiskDeviceMapping { 116 | deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest() 117 | deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId 118 | if _, err := client.DeleteSnapshot(deleteSnapshotRequest); err != nil { 119 | ui.Error(fmt.Sprintf("Error deleting snapshot, it may still be around: %s", err)) 120 | return 121 | } 122 | } 123 | } 124 | 125 | func (s *stepCreateAlicloudImage) buildCreateImageRequest(state multistep.StateBag, imageName string) *ecs.CreateImageRequest { 126 | config := state.Get("config").(*Config) 127 | 128 | request := ecs.CreateCreateImageRequest() 129 | request.ClientToken = uuid.TimeOrderedUUID() 130 | request.RegionId = config.AlicloudRegion 131 | request.ImageName = imageName 132 | request.ImageVersion = config.AlicloudImageVersion 133 | request.Description = config.AlicloudImageDescription 134 | 135 | if s.AlicloudImageIgnoreDataDisks { 136 | snapshotId := state.Get("alicloudsnapshot").(string) 137 | request.SnapshotId = snapshotId 138 | } else { 139 | instance := state.Get("instance").(*ecs.Instance) 140 | request.InstanceId = instance.InstanceId 141 | } 142 | 143 | return request 144 | } 145 | -------------------------------------------------------------------------------- /ecs/step_config_security_group.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 8 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 9 | "github.com/hashicorp/packer/common/uuid" 10 | "github.com/hashicorp/packer/helper/multistep" 11 | "github.com/hashicorp/packer/packer" 12 | ) 13 | 14 | type stepConfigAlicloudSecurityGroup struct { 15 | SecurityGroupId string 16 | SecurityGroupName string 17 | Description string 18 | VpcId string 19 | RegionId string 20 | isCreate bool 21 | } 22 | 23 | var createSecurityGroupRetryErrors = []string{ 24 | "IdempotentProcessing", 25 | } 26 | 27 | var deleteSecurityGroupRetryErrors = []string{ 28 | "DependencyViolation", 29 | } 30 | 31 | func (s *stepConfigAlicloudSecurityGroup) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 32 | client := state.Get("client").(*ClientWrapper) 33 | ui := state.Get("ui").(packer.Ui) 34 | networkType := state.Get("networktype").(InstanceNetWork) 35 | 36 | if len(s.SecurityGroupId) != 0 { 37 | describeSecurityGroupsRequest := ecs.CreateDescribeSecurityGroupsRequest() 38 | describeSecurityGroupsRequest.RegionId = s.RegionId 39 | 40 | if networkType == InstanceNetworkVpc { 41 | vpcId := state.Get("vpcid").(string) 42 | describeSecurityGroupsRequest.VpcId = vpcId 43 | } 44 | 45 | securityGroupsResponse, err := client.DescribeSecurityGroups(describeSecurityGroupsRequest) 46 | if err != nil { 47 | return halt(state, err, "Failed querying security group") 48 | } 49 | 50 | securityGroupItems := securityGroupsResponse.SecurityGroups.SecurityGroup 51 | for _, securityGroupItem := range securityGroupItems { 52 | if securityGroupItem.SecurityGroupId == s.SecurityGroupId { 53 | state.Put("securitygroupid", s.SecurityGroupId) 54 | s.isCreate = false 55 | return multistep.ActionContinue 56 | } 57 | } 58 | 59 | s.isCreate = false 60 | err = fmt.Errorf("The specified security group {%s} doesn't exist.", s.SecurityGroupId) 61 | return halt(state, err, "") 62 | } 63 | 64 | ui.Say("Creating security group...") 65 | 66 | createSecurityGroupRequest := s.buildCreateSecurityGroupRequest(state) 67 | securityGroupResponse, err := client.WaitForExpected(&WaitForExpectArgs{ 68 | RequestFunc: func() (responses.AcsResponse, error) { 69 | return client.CreateSecurityGroup(createSecurityGroupRequest) 70 | }, 71 | EvalFunc: client.EvalCouldRetryResponse(createSecurityGroupRetryErrors, EvalRetryErrorType), 72 | }) 73 | 74 | if err != nil { 75 | return halt(state, err, "Failed creating security group") 76 | } 77 | 78 | securityGroupId := securityGroupResponse.(*ecs.CreateSecurityGroupResponse).SecurityGroupId 79 | 80 | ui.Message(fmt.Sprintf("Created security group: %s", securityGroupId)) 81 | state.Put("securitygroupid", securityGroupId) 82 | s.isCreate = true 83 | s.SecurityGroupId = securityGroupId 84 | 85 | authorizeSecurityGroupEgressRequest := ecs.CreateAuthorizeSecurityGroupEgressRequest() 86 | authorizeSecurityGroupEgressRequest.SecurityGroupId = securityGroupId 87 | authorizeSecurityGroupEgressRequest.RegionId = s.RegionId 88 | authorizeSecurityGroupEgressRequest.IpProtocol = IpProtocolAll 89 | authorizeSecurityGroupEgressRequest.PortRange = DefaultPortRange 90 | authorizeSecurityGroupEgressRequest.NicType = NicTypeInternet 91 | authorizeSecurityGroupEgressRequest.DestCidrIp = DefaultCidrIp 92 | 93 | if _, err := client.AuthorizeSecurityGroupEgress(authorizeSecurityGroupEgressRequest); err != nil { 94 | return halt(state, err, "Failed authorizing security group") 95 | } 96 | 97 | authorizeSecurityGroupRequest := ecs.CreateAuthorizeSecurityGroupRequest() 98 | authorizeSecurityGroupRequest.SecurityGroupId = securityGroupId 99 | authorizeSecurityGroupRequest.RegionId = s.RegionId 100 | authorizeSecurityGroupRequest.IpProtocol = IpProtocolAll 101 | authorizeSecurityGroupRequest.PortRange = DefaultPortRange 102 | authorizeSecurityGroupRequest.NicType = NicTypeInternet 103 | authorizeSecurityGroupRequest.SourceCidrIp = DefaultCidrIp 104 | 105 | if _, err := client.AuthorizeSecurityGroup(authorizeSecurityGroupRequest); err != nil { 106 | return halt(state, err, "Failed authorizing security group") 107 | } 108 | 109 | return multistep.ActionContinue 110 | } 111 | 112 | func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) { 113 | if !s.isCreate { 114 | return 115 | } 116 | 117 | cleanUpMessage(state, "security group") 118 | 119 | client := state.Get("client").(*ClientWrapper) 120 | ui := state.Get("ui").(packer.Ui) 121 | 122 | _, err := client.WaitForExpected(&WaitForExpectArgs{ 123 | RequestFunc: func() (responses.AcsResponse, error) { 124 | request := ecs.CreateDeleteSecurityGroupRequest() 125 | request.RegionId = s.RegionId 126 | request.SecurityGroupId = s.SecurityGroupId 127 | return client.DeleteSecurityGroup(request) 128 | }, 129 | EvalFunc: client.EvalCouldRetryResponse(deleteSecurityGroupRetryErrors, EvalRetryErrorType), 130 | RetryTimes: shortRetryTimes, 131 | }) 132 | 133 | if err != nil { 134 | ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err)) 135 | } 136 | } 137 | 138 | func (s *stepConfigAlicloudSecurityGroup) buildCreateSecurityGroupRequest(state multistep.StateBag) *ecs.CreateSecurityGroupRequest { 139 | networkType := state.Get("networktype").(InstanceNetWork) 140 | 141 | request := ecs.CreateCreateSecurityGroupRequest() 142 | request.ClientToken = uuid.TimeOrderedUUID() 143 | request.RegionId = s.RegionId 144 | request.SecurityGroupName = s.SecurityGroupName 145 | 146 | if networkType == InstanceNetworkVpc { 147 | vpcId := state.Get("vpcid").(string) 148 | request.VpcId = vpcId 149 | } 150 | 151 | return request 152 | } 153 | -------------------------------------------------------------------------------- /ecs/artifact.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 10 | "github.com/hashicorp/packer/packer" 11 | ) 12 | 13 | type Artifact struct { 14 | // A map of regions to alicloud image IDs. 15 | AlicloudImages map[string]string 16 | 17 | // BuilderId is the unique ID for the builder that created this alicloud image 18 | BuilderIdValue string 19 | 20 | // Alcloud connection for performing API stuff. 21 | Client *ClientWrapper 22 | } 23 | 24 | func (a *Artifact) BuilderId() string { 25 | return a.BuilderIdValue 26 | } 27 | 28 | func (*Artifact) Files() []string { 29 | // We have no files 30 | return nil 31 | } 32 | 33 | func (a *Artifact) Id() string { 34 | parts := make([]string, 0, len(a.AlicloudImages)) 35 | for region, ecsImageId := range a.AlicloudImages { 36 | parts = append(parts, fmt.Sprintf("%s:%s", region, ecsImageId)) 37 | } 38 | 39 | sort.Strings(parts) 40 | return strings.Join(parts, ",") 41 | } 42 | 43 | func (a *Artifact) String() string { 44 | alicloudImageStrings := make([]string, 0, len(a.AlicloudImages)) 45 | for region, id := range a.AlicloudImages { 46 | single := fmt.Sprintf("%s: %s", region, id) 47 | alicloudImageStrings = append(alicloudImageStrings, single) 48 | } 49 | 50 | sort.Strings(alicloudImageStrings) 51 | return fmt.Sprintf("Alicloud images were created:\n\n%s", strings.Join(alicloudImageStrings, "\n")) 52 | } 53 | 54 | func (a *Artifact) State(name string) interface{} { 55 | switch name { 56 | case "atlas.artifact.metadata": 57 | return a.stateAtlasMetadata() 58 | default: 59 | return nil 60 | } 61 | } 62 | 63 | func (a *Artifact) Destroy() error { 64 | errors := make([]error, 0) 65 | 66 | copyingImages := make(map[string]string, len(a.AlicloudImages)) 67 | sourceImage := make(map[string]*ecs.Image, 1) 68 | for regionId, imageId := range a.AlicloudImages { 69 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 70 | describeImagesRequest.RegionId = regionId 71 | describeImagesRequest.ImageId = imageId 72 | describeImagesRequest.Status = ImageStatusQueried 73 | imagesResponse, err := a.Client.DescribeImages(describeImagesRequest) 74 | if err != nil { 75 | errors = append(errors, err) 76 | } 77 | 78 | images := imagesResponse.Images.Image 79 | if len(images) == 0 { 80 | err := fmt.Errorf("Error retrieving details for alicloud image(%s), no alicloud images found", imageId) 81 | errors = append(errors, err) 82 | continue 83 | } 84 | 85 | if images[0].IsCopied && images[0].Status != ImageStatusAvailable { 86 | copyingImages[regionId] = imageId 87 | } else { 88 | sourceImage[regionId] = &images[0] 89 | } 90 | } 91 | 92 | for regionId, imageId := range copyingImages { 93 | log.Printf("Cancel copying alicloud image (%s) from region (%s)", imageId, regionId) 94 | 95 | errs := a.unsharedAccountsOnImages(regionId, imageId) 96 | if errs != nil { 97 | errors = append(errors, errs...) 98 | } 99 | 100 | cancelImageCopyRequest := ecs.CreateCancelCopyImageRequest() 101 | cancelImageCopyRequest.RegionId = regionId 102 | cancelImageCopyRequest.ImageId = imageId 103 | if _, err := a.Client.CancelCopyImage(cancelImageCopyRequest); err != nil { 104 | errors = append(errors, err) 105 | } 106 | } 107 | 108 | for regionId, image := range sourceImage { 109 | imageId := image.ImageId 110 | log.Printf("Delete alicloud image (%s) from region (%s)", imageId, regionId) 111 | 112 | errs := a.unsharedAccountsOnImages(regionId, imageId) 113 | if errs != nil { 114 | errors = append(errors, errs...) 115 | } 116 | 117 | deleteImageRequest := ecs.CreateDeleteImageRequest() 118 | deleteImageRequest.RegionId = regionId 119 | deleteImageRequest.ImageId = imageId 120 | if _, err := a.Client.DeleteImage(deleteImageRequest); err != nil { 121 | errors = append(errors, err) 122 | } 123 | 124 | //Delete the snapshot of this images 125 | for _, diskDevices := range image.DiskDeviceMappings.DiskDeviceMapping { 126 | deleteSnapshotRequest := ecs.CreateDeleteSnapshotRequest() 127 | deleteSnapshotRequest.SnapshotId = diskDevices.SnapshotId 128 | _, err := a.Client.DeleteSnapshot(deleteSnapshotRequest) 129 | if err != nil { 130 | errors = append(errors, err) 131 | } 132 | } 133 | } 134 | 135 | if len(errors) > 0 { 136 | if len(errors) == 1 { 137 | return errors[0] 138 | } else { 139 | return &packer.MultiError{Errors: errors} 140 | } 141 | } 142 | 143 | return nil 144 | } 145 | 146 | func (a *Artifact) unsharedAccountsOnImages(regionId string, imageId string) []error { 147 | var errors []error 148 | 149 | describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest() 150 | describeImageShareRequest.RegionId = regionId 151 | describeImageShareRequest.ImageId = imageId 152 | imageShareResponse, err := a.Client.DescribeImageSharePermission(describeImageShareRequest) 153 | if err != nil { 154 | errors = append(errors, err) 155 | return errors 156 | } 157 | 158 | accountsNumber := len(imageShareResponse.Accounts.Account) 159 | if accountsNumber > 0 { 160 | accounts := make([]string, accountsNumber) 161 | for index, account := range imageShareResponse.Accounts.Account { 162 | accounts[index] = account.AliyunId 163 | } 164 | 165 | modifyImageShareRequest := ecs.CreateModifyImageSharePermissionRequest() 166 | modifyImageShareRequest.RegionId = regionId 167 | modifyImageShareRequest.ImageId = imageId 168 | modifyImageShareRequest.RemoveAccount = &accounts 169 | _, err := a.Client.ModifyImageSharePermission(modifyImageShareRequest) 170 | if err != nil { 171 | errors = append(errors, err) 172 | } 173 | } 174 | 175 | return errors 176 | } 177 | 178 | func (a *Artifact) stateAtlasMetadata() interface{} { 179 | metadata := make(map[string]string) 180 | for region, imageId := range a.AlicloudImages { 181 | k := fmt.Sprintf("region.%s", region) 182 | metadata[k] = imageId 183 | } 184 | 185 | return metadata 186 | } 187 | -------------------------------------------------------------------------------- /ecs/step_config_eip.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" 8 | "github.com/hashicorp/packer/common/uuid" 9 | 10 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 11 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 12 | "github.com/hashicorp/packer/helper/multistep" 13 | "github.com/hashicorp/packer/packer" 14 | ) 15 | 16 | type stepConfigAlicloudEIP struct { 17 | AssociatePublicIpAddress bool 18 | RegionId string 19 | InternetChargeType string 20 | InternetMaxBandwidthOut int 21 | allocatedId string 22 | SSHPrivateIp bool 23 | } 24 | 25 | var allocateEipAddressRetryErrors = []string{ 26 | "LastTokenProcessing", 27 | } 28 | 29 | func (s *stepConfigAlicloudEIP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 30 | client := state.Get("client").(*ClientWrapper) 31 | ui := state.Get("ui").(packer.Ui) 32 | instance := state.Get("instance").(*ecs.Instance) 33 | 34 | if s.SSHPrivateIp { 35 | ipaddress := instance.VpcAttributes.PrivateIpAddress.IpAddress 36 | if len(ipaddress) == 0 { 37 | ui.Say("Failed to get private ip of instance") 38 | return multistep.ActionHalt 39 | } 40 | state.Put("ipaddress", ipaddress[0]) 41 | return multistep.ActionContinue 42 | } 43 | 44 | ui.Say("Allocating eip...") 45 | 46 | allocateEipAddressRequest := s.buildAllocateEipAddressRequest(state) 47 | allocateEipAddressResponse, err := client.WaitForExpected(&WaitForExpectArgs{ 48 | RequestFunc: func() (responses.AcsResponse, error) { 49 | return client.AllocateEipAddress(allocateEipAddressRequest) 50 | }, 51 | EvalFunc: client.EvalCouldRetryResponse(allocateEipAddressRetryErrors, EvalRetryErrorType), 52 | }) 53 | 54 | if err != nil { 55 | return halt(state, err, "Error allocating eip") 56 | } 57 | 58 | ipaddress := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).EipAddress 59 | ui.Message(fmt.Sprintf("Allocated eip: %s", ipaddress)) 60 | 61 | allocateId := allocateEipAddressResponse.(*ecs.AllocateEipAddressResponse).AllocationId 62 | s.allocatedId = allocateId 63 | 64 | err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable) 65 | if err != nil { 66 | return halt(state, err, "Error wait eip available timeout") 67 | } 68 | 69 | associateEipAddressRequest := ecs.CreateAssociateEipAddressRequest() 70 | associateEipAddressRequest.AllocationId = allocateId 71 | associateEipAddressRequest.InstanceId = instance.InstanceId 72 | if _, err := client.AssociateEipAddress(associateEipAddressRequest); err != nil { 73 | e, ok := err.(errors.Error) 74 | if !ok || e.ErrorCode() != "TaskConflict" { 75 | return halt(state, err, "Error associating eip") 76 | } 77 | 78 | ui.Error(fmt.Sprintf("Error associate eip: %s", err)) 79 | } 80 | 81 | err = s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusInUse) 82 | if err != nil { 83 | return halt(state, err, "Error wait eip associated timeout") 84 | } 85 | 86 | state.Put("ipaddress", ipaddress) 87 | return multistep.ActionContinue 88 | } 89 | 90 | func (s *stepConfigAlicloudEIP) Cleanup(state multistep.StateBag) { 91 | if len(s.allocatedId) == 0 { 92 | return 93 | } 94 | 95 | cleanUpMessage(state, "EIP") 96 | 97 | client := state.Get("client").(*ClientWrapper) 98 | instance := state.Get("instance").(*ecs.Instance) 99 | ui := state.Get("ui").(packer.Ui) 100 | 101 | unassociateEipAddressRequest := ecs.CreateUnassociateEipAddressRequest() 102 | unassociateEipAddressRequest.AllocationId = s.allocatedId 103 | unassociateEipAddressRequest.InstanceId = instance.InstanceId 104 | if _, err := client.UnassociateEipAddress(unassociateEipAddressRequest); err != nil { 105 | ui.Say(fmt.Sprintf("Failed to unassociate eip: %s", err)) 106 | } 107 | 108 | if err := s.waitForEipStatus(client, instance.RegionId, s.allocatedId, EipStatusAvailable); err != nil { 109 | ui.Say(fmt.Sprintf("Timeout while unassociating eip: %s", err)) 110 | } 111 | 112 | releaseEipAddressRequest := ecs.CreateReleaseEipAddressRequest() 113 | releaseEipAddressRequest.AllocationId = s.allocatedId 114 | if _, err := client.ReleaseEipAddress(releaseEipAddressRequest); err != nil { 115 | ui.Say(fmt.Sprintf("Failed to release eip: %s", err)) 116 | } 117 | } 118 | 119 | func (s *stepConfigAlicloudEIP) waitForEipStatus(client *ClientWrapper, regionId string, allocationId string, expectedStatus string) error { 120 | describeEipAddressesRequest := ecs.CreateDescribeEipAddressesRequest() 121 | describeEipAddressesRequest.RegionId = regionId 122 | describeEipAddressesRequest.AllocationId = s.allocatedId 123 | 124 | _, err := client.WaitForExpected(&WaitForExpectArgs{ 125 | RequestFunc: func() (responses.AcsResponse, error) { 126 | response, err := client.DescribeEipAddresses(describeEipAddressesRequest) 127 | if err == nil && len(response.EipAddresses.EipAddress) == 0 { 128 | err = fmt.Errorf("eip allocated is not find") 129 | } 130 | 131 | return response, err 132 | }, 133 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 134 | if err != nil { 135 | return WaitForExpectToRetry 136 | } 137 | 138 | eipAddressesResponse := response.(*ecs.DescribeEipAddressesResponse) 139 | eipAddresses := eipAddressesResponse.EipAddresses.EipAddress 140 | 141 | for _, eipAddress := range eipAddresses { 142 | if eipAddress.Status == expectedStatus { 143 | return WaitForExpectSuccess 144 | } 145 | } 146 | 147 | return WaitForExpectToRetry 148 | }, 149 | RetryTimes: shortRetryTimes, 150 | }) 151 | 152 | return err 153 | } 154 | 155 | func (s *stepConfigAlicloudEIP) buildAllocateEipAddressRequest(state multistep.StateBag) *ecs.AllocateEipAddressRequest { 156 | instance := state.Get("instance").(*ecs.Instance) 157 | 158 | request := ecs.CreateAllocateEipAddressRequest() 159 | request.ClientToken = uuid.TimeOrderedUUID() 160 | request.RegionId = instance.RegionId 161 | request.InternetChargeType = s.InternetChargeType 162 | request.Bandwidth = string(convertNumber(s.InternetMaxBandwidthOut)) 163 | 164 | return request 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alicloud [(Alibaba Cloud)](http://www.aliyun.com) packer provider 2 | 3 | This is the official repository for the Alicloud packer provider. 4 | Currently it supports packer version ≥ v0.12.1. 5 | 6 | If you are not planning to contribute to this repo, you can download the [compiled binaries](https://github.com/alibaba/packer-provider/releases) according to you platform, unzip and move 7 | them into the folder under the packer **PATH** such as **/usr/local/packer**. 8 | 9 | ## Install 10 | - Download the correct packer from you platform from https://www.packer.io/downloads.html 11 | - Install packer according to the guide from https://www.packer.io/docs/installation.html 12 | - Install Go according to the guide from [https://golang.org/doc/install](https://golang.org/doc/install) 13 | - Setup your access key and secret key in the environment variables according to platform, for example In Linux platform with default bash, open your .bashrc in your home directory and add following two lines

14 | ```aidl 15 | export ALICLOUD_ACCESS_KEY="access key value" 16 | 17 | export ALICLOUD_SECRET_KEY="secret key value" 18 | ``` 19 | - Open a terminator and clone Alicloud packer provider and build,install and test

20 | ``` 21 | cd <$GOPATH> 22 | 23 | mkdir -p src/github.com/alibaba/ 24 | 25 | cd <$GOPATH>/src/github.com/alibaba/ 26 | 27 | git clone https://github.com/alibaba/packer-provider 28 | 29 | cd <$GOPTH>/src/github.com/alibaba/packer-provider 30 | 31 | make all 32 | 33 | sorce ~/.bashrc 34 | 35 | packer build example/alicloud.json 36 | ``` 37 | If output similar as following, configurations, you can now start the journey of alicloud with packer support 38 | ``` 39 | alicloud output will be in this color. 40 | 41 | ==> alicloud: Force delete flag found, skipping prevalidating Alicloud ECS Image Name 42 | alicloud: Found Image ID: centos_7_03_64_20G_alibase_20170818.vhd 43 | ==> alicloud: allocated eip address 121.196.193.14 44 | ==> alicloud: Instance starting 45 | ==> alicloud: Waiting for SSH to become available... 46 | ==> alicloud: This machine's host=121.196.193.14 47 | ==> alicloud: This machine's host=121.196.193.14 48 | ==> alicloud: This machine's host=121.196.193.14 49 | ==> alicloud: Connected to SSH! 50 | ==> alicloud: Provisioning with shell script: /var/folders/3q/w38xx_js6cl6k5mwkrqsnw7w0000gn/T/packer-shell170579778 51 | ``` 52 | ## Example 53 | ### Create a simple image with redis installed 54 | ``` 55 | { 56 | "variables": { 57 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 58 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 59 | }, 60 | "builders": [{ 61 | "type":"alicloud-ecs", 62 | "access_key":"{{user `access_key`}}", 63 | "secret_key":"{{user `secret_key`}}", 64 | "region":"cn-beijing", 65 | "image_name":"packer_test2", 66 | "source_image":"centos_7_03_64_20G_alibase_20170818.vhd", 67 | "ssh_username":"root", 68 | "instance_type":"ecs.n1.tiny", 69 | "io_optimized":"true", 70 | "internet_charge_type":"PayByTraffic", 71 | "image_force_delete":"true" 72 | }], 73 | "provisioners": [{ 74 | "type": "shell", 75 | "inline": [ 76 | "sleep 30", 77 | "yum install redis.x86_64 -y" 78 | ] 79 | }] 80 | } 81 | 82 | ``` 83 | ### Create a simple image for windows 84 | ```aidl 85 | { 86 | "variables": { 87 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 88 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 89 | }, 90 | "builders": [{ 91 | "type":"alicloud-ecs", 92 | "access_key":"{{user `access_key`}}", 93 | "secret_key":"{{user `secret_key`}}", 94 | "region":"cn-beijing", 95 | "image_name":"packer_test", 96 | "source_image":"win2008r2_64_ent_sp1_zh-cn_40G_alibase_20170915.vhd", 97 | "instance_type":"ecs.n1.tiny", 98 | "internet_charge_type":"PayByTraffic", 99 | "io_optimized":"true", 100 | "image_force_delete":"true", 101 | "communicator": "winrm", 102 | "winrm_port": 5985, 103 | "winrm_username": "Administrator", 104 | "winrm_password": "Test1234", 105 | "user_data_file": "examples/alicloud/basic/winrm_enable_userdata.ps1" 106 | }], 107 | "provisioners": [{ 108 | "type": "powershell", 109 | "inline": ["dir c:\\"] 110 | }] 111 | } 112 | 113 | ``` 114 | 115 | > Note: Since WinRM is closed by default in the system image. 116 | You need enable it by userdata in order to connect to the instance, 117 | check [winrm_enable_userdata.ps1](https://github.com/alibaba/packer-provider/tree/master/examples/alicloud/basic/winrm_enable_userdata.ps1) for details. 118 | 119 | ### Create a simple image with redis installed and mounted disk 120 | ``` 121 | { 122 | "variables": { 123 | "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}", 124 | "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}" 125 | }, 126 | "builders": [{ 127 | "type":"alicloud-ecs", 128 | "access_key":"{{user `access_key`}}", 129 | "secret_key":"{{user `secret_key`}}", 130 | "region":"cn-beijing", 131 | "image_name":"packer_with_data_disk", 132 | "source_image":"centos_7_03_64_20G_alibase_20170818.vhd", 133 | "ssh_username":"root", 134 | "instance_type":"ecs.n1.tiny", 135 | "internet_charge_type":"PayByTraffic", 136 | "io_optimized":"true", 137 | "image_disk_mappings":[{"disk_name":"data1","disk_size":20},{"disk_name":"data1","disk_size":20,"disk_device":"/dev/xvdz"}] 138 | }], 139 | "provisioners": [{ 140 | "type": "shell", 141 | "inline": [ 142 | "sleep 30", 143 | "yum install redis.x86_64 -y" 144 | ] 145 | }] 146 | } 147 | ``` 148 | 149 | > Note: Images can become deprecated after a while; run 150 | `aliyun ecs DescribeImages` to find one that exists. 151 | 152 | ### Here are [more examples](https://github.com/alibaba/packer-provider/tree/master/examples/alicloud) include chef, jenkins image template etc. 153 | 154 | ## 155 | ### How to contribute code 156 | * If you are not sure or have any doubts, feel free to ask and/or submit an issue or PR. We appreciate all contributions and don't want to create artificial obstacles that get in the way. 157 | * Contributions are welcome and will be merged via PRs. 158 | 159 | ### Contributors 160 | * zhuzhih2017 161 | 162 | ### License 163 | * This project is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/alibaba/packer-provider/blob/master/LICENSE) for the full license text. 164 | 165 | ### Refrence 166 | * Pakcer document: https://www.packer.io/intro/ 167 | 168 | -------------------------------------------------------------------------------- /ecs/step_create_instance.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "io/ioutil" 8 | "strconv" 9 | 10 | "github.com/hashicorp/packer/common/uuid" 11 | 12 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 13 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 14 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 15 | "github.com/hashicorp/packer/helper/multistep" 16 | "github.com/hashicorp/packer/packer" 17 | ) 18 | 19 | type stepCreateAlicloudInstance struct { 20 | IOOptimized *bool 21 | InstanceType string 22 | UserData string 23 | UserDataFile string 24 | instanceId string 25 | RegionId string 26 | InternetChargeType string 27 | InternetMaxBandwidthOut int 28 | InstanceName string 29 | ZoneId string 30 | instance *ecs.Instance 31 | } 32 | 33 | var createInstanceRetryErrors = []string{ 34 | "IdempotentProcessing", 35 | } 36 | 37 | var deleteInstanceRetryErrors = []string{ 38 | "IncorrectInstanceStatus.Initializing", 39 | } 40 | 41 | func (s *stepCreateAlicloudInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 42 | client := state.Get("client").(*ClientWrapper) 43 | ui := state.Get("ui").(packer.Ui) 44 | 45 | ui.Say("Creating instance...") 46 | createInstanceRequest, err := s.buildCreateInstanceRequest(state) 47 | if err != nil { 48 | return halt(state, err, "") 49 | } 50 | 51 | createInstanceResponse, err := client.WaitForExpected(&WaitForExpectArgs{ 52 | RequestFunc: func() (responses.AcsResponse, error) { 53 | return client.CreateInstance(createInstanceRequest) 54 | }, 55 | EvalFunc: client.EvalCouldRetryResponse(createInstanceRetryErrors, EvalRetryErrorType), 56 | }) 57 | 58 | if err != nil { 59 | return halt(state, err, "Error creating instance") 60 | } 61 | 62 | instanceId := createInstanceResponse.(*ecs.CreateInstanceResponse).InstanceId 63 | 64 | _, err = client.WaitForInstanceStatus(s.RegionId, instanceId, InstanceStatusStopped) 65 | if err != nil { 66 | return halt(state, err, "Error waiting create instance") 67 | } 68 | 69 | describeInstancesRequest := ecs.CreateDescribeInstancesRequest() 70 | describeInstancesRequest.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId) 71 | instances, err := client.DescribeInstances(describeInstancesRequest) 72 | if err != nil { 73 | return halt(state, err, "") 74 | } 75 | 76 | ui.Message(fmt.Sprintf("Created instance: %s", instanceId)) 77 | s.instance = &instances.Instances.Instance[0] 78 | state.Put("instance", s.instance) 79 | 80 | return multistep.ActionContinue 81 | } 82 | 83 | func (s *stepCreateAlicloudInstance) Cleanup(state multistep.StateBag) { 84 | if s.instance == nil { 85 | return 86 | } 87 | cleanUpMessage(state, "instance") 88 | 89 | client := state.Get("client").(*ClientWrapper) 90 | ui := state.Get("ui").(packer.Ui) 91 | 92 | _, err := client.WaitForExpected(&WaitForExpectArgs{ 93 | RequestFunc: func() (responses.AcsResponse, error) { 94 | request := ecs.CreateDeleteInstanceRequest() 95 | request.InstanceId = s.instance.InstanceId 96 | request.Force = requests.NewBoolean(true) 97 | return client.DeleteInstance(request) 98 | }, 99 | EvalFunc: client.EvalCouldRetryResponse(deleteInstanceRetryErrors, EvalRetryErrorType), 100 | RetryTimes: shortRetryTimes, 101 | }) 102 | 103 | if err != nil { 104 | ui.Say(fmt.Sprintf("Failed to clean up instance %s: %s", s.instance.InstanceId, err)) 105 | } 106 | } 107 | 108 | func (s *stepCreateAlicloudInstance) buildCreateInstanceRequest(state multistep.StateBag) (*ecs.CreateInstanceRequest, error) { 109 | request := ecs.CreateCreateInstanceRequest() 110 | request.ClientToken = uuid.TimeOrderedUUID() 111 | request.RegionId = s.RegionId 112 | request.InstanceType = s.InstanceType 113 | request.InstanceName = s.InstanceName 114 | request.ZoneId = s.ZoneId 115 | 116 | sourceImage := state.Get("source_image").(*ecs.Image) 117 | request.ImageId = sourceImage.ImageId 118 | 119 | securityGroupId := state.Get("securitygroupid").(string) 120 | request.SecurityGroupId = securityGroupId 121 | 122 | networkType := state.Get("networktype").(InstanceNetWork) 123 | if networkType == InstanceNetworkVpc { 124 | vswitchId := state.Get("vswitchid").(string) 125 | request.VSwitchId = vswitchId 126 | 127 | userData, err := s.getUserData(state) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | request.UserData = userData 133 | } else { 134 | if s.InternetChargeType == "" { 135 | s.InternetChargeType = "PayByTraffic" 136 | } 137 | 138 | if s.InternetMaxBandwidthOut == 0 { 139 | s.InternetMaxBandwidthOut = 5 140 | } 141 | } 142 | request.InternetChargeType = s.InternetChargeType 143 | request.InternetMaxBandwidthOut = requests.Integer(convertNumber(s.InternetMaxBandwidthOut)) 144 | 145 | if s.IOOptimized != nil { 146 | if *s.IOOptimized { 147 | request.IoOptimized = IOOptimizedOptimized 148 | } else { 149 | request.IoOptimized = IOOptimizedNone 150 | } 151 | } 152 | 153 | config := state.Get("config").(*Config) 154 | password := config.Comm.SSHPassword 155 | if password == "" && config.Comm.WinRMPassword != "" { 156 | password = config.Comm.WinRMPassword 157 | } 158 | request.Password = password 159 | 160 | systemDisk := config.AlicloudImageConfig.ECSSystemDiskMapping 161 | request.SystemDiskDiskName = systemDisk.DiskName 162 | request.SystemDiskCategory = systemDisk.DiskCategory 163 | request.SystemDiskSize = requests.Integer(convertNumber(systemDisk.DiskSize)) 164 | request.SystemDiskDescription = systemDisk.Description 165 | 166 | imageDisks := config.AlicloudImageConfig.ECSImagesDiskMappings 167 | var dataDisks []ecs.CreateInstanceDataDisk 168 | for _, imageDisk := range imageDisks { 169 | var dataDisk ecs.CreateInstanceDataDisk 170 | dataDisk.DiskName = imageDisk.DiskName 171 | dataDisk.Category = imageDisk.DiskCategory 172 | dataDisk.Size = string(convertNumber(imageDisk.DiskSize)) 173 | dataDisk.SnapshotId = imageDisk.SnapshotId 174 | dataDisk.Description = imageDisk.Description 175 | dataDisk.DeleteWithInstance = strconv.FormatBool(imageDisk.DeleteWithInstance) 176 | dataDisk.Device = imageDisk.Device 177 | if imageDisk.Encrypted != nil { 178 | dataDisk.Encrypted = strconv.FormatBool(*imageDisk.Encrypted) 179 | } 180 | 181 | dataDisks = append(dataDisks, dataDisk) 182 | } 183 | request.DataDisk = &dataDisks 184 | 185 | return request, nil 186 | } 187 | 188 | func (s *stepCreateAlicloudInstance) getUserData(state multistep.StateBag) (string, error) { 189 | userData := s.UserData 190 | 191 | if s.UserDataFile != "" { 192 | data, err := ioutil.ReadFile(s.UserDataFile) 193 | if err != nil { 194 | return "", err 195 | } 196 | 197 | userData = string(data) 198 | } 199 | 200 | if userData != "" { 201 | userData = base64.StdEncoding.EncodeToString([]byte(userData)) 202 | } 203 | 204 | return userData, nil 205 | 206 | } 207 | -------------------------------------------------------------------------------- /ecs/step_config_vswitch.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 8 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 9 | "github.com/hashicorp/packer/common/uuid" 10 | "github.com/hashicorp/packer/helper/multistep" 11 | "github.com/hashicorp/packer/packer" 12 | ) 13 | 14 | type stepConfigAlicloudVSwitch struct { 15 | VSwitchId string 16 | ZoneId string 17 | isCreate bool 18 | CidrBlock string 19 | VSwitchName string 20 | } 21 | 22 | var createVSwitchRetryErrors = []string{ 23 | "TOKEN_PROCESSING", 24 | } 25 | 26 | var deleteVSwitchRetryErrors = []string{ 27 | "IncorrectVSwitchStatus", 28 | "DependencyViolation", 29 | "DependencyViolation.HaVip", 30 | "IncorrectRouteEntryStatus", 31 | "TaskConflict", 32 | } 33 | 34 | func (s *stepConfigAlicloudVSwitch) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { 35 | client := state.Get("client").(*ClientWrapper) 36 | ui := state.Get("ui").(packer.Ui) 37 | vpcId := state.Get("vpcid").(string) 38 | config := state.Get("config").(*Config) 39 | 40 | if len(s.VSwitchId) != 0 { 41 | describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest() 42 | describeVSwitchesRequest.VpcId = vpcId 43 | describeVSwitchesRequest.VSwitchId = s.VSwitchId 44 | describeVSwitchesRequest.ZoneId = s.ZoneId 45 | 46 | vswitchesResponse, err := client.DescribeVSwitches(describeVSwitchesRequest) 47 | if err != nil { 48 | return halt(state, err, "Failed querying vswitch") 49 | } 50 | 51 | vswitch := vswitchesResponse.VSwitches.VSwitch 52 | if len(vswitch) > 0 { 53 | state.Put("vswitchid", vswitch[0].VSwitchId) 54 | s.isCreate = false 55 | return multistep.ActionContinue 56 | } 57 | 58 | s.isCreate = false 59 | return halt(state, fmt.Errorf("The specified vswitch {%s} doesn't exist.", s.VSwitchId), "") 60 | } 61 | 62 | if s.ZoneId == "" { 63 | describeZonesRequest := ecs.CreateDescribeZonesRequest() 64 | describeZonesRequest.RegionId = config.AlicloudRegion 65 | 66 | zonesResponse, err := client.DescribeZones(describeZonesRequest) 67 | if err != nil { 68 | return halt(state, err, "Query for available zones failed") 69 | } 70 | 71 | var instanceTypes []string 72 | zones := zonesResponse.Zones.Zone 73 | for _, zone := range zones { 74 | isVSwitchSupported := false 75 | for _, resourceType := range zone.AvailableResourceCreation.ResourceTypes { 76 | if resourceType == "VSwitch" { 77 | isVSwitchSupported = true 78 | } 79 | } 80 | 81 | if isVSwitchSupported { 82 | for _, instanceType := range zone.AvailableInstanceTypes.InstanceTypes { 83 | if instanceType == config.InstanceType { 84 | s.ZoneId = zone.ZoneId 85 | break 86 | } 87 | instanceTypes = append(instanceTypes, instanceType) 88 | } 89 | } 90 | } 91 | 92 | if s.ZoneId == "" { 93 | if len(instanceTypes) > 0 { 94 | ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+ 95 | "\n You can either change the instance to one of following: %v \n"+ 96 | "or choose another region.", config.InstanceType, instanceTypes)) 97 | 98 | state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+ 99 | "\n You can either change the instance to one of following: %v \n"+ 100 | "or choose another region.", config.InstanceType, instanceTypes)) 101 | return multistep.ActionHalt 102 | } else { 103 | ui.Say(fmt.Sprintf("The instance type %s isn't available in this region."+ 104 | "\n You can change to other regions.", config.InstanceType)) 105 | 106 | state.Put("error", fmt.Errorf("The instance type %s isn't available in this region."+ 107 | "\n You can change to other regions.", config.InstanceType)) 108 | return multistep.ActionHalt 109 | } 110 | } 111 | } 112 | 113 | if config.CidrBlock == "" { 114 | s.CidrBlock = DefaultCidrBlock //use the default CirdBlock 115 | } 116 | 117 | ui.Say("Creating vswitch...") 118 | 119 | createVSwitchRequest := s.buildCreateVSwitchRequest(state) 120 | createVSwitchResponse, err := client.WaitForExpected(&WaitForExpectArgs{ 121 | RequestFunc: func() (responses.AcsResponse, error) { 122 | return client.CreateVSwitch(createVSwitchRequest) 123 | }, 124 | EvalFunc: client.EvalCouldRetryResponse(createVSwitchRetryErrors, EvalRetryErrorType), 125 | }) 126 | if err != nil { 127 | return halt(state, err, "Error Creating vswitch") 128 | } 129 | 130 | vSwitchId := createVSwitchResponse.(*ecs.CreateVSwitchResponse).VSwitchId 131 | 132 | describeVSwitchesRequest := ecs.CreateDescribeVSwitchesRequest() 133 | describeVSwitchesRequest.VpcId = vpcId 134 | describeVSwitchesRequest.VSwitchId = vSwitchId 135 | 136 | _, err = client.WaitForExpected(&WaitForExpectArgs{ 137 | RequestFunc: func() (responses.AcsResponse, error) { 138 | return client.DescribeVSwitches(describeVSwitchesRequest) 139 | }, 140 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 141 | if err != nil { 142 | return WaitForExpectToRetry 143 | } 144 | 145 | vSwitchesResponse := response.(*ecs.DescribeVSwitchesResponse) 146 | vSwitches := vSwitchesResponse.VSwitches.VSwitch 147 | if len(vSwitches) > 0 { 148 | for _, vSwitch := range vSwitches { 149 | if vSwitch.Status == VSwitchStatusAvailable { 150 | return WaitForExpectSuccess 151 | } 152 | } 153 | } 154 | 155 | return WaitForExpectToRetry 156 | }, 157 | RetryTimes: shortRetryTimes, 158 | }) 159 | 160 | if err != nil { 161 | return halt(state, err, "Timeout waiting for vswitch to become available") 162 | } 163 | 164 | ui.Message(fmt.Sprintf("Created vswitch: %s", vSwitchId)) 165 | state.Put("vswitchid", vSwitchId) 166 | s.isCreate = true 167 | s.VSwitchId = vSwitchId 168 | return multistep.ActionContinue 169 | } 170 | 171 | func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) { 172 | if !s.isCreate { 173 | return 174 | } 175 | 176 | cleanUpMessage(state, "vSwitch") 177 | 178 | client := state.Get("client").(*ClientWrapper) 179 | ui := state.Get("ui").(packer.Ui) 180 | 181 | _, err := client.WaitForExpected(&WaitForExpectArgs{ 182 | RequestFunc: func() (responses.AcsResponse, error) { 183 | request := ecs.CreateDeleteVSwitchRequest() 184 | request.VSwitchId = s.VSwitchId 185 | return client.DeleteVSwitch(request) 186 | }, 187 | EvalFunc: client.EvalCouldRetryResponse(deleteVSwitchRetryErrors, EvalRetryErrorType), 188 | RetryTimes: shortRetryTimes, 189 | }) 190 | 191 | if err != nil { 192 | ui.Error(fmt.Sprintf("Error deleting vswitch, it may still be around: %s", err)) 193 | } 194 | } 195 | 196 | func (s *stepConfigAlicloudVSwitch) buildCreateVSwitchRequest(state multistep.StateBag) *ecs.CreateVSwitchRequest { 197 | vpcId := state.Get("vpcid").(string) 198 | 199 | request := ecs.CreateCreateVSwitchRequest() 200 | request.ClientToken = uuid.TimeOrderedUUID() 201 | request.CidrBlock = s.CidrBlock 202 | request.ZoneId = s.ZoneId 203 | request.VpcId = vpcId 204 | request.VSwitchName = s.VSwitchName 205 | 206 | return request 207 | } 208 | -------------------------------------------------------------------------------- /ecs/builder_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/hashicorp/packer/packer" 8 | ) 9 | 10 | func testBuilderConfig() map[string]interface{} { 11 | return map[string]interface{}{ 12 | "access_key": "foo", 13 | "secret_key": "bar", 14 | "source_image": "foo", 15 | "instance_type": "ecs.n1.tiny", 16 | "region": "cn-beijing", 17 | "ssh_username": "root", 18 | "image_name": "foo", 19 | "io_optimized": true, 20 | } 21 | } 22 | 23 | func TestBuilder_ImplementsBuilder(t *testing.T) { 24 | var raw interface{} 25 | raw = &Builder{} 26 | if _, ok := raw.(packer.Builder); !ok { 27 | t.Fatalf("Builder should be a builder") 28 | } 29 | } 30 | 31 | func TestBuilder_Prepare_BadType(t *testing.T) { 32 | b := &Builder{} 33 | c := map[string]interface{}{ 34 | "access_key": []string{}, 35 | } 36 | 37 | warnings, err := b.Prepare(c) 38 | if len(warnings) > 0 { 39 | t.Fatalf("bad: %#v", warnings) 40 | } 41 | if err == nil { 42 | t.Fatalf("prepare should fail") 43 | } 44 | } 45 | 46 | func TestBuilderPrepare_ECSImageName(t *testing.T) { 47 | var b Builder 48 | config := testBuilderConfig() 49 | 50 | // Test good 51 | config["image_name"] = "ecs.n1.tiny" 52 | warnings, err := b.Prepare(config) 53 | if len(warnings) > 0 { 54 | t.Fatalf("bad: %#v", warnings) 55 | } 56 | if err != nil { 57 | t.Fatalf("should not have error: %s", err) 58 | } 59 | 60 | // Test bad 61 | config["ecs_image_name"] = "foo {{" 62 | b = Builder{} 63 | warnings, err = b.Prepare(config) 64 | if len(warnings) > 0 { 65 | t.Fatalf("bad: %#v", warnings) 66 | } 67 | if err == nil { 68 | t.Fatal("should have error") 69 | } 70 | 71 | // Test bad 72 | delete(config, "image_name") 73 | b = Builder{} 74 | warnings, err = b.Prepare(config) 75 | if len(warnings) > 0 { 76 | t.Fatalf("bad: %#v", warnings) 77 | } 78 | if err == nil { 79 | t.Fatal("should have error") 80 | } 81 | } 82 | 83 | func TestBuilderPrepare_InvalidKey(t *testing.T) { 84 | var b Builder 85 | config := testBuilderConfig() 86 | 87 | // Add a random key 88 | config["i_should_not_be_valid"] = true 89 | warnings, err := b.Prepare(config) 90 | if len(warnings) > 0 { 91 | t.Fatalf("bad: %#v", warnings) 92 | } 93 | if err == nil { 94 | t.Fatal("should have error") 95 | } 96 | } 97 | 98 | func TestBuilderPrepare_Devices(t *testing.T) { 99 | var b Builder 100 | config := testBuilderConfig() 101 | config["system_disk_mapping"] = map[string]interface{}{ 102 | "disk_category": "cloud", 103 | "disk_description": "system disk", 104 | "disk_name": "system_disk", 105 | "disk_size": 60, 106 | } 107 | config["image_disk_mappings"] = []map[string]interface{}{ 108 | { 109 | "disk_category": "cloud_efficiency", 110 | "disk_name": "data_disk1", 111 | "disk_size": 100, 112 | "disk_snapshot_id": "s-1", 113 | "disk_description": "data disk1", 114 | "disk_device": "/dev/xvdb", 115 | "disk_delete_with_instance": false, 116 | }, 117 | { 118 | "disk_name": "data_disk2", 119 | "disk_device": "/dev/xvdc", 120 | }, 121 | } 122 | warnings, err := b.Prepare(config) 123 | if len(warnings) > 0 { 124 | t.Fatalf("bad: %#v", warnings) 125 | } 126 | if err != nil { 127 | t.Fatalf("should not have error: %s", err) 128 | } 129 | if !reflect.DeepEqual(b.config.ECSSystemDiskMapping, AlicloudDiskDevice{ 130 | DiskCategory: "cloud", 131 | Description: "system disk", 132 | DiskName: "system_disk", 133 | DiskSize: 60, 134 | }) { 135 | t.Fatalf("system disk is not set properly, actual: %#v", b.config.ECSSystemDiskMapping) 136 | } 137 | if !reflect.DeepEqual(b.config.ECSImagesDiskMappings, []AlicloudDiskDevice{ 138 | { 139 | DiskCategory: "cloud_efficiency", 140 | DiskName: "data_disk1", 141 | DiskSize: 100, 142 | SnapshotId: "s-1", 143 | Description: "data disk1", 144 | Device: "/dev/xvdb", 145 | DeleteWithInstance: false, 146 | }, 147 | { 148 | DiskName: "data_disk2", 149 | Device: "/dev/xvdc", 150 | }, 151 | }) { 152 | t.Fatalf("data disks are not set properly, actual: %#v", b.config.ECSImagesDiskMappings) 153 | } 154 | } 155 | 156 | func TestBuilderPrepare_IgnoreDataDisks(t *testing.T) { 157 | var b Builder 158 | config := testBuilderConfig() 159 | 160 | warnings, err := b.Prepare(config) 161 | if len(warnings) > 0 { 162 | t.Fatalf("bad: %#v", warnings) 163 | } 164 | if err != nil { 165 | t.Fatalf("should not have error: %s", err) 166 | } 167 | 168 | if b.config.AlicloudImageIgnoreDataDisks != false { 169 | t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks) 170 | } 171 | 172 | config["image_ignore_data_disks"] = "false" 173 | warnings, err = b.Prepare(config) 174 | if len(warnings) > 0 { 175 | t.Fatalf("bad: %#v", warnings) 176 | } 177 | if err != nil { 178 | t.Fatalf("should not have error: %s", err) 179 | } 180 | 181 | if b.config.AlicloudImageIgnoreDataDisks != false { 182 | t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", false, b.config.AlicloudImageIgnoreDataDisks) 183 | } 184 | 185 | config["image_ignore_data_disks"] = "true" 186 | warnings, err = b.Prepare(config) 187 | if len(warnings) > 0 { 188 | t.Fatalf("bad: %#v", warnings) 189 | } 190 | if err != nil { 191 | t.Fatalf("should not have error: %s", err) 192 | } 193 | 194 | if b.config.AlicloudImageIgnoreDataDisks != true { 195 | t.Fatalf("image_ignore_data_disks is not set properly, expect: %t, actual: %t", true, b.config.AlicloudImageIgnoreDataDisks) 196 | } 197 | } 198 | 199 | func TestBuilderPrepare_WaitSnapshotReadyTimeout(t *testing.T) { 200 | var b Builder 201 | config := testBuilderConfig() 202 | 203 | warnings, err := b.Prepare(config) 204 | if len(warnings) > 0 { 205 | t.Fatalf("bad: %#v", warnings) 206 | } 207 | if err != nil { 208 | t.Fatalf("should not have error: %s", err) 209 | } 210 | 211 | if b.config.WaitSnapshotReadyTimeout != 0 { 212 | t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", 0, b.config.WaitSnapshotReadyTimeout) 213 | } 214 | if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_LONG_TIMEOUT { 215 | t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_LONG_TIMEOUT, b.getSnapshotReadyTimeout()) 216 | } 217 | 218 | config["wait_snapshot_ready_timeout"] = ALICLOUD_DEFAULT_TIMEOUT 219 | warnings, err = b.Prepare(config) 220 | if len(warnings) > 0 { 221 | t.Fatalf("bad: %#v", warnings) 222 | } 223 | if err != nil { 224 | t.Fatalf("should not have error: %s", err) 225 | } 226 | 227 | if b.config.WaitSnapshotReadyTimeout != ALICLOUD_DEFAULT_TIMEOUT { 228 | t.Fatalf("wait_snapshot_ready_timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.config.WaitSnapshotReadyTimeout) 229 | } 230 | 231 | if b.getSnapshotReadyTimeout() != ALICLOUD_DEFAULT_TIMEOUT { 232 | t.Fatalf("default timeout is not set properly, expect: %d, actual: %d", ALICLOUD_DEFAULT_TIMEOUT, b.getSnapshotReadyTimeout()) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /ecs/builder.go: -------------------------------------------------------------------------------- 1 | // The alicloud contains a packer.Builder implementation that 2 | // builds ecs images for alicloud. 3 | package ecs 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "github.com/hashicorp/packer/common" 10 | "github.com/hashicorp/packer/helper/communicator" 11 | "github.com/hashicorp/packer/helper/config" 12 | "github.com/hashicorp/packer/helper/multistep" 13 | "github.com/hashicorp/packer/packer" 14 | "github.com/hashicorp/packer/template/interpolate" 15 | ) 16 | 17 | // The unique ID for this builder 18 | const BuilderId = "alibaba.alicloud" 19 | 20 | type Config struct { 21 | common.PackerConfig `mapstructure:",squash"` 22 | AlicloudAccessConfig `mapstructure:",squash"` 23 | AlicloudImageConfig `mapstructure:",squash"` 24 | RunConfig `mapstructure:",squash"` 25 | 26 | ctx interpolate.Context 27 | } 28 | 29 | type Builder struct { 30 | config Config 31 | runner multistep.Runner 32 | } 33 | 34 | type InstanceNetWork string 35 | 36 | const ( 37 | ALICLOUD_DEFAULT_SHORT_TIMEOUT = 180 38 | ALICLOUD_DEFAULT_TIMEOUT = 1800 39 | ALICLOUD_DEFAULT_LONG_TIMEOUT = 3600 40 | ) 41 | 42 | func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 43 | err := config.Decode(&b.config, &config.DecodeOpts{ 44 | Interpolate: true, 45 | InterpolateContext: &b.config.ctx, 46 | InterpolateFilter: &interpolate.RenderFilter{ 47 | Exclude: []string{ 48 | "run_command", 49 | }, 50 | }, 51 | }, raws...) 52 | b.config.ctx.EnableEnv = true 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if b.config.PackerConfig.PackerForce { 58 | b.config.AlicloudImageForceDelete = true 59 | b.config.AlicloudImageForceDeleteSnapshots = true 60 | } 61 | 62 | // Accumulate any errors 63 | var errs *packer.MultiError 64 | errs = packer.MultiErrorAppend(errs, b.config.AlicloudAccessConfig.Prepare(&b.config.ctx)...) 65 | errs = packer.MultiErrorAppend(errs, b.config.AlicloudImageConfig.Prepare(&b.config.ctx)...) 66 | errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) 67 | 68 | if errs != nil && len(errs.Errors) > 0 { 69 | return nil, errs 70 | } 71 | 72 | packer.LogSecretFilter.Set(b.config.AlicloudAccessKey, b.config.AlicloudSecretKey) 73 | return nil, nil 74 | } 75 | 76 | func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { 77 | 78 | client, err := b.config.Client() 79 | if err != nil { 80 | return nil, err 81 | } 82 | state := new(multistep.BasicStateBag) 83 | state.Put("config", &b.config) 84 | state.Put("client", client) 85 | state.Put("hook", hook) 86 | state.Put("ui", ui) 87 | state.Put("networktype", b.chooseNetworkType()) 88 | var steps []multistep.Step 89 | 90 | // Build the steps 91 | steps = []multistep.Step{ 92 | &stepPreValidate{ 93 | AlicloudDestImageName: b.config.AlicloudImageName, 94 | ForceDelete: b.config.AlicloudImageForceDelete, 95 | }, 96 | &stepCheckAlicloudSourceImage{ 97 | SourceECSImageId: b.config.AlicloudSourceImage, 98 | }, 99 | &stepConfigAlicloudKeyPair{ 100 | Debug: b.config.PackerDebug, 101 | Comm: &b.config.Comm, 102 | DebugKeyPath: fmt.Sprintf("ecs_%s.pem", b.config.PackerBuildName), 103 | RegionId: b.config.AlicloudRegion, 104 | }, 105 | } 106 | if b.chooseNetworkType() == InstanceNetworkVpc { 107 | steps = append(steps, 108 | &stepConfigAlicloudVPC{ 109 | VpcId: b.config.VpcId, 110 | CidrBlock: b.config.CidrBlock, 111 | VpcName: b.config.VpcName, 112 | }, 113 | &stepConfigAlicloudVSwitch{ 114 | VSwitchId: b.config.VSwitchId, 115 | ZoneId: b.config.ZoneId, 116 | CidrBlock: b.config.CidrBlock, 117 | VSwitchName: b.config.VSwitchName, 118 | }) 119 | } 120 | steps = append(steps, 121 | &stepConfigAlicloudSecurityGroup{ 122 | SecurityGroupId: b.config.SecurityGroupId, 123 | SecurityGroupName: b.config.SecurityGroupId, 124 | RegionId: b.config.AlicloudRegion, 125 | }, 126 | &stepCreateAlicloudInstance{ 127 | IOOptimized: b.config.IOOptimized, 128 | InstanceType: b.config.InstanceType, 129 | UserData: b.config.UserData, 130 | UserDataFile: b.config.UserDataFile, 131 | RegionId: b.config.AlicloudRegion, 132 | InternetChargeType: b.config.InternetChargeType, 133 | InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut, 134 | InstanceName: b.config.InstanceName, 135 | ZoneId: b.config.ZoneId, 136 | }) 137 | if b.chooseNetworkType() == InstanceNetworkVpc { 138 | steps = append(steps, &stepConfigAlicloudEIP{ 139 | AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, 140 | RegionId: b.config.AlicloudRegion, 141 | InternetChargeType: b.config.InternetChargeType, 142 | InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut, 143 | SSHPrivateIp: b.config.SSHPrivateIp, 144 | }) 145 | } else { 146 | steps = append(steps, &stepConfigAlicloudPublicIP{ 147 | RegionId: b.config.AlicloudRegion, 148 | SSHPrivateIp: b.config.SSHPrivateIp, 149 | }) 150 | } 151 | steps = append(steps, 152 | &stepAttachKeyPair{}, 153 | &stepRunAlicloudInstance{}, 154 | &communicator.StepConnect{ 155 | Config: &b.config.RunConfig.Comm, 156 | Host: SSHHost( 157 | client, 158 | b.config.SSHPrivateIp), 159 | SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(), 160 | }, 161 | &common.StepProvision{}, 162 | &common.StepCleanupTempKeys{ 163 | Comm: &b.config.RunConfig.Comm, 164 | }, 165 | &stepStopAlicloudInstance{ 166 | ForceStop: b.config.ForceStopInstance, 167 | DisableStop: b.config.DisableStopInstance, 168 | }, 169 | &stepDeleteAlicloudImageSnapshots{ 170 | AlicloudImageForceDeleteSnapshots: b.config.AlicloudImageForceDeleteSnapshots, 171 | AlicloudImageForceDelete: b.config.AlicloudImageForceDelete, 172 | AlicloudImageName: b.config.AlicloudImageName, 173 | AlicloudImageDestinationRegions: b.config.AlicloudImageConfig.AlicloudImageDestinationRegions, 174 | AlicloudImageDestinationNames: b.config.AlicloudImageConfig.AlicloudImageDestinationNames, 175 | }) 176 | 177 | if b.config.AlicloudImageIgnoreDataDisks { 178 | steps = append(steps, &stepCreateAlicloudSnapshot{ 179 | WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(), 180 | }) 181 | } 182 | 183 | steps = append(steps, 184 | &stepCreateAlicloudImage{ 185 | AlicloudImageIgnoreDataDisks: b.config.AlicloudImageIgnoreDataDisks, 186 | WaitSnapshotReadyTimeout: b.getSnapshotReadyTimeout(), 187 | }, 188 | &stepCreateTags{ 189 | Tags: b.config.AlicloudImageTags, 190 | }, 191 | &stepRegionCopyAlicloudImage{ 192 | AlicloudImageDestinationRegions: b.config.AlicloudImageDestinationRegions, 193 | AlicloudImageDestinationNames: b.config.AlicloudImageDestinationNames, 194 | RegionId: b.config.AlicloudRegion, 195 | }, 196 | &stepShareAlicloudImage{ 197 | AlicloudImageShareAccounts: b.config.AlicloudImageShareAccounts, 198 | AlicloudImageUNShareAccounts: b.config.AlicloudImageUNShareAccounts, 199 | RegionId: b.config.AlicloudRegion, 200 | }) 201 | 202 | // Run! 203 | b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 204 | b.runner.Run(ctx, state) 205 | 206 | // If there was an error, return that 207 | if rawErr, ok := state.GetOk("error"); ok { 208 | return nil, rawErr.(error) 209 | } 210 | 211 | // If there are no ECS images, then just return 212 | if _, ok := state.GetOk("alicloudimages"); !ok { 213 | return nil, nil 214 | } 215 | 216 | // Build the artifact and return it 217 | artifact := &Artifact{ 218 | AlicloudImages: state.Get("alicloudimages").(map[string]string), 219 | BuilderIdValue: BuilderId, 220 | Client: client, 221 | } 222 | 223 | return artifact, nil 224 | } 225 | 226 | func (b *Builder) chooseNetworkType() InstanceNetWork { 227 | if b.isVpcNetRequired() { 228 | return InstanceNetworkVpc 229 | } else { 230 | return InstanceNetworkClassic 231 | } 232 | } 233 | 234 | func (b *Builder) isVpcNetRequired() bool { 235 | // UserData and KeyPair only works in VPC 236 | return b.isVpcSpecified() || b.isUserDataNeeded() || b.isKeyPairNeeded() 237 | } 238 | 239 | func (b *Builder) isVpcSpecified() bool { 240 | return b.config.VpcId != "" || b.config.VSwitchId != "" 241 | } 242 | 243 | func (b *Builder) isUserDataNeeded() bool { 244 | // Public key setup requires userdata 245 | if b.config.RunConfig.Comm.SSHPrivateKeyFile != "" { 246 | return true 247 | } 248 | 249 | return b.config.UserData != "" || b.config.UserDataFile != "" 250 | } 251 | 252 | func (b *Builder) isKeyPairNeeded() bool { 253 | return b.config.Comm.SSHKeyPairName != "" || b.config.Comm.SSHTemporaryKeyPairName != "" 254 | } 255 | 256 | func (b *Builder) getSnapshotReadyTimeout() int { 257 | if b.config.WaitSnapshotReadyTimeout > 0 { 258 | return b.config.WaitSnapshotReadyTimeout 259 | } 260 | 261 | return ALICLOUD_DEFAULT_LONG_TIMEOUT 262 | } 263 | -------------------------------------------------------------------------------- /ecs/client.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" 8 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 9 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 10 | ) 11 | 12 | type ClientWrapper struct { 13 | *ecs.Client 14 | } 15 | 16 | const ( 17 | InstanceStatusRunning = "Running" 18 | InstanceStatusStarting = "Starting" 19 | InstanceStatusStopped = "Stopped" 20 | InstanceStatusStopping = "Stopping" 21 | ) 22 | 23 | const ( 24 | ImageStatusWaiting = "Waiting" 25 | ImageStatusCreating = "Creating" 26 | ImageStatusCreateFailed = "CreateFailed" 27 | ImageStatusAvailable = "Available" 28 | ) 29 | 30 | var ImageStatusQueried = fmt.Sprintf("%s,%s,%s,%s", ImageStatusWaiting, ImageStatusCreating, ImageStatusCreateFailed, ImageStatusAvailable) 31 | 32 | const ( 33 | SnapshotStatusAll = "all" 34 | SnapshotStatusProgressing = "progressing" 35 | SnapshotStatusAccomplished = "accomplished" 36 | SnapshotStatusFailed = "failed" 37 | ) 38 | 39 | const ( 40 | DiskStatusInUse = "In_use" 41 | DiskStatusAvailable = "Available" 42 | DiskStatusAttaching = "Attaching" 43 | DiskStatusDetaching = "Detaching" 44 | DiskStatusCreating = "Creating" 45 | DiskStatusReIniting = "ReIniting" 46 | ) 47 | 48 | const ( 49 | VpcStatusPending = "Pending" 50 | VpcStatusAvailable = "Available" 51 | ) 52 | 53 | const ( 54 | VSwitchStatusPending = "Pending" 55 | VSwitchStatusAvailable = "Available" 56 | ) 57 | 58 | const ( 59 | EipStatusAssociating = "Associating" 60 | EipStatusUnassociating = "Unassociating" 61 | EipStatusInUse = "InUse" 62 | EipStatusAvailable = "Available" 63 | ) 64 | 65 | const ( 66 | ImageOwnerSystem = "system" 67 | ImageOwnerSelf = "self" 68 | ImageOwnerOthers = "others" 69 | ImageOwnerMarketplace = "marketplace" 70 | ) 71 | 72 | const ( 73 | IOOptimizedNone = "none" 74 | IOOptimizedOptimized = "optimized" 75 | ) 76 | 77 | const ( 78 | InstanceNetworkClassic = "classic" 79 | InstanceNetworkVpc = "vpc" 80 | ) 81 | 82 | const ( 83 | DiskTypeSystem = "system" 84 | DiskTypeData = "data" 85 | ) 86 | 87 | const ( 88 | TagResourceImage = "image" 89 | TagResourceInstance = "instance" 90 | TagResourceSnapshot = "snapshot" 91 | TagResourceDisk = "disk" 92 | ) 93 | 94 | const ( 95 | IpProtocolAll = "all" 96 | IpProtocolTCP = "tcp" 97 | IpProtocolUDP = "udp" 98 | IpProtocolICMP = "icmp" 99 | IpProtocolGRE = "gre" 100 | ) 101 | 102 | const ( 103 | NicTypeInternet = "internet" 104 | NicTypeIntranet = "intranet" 105 | ) 106 | 107 | const ( 108 | DefaultPortRange = "-1/-1" 109 | DefaultCidrIp = "0.0.0.0/0" 110 | DefaultCidrBlock = "172.16.0.0/24" 111 | ) 112 | 113 | const ( 114 | defaultRetryInterval = 5 * time.Second 115 | defaultRetryTimes = 12 116 | shortRetryTimes = 36 117 | mediumRetryTimes = 360 118 | longRetryTimes = 720 119 | ) 120 | 121 | type WaitForExpectEvalResult struct { 122 | evalPass bool 123 | stopRetry bool 124 | } 125 | 126 | var ( 127 | WaitForExpectSuccess = WaitForExpectEvalResult{ 128 | evalPass: true, 129 | stopRetry: true, 130 | } 131 | 132 | WaitForExpectToRetry = WaitForExpectEvalResult{ 133 | evalPass: false, 134 | stopRetry: false, 135 | } 136 | 137 | WaitForExpectFailToStop = WaitForExpectEvalResult{ 138 | evalPass: false, 139 | stopRetry: true, 140 | } 141 | ) 142 | 143 | type WaitForExpectArgs struct { 144 | RequestFunc func() (responses.AcsResponse, error) 145 | EvalFunc func(response responses.AcsResponse, err error) WaitForExpectEvalResult 146 | RetryInterval time.Duration 147 | RetryTimes int 148 | RetryTimeout time.Duration 149 | } 150 | 151 | func (c *ClientWrapper) WaitForExpected(args *WaitForExpectArgs) (responses.AcsResponse, error) { 152 | if args.RetryInterval <= 0 { 153 | args.RetryInterval = defaultRetryInterval 154 | } 155 | if args.RetryTimes <= 0 { 156 | args.RetryTimes = defaultRetryTimes 157 | } 158 | 159 | var timeoutPoint time.Time 160 | if args.RetryTimeout > 0 { 161 | timeoutPoint = time.Now().Add(args.RetryTimeout) 162 | } 163 | 164 | var lastResponse responses.AcsResponse 165 | var lastError error 166 | 167 | for i := 0; ; i++ { 168 | if args.RetryTimeout > 0 && time.Now().After(timeoutPoint) { 169 | break 170 | } 171 | 172 | if args.RetryTimeout <= 0 && i >= args.RetryTimes { 173 | break 174 | } 175 | 176 | response, err := args.RequestFunc() 177 | lastResponse = response 178 | lastError = err 179 | 180 | evalResult := args.EvalFunc(response, err) 181 | if evalResult.evalPass { 182 | return response, nil 183 | } 184 | if evalResult.stopRetry { 185 | return response, err 186 | } 187 | 188 | time.Sleep(args.RetryInterval) 189 | } 190 | 191 | if lastError == nil { 192 | lastError = fmt.Errorf("") 193 | } 194 | 195 | if args.RetryTimeout > 0 { 196 | return lastResponse, fmt.Errorf("evaluate failed after %d seconds timeout with %d seconds retry interval: %s", int(args.RetryTimeout.Seconds()), int(args.RetryInterval.Seconds()), lastError) 197 | } 198 | 199 | return lastResponse, fmt.Errorf("evaluate failed after %d times retry with %d seconds retry interval: %s", args.RetryTimes, int(args.RetryInterval.Seconds()), lastError) 200 | } 201 | 202 | func (c *ClientWrapper) WaitForInstanceStatus(regionId string, instanceId string, expectedStatus string) (responses.AcsResponse, error) { 203 | return c.WaitForExpected(&WaitForExpectArgs{ 204 | RequestFunc: func() (responses.AcsResponse, error) { 205 | request := ecs.CreateDescribeInstancesRequest() 206 | request.RegionId = regionId 207 | request.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId) 208 | return c.DescribeInstances(request) 209 | }, 210 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 211 | if err != nil { 212 | return WaitForExpectToRetry 213 | } 214 | 215 | instancesResponse := response.(*ecs.DescribeInstancesResponse) 216 | instances := instancesResponse.Instances.Instance 217 | for _, instance := range instances { 218 | if instance.Status == expectedStatus { 219 | return WaitForExpectSuccess 220 | } 221 | } 222 | return WaitForExpectToRetry 223 | }, 224 | RetryTimes: mediumRetryTimes, 225 | }) 226 | } 227 | 228 | func (c *ClientWrapper) WaitForImageStatus(regionId string, imageId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) { 229 | return c.WaitForExpected(&WaitForExpectArgs{ 230 | RequestFunc: func() (responses.AcsResponse, error) { 231 | request := ecs.CreateDescribeImagesRequest() 232 | request.RegionId = regionId 233 | request.ImageId = imageId 234 | request.Status = ImageStatusQueried 235 | return c.DescribeImages(request) 236 | }, 237 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 238 | if err != nil { 239 | return WaitForExpectToRetry 240 | } 241 | 242 | imagesResponse := response.(*ecs.DescribeImagesResponse) 243 | images := imagesResponse.Images.Image 244 | for _, image := range images { 245 | if image.Status == expectedStatus { 246 | return WaitForExpectSuccess 247 | } 248 | } 249 | 250 | return WaitForExpectToRetry 251 | }, 252 | RetryTimeout: timeout, 253 | }) 254 | } 255 | 256 | func (c *ClientWrapper) WaitForSnapshotStatus(regionId string, snapshotId string, expectedStatus string, timeout time.Duration) (responses.AcsResponse, error) { 257 | return c.WaitForExpected(&WaitForExpectArgs{ 258 | RequestFunc: func() (responses.AcsResponse, error) { 259 | request := ecs.CreateDescribeSnapshotsRequest() 260 | request.RegionId = regionId 261 | request.SnapshotIds = fmt.Sprintf("[\"%s\"]", snapshotId) 262 | return c.DescribeSnapshots(request) 263 | }, 264 | EvalFunc: func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 265 | if err != nil { 266 | return WaitForExpectToRetry 267 | } 268 | 269 | snapshotsResponse := response.(*ecs.DescribeSnapshotsResponse) 270 | snapshots := snapshotsResponse.Snapshots.Snapshot 271 | for _, snapshot := range snapshots { 272 | if snapshot.Status == expectedStatus { 273 | return WaitForExpectSuccess 274 | } 275 | } 276 | return WaitForExpectToRetry 277 | }, 278 | RetryTimeout: timeout, 279 | }) 280 | } 281 | 282 | type EvalErrorType bool 283 | 284 | const ( 285 | EvalRetryErrorType = EvalErrorType(true) 286 | EvalNotRetryErrorType = EvalErrorType(false) 287 | ) 288 | 289 | func (c *ClientWrapper) EvalCouldRetryResponse(evalErrors []string, evalErrorType EvalErrorType) func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 290 | return func(response responses.AcsResponse, err error) WaitForExpectEvalResult { 291 | if err == nil { 292 | return WaitForExpectSuccess 293 | } 294 | 295 | e, ok := err.(errors.Error) 296 | if !ok { 297 | return WaitForExpectToRetry 298 | } 299 | 300 | if evalErrorType == EvalRetryErrorType && !ContainsInArray(evalErrors, e.ErrorCode()) { 301 | return WaitForExpectFailToStop 302 | } 303 | 304 | if evalErrorType == EvalNotRetryErrorType && ContainsInArray(evalErrors, e.ErrorCode()) { 305 | return WaitForExpectFailToStop 306 | } 307 | 308 | return WaitForExpectToRetry 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | 179 | Copyright 2017 Alibaba Group Holding Limited. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /alicloud-import/post-processor.go: -------------------------------------------------------------------------------- 1 | package alicloudimport 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 12 | 13 | packerecs "github.com/alibaba/packer-provider/ecs" 14 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors" 15 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" 16 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 17 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ram" 18 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 19 | "github.com/hashicorp/packer/common" 20 | "github.com/hashicorp/packer/helper/config" 21 | "github.com/hashicorp/packer/packer" 22 | "github.com/hashicorp/packer/template/interpolate" 23 | ) 24 | 25 | const ( 26 | Packer = "HashiCorp-Packer" 27 | BuilderId = "packer.post-processor.alicloud-import" 28 | OSSSuffix = "oss-" 29 | RAWFileFormat = "raw" 30 | VHDFileFormat = "vhd" 31 | ) 32 | 33 | const ( 34 | PolicyTypeSystem = "System" 35 | NoSetRoleError = "NoSetRoletoECSServiceAcount" 36 | RoleNotExistError = "EntityNotExist.Role" 37 | DefaultImportRoleName = "AliyunECSImageImportDefaultRole" 38 | DefaultImportPolicyName = "AliyunECSImageImportRolePolicy" 39 | DefaultImportRolePolicy = `{ 40 | "Statement": [ 41 | { 42 | "Action": "sts:AssumeRole", 43 | "Effect": "Allow", 44 | "Principal": { 45 | "Service": [ 46 | "ecs.aliyuncs.com" 47 | ] 48 | } 49 | } 50 | ], 51 | "Version": "1" 52 | }` 53 | ) 54 | 55 | // Configuration of this post processor 56 | type Config struct { 57 | common.PackerConfig `mapstructure:",squash"` 58 | packerecs.Config `mapstructure:",squash"` 59 | 60 | // Variables specific to this post processor 61 | OSSBucket string `mapstructure:"oss_bucket_name"` 62 | OSSKey string `mapstructure:"oss_key_name"` 63 | SkipClean bool `mapstructure:"skip_clean"` 64 | Tags map[string]string `mapstructure:"tags"` 65 | AlicloudImageName string `mapstructure:"image_name"` 66 | AlicloudImageDescription string `mapstructure:"image_description"` 67 | AlicloudImageShareAccounts []string `mapstructure:"image_share_account"` 68 | AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"` 69 | OSType string `mapstructure:"image_os_type"` 70 | Platform string `mapstructure:"image_platform"` 71 | Architecture string `mapstructure:"image_architecture"` 72 | Size string `mapstructure:"image_system_size"` 73 | Format string `mapstructure:"format"` 74 | AlicloudImageForceDelete bool `mapstructure:"image_force_delete"` 75 | 76 | ctx interpolate.Context 77 | } 78 | 79 | type PostProcessor struct { 80 | config Config 81 | DiskDeviceMapping []ecs.DiskDeviceMapping 82 | 83 | ossClient *oss.Client 84 | ramClient *ram.Client 85 | } 86 | 87 | // Entry point for configuration parsing when we've defined 88 | func (p *PostProcessor) Configure(raws ...interface{}) error { 89 | err := config.Decode(&p.config, &config.DecodeOpts{ 90 | Interpolate: true, 91 | InterpolateContext: &p.config.ctx, 92 | InterpolateFilter: &interpolate.RenderFilter{ 93 | Exclude: []string{ 94 | "oss_key_name", 95 | }, 96 | }, 97 | }, raws...) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | errs := new(packer.MultiError) 103 | 104 | // Check and render oss_key_name 105 | if err = interpolate.Validate(p.config.OSSKey, &p.config.ctx); err != nil { 106 | errs = packer.MultiErrorAppend( 107 | errs, fmt.Errorf("Error parsing oss_key_name template: %s", err)) 108 | } 109 | 110 | // Check we have alicloud access variables defined somewhere 111 | errs = packer.MultiErrorAppend(errs, p.config.AlicloudAccessConfig.Prepare(&p.config.ctx)...) 112 | 113 | // define all our required parameters 114 | templates := map[string]*string{ 115 | "oss_bucket_name": &p.config.OSSBucket, 116 | } 117 | // Check out required params are defined 118 | for key, ptr := range templates { 119 | if *ptr == "" { 120 | errs = packer.MultiErrorAppend( 121 | errs, fmt.Errorf("%s must be set", key)) 122 | } 123 | } 124 | 125 | // Anything which flagged return back up the stack 126 | if len(errs.Errors) > 0 { 127 | return errs 128 | } 129 | 130 | packer.LogSecretFilter.Set(p.config.AlicloudAccessKey, p.config.AlicloudSecretKey) 131 | log.Println(p.config) 132 | return nil 133 | } 134 | 135 | func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, bool, error) { 136 | var err error 137 | 138 | // Render this key since we didn't in the configure phase 139 | p.config.OSSKey, err = interpolate.Render(p.config.OSSKey, &p.config.ctx) 140 | if err != nil { 141 | return nil, false, false, fmt.Errorf("Error rendering oss_key_name template: %s", err) 142 | } 143 | if p.config.OSSKey == "" { 144 | p.config.OSSKey = "Packer_" + strconv.Itoa(time.Now().Nanosecond()) 145 | } 146 | 147 | ui.Say(fmt.Sprintf("Rendered oss_key_name as %s", p.config.OSSKey)) 148 | ui.Say("Looking for RAW or VHD in artifact") 149 | 150 | // Locate the files output from the builder 151 | source := "" 152 | for _, path := range artifact.Files() { 153 | if strings.HasSuffix(path, VHDFileFormat) || strings.HasSuffix(path, RAWFileFormat) { 154 | source = path 155 | break 156 | } 157 | } 158 | 159 | // Hope we found something useful 160 | if source == "" { 161 | return nil, false, false, fmt.Errorf("No vhd or raw file found in artifact from builder") 162 | } 163 | 164 | ecsClient, err := p.config.AlicloudAccessConfig.Client() 165 | if err != nil { 166 | return nil, false, false, fmt.Errorf("Failed to connect alicloud ecs %s", err) 167 | } 168 | 169 | endpoint := getEndPoint(p.config.AlicloudRegion, p.config.OSSBucket) 170 | 171 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 172 | describeImagesRequest.RegionId = p.config.AlicloudRegion 173 | describeImagesRequest.ImageName = p.config.AlicloudImageName 174 | imagesResponse, err := ecsClient.DescribeImages(describeImagesRequest) 175 | if err != nil { 176 | return nil, false, false, fmt.Errorf("Failed to start import from %s/%s: %s", endpoint, p.config.OSSKey, err) 177 | } 178 | 179 | images := imagesResponse.Images.Image 180 | if len(images) > 0 && !p.config.AlicloudImageForceDelete { 181 | return nil, false, false, fmt.Errorf("Duplicated image exists, please delete the existing images " + 182 | "or set the 'image_force_delete' value as true") 183 | } 184 | 185 | bucket, err := p.queryOrCreateBucket(p.config.OSSBucket) 186 | if err != nil { 187 | return nil, false, false, fmt.Errorf("Failed to query or create bucket %s: %s", p.config.OSSBucket, err) 188 | } 189 | 190 | ui.Say(fmt.Sprintf("Waiting for uploading file %s to %s/%s...", source, endpoint, p.config.OSSKey)) 191 | 192 | err = bucket.PutObjectFromFile(p.config.OSSKey, source) 193 | if err != nil { 194 | return nil, false, false, fmt.Errorf("Failed to upload image %s: %s", source, err) 195 | } 196 | 197 | ui.Say(fmt.Sprintf("Image file %s has been uploaded to OSS", source)) 198 | 199 | if len(images) > 0 && p.config.AlicloudImageForceDelete { 200 | deleteImageRequest := ecs.CreateDeleteImageRequest() 201 | deleteImageRequest.RegionId = p.config.AlicloudRegion 202 | deleteImageRequest.ImageId = images[0].ImageId 203 | _, err := ecsClient.DeleteImage(deleteImageRequest) 204 | if err != nil { 205 | return nil, false, false, fmt.Errorf("Delete duplicated image %s failed", images[0].ImageName) 206 | } 207 | } 208 | 209 | importImageRequest := p.buildImportImageRequest() 210 | importImageResponse, err := ecsClient.ImportImage(importImageRequest) 211 | if err != nil { 212 | e, ok := err.(errors.Error) 213 | if !ok || e.ErrorCode() != NoSetRoleError { 214 | return nil, false, false, fmt.Errorf("Failed to start import from %s/%s: %s", endpoint, p.config.OSSKey, err) 215 | } 216 | 217 | ui.Say("initialize ram role for importing image") 218 | if err := p.prepareImportRole(); err != nil { 219 | return nil, false, false, fmt.Errorf("Failed to start import from %s/%s: %s", endpoint, p.config.OSSKey, err) 220 | } 221 | 222 | acsResponse, err := ecsClient.WaitForExpected(&packerecs.WaitForExpectArgs{ 223 | RequestFunc: func() (responses.AcsResponse, error) { 224 | return ecsClient.ImportImage(importImageRequest) 225 | }, 226 | EvalFunc: func(response responses.AcsResponse, err error) packerecs.WaitForExpectEvalResult { 227 | if err == nil { 228 | return packerecs.WaitForExpectSuccess 229 | } 230 | 231 | e, ok = err.(errors.Error) 232 | if ok && packerecs.ContainsInArray([]string{ 233 | "ImageIsImporting", 234 | "InvalidImageName.Duplicated", 235 | }, e.ErrorCode()) { 236 | return packerecs.WaitForExpectSuccess 237 | } 238 | 239 | if ok && e.ErrorCode() != NoSetRoleError { 240 | return packerecs.WaitForExpectFailToStop 241 | } 242 | 243 | return packerecs.WaitForExpectToRetry 244 | }, 245 | }) 246 | 247 | if err != nil { 248 | return nil, false, false, fmt.Errorf("Failed to start import from %s/%s: %s", endpoint, p.config.OSSKey, err) 249 | } 250 | 251 | importImageResponse = acsResponse.(*ecs.ImportImageResponse) 252 | } 253 | 254 | imageId := importImageResponse.ImageId 255 | 256 | ui.Say(fmt.Sprintf("Waiting for importing %s/%s to alicloud...", endpoint, p.config.OSSKey)) 257 | _, err = ecsClient.WaitForImageStatus(p.config.AlicloudRegion, imageId, packerecs.ImageStatusAvailable, time.Duration(packerecs.ALICLOUD_DEFAULT_LONG_TIMEOUT)*time.Second) 258 | if err != nil { 259 | return nil, false, false, fmt.Errorf("Import image %s failed: %s", imageId, err) 260 | } 261 | 262 | // Add the reported Alicloud image ID to the artifact list 263 | ui.Say(fmt.Sprintf("Importing created alicloud image ID %s in region %s Finished.", imageId, p.config.AlicloudRegion)) 264 | artifact = &packerecs.Artifact{ 265 | AlicloudImages: map[string]string{ 266 | p.config.AlicloudRegion: imageId, 267 | }, 268 | BuilderIdValue: BuilderId, 269 | Client: ecsClient, 270 | } 271 | 272 | if !p.config.SkipClean { 273 | ui.Message(fmt.Sprintf("Deleting import source %s/%s/%s", endpoint, p.config.OSSBucket, p.config.OSSKey)) 274 | if err = bucket.DeleteObject(p.config.OSSKey); err != nil { 275 | return nil, false, false, fmt.Errorf("Failed to delete %s/%s/%s: %s", endpoint, p.config.OSSBucket, p.config.OSSKey, err) 276 | } 277 | } 278 | 279 | return artifact, false, false, nil 280 | } 281 | 282 | func (p *PostProcessor) getOssClient() *oss.Client { 283 | if p.ossClient == nil { 284 | log.Println("Creating OSS Client") 285 | ossClient, _ := oss.New(getEndPoint(p.config.AlicloudRegion, ""), p.config.AlicloudAccessKey, 286 | p.config.AlicloudSecretKey) 287 | p.ossClient = ossClient 288 | } 289 | 290 | return p.ossClient 291 | } 292 | 293 | func (p *PostProcessor) getRamClient() *ram.Client { 294 | if p.ramClient == nil { 295 | ramClient, _ := ram.NewClientWithAccessKey(p.config.AlicloudRegion, p.config.AlicloudAccessKey, p.config.AlicloudSecretKey) 296 | p.ramClient = ramClient 297 | } 298 | 299 | return p.ramClient 300 | } 301 | 302 | func (p *PostProcessor) queryOrCreateBucket(bucketName string) (*oss.Bucket, error) { 303 | ossClient := p.getOssClient() 304 | 305 | isExist, err := ossClient.IsBucketExist(bucketName) 306 | if err != nil { 307 | return nil, err 308 | } 309 | if !isExist { 310 | err = ossClient.CreateBucket(bucketName) 311 | if err != nil { 312 | return nil, err 313 | } 314 | } 315 | bucket, err := ossClient.Bucket(bucketName) 316 | if err != nil { 317 | return nil, err 318 | } 319 | return bucket, nil 320 | 321 | } 322 | 323 | func (p *PostProcessor) prepareImportRole() error { 324 | ramClient := p.getRamClient() 325 | 326 | getRoleRequest := ram.CreateGetRoleRequest() 327 | getRoleRequest.SetScheme(requests.HTTPS) 328 | getRoleRequest.RoleName = DefaultImportRoleName 329 | _, err := ramClient.GetRole(getRoleRequest) 330 | if err == nil { 331 | if e := p.updateOrAttachPolicy(); e != nil { 332 | return e 333 | } 334 | 335 | return nil 336 | } 337 | 338 | e, ok := err.(errors.Error) 339 | if !ok || e.ErrorCode() != RoleNotExistError { 340 | return e 341 | } 342 | 343 | if err := p.createRoleAndAttachPolicy(); err != nil { 344 | return err 345 | } 346 | 347 | time.Sleep(1 * time.Minute) 348 | return nil 349 | } 350 | 351 | func (p *PostProcessor) updateOrAttachPolicy() error { 352 | ramClient := p.getRamClient() 353 | 354 | listPoliciesForRoleRequest := ram.CreateListPoliciesForRoleRequest() 355 | listPoliciesForRoleRequest.SetScheme(requests.HTTPS) 356 | listPoliciesForRoleRequest.RoleName = DefaultImportRoleName 357 | policyListResponse, err := p.ramClient.ListPoliciesForRole(listPoliciesForRoleRequest) 358 | if err != nil { 359 | return fmt.Errorf("Failed to list policies: %s", err) 360 | } 361 | 362 | rolePolicyExists := false 363 | for _, policy := range policyListResponse.Policies.Policy { 364 | if policy.PolicyName == DefaultImportPolicyName && policy.PolicyType == PolicyTypeSystem { 365 | rolePolicyExists = true 366 | break 367 | } 368 | } 369 | 370 | if rolePolicyExists { 371 | updateRoleRequest := ram.CreateUpdateRoleRequest() 372 | updateRoleRequest.SetScheme(requests.HTTPS) 373 | updateRoleRequest.RoleName = DefaultImportRoleName 374 | updateRoleRequest.NewAssumeRolePolicyDocument = DefaultImportRolePolicy 375 | if _, err := ramClient.UpdateRole(updateRoleRequest); err != nil { 376 | return fmt.Errorf("Failed to update role policy: %s", err) 377 | } 378 | } else { 379 | attachPolicyToRoleRequest := ram.CreateAttachPolicyToRoleRequest() 380 | attachPolicyToRoleRequest.SetScheme(requests.HTTPS) 381 | attachPolicyToRoleRequest.PolicyName = DefaultImportPolicyName 382 | attachPolicyToRoleRequest.PolicyType = PolicyTypeSystem 383 | attachPolicyToRoleRequest.RoleName = DefaultImportRoleName 384 | if _, err := ramClient.AttachPolicyToRole(attachPolicyToRoleRequest); err != nil { 385 | return fmt.Errorf("Failed to attach role policy: %s", err) 386 | } 387 | } 388 | 389 | return nil 390 | } 391 | 392 | func (p *PostProcessor) createRoleAndAttachPolicy() error { 393 | ramClient := p.getRamClient() 394 | 395 | createRoleRequest := ram.CreateCreateRoleRequest() 396 | createRoleRequest.SetScheme(requests.HTTPS) 397 | createRoleRequest.RoleName = DefaultImportRoleName 398 | createRoleRequest.AssumeRolePolicyDocument = DefaultImportRolePolicy 399 | if _, err := ramClient.CreateRole(createRoleRequest); err != nil { 400 | return fmt.Errorf("Failed to create role: %s", err) 401 | } 402 | 403 | attachPolicyToRoleRequest := ram.CreateAttachPolicyToRoleRequest() 404 | attachPolicyToRoleRequest.SetScheme(requests.HTTPS) 405 | attachPolicyToRoleRequest.PolicyName = DefaultImportPolicyName 406 | attachPolicyToRoleRequest.PolicyType = PolicyTypeSystem 407 | attachPolicyToRoleRequest.RoleName = DefaultImportRoleName 408 | if _, err := ramClient.AttachPolicyToRole(attachPolicyToRoleRequest); err != nil { 409 | return fmt.Errorf("Failed to attach policy: %s", err) 410 | } 411 | return nil 412 | } 413 | 414 | func (p *PostProcessor) buildImportImageRequest() *ecs.ImportImageRequest { 415 | request := ecs.CreateImportImageRequest() 416 | request.RegionId = p.config.AlicloudRegion 417 | request.ImageName = p.config.AlicloudImageName 418 | request.Description = p.config.AlicloudImageDescription 419 | request.Architecture = p.config.Architecture 420 | request.OSType = p.config.OSType 421 | request.Platform = p.config.Platform 422 | request.DiskDeviceMapping = &[]ecs.ImportImageDiskDeviceMapping{ 423 | { 424 | DiskImageSize: p.config.Size, 425 | Format: p.config.Format, 426 | OSSBucket: p.config.OSSBucket, 427 | OSSObject: p.config.OSSKey, 428 | }, 429 | } 430 | 431 | return request 432 | } 433 | 434 | func getEndPoint(region string, bucket string) string { 435 | if bucket != "" { 436 | return "https://" + bucket + "." + getOSSRegion(region) + ".aliyuncs.com" 437 | } 438 | 439 | return "https://" + getOSSRegion(region) + ".aliyuncs.com" 440 | } 441 | 442 | func getOSSRegion(region string) string { 443 | if strings.HasPrefix(region, OSSSuffix) { 444 | return region 445 | } 446 | return OSSSuffix + region 447 | } 448 | -------------------------------------------------------------------------------- /ecs/builder_acc_test.go: -------------------------------------------------------------------------------- 1 | package ecs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" 11 | builderT "github.com/hashicorp/packer/helper/builder/testing" 12 | "github.com/hashicorp/packer/packer" 13 | ) 14 | 15 | const defaultTestRegion = "cn-beijing" 16 | 17 | func TestBuilderAcc_validateRegion(t *testing.T) { 18 | t.Parallel() 19 | 20 | if os.Getenv(builderT.TestEnvVar) == "" { 21 | t.Skip(fmt.Sprintf("Acceptance tests skipped unless env '%s' set", builderT.TestEnvVar)) 22 | return 23 | } 24 | 25 | testAccPreCheck(t) 26 | 27 | access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"} 28 | err := access.Config() 29 | if err != nil { 30 | t.Fatalf("init AlicloudAccessConfig failed: %s", err) 31 | } 32 | 33 | err = access.ValidateRegion("cn-hangzhou") 34 | if err != nil { 35 | t.Fatalf("Expect pass with valid region id but failed: %s", err) 36 | } 37 | 38 | err = access.ValidateRegion("invalidRegionId") 39 | if err == nil { 40 | t.Fatal("Expect failure due to invalid region id but passed") 41 | } 42 | } 43 | 44 | func TestBuilderAcc_basic(t *testing.T) { 45 | t.Parallel() 46 | builderT.Test(t, builderT.TestCase{ 47 | PreCheck: func() { 48 | testAccPreCheck(t) 49 | }, 50 | Builder: &Builder{}, 51 | Template: testBuilderAccBasic, 52 | }) 53 | } 54 | 55 | const testBuilderAccBasic = ` 56 | { "builders": [{ 57 | "type": "test", 58 | "region": "cn-beijing", 59 | "instance_type": "ecs.n1.tiny", 60 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 61 | "io_optimized":"true", 62 | "ssh_username":"root", 63 | "image_name": "packer-test-basic_{{timestamp}}" 64 | }] 65 | }` 66 | 67 | func TestBuilderAcc_withDiskSettings(t *testing.T) { 68 | t.Parallel() 69 | builderT.Test(t, builderT.TestCase{ 70 | PreCheck: func() { 71 | testAccPreCheck(t) 72 | }, 73 | Builder: &Builder{}, 74 | Template: testBuilderAccWithDiskSettings, 75 | Check: checkImageDisksSettings(), 76 | }) 77 | } 78 | 79 | const testBuilderAccWithDiskSettings = ` 80 | { "builders": [{ 81 | "type": "test", 82 | "region": "cn-beijing", 83 | "instance_type": "ecs.n1.tiny", 84 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 85 | "io_optimized":"true", 86 | "ssh_username":"root", 87 | "image_name": "packer-test-withDiskSettings_{{timestamp}}", 88 | "system_disk_mapping": { 89 | "disk_size": 60 90 | }, 91 | "image_disk_mappings": [ 92 | { 93 | "disk_name": "datadisk1", 94 | "disk_size": 25, 95 | "disk_delete_with_instance": true 96 | }, 97 | { 98 | "disk_name": "datadisk2", 99 | "disk_size": 25, 100 | "disk_delete_with_instance": true 101 | } 102 | ] 103 | }] 104 | }` 105 | 106 | func checkImageDisksSettings() builderT.TestCheckFunc { 107 | return func(artifacts []packer.Artifact) error { 108 | if len(artifacts) > 1 { 109 | return fmt.Errorf("more than 1 artifact") 110 | } 111 | 112 | // Get the actual *Artifact pointer so we can access the AMIs directly 113 | artifactRaw := artifacts[0] 114 | artifact, ok := artifactRaw.(*Artifact) 115 | if !ok { 116 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 117 | } 118 | imageId := artifact.AlicloudImages[defaultTestRegion] 119 | 120 | // describe the image, get block devices with a snapshot 121 | client, _ := testAliyunClient() 122 | 123 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 124 | describeImagesRequest.RegionId = defaultTestRegion 125 | describeImagesRequest.ImageId = imageId 126 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 127 | if err != nil { 128 | return fmt.Errorf("describe images failed due to %s", err) 129 | } 130 | 131 | if len(imagesResponse.Images.Image) == 0 { 132 | return fmt.Errorf("image %s generated can not be found", imageId) 133 | } 134 | 135 | image := imagesResponse.Images.Image[0] 136 | if image.Size != 60 { 137 | return fmt.Errorf("the size of image %s should be equal to 60G but got %dG", imageId, image.Size) 138 | } 139 | if len(image.DiskDeviceMappings.DiskDeviceMapping) != 3 { 140 | return fmt.Errorf("image %s should contains 3 disks", imageId) 141 | } 142 | 143 | var snapshotIds []string 144 | for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping { 145 | if mapping.Type == DiskTypeSystem { 146 | if mapping.Size != "60" { 147 | return fmt.Errorf("the system snapshot size of image %s should be equal to 60G but got %sG", imageId, mapping.Size) 148 | } 149 | } else { 150 | if mapping.Size != "25" { 151 | return fmt.Errorf("the data disk size of image %s should be equal to 25G but got %sG", imageId, mapping.Size) 152 | } 153 | 154 | snapshotIds = append(snapshotIds, mapping.SnapshotId) 155 | } 156 | } 157 | 158 | data, _ := json.Marshal(snapshotIds) 159 | 160 | describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest() 161 | describeSnapshotRequest.RegionId = defaultTestRegion 162 | describeSnapshotRequest.SnapshotIds = string(data) 163 | describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest) 164 | if err != nil { 165 | return fmt.Errorf("describe data snapshots failed due to %s", err) 166 | } 167 | if len(describeSnapshotsResponse.Snapshots.Snapshot) != 2 { 168 | return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot)) 169 | } 170 | 171 | var dataDiskIds []string 172 | for _, snapshot := range describeSnapshotsResponse.Snapshots.Snapshot { 173 | dataDiskIds = append(dataDiskIds, snapshot.SourceDiskId) 174 | } 175 | data, _ = json.Marshal(dataDiskIds) 176 | 177 | describeDisksRequest := ecs.CreateDescribeDisksRequest() 178 | describeDisksRequest.RegionId = defaultTestRegion 179 | describeDisksRequest.DiskIds = string(data) 180 | describeDisksResponse, err := client.DescribeDisks(describeDisksRequest) 181 | if err != nil { 182 | return fmt.Errorf("describe snapshots failed due to %s", err) 183 | } 184 | if len(describeDisksResponse.Disks.Disk) != 0 { 185 | return fmt.Errorf("data disks should be deleted but %d left", len(describeDisksResponse.Disks.Disk)) 186 | } 187 | 188 | return nil 189 | } 190 | } 191 | 192 | func TestBuilderAcc_withIgnoreDataDisks(t *testing.T) { 193 | t.Parallel() 194 | builderT.Test(t, builderT.TestCase{ 195 | PreCheck: func() { 196 | testAccPreCheck(t) 197 | }, 198 | Builder: &Builder{}, 199 | Template: testBuilderAccIgnoreDataDisks, 200 | Check: checkIgnoreDataDisks(), 201 | }) 202 | } 203 | 204 | const testBuilderAccIgnoreDataDisks = ` 205 | { "builders": [{ 206 | "type": "test", 207 | "region": "cn-beijing", 208 | "instance_type": "ecs.gn5-c8g1.2xlarge", 209 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 210 | "io_optimized":"true", 211 | "ssh_username":"root", 212 | "image_name": "packer-test-ignoreDataDisks_{{timestamp}}", 213 | "image_ignore_data_disks": true 214 | }] 215 | }` 216 | 217 | func checkIgnoreDataDisks() builderT.TestCheckFunc { 218 | return func(artifacts []packer.Artifact) error { 219 | if len(artifacts) > 1 { 220 | return fmt.Errorf("more than 1 artifact") 221 | } 222 | 223 | // Get the actual *Artifact pointer so we can access the AMIs directly 224 | artifactRaw := artifacts[0] 225 | artifact, ok := artifactRaw.(*Artifact) 226 | if !ok { 227 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 228 | } 229 | imageId := artifact.AlicloudImages[defaultTestRegion] 230 | 231 | // describe the image, get block devices with a snapshot 232 | client, _ := testAliyunClient() 233 | 234 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 235 | describeImagesRequest.RegionId = defaultTestRegion 236 | describeImagesRequest.ImageId = imageId 237 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 238 | if err != nil { 239 | return fmt.Errorf("describe images failed due to %s", err) 240 | } 241 | 242 | if len(imagesResponse.Images.Image) == 0 { 243 | return fmt.Errorf("image %s generated can not be found", imageId) 244 | } 245 | 246 | image := imagesResponse.Images.Image[0] 247 | if len(image.DiskDeviceMappings.DiskDeviceMapping) != 1 { 248 | return fmt.Errorf("image %s should only contain one disks", imageId) 249 | } 250 | 251 | return nil 252 | } 253 | } 254 | 255 | func TestBuilderAcc_windows(t *testing.T) { 256 | t.Parallel() 257 | builderT.Test(t, builderT.TestCase{ 258 | PreCheck: func() { 259 | testAccPreCheck(t) 260 | }, 261 | Builder: &Builder{}, 262 | Template: testBuilderAccWindows, 263 | }) 264 | } 265 | 266 | const testBuilderAccWindows = ` 267 | { "builders": [{ 268 | "type": "test", 269 | "region": "cn-beijing", 270 | "instance_type": "ecs.n1.tiny", 271 | "source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd", 272 | "io_optimized":"true", 273 | "communicator": "winrm", 274 | "winrm_port": 5985, 275 | "winrm_username": "Administrator", 276 | "winrm_password": "Test1234", 277 | "image_name": "packer-test-windows_{{timestamp}}", 278 | "user_data_file": "../../../examples/alicloud/basic/winrm_enable_userdata.ps1" 279 | }] 280 | }` 281 | 282 | func TestBuilderAcc_regionCopy(t *testing.T) { 283 | t.Parallel() 284 | builderT.Test(t, builderT.TestCase{ 285 | PreCheck: func() { 286 | testAccPreCheck(t) 287 | }, 288 | Builder: &Builder{}, 289 | Template: testBuilderAccRegionCopy, 290 | Check: checkRegionCopy([]string{"cn-hangzhou", "cn-shenzhen"}), 291 | }) 292 | } 293 | 294 | const testBuilderAccRegionCopy = ` 295 | { 296 | "builders": [{ 297 | "type": "test", 298 | "region": "cn-beijing", 299 | "instance_type": "ecs.n1.tiny", 300 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 301 | "io_optimized":"true", 302 | "ssh_username":"root", 303 | "image_name": "packer-test-regionCopy_{{timestamp}}", 304 | "image_copy_regions": ["cn-hangzhou", "cn-shenzhen"], 305 | "image_copy_names": ["packer-copy-test-hz_{{timestamp}}", "packer-copy-test-sz_{{timestamp}}"] 306 | }] 307 | } 308 | ` 309 | 310 | func checkRegionCopy(regions []string) builderT.TestCheckFunc { 311 | return func(artifacts []packer.Artifact) error { 312 | if len(artifacts) > 1 { 313 | return fmt.Errorf("more than 1 artifact") 314 | } 315 | 316 | // Get the actual *Artifact pointer so we can access the AMIs directly 317 | artifactRaw := artifacts[0] 318 | artifact, ok := artifactRaw.(*Artifact) 319 | if !ok { 320 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 321 | } 322 | 323 | // Verify that we copied to only the regions given 324 | regionSet := make(map[string]struct{}) 325 | for _, r := range regions { 326 | regionSet[r] = struct{}{} 327 | } 328 | 329 | for r := range artifact.AlicloudImages { 330 | if r == "cn-beijing" { 331 | delete(regionSet, r) 332 | continue 333 | } 334 | 335 | if _, ok := regionSet[r]; !ok { 336 | return fmt.Errorf("region %s is not the target region but found in artifacts", r) 337 | } 338 | 339 | delete(regionSet, r) 340 | } 341 | 342 | if len(regionSet) > 0 { 343 | return fmt.Errorf("following region(s) should be the copying targets but corresponding artifact(s) not found: %#v", regionSet) 344 | } 345 | 346 | client, _ := testAliyunClient() 347 | for regionId, imageId := range artifact.AlicloudImages { 348 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 349 | describeImagesRequest.RegionId = regionId 350 | describeImagesRequest.ImageId = imageId 351 | describeImagesRequest.Status = ImageStatusQueried 352 | describeImagesResponse, err := client.DescribeImages(describeImagesRequest) 353 | if err != nil { 354 | return fmt.Errorf("describe generated image %s failed due to %s", imageId, err) 355 | } 356 | if len(describeImagesResponse.Images.Image) == 0 { 357 | return fmt.Errorf("image %s in artifacts can not be found", imageId) 358 | } 359 | 360 | image := describeImagesResponse.Images.Image[0] 361 | if image.IsCopied && regionId == "cn-hangzhou" && !strings.HasPrefix(image.ImageName, "packer-copy-test-hz") { 362 | return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-hz", image.ImageName) 363 | } 364 | if image.IsCopied && regionId == "cn-shenzhen" && !strings.HasPrefix(image.ImageName, "packer-copy-test-sz") { 365 | return fmt.Errorf("the name of image %s in artifacts should begin with %s but got %s", imageId, "packer-copy-test-sz", image.ImageName) 366 | } 367 | } 368 | 369 | return nil 370 | } 371 | } 372 | 373 | func TestBuilderAcc_forceDelete(t *testing.T) { 374 | t.Parallel() 375 | // Build the same alicloud image twice, with ecs_image_force_delete on the second run 376 | builderT.Test(t, builderT.TestCase{ 377 | PreCheck: func() { 378 | testAccPreCheck(t) 379 | }, 380 | Builder: &Builder{}, 381 | Template: buildForceDeregisterConfig("false", "delete"), 382 | SkipArtifactTeardown: true, 383 | }) 384 | 385 | builderT.Test(t, builderT.TestCase{ 386 | PreCheck: func() { 387 | testAccPreCheck(t) 388 | }, 389 | Builder: &Builder{}, 390 | Template: buildForceDeregisterConfig("true", "delete"), 391 | }) 392 | } 393 | 394 | func buildForceDeregisterConfig(val, name string) string { 395 | return fmt.Sprintf(testBuilderAccForceDelete, val, name) 396 | } 397 | 398 | const testBuilderAccForceDelete = ` 399 | { 400 | "builders": [{ 401 | "type": "test", 402 | "region": "cn-beijing", 403 | "instance_type": "ecs.n1.tiny", 404 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 405 | "io_optimized":"true", 406 | "ssh_username":"root", 407 | "image_force_delete": "%s", 408 | "image_name": "packer-test-forceDelete_%s" 409 | }] 410 | } 411 | ` 412 | 413 | func TestBuilderAcc_ECSImageSharing(t *testing.T) { 414 | t.Parallel() 415 | builderT.Test(t, builderT.TestCase{ 416 | PreCheck: func() { 417 | testAccPreCheck(t) 418 | }, 419 | Builder: &Builder{}, 420 | Template: testBuilderAccSharing, 421 | Check: checkECSImageSharing("1309208528360047"), 422 | }) 423 | } 424 | 425 | // share with catsby 426 | const testBuilderAccSharing = ` 427 | { 428 | "builders": [{ 429 | "type": "test", 430 | "region": "cn-beijing", 431 | "instance_type": "ecs.n1.tiny", 432 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 433 | "io_optimized":"true", 434 | "ssh_username":"root", 435 | "image_name": "packer-test-ECSImageSharing_{{timestamp}}", 436 | "image_share_account":["1309208528360047"] 437 | }] 438 | } 439 | ` 440 | 441 | func checkECSImageSharing(uid string) builderT.TestCheckFunc { 442 | return func(artifacts []packer.Artifact) error { 443 | if len(artifacts) > 1 { 444 | return fmt.Errorf("more than 1 artifact") 445 | } 446 | 447 | // Get the actual *Artifact pointer so we can access the AMIs directly 448 | artifactRaw := artifacts[0] 449 | artifact, ok := artifactRaw.(*Artifact) 450 | if !ok { 451 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 452 | } 453 | 454 | // describe the image, get block devices with a snapshot 455 | client, _ := testAliyunClient() 456 | 457 | describeImageShareRequest := ecs.CreateDescribeImageSharePermissionRequest() 458 | describeImageShareRequest.RegionId = "cn-beijing" 459 | describeImageShareRequest.ImageId = artifact.AlicloudImages["cn-beijing"] 460 | imageShareResponse, err := client.DescribeImageSharePermission(describeImageShareRequest) 461 | 462 | if err != nil { 463 | return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+ 464 | "in ECS Image Sharing Test: %s", artifact, err) 465 | } 466 | 467 | if len(imageShareResponse.Accounts.Account) != 1 && imageShareResponse.Accounts.Account[0].AliyunId != uid { 468 | return fmt.Errorf("share account is incorrect %d", len(imageShareResponse.Accounts.Account)) 469 | } 470 | 471 | return nil 472 | } 473 | } 474 | 475 | func TestBuilderAcc_forceDeleteSnapshot(t *testing.T) { 476 | t.Parallel() 477 | destImageName := "delete" 478 | 479 | // Build the same alicloud image name twice, with force_delete_snapshot on the second run 480 | builderT.Test(t, builderT.TestCase{ 481 | PreCheck: func() { 482 | testAccPreCheck(t) 483 | }, 484 | Builder: &Builder{}, 485 | Template: buildForceDeleteSnapshotConfig("false", destImageName), 486 | SkipArtifactTeardown: true, 487 | }) 488 | 489 | // Get image data by image image name 490 | client, _ := testAliyunClient() 491 | 492 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 493 | describeImagesRequest.RegionId = "cn-beijing" 494 | describeImagesRequest.ImageName = "packer-test-" + destImageName 495 | images, _ := client.DescribeImages(describeImagesRequest) 496 | 497 | image := images.Images.Image[0] 498 | 499 | // Get snapshot ids for image 500 | snapshotIds := []string{} 501 | for _, device := range image.DiskDeviceMappings.DiskDeviceMapping { 502 | if device.Device != "" && device.SnapshotId != "" { 503 | snapshotIds = append(snapshotIds, device.SnapshotId) 504 | } 505 | } 506 | 507 | builderT.Test(t, builderT.TestCase{ 508 | PreCheck: func() { 509 | testAccPreCheck(t) 510 | }, 511 | Builder: &Builder{}, 512 | Template: buildForceDeleteSnapshotConfig("true", destImageName), 513 | Check: checkSnapshotsDeleted(snapshotIds), 514 | }) 515 | } 516 | 517 | func buildForceDeleteSnapshotConfig(val, name string) string { 518 | return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name) 519 | } 520 | 521 | const testBuilderAccForceDeleteSnapshot = ` 522 | { 523 | "builders": [{ 524 | "type": "test", 525 | "region": "cn-beijing", 526 | "instance_type": "ecs.n1.tiny", 527 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 528 | "io_optimized":"true", 529 | "ssh_username":"root", 530 | "image_force_delete_snapshots": "%s", 531 | "image_force_delete": "%s", 532 | "image_name": "packer-test-%s" 533 | }] 534 | } 535 | ` 536 | 537 | func checkSnapshotsDeleted(snapshotIds []string) builderT.TestCheckFunc { 538 | return func(artifacts []packer.Artifact) error { 539 | // Verify the snapshots are gone 540 | client, _ := testAliyunClient() 541 | data, err := json.Marshal(snapshotIds) 542 | if err != nil { 543 | return fmt.Errorf("Marshal snapshotIds array failed %v", err) 544 | } 545 | 546 | describeSnapshotsRequest := ecs.CreateDescribeSnapshotsRequest() 547 | describeSnapshotsRequest.RegionId = "cn-beijing" 548 | describeSnapshotsRequest.SnapshotIds = string(data) 549 | snapshotResp, err := client.DescribeSnapshots(describeSnapshotsRequest) 550 | if err != nil { 551 | return fmt.Errorf("Query snapshot failed %v", err) 552 | } 553 | snapshots := snapshotResp.Snapshots.Snapshot 554 | if len(snapshots) > 0 { 555 | return fmt.Errorf("Snapshots weren't successfully deleted by " + 556 | "`ecs_image_force_delete_snapshots`") 557 | } 558 | return nil 559 | } 560 | } 561 | 562 | func TestBuilderAcc_imageTags(t *testing.T) { 563 | t.Parallel() 564 | builderT.Test(t, builderT.TestCase{ 565 | PreCheck: func() { 566 | testAccPreCheck(t) 567 | }, 568 | Builder: &Builder{}, 569 | Template: testBuilderAccImageTags, 570 | Check: checkImageTags(), 571 | }) 572 | } 573 | 574 | const testBuilderAccImageTags = ` 575 | { "builders": [{ 576 | "type": "test", 577 | "region": "cn-beijing", 578 | "instance_type": "ecs.n1.tiny", 579 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 580 | "ssh_username": "root", 581 | "io_optimized":"true", 582 | "image_name": "packer-test-imageTags_{{timestamp}}", 583 | "tags": { 584 | "TagKey1": "TagValue1", 585 | "TagKey2": "TagValue2" 586 | } 587 | }] 588 | }` 589 | 590 | func checkImageTags() builderT.TestCheckFunc { 591 | return func(artifacts []packer.Artifact) error { 592 | if len(artifacts) > 1 { 593 | return fmt.Errorf("more than 1 artifact") 594 | } 595 | // Get the actual *Artifact pointer so we can access the AMIs directly 596 | artifactRaw := artifacts[0] 597 | artifact, ok := artifactRaw.(*Artifact) 598 | if !ok { 599 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 600 | } 601 | imageId := artifact.AlicloudImages[defaultTestRegion] 602 | 603 | // describe the image, get block devices with a snapshot 604 | client, _ := testAliyunClient() 605 | 606 | describeImageTagsRequest := ecs.CreateDescribeTagsRequest() 607 | describeImageTagsRequest.RegionId = defaultTestRegion 608 | describeImageTagsRequest.ResourceType = TagResourceImage 609 | describeImageTagsRequest.ResourceId = imageId 610 | imageTagsResponse, err := client.DescribeTags(describeImageTagsRequest) 611 | if err != nil { 612 | return fmt.Errorf("Error retrieving Image Attributes for ECS Image Artifact (%#v) "+ 613 | "in ECS Image Tags Test: %s", artifact, err) 614 | } 615 | 616 | if len(imageTagsResponse.Tags.Tag) != 2 { 617 | return fmt.Errorf("expect 2 tags set on image %s but got %d", imageId, len(imageTagsResponse.Tags.Tag)) 618 | } 619 | 620 | for _, tag := range imageTagsResponse.Tags.Tag { 621 | if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" { 622 | return fmt.Errorf("tags on image %s should be within the list of TagKey1 and TagKey2 but got %s", imageId, tag.TagKey) 623 | } 624 | 625 | if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" { 626 | return fmt.Errorf("the value for tag %s on image %s should be TagValue1 but got %s", tag.TagKey, imageId, tag.TagValue) 627 | } else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" { 628 | return fmt.Errorf("the value for tag %s on image %s should be TagValue2 but got %s", tag.TagKey, imageId, tag.TagValue) 629 | } 630 | } 631 | 632 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 633 | describeImagesRequest.RegionId = defaultTestRegion 634 | describeImagesRequest.ImageId = imageId 635 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 636 | if err != nil { 637 | return fmt.Errorf("describe images failed due to %s", err) 638 | } 639 | 640 | if len(imagesResponse.Images.Image) == 0 { 641 | return fmt.Errorf("image %s generated can not be found", imageId) 642 | } 643 | 644 | image := imagesResponse.Images.Image[0] 645 | for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping { 646 | describeSnapshotTagsRequest := ecs.CreateDescribeTagsRequest() 647 | describeSnapshotTagsRequest.RegionId = defaultTestRegion 648 | describeSnapshotTagsRequest.ResourceType = TagResourceSnapshot 649 | describeSnapshotTagsRequest.ResourceId = mapping.SnapshotId 650 | snapshotTagsResponse, err := client.DescribeTags(describeSnapshotTagsRequest) 651 | if err != nil { 652 | return fmt.Errorf("failed to get snapshot tags due to %s", err) 653 | } 654 | 655 | if len(snapshotTagsResponse.Tags.Tag) != 2 { 656 | return fmt.Errorf("expect 2 tags set on snapshot %s but got %d", mapping.SnapshotId, len(snapshotTagsResponse.Tags.Tag)) 657 | } 658 | 659 | for _, tag := range snapshotTagsResponse.Tags.Tag { 660 | if tag.TagKey != "TagKey1" && tag.TagKey != "TagKey2" { 661 | return fmt.Errorf("tags on snapshot %s should be within the list of TagKey1 and TagKey2 but got %s", mapping.SnapshotId, tag.TagKey) 662 | } 663 | 664 | if tag.TagKey == "TagKey1" && tag.TagValue != "TagValue1" { 665 | return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue1 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue) 666 | } else if tag.TagKey == "TagKey2" && tag.TagValue != "TagValue2" { 667 | return fmt.Errorf("the value for tag %s on snapshot %s should be TagValue2 but got %s", tag.TagKey, mapping.SnapshotId, tag.TagValue) 668 | } 669 | } 670 | } 671 | 672 | return nil 673 | } 674 | } 675 | 676 | func TestBuilderAcc_dataDiskEncrypted(t *testing.T) { 677 | t.Parallel() 678 | builderT.Test(t, builderT.TestCase{ 679 | PreCheck: func() { 680 | testAccPreCheck(t) 681 | }, 682 | Builder: &Builder{}, 683 | Template: testBuilderAccDataDiskEncrypted, 684 | Check: checkDataDiskEncrypted(), 685 | }) 686 | } 687 | 688 | const testBuilderAccDataDiskEncrypted = ` 689 | { "builders": [{ 690 | "type": "test", 691 | "region": "cn-beijing", 692 | "instance_type": "ecs.n1.tiny", 693 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 694 | "io_optimized":"true", 695 | "ssh_username":"root", 696 | "image_name": "packer-test-dataDiskEncrypted_{{timestamp}}", 697 | "image_disk_mappings": [ 698 | { 699 | "disk_name": "data_disk1", 700 | "disk_size": 25, 701 | "disk_encrypted": true, 702 | "disk_delete_with_instance": true 703 | }, 704 | { 705 | "disk_name": "data_disk2", 706 | "disk_size": 35, 707 | "disk_encrypted": false, 708 | "disk_delete_with_instance": true 709 | }, 710 | { 711 | "disk_name": "data_disk3", 712 | "disk_size": 45, 713 | "disk_delete_with_instance": true 714 | } 715 | ] 716 | }] 717 | }` 718 | 719 | func checkDataDiskEncrypted() builderT.TestCheckFunc { 720 | return func(artifacts []packer.Artifact) error { 721 | if len(artifacts) > 1 { 722 | return fmt.Errorf("more than 1 artifact") 723 | } 724 | 725 | // Get the actual *Artifact pointer so we can access the AMIs directly 726 | artifactRaw := artifacts[0] 727 | artifact, ok := artifactRaw.(*Artifact) 728 | if !ok { 729 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 730 | } 731 | imageId := artifact.AlicloudImages[defaultTestRegion] 732 | 733 | // describe the image, get block devices with a snapshot 734 | client, _ := testAliyunClient() 735 | 736 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 737 | describeImagesRequest.RegionId = defaultTestRegion 738 | describeImagesRequest.ImageId = imageId 739 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 740 | if err != nil { 741 | return fmt.Errorf("describe images failed due to %s", err) 742 | } 743 | 744 | if len(imagesResponse.Images.Image) == 0 { 745 | return fmt.Errorf("image %s generated can not be found", imageId) 746 | } 747 | image := imagesResponse.Images.Image[0] 748 | 749 | var snapshotIds []string 750 | for _, mapping := range image.DiskDeviceMappings.DiskDeviceMapping { 751 | snapshotIds = append(snapshotIds, mapping.SnapshotId) 752 | } 753 | 754 | data, _ := json.Marshal(snapshotIds) 755 | 756 | describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest() 757 | describeSnapshotRequest.RegionId = defaultTestRegion 758 | describeSnapshotRequest.SnapshotIds = string(data) 759 | describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest) 760 | if err != nil { 761 | return fmt.Errorf("describe data snapshots failed due to %s", err) 762 | } 763 | if len(describeSnapshotsResponse.Snapshots.Snapshot) != 4 { 764 | return fmt.Errorf("expect %d data snapshots but got %d", len(snapshotIds), len(describeSnapshotsResponse.Snapshots.Snapshot)) 765 | } 766 | snapshots := describeSnapshotsResponse.Snapshots.Snapshot 767 | for _, snapshot := range snapshots { 768 | if snapshot.SourceDiskType == DiskTypeSystem { 769 | if snapshot.Encrypted != false { 770 | return fmt.Errorf("the system snapshot expected to be non-encrypted but got true") 771 | } 772 | 773 | continue 774 | } 775 | 776 | if snapshot.SourceDiskSize == "25" && snapshot.Encrypted != true { 777 | return fmt.Errorf("the first snapshot expected to be encrypted but got false") 778 | } 779 | 780 | if snapshot.SourceDiskSize == "35" && snapshot.Encrypted != false { 781 | return fmt.Errorf("the second snapshot expected to be non-encrypted but got true") 782 | } 783 | 784 | if snapshot.SourceDiskSize == "45" && snapshot.Encrypted != false { 785 | return fmt.Errorf("the third snapshot expected to be non-encrypted but got true") 786 | } 787 | } 788 | return nil 789 | } 790 | } 791 | 792 | func TestBuilderAcc_systemDiskEncrypted(t *testing.T) { 793 | t.Parallel() 794 | builderT.Test(t, builderT.TestCase{ 795 | PreCheck: func() { 796 | testAccPreCheck(t) 797 | }, 798 | Builder: &Builder{}, 799 | Template: testBuilderAccSystemDiskEncrypted, 800 | Check: checkSystemDiskEncrypted(), 801 | }) 802 | } 803 | 804 | const testBuilderAccSystemDiskEncrypted = ` 805 | { 806 | "builders": [{ 807 | "type": "test", 808 | "region": "cn-beijing", 809 | "instance_type": "ecs.n1.tiny", 810 | "source_image":"ubuntu_18_04_64_20G_alibase_20190509.vhd", 811 | "io_optimized":"true", 812 | "ssh_username":"root", 813 | "image_name": "packer-test_{{timestamp}}", 814 | "image_encrypted": "true" 815 | }] 816 | }` 817 | 818 | func checkSystemDiskEncrypted() builderT.TestCheckFunc { 819 | return func(artifacts []packer.Artifact) error { 820 | if len(artifacts) > 1 { 821 | return fmt.Errorf("more than 1 artifact") 822 | } 823 | 824 | // Get the actual *Artifact pointer so we can access the AMIs directly 825 | artifactRaw := artifacts[0] 826 | artifact, ok := artifactRaw.(*Artifact) 827 | if !ok { 828 | return fmt.Errorf("unknown artifact: %#v", artifactRaw) 829 | } 830 | 831 | // describe the image, get block devices with a snapshot 832 | client, _ := testAliyunClient() 833 | imageId := artifact.AlicloudImages[defaultTestRegion] 834 | 835 | describeImagesRequest := ecs.CreateDescribeImagesRequest() 836 | describeImagesRequest.RegionId = defaultTestRegion 837 | describeImagesRequest.ImageId = imageId 838 | describeImagesRequest.Status = ImageStatusQueried 839 | imagesResponse, err := client.DescribeImages(describeImagesRequest) 840 | if err != nil { 841 | return fmt.Errorf("describe images failed due to %s", err) 842 | } 843 | 844 | if len(imagesResponse.Images.Image) == 0 { 845 | return fmt.Errorf("image %s generated can not be found", imageId) 846 | } 847 | 848 | image := imagesResponse.Images.Image[0] 849 | if image.IsCopied == false { 850 | return fmt.Errorf("image %s generated expexted to be copied but false", image.ImageId) 851 | } 852 | 853 | describeSnapshotRequest := ecs.CreateDescribeSnapshotsRequest() 854 | describeSnapshotRequest.RegionId = defaultTestRegion 855 | describeSnapshotRequest.SnapshotIds = fmt.Sprintf("[\"%s\"]", image.DiskDeviceMappings.DiskDeviceMapping[0].SnapshotId) 856 | describeSnapshotsResponse, err := client.DescribeSnapshots(describeSnapshotRequest) 857 | if err != nil { 858 | return fmt.Errorf("describe system snapshots failed due to %s", err) 859 | } 860 | snapshots := describeSnapshotsResponse.Snapshots.Snapshot[0] 861 | 862 | if snapshots.Encrypted != true { 863 | return fmt.Errorf("system snapshot of image %s expected to be encrypted but got false", imageId) 864 | } 865 | 866 | return nil 867 | } 868 | } 869 | 870 | func testAccPreCheck(t *testing.T) { 871 | if v := os.Getenv("ALICLOUD_ACCESS_KEY"); v == "" { 872 | t.Fatal("ALICLOUD_ACCESS_KEY must be set for acceptance tests") 873 | } 874 | 875 | if v := os.Getenv("ALICLOUD_SECRET_KEY"); v == "" { 876 | t.Fatal("ALICLOUD_SECRET_KEY must be set for acceptance tests") 877 | } 878 | } 879 | 880 | func testAliyunClient() (*ClientWrapper, error) { 881 | access := &AlicloudAccessConfig{AlicloudRegion: "cn-beijing"} 882 | err := access.Config() 883 | if err != nil { 884 | return nil, err 885 | } 886 | client, err := access.Client() 887 | if err != nil { 888 | return nil, err 889 | } 890 | 891 | return client, nil 892 | } 893 | --------------------------------------------------------------------------------