using only remoteFolder as parameter
This commit is contained in:
commit
da54bf8e73
|
@ -23,3 +23,4 @@ packer-test*.log
|
|||
.idea/
|
||||
*.iml
|
||||
Thumbs.db
|
||||
/packer.exe
|
71
CHANGELOG.md
71
CHANGELOG.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
3
Makefile
3
Makefile
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -10,6 +10,7 @@ func testConfig() map[string]interface{} {
|
|||
return map[string]interface{}{
|
||||
"ami_name": "foo",
|
||||
"source_ami": "foo",
|
||||
"region": "us-east-1",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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...")
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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...
|
||||
}
|
|
@ -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{
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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."))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)'"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -28,7 +28,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, errs
|
||||
}
|
||||
b.config = c
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
)
|
||||
|
||||
type Image struct {
|
||||
Labels map[string]string
|
||||
Licenses []string
|
||||
Name string
|
||||
ProjectId string
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{},
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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...")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue