using only remoteFolder as parameter

This commit is contained in:
bugbuilder 2017-11-10 14:03:15 -03:00
commit da54bf8e73
239 changed files with 9821 additions and 3672 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ packer-test*.log
.idea/
*.iml
Thumbs.db
/packer.exe

View File

@ -1,8 +1,75 @@
## UNRELEASED
## (UNRELEASED)
### IMRPOVEMENTS:
* post-processor/docker-push: Add `aws_profile` option to control the aws profile for ECR. [GH-5470]
* builder/docker: Add `aws_profile` option to control the aws profile for ECR. [GH-5470]
* post-processor/vsphere: Properly capture `ovftool` output. [GH-5499]
* builder/hyper-v: Also disable automatic checkpoints for gen 2 VMs. [GH-5517]
* builder/hyper-v: Add `disk_additional_size` option to allow for up to 64 additional disks. [GH-5491]
* builder/amazon: correctly deregister AMIs when `force_deregister` is set. [GH-5525]
* builder/digitalocean: Add `ipv6` option to enable on droplet. [GH-5534]
* builder/triton: Add `source_machine_image_filter` option to select an image ID based on a variety of parameters. [GH-5538]
* communicator/ssh: Add socks 5 proxy support. [GH-5439]
* builder/lxc: Add new `publish_properties` field to set image properties. [GH-5475]
* builder/virtualbox-ovf: Retry while removing VM to solve for transient errors. [GH-5512]
### BUG FIXES:
* builder/puppet-masterless: Make sure directories created with sudo are writable by the packer user. [GH-5351]
* builder/docker: Remove `login_email`, which no longer exists in the docker client. [GH-5511]
* builder/triton: Fix a bug where partially created images can be reported as complete. [GH-5566]
* builder/amazon: region is set from profile, if profile is set, rather than being overridden by metadata [GH-5562]
## 1.1.1 (October 13, 2017)
### IMPROVEMENTS:
* **New builder:** `hyperv-vmcx` for building images from existing VMs.
[GH-4944] [GH-5444]
* builder/amazon-instance: Add `.Token` as a variable in the
`BundleUploadCommand` template. [GH-5288]
* builder/amazon: Add `temporary_security_group_source_cidr` option to control
ingress to source instances. [GH-5384]
* builder/amazon: Output AMI Name during prevalidation. [GH-5389]
* builder/amazon: Support template functions in tag keys. [GH-5381]
* builder/amazon: Tag volumes on creation instead of as a separate step.
[GH-5417]
* builder/docker: Add option to set `--user` flag when running `exec`.
[GH-5406]
* builder/docker: Set file owner to container user when uploading. Can be
disabled by setting `fix_upload_owner` to `false`. [GH-5422]
* builder/googlecompute: Support setting labels on the resulting image.
[GH-5356]
* builder/hyper-v: Add `vhd_temp_path` option to control where the VHD resides
while it's being provisioned. [GH-5206]
* builder/hyper-v: Allow vhd or vhdx source images instead of just ISO.
[GH-4944] [GH-5444]
* builder/hyper-v: Disable automatic checkpoints. [GH-5374]
* builder/virtualbox-ovf: Add `keep_registered` option. [GH-5336]
* builder/vmware: Add `disable_vnc` option to prevent VNC connections from
being made. [GH-5436]
* core: Releases will now be built for ppc64le.
* post-processor/vagrant: When building from a builder/hyper-v artifact, link
instead of copy when available. [GH-5207]
### BUG FIXES:
* builder/cloudstack: Fix panic if build is aborted. [GH-5388]
* builder/hyper-v: Respect `enable_dynamic_memory` flag. [GH-5363]
* builder/puppet-masterless: Make sure directories created with sudo are
writable by the packer user. [GH-5351]
* provisioner/chef-solo: Fix issue installing chef-solo on Windows. [GH-5357]
* provisioner/powershell: Fix issue setting environment variables by writing
them to a file, instead of the command line. [GH-5345]
* provisioner/powershell: Fix issue where powershell scripts could hang.
[GH-5082]
* provisioner/powershell: Fix Powershell progress stream leak to stderr for
normal and elevated commands. [GH-5365]
* provisioner/puppet-masterless: Fix bug where `puppet_bin_dir` wasn't being
respected. [GH-5340]
* provisioner/puppet: Fix setting facter vars on Windows. [GH-5341]
## 1.1.0 (September 12, 2017)

26
CODEOWNERS Normal file
View File

@ -0,0 +1,26 @@
* @hashicorp/packer
# builders
/builder/alicloud/ dongxiao.zzh@alibaba-inc.com
/builder/amazon/ebssurrogate/ @jen20
/builder/amazon/ebsvolume/ @jen20
/builder/azure/ @boumenot
/builder/hyperv/ @taliesins
/builder/lxc/ @ChrisLundquist
/builder/lxd/ @ChrisLundquist
/builder/oneandone/ @jasmingacic
/builder/oracle/ @prydie @owainlewis
/builder/profitbricks/ @jasmingacic
/builder/triton/ @jen20 @sean-
# provisioners
/provisioner/ansible/ @bhcleek
/provisioner/converge/ @stevendborrelli
# post-processors
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
/post-processor/checksum/ v.tolstov@selfip.ru
/post-processor/googlecompute-export/ crunkleton@google.com
/post-processor/vsphere-template/ nelson@bennu.cl

View File

@ -51,8 +51,9 @@ dev: deps ## Build and install a development build
exit 1; \
fi
@mkdir -p pkg/$(GOOS)_$(GOARCH)
@mkdir -p bin
@go install -ldflags '$(GOLDFLAGS)'
@cp $(GOPATH)/bin/packer bin
@cp $(GOPATH)/bin/packer bin/packer
@cp $(GOPATH)/bin/packer pkg/$(GOOS)_$(GOARCH)
fmt: ## Format Go code

View File

@ -34,7 +34,7 @@ comes out of the box with support for the following platforms:
* Hyper-V
* 1&1
* OpenStack
* Oracle Bare Metal Cloud Services
* Oracle Cloud Infrastructure
* Parallels
* ProfitBricks
* QEMU. Both KVM and Xen images.

121
Vagrantfile vendored
View File

@ -1,50 +1,89 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
$script = <<SCRIPT
# Fetch from https://golang.org/dl
TARBALL="https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz"
UNTARPATH="/opt"
GOROOT="${UNTARPATH}/go"
GOPATH="${UNTARPATH}/gopath"
# Install Go
if [ ! -d ${GOROOT} ]; then
sudo wget --progress=bar:force --output-document - ${TARBALL} |\
tar xfz - -C ${UNTARPATH}
fi
# Setup the GOPATH
sudo mkdir -p ${GOPATH}
cat <<EOF >/tmp/gopath.sh
export GOROOT="${GOROOT}"
export GOPATH="${GOPATH}"
export PATH="${GOROOT}/bin:${GOPATH}/bin:\$PATH"
EOF
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
# Make sure the GOPATH is usable by vagrant
sudo chown -R vagrant:vagrant ${GOROOT}
sudo chown -R vagrant:vagrant ${GOPATH}
# Install some other stuff we need
sudo apt-get update
sudo apt-get install -y curl make git mercurial bzr zip
SCRIPT
LINUX_BASE_BOX = "bento/ubuntu-16.04"
FREEBSD_BASE_BOX = "jen20/FreeBSD-12.0-CURRENT"
Vagrant.configure(2) do |config|
config.vm.box = "bento/ubuntu-14.04"
# Compilation and development boxes
config.vm.define "linux", autostart: true, primary: true do |vmCfg|
vmCfg.vm.box = LINUX_BASE_BOX
vmCfg.vm.hostname = "linux"
vmCfg = configureProviders vmCfg,
cpus: suggestedCPUCores()
config.vm.provision "shell", inline: $script
vmCfg.vm.synced_folder ".", "/vagrant", disabled: true
vmCfg.vm.synced_folder '.',
'/opt/gopath/src/github.com/hashicorp/packer'
config.vm.synced_folder ".", "/vagrant", disabled: true
vmCfg.vm.provision "shell",
privileged: true,
inline: 'rm -f /home/vagrant/linux.iso'
["vmware_fusion", "vmware_workstation"].each do |p|
config.vm.provider "p" do |v|
v.vmx["memsize"] = "2048"
v.vmx["numvcpus"] = "2"
v.vmx["cpuid.coresPerSocket"] = "1"
end
end
vmCfg.vm.provision "shell",
privileged: true,
path: './scripts/vagrant-linux-priv-go.sh'
vmCfg.vm.provision "shell",
privileged: true,
path: './scripts/vagrant-linux-priv-config.sh'
vmCfg.vm.provision "shell",
privileged: false,
path: './scripts/vagrant-linux-unpriv-bootstrap.sh'
end
config.vm.define "freebsd", autostart: false, primary: false do |vmCfg|
vmCfg.vm.box = FREEBSD_BASE_BOX
vmCfg.vm.hostname = "freebsd"
vmCfg = configureProviders vmCfg,
cpus: suggestedCPUCores()
vmCfg.vm.synced_folder ".", "/vagrant", disabled: true
vmCfg.vm.synced_folder '.',
'/opt/gopath/src/github.com/hashicorp/packer',
type: "nfs",
bsd__nfs_options: ['noatime']
vmCfg.vm.provision "shell",
privileged: true,
path: './scripts/vagrant-freebsd-priv-config.sh'
vmCfg.vm.provision "shell",
privileged: false,
path: './scripts/vagrant-freebsd-unpriv-bootstrap.sh'
end
end
def configureProviders(vmCfg, cpus: "2", memory: "2048")
vmCfg.vm.provider "virtualbox" do |v|
v.memory = memory
v.cpus = cpus
end
["vmware_fusion", "vmware_workstation"].each do |p|
vmCfg.vm.provider p do |v|
v.enable_vmrun_ip_lookup = false
v.vmx["memsize"] = memory
v.vmx["numvcpus"] = cpus
end
end
vmCfg.vm.provider "virtualbox" do |v|
v.memory = memory
v.cpus = cpus
end
return vmCfg
end
def suggestedCPUCores()
case RbConfig::CONFIG['host_os']
when /darwin/
Integer(`sysctl -n hw.ncpu`) / 2
when /linux/
Integer(`cat /proc/cpuinfo | grep processor | wc -l`) / 2
else
2
end
end

View File

@ -59,11 +59,11 @@ func (s *setpRegionCopyAlicloudImage) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*ecs.Client)
alicloudImages := state.Get("alicloudimages").(map[string]string)
ui.Say(fmt.Sprintf("Stopping copy image because cancellation or error..."))
for copyedRegionId, copyedImageId := range alicloudImages {
if copyedRegionId == s.RegionId {
for copiedRegionId, copiedImageId := range alicloudImages {
if copiedRegionId == s.RegionId {
continue
}
if err := client.CancelCopyImage(common.Region(copyedRegionId), copyedImageId); err != nil {
if err := client.CancelCopyImage(common.Region(copiedRegionId), copiedImageId); err != nil {
ui.Say(fmt.Sprintf("Error cancelling copy image: %v", err))
}
}

View File

@ -19,11 +19,11 @@ func (s *setpShareAlicloudImage) Run(state multistep.StateBag) multistep.StepAct
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
alicloudImages := state.Get("alicloudimages").(map[string]string)
for copyedRegion, copyedImageId := range alicloudImages {
for copiedRegion, copiedImageId := range alicloudImages {
err := client.ModifyImageSharePermission(
&ecs.ModifyImageSharePermissionArgs{
RegionId: common.Region(copyedRegion),
ImageId: copyedImageId,
RegionId: common.Region(copiedRegion),
ImageId: copiedImageId,
AddAccount: s.AlicloudImageShareAccounts,
RemoveAccount: s.AlicloudImageUNShareAccounts,
})
@ -44,11 +44,11 @@ func (s *setpShareAlicloudImage) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*ecs.Client)
alicloudImages := state.Get("alicloudimages").(map[string]string)
ui.Say("Restoring image share permission because cancellations or error...")
for copyedRegion, copyedImageId := range alicloudImages {
for copiedRegion, copiedImageId := range alicloudImages {
err := client.ModifyImageSharePermission(
&ecs.ModifyImageSharePermissionArgs{
RegionId: common.Region(copyedRegion),
ImageId: copyedImageId,
RegionId: common.Region(copiedRegion),
ImageId: copiedImageId,
AddAccount: s.AlicloudImageUNShareAccounts,
RemoveAccount: s.AlicloudImageShareAccounts,
})

View File

@ -121,7 +121,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
var warns []string
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
for _, mounts := range b.config.ChrootMounts {
if len(mounts) != 3 {

View File

@ -10,6 +10,7 @@ func testConfig() map[string]interface{} {
return map[string]interface{}{
"ami_name": "foo",
"source_ami": "foo",
"region": "us-east-1",
}
}

View File

@ -4,11 +4,13 @@ import (
"fmt"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/packer/template/interpolate"
)
@ -32,19 +34,18 @@ func (c *AccessConfig) Session() (*session.Session, error) {
return c.session, nil
}
region, err := c.Region()
if err != nil {
return nil, err
}
config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)
if c.ProfileName != "" {
if err := os.Setenv("AWS_PROFILE", c.ProfileName); err != nil {
log.Printf("Set env error: %s", err)
return nil, fmt.Errorf("Set env error: %s", err)
}
} else if c.RawRegion != "" {
config = config.WithRegion(c.RawRegion)
} else if region := c.metadataRegion(); region != "" {
config = config.WithRegion(region)
}
config := aws.NewConfig().WithRegion(region).WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)
if c.CustomEndpointEc2 != "" {
config = config.WithEndpoint(c.CustomEndpointEc2)
}
@ -67,40 +68,42 @@ func (c *AccessConfig) Session() (*session.Session, error) {
SharedConfigState: session.SharedConfigEnable,
Config: *config,
}
if c.MFACode != "" {
opts.AssumeRoleTokenProvider = func() (string, error) {
return c.MFACode, nil
}
}
c.session, err = session.NewSessionWithOptions(opts)
if err != nil {
if sess, err := session.NewSessionWithOptions(opts); err != nil {
return nil, err
} else if *sess.Config.Region == "" {
return nil, fmt.Errorf("Could not find AWS region, make sure it's set.")
} else {
log.Printf("Found region %s", *sess.Config.Region)
c.session = sess
}
return c.session, nil
}
// Region returns the aws.Region object for access to AWS services, requesting
// the region from the instance metadata if possible.
func (c *AccessConfig) Region() (string, error) {
if c.RawRegion != "" {
if !c.SkipValidation {
if valid := ValidateRegion(c.RawRegion); !valid {
return "", fmt.Errorf("Not a valid region: %s", c.RawRegion)
}
}
return c.RawRegion, nil
}
// metadataRegion returns the region from the metadata service
func (c *AccessConfig) metadataRegion() string {
sess := session.New()
ec2meta := ec2metadata.New(sess)
identity, err := ec2meta.GetInstanceIdentityDocument()
client := cleanhttp.DefaultClient()
// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
client.Timeout = 100 * time.Millisecond
ec2meta := ec2metadata.New(session.New(), &aws.Config{
HTTPClient: client,
})
region, err := ec2meta.Region()
if err != nil {
log.Println("Error getting region from metadata service, "+
"probably because we're not running on AWS.", err)
return "", nil
return ""
}
return identity.Region, nil
return region
}
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
@ -111,9 +114,5 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if len(errs) > 0 {
return errs
}
return nil
return errs
}

View File

@ -38,8 +38,21 @@ func stringInSlice(s []string, searchstr string) bool {
return false
}
func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context) []error {
var errs []error
if accessConfig != nil {
session, err := accessConfig.Session()
if err != nil {
errs = append(errs, err)
} else {
region := *session.Config.Region
if stringInSlice(c.AMIRegions, region) {
errs = append(errs, fmt.Errorf("Cannot copy AMI to AWS session region '%s', please remove it from `ami_regions`.", region))
}
}
}
if c.AMIName == "" {
errs = append(errs, fmt.Errorf("ami_name must be specified"))
}
@ -61,7 +74,6 @@ func (c *AMIConfig) Prepare(ctx *interpolate.Context) []error {
// Verify the region is real
if valid := ValidateRegion(region); !valid {
errs = append(errs, fmt.Errorf("Unknown region: %s", region))
continue
}
}

View File

@ -13,12 +13,12 @@ func testAMIConfig() *AMIConfig {
func TestAMIConfigPrepare_name(t *testing.T) {
c := testAMIConfig()
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIName = ""
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("should have error")
}
}
@ -26,22 +26,22 @@ func TestAMIConfigPrepare_name(t *testing.T) {
func TestAMIConfigPrepare_regions(t *testing.T) {
c := testAMIConfig()
c.AMIRegions = nil
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIRegions = listEC2Regions()
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatalf("shouldn't have err: %s", err)
}
c.AMIRegions = []string{"foo"}
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("should have error")
}
c.AMIRegions = []string{"us-east-1", "us-west-1", "us-east-1"}
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatalf("bad: %s", err)
}
@ -52,7 +52,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
c.AMIRegions = []string{"custom"}
c.AMISkipRegionValidation = true
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatal("shouldn't have error")
}
c.AMISkipRegionValidation = false
@ -63,7 +63,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatal("shouldn't have error")
}
@ -73,7 +73,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatal("should have passed; we are able to use default KMS key if not sharing")
}
@ -84,7 +84,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
"us-west-1": "789-012-3456",
"us-east-2": "",
}
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("should have an error b/c can't use default KMS key if sharing")
}
@ -94,7 +94,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
"us-west-1": "789-012-3456",
"us-east-2": "456-789-0123",
}
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("should have error b/c theres a region in the key map that isn't in ami_regions")
}
@ -103,7 +103,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
"us-east-1": "123-456-7890",
"us-west-1": "789-012-3456",
}
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
@ -115,7 +115,7 @@ func TestAMIConfigPrepare_regions(t *testing.T) {
"us-east-1": "123-456-7890",
"us-west-1": "",
}
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("should have error b/c theres a region in in ami_regions that isn't in the key map")
}
}
@ -126,12 +126,12 @@ func TestAMIConfigPrepare_Share_EncryptedBoot(t *testing.T) {
c.AMIEncryptBootVolume = true
c.AMIKmsKeyId = ""
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
}
c.AMIKmsKeyId = "89c3fb9a-de87-4f2a-aedc-fddc5138193c"
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("shouldn't be able to share ami with encrypted boot volume")
}
}
@ -140,7 +140,7 @@ func TestAMINameValidation(t *testing.T) {
c := testAMIConfig()
c.AMIName = "aa"
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with less than 3 characters")
}
@ -149,22 +149,22 @@ func TestAMINameValidation(t *testing.T) {
longAmiName += "a"
}
c.AMIName = longAmiName
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with great than 128 characters")
}
c.AMIName = "+aaa"
if err := c.Prepare(nil); err == nil {
if err := c.Prepare(nil, nil); err == nil {
t.Fatal("shouldn't be able to have an ami name with invalid characters")
}
c.AMIName = "fooBAR1()[] ./-'@_"
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatal("should be able to use all of the allowed AMI characters")
}
c.AMIName = `xyz-base-2017-04-05-1934`
if err := c.Prepare(nil); err != nil {
if err := c.Prepare(nil, nil); err != nil {
t.Fatalf("expected `xyz-base-2017-04-05-1934` to pass validation.")
}

View File

@ -3,6 +3,7 @@ package common
import (
"errors"
"fmt"
"net"
"os"
"regexp"
"time"
@ -40,6 +41,7 @@ type RunConfig struct {
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
UserData string `mapstructure:"user_data"`
@ -115,6 +117,14 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
}
if c.TemporarySGSourceCidr == "" {
c.TemporarySGSourceCidr = "0.0.0.0/0"
} else {
if _, _, err := net.ParseCIDR(c.TemporarySGSourceCidr); err != nil {
errs = append(errs, fmt.Errorf("Error parsing temporary_security_group_source_cidr: %s", err.Error()))
}
}
if c.InstanceInitiatedShutdownBehavior == "" {
c.InstanceInitiatedShutdownBehavior = "stop"
} else if !reShutdownBehavior.MatchString(c.InstanceInitiatedShutdownBehavior) {

View File

@ -166,13 +166,17 @@ func ConvertToEC2Tags(tags map[string]string, region, sourceAmiId string, ctx in
SourceAMI: sourceAmiId,
BuildRegion: region,
}
interpolatedKey, err := interpolate.Render(key, &ctx)
if err != nil {
return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
interpolatedValue, err := interpolate.Render(value, &ctx)
if err != nil {
return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
ec2Tags = append(ec2Tags, &ec2.Tag{
Key: aws.String(key),
Key: aws.String(interpolatedKey),
Value: aws.String(interpolatedValue),
})
}

View File

@ -18,68 +18,69 @@ type StepDeregisterAMI struct {
}
func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
regions := s.Regions
if len(regions) == 0 {
regions = append(regions, s.AccessConfig.RawRegion)
// Check for force deregister
if !s.ForceDeregister {
return multistep.ActionContinue
}
// Check for force deregister
if s.ForceDeregister {
for _, region := range regions {
// get new connection for each region in which we need to deregister vms
session, err := s.AccessConfig.Session()
if err != nil {
return multistep.ActionHalt
}
ui := state.Get("ui").(packer.Ui)
ec2conn := state.Get("ec2").(*ec2.EC2)
// Add the session region to list of regions will will deregister AMIs in
regions := append(s.Regions, *ec2conn.Config.Region)
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(region)},
))
for _, region := range regions {
// get new connection for each region in which we need to deregister vms
session, err := s.AccessConfig.Session()
if err != nil {
return multistep.ActionHalt
}
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
Filters: []*ec2.Filter{{
Name: aws.String("name"),
Values: []*string{aws.String(s.AMIName)},
}}})
regionconn := ec2.New(session.Copy(&aws.Config{
Region: aws.String(region)},
))
resp, err := regionconn.DescribeImages(&ec2.DescribeImagesInput{
Filters: []*ec2.Filter{{
Name: aws.String("name"),
Values: []*string{aws.String(s.AMIName)},
}}})
if err != nil {
err := fmt.Errorf("Error describing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Deregister image(s) by name
for _, i := range resp.Images {
_, err := regionconn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: i.ImageId,
})
if err != nil {
err := fmt.Errorf("Error describing AMI: %s", err)
err := fmt.Errorf("Error deregistering existing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deregistered AMI %s, id: %s", s.AMIName, *i.ImageId))
// Deregister image(s) by name
for _, i := range resp.Images {
_, err := regionconn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: i.ImageId,
})
// Delete snapshot(s) by image
if s.ForceDeleteSnapshot {
for _, b := range i.BlockDeviceMappings {
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
_, err := regionconn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
SnapshotId: b.Ebs.SnapshotId,
})
if err != nil {
err := fmt.Errorf("Error deregistering existing AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deregistered AMI %s, id: %s", s.AMIName, *i.ImageId))
// Delete snapshot(s) by image
if s.ForceDeleteSnapshot {
for _, b := range i.BlockDeviceMappings {
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
_, err := regionconn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
SnapshotId: b.Ebs.SnapshotId,
})
if err != nil {
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId))
if err != nil {
err := fmt.Errorf("Error deleting existing snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Deleted snapshot: %s", *b.Ebs.SnapshotId))
}
}
}

View File

@ -26,7 +26,7 @@ func (s *StepPreValidate) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui.Say("Prevalidating AMI Name...")
ui.Say(fmt.Sprintf("Prevalidating AMI Name: %s", s.DestAmiName))
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
Filters: []*ec2.Filter{{
Name: aws.String("name"),

View File

@ -5,14 +5,10 @@ import (
"fmt"
"io/ioutil"
"log"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
@ -29,16 +25,14 @@ type StepRunSourceInstance struct {
InstanceInitiatedShutdownBehavior string
InstanceType string
SourceAMI string
SpotPrice string
SpotPriceProduct string
SubnetId string
Tags map[string]string
VolumeTags map[string]string
UserData string
UserDataFile string
Ctx interpolate.Context
instanceId string
spotRequest *ec2.SpotInstanceRequest
instanceId string
}
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
@ -83,57 +77,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
return multistep.ActionHalt
}
spotPrice := s.SpotPrice
availabilityZone := s.AvailabilityZone
if spotPrice == "auto" {
ui.Message(fmt.Sprintf(
"Finding spot price for %s %s...",
s.SpotPriceProduct, s.InstanceType))
// Detect the spot price
startTime := time.Now().Add(-1 * time.Hour)
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
InstanceTypes: []*string{&s.InstanceType},
ProductDescriptions: []*string{&s.SpotPriceProduct},
AvailabilityZone: &s.AvailabilityZone,
StartTime: &startTime,
})
if err != nil {
err := fmt.Errorf("Error finding spot price: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var price float64
for _, history := range resp.SpotPriceHistory {
log.Printf("[INFO] Candidate spot price: %s", *history.SpotPrice)
current, err := strconv.ParseFloat(*history.SpotPrice, 64)
if err != nil {
log.Printf("[ERR] Error parsing spot price: %s", err)
continue
}
if price == 0 || current < price {
price = current
if s.AvailabilityZone == "" {
availabilityZone = *history.AvailabilityZone
}
}
}
if price == 0 {
err := fmt.Errorf("No candidate spot prices found!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else {
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
// Avoids price-too-low error in active markets which can fluctuate
price = price + 0.005
}
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
}
var instanceId string
ui.Say("Adding tags to source instance")
@ -141,7 +84,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
s.Tags["Name"] = "Packer Builder"
}
createTagsAfterInstanceStarts := true
ec2Tags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging source instance: %s", err)
@ -151,139 +93,82 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
}
ReportTags(ui, ec2Tags)
if spotPrice == "" || spotPrice == "0" {
runOpts := &ec2.RunInstancesInput{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone},
EbsOptimized: &s.EbsOptimized,
}
if len(ec2Tags) > 0 {
runTags := &ec2.TagSpecification{
ResourceType: aws.String("instance"),
Tags: ec2Tags,
}
runOpts.SetTagSpecifications([]*ec2.TagSpecification{runTags})
createTagsAfterInstanceStarts = false
}
if keyName != "" {
runOpts.KeyName = &keyName
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
}
} else {
runOpts.SubnetId = &s.SubnetId
runOpts.SecurityGroupIds = securityGroupIds
}
if s.ExpectedRootDevice == "ebs" {
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
}
runResp, err := ec2conn.RunInstances(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *runResp.Instances[0].InstanceId
} else {
ui.Message(fmt.Sprintf(
"Requesting spot instance '%s' for: %s",
s.InstanceType, spotPrice))
runOpts := &ec2.RequestSpotLaunchSpecification{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
Placement: &ec2.SpotPlacement{
AvailabilityZone: &availabilityZone,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
}
} else {
runOpts.SubnetId = &s.SubnetId
runOpts.SecurityGroupIds = securityGroupIds
}
if keyName != "" {
runOpts.KeyName = &keyName
}
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
SpotPrice: &spotPrice,
LaunchSpecification: runOpts,
})
if err != nil {
err := fmt.Errorf("Error launching source spot instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.spotRequest = runSpotResp.SpotInstanceRequests[0]
spotRequestId := s.spotRequest.SpotInstanceRequestId
ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId))
stateChange := StateChangeConf{
Pending: []string{"open"},
Target: "active",
Refresh: SpotRequestStateRefreshFunc(ec2conn, *spotRequestId),
StepState: state,
}
_, err = WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{spotRequestId},
})
if err != nil {
err := fmt.Errorf("Error finding spot request (%s): %s", *spotRequestId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *spotResp.SpotInstanceRequests[0].InstanceId
volTags, err := ConvertToEC2Tags(s.VolumeTags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging volumes: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
runOpts := &ec2.RunInstancesInput{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone},
EbsOptimized: &s.EbsOptimized,
}
var tagSpecs []*ec2.TagSpecification
if len(ec2Tags) > 0 {
runTags := &ec2.TagSpecification{
ResourceType: aws.String("instance"),
Tags: ec2Tags,
}
tagSpecs = append(tagSpecs, runTags)
}
if len(volTags) > 0 {
runVolTags := &ec2.TagSpecification{
ResourceType: aws.String("volume"),
Tags: volTags,
}
tagSpecs = append(tagSpecs, runVolTags)
}
if len(tagSpecs) > 0 {
runOpts.SetTagSpecifications(tagSpecs)
}
if keyName != "" {
runOpts.KeyName = &keyName
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
}
} else {
runOpts.SubnetId = &s.SubnetId
runOpts.SecurityGroupIds = securityGroupIds
}
if s.ExpectedRootDevice == "ebs" {
runOpts.InstanceInitiatedShutdownBehavior = &s.InstanceInitiatedShutdownBehavior
}
runResp, err := ec2conn.RunInstances(runOpts)
if err != nil {
err := fmt.Errorf("Error launching source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *runResp.Instances[0].InstanceId
// Set the instance ID so that the cleanup works properly
s.instanceId = instanceId
@ -305,32 +190,6 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
instance := latestInstance.(*ec2.Instance)
if createTagsAfterInstanceStarts {
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
Tags: ec2Tags,
Resources: []*string{instance.InstanceId},
})
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidInstanceID.NotFound" {
return false, nil
}
}
return true, err
})
if err != nil {
err := fmt.Errorf("Error tagging source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if s.Debug {
if instance.PublicDnsName != nil && *instance.PublicDnsName != "" {
ui.Message(fmt.Sprintf("Public DNS: %s", *instance.PublicDnsName))
@ -355,29 +214,6 @@ func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
// Cancel the spot request if it exists
if s.spotRequest != nil {
ui.Say("Cancelling the spot request...")
input := &ec2.CancelSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{s.spotRequest.SpotInstanceRequestId},
}
if _, err := ec2conn.CancelSpotInstanceRequests(input); err != nil {
ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err))
return
}
stateChange := StateChangeConf{
Pending: []string{"active", "open"},
Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestId),
Target: "cancelled",
}
_, err := WaitForState(&stateChange)
if err != nil {
ui.Error(err.Error())
}
}
// Terminate the source instance if it exists
if s.instanceId != "" {
ui.Say("Terminating the source AWS instance...")

View File

@ -0,0 +1,372 @@
package common
import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
retry "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
)
type StepRunSpotInstance struct {
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
Debug bool
EbsOptimized bool
ExpectedRootDevice string
IamInstanceProfile string
InstanceInitiatedShutdownBehavior string
InstanceType string
SourceAMI string
SpotPrice string
SpotPriceProduct string
SubnetId string
Tags map[string]string
VolumeTags map[string]string
UserData string
UserDataFile string
Ctx interpolate.Context
instanceId string
spotRequest *ec2.SpotInstanceRequest
}
func (s *StepRunSpotInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
var keyName string
if name, ok := state.GetOk("keyPair"); ok {
keyName = name.(string)
}
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
ui := state.Get("ui").(packer.Ui)
userData := s.UserData
if s.UserDataFile != "" {
contents, err := ioutil.ReadFile(s.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
}
userData = string(contents)
}
// Test if it is encoded already, and if not, encode it
if _, err := base64.StdEncoding.DecodeString(userData); err != nil {
log.Printf("[DEBUG] base64 encoding user data...")
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}
ui.Say("Launching a source AWS instance...")
image, ok := state.Get("source_image").(*ec2.Image)
if !ok {
state.Put("error", fmt.Errorf("source_image type assertion failed"))
return multistep.ActionHalt
}
s.SourceAMI = *image.ImageId
if s.ExpectedRootDevice != "" && *image.RootDeviceType != s.ExpectedRootDevice {
state.Put("error", fmt.Errorf(
"The provided source AMI has an invalid root device type.\n"+
"Expected '%s', got '%s'.",
s.ExpectedRootDevice, *image.RootDeviceType))
return multistep.ActionHalt
}
spotPrice := s.SpotPrice
availabilityZone := s.AvailabilityZone
if spotPrice == "auto" {
ui.Message(fmt.Sprintf(
"Finding spot price for %s %s...",
s.SpotPriceProduct, s.InstanceType))
// Detect the spot price
startTime := time.Now().Add(-1 * time.Hour)
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
InstanceTypes: []*string{&s.InstanceType},
ProductDescriptions: []*string{&s.SpotPriceProduct},
AvailabilityZone: &s.AvailabilityZone,
StartTime: &startTime,
})
if err != nil {
err := fmt.Errorf("Error finding spot price: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var price float64
for _, history := range resp.SpotPriceHistory {
log.Printf("[INFO] Candidate spot price: %s", *history.SpotPrice)
current, err := strconv.ParseFloat(*history.SpotPrice, 64)
if err != nil {
log.Printf("[ERR] Error parsing spot price: %s", err)
continue
}
if price == 0 || current < price {
price = current
if s.AvailabilityZone == "" {
availabilityZone = *history.AvailabilityZone
}
}
}
if price == 0 {
err := fmt.Errorf("No candidate spot prices found!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} else {
// Add 0.5 cents to minimum spot bid to ensure capacity will be available
// Avoids price-too-low error in active markets which can fluctuate
price = price + 0.005
}
spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
}
var instanceId string
ui.Say("Adding tags to source instance")
if _, exists := s.Tags["Name"]; !exists {
s.Tags["Name"] = "Packer Builder"
}
ec2Tags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ReportTags(ui, ec2Tags)
ui.Message(fmt.Sprintf(
"Requesting spot instance '%s' for: %s",
s.InstanceType, spotPrice))
runOpts := &ec2.RequestSpotLaunchSpecification{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
UserData: &userData,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
Placement: &ec2.SpotPlacement{
AvailabilityZone: &availabilityZone,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
}
} else {
runOpts.SubnetId = &s.SubnetId
runOpts.SecurityGroupIds = securityGroupIds
}
if keyName != "" {
runOpts.KeyName = &keyName
}
runSpotResp, err := ec2conn.RequestSpotInstances(&ec2.RequestSpotInstancesInput{
SpotPrice: &spotPrice,
LaunchSpecification: runOpts,
})
if err != nil {
err := fmt.Errorf("Error launching source spot instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.spotRequest = runSpotResp.SpotInstanceRequests[0]
spotRequestId := s.spotRequest.SpotInstanceRequestId
ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", *spotRequestId))
stateChange := StateChangeConf{
Pending: []string{"open"},
Target: "active",
Refresh: SpotRequestStateRefreshFunc(ec2conn, *spotRequestId),
StepState: state,
}
_, err = WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", *spotRequestId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
spotResp, err := ec2conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{spotRequestId},
})
if err != nil {
err := fmt.Errorf("Error finding spot request (%s): %s", *spotRequestId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instanceId = *spotResp.SpotInstanceRequests[0].InstanceId
// Set the instance ID so that the cleanup works properly
s.instanceId = instanceId
ui.Message(fmt.Sprintf("Instance ID: %s", instanceId))
ui.Say(fmt.Sprintf("Waiting for instance (%v) to become ready...", instanceId))
stateChangeSpot := StateChangeConf{
Pending: []string{"pending"},
Target: "running",
Refresh: InstanceStateRefreshFunc(ec2conn, instanceId),
StepState: state,
}
latestInstance, err := WaitForState(&stateChangeSpot)
if err != nil {
err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", instanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
instance := latestInstance.(*ec2.Instance)
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
Tags: ec2Tags,
Resources: []*string{instance.InstanceId},
})
if err == nil {
return true, nil
}
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "InvalidInstanceID.NotFound" {
return false, nil
}
}
return true, err
})
if err != nil {
err := fmt.Errorf("Error tagging source instance: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
volumeIds := make([]*string, 0)
for _, v := range instance.BlockDeviceMappings {
if ebs := v.Ebs; ebs != nil {
volumeIds = append(volumeIds, ebs.VolumeId)
}
}
if len(volumeIds) > 0 && len(s.VolumeTags) > 0 {
ui.Say("Adding tags to source EBS Volumes")
tags, err := ConvertToEC2Tags(s.VolumeTags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ReportTags(ui, tags)
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
Resources: volumeIds,
Tags: tags,
})
if err != nil {
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if s.Debug {
if instance.PublicDnsName != nil && *instance.PublicDnsName != "" {
ui.Message(fmt.Sprintf("Public DNS: %s", *instance.PublicDnsName))
}
if instance.PublicIpAddress != nil && *instance.PublicIpAddress != "" {
ui.Message(fmt.Sprintf("Public IP: %s", *instance.PublicIpAddress))
}
if instance.PrivateIpAddress != nil && *instance.PrivateIpAddress != "" {
ui.Message(fmt.Sprintf("Private IP: %s", *instance.PrivateIpAddress))
}
}
state.Put("instance", instance)
return multistep.ActionContinue
}
func (s *StepRunSpotInstance) Cleanup(state multistep.StateBag) {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
// Cancel the spot request if it exists
if s.spotRequest != nil {
ui.Say("Cancelling the spot request...")
input := &ec2.CancelSpotInstanceRequestsInput{
SpotInstanceRequestIds: []*string{s.spotRequest.SpotInstanceRequestId},
}
if _, err := ec2conn.CancelSpotInstanceRequests(input); err != nil {
ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err))
return
}
stateChange := StateChangeConf{
Pending: []string{"active", "open"},
Refresh: SpotRequestStateRefreshFunc(ec2conn, *s.spotRequest.SpotInstanceRequestId),
Target: "cancelled",
}
_, err := WaitForState(&stateChange)
if err != nil {
ui.Error(err.Error())
}
}
// Terminate the source instance if it exists
if s.instanceId != "" {
ui.Say("Terminating the source AWS instance...")
if _, err := ec2conn.TerminateInstances(&ec2.TerminateInstancesInput{InstanceIds: []*string{&s.instanceId}}); err != nil {
ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err))
return
}
stateChange := StateChangeConf{
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
Refresh: InstanceStateRefreshFunc(ec2conn, s.instanceId),
Target: "terminated",
}
_, err := WaitForState(&stateChange)
if err != nil {
ui.Error(err.Error())
}
}
}

View File

@ -15,9 +15,10 @@ import (
)
type StepSecurityGroup struct {
CommConfig *communicator.Config
SecurityGroupIds []string
VpcId string
CommConfig *communicator.Config
SecurityGroupIds []string
VpcId string
TemporarySGSourceCidr string
createdGroupId string
}
@ -78,15 +79,15 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
IpProtocol: aws.String("tcp"),
FromPort: aws.Int64(int64(port)),
ToPort: aws.Int64(int64(port)),
CidrIp: aws.String("0.0.0.0/0"),
CidrIp: aws.String(s.TemporarySGSourceCidr),
}
// We loop and retry this a few times because sometimes the security
// group isn't available immediately because AWS resources are eventually
// consistent.
ui.Say(fmt.Sprintf(
"Authorizing access to port %d on the temporary security group...",
port))
"Authorizing access to port %d from %s in the temporary security group...",
port, s.TemporarySGSourceCidr))
for i := 0; i < 5; i++ {
_, err = ec2conn.AuthorizeSecurityGroupIngress(req)
if err == nil {
@ -157,6 +158,7 @@ func waitUntilSecurityGroupExists(c *ec2.EC2, input *ec2.DescribeSecurityGroupsI
w := request.Waiter{
Name: "DescribeSecurityGroups",
MaxAttempts: 40,
Delay: request.ConstantWaiterDelay(5 * time.Second),
Acceptors: []request.WaiterAcceptor{
{
State: request.SuccessWaiterState,

View File

@ -11,7 +11,7 @@ import (
)
type StepStopEBSBackedInstance struct {
SpotPrice string
Skip bool
DisableStopInstance bool
}
@ -21,7 +21,7 @@ func (s *StepStopEBSBackedInstance) Run(state multistep.StateBag) multistep.Step
ui := state.Get("ui").(packer.Ui)
// Skip when it is a spot instance
if s.SpotPrice != "" && s.SpotPrice != "0" {
if s.Skip {
return multistep.ActionContinue
}

View File

@ -1,65 +0,0 @@
package common
import (
"fmt"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
)
type StepTagEBSVolumes struct {
VolumeRunTags map[string]string
Ctx interpolate.Context
}
func (s *StepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
sourceAMI := state.Get("source_image").(*ec2.Image)
ui := state.Get("ui").(packer.Ui)
if len(s.VolumeRunTags) == 0 {
return multistep.ActionContinue
}
volumeIds := make([]*string, 0)
for _, v := range instance.BlockDeviceMappings {
if ebs := v.Ebs; ebs != nil {
volumeIds = append(volumeIds, ebs.VolumeId)
}
}
if len(volumeIds) == 0 {
return multistep.ActionContinue
}
ui.Say("Adding tags to source EBS Volumes")
tags, err := ConvertToEC2Tags(s.VolumeRunTags, *ec2conn.Config.Region, *sourceAMI.ImageId, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ReportTags(ui, tags)
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
Resources: volumeIds,
Tags: tags,
})
if err != nil {
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepTagEBSVolumes) Cleanup(state multistep.StateBag) {
// No cleanup...
}

View File

@ -64,8 +64,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
@ -108,6 +109,51 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("hook", hook)
state.Put("ui", ui)
var instanceStep multistep.Step
isSpotInstance := b.config.SpotPrice != "" && b.config.SpotPrice != "0"
if isSpotInstance {
instanceStep = &awscommon.StepRunSpotInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
VolumeTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
VolumeTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
}
}
// Build the steps
steps := []multistep.Step{
&awscommon.StepPreValidate{
@ -132,33 +178,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&stepCleanupVolumes{
BlockDevices: b.config.BlockDevices,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&awscommon.StepTagEBSVolumes{
VolumeRunTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
},
instanceStep,
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
@ -176,7 +201,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&common.StepProvision{},
&awscommon.StepStopEBSBackedInstance{
SpotPrice: b.config.SpotPrice,
Skip: isSpotInstance,
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{

View File

@ -64,7 +64,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RootDevice.Prepare(&b.config.ctx)...)
@ -122,6 +123,51 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("hook", hook)
state.Put("ui", ui)
var instanceStep multistep.Step
isSpotInstance := b.config.SpotPrice != "" && b.config.SpotPrice != "0"
if isSpotInstance {
instanceStep = &awscommon.StepRunSpotInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
VolumeTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
VolumeTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
}
}
// Build the steps
steps := []multistep.Step{
&awscommon.StepPreValidate{
@ -146,29 +192,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&awscommon.StepTagEBSVolumes{
VolumeRunTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
},
instanceStep,
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
@ -186,7 +212,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&common.StepProvision{},
&awscommon.StepStopEBSBackedInstance{
SpotPrice: b.config.SpotPrice,
Skip: isSpotInstance,
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{

View File

@ -101,6 +101,50 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("hook", hook)
state.Put("ui", ui)
var instanceStep multistep.Step
isSpotInstance := b.config.SpotPrice != "" && b.config.SpotPrice != "0"
if isSpotInstance {
instanceStep = &awscommon.StepRunSpotInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.launchBlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
}
} else {
instanceStep = &awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.launchBlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
}
}
// Build the steps
steps := []multistep.Step{
&awscommon.StepSourceAMIInfo{
@ -121,26 +165,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.launchBlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
instanceStep,
&stepTagEBSVolumes{
VolumeMapping: b.config.VolumeMappings,
Ctx: b.config.ctx,
@ -162,7 +189,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&common.StepProvision{},
&awscommon.StepStopEBSBackedInstance{
SpotPrice: b.config.SpotPrice,
Skip: isSpotInstance,
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{

View File

@ -127,7 +127,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs,
b.config.AMIConfig.Prepare(&b.config.AccessConfig, &b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
if b.config.AccountId == "" {
@ -193,6 +194,44 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("hook", hook)
state.Put("ui", ui)
var instanceStep multistep.Step
if b.config.SpotPrice == "" || b.config.SpotPrice == "0" {
instanceStep = &awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
}
} else {
instanceStep = &awscommon.StepRunSpotInstance{
Debug: b.config.PackerDebug,
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
}
}
// Build the steps
steps := []multistep.Step{
&awscommon.StepPreValidate{
@ -217,24 +256,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
CommConfig: &b.config.RunConfig.Comm,
SecurityGroupIds: b.config.SecurityGroupIds,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
IamInstanceProfile: b.config.IamInstanceProfile,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
},
instanceStep,
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,

View File

@ -15,6 +15,7 @@ type uploadCmdData struct {
ManifestPath string
Region string
SecretKey string
Token string
}
type StepUploadBundle struct {
@ -28,23 +29,18 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
manifestPath := state.Get("manifest_path").(string)
ui := state.Get("ui").(packer.Ui)
region, err := config.Region()
if err != nil {
err := fmt.Errorf("Error retrieving region: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
accessKey := config.AccessKey
secretKey := config.SecretKey
session, err := config.AccessConfig.Session()
region := *session.Config.Region
accessConfig := session.Config
var token string
if err == nil && accessKey == "" && secretKey == "" {
credentials, err := accessConfig.Credentials.Get()
if err == nil {
accessKey = credentials.AccessKeyID
secretKey = credentials.SecretAccessKey
token = credentials.SessionToken
}
}
@ -55,6 +51,7 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
ManifestPath: manifestPath,
Region: region,
SecretKey: secretKey,
Token: token,
}
config.BundleUploadCommand, err = interpolate.Render(config.BundleUploadCommand, &config.ctx)
if err != nil {
@ -78,6 +75,12 @@ func (s *StepUploadBundle) Run(state multistep.StateBag) multistep.StepAction {
}
if cmd.ExitStatus != 0 {
if cmd.ExitStatus == 3 {
ui.Error(fmt.Sprintf("Please check that the bucket `%s` "+
"does not exist, or exists and is writable. This error "+
"indicates that the bucket may be owned by somebody else.",
config.S3Bucket))
}
state.Put("error", fmt.Errorf(
"Bundle upload failed. Please see the output above for more\n"+
"details on what went wrong."))

View File

@ -102,6 +102,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, rawErr.(error)
}
// If there was no template created, just return
if _, ok := state.GetOk("template"); !ok {
return nil, nil
}
// Build the artifact and return it
artifact := &Artifact{
client: client,

View File

@ -28,6 +28,7 @@ type Config struct {
PrivateNetworking bool `mapstructure:"private_networking"`
Monitoring bool `mapstructure:"monitoring"`
IPv6 bool `mapstructure:"ipv6"`
SnapshotName string `mapstructure:"snapshot_name"`
SnapshotRegions []string `mapstructure:"snapshot_regions"`
StateTimeout time.Duration `mapstructure:"state_timeout"`

View File

@ -47,6 +47,7 @@ func (s *stepCreateDroplet) Run(state multistep.StateBag) multistep.StepAction {
},
PrivateNetworking: c.PrivateNetworking,
Monitoring: c.Monitoring,
IPv6: c.IPv6,
UserData: userData,
})
if err != nil {

View File

@ -18,22 +18,36 @@ import (
)
type Communicator struct {
ContainerID string
HostDir string
ContainerDir string
Version *version.Version
Config *Config
lock sync.Mutex
ContainerID string
HostDir string
ContainerDir string
Version *version.Version
Config *Config
ContainerUser string
lock sync.Mutex
}
func (c *Communicator) Start(remote *packer.RemoteCmd) error {
var cmd *exec.Cmd
if c.Config.Pty {
cmd = exec.Command("docker", "exec", "-i", "-t", c.ContainerID, "/bin/sh", "-c", fmt.Sprintf("(%s)", remote.Command))
} else {
cmd = exec.Command("docker", "exec", "-i", c.ContainerID, "/bin/sh", "-c", fmt.Sprintf("(%s)", remote.Command))
dockerArgs := []string{
"exec",
"-i",
c.ContainerID,
"/bin/sh",
"-c",
fmt.Sprintf("(%s)", remote.Command),
}
if c.Config.Pty {
dockerArgs = append(dockerArgs[:2], append([]string{"-t"}, dockerArgs[2:]...)...)
}
if c.Config.ExecUser != "" {
dockerArgs = append(dockerArgs[:2],
append([]string{"-u", c.Config.ExecUser}, dockerArgs[2:]...)...)
}
cmd := exec.Command("docker", dockerArgs...)
var (
stdin_w io.WriteCloser
err error
@ -141,6 +155,10 @@ func (c *Communicator) uploadFile(dst string, src io.Reader, fi *os.FileInfo) er
return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err)
}
if err := c.fixDestinationOwner(dst); err != nil {
return err
}
return nil
}
@ -194,6 +212,10 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error
return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err)
}
if err := c.fixDestinationOwner(dst); err != nil {
return err
}
return nil
}
@ -297,3 +319,25 @@ func (c *Communicator) run(cmd *exec.Cmd, remote *packer.RemoteCmd, stdin io.Wri
// Set the exit status which triggers waiters
remote.SetExited(exitStatus)
}
// TODO Workaround for #5307. Remove once #5409 is fixed.
func (c *Communicator) fixDestinationOwner(destination string) error {
if !c.Config.FixUploadOwner {
return nil
}
owner := c.ContainerUser
if owner == "" {
owner = "root"
}
chownArgs := []string{
"docker", "exec", "--user", "root", c.ContainerID, "/bin/sh", "-c",
fmt.Sprintf("chown -R %s %s", owner, destination),
}
if output, err := exec.Command(chownArgs[0], chownArgs[1:]...).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to set owner of the uploaded file: %s, %s", err, output)
}
return nil
}

View File

@ -209,6 +209,82 @@ func TestLargeDownload(t *testing.T) {
}
// TestFixUploadOwner verifies that owner of uploaded files is the user the container is running as.
func TestFixUploadOwner(t *testing.T) {
ui := packer.TestUi(t)
cache := &packer.FileCache{CacheDir: os.TempDir()}
tpl, err := template.Parse(strings.NewReader(testFixUploadOwnerTemplate))
if err != nil {
t.Fatalf("Unable to parse config: %s", err)
}
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1")
}
cmd := exec.Command("docker", "-v")
cmd.Run()
if !cmd.ProcessState.Success() {
t.Error("docker command not found; please make sure docker is installed")
}
// Setup the builder
builder := &Builder{}
warnings, err := builder.Prepare(tpl.Builders["docker"].Config)
if err != nil {
t.Fatalf("Error preparing configuration %s", err)
}
if len(warnings) > 0 {
t.Fatal("Encountered configuration warnings; aborting")
}
// Setup the provisioners
fileProvisioner := &file.Provisioner{}
err = fileProvisioner.Prepare(tpl.Provisioners[0].Config)
if err != nil {
t.Fatalf("Error preparing single file upload provisioner: %s", err)
}
dirProvisioner := &file.Provisioner{}
err = dirProvisioner.Prepare(tpl.Provisioners[1].Config)
if err != nil {
t.Fatalf("Error preparing directory upload provisioner: %s", err)
}
shellProvisioner := &shell.Provisioner{}
err = shellProvisioner.Prepare(tpl.Provisioners[2].Config)
if err != nil {
t.Fatalf("Error preparing shell provisioner: %s", err)
}
verifyProvisioner := &shell.Provisioner{}
err = verifyProvisioner.Prepare(tpl.Provisioners[3].Config)
if err != nil {
t.Fatalf("Error preparing verification provisioner: %s", err)
}
// Add hooks so the provisioners run during the build
hooks := map[string][]packer.Hook{}
hooks[packer.HookProvision] = []packer.Hook{
&packer.ProvisionHook{
Provisioners: []packer.Provisioner{
fileProvisioner,
dirProvisioner,
shellProvisioner,
verifyProvisioner,
},
ProvisionerTypes: []string{"", "", "", ""},
},
}
hook := &packer.DispatchHook{Mapping: hooks}
artifact, err := builder.Run(ui, hook, cache)
if err != nil {
t.Fatalf("Error running build %s", err)
}
defer artifact.Destroy()
}
const dockerBuilderConfig = `
{
"builders": [
@ -269,3 +345,40 @@ const dockerLargeBuilderConfig = `
]
}
`
const testFixUploadOwnerTemplate = `
{
"builders": [
{
"type": "docker",
"image": "ubuntu",
"discard": true,
"run_command": ["-d", "-i", "-t", "-u", "42", "{{.Image}}", "/bin/sh"]
}
],
"provisioners": [
{
"type": "file",
"source": "test-fixtures/onecakes/strawberry",
"destination": "/tmp/strawberry-cake"
},
{
"type": "file",
"source": "test-fixtures/manycakes",
"destination": "/tmp/"
},
{
"type": "shell",
"inline": "touch /tmp/testUploadOwner"
},
{
"type": "shell",
"inline": [
"[ $(stat -c %u /tmp/strawberry-cake) -eq 42 ] || (echo 'Invalid owner of /tmp/strawberry-cake' && exit 1)",
"[ $(stat -c %u /tmp/testUploadOwner) -eq 42 ] || (echo 'Invalid owner of /tmp/testUploadOwner' && exit 1)",
"find /tmp/manycakes | xargs -n1 -IFILE /bin/sh -c '[ $(stat -c %u FILE) -eq 42 ] || (echo \"Invalid owner of FILE\" && exit 1)'"
]
}
]
}
`

View File

@ -23,24 +23,25 @@ type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
Commit bool
Discard bool
ExportPath string `mapstructure:"export_path"`
Image string
Pty bool
Pull bool
RunCommand []string `mapstructure:"run_command"`
Volumes map[string]string
Privileged bool `mapstructure:"privileged"`
Author string
Changes []string
Message string
ContainerDir string `mapstructure:"container_dir"`
Author string
Changes []string
Commit bool
ContainerDir string `mapstructure:"container_dir"`
Discard bool
ExecUser string `mapstructure:"exec_user"`
ExportPath string `mapstructure:"export_path"`
Image string
Message string
Privileged bool `mapstructure:"privileged"`
Pty bool
Pull bool
RunCommand []string `mapstructure:"run_command"`
Volumes map[string]string
FixUploadOwner bool `mapstructure:"fix_upload_owner"`
// This is used to login to dockerhub to pull a private base container. For
// pushing to dockerhub, see the docker post-processors
Login bool
LoginEmail string `mapstructure:"login_email"`
LoginPassword string `mapstructure:"login_password"`
LoginServer string `mapstructure:"login_server"`
LoginUsername string `mapstructure:"login_username"`
@ -53,6 +54,8 @@ type Config struct {
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
c.FixUploadOwner = true
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,

View File

@ -28,7 +28,7 @@ type Driver interface {
// Login. This will lock the driver from performing another Login
// until Logout is called. Therefore, any users MUST call Logout.
Login(repo, email, username, password string) error
Login(repo, username, password string) error
// Logout. This can only be called if Login succeeded.
Logout(repo string) error

View File

@ -147,13 +147,10 @@ func (d *DockerDriver) IPAddress(id string) (string, error) {
return strings.TrimSpace(stdout.String()), nil
}
func (d *DockerDriver) Login(repo, email, user, pass string) error {
func (d *DockerDriver) Login(repo, user, pass string) error {
d.l.Lock()
args := []string{"login"}
if email != "" {
args = append(args, "-e", email)
}
if user != "" {
args = append(args, "-u", user)
}

View File

@ -29,7 +29,6 @@ type MockDriver struct {
IPAddressErr error
LoginCalled bool
LoginEmail string
LoginUsername string
LoginPassword string
LoginRepo string
@ -115,10 +114,9 @@ func (d *MockDriver) IPAddress(id string) (string, error) {
return d.IPAddressResult, d.IPAddressErr
}
func (d *MockDriver) Login(r, e, u, p string) error {
func (d *MockDriver) Login(r, u, p string) error {
d.LoginCalled = true
d.LoginRepo = r
d.LoginEmail = e
d.LoginUsername = u
d.LoginPassword = p
return d.LoginErr

View File

@ -8,42 +8,16 @@ import (
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/hashicorp/packer/builder/amazon/common"
)
type AwsAccessConfig struct {
AccessKey string `mapstructure:"aws_access_key"`
SecretKey string `mapstructure:"aws_secret_key"`
Token string `mapstructure:"aws_token"`
}
// Config returns a valid aws.Config object for access to AWS services, or
// an error if the authentication and region couldn't be resolved
func (c *AwsAccessConfig) config(region string) (*aws.Config, error) {
var creds *credentials.Credentials
config := aws.NewConfig().WithRegion(region).WithMaxRetries(11)
session, err := session.NewSession(config)
if err != nil {
return nil, err
}
creds = credentials.NewChainCredentials([]credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: c.AccessKey,
SecretAccessKey: c.SecretKey,
SessionToken: c.Token,
}},
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(session),
},
})
return config.WithCredentials(creds), nil
Profile string `mapstructure:"aws_profile"`
cfg *common.AccessConfig
}
// Get a login token for Amazon AWS ECR. Returns username and password
@ -60,12 +34,15 @@ func (c *AwsAccessConfig) EcrGetLogin(ecrUrl string) (string, string, error) {
log.Println(fmt.Sprintf("Getting ECR token for account: %s in %s..", accountId, region))
awsConfig, err := c.config(region)
if err != nil {
return "", "", err
c.cfg = &common.AccessConfig{
AccessKey: c.AccessKey,
ProfileName: c.Profile,
RawRegion: region,
SecretKey: c.SecretKey,
Token: c.Token,
}
session, err := session.NewSession(awsConfig)
session, err := c.cfg.Session()
if err != nil {
return "", "", fmt.Errorf("failed to create session: %s", err)
}

View File

@ -1,7 +1,10 @@
package docker
import (
"fmt"
"github.com/mitchellh/multistep"
"os/exec"
"strings"
)
type StepConnectDocker struct{}
@ -19,14 +22,21 @@ func (s *StepConnectDocker) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
containerUser, err := getContainerUser(containerId)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
// Create the communicator that talks to Docker via various
// os/exec tricks.
comm := &Communicator{
ContainerID: containerId,
HostDir: tempDir,
ContainerDir: config.ContainerDir,
Version: version,
Config: config,
ContainerID: containerId,
HostDir: tempDir,
ContainerDir: config.ContainerDir,
Version: version,
Config: config,
ContainerUser: containerUser,
}
state.Put("communicator", comm)
@ -34,3 +44,16 @@ func (s *StepConnectDocker) Run(state multistep.StateBag) multistep.StepAction {
}
func (s *StepConnectDocker) Cleanup(state multistep.StateBag) {}
func getContainerUser(containerId string) (string, error) {
inspectArgs := []string{"docker", "inspect", "--format", "{{.Config.User}}", containerId}
stdout, err := exec.Command(inspectArgs[0], inspectArgs[1:]...).Output()
if err != nil {
errStr := fmt.Sprintf("Failed to inspect the container: %s", err)
if ee, ok := err.(*exec.ExitError); ok {
errStr = fmt.Sprintf("%s, %s", errStr, ee.Stderr)
}
return "", fmt.Errorf(errStr)
}
return strings.TrimSpace(string(stdout)), nil
}

View File

@ -2,9 +2,10 @@ package docker
import (
"fmt"
"log"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"log"
)
type StepPull struct{}
@ -40,7 +41,6 @@ func (s *StepPull) Run(state multistep.StateBag) multistep.StepAction {
ui.Message("Logging in...")
err := driver.Login(
config.LoginServer,
config.LoginEmail,
config.LoginUsername,
config.LoginPassword)
if err != nil {

View File

@ -28,7 +28,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
return warnings, errs
}
b.config = c
return warnings, nil
}

View File

@ -35,6 +35,7 @@ type Config struct {
ImageName string `mapstructure:"image_name"`
ImageDescription string `mapstructure:"image_description"`
ImageFamily string `mapstructure:"image_family"`
ImageLabels map[string]string `mapstructure:"image_labels"`
InstanceName string `mapstructure:"instance_name"`
Labels map[string]string `mapstructure:"labels"`
MachineType string `mapstructure:"machine_type"`
@ -64,6 +65,7 @@ type Config struct {
func NewConfig(raws ...interface{}) (*Config, []string, error) {
c := new(Config)
c.ctx.Funcs = TemplateFuncs
err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
@ -80,10 +82,14 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
var errs *packer.MultiError
// Set defaults.
if c.Network == "" {
if c.Network == "" && c.Subnetwork == "" {
c.Network = "default"
}
if c.NetworkProjectId == "" {
c.NetworkProjectId = c.ProjectId
}
if c.DiskSizeGb == 0 {
c.DiskSizeGb = 10
}

View File

@ -305,7 +305,11 @@ func testConfig(t *testing.T) map[string]interface{} {
"source_image": "foo",
"ssh_username": "root",
"image_family": "bar",
"zone": "us-east1-a",
"image_labels": map[string]string{
"label-1": "value-1",
"label-2": "value-2",
},
"zone": "us-east1-a",
}
}

View File

@ -11,7 +11,7 @@ import (
type Driver interface {
// CreateImage creates an image from the given disk in Google Compute
// Engine.
CreateImage(name, description, family, zone, disk string) (<-chan *Image, <-chan error)
CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error)
// DeleteImage deletes the image with the given name.
DeleteImage(name string) <-chan error

View File

@ -10,7 +10,6 @@ import (
"fmt"
"log"
"net/http"
"net/url"
"runtime"
"strings"
"time"
@ -98,11 +97,12 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
}, nil
}
func (d *driverGCE) CreateImage(name, description, family, zone, disk string) (<-chan *Image, <-chan error) {
func (d *driverGCE) CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error) {
gce_image := &compute.Image{
Description: description,
Name: name,
Family: family,
Labels: image_labels,
SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk),
SourceType: "RAW",
}
@ -298,56 +298,9 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
}
// TODO(mitchellh): deprecation warnings
networkSelfLink := ""
subnetworkSelfLink := ""
if u, err := url.Parse(c.Network); err == nil && (u.Scheme == "https" || u.Scheme == "http") {
// Network is a full server URL
// Parse out Network and NetworkProjectId from URL
// https://www.googleapis.com/compute/v1/projects/<ProjectId>/global/networks/<Network>
networkSelfLink = c.Network
parts := strings.Split(u.String(), "/")
if len(parts) >= 10 {
c.NetworkProjectId = parts[6]
c.Network = parts[9]
}
}
if u, err := url.Parse(c.Subnetwork); err == nil && (u.Scheme == "https" || u.Scheme == "http") {
// Subnetwork is a full server URL
subnetworkSelfLink = c.Subnetwork
}
// If subnetwork is ID's and not full service URL's look them up.
if subnetworkSelfLink == "" {
// Get the network
if c.NetworkProjectId == "" {
c.NetworkProjectId = d.projectId
}
d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network))
network, err := d.service.Networks.Get(c.NetworkProjectId, c.Network).Do()
if err != nil {
return nil, err
}
networkSelfLink = network.SelfLink
// Subnetwork
// Validate Subnetwork config now that we have some info about the network
if !network.AutoCreateSubnetworks && len(network.Subnetworks) > 0 {
// Network appears to be in "custom" mode, so a subnetwork is required
if c.Subnetwork == "" {
return nil, fmt.Errorf("a subnetwork must be specified")
}
}
// Get the subnetwork
if c.Subnetwork != "" {
d.ui.Message(fmt.Sprintf("Loading subnetwork: %s for region: %s", c.Subnetwork, c.Region))
subnetwork, err := d.service.Subnetworks.Get(c.NetworkProjectId, c.Region, c.Subnetwork).Do()
if err != nil {
return nil, err
}
subnetworkSelfLink = subnetwork.SelfLink
}
networkId, subnetworkId, err := getNetworking(c)
if err != nil {
return nil, err
}
var accessconfig *compute.AccessConfig
@ -416,8 +369,8 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
NetworkInterfaces: []*compute.NetworkInterface{
{
AccessConfigs: []*compute.AccessConfig{accessconfig},
Network: networkSelfLink,
Subnetwork: subnetworkSelfLink,
Network: networkId,
Subnetwork: subnetworkId,
},
},
Scheduling: &compute.Scheduling{
@ -610,7 +563,6 @@ func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefre
}
}
// stateRefreshFunc is used to refresh the state of a thing and is
// used in conjunction with waitForState.
type stateRefreshFunc func() (string, error)

View File

@ -8,6 +8,7 @@ type DriverMock struct {
CreateImageName string
CreateImageDesc string
CreateImageFamily string
CreateImageLabels map[string]string
CreateImageZone string
CreateImageDisk string
CreateImageResultLicenses []string
@ -81,10 +82,11 @@ type DriverMock struct {
WaitForInstanceErrCh <-chan error
}
func (d *DriverMock) CreateImage(name, description, family, zone, disk string) (<-chan *Image, <-chan error) {
func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error) {
d.CreateImageName = name
d.CreateImageDesc = description
d.CreateImageFamily = family
d.CreateImageLabels = image_labels
d.CreateImageZone = zone
d.CreateImageDisk = disk
if d.CreateImageResultProjectId == "" {
@ -103,6 +105,7 @@ func (d *DriverMock) CreateImage(name, description, family, zone, disk string) (
if resultCh == nil {
ch := make(chan *Image, 1)
ch <- &Image{
Labels: d.CreateImageLabels,
Licenses: d.CreateImageResultLicenses,
Name: name,
ProjectId: d.CreateImageResultProjectId,

View File

@ -5,6 +5,7 @@ import (
)
type Image struct {
Labels map[string]string
Licenses []string
Name string
ProjectId string

View File

@ -0,0 +1,59 @@
package googlecompute
import (
"fmt"
"strings"
)
// This method will build a network and subnetwork ID from the provided
// instance config, and return them in that order.
func getNetworking(c *InstanceConfig) (string, string, error) {
networkId := c.Network
subnetworkId := c.Subnetwork
// Apply network naming requirements per
// https://cloud.google.com/compute/docs/reference/latest/instances#resource
switch c.Network {
// It is possible to omit the network property as long as a subnet is
// specified. That will be validated later.
case "":
break
// This special short name should be expanded.
case "default":
networkId = "global/networks/default"
// A value other than "default" was provided for the network name.
default:
// If the value doesn't contain a slash, we assume it's not a full or
// partial URL. We will expand it into a partial URL here and avoid
// making an API call to discover the network as it's common for the
// caller to not have permission against network discovery APIs.
if !strings.Contains(c.Network, "/") {
networkId = "projects/" + c.NetworkProjectId + "/global/networks/" + c.Network
}
}
// Apply subnetwork naming requirements per
// https://cloud.google.com/compute/docs/reference/latest/instances#resource
switch c.Subnetwork {
case "":
// You can't omit both subnetwork and network
if networkId == "" {
return networkId, subnetworkId, fmt.Errorf("both network and subnetwork were empty.")
}
// An empty subnetwork is only valid for networks in legacy mode or
// auto-subnet mode. We could make an API call to get that information
// about the network, but it's common for the caller to not have
// permission to that API. We'll proceed assuming they're correct in
// omitting the subnetwork and let the compute.insert API surface an
// error about an invalid network configuration if it exists.
break
default:
// If the value doesn't contain a slash, we assume it's not a full or
// partial URL. We will expand it into a partial URL here and avoid
// making a call to discover the subnetwork.
if !strings.Contains(c.Subnetwork, "/") {
subnetworkId = "projects/" + c.NetworkProjectId + "/regions/" + c.Region + "/subnetworks/" + c.Subnetwork
}
}
return networkId, subnetworkId, nil
}

View File

@ -0,0 +1,72 @@
package googlecompute
import (
"testing"
)
func TestGetNetworking(t *testing.T) {
cases := []struct {
c *InstanceConfig
expectedNetwork string
expectedSubnetwork string
error bool
}{
{
c: &InstanceConfig{
Network: "default",
Subnetwork: "",
NetworkProjectId: "project-id",
Region: "region-id",
},
expectedNetwork: "global/networks/default",
expectedSubnetwork: "",
error: false,
},
{
c: &InstanceConfig{
Network: "",
Subnetwork: "",
NetworkProjectId: "project-id",
Region: "region-id",
},
expectedNetwork: "",
expectedSubnetwork: "",
error: true,
},
{
c: &InstanceConfig{
Network: "some/network/path",
Subnetwork: "some/subnetwork/path",
NetworkProjectId: "project-id",
Region: "region-id",
},
expectedNetwork: "some/network/path",
expectedSubnetwork: "some/subnetwork/path",
error: false,
},
{
c: &InstanceConfig{
Network: "network-value",
Subnetwork: "subnetwork-value",
NetworkProjectId: "project-id",
Region: "region-id",
},
expectedNetwork: "projects/project-id/global/networks/network-value",
expectedSubnetwork: "projects/project-id/regions/region-id/subnetworks/subnetwork-value",
error: false,
},
}
for _, tc := range cases {
n, sn, err := getNetworking(tc.c)
if n != tc.expectedNetwork {
t.Errorf("Expected network %q but got network %q", tc.expectedNetwork, n)
}
if sn != tc.expectedSubnetwork {
t.Errorf("Expected subnetwork %q but got subnetwork %q", tc.expectedSubnetwork, sn)
}
if !tc.error && err != nil {
t.Errorf("Did not expect an error but got: %v", err)
}
}
}

View File

@ -39,7 +39,7 @@ func (s *StepCreateImage) Run(state multistep.StateBag) multistep.StepAction {
imageCh, errCh := driver.CreateImage(
config.ImageName, config.ImageDescription, config.ImageFamily, config.Zone,
config.DiskName)
config.DiskName, config.ImageLabels)
var err error
select {
case err = <-errCh:

View File

@ -46,6 +46,7 @@ func TestStepCreateImage(t *testing.T) {
assert.Equal(t, d.CreateImageFamily, c.ImageFamily, "Incorrect image family passed to driver.")
assert.Equal(t, d.CreateImageZone, c.Zone, "Incorrect image zone passed to driver.")
assert.Equal(t, d.CreateImageDisk, c.DiskName, "Incorrect disk passed to driver.")
assert.Equal(t, d.CreateImageLabels, c.ImageLabels, "Incorrect image_labels passed to driver.")
}
func TestStepCreateImage_errorOnChannel(t *testing.T) {

View File

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

View File

@ -0,0 +1,45 @@
package googlecompute
import "testing"
func Test_templateCleanImageName(t *testing.T) {
vals := []struct {
origName string
expected string
}{
// test that valid name is unchanged
{
origName: "abcde-012345xyz",
expected: "abcde-012345xyz",
},
//test that capital letters are converted to lowercase
{
origName: "ABCDE-012345xyz",
expected: "abcde-012345xyz",
},
// test that periods and colons are converted to hyphens
{
origName: "abcde-012345v1.0:0",
expected: "abcde-012345v1-0-0",
},
// Name starting with number is not valid, but not in scope of this
// function to correct
{
origName: "012345v1.0:0",
expected: "012345v1-0-0",
},
// Name over 64 chars is not valid, but not corrected by this function.
{
origName: "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
expected: "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
},
}
for _, v := range vals {
name := templateCleanImageName(v.origName)
if name != v.expected {
t.Fatalf("template names do not match: expected %s got %s\n", v.expected, name)
}
}
}

View File

@ -64,10 +64,16 @@ type Driver interface {
DeleteVirtualSwitch(string) error
CreateVirtualMachine(string, string, int64, int64, string, uint) error
CreateVirtualMachine(string, string, string, string, int64, int64, string, uint) error
AddVirtualMachineHardDrive(string, string, string, int64, string) error
CloneVirtualMachine(string, string, string, bool, string, string, string, int64, string) error
DeleteVirtualMachine(string) error
GetVirtualMachineGeneration(string) (uint, error)
SetVirtualMachineCpuCount(string, uint) error
SetVirtualMachineMacSpoofing(string, bool) error

View File

@ -107,6 +107,10 @@ func (d *HypervPS4Driver) GetHostName(ip string) (string, error) {
return powershell.GetHostName(ip)
}
func (d *HypervPS4Driver) GetVirtualMachineGeneration(vmName string) (uint, error) {
return hyperv.GetVirtualMachineGeneration(vmName)
}
// Finds the IP address of a host adapter connected to switch
func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) {
res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName)
@ -166,8 +170,16 @@ func (d *HypervPS4Driver) CreateVirtualSwitch(switchName string, switchType stri
return hyperv.CreateVirtualSwitch(switchName, switchType)
}
func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, ram int64, diskSize int64, switchName string, generation uint) error {
return hyperv.CreateVirtualMachine(vmName, path, ram, diskSize, switchName, generation)
func (d *HypervPS4Driver) AddVirtualMachineHardDrive(vmName string, vhdFile string, vhdName string, vhdSizeBytes int64, controllerType string) error {
return hyperv.AddVirtualMachineHardDiskDrive(vmName, vhdFile, vhdName, vhdSizeBytes, controllerType)
}
func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdPath string, ram int64, diskSize int64, switchName string, generation uint) error {
return hyperv.CreateVirtualMachine(vmName, path, harddrivePath, vhdPath, ram, diskSize, switchName, generation)
}
func (d *HypervPS4Driver) CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
return hyperv.CloneVirtualMachine(cloneFromVmxcPath, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots, vmName, path, harddrivePath, ram, switchName)
}
func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error {
@ -283,9 +295,7 @@ func (d *HypervPS4Driver) verifyPSHypervModule() error {
return err
}
res := strings.TrimSpace(cmdOut)
if res == "False" {
if powershell.IsFalse(cmdOut) {
err := fmt.Errorf("%s", "PS Hyper-V module is not loaded. Make sure Hyper-V feature is on.")
return err
}
@ -293,23 +303,36 @@ func (d *HypervPS4Driver) verifyPSHypervModule() error {
return nil
}
func (d *HypervPS4Driver) isCurrentUserAHyperVAdministrator() (bool, error) {
//SID:S-1-5-32-578 = 'BUILTIN\Hyper-V Administrators'
//https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
var script = `
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = new-object System.Security.Principal.WindowsPrincipal($identity)
$hypervrole = [System.Security.Principal.SecurityIdentifier]"S-1-5-32-578"
return $principal.IsInRole($hypervrole)
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script)
if err != nil {
return false, err
}
return powershell.IsTrue(cmdOut), nil
}
func (d *HypervPS4Driver) verifyHypervPermissions() error {
log.Printf("Enter method: %s", "verifyHypervPermissions")
//SID:S-1-5-32-578 = 'BUILTIN\Hyper-V Administrators'
//https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
hypervAdminCmd := "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('S-1-5-32-578')"
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(hypervAdminCmd)
hyperVAdmin, err := d.isCurrentUserAHyperVAdministrator()
if err != nil {
return err
log.Printf("Error discovering if current is is a Hyper-V Admin: %s", err)
}
if !hyperVAdmin {
res := strings.TrimSpace(cmdOut)
if res == "False" {
isAdmin, _ := powershell.IsCurrentUserAnAdministrator()
if !isAdmin {

View File

@ -0,0 +1,139 @@
package common
import (
"fmt"
"log"
"path/filepath"
"strings"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
)
// This step clones an existing virtual machine.
//
// Produces:
// VMName string - The name of the VM
type StepCloneVM struct {
CloneFromVMXCPath string
CloneFromVMName string
CloneFromSnapshotName string
CloneAllSnapshots bool
VMName string
SwitchName string
RamSize uint
Cpu uint
EnableMacSpoofing bool
EnableDynamicMemory bool
EnableSecureBoot bool
EnableVirtualizationExtensions bool
}
func (s *StepCloneVM) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Cloning virtual machine...")
path := state.Get("packerTempDir").(string)
// Determine if we even have an existing virtual harddrive to attach
harddrivePath := ""
if harddrivePathRaw, ok := state.GetOk("iso_path"); ok {
extension := strings.ToLower(filepath.Ext(harddrivePathRaw.(string)))
if extension == ".vhd" || extension == ".vhdx" {
harddrivePath = harddrivePathRaw.(string)
} else {
log.Println("No existing virtual harddrive, not attaching.")
}
} else {
log.Println("No existing virtual harddrive, not attaching.")
}
// convert the MB to bytes
ramSize := int64(s.RamSize * 1024 * 1024)
err := driver.CloneVirtualMachine(s.CloneFromVMXCPath, s.CloneFromVMName, s.CloneFromSnapshotName, s.CloneAllSnapshots, s.VMName, path, harddrivePath, ramSize, s.SwitchName)
if err != nil {
err := fmt.Errorf("Error cloning virtual machine: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
err = driver.SetVirtualMachineCpuCount(s.VMName, s.Cpu)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.EnableDynamicMemory {
err = driver.SetVirtualMachineDynamicMemory(s.VMName, s.EnableDynamicMemory)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine dynamic memory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if s.EnableMacSpoofing {
err = driver.SetVirtualMachineMacSpoofing(s.VMName, s.EnableMacSpoofing)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine mac spoofing: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
generation, err := driver.GetVirtualMachineGeneration(s.VMName)
if err != nil {
err := fmt.Errorf("Error detecting vm generation: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if generation == 2 {
err = driver.SetVirtualMachineSecureBoot(s.VMName, s.EnableSecureBoot)
if err != nil {
err := fmt.Errorf("Error setting secure boot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if s.EnableVirtualizationExtensions {
//This is only supported on Windows 10 and Windows Server 2016 onwards
err = driver.SetVirtualMachineVirtualizationExtensions(s.VMName, s.EnableVirtualizationExtensions)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine virtualization extensions: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Set the final name in the state bag so others can use it
state.Put("vmName", s.VMName)
return multistep.ActionContinue
}
func (s *StepCloneVM) Cleanup(state multistep.StateBag) {
if s.VMName == "" {
return
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Unregistering and deleting virtual machine...")
err := driver.DeleteVirtualMachine(s.VMName)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err))
}
}

View File

@ -10,8 +10,12 @@ import (
)
type StepCreateTempDir struct {
TempPath string
dirPath string
// The user-supplied root directores into which we create subdirectories.
TempPath string
VhdTempPath string
// The subdirectories with the randomly generated name.
dirPath string
vhdDirPath string
}
func (s *StepCreateTempDir) Run(state multistep.StateBag) multistep.StepAction {
@ -34,23 +38,47 @@ func (s *StepCreateTempDir) Run(state multistep.StateBag) multistep.StepAction {
s.dirPath = packerTempDir
state.Put("packerTempDir", packerTempDir)
if s.VhdTempPath == "" {
// Fall back to regular temp dir if no separate VHD temp dir set.
state.Put("packerVhdTempDir", packerTempDir)
} else {
packerVhdTempDir, err := ioutil.TempDir(s.VhdTempPath, "packerhv-vhd")
if err != nil {
err := fmt.Errorf("Error creating temporary VHD directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.vhdDirPath = packerVhdTempDir
state.Put("packerVhdTempDir", packerVhdTempDir)
}
// ui.Say("packerTempDir = '" + packerTempDir + "'")
return multistep.ActionContinue
}
func (s *StepCreateTempDir) Cleanup(state multistep.StateBag) {
if s.dirPath == "" {
return
}
ui := state.Get("ui").(packer.Ui)
ui.Say("Deleting temporary directory...")
if s.dirPath != "" {
ui.Say("Deleting temporary directory...")
err := os.RemoveAll(s.dirPath)
err := os.RemoveAll(s.dirPath)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting temporary directory: %s", err))
if err != nil {
ui.Error(fmt.Sprintf("Error deleting temporary directory: %s", err))
}
}
if s.vhdDirPath != "" && s.dirPath != s.vhdDirPath {
ui.Say("Deleting temporary VHD directory...")
err := os.RemoveAll(s.vhdDirPath)
if err != nil {
ui.Error(fmt.Sprintf("Error deleting temporary VHD directory: %s", err))
}
}
}

View File

@ -2,6 +2,9 @@ package common
import (
"fmt"
"log"
"path/filepath"
"strings"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
@ -14,6 +17,7 @@ import (
type StepCreateVM struct {
VMName string
SwitchName string
HarddrivePath string
RamSize uint
DiskSize uint
Generation uint
@ -22,6 +26,7 @@ type StepCreateVM struct {
EnableDynamicMemory bool
EnableSecureBoot bool
EnableVirtualizationExtensions bool
AdditionalDiskSize []uint
}
func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
@ -31,11 +36,25 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
path := state.Get("packerTempDir").(string)
// Determine if we even have an existing virtual harddrive to attach
harddrivePath := ""
if harddrivePathRaw, ok := state.GetOk("iso_path"); ok {
extension := strings.ToLower(filepath.Ext(harddrivePathRaw.(string)))
if extension == ".vhd" || extension == ".vhdx" {
harddrivePath = harddrivePathRaw.(string)
} else {
log.Println("No existing virtual harddrive, not attaching.")
}
} else {
log.Println("No existing virtual harddrive, not attaching.")
}
vhdPath := state.Get("packerVhdTempDir").(string)
// convert the MB to bytes
ramSize := int64(s.RamSize * 1024 * 1024)
diskSize := int64(s.DiskSize * 1024 * 1024)
err := driver.CreateVirtualMachine(s.VMName, path, ramSize, diskSize, s.SwitchName, s.Generation)
err := driver.CreateVirtualMachine(s.VMName, path, harddrivePath, vhdPath, ramSize, diskSize, s.SwitchName, s.Generation)
if err != nil {
err := fmt.Errorf("Error creating virtual machine: %s", err)
state.Put("error", err)
@ -45,26 +64,24 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
err = driver.SetVirtualMachineCpuCount(s.VMName, s.Cpu)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err)
err := fmt.Errorf("Error setting virtual machine cpu count: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.EnableDynamicMemory {
err = driver.SetVirtualMachineDynamicMemory(s.VMName, s.EnableDynamicMemory)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine dynamic memory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
err = driver.SetVirtualMachineDynamicMemory(s.VMName, s.EnableDynamicMemory)
if err != nil {
err := fmt.Errorf("Error setting virtual machine dynamic memory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.EnableMacSpoofing {
err = driver.SetVirtualMachineMacSpoofing(s.VMName, s.EnableMacSpoofing)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine mac spoofing: %s", err)
err := fmt.Errorf("Error setting virtual machine mac spoofing: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
@ -85,13 +102,27 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
//This is only supported on Windows 10 and Windows Server 2016 onwards
err = driver.SetVirtualMachineVirtualizationExtensions(s.VMName, s.EnableVirtualizationExtensions)
if err != nil {
err := fmt.Errorf("Error creating setting virtual machine virtualization extensions: %s", err)
err := fmt.Errorf("Error setting virtual machine virtualization extensions: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if len(s.AdditionalDiskSize) > 0 {
for index, size := range s.AdditionalDiskSize {
diskSize := int64(size * 1024 * 1024)
diskFile := fmt.Sprintf("%s-%d.vhdx", s.VMName, index)
err = driver.AddVirtualMachineHardDrive(s.VMName, vhdPath, diskFile, diskSize, "SCSI")
if err != nil {
err := fmt.Errorf("Error creating and attaching additional disk drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
// Set the final name in the state bag so others can use it
state.Put("vmName", s.VMName)

View File

@ -5,6 +5,8 @@ import (
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"log"
"path/filepath"
"strings"
)
type StepMountDvdDrive struct {
@ -17,7 +19,21 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction {
errorMsg := "Error mounting dvd drive: %s"
vmName := state.Get("vmName").(string)
isoPath := state.Get("iso_path").(string)
// Determine if we even have a dvd disk to attach
var isoPath string
if isoPathRaw, ok := state.GetOk("iso_path"); ok {
isoPath = isoPathRaw.(string)
} else {
log.Println("No dvd disk, not attaching.")
return multistep.ActionContinue
}
// Determine if its a virtual hdd to mount
if strings.ToLower(filepath.Ext(isoPath)) == ".vhd" || strings.ToLower(filepath.Ext(isoPath)) == ".vhdx" {
log.Println("Its a hard disk, not attaching.")
return multistep.ActionContinue
}
// should be able to mount up to 60 additional iso images using SCSI
// but Windows would only allow a max of 22 due to available drive letters

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
hypervcommon "github.com/hashicorp/packer/builder/hyperv/common"
@ -82,8 +83,15 @@ type Config struct {
EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"`
TempPath string `mapstructure:"temp_path"`
// A separate path can be used for storing the VM's disk image. The purpose is to enable
// reading and writing to take place on different physical disks (read from VHD temp path
// write to regular temp path while exporting the VM) to eliminate a single-disk bottleneck.
VhdTempPath string `mapstructure:"vhd_temp_path"`
Communicator string `mapstructure:"communicator"`
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
SkipCompaction bool `mapstructure:"skip_compaction"`
ctx interpolate.Context
@ -119,9 +127,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
err = b.checkDiskSize()
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
if len(b.config.ISOConfig.ISOUrls) < 1 || (strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhd" && strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhdx") {
//We only create a new hard drive if an existing one to copy from does not exist
err = b.checkDiskSize()
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
}
err = b.checkRamSize()
@ -143,7 +154,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.Cpu = 1
}
if b.config.Generation != 2 {
if b.config.Generation < 1 || b.config.Generation > 2 {
b.config.Generation = 1
}
@ -154,10 +165,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if len(b.config.AdditionalDiskSize) > 64 {
err = errors.New("VM's currently support a maximun of 64 additional SCSI attached disks.")
errs = packer.MultiErrorAppend(errs, err)
}
log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName))
log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName))
// Errors
if b.config.GuestAdditionsMode == "" {
if b.config.GuestAdditionsPath != "" {
b.config.GuestAdditionsMode = "attach"
@ -296,7 +313,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
steps := []multistep.Step{
&hypervcommon.StepCreateTempDir{
TempPath: b.config.TempPath,
TempPath: b.config.TempPath,
VhdTempPath: b.config.VhdTempPath,
},
&hypervcommon.StepOutputDir{
Force: b.config.PackerForce,
@ -334,6 +352,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
EnableDynamicMemory: b.config.EnableDynamicMemory,
EnableSecureBoot: b.config.EnableSecureBoot,
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
AdditionalDiskSize: b.config.AdditionalDiskSize,
},
&hypervcommon.StepEnableIntegrationService{},

View File

@ -3,6 +3,7 @@ package iso
import (
"fmt"
"reflect"
"strconv"
"testing"
"github.com/hashicorp/packer/packer"
@ -18,6 +19,7 @@ func testConfig() map[string]interface{} {
"ram_size": 64,
"disk_size": 256,
"guest_additions_mode": "none",
"disk_additional_size": "50000,40000,30000",
packer.BuildNameConfigKey: "foo",
}
}
@ -235,7 +237,7 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
delete(config, "iso_url")
delete(config, "iso_urls")
// Test both epty
// Test both empty
config["iso_url"] = ""
b = Builder{}
warns, err := b.Prepare(config)
@ -298,3 +300,175 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
func TestBuilderPrepare_SizeNotRequiredWhenUsingExistingHarddrive(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "iso_url")
delete(config, "iso_urls")
delete(config, "disk_size")
config["disk_size"] = 1
// Test just iso_urls set but with vhdx
delete(config, "iso_url")
config["iso_urls"] = []string{
"http://www.packer.io/hdd.vhdx",
"http://www.hashicorp.com/dvd.iso",
}
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected := []string{
"http://www.packer.io/hdd.vhdx",
"http://www.hashicorp.com/dvd.iso",
}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
// Test just iso_urls set but with vhd
delete(config, "iso_url")
config["iso_urls"] = []string{
"http://www.packer.io/hdd.vhd",
"http://www.hashicorp.com/dvd.iso",
}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected = []string{
"http://www.packer.io/hdd.vhd",
"http://www.hashicorp.com/dvd.iso",
}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
func TestBuilderPrepare_SizeIsRequiredWhenNotUsingExistingHarddrive(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "iso_url")
delete(config, "iso_urls")
delete(config, "disk_size")
config["disk_size"] = 1
// Test just iso_urls set but with vhdx
delete(config, "iso_url")
config["iso_urls"] = []string{
"http://www.packer.io/os.iso",
"http://www.hashicorp.com/dvd.iso",
}
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Errorf("should have error")
}
expected := []string{
"http://www.packer.io/os.iso",
"http://www.hashicorp.com/dvd.iso",
}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
func TestBuilderPrepare_MaximumOfSixtyFourAdditionalDisks(t *testing.T) {
var b Builder
config := testConfig()
disks := make([]string, 65)
for i := range disks {
disks[i] = strconv.Itoa(i)
}
config["disk_additional_size"] = disks
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Errorf("should have error")
}
}
func TestBuilderPrepare_CommConfig(t *testing.T) {
// Test Winrm
{
config := testConfig()
config["communicator"] = "winrm"
config["winrm_username"] = "username"
config["winrm_password"] = "password"
config["winrm_host"] = "1.2.3.4"
var b Builder
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.Comm.WinRMUser != "username" {
t.Errorf("bad winrm_username: %s", b.config.Comm.WinRMUser)
}
if b.config.Comm.WinRMPassword != "password" {
t.Errorf("bad winrm_password: %s", b.config.Comm.WinRMPassword)
}
if host := b.config.Comm.Host(); host != "1.2.3.4" {
t.Errorf("bad host: %s", host)
}
}
// Test SSH
{
config := testConfig()
config["communicator"] = "ssh"
config["ssh_username"] = "username"
config["ssh_password"] = "password"
config["ssh_host"] = "1.2.3.4"
var b Builder
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.Comm.SSHUsername != "username" {
t.Errorf("bad ssh_username: %s", b.config.Comm.SSHUsername)
}
if b.config.Comm.SSHPassword != "password" {
t.Errorf("bad ssh_password: %s", b.config.Comm.SSHPassword)
}
if host := b.config.Comm.Host(); host != "1.2.3.4" {
t.Errorf("bad host: %s", host)
}
}
}

View File

@ -0,0 +1,558 @@
package vmcx
import (
"errors"
"fmt"
"log"
"os"
"strings"
hypervcommon "github.com/hashicorp/packer/builder/hyperv/common"
"github.com/hashicorp/packer/common"
powershell "github.com/hashicorp/packer/common/powershell"
"github.com/hashicorp/packer/common/powershell/hyperv"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/multistep"
)
const (
DefaultRamSize = 1 * 1024 // 1GB
MinRamSize = 32 // 32MB
MaxRamSize = 32 * 1024 // 32GB
MinNestedVirtualizationRamSize = 4 * 1024 // 4GB
LowRam = 256 // 256MB
DefaultUsername = ""
DefaultPassword = ""
)
// Builder implements packer.Builder and builds the actual Hyperv
// images.
type Builder struct {
config Config
runner multistep.Runner
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
hypervcommon.OutputConfig `mapstructure:",squash"`
hypervcommon.SSHConfig `mapstructure:",squash"`
hypervcommon.RunConfig `mapstructure:",squash"`
hypervcommon.ShutdownConfig `mapstructure:",squash"`
// The size, in megabytes, of the computer memory in the VM.
// By default, this is 1024 (about 1 GB).
RamSize uint `mapstructure:"ram_size"`
//
SecondaryDvdImages []string `mapstructure:"secondary_iso_images"`
// Should integration services iso be mounted
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
// The path to the integration services iso
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
// This is the path to a directory containing an exported virtual machine.
CloneFromVMXCPath string `mapstructure:"clone_from_vmxc_path"`
// This is the name of the virtual machine to clone from.
CloneFromVMName string `mapstructure:"clone_from_vm_name"`
// This is the name of the snapshot to clone from. A blank snapshot name will use the latest snapshot.
CloneFromSnapshotName string `mapstructure:"clone_from_snapshot_name"`
// This will clone all snapshots if true. It will clone latest snapshot if false.
CloneAllSnapshots bool `mapstructure:"clone_all_snapshots"`
// This is the name of the new virtual machine.
// By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build.
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
SwitchName string `mapstructure:"switch_name"`
SwitchVlanId string `mapstructure:"switch_vlan_id"`
VlanId string `mapstructure:"vlan_id"`
Cpu uint `mapstructure:"cpu"`
Generation uint
EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"`
EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"`
EnableSecureBoot bool `mapstructure:"enable_secure_boot"`
EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"`
Communicator string `mapstructure:"communicator"`
SkipCompaction bool `mapstructure:"skip_compaction"`
ctx interpolate.Context
}
// Prepare processes the build configuration parameters.
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
return nil, err
}
// Accumulate any errors and warnings
var errs *packer.MultiError
warnings := make([]string, 0)
if b.config.RawSingleISOUrl != "" || len(b.config.ISOUrls) > 0 {
isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
}
errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...)
err = b.checkRamSize()
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if b.config.VMName == "" {
b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}
log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName))
if b.config.SwitchName == "" {
b.config.SwitchName = b.detectSwitchName()
}
if b.config.Cpu < 1 {
b.config.Cpu = 1
}
b.config.Generation = 1
if b.config.CloneFromVMName == "" {
if b.config.CloneFromVMXCPath == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vm_name must be specified if clone_from_vmxc_path is not specified."))
}
} else {
virtualMachineExists, err := powershell.DoesVirtualMachineExist(b.config.CloneFromVMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine to clone from exists: %s", err))
} else {
if !virtualMachineExists {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine '%s' to clone from does not exist.", b.config.CloneFromVMName))
} else {
b.config.Generation, err = powershell.GetVirtualMachineGeneration(b.config.CloneFromVMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine to clone from generation: %s", err))
}
if b.config.CloneFromSnapshotName != "" {
virtualMachineSnapshotExists, err := powershell.DoesVirtualMachineSnapshotExist(b.config.CloneFromVMName, b.config.CloneFromSnapshotName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine snapshot to clone from exists: %s", err))
} else {
if !virtualMachineSnapshotExists {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Virtual machine snapshot '%s' on virtual machine '%s' to clone from does not exist.", b.config.CloneFromSnapshotName, b.config.CloneFromVMName))
}
}
}
virtualMachineOn, err := powershell.IsVirtualMachineOn(b.config.CloneFromVMName)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting if virtual machine to clone is running: %s", err))
} else {
if virtualMachineOn {
warning := fmt.Sprintf("Cloning from a virtual machine that is running.")
warnings = appendWarnings(warnings, warning)
}
}
}
}
}
if b.config.CloneFromVMXCPath == "" {
if b.config.CloneFromVMName == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The clone_from_vmxc_path be specified if clone_from_vm_name must is not specified."))
}
} else {
if _, err := os.Stat(b.config.CloneFromVMXCPath); os.IsNotExist(err) {
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("CloneFromVMXCPath does not exist: %s", err))
}
}
}
if b.config.Generation < 1 || b.config.Generation > 2 {
b.config.Generation = 1
}
if b.config.Generation == 2 {
if len(b.config.FloppyFiles) > 0 || len(b.config.FloppyDirectories) > 0 {
err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.")
errs = packer.MultiErrorAppend(errs, err)
}
}
log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName))
log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName))
// Errors
if b.config.GuestAdditionsMode == "" {
if b.config.GuestAdditionsPath != "" {
b.config.GuestAdditionsMode = "attach"
} else {
b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso"
if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) {
if err != nil {
b.config.GuestAdditionsPath = ""
b.config.GuestAdditionsMode = "none"
} else {
b.config.GuestAdditionsMode = "attach"
}
}
}
}
if b.config.GuestAdditionsPath == "" && b.config.GuestAdditionsMode == "attach" {
b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso"
if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) {
if err != nil {
b.config.GuestAdditionsPath = ""
}
}
}
for _, isoPath := range b.config.SecondaryDvdImages {
if _, err := os.Stat(isoPath); os.IsNotExist(err) {
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err))
}
}
}
numberOfIsos := len(b.config.SecondaryDvdImages)
if b.config.GuestAdditionsMode == "attach" {
if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) {
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Guest additions iso does not exist: %s", err))
}
}
numberOfIsos = numberOfIsos + 1
}
if b.config.Generation < 2 && numberOfIsos > 2 {
if b.config.GuestAdditionsMode == "attach" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
}
} else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 {
if b.config.GuestAdditionsMode == "attach" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", ")))
}
}
if b.config.EnableVirtualizationExtensions {
hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions()
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization extensions support: %s", err))
} else {
if !hasVirtualMachineVirtualizationExtensions {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support virtual machine virtualization extension. Please use Windows 10 or Windows Server 2016 or newer."))
}
}
}
// Warnings
if b.config.ShutdownCommand == "" {
warnings = appendWarnings(warnings,
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
"will forcibly halt the virtual machine, which may result in data loss.")
}
warning := b.checkHostAvailableMemory()
if warning != "" {
warnings = appendWarnings(warnings, warning)
}
if b.config.EnableVirtualizationExtensions {
if b.config.EnableDynamicMemory {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, dynamic memory should not be allowed.")
warnings = appendWarnings(warnings, warning)
}
if !b.config.EnableMacSpoofing {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, mac spoofing should be allowed.")
warnings = appendWarnings(warnings, warning)
}
if b.config.RamSize < MinNestedVirtualizationRamSize {
warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start any nested VMs.")
warnings = appendWarnings(warnings, warning)
}
}
if b.config.SwitchVlanId != "" {
if b.config.SwitchVlanId != b.config.VlanId {
warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor vlan. The switch will not be able to see traffic from the VM.")
warnings = appendWarnings(warnings, warning)
}
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
return warnings, nil
}
// Run executes a Packer build and returns a packer.Artifact representing
// a Hyperv appliance.
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
// Create the driver that we'll use to communicate with Hyperv
driver, err := hypervcommon.NewHypervPS4Driver()
if err != nil {
return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err)
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("cache", cache)
state.Put("config", &b.config)
state.Put("debug", b.config.PackerDebug)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
steps := []multistep.Step{
&hypervcommon.StepCreateTempDir{},
&hypervcommon.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
}
if b.config.RawSingleISOUrl != "" || len(b.config.ISOUrls) > 0 {
steps = append(steps,
&common.StepDownload{
Checksum: b.config.ISOChecksum,
ChecksumType: b.config.ISOChecksumType,
Description: "ISO",
ResultKey: "iso_path",
Url: b.config.ISOUrls,
Extension: b.config.TargetExtension,
TargetPath: b.config.TargetPath,
},
)
}
steps = append(steps,
&common.StepCreateFloppy{
Files: b.config.FloppyFiles,
Directories: b.config.FloppyConfig.FloppyDirectories,
},
&common.StepHTTPServer{
HTTPDir: b.config.HTTPDir,
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&hypervcommon.StepCreateSwitch{
SwitchName: b.config.SwitchName,
},
&hypervcommon.StepCloneVM{
CloneFromVMXCPath: b.config.CloneFromVMXCPath,
CloneFromVMName: b.config.CloneFromVMName,
CloneFromSnapshotName: b.config.CloneFromSnapshotName,
CloneAllSnapshots: b.config.CloneAllSnapshots,
VMName: b.config.VMName,
SwitchName: b.config.SwitchName,
RamSize: b.config.RamSize,
Cpu: b.config.Cpu,
EnableMacSpoofing: b.config.EnableMacSpoofing,
EnableDynamicMemory: b.config.EnableDynamicMemory,
EnableSecureBoot: b.config.EnableSecureBoot,
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
},
&hypervcommon.StepEnableIntegrationService{},
&hypervcommon.StepMountDvdDrive{
Generation: b.config.Generation,
},
&hypervcommon.StepMountFloppydrive{
Generation: b.config.Generation,
},
&hypervcommon.StepMountGuestAdditions{
GuestAdditionsMode: b.config.GuestAdditionsMode,
GuestAdditionsPath: b.config.GuestAdditionsPath,
Generation: b.config.Generation,
},
&hypervcommon.StepMountSecondaryDvdImages{
IsoPaths: b.config.SecondaryDvdImages,
Generation: b.config.Generation,
},
&hypervcommon.StepConfigureVlan{
VlanId: b.config.VlanId,
SwitchVlanId: b.config.SwitchVlanId,
},
&hypervcommon.StepRun{
BootWait: b.config.BootWait,
},
&hypervcommon.StepTypeBootCommand{
BootCommand: b.config.BootCommand,
SwitchName: b.config.SwitchName,
Ctx: b.config.ctx,
},
// configure the communicator ssh, winrm
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: hypervcommon.CommHost,
SSHConfig: hypervcommon.SSHConfigFunc(&b.config.SSHConfig),
},
// provision requires communicator to be setup
&common.StepProvision{},
&hypervcommon.StepShutdown{
Command: b.config.ShutdownCommand,
Timeout: b.config.ShutdownTimeout,
},
// wait for the vm to be powered off
&hypervcommon.StepWaitForPowerOff{},
// remove the secondary dvd images
// after we power down
&hypervcommon.StepUnmountSecondaryDvdImages{},
&hypervcommon.StepUnmountGuestAdditions{},
&hypervcommon.StepUnmountDvdDrive{},
&hypervcommon.StepUnmountFloppyDrive{
Generation: b.config.Generation,
},
&hypervcommon.StepExportVm{
OutputDir: b.config.OutputDir,
SkipCompaction: b.config.SkipCompaction,
},
// the clean up actions for each step will be executed reverse order
)
// Run the steps.
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// Report any errors.
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// If we were interrupted or cancelled, then just exit.
if _, ok := state.GetOk(multistep.StateCancelled); ok {
return nil, errors.New("Build was cancelled.")
}
if _, ok := state.GetOk(multistep.StateHalted); ok {
return nil, errors.New("Build was halted.")
}
return hypervcommon.NewArtifact(b.config.OutputDir)
}
// Cancel.
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}
func appendWarnings(slice []string, data ...string) []string {
m := len(slice)
n := m + len(data)
if n > cap(slice) { // if necessary, reallocate
// allocate double what's needed, for future growth.
newSlice := make([]string, (n+1)*2)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:n]
copy(slice[m:n], data)
return slice
}
func (b *Builder) checkRamSize() error {
if b.config.RamSize == 0 {
b.config.RamSize = DefaultRamSize
}
log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize))
if b.config.RamSize < MinRamSize {
return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize)
} else if b.config.RamSize > MaxRamSize {
return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize)
}
return nil
}
func (b *Builder) checkHostAvailableMemory() string {
powershellAvailable, _, _ := powershell.IsPowershellAvailable()
if powershellAvailable {
freeMB := powershell.GetHostAvailableMemory()
if (freeMB - float64(b.config.RamSize)) < LowRam {
return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.")
}
}
return ""
}
func (b *Builder) detectSwitchName() string {
powershellAvailable, _, _ := powershell.IsPowershellAvailable()
if powershellAvailable {
// no switch name, try to get one attached to a online network adapter
onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch()
if onlineSwitchName != "" && err == nil {
return onlineSwitchName
}
}
return fmt.Sprintf("packer-%s", b.config.PackerBuildName)
}

View File

@ -0,0 +1,488 @@
package vmcx
import (
"reflect"
"testing"
"fmt"
"github.com/hashicorp/packer/packer"
"io/ioutil"
"os"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"iso_checksum": "foo",
"iso_checksum_type": "md5",
"iso_url": "http://www.packer.io",
"shutdown_command": "yes",
"ssh_username": "foo",
"ram_size": 64,
"guest_additions_mode": "none",
"clone_from_vmxc_path": "generated",
packer.BuildNameConfigKey: "foo",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Error("Builder must implement builder.")
}
}
func TestBuilderPrepare_Defaults(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.VMName != "packer-foo" {
t.Errorf("bad vm name: %s", b.config.VMName)
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
// Add a random key
config["i_should_not_be_valid"] = true
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_CloneFromExistingMachineOrImportFromExportedMachineSettingsRequired(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "clone_from_vmxc_path")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ExportedMachinePathDoesNotExist(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
//Delete the folder immediately
os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
}
func TestBuilderPrepare_ExportedMachinePathExists(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
//Only delete afterwards
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func disabled_TestBuilderPrepare_CloneFromVmSettingUsedSoNoCloneFromVmxcPathRequired(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "clone_from_vmxc_path")
config["clone_from_vm_name"] = "test_machine_name_that_does_not_exist"
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
} else {
errorMessage := err.Error()
if errorMessage != "1 error(s) occurred:\n\n* Virtual machine 'test_machine_name_that_does_not_exist' to clone from does not exist." {
t.Fatalf("should not have error: %s", err)
}
}
}
func TestBuilderPrepare_ISOChecksum(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
// Test bad
config["iso_checksum"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good
config["iso_checksum"] = "FOo"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksum != "foo" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
}
}
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
// Test bad
config["iso_checksum_type"] = ""
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test good
config["iso_checksum_type"] = "mD5"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksumType != "md5" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
}
// Test unknown
config["iso_checksum_type"] = "fake"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test none
config["iso_checksum_type"] = "none"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) == 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.ISOChecksumType != "none" {
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
}
}
func TestBuilderPrepare_ISOUrl(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
delete(config, "iso_url")
delete(config, "iso_urls")
// Test both empty (should be allowed, as we cloning a vm so we probably don't need an ISO file)
config["iso_url"] = ""
b = Builder{}
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatal("should not have an error")
}
// Test iso_url set
config["iso_url"] = "http://www.packer.io"
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected := []string{"http://www.packer.io"}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
// Test both set
config["iso_url"] = "http://www.packer.io"
config["iso_urls"] = []string{"http://www.packer.io"}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should have error")
}
// Test just iso_urls set
delete(config, "iso_url")
config["iso_urls"] = []string{
"http://www.packer.io",
"http://www.hashicorp.com",
}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Errorf("should not have error: %s", err)
}
expected = []string{
"http://www.packer.io",
"http://www.hashicorp.com",
}
if !reflect.DeepEqual(b.config.ISOUrls, expected) {
t.Fatalf("bad: %#v", b.config.ISOUrls)
}
}
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
delete(config, "floppy_files")
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad err: %s", err)
}
if len(b.config.FloppyFiles) != 0 {
t.Fatalf("bad: %#v", b.config.FloppyFiles)
}
floppies_path := "../../../common/test-fixtures/floppies"
config["floppy_files"] = []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
b = Builder{}
warns, err = b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
expected := []string{fmt.Sprintf("%s/bar.bat", floppies_path), fmt.Sprintf("%s/foo.ps1", floppies_path)}
if !reflect.DeepEqual(b.config.FloppyFiles, expected) {
t.Fatalf("bad: %#v", b.config.FloppyFiles)
}
}
func TestBuilderPrepare_InvalidFloppies(t *testing.T) {
var b Builder
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["floppy_files"] = []string{"nonexistent.bat", "nonexistent.ps1"}
b = Builder{}
_, errs := b.Prepare(config)
if errs == nil {
t.Fatalf("Nonexistent floppies should trigger multierror")
}
if len(errs.(*packer.MultiError).Errors) != 2 {
t.Fatalf("Multierror should work and report 2 errors")
}
}
func TestBuilderPrepare_CommConfig(t *testing.T) {
// Test Winrm
{
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["communicator"] = "winrm"
config["winrm_username"] = "username"
config["winrm_password"] = "password"
config["winrm_host"] = "1.2.3.4"
var b Builder
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.Comm.WinRMUser != "username" {
t.Errorf("bad winrm_username: %s", b.config.Comm.WinRMUser)
}
if b.config.Comm.WinRMPassword != "password" {
t.Errorf("bad winrm_password: %s", b.config.Comm.WinRMPassword)
}
if host := b.config.Comm.Host(); host != "1.2.3.4" {
t.Errorf("bad host: %s", host)
}
}
// Test SSH
{
config := testConfig()
//Create vmxc folder
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
config["clone_from_vmxc_path"] = td
config["communicator"] = "ssh"
config["ssh_username"] = "username"
config["ssh_password"] = "password"
config["ssh_host"] = "1.2.3.4"
var b Builder
warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if b.config.Comm.SSHUsername != "username" {
t.Errorf("bad ssh_username: %s", b.config.Comm.SSHUsername)
}
if b.config.Comm.SSHPassword != "password" {
t.Errorf("bad ssh_password: %s", b.config.Comm.SSHPassword)
}
if host := b.config.Comm.Host(); host != "1.2.3.4" {
t.Errorf("bad host: %s", host)
}
}
}

View File

@ -16,6 +16,7 @@ import (
type LxcAttachCommunicator struct {
RootFs string
ContainerName string
AttachOptions []string
CmdWrapper CommandWrapper
}
@ -110,8 +111,13 @@ func (c *LxcAttachCommunicator) DownloadDir(src string, dst string, exclude []st
func (c *LxcAttachCommunicator) Execute(commandString string) (*exec.Cmd, error) {
log.Printf("Executing with lxc-attach in container: %s %s %s", c.ContainerName, c.RootFs, commandString)
attachCommand := []string{"sudo", "lxc-attach"}
attachCommand = append(attachCommand, c.AttachOptions...)
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"%s\""}...)
command, err := c.CmdWrapper(
fmt.Sprintf("sudo lxc-attach --name %s -- /bin/sh -c \"%s\"", c.ContainerName, commandString))
fmt.Sprintf(strings.Join(attachCommand, " "), c.ContainerName, commandString))
if err != nil {
return nil, err
}

View File

@ -18,6 +18,9 @@ type Config struct {
ContainerName string `mapstructure:"container_name"`
CommandWrapper string `mapstructure:"command_wrapper"`
RawInitTimeout string `mapstructure:"init_timeout"`
CreateOptions []string `mapstructure:"create_options"`
StartOptions []string `mapstructure:"start_options"`
AttachOptions []string `mapstructure:"attach_options"`
Name string `mapstructure:"template_name"`
Parameters []string `mapstructure:"template_parameters"`
EnvVars []string `mapstructure:"template_environment_vars"`

View File

@ -28,12 +28,15 @@ func (s *stepLxcCreate) Run(state multistep.StateBag) multistep.StepAction {
}
commands := make([][]string, 3)
commands[0] = append(config.EnvVars, []string{"lxc-create", "-n", name, "-t", config.Name, "--"}...)
commands[0] = append(config.EnvVars, "lxc-create")
commands[0] = append(commands[0], config.CreateOptions...)
commands[0] = append(commands[0], []string{"-n", name, "-t", config.Name, "--"}...)
commands[0] = append(commands[0], config.Parameters...)
// prevent tmp from being cleaned on boot, we put provisioning scripts there
// todo: wait for init to finish before moving on to provisioning instead of this
commands[1] = []string{"touch", filepath.Join(rootfs, "tmp", ".tmpfs")}
commands[2] = []string{"lxc-start", "-d", "--name", name}
commands[2] = append([]string{"lxc-start"}, config.StartOptions...)
commands[2] = append(commands[2], []string{"-d", "--name", name}...)
ui.Say("Creating container...")
for _, command := range commands {

View File

@ -19,6 +19,7 @@ func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
// Create our communicator
comm := &LxcAttachCommunicator{
ContainerName: config.ContainerName,
AttachOptions: config.AttachOptions,
RootFs: mountPath,
CmdWrapper: wrappedCommand,
}

View File

@ -76,6 +76,7 @@ func (s *StepWaitInit) waitForInit(state multistep.StateBag, cancel <-chan struc
comm := &LxcAttachCommunicator{
ContainerName: config.ContainerName,
AttachOptions: config.AttachOptions,
RootFs: mountPath,
CmdWrapper: wrappedCommand,
}

View File

@ -12,10 +12,11 @@ import (
type Config struct {
common.PackerConfig `mapstructure:",squash"`
OutputImage string `mapstructure:"output_image"`
ContainerName string `mapstructure:"container_name"`
CommandWrapper string `mapstructure:"command_wrapper"`
Image string `mapstructure:"image"`
OutputImage string `mapstructure:"output_image"`
ContainerName string `mapstructure:"container_name"`
CommandWrapper string `mapstructure:"command_wrapper"`
Image string `mapstructure:"image"`
PublishProperties map[string]string `mapstructure:"publish_properties"`
InitTimeout time.Duration
ctx interpolate.Context

View File

@ -32,6 +32,10 @@ func (s *stepPublish) Run(state multistep.StateBag) multistep.StepAction {
"publish", name, "--alias", config.OutputImage,
}
for k, v := range config.PublishProperties {
publish_args = append(publish_args, fmt.Sprintf("%s=%s", k, v))
}
ui.Say("Publishing container...")
stdoutString, err := LXDCommand(publish_args...)
if err != nil {

View File

@ -182,7 +182,7 @@ func BaseTestConfig() (*ini.File, *os.File, error) {
// Build ini
cfg := ini.Empty()
section, _ := cfg.NewSection("DEFAULT")
section.NewKey("region", "us-phoenix-1")
section.NewKey("region", "us-ashburn-1")
section.NewKey("tenancy", "ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
section.NewKey("user", "ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
section.NewKey("fingerprint", "3c:b6:44:d7:49:1a:ac:bf:de:7d:76:22:a7:f5:df:55")

View File

@ -81,8 +81,8 @@ func TestNewConfigDefaultsPopulated(t *testing.T) {
t.Fatal("Expected ADMIN config to exist in map")
}
if adminConfig.Region != "us-phoenix-1" {
t.Errorf("Expected 'us-phoenix-1', got '%s'", adminConfig.Region)
if adminConfig.Region != "us-ashburn-1" {
t.Errorf("Expected 'us-ashburn-1', got '%s'", adminConfig.Region)
}
}

View File

@ -101,7 +101,10 @@ func NewConfig(raws ...interface{}) (*Config, error) {
if c.Region != "" {
accessCfg.Region = c.Region
} else {
}
// Default if the template nor the API config contains a region.
if accessCfg.Region == "" {
accessCfg.Region = "us-phoenix-1"
}

View File

@ -122,6 +122,20 @@ func TestConfig(t *testing.T) {
})
t.Run("RegionNotDefaultedToPHXWhenSetInOCISettings", func(t *testing.T) {
raw := testConfig(cfgFile)
c, errs := NewConfig(raw)
if errs != nil {
t.Fatalf("err: %+v", errs)
}
expected := "us-ashburn-1"
if c.AccessCfg.Region != expected {
t.Errorf("Expected region: %s, got %s.", expected, c.AccessCfg.Region)
}
})
// Test the correct errors are produced when required template keys are
// omitted.
requiredKeys := []string{"availability_domain", "base_image_ocid", "shape", "subnet_ocid"}

View File

@ -6,10 +6,13 @@ import (
"io/ioutil"
"os"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/template/interpolate"
"github.com/joyent/triton-go"
tgo "github.com/joyent/triton-go"
"github.com/joyent/triton-go/authentication"
"github.com/joyent/triton-go/compute"
"github.com/joyent/triton-go/network"
)
// AccessConfig is for common configuration related to Triton access
@ -106,8 +109,39 @@ func (c *AccessConfig) createPrivateKeySigner() (authentication.Signer, error) {
return signer, nil
}
func (c *AccessConfig) CreateTritonClient() (*triton.Client, error) {
return triton.NewClient(c.Endpoint, c.Account, c.signer)
func (c *AccessConfig) CreateTritonClient() (*Client, error) {
config := &tgo.ClientConfig{
AccountName: c.Account,
TritonURL: c.Endpoint,
Signers: []authentication.Signer{c.signer},
}
return &Client{
config: config,
}, nil
}
type Client struct {
config *tgo.ClientConfig
}
func (c *Client) Compute() (*compute.ComputeClient, error) {
computeClient, err := compute.NewClient(c.config)
if err != nil {
return nil, errwrap.Wrapf("Error Creating Triton Compute Client: {{err}}", err)
}
return computeClient, nil
}
func (c *Client) Network() (*network.NetworkClient, error) {
networkClient, err := network.NewClient(c.config)
if err != nil {
return nil, errwrap.Wrapf("Error Creating Triton Network Client: {{err}}", err)
}
return networkClient, nil
}
func (c *AccessConfig) Comm() communicator.Config {

View File

@ -5,6 +5,7 @@ import (
)
type Driver interface {
GetImage(config Config) (string, error)
CreateImageFromMachine(machineId string, config Config) (string, error)
CreateMachine(config Config) (string, error)
DeleteImage(imageId string) error

View File

@ -17,6 +17,9 @@ type DriverMock struct {
DeleteMachineId string
DeleteMachineErr error
GetImageId string
GetImageErr error
GetMachineErr error
StopMachineId string
@ -29,6 +32,14 @@ type DriverMock struct {
WaitForMachineStateErr error
}
func (d *DriverMock) GetImage(config Config) (string, error) {
if d.GetImageErr != nil {
return "", d.GetImageErr
}
return config.MachineImage, nil
}
func (d *DriverMock) CreateImageFromMachine(machineId string, config Config) (string, error) {
if d.CreateImageFromMachineErr != nil {
return "", d.CreateImageFromMachineErr

View File

@ -4,14 +4,16 @@ import (
"context"
"errors"
"net/http"
"sort"
"time"
"github.com/hashicorp/packer/packer"
"github.com/joyent/triton-go"
"github.com/joyent/triton-go/client"
"github.com/joyent/triton-go/compute"
)
type driverTriton struct {
client *triton.Client
client *Client
ui packer.Ui
}
@ -27,8 +29,39 @@ func NewDriverTriton(ui packer.Ui, config Config) (Driver, error) {
}, nil
}
func (d *driverTriton) GetImage(config Config) (string, error) {
computeClient, _ := d.client.Compute()
images, err := computeClient.Images().List(context.Background(), &compute.ListImagesInput{
Name: config.MachineImageFilters.Name,
OS: config.MachineImageFilters.OS,
Version: config.MachineImageFilters.Version,
Public: config.MachineImageFilters.Public,
Type: config.MachineImageFilters.Type,
State: config.MachineImageFilters.State,
Owner: config.MachineImageFilters.Owner,
})
if err != nil {
return "", err
}
if len(images) == 0 {
return "", errors.New("No images found in your search. Please refine your search criteria")
}
if len(images) > 1 {
if !config.MachineImageFilters.MostRecent {
return "", errors.New("More than 1 machine image was found in your search. Please refine your search criteria")
} else {
return mostRecentImages(images).ID, nil
}
} else {
return images[0].ID, nil
}
}
func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (string, error) {
image, err := d.client.Images().CreateImageFromMachine(context.Background(), &triton.CreateImageFromMachineInput{
computeClient, _ := d.client.Compute()
image, err := computeClient.Images().CreateFromMachine(context.Background(), &compute.CreateImageFromMachineInput{
MachineID: machineId,
Name: config.ImageName,
Version: config.ImageVersion,
@ -46,7 +79,8 @@ func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (
}
func (d *driverTriton) CreateMachine(config Config) (string, error) {
input := &triton.CreateMachineInput{
computeClient, _ := d.client.Compute()
input := &compute.CreateInstanceInput{
Package: config.MachinePackage,
Image: config.MachineImage,
Metadata: config.MachineMetadata,
@ -66,7 +100,7 @@ func (d *driverTriton) CreateMachine(config Config) (string, error) {
input.Networks = config.MachineNetworks
}
machine, err := d.client.Machines().CreateMachine(context.Background(), input)
machine, err := computeClient.Instances().Create(context.Background(), input)
if err != nil {
return "", err
}
@ -75,19 +109,22 @@ func (d *driverTriton) CreateMachine(config Config) (string, error) {
}
func (d *driverTriton) DeleteImage(imageId string) error {
return d.client.Images().DeleteImage(context.Background(), &triton.DeleteImageInput{
computeClient, _ := d.client.Compute()
return computeClient.Images().Delete(context.Background(), &compute.DeleteImageInput{
ImageID: imageId,
})
}
func (d *driverTriton) DeleteMachine(machineId string) error {
return d.client.Machines().DeleteMachine(context.Background(), &triton.DeleteMachineInput{
computeClient, _ := d.client.Compute()
return computeClient.Instances().Delete(context.Background(), &compute.DeleteInstanceInput{
ID: machineId,
})
}
func (d *driverTriton) GetMachineIP(machineId string) (string, error) {
machine, err := d.client.Machines().GetMachine(context.Background(), &triton.GetMachineInput{
computeClient, _ := d.client.Compute()
machine, err := computeClient.Instances().Get(context.Background(), &compute.GetInstanceInput{
ID: machineId,
})
if err != nil {
@ -98,8 +135,9 @@ func (d *driverTriton) GetMachineIP(machineId string) (string, error) {
}
func (d *driverTriton) StopMachine(machineId string) error {
return d.client.Machines().StopMachine(context.Background(), &triton.StopMachineInput{
MachineID: machineId,
computeClient, _ := d.client.Compute()
return computeClient.Instances().Stop(context.Background(), &compute.StopInstanceInput{
InstanceID: machineId,
})
}
@ -111,7 +149,8 @@ func (d *driverTriton) StopMachine(machineId string) error {
func (d *driverTriton) WaitForMachineState(machineId string, state string, timeout time.Duration) error {
return waitFor(
func() (bool, error) {
machine, err := d.client.Machines().GetMachine(context.Background(), &triton.GetMachineInput{
computeClient, _ := d.client.Compute()
machine, err := computeClient.Instances().Get(context.Background(), &compute.GetInstanceInput{
ID: machineId,
})
if machine == nil {
@ -130,14 +169,15 @@ func (d *driverTriton) WaitForMachineState(machineId string, state string, timeo
func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Duration) error {
return waitFor(
func() (bool, error) {
_, err := d.client.Machines().GetMachine(context.Background(), &triton.GetMachineInput{
computeClient, _ := d.client.Compute()
_, err := computeClient.Instances().Get(context.Background(), &compute.GetInstanceInput{
ID: machineId,
})
if err != nil {
// Return true only when we receive a 410 (Gone) response. A 404
// indicates that the machine is being deleted whereas a 410 indicates
// that this process has completed.
if triErr, ok := err.(*triton.TritonError); ok && triErr.StatusCode == http.StatusGone {
if triErr, ok := err.(*client.TritonError); ok && triErr.StatusCode == http.StatusGone {
return true, nil
}
}
@ -152,13 +192,14 @@ func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Dur
func (d *driverTriton) WaitForImageCreation(imageId string, timeout time.Duration) error {
return waitFor(
func() (bool, error) {
image, err := d.client.Images().GetImage(context.Background(), &triton.GetImageInput{
computeClient, _ := d.client.Compute()
image, err := computeClient.Images().Get(context.Background(), &compute.GetImageInput{
ImageID: imageId,
})
if image == nil {
return false, err
}
return image.OS != "", err
return image.State == "active", err
},
3*time.Second,
timeout,
@ -183,3 +224,29 @@ func waitFor(f func() (bool, error), every, timeout time.Duration) error {
return errors.New("Timed out while waiting for resource change")
}
func mostRecentImages(images []*compute.Image) *compute.Image {
return sortImages(images)[0]
}
type imageSort []*compute.Image
func sortImages(images []*compute.Image) []*compute.Image {
sortedImages := images
sort.Sort(sort.Reverse(imageSort(sortedImages)))
return sortedImages
}
func (a imageSort) Len() int {
return len(a)
}
func (a imageSort) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a imageSort) Less(i, j int) bool {
itime := a[i].PublishedAt
jtime := a[j].PublishedAt
return itime.Unix() < jtime.Unix()
}

View File

@ -9,13 +9,29 @@ import (
// SourceMachineConfig represents the configuration to run a machine using
// the SDC API in order for provisioning to take place.
type SourceMachineConfig struct {
MachineName string `mapstructure:"source_machine_name"`
MachinePackage string `mapstructure:"source_machine_package"`
MachineImage string `mapstructure:"source_machine_image"`
MachineNetworks []string `mapstructure:"source_machine_networks"`
MachineMetadata map[string]string `mapstructure:"source_machine_metadata"`
MachineTags map[string]string `mapstructure:"source_machine_tags"`
MachineFirewallEnabled bool `mapstructure:"source_machine_firewall_enabled"`
MachineName string `mapstructure:"source_machine_name"`
MachinePackage string `mapstructure:"source_machine_package"`
MachineImage string `mapstructure:"source_machine_image"`
MachineNetworks []string `mapstructure:"source_machine_networks"`
MachineMetadata map[string]string `mapstructure:"source_machine_metadata"`
MachineTags map[string]string `mapstructure:"source_machine_tags"`
MachineFirewallEnabled bool `mapstructure:"source_machine_firewall_enabled"`
MachineImageFilters MachineImageFilter `mapstructure:"source_machine_image_filter"`
}
type MachineImageFilter struct {
MostRecent bool `mapstructure:"most_recent"`
Name string
OS string
Version string
Public bool
State string
Owner string
Type string
}
func (m *MachineImageFilter) Empty() bool {
return m.Name == "" && m.OS == "" && m.Version == "" && m.State == "" && m.Owner == "" && m.Type == ""
}
// Prepare performs basic validation on a SourceMachineConfig struct.
@ -26,8 +42,8 @@ func (c *SourceMachineConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("A source_machine_package must be specified"))
}
if c.MachineImage == "" {
errs = append(errs, fmt.Errorf("A source_machine_image must be specified"))
if c.MachineImage != "" && c.MachineImageFilters.Name != "" {
errs = append(errs, fmt.Errorf("You cannot specify a Machine Image and also Machine Name filter"))
}
if c.MachineNetworks == nil {

View File

@ -24,13 +24,6 @@ func TestSourceMachineConfig_Prepare(t *testing.T) {
if errs == nil {
t.Fatalf("should error: %#v", sc)
}
sc = testSourceMachineConfig(t)
sc.MachineImage = ""
errs = sc.Prepare(nil)
if errs == nil {
t.Fatalf("should error: %#v", sc)
}
}
func testSourceMachineConfig(t *testing.T) SourceMachineConfig {

View File

@ -17,7 +17,16 @@ func (s *StepCreateSourceMachine) Run(state multistep.StateBag) multistep.StepAc
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating source machine...")
if !config.MachineImageFilters.Empty() {
ui.Say("Selecting an image based on search criteria")
imageId, err := driver.GetImage(config)
if err != nil {
state.Put("error", fmt.Errorf("Problem selecting an image based on an search criteria: %s", err))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Based, on given search criteria, Machine ID is: %q", imageId))
config.MachineImage = imageId
}
machineId, err := driver.CreateMachine(config)
if err != nil {
@ -33,7 +42,6 @@ func (s *StepCreateSourceMachine) Run(state multistep.StateBag) multistep.StepAc
}
state.Put("machine", machineId)
return multistep.ActionContinue
}

View File

@ -9,6 +9,8 @@ import (
"strconv"
"strings"
"time"
packer "github.com/hashicorp/packer/common"
)
type VBox42Driver struct {
@ -50,7 +52,15 @@ func (d *VBox42Driver) CreateSCSIController(vmName string, name string) error {
}
func (d *VBox42Driver) Delete(name string) error {
return d.VBoxManage("unregistervm", name, "--delete")
return packer.Retry(1, 1, 5, func(i uint) (bool, error) {
if err := d.VBoxManage("unregistervm", name, "--delete"); err != nil {
if i+1 == 5 {
return false, err
}
return false, nil
}
return true, nil
})
}
func (d *VBox42Driver) Iso() (string, error) {

View File

@ -2,10 +2,10 @@ package iso
import (
"fmt"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"time"
)
// This step creates the actual virtual machine.
@ -73,18 +73,8 @@ func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
return
}
ui.Say("Unregistering and deleting virtual machine...")
var err error = nil
for i := 0; i < 5; i++ {
err = driver.Delete(s.vmName)
if err == nil {
break
}
time.Sleep(1 * time.Second * time.Duration(i))
}
if err != nil {
ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err))
ui.Say("Deregistering and deleting VM...")
if err := driver.Delete(s.vmName); err != nil {
ui.Error(fmt.Sprintf("Error deleting VM: %s", err))
}
}

View File

@ -38,6 +38,7 @@ type Config struct {
SourcePath string `mapstructure:"source_path"`
TargetPath string `mapstructure:"target_path"`
VMName string `mapstructure:"vm_name"`
KeepRegistered bool `mapstructure:"keep_registered"`
SkipExport bool `mapstructure:"skip_export"`
ctx interpolate.Context

View File

@ -2,6 +2,7 @@ package ovf
import (
"fmt"
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
@ -40,8 +41,16 @@ func (s *StepImport) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packer.Ui)
config := state.Get("config").(*Config)
ui.Say("Unregistering and deleting imported VM...")
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if (config.KeepRegistered) && (!cancelled && !halted) {
ui.Say("Keeping virtual machine registered with VirtualBox host (keep_registered = true)")
return
}
ui.Say("Deregistering and deleting imported VM...")
if err := driver.Delete(s.vmName); err != nil {
ui.Error(fmt.Sprintf("Error deleting VM: %s", err))
}

View File

@ -12,7 +12,10 @@ func TestStepImport_impl(t *testing.T) {
func TestStepImport(t *testing.T) {
state := testState(t)
c := testConfig(t)
config, _, _ := NewConfig(c)
state.Put("vm_path", "foo")
state.Put("config", config)
step := new(StepImport)
step.Name = "bar"
@ -42,6 +45,14 @@ func TestStepImport(t *testing.T) {
}
// Test cleanup
config.KeepRegistered = true
step.Cleanup(state)
if driver.DeleteCalled {
t.Fatal("delete should not be called")
}
config.KeepRegistered = false
step.Cleanup(state)
if !driver.DeleteCalled {
t.Fatal("delete should be called")

View File

@ -121,7 +121,7 @@ func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) {
func runAndLog(cmd *exec.Cmd) (string, string, error) {
var stdout, stderr bytes.Buffer
log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
log.Printf("Executing: %s %s", cmd.Path, strings.Join(cmd.Args[1:], " "))
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()

View File

@ -8,8 +8,10 @@ import (
)
type RunConfig struct {
Headless bool `mapstructure:"headless"`
RawBootWait string `mapstructure:"boot_wait"`
Headless bool `mapstructure:"headless"`
RawBootWait string `mapstructure:"boot_wait"`
DisableVNC bool `mapstructure:"disable_vnc"`
BootCommand []string `mapstructure:"boot_command"`
VNCBindAddress string `mapstructure:"vnc_bind_address"`
VNCPortMin uint `mapstructure:"vnc_port_min"`
@ -38,6 +40,11 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
var err error
if len(c.BootCommand) > 0 && c.DisableVNC {
errs = append(errs,
fmt.Errorf("A boot command cannot be used when vnc is disabled."))
}
if c.RawBootWait != "" {
c.BootWait, err = time.ParseDuration(c.RawBootWait)
if err != nil {

View File

@ -21,6 +21,7 @@ import (
// <nothing>
type StepCleanVMX struct {
RemoveEthernetInterfaces bool
VNCEnabled bool
}
func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
@ -59,8 +60,10 @@ func (s StepCleanVMX) Run(state multistep.StateBag) multistep.StepAction {
vmxData[ide+"clientdevice"] = "TRUE"
}
ui.Message("Disabling VNC server...")
vmxData["remotedisplay.vnc.enabled"] = "FALSE"
if s.VNCEnabled {
ui.Message("Disabling VNC server...")
vmxData["remotedisplay.vnc.enabled"] = "FALSE"
}
if s.RemoveEthernetInterfaces {
ui.Message("Removing Ethernet Interfaces...")

View File

@ -21,6 +21,7 @@ import (
// Produces:
// vnc_port uint - The port that VNC is configured to listen on.
type StepConfigureVNC struct {
Enabled bool
VNCBindAddress string
VNCPortMin uint
VNCPortMax uint
@ -76,6 +77,11 @@ func VNCPassword(skipPassword bool) string {
}
func (s *StepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
if !s.Enabled {
log.Println("Skipping VNC configuration step...")
return multistep.ActionContinue
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmxPath := state.Get("vmx_path").(string)

View File

@ -2,9 +2,10 @@ package common
import (
"fmt"
"time"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"time"
)
// This step runs the created virtual machine.

View File

@ -36,12 +36,18 @@ type bootCommandTemplateData struct {
// Produces:
// <nothing>
type StepTypeBootCommand struct {
VNCEnabled bool
BootCommand []string
VMName string
Ctx interpolate.Context
}
func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction {
if !s.VNCEnabled {
log.Println("Skipping boot command step...")
return multistep.ActionContinue
}
debug := state.Get("debug").(bool)
driver := state.Get("driver").(Driver)
httpPort := state.Get("http_port").(uint)

View File

@ -4,6 +4,12 @@ import (
"fmt"
)
const (
ArtifactConfFormat = "artifact.conf.format"
ArtifactConfKeepRegistered = "artifact.conf.keep_registered"
ArtifactConfSkipExport = "artifact.conf.skip_export"
)
// Artifact is the result of running the VMware builder, namely a set
// of files associated with the resulting machine.
type Artifact struct {
@ -11,6 +17,7 @@ type Artifact struct {
id string
dir OutputDir
f []string
config map[string]string
}
func (a *Artifact) BuilderId() string {
@ -30,7 +37,7 @@ func (a *Artifact) String() string {
}
func (a *Artifact) State(name string) interface{} {
return nil
return a.config[name]
}
func (a *Artifact) Destroy() error {

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"log"
"os"
"strconv"
"time"
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
@ -38,7 +39,6 @@ type Config struct {
vmwcommon.VMXConfig `mapstructure:",squash"`
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
BootCommand []string `mapstructure:"boot_command"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
@ -149,6 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
if b.config.RemotePort == 0 {
b.config.RemotePort = 22
}
if b.config.VMXTemplatePath != "" {
if err := b.validateVMXTemplatePath(); err != nil {
errs = packer.MultiErrorAppend(
@ -179,6 +180,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"will forcibly halt the virtual machine, which may result in data loss.")
}
if b.config.Headless && b.config.DisableVNC {
warnings = append(warnings,
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
"you won't be able to see any output.")
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
@ -259,6 +266,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
HTTPPortMax: b.config.HTTPPortMax,
},
&vmwcommon.StepConfigureVNC{
Enabled: !b.config.DisableVNC,
VNCBindAddress: b.config.VNCBindAddress,
VNCPortMin: b.config.VNCPortMin,
VNCPortMax: b.config.VNCPortMax,
@ -273,6 +281,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Headless: b.config.Headless,
},
&vmwcommon.StepTypeBootCommand{
VNCEnabled: !b.config.DisableVNC,
BootCommand: b.config.BootCommand,
VMName: b.config.VMName,
Ctx: b.config.ctx,
@ -303,6 +312,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&vmwcommon.StepCleanVMX{
RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet,
VNCEnabled: !b.config.DisableVNC,
},
&StepUploadVMX{
RemoteType: b.config.RemoteType,
@ -334,7 +344,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Compile the artifact list
var files []string
if b.config.RemoteType != "" && b.config.Format != "" {
if b.config.RemoteType != "" && b.config.Format != "" && !b.config.SkipExport {
dir = new(vmwcommon.LocalOutputDir)
dir.SetOutputDir(exportOutputPath)
files, err = dir.ListFiles()
@ -351,11 +361,17 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
builderId = BuilderIdESX
}
config := make(map[string]string)
config[ArtifactConfKeepRegistered] = strconv.FormatBool(b.config.KeepRegistered)
config[ArtifactConfFormat] = b.config.Format
config[ArtifactConfSkipExport] = strconv.FormatBool(b.config.SkipExport)
return &Artifact{
builderId: builderId,
id: b.config.VMName,
dir: dir,
f: files,
config: config,
}, nil
}

View File

@ -165,10 +165,10 @@ func (d *ESX5Driver) Verify() error {
func (d *ESX5Driver) HostIP() (string, error) {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
defer conn.Close()
if err != nil {
return "", err
}
defer conn.Close()
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
return host, err

View File

@ -51,7 +51,7 @@ func (s *StepRegister) Cleanup(state multistep.StateBag) {
}
if remoteDriver, ok := driver.(RemoteDriver); ok {
if s.Format == "" {
if s.Format == "" || config.SkipExport {
ui.Say("Unregistering virtual machine...")
if err := remoteDriver.Unregister(s.registeredPath); err != nil {
ui.Error(fmt.Sprintf("Error unregistering VM: %s", err))

View File

@ -80,6 +80,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
HTTPPortMax: b.config.HTTPPortMax,
},
&vmwcommon.StepConfigureVNC{
Enabled: !b.config.DisableVNC,
VNCBindAddress: b.config.VNCBindAddress,
VNCPortMin: b.config.VNCPortMin,
VNCPortMax: b.config.VNCPortMax,
@ -91,6 +92,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Headless: b.config.Headless,
},
&vmwcommon.StepTypeBootCommand{
VNCEnabled: !b.config.DisableVNC,
BootCommand: b.config.BootCommand,
VMName: b.config.VMName,
Ctx: b.config.ctx,
@ -121,6 +123,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&vmwcommon.StepCleanVMX{
RemoveEthernetInterfaces: b.config.VMXConfig.VMXRemoveEthernet,
VNCEnabled: !b.config.DisableVNC,
},
}

View File

@ -24,11 +24,10 @@ type Config struct {
vmwcommon.ToolsConfig `mapstructure:",squash"`
vmwcommon.VMXConfig `mapstructure:",squash"`
BootCommand []string `mapstructure:"boot_command"`
RemoteType string `mapstructure:"remote_type"`
SkipCompaction bool `mapstructure:"skip_compaction"`
SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"`
RemoteType string `mapstructure:"remote_type"`
SkipCompaction bool `mapstructure:"skip_compaction"`
SourcePath string `mapstructure:"source_path"`
VMName string `mapstructure:"vm_name"`
ctx interpolate.Context
}
@ -84,6 +83,12 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
"will forcibly halt the virtual machine, which may result in data loss.")
}
if c.Headless && c.DisableVNC {
warnings = append(warnings,
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
"you won't be able to see any output.")
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs

View File

@ -26,6 +26,7 @@ import (
filebuilder "github.com/hashicorp/packer/builder/file"
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
hypervisobuilder "github.com/hashicorp/packer/builder/hyperv/iso"
hypervvmcxbuilder "github.com/hashicorp/packer/builder/hyperv/vmcx"
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
lxdbuilder "github.com/hashicorp/packer/builder/lxd"
nullbuilder "github.com/hashicorp/packer/builder/null"
@ -92,6 +93,7 @@ var Builders = map[string]packer.Builder{
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"hyperv-vmcx": new(hypervvmcxbuilder.Builder),
"lxc": new(lxcbuilder.Builder),
"lxd": new(lxdbuilder.Builder),
"null": new(nullbuilder.Builder),

View File

@ -62,7 +62,7 @@ func (c *VersionCommand) Run(args []string) int {
if info.Outdated {
c.Ui.Say(fmt.Sprintf(
"\nYour version of Packer is out of date! The latest version\n"+
"is %s. You can update by downloading from www.packer.io",
"is %s. You can update by downloading from www.packer.io/downloads.html",
info.Latest))
}
}

View File

@ -187,29 +187,44 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null
return err
}
func CreateVirtualMachine(vmName string, path string, ram int64, diskSize int64, switchName string, generation uint) error {
func CreateVirtualMachine(vmName string, path string, harddrivePath string, vhdRoot string, ram int64, diskSize int64, switchName string, generation uint) error {
if generation == 2 {
var script = `
param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation)
param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation)
$vhdx = $vmName + '.vhdx'
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation
$vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx
if ($harddrivePath){
Copy-Item -Path $harddrivePath -Destination $vhdPath
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName -Generation $generation
} else {
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10))
return err
if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10)); err != nil {
return err
}
return DisableAutomaticCheckpoints(vmName)
} else {
var script = `
param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName)
param([string]$vmName, [string]$path, [string]$harddrivePath, [string]$vhdRoot, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName)
$vhdx = $vmName + '.vhdx'
$vhdPath = Join-Path -Path $path -ChildPath $vhdx
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName
$vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdx
if ($harddrivePath){
Copy-Item -Path $harddrivePath -Destination $vhdPath
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -VHDPath $vhdPath -SwitchName $switchName
} else {
New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName)
if err := ps.Run(script, vmName, path, harddrivePath, vhdRoot, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName); err != nil {
return err
}
if err != nil {
if err := DisableAutomaticCheckpoints(vmName); err != nil {
return err
}
@ -217,6 +232,186 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD
}
}
func DisableAutomaticCheckpoints(vmName string) error {
var script = `
param([string]$vmName)
if ((Get-Command Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
Set-Vm -Name $vmName -AutomaticCheckpointsEnabled $false }
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName)
return err
}
func ExportVmxcVirtualMachine(exportPath string, vmName string, snapshotName string, allSnapshots bool) error {
var script = `
param([string]$exportPath, [string]$vmName, [string]$snapshotName, [string]$allSnapshotsString)
$WorkingPath = Join-Path $exportPath $vmName
if (Test-Path $WorkingPath) {
throw "Export path working directory: $WorkingPath already exists!"
}
$allSnapshots = [System.Boolean]::Parse($allSnapshotsString)
if ($snapshotName) {
$snapshot = Get-VMSnapshot -VMName $vmName -Name $snapshotName
Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
} else {
if (!$allSnapshots) {
#Use last snapshot if one was not specified
$snapshot = Get-VMSnapshot -VMName $vmName | Select -Last 1
} else {
$snapshot = $null
}
if (!$snapshot) {
#No snapshot clone
Export-VM -Name $vmName -Path $exportPath -ErrorAction Stop
} else {
#Snapshot clone
Export-VMSnapshot -VMSnapshot $snapshot -Path $exportPath -ErrorAction Stop
}
}
$result = Get-ChildItem -Path $WorkingPath | Move-Item -Destination $exportPath -Force
$result = Remove-Item -Path $WorkingPath
`
allSnapshotsString := "False"
if allSnapshots {
allSnapshotsString = "True"
}
var ps powershell.PowerShellCmd
err := ps.Run(script, exportPath, vmName, snapshotName, allSnapshotsString)
return err
}
func CopyVmxcVirtualMachine(exportPath string, cloneFromVmxcPath string) error {
var script = `
param([string]$exportPath, [string]$cloneFromVmxcPath)
if (!(Test-Path $cloneFromVmxcPath)){
throw "Clone from vmxc directory: $cloneFromVmxcPath does not exist!"
}
if (!(Test-Path $exportPath)){
New-Item -ItemType Directory -Force -Path $exportPath
}
$cloneFromVmxcPath = Join-Path $cloneFromVmxcPath '\*'
Copy-Item $cloneFromVmxcPath $exportPath -Recurse -Force
`
var ps powershell.PowerShellCmd
err := ps.Run(script, exportPath, cloneFromVmxcPath)
return err
}
func ImportVmxcVirtualMachine(importPath string, vmName string, harddrivePath string, ram int64, switchName string) error {
var script = `
param([string]$importPath, [string]$vmName, [string]$harddrivePath, [long]$memoryStartupBytes, [string]$switchName)
$VirtualHarddisksPath = Join-Path -Path $importPath -ChildPath 'Virtual Hard Disks'
if (!(Test-Path $VirtualHarddisksPath)) {
New-Item -ItemType Directory -Force -Path $VirtualHarddisksPath
}
$vhdPath = ""
if ($harddrivePath){
$vhdx = $vmName + '.vhdx'
$vhdPath = Join-Path -Path $VirtualHarddisksPath -ChildPath $vhdx
}
$VirtualMachinesPath = Join-Path $importPath 'Virtual Machines'
if (!(Test-Path $VirtualMachinesPath)) {
New-Item -ItemType Directory -Force -Path $VirtualMachinesPath
}
$VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.vmcx -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
if (!$VirtualMachinePath){
$VirtualMachinePath = Get-ChildItem -Path $VirtualMachinesPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
}
if (!$VirtualMachinePath){
$VirtualMachinePath = Get-ChildItem -Path $importPath -Filter *.xml -Recurse -ErrorAction SilentlyContinue | select -First 1 | %{$_.FullName}
}
$compatibilityReport = Compare-VM -Path $VirtualMachinePath -VirtualMachinePath $importPath -SmartPagingFilePath $importPath -SnapshotFilePath $importPath -VhdDestinationPath $VirtualHarddisksPath -GenerateNewId -Copy:$false
if ($vhdPath){
Copy-Item -Path $harddrivePath -Destination $vhdPath
$existingFirstHarddrive = $compatibilityReport.VM.HardDrives | Select -First 1
if ($existingFirstHarddrive) {
$existingFirstHarddrive | Set-VMHardDiskDrive -Path $vhdPath
} else {
Add-VMHardDiskDrive -VM $compatibilityReport.VM -Path $vhdPath
}
}
Set-VMMemory -VM $compatibilityReport.VM -StartupBytes $memoryStartupBytes
$networkAdaptor = $compatibilityReport.VM.NetworkAdapters | Select -First 1
Disconnect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor
Connect-VMNetworkAdapter -VMNetworkAdapter $networkAdaptor -SwitchName $switchName
$vm = Import-VM -CompatibilityReport $compatibilityReport
if ($vm) {
$result = Rename-VM -VM $vm -NewName $VMName
}
`
var ps powershell.PowerShellCmd
err := ps.Run(script, importPath, vmName, harddrivePath, strconv.FormatInt(ram, 10), switchName)
return err
}
func CloneVirtualMachine(cloneFromVmxcPath string, cloneFromVmName string, cloneFromSnapshotName string, cloneAllSnapshots bool, vmName string, path string, harddrivePath string, ram int64, switchName string) error {
if cloneFromVmName != "" {
if err := ExportVmxcVirtualMachine(path, cloneFromVmName, cloneFromSnapshotName, cloneAllSnapshots); err != nil {
return err
}
}
if cloneFromVmxcPath != "" {
if err := CopyVmxcVirtualMachine(path, cloneFromVmxcPath); err != nil {
return err
}
}
if err := ImportVmxcVirtualMachine(path, vmName, harddrivePath, ram, switchName); err != nil {
return err
}
return DeleteAllDvdDrives(vmName)
}
func GetVirtualMachineGeneration(vmName string) (uint, error) {
var script = `
param([string]$vmName)
$generation = Get-Vm -Name $vmName | %{$_.Generation}
if (!$generation){
$generation = 1
}
return $generation
`
var ps powershell.PowerShellCmd
cmdOut, err := ps.Output(script, vmName)
if err != nil {
return 0, err
}
generationUint32, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 32)
if err != nil {
return 0, err
}
generation := uint(generationUint32)
return generation, err
}
func SetVirtualMachineCpuCount(vmName string, cpu uint) error {
var script = `
@ -656,6 +851,19 @@ Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -SwitchName $swi
return err
}
func AddVirtualMachineHardDiskDrive(vmName string, vhdRoot string, vhdName string, vhdSizeBytes int64, controllerType string) error {
var script = `
param([string]$vmName,[string]$vhdRoot, [string]$vhdName, [string]$vhdSizeInBytes, [string]$controllerType)
$vhdPath = Join-Path -Path $vhdRoot -ChildPath $vhdName
New-VHD $vhdPath -SizeBytes $vhdSizeInBytes
Add-VMHardDiskDrive -VMName $vmName -path $vhdPath -controllerType $controllerType
`
var ps powershell.PowerShellCmd
err := ps.Run(script, vmName, vhdRoot, vhdName, strconv.FormatInt(vhdSizeBytes, 10), controllerType)
return err
}
func UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error {
var script = `

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