Merge branch 'master' of https://github.com/hashicorp/packer into vsphere-tpl

This commit is contained in:
bugbuilder 2017-08-30 18:13:54 -03:00
commit 138f54d9d3
524 changed files with 250610 additions and 33258 deletions

1
.gitignore vendored
View File

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

View File

@ -8,6 +8,7 @@ language: go
go:
- 1.7.4
- 1.8.3
- 1.9
install:
- make deps
@ -21,5 +22,3 @@ branches:
matrix:
fast_finish: true
allow_failures:
- go: 1.4.3

View File

@ -1,20 +1,98 @@
## (UNRELEASED)
## UNRELEASED
### IMRPOVEMENTS:
* core: Experimental Android ARM support. [GH-5111]
* postprocessor/atlas: Disallow pushing vagrant.box artifacts now that Vagrant cloud is live. [GH-4780]
* builder/cloudstack: Add support for using a HTTP server. [GH-5017]
* builder/cloudstack: Make expunge optional and improve logging output. [GH-5099]
* builder/googlecompute: Allow using URL's for network and subnetwork. [GH-5035]
### IMPROVEMENTS:
* provisioner/salt-masterless: Also use sudo to clean up if we used sudo to install. [GH-5240]
* builder/profitbricks: added support for Cloud API v4. [GH-5233]
* builder/vmware: Set artifact ID to `VMName`. [GH-5187]
### BACKWARDS INCOMPATIBILITIES:
* communicator/ssh: Renamed ssh_disable_agent to ssh_disable_agent_forwarding. Need to run fixer on packer configs that use ssh_disable_agent. [GH-5024]
* provisioner/shell: Set default for ExpectDisconnect to false. [GH-5283]
* communicator: Preserve left-sided white-space in remote command output. Make sure any scripts that parse this output can handle the new whitespace before upgrading. [GH-5167]
### BUG FIXES:
* builder/hyperv: Verify membership in the group Hyper-V Administrators by SID not name. [GH-5022]
* builder/docker: Fix windows filepath in docker-toolbox call. [GH-4887]
* builder/amazon: Fix panic that happens if ami_block_device_mappings is empty. [GH-5059]
* builder/vmware: Fix timestamp in default VMName. [GH-5274]
* provisioner/windows-restart: The first powershell provisioner after a restart now works. [GH-5272]
* builder/amazon: force_deregister works in all regions, not just original region. [GH-5250]
* builder/docker: Fix file uploads. [GH-5251]
## 1.0.4 (August 11, 2017)
### IMPROVEMENTS:
* builder/alicloud: Increase polling timeout. [GH-5148]
* builder/azure: Add `private_virtual_network_with_public_ip` option to
optionally obtain a public IP. [GH-5222]
* builder/googlecompute: use a more portable method of obtaining zone.
[GH-5192]
* builder/hyperv: Properly interpolate user variables in template. [GH-5184]
* builder/parallels: Remove soon to be removed --vmtype flag in createvm.
[GH-5172]
* contrib: add json files to zsh completion. [GH-5195]
### BUG FIXES:
* builder/amazon: Don't delete snapshots we didn't create. [GH-5211]
* builder/amazon: fix builds when using the null communicator. [GH-5217]
* builder/docker: Correctly handle case when uploading an empty directory.
[GH-5234]
* command/push: Don't push variables if they are unspecified. Reverts to
behavior in 1.0.1. [GH-5235]
* command/push: fix handling of symlinks. [GH-5226]
* core: Strip query parameters from ISO URLs when checking against a checksum
file. [GH-5181]
* provisioner/ansible-remote: Fix issue where packer could hang communicating
with ansible-remote. [GH-5146]
## 1.0.3 (July 17, 2017)
### IMPROVEMENTS:
* builder/azure: Update to latest Azure SDK, enabling support for managed
disks. [GH-4511]
* builder/cloudstack: Add default cidr_list [ 0.0.0.0/0 ]. [GH-5125]
* builder/cloudstack: Add support for ssh_agent_auth. [GH-5130]
* builder/cloudstack: Add support for using a HTTP server. [GH-5017]
* builder/cloudstack: Allow reading api_url, api_key, and secret_key from env
vars. [GH-5124]
* builder/cloudstack: Make expunge optional and improve logging output.
[GH-5099]
* builder/googlecompute: Allow using URL's for network and subnetwork.
[GH-5035]
* builder/hyperv: Add support for floppy_dirs with hyperv-iso builder.
* builder/hyperv: Add support for override of system %temp% path.
* core: Experimental Android ARM support. [GH-5111]
* post-processor/atlas: Disallow packer push of vagrant.box artifacts to atlas.
[GH-4780]
* postprocessor/atlas: Disallow pushing vagrant.box artifacts now that Vagrant
cloud is live. [GH-4780]
### BUG FIXES:
* builder/amazon: Fix panic that happens if ami_block_device_mappings is empty.
[GH-5059]
* builder/azure: Write private SSH to file in debug mode. [GH-5070] [GH-5074]
* builder/cloudstack: Properly report back errors. [GH-5103]
* builder/parallels: Skip missing paths when looking for unnecessary files. [GH-5058]
* builder/cloudstack: Properly report back errors. [GH-5103] [GH-5123]
* builder/docker: Fix windows filepath in docker-toolbox call [GH-4887]
* builder/docker: Fix windows filepath in docker-toolbox call. [GH-4887]
* builder/hyperv: Use SID to verify membersip in Admin group, fixing for non-
english users. [GH-5022]
* builder/hyperv: Verify membership in the group Hyper-V Administrators by SID
not name. [GH-5022]
* builder/openstack: Update gophercloud version, fixing builds > 1 hr long.
[GH-5046]
* builder/parallels: Skip missing paths when looking for unnecessary files.
[GH-5058]
* builder/vmware-esxi: Fix VNC port discovery default timeout. [GH-5051]
* communicator/ssh: Add ProvisionerTypes to communicator tests, resolving panic
[GH-5116]
* communicator/ssh: Resolve race condition that sometimes truncates ssh
provisioner stdout [GH-4719]
* post-processor/checksum: Fix interpolation of "output". [GH-5112]
* push: Push vars in packer config, not just those set from command line and in
var-file. [GH-5101]
## 1.0.2 (June 21, 2017)

View File

@ -54,7 +54,7 @@ If you have never worked with Go before, you will have to complete the
following steps in order to be able to compile and test Packer. These instructions target POSIX-like environments (Mac OS X, Linux, Cygwin, etc.) so you may need to adjust them for Windows or other shells.
1. [Download](https://golang.org/dl) and install Go. The instructions below
are for go 1.6. Earlier versions of Go are no longer supported.
are for go 1.7. Earlier versions of Go are no longer supported.
2. Set and export the `GOPATH` environment variable and update your `PATH`. For
example, you can add to your `.bash_profile`.

View File

@ -7,6 +7,7 @@ GITBRANCH:=$(shell git symbolic-ref --short HEAD 2>/dev/null)
GOFMT_FILES?=$$(find . -not -path "./vendor/*" -name "*.go")
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)
GOPATH=$(shell go env GOPATH)
# Get the git commit
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)

View File

@ -5,6 +5,7 @@ package ecs
import (
"log"
"fmt"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/config"
@ -98,7 +99,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
//DebugKeyPath: b.config.Com
DebugKeyPath: fmt.Sprintf("ecs_%s.pem", b.config.PackerBuildName),
RegionId: b.config.AlicloudRegion,
},
}

View File

@ -7,6 +7,7 @@ import (
"github.com/denverdino/aliyungo/ecs"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"time"
)
type stepAttachKeyPar struct {
@ -21,7 +22,7 @@ func (s *stepAttachKeyPar) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*ecs.Client)
config := state.Get("config").(Config)
instance := state.Get("instance").(*ecs.InstanceAttributesType)
retry_times := 3
timeoutPoint := time.Now().Add(120 * time.Second)
for {
err := client.AttachKeyPair(&ecs.AttachKeyPairArgs{RegionId: common.Region(config.AlicloudRegion),
KeyPairName: keyPairName, InstanceIds: "[\"" + instance.InstanceId + "\"]"})
@ -29,8 +30,8 @@ func (s *stepAttachKeyPar) Run(state multistep.StateBag) multistep.StepAction {
e, _ := err.(*common.Error)
if (!(e.Code == "MissingParameter" || e.Code == "DependencyViolation.WindowsInstance" ||
e.Code == "InvalidKeyPairName.NotFound" || e.Code == "InvalidRegionId.NotFound")) &&
retry_times > 0 {
retry_times = retry_times - 1
time.Now().Before(timeoutPoint) {
time.Sleep(5 * time.Second)
continue
}
err := fmt.Errorf("Error attaching keypair %s to instance %s : %s",

View File

@ -121,12 +121,12 @@ func (s *stepConfigAlicloudSecurityGroup) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
message(state, "security group")
start := time.Now().Add(10 * time.Second)
timeoutPoint := time.Now().Add(120 * time.Second)
for {
if err := client.DeleteSecurityGroup(common.Region(s.RegionId), s.SecurityGroupId); err != nil {
e, _ := err.(*common.Error)
if e.Code == "DependencyViolation" && time.Now().Before(start) {
time.Sleep(1 * time.Second)
if e.Code == "DependencyViolation" && time.Now().Before(timeoutPoint) {
time.Sleep(5 * time.Second)
continue
}
ui.Error(fmt.Sprintf("Failed to delete security group, it may still be around: %s", err))

View File

@ -78,13 +78,13 @@ func (s *stepConfigAlicloudVPC) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
message(state, "VPC")
start := time.Now().Add(10 * time.Second)
timeoutPoint := time.Now().Add(60 * time.Second)
for {
if err := client.DeleteVpc(s.VpcId); err != nil {
e, _ := err.(*common.Error)
if (e.Code == "DependencyViolation.Instance" || e.Code == "DependencyViolation.RouteEntry" ||
e.Code == "DependencyViolation.VSwitch" ||
e.Code == "DependencyViolation.SecurityGroup") && time.Now().Before(start) {
e.Code == "DependencyViolation.SecurityGroup") && time.Now().Before(timeoutPoint) {
time.Sleep(1 * time.Second)
continue
}

View File

@ -113,7 +113,7 @@ func (s *stepConfigAlicloudVSwitch) Run(state multistep.StateBag) multistep.Step
}
if err := client.WaitForVSwitchAvailable(vpcId, s.VSwitchId, ALICLOUD_DEFAULT_TIMEOUT); err != nil {
state.Put("error", err)
ui.Error(fmt.Sprintf("Timeout waiting for vswitch to become avaiable: %v", err))
ui.Error(fmt.Sprintf("Timeout waiting for vswitch to become available: %v", err))
return multistep.ActionHalt
}
state.Put("vswitchid", vswitchId)
@ -130,13 +130,13 @@ func (s *stepConfigAlicloudVSwitch) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*ecs.Client)
ui := state.Get("ui").(packer.Ui)
message(state, "vSwitch")
start := time.Now().Add(10 * time.Second)
timeoutPoint := time.Now().Add(10 * time.Second)
for {
if err := client.DeleteVSwitch(s.VSwitchId); err != nil {
e, _ := err.(*common.Error)
if (e.Code == "IncorrectVSwitchStatus" || e.Code == "DependencyViolation" ||
e.Code == "DependencyViolation.HaVip" ||
e.Code == "IncorretRouteEntryStatus") && time.Now().Before(start) {
e.Code == "IncorretRouteEntryStatus") && time.Now().Before(timeoutPoint) {
time.Sleep(1 * time.Second)
continue
}

View File

@ -214,7 +214,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
steps = append(steps,
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AmiFilters: b.config.SourceAmiFilter,
},
&StepCheckRootDevice{},
@ -245,17 +246,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepEarlyCleanup{},
&StepSnapshot{},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.AMIForceDeregister,
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
AMIName: b.config.AMIName,
Regions: b.config.AMIRegions,
},
&StepRegisterAMI{
RootVolumeSize: b.config.RootVolumeSize,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
AMIMappings: b.config.AMIBlockDevices.AMIMappings,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,

View File

@ -4,7 +4,8 @@ package chroot
import (
"os"
"syscall"
"golang.org/x/sys/unix"
)
// See: http://linux.die.net/include/sys/file.h
@ -13,7 +14,7 @@ const LOCK_NB = 4
const LOCK_UN = 8
func lockFile(f *os.File) error {
err := syscall.Flock(int(f.Fd()), LOCK_EX)
err := unix.Flock(int(f.Fd()), LOCK_EX)
if err != nil {
return err
}
@ -22,5 +23,5 @@ func lockFile(f *os.File) error {
}
func unlockFile(f *os.File) error {
return syscall.Flock(int(f.Fd()), LOCK_UN)
return unix.Flock(int(f.Fd()), LOCK_UN)
}

View File

@ -13,6 +13,8 @@ import (
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct {
RootVolumeSize int64
EnableAMIENASupport bool
EnableAMISriovNetSupport bool
}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
@ -75,11 +77,12 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerOpts = buildRegisterOpts(config, image, newMappings)
}
if config.AMIEnhancedNetworking {
if s.EnableAMISriovNetSupport {
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
registerOpts.SriovNetSupport = aws.String("simple")
}
if s.EnableAMIENASupport {
// Set EnaSupport to true
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
registerOpts.EnaSupport = aws.Bool(true)

View File

@ -17,7 +17,8 @@ type AMIConfig struct {
AMIRegions []string `mapstructure:"ami_regions"`
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
AMITags map[string]string `mapstructure:"tags"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIENASupport bool `mapstructure:"ena_support"`
AMISriovNetSupport bool `mapstructure:"sriov_support"`
AMIForceDeregister bool `mapstructure:"force_deregister"`
AMIForceDeleteSnapshot bool `mapstructure:"force_delete_snapshot"`
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`

View File

@ -51,7 +51,7 @@ func (a *Artifact) String() string {
}
sort.Strings(amiStrings)
return fmt.Sprintf("AMIs were created:\n\n%s", strings.Join(amiStrings, "\n"))
return fmt.Sprintf("AMIs were created:\n%s\n", strings.Join(amiStrings, "\n"))
}
func (a *Artifact) State(name string) interface{} {

View File

@ -48,9 +48,9 @@ func TestArtifactState_atlasMetadata(t *testing.T) {
func TestArtifactString(t *testing.T) {
expected := `AMIs were created:
east: foo
west: bar`
west: bar
`
amis := make(map[string]string)
amis["east"] = "foo"

View File

@ -10,18 +10,34 @@ import (
)
type StepDeregisterAMI struct {
AccessConfig *AccessConfig
ForceDeregister bool
ForceDeleteSnapshot bool
AMIName string
Regions []string
}
func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
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 {
resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
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
}
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)},
@ -36,7 +52,7 @@ func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
// Deregister image(s) by name
for _, i := range resp.Images {
_, err := ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
_, err := regionconn.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: i.ImageId,
})
@ -52,7 +68,7 @@ func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
if s.ForceDeleteSnapshot {
for _, b := range i.BlockDeviceMappings {
if b.Ebs != nil && aws.StringValue(b.Ebs.SnapshotId) != "" {
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
_, err := regionconn.DeleteSnapshot(&ec2.DeleteSnapshotInput{
SnapshotId: b.Ebs.SnapshotId,
})
@ -68,6 +84,7 @@ func (s *StepDeregisterAMI) Run(state multistep.StateBag) multistep.StepAction {
}
}
}
}
return multistep.ActionContinue
}

View File

@ -15,6 +15,7 @@ type StepCreateEncryptedAMICopy struct {
KeyID string
EncryptBootVolume bool
Name string
AMIMappings []BlockDevice
}
func (s *StepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction {
@ -116,9 +117,18 @@ func (s *StepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
ui.Say("Deleting unencrypted snapshots")
snapshots := state.Get("snapshots").(map[string][]string)
OuterLoop:
for _, blockDevice := range unencImage.BlockDeviceMappings {
if blockDevice.Ebs != nil && blockDevice.Ebs.SnapshotId != nil {
ui.Message(fmt.Sprintf("Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
// If this packer run didn't create it, then don't delete it
for _, origDevice := range s.AMIMappings {
if origDevice.SnapshotId == *blockDevice.Ebs.SnapshotId {
ui.Message(fmt.Sprintf("Keeping Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
continue OuterLoop
}
}
ui.Message(fmt.Sprintf("Deleting Snapshot ID: %s", *blockDevice.Ebs.SnapshotId))
deleteSnapOpts := &ec2.DeleteSnapshotInput{
SnapshotId: aws.String(*blockDevice.Ebs.SnapshotId),
}

View File

@ -10,7 +10,8 @@ import (
)
type StepModifyEBSBackedInstance struct {
EnableEnhancedNetworking bool
EnableAMIENASupport bool
EnableAMISriovNetSupport bool
}
func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.StepAction {
@ -18,9 +19,9 @@ func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.St
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
if s.EnableEnhancedNetworking {
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
if s.EnableAMISriovNetSupport {
ui.Say("Enabling Enhanced Networking (SR-IOV)...")
simple := "simple"
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
@ -33,11 +34,13 @@ func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.St
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Set EnaSupport to true.
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
if s.EnableAMIENASupport {
ui.Say("Enabling Enhanced Networking (ENA)...")
_, err = ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
InstanceId: instance.InstanceId,
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(true)},
})

View File

@ -136,7 +136,23 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
var instanceId string
ui.Say("Adding tags to source instance")
if _, exists := s.Tags["Name"]; !exists {
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)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ReportTags(ui, ec2Tags)
if spotPrice == "" || spotPrice == "0" {
runOpts := &ec2.RunInstancesInput{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
@ -149,6 +165,16 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
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
}
@ -255,6 +281,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
return multistep.ActionHalt
}
instanceId = *spotResp.SpotInstanceRequests[0].InstanceId
}
// Set the instance ID so that the cleanup works properly
@ -278,21 +305,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
instance := latestInstance.(*ec2.Instance)
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)
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{
@ -316,6 +329,7 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if s.Debug {
if instance.PublicDnsName != nil && *instance.PublicDnsName != "" {

View File

@ -6,7 +6,7 @@ import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/private/waiter"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/communicator"
@ -45,8 +45,10 @@ func (s *StepSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
port := s.CommConfig.Port()
if port == 0 {
if s.CommConfig.Type != "none" {
panic("port must be set to a non-zero value.")
}
}
// Create the group
groupName := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
@ -151,36 +153,42 @@ func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) {
}
func waitUntilSecurityGroupExists(c *ec2.EC2, input *ec2.DescribeSecurityGroupsInput) error {
waiterCfg := waiter.Config{
Operation: "DescribeSecurityGroups",
Delay: 15,
ctx := aws.BackgroundContext()
w := request.Waiter{
Name: "DescribeSecurityGroups",
MaxAttempts: 40,
Acceptors: []waiter.WaitAcceptor{
Acceptors: []request.WaiterAcceptor{
{
State: "success",
Matcher: "path",
State: request.SuccessWaiterState,
Matcher: request.PathWaiterMatch,
Argument: "length(SecurityGroups[]) > `0`",
Expected: true,
},
{
State: "retry",
Matcher: "error",
State: request.RetryWaiterState,
Matcher: request.ErrorWaiterMatch,
Argument: "",
Expected: "InvalidGroup.NotFound",
},
{
State: "retry",
Matcher: "error",
State: request.RetryWaiterState,
Matcher: request.ErrorWaiterMatch,
Argument: "",
Expected: "InvalidSecurityGroupID.NotFound",
},
},
Logger: c.Config.Logger,
NewRequest: func(opts []request.Option) (*request.Request, error) {
var inCpy *ec2.DescribeSecurityGroupsInput
if input != nil {
tmp := *input
inCpy = &tmp
}
w := waiter.Waiter{
Client: c,
Input: input,
Config: waiterCfg,
req, _ := c.DescribeSecurityGroupsRequest(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
return w.Wait()
return w.WaitWithContext(ctx)
}

View File

@ -18,7 +18,8 @@ import (
// source_image *ec2.Image - the source AMI info
type StepSourceAMIInfo struct {
SourceAmi string
EnhancedNetworking bool
EnableAMISriovNetSupport bool
EnableAMIENASupport bool
AmiFilters AmiFilterOptions
}
@ -103,7 +104,7 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
// Enhanced Networking can only be enabled on HVM AMIs.
// See http://goo.gl/icuXh5
if s.EnhancedNetworking && *image.VirtualizationType != "hvm" {
if (s.EnableAMIENASupport || s.EnableAMISriovNetSupport) && *image.VirtualizationType != "hvm" {
err := fmt.Errorf("Cannot enable enhanced networking, source AMI '%s' is not HVM", s.SourceAmi)
state.Put("error", err)
ui.Error(err.Error())

View File

@ -116,7 +116,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AmiFilters: b.config.SourceAmiFilter,
},
&awscommon.StepKeyPair{
@ -179,18 +180,22 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{
EnableEnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.AMIForceDeregister,
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
AMIName: b.config.AMIName,
Regions: b.config.AMIRegions,
},
&stepCreateAMI{},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
AMIMappings: b.config.AMIBlockDevices.AMIMappings,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,

View File

@ -130,7 +130,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AmiFilters: b.config.SourceAmiFilter,
},
&awscommon.StepKeyPair{
@ -189,19 +190,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{
EnableEnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&StepSnapshotNewRootVolume{
NewRootMountPoint: b.config.RootDevice.SourceDeviceName,
},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.AMIForceDeregister,
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
AMIName: b.config.AMIName,
Regions: b.config.AMIRegions,
},
&StepRegisterAMI{
RootDevice: b.config.RootDevice,
BlockDevices: b.config.BlockDevices.BuildAMIDevices(),
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,

View File

@ -14,6 +14,8 @@ import (
type StepRegisterAMI struct {
RootDevice RootBlockDevice
BlockDevices []*ec2.BlockDeviceMapping
EnableAMIENASupport bool
EnableAMISriovNetSupport bool
image *ec2.Image
}
@ -35,16 +37,16 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
BlockDeviceMappings: blockDevicesExcludingRoot,
}
if config.AMIEnhancedNetworking {
if s.EnableAMISriovNetSupport {
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
registerOpts.SriovNetSupport = aws.String("simple")
}
if s.EnableAMIENASupport {
// Set EnaSupport to true
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
registerOpts.EnaSupport = aws.Bool(true)
}
registerResp, err := ec2conn.RegisterImage(registerOpts)
if err != nil {
state.Put("error", fmt.Errorf("Error registering AMI: %s", err))

View File

@ -24,7 +24,8 @@ type Config struct {
awscommon.RunConfig `mapstructure:",squash"`
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
AMIENASupport bool `mapstructure:"ena_support"`
AMISriovNetSupport bool `mapstructure:"sriov_support"`
launchBlockDevices awscommon.BlockDevices
ctx interpolate.Context
@ -104,7 +105,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
steps := []multistep.Step{
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AmiFilters: b.config.SourceAmiFilter,
},
&awscommon.StepKeyPair{
@ -164,7 +166,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{
EnableEnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
}

View File

@ -201,7 +201,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
AmiFilters: b.config.SourceAmiFilter,
},
&awscommon.StepKeyPair{
@ -258,11 +259,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
Debug: b.config.PackerDebug,
},
&awscommon.StepDeregisterAMI{
AccessConfig: &b.config.AccessConfig,
ForceDeregister: b.config.AMIForceDeregister,
ForceDeleteSnapshot: b.config.AMIForceDeleteSnapshot,
AMIName: b.config.AMIName,
Regions: b.config.AMIRegions,
},
&StepRegisterAMI{
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport,
},
&StepRegisterAMI{},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,

View File

@ -10,7 +10,10 @@ import (
"github.com/mitchellh/multistep"
)
type StepRegisterAMI struct{}
type StepRegisterAMI struct {
EnableAMIENASupport bool
EnableAMISriovNetSupport bool
}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
@ -29,12 +32,13 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerOpts.VirtualizationType = aws.String(config.AMIVirtType)
}
if config.AMIEnhancedNetworking {
if s.EnableAMISriovNetSupport {
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
// As of February 2017, this applies to C3, C4, D2, I2, R3, and M4 (excluding m4.16xlarge)
registerOpts.SriovNetSupport = aws.String("simple")
// Set EnaSupport to true.
}
if s.EnableAMIENASupport {
// Set EnaSupport to true
// As of February 2017, this applies to C5, I3, P2, R4, X1, and m4.16xlarge
registerOpts.EnaSupport = aws.Bool(true)
}

View File

@ -119,7 +119,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
endpointConnectType := PublicEndpoint
if b.isPrivateNetworkCommunication() {
if b.isPublicPrivateNetworkCommunication() && b.isPrivateNetworkCommunication() {
endpointConnectType = PublicEndpointInPrivateNetwork
} else if b.isPrivateNetworkCommunication() {
endpointConnectType = PrivateEndpoint
}
@ -245,6 +247,10 @@ func (b *Builder) writeSSHPrivateKey(ui packer.Ui, debugKeyPath string) {
}
}
func (b *Builder) isPublicPrivateNetworkCommunication() bool {
return DefaultPrivateVirtualNetworkWithPublicIp != b.config.PrivateVirtualNetworkWithPublicIp
}
func (b *Builder) isPrivateNetworkCommunication() bool {
return b.config.VirtualNetworkName != ""
}

View File

@ -37,6 +37,7 @@ const (
DefaultCloudEnvironmentName = "Public"
DefaultImageVersion = "latest"
DefaultUserName = "packer"
DefaultPrivateVirtualNetworkWithPublicIp = false
DefaultVMSize = "Standard_A1"
)
@ -75,6 +76,8 @@ type Config struct {
ManagedImageResourceGroupName string `mapstructure:"managed_image_resource_group_name"`
ManagedImageName string `mapstructure:"managed_image_name"`
ManagedImageStorageAccountType string `mapstructure:"managed_image_storage_account_type"`
managedImageStorageAccountType compute.StorageAccountTypes
manageImageLocation string
// Deployment
@ -86,6 +89,7 @@ type Config struct {
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"`
VirtualNetworkName string `mapstructure:"virtual_network_name"`
VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"`
VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"`
@ -403,6 +407,10 @@ func provideDefaultValues(c *Config) {
c.VMSize = DefaultVMSize
}
if c.ManagedImageStorageAccountType == "" {
c.managedImageStorageAccountType = compute.StandardLRS
}
if c.ImagePublisher != "" && c.ImageVersion == "" {
c.ImageVersion = DefaultImageVersion
}
@ -526,6 +534,10 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Specify either a VHD (image_url), Image Reference (image_publisher, image_offer, image_sku) or a Managed Disk (custom_managed_disk_image_name, custom_managed_disk_resource_group_name"))
}
if isImageUrl && c.ManagedImageResourceGroupName != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A managed image must be created from a managed image, it cannot be created from a VHD."))
}
if c.ImageUrl == "" && c.CustomManagedImageName == "" {
if c.ImagePublisher == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("An image_publisher must be specified"))
@ -596,4 +608,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The os_type %q is invalid", c.OSType))
}
switch c.ManagedImageStorageAccountType {
case "", string(compute.StandardLRS):
c.managedImageStorageAccountType = compute.StandardLRS
case string(compute.PremiumLRS):
c.managedImageStorageAccountType = compute.PremiumLRS
default:
errs = packer.MultiErrorAppend(errs, fmt.Errorf("The managed_image_storage_account_type %q is invalid", c.ManagedImageStorageAccountType))
}
}

View File

@ -9,6 +9,7 @@ import (
"testing"
"time"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/hashicorp/packer/builder/azure/common/constants"
"github.com/hashicorp/packer/packer"
)
@ -48,6 +49,10 @@ func TestConfigShouldProvideReasonableDefaultValues(t *testing.T) {
if c.ObjectID != "" {
t.Errorf("Expected 'ObjectID' to be nil, but it was '%s'!", c.ObjectID)
}
if c.managedImageStorageAccountType == "" {
t.Errorf("Expected 'managedImageStorageAccountType' to be populated, but it was empty!")
}
}
func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
@ -56,6 +61,7 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
builderValues["ssh_username"] = "override_username"
builderValues["vm_size"] = "override_vm_size"
builderValues["communicator"] = "ssh"
builderValues["managed_image_storage_account_type"] = "Premium_LRS"
c, _, err := newConfig(builderValues, getPackerConfiguration())
@ -64,23 +70,27 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
}
if c.Password != "override_password" {
t.Errorf("Expected 'Password' to be set to 'override_password', but found '%s'!", c.Password)
t.Errorf("Expected 'Password' to be set to 'override_password', but found %q!", c.Password)
}
if c.Comm.SSHPassword != "override_password" {
t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found '%s'!", c.Comm.SSHPassword)
t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found %q!", c.Comm.SSHPassword)
}
if c.UserName != "override_username" {
t.Errorf("Expected 'UserName' to be set to 'override_username', but found '%s'!", c.UserName)
t.Errorf("Expected 'UserName' to be set to 'override_username', but found %q!", c.UserName)
}
if c.Comm.SSHUsername != "override_username" {
t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found '%s'!", c.Comm.SSHUsername)
t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found %q!", c.Comm.SSHUsername)
}
if c.VMSize != "override_vm_size" {
t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found '%s'!", c.VMSize)
t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found %q!", c.VMSize)
}
if c.managedImageStorageAccountType != compute.PremiumLRS {
t.Errorf("Expected 'managed_image_storage_account_type' to be set to 'Premium_LRS', but found %q!", c.managedImageStorageAccountType)
}
}
@ -782,6 +792,26 @@ func TestConfigShouldRejectVhdAndManagedImageOutput(t *testing.T) {
}
}
// If the user specified a build of a VHD, but started with a managed image it should be rejected.
func TestConfigShouldRejectManagedImageSourceAndVhdOutput(t *testing.T) {
config := map[string]interface{}{
"image_url": "ignore",
"location": "ignore",
"subscription_id": "ignore",
"communicator": "none",
"managed_image_resource_group_name": "ignore",
"managed_image_name": "ignore",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject VHD and Managed Image build")
}
}
func TestConfigShouldRejectCustomAndPlatformManagedImageBuild(t *testing.T) {
config := map[string]interface{}{
"custom_managed_image_resource_group_name": "ignore",
@ -826,6 +856,52 @@ func TestConfigShouldRejectCustomAndImageUrlForManagedImageBuild(t *testing.T) {
}
}
func TestConfigShouldRejectMalformedManageImageStorageAccountTypes(t *testing.T) {
config := map[string]interface{}{
"custom_managed_image_resource_group_name": "ignore",
"custom_managed_image_name": "ignore",
"location": "ignore",
"subscription_id": "ignore",
"communicator": "none",
"managed_image_resource_group_name": "ignore",
"managed_image_name": "ignore",
"managed_image_storage_account_type": "--invalid--",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
_, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Fatal("expected config to reject custom and platform input for a managed image build")
}
}
func TestConfigShouldAcceptManagedImageStorageAccountTypes(t *testing.T) {
config := map[string]interface{}{
"custom_managed_image_resource_group_name": "ignore",
"custom_managed_image_name": "ignore",
"location": "ignore",
"subscription_id": "ignore",
"communicator": "none",
"managed_image_resource_group_name": "ignore",
"managed_image_name": "ignore",
// Does not matter for this test case, just pick one.
"os_type": constants.Target_Linux,
}
storage_account_types := []string{"Premium_LRS", "Standard_LRS"}
for _, x := range storage_account_types {
config["managed_image_storage_account_type"] = x
_, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatalf("expected config to accept a managed_image_storage_account_type of %q", x)
}
}
}
func getArmBuilderConfiguration() map[string]string {
m := make(map[string]string)
for _, v := range requiredConfigValues {

View File

@ -16,12 +16,14 @@ type EndpointType int
const (
PublicEndpoint EndpointType = iota
PrivateEndpoint
PublicEndpointInPrivateNetwork
)
var (
EndpointCommunicationText = map[EndpointType]string{
PublicEndpoint: "PublicEndpoint",
PrivateEndpoint: "PrivateEndpoint",
PublicEndpointInPrivateNetwork: "PublicEndpointInPrivateNetwork",
}
)
@ -46,6 +48,8 @@ func NewStepGetIPAddress(client *AzureClient, ui packer.Ui, endpoint EndpointTyp
step.get = step.getPrivateIP
case PublicEndpoint:
step.get = step.getPublicIP
case PublicEndpointInPrivateNetwork:
step.get = step.getPublicIPInPrivateNetwork
}
return step
@ -70,6 +74,11 @@ func (s *StepGetIPAddress) getPublicIP(resourceGroupName string, ipAddressName s
return *resp.IPAddress, nil
}
func (s *StepGetIPAddress) getPublicIPInPrivateNetwork(resourceGroupName string, ipAddressName string, interfaceName string) (string, error) {
s.getPrivateIP(resourceGroupName, ipAddressName, interfaceName)
return s.getPublicIP(resourceGroupName, ipAddressName, interfaceName)
}
func (s *StepGetIPAddress) Run(state multistep.StateBag) multistep.StepAction {
s.say("Getting the VM's IP address ...")

View File

@ -12,9 +12,12 @@ import (
)
func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
for _, endpoint := range endpoints {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", fmt.Errorf("!! Unit Test FAIL !!") },
endpoint: PublicEndpoint,
endpoint: endpoint,
say: func(message string) {},
error: func(e error) {},
}
@ -30,11 +33,15 @@ func TestStepGetIPAddressShouldFailIfGetFails(t *testing.T) {
t.Fatalf("Expected the step to set stateBag['%s'], but it was not.", constants.Error)
}
}
}
func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
for _, endpoint := range endpoints {
var testSubject = &StepGetIPAddress{
get: func(string, string, string) (string, error) { return "", nil },
endpoint: PublicEndpoint,
endpoint: endpoint,
say: func(message string) {},
error: func(e error) {},
}
@ -50,12 +57,15 @@ func TestStepGetIPAddressShouldPassIfGetPasses(t *testing.T) {
t.Fatalf("Expected the step to not set stateBag['%s'], but it was.", constants.Error)
}
}
}
func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
var actualResourceGroupName string
var actualIPAddressName string
var actualNicName string
endpoints := []EndpointType{PublicEndpoint, PublicEndpointInPrivateNetwork}
for _, endpoint := range endpoints {
var testSubject = &StepGetIPAddress{
get: func(resourceGroupName string, ipAddressName string, nicName string) (string, error) {
actualResourceGroupName = resourceGroupName
@ -64,7 +74,7 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
return "127.0.0.1", nil
},
endpoint: PublicEndpoint,
endpoint: endpoint,
say: func(message string) {},
error: func(e error) {},
}
@ -101,6 +111,7 @@ func TestStepGetIPAddressShouldTakeStepArgumentsFromStateBag(t *testing.T) {
t.Fatalf("Expected the value of stateBag[%s] to be '127.0.0.1', but got '%s'.", constants.SSHHost, expectedIPAddress)
}
}
}
func createTestStateBagStepGetIPAddress() multistep.StateBag {
stateBag := new(multistep.BasicStateBag)

View File

@ -53,7 +53,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
if config.ImageUrl != "" {
builder.SetImageUrl(config.ImageUrl, osType)
} else if config.CustomManagedImageName != "" {
builder.SetManagedDiskUrl(config.customManagedImageID)
builder.SetManagedDiskUrl(config.customManagedImageID, config.managedImageStorageAccountType)
} else if config.ManagedImageName != "" && config.ImagePublisher != "" {
imageID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/ArtifactTypes/vmimage/offers/%s/skus/%s/versions/%s",
config.SubscriptionID,
@ -63,7 +63,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
config.ImageSku,
config.ImageVersion)
builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID)
builder.SetManagedMarketplaceImage(config.Location, config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion, imageID, config.managedImageStorageAccountType)
} else {
builder.SetMarketPlaceImage(config.ImagePublisher, config.ImageOffer, config.ImageSku, config.ImageVersion)
}
@ -76,7 +76,12 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
builder.SetCustomData(config.customData)
}
if config.VirtualNetworkName != "" {
if config.VirtualNetworkName != "" && DefaultPrivateVirtualNetworkWithPublicIp != config.PrivateVirtualNetworkWithPublicIp {
builder.SetPrivateVirtualNetworWithPublicIp(
config.VirtualNetworkResourceGroupName,
config.VirtualNetworkName,
config.VirtualNetworkSubnetName)
} else if config.VirtualNetworkName != "" {
builder.SetVirtualNetwork(
config.VirtualNetworkResourceGroupName,
config.VirtualNetworkName,

View File

@ -129,6 +129,9 @@
"osDisk": {
"caching": "ReadWrite",
"createOption": "fromImage",
"managedDisk": {
"storageAccountType": "Standard_LRS"
},
"name": "osdisk",
"osType": "Linux"
}

View File

@ -132,6 +132,9 @@
"osDisk": {
"caching": "ReadWrite",
"createOption": "fromImage",
"managedDisk": {
"storageAccountType": "Standard_LRS"
},
"name": "osdisk",
"osType": "Linux"
}

View File

@ -0,0 +1,144 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('publicIPAddressApiVersion')]",
"location": "[variables('location')]",
"name": "[variables('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('networkInterfacesApiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]"
],
"location": "[variables('location')]",
"name": "[variables('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"linuxConfiguration": {
"ssh": {
"publicKeys": [
{
"keyData": "",
"path": "[variables('sshKeyPath')]"
}
]
}
}
},
"storageProfile": {
"imageReference": {
"offer": "--image-offer--",
"publisher": "--image-publisher--",
"sku": "--image-sku--",
"version": "--version--"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "fromImage",
"managedDisk": {
"storageAccountType": "Standard_LRS"
},
"name": "osdisk",
"osType": "Linux"
}
}
},
"type": "Microsoft.Compute/virtualMachines"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2017-03-30",
"location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01",
"nicName": "packerNic",
"publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressName": "packerPublicIP",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "--virtual_network_subnet_name--",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "--virtual_network_name--",
"virtualNetworkResourceGroup": "--virtual_network_resource_group_name--",
"virtualNetworksApiVersion": "2017-04-01",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -318,6 +318,43 @@ func TestVirtualMachineDeployment09(t *testing.T) {
}
}
// Ensure the VM template is correct when building with PublicIp and connect to Private Network
func TestVirtualMachineDeployment10(t *testing.T) {
config := map[string]interface{}{
"location": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"image_publisher": "--image-publisher--",
"image_offer": "--image-offer--",
"image_sku": "--image-sku--",
"image_version": "--version--",
"virtual_network_resource_group_name": "--virtual_network_resource_group_name--",
"virtual_network_name": "--virtual_network_name--",
"virtual_network_subnet_name": "--virtual_network_subnet_name--",
"private_virtual_network_with_public_ip": true,
"managed_image_name": "ManagedImageName",
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the link values are not set, and the concrete values are set.
func TestKeyVaultDeployment00(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())

View File

@ -45,6 +45,7 @@ type OSDiskUnion struct {
Caching compute.CachingTypes `json:"caching,omitempty"`
CreateOption compute.DiskCreateOptionTypes `json:"createOption,omitempty"`
DiskSizeGB *int32 `json:"diskSizeGB,omitempty"`
ManagedDisk *compute.ManagedDiskParameters `json:"managedDisk,omitempty"`
}
// Union of the StorageProfile and ImageStorageProfile types.

View File

@ -101,7 +101,7 @@ func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string)
return nil
}
func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string) error {
func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string, storageAccountType compute.StorageAccountTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
@ -115,11 +115,14 @@ func (s *TemplateBuilder) SetManagedDiskUrl(managedImageId string) error {
profile.OsDisk.OsType = s.osType
profile.OsDisk.CreateOption = compute.FromImage
profile.OsDisk.Vhd = nil
profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{
StorageAccountType: storageAccountType,
}
return nil
}
func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string) error {
func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer, sku, version, imageID string, storageAccountType compute.StorageAccountTypes) error {
resource, err := s.getResourceByType(resourceVirtualMachine)
if err != nil {
return err
@ -137,6 +140,9 @@ func (s *TemplateBuilder) SetManagedMarketplaceImage(location, publisher, offer,
profile.OsDisk.OsType = s.osType
profile.OsDisk.CreateOption = compute.FromImage
profile.OsDisk.Vhd = nil
profile.OsDisk.ManagedDisk = &compute.ManagedDiskParameters{
StorageAccountType: storageAccountType,
}
return nil
}
@ -219,6 +225,24 @@ func (s *TemplateBuilder) SetVirtualNetwork(virtualNetworkResourceGroup, virtual
return nil
}
func (s *TemplateBuilder) SetPrivateVirtualNetworWithPublicIp(virtualNetworkResourceGroup, virtualNetworkName, subnetName string) error {
s.setVariable("virtualNetworkResourceGroup", virtualNetworkResourceGroup)
s.setVariable("virtualNetworkName", virtualNetworkName)
s.setVariable("subnetName", subnetName)
s.deleteResourceByType(resourceVirtualNetworks)
resource, err := s.getResourceByType(resourceNetworkInterfaces)
if err != nil {
return err
}
s.deleteResourceDependency(resource, func(s string) bool {
return strings.Contains(s, "Microsoft.Network/virtualNetworks")
})
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 {
return nil

View File

@ -63,7 +63,7 @@ func TestBMPString(t *testing.T) {
// some character outside the BMP should error
tst = "\U0001f000 East wind (Mahjong)"
str, err = bmpString(tst)
_, err = bmpString(tst)
if err == nil {
t.Errorf("expected '%s' to throw error because the first character is not in the BMP", tst)
}

View File

@ -21,7 +21,6 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
k := deriveKeyByAlg[algorithmName](params.Salt, password, params.Iterations)
iv := deriveIVByAlg[algorithmName](params.Salt, password, params.Iterations)
password = nil
code, err := blockcodeByAlg[algorithmName](k)
if err != nil {
@ -34,7 +33,6 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) {
cbc, err := pbDecrypterFor(info.GetAlgorithm(), password)
password = nil
if err != nil {
return nil, err
}

View File

@ -113,7 +113,6 @@ func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID
for len(P) < times*v {
P = append(P, password...)
}
password = nil
P = P[:times*v]
}

View File

@ -68,7 +68,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&communicator.StepConnect{
Config: &b.config.Comm,
Host: commHost,
SSHConfig: sshConfig,
SSHConfig: SSHConfig(
b.config.Comm.SSHAgentAuth,
b.config.Comm.SSHUsername,
b.config.Comm.SSHPassword),
},
&common.StepProvision{},
&stepShutdownInstance{},

View File

@ -79,10 +79,29 @@ func NewConfig(raws ...interface{}) (*Config, error) {
var errs *packer.MultiError
// Set some defaults.
if c.APIURL == "" {
// Default to environment variable for api_url, if it exists
c.APIURL = os.Getenv("CLOUDSTACK_API_URL")
}
if c.APIKey == "" {
// Default to environment variable for api_key, if it exists
c.APIKey = os.Getenv("CLOUDSTACK_API_KEY")
}
if c.SecretKey == "" {
// Default to environment variable for secret_key, if it exists
c.SecretKey = os.Getenv("CLOUDSTACK_SECRET_KEY")
}
if c.AsyncTimeout == 0 {
c.AsyncTimeout = 30 * time.Minute
}
if len(c.CIDRList) == 0 && !c.UseLocalIPAddress {
c.CIDRList = []string{"0.0.0.0/0"}
}
if c.InstanceName == "" {
c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
}
@ -114,10 +133,6 @@ func NewConfig(raws ...interface{}) (*Config, error) {
errs = packer.MultiErrorAppend(errs, errors.New("a secret_key must be specified"))
}
if len(c.CIDRList) == 0 && !c.UseLocalIPAddress {
errs = packer.MultiErrorAppend(errs, errors.New("a cidr_list must be specified"))
}
if c.Network == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a network must be specified"))
}

View File

@ -38,7 +38,7 @@ func TestNewConfig(t *testing.T) {
"source_template": "d31e6af5-94a8-4756-abf3-6493c38db7e5",
},
Nullify: "cidr_list",
Err: true,
Err: false,
},
"no_cidr_list_with_use_local_ip_address": {
Config: map[string]interface{}{

View File

@ -2,12 +2,14 @@ package cloudstack
import (
"fmt"
"io/ioutil"
"net"
"os"
packerssh "github.com/hashicorp/packer/communicator/ssh"
"github.com/mitchellh/multistep"
"github.com/xanzy/go-cloudstack/cloudstack"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func commHost(state multistep.StateBag) (string, error) {
@ -26,32 +28,54 @@ func commHost(state multistep.StateBag) (string, error) {
return config.hostAddress, nil
}
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
config := state.Get("config").(*Config)
func SSHConfig(useAgent bool, username, password string) func(state multistep.StateBag) (*ssh.ClientConfig, error) {
return func(state multistep.StateBag) (*ssh.ClientConfig, error) {
if useAgent {
authSock := os.Getenv("SSH_AUTH_SOCK")
if authSock == "" {
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
}
clientConfig := &ssh.ClientConfig{
User: config.Comm.SSHUsername,
sshAgent, err := net.Dial("unix", authSock)
if err != nil {
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(config.Comm.SSHPassword),
ssh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}, nil
}
if config.Comm.SSHPrivateKey != "" {
privateKey, err := ioutil.ReadFile(config.Comm.SSHPrivateKey)
if err != nil {
return nil, fmt.Errorf("Error loading configured private key file: %s", err)
}
privateKey, hasKey := state.GetOk("privateKey")
signer, err := ssh.ParsePrivateKey(privateKey)
if hasKey {
signer, err := ssh.ParsePrivateKey([]byte(privateKey.(string)))
if err != nil {
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
}
clientConfig.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}, nil
return clientConfig, nil
} else {
return &ssh.ClientConfig{
User: username,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{
ssh.Password(password),
ssh.KeyboardInteractive(
packerssh.PasswordKeyboardInteractive(password)),
}}, nil
}
}
}

View File

@ -47,7 +47,9 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
// Retrieve the instance ID from the previously saved state.
instanceID, ok := state.Get("instance_id").(string)
if !ok || instanceID == "" {
state.Put("error", fmt.Errorf("Could not retrieve instance_id from state!"))
err := fmt.Errorf("Could not retrieve instance_id from state!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -56,7 +58,9 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
cloudstack.WithProject(config.Project),
)
if err != nil {
state.Put("error", fmt.Errorf("Failed to retrieve the network object: %s", err))
err := fmt.Errorf("Failed to retrieve the network object: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -79,7 +83,9 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
// Associate a new public IP address.
ipAddr, err := client.Address.AssociateIpAddress(p)
if err != nil {
state.Put("error", fmt.Errorf("Failed to associate public IP address: %s", err))
err := fmt.Errorf("Failed to associate public IP address: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -107,7 +113,10 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
// Create the port forward.
forward, err := client.Firewall.CreatePortForwardingRule(p)
if err != nil {
ui.Error(fmt.Sprintf("Failed to create port forward: %s", err))
err := fmt.Errorf("Failed to create port forward: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Store the port forward ID.
@ -117,7 +126,9 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
ui.Message("Creating network ACL rule...")
if network.Aclid == "" {
state.Put("error", fmt.Errorf("Failed to configure the firewall: no ACL connected to the VPC network"))
err := fmt.Errorf("Failed to configure the firewall: no ACL connected to the VPC network")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -135,7 +146,9 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
// Create the network ACL rule.
aclRule, err := client.NetworkACL.CreateNetworkACL(p)
if err != nil {
state.Put("error", fmt.Errorf("Failed to create network ACL rule: %s", err))
err := fmt.Errorf("Failed to create network ACL rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -154,7 +167,9 @@ func (s *stepSetupNetworking) Run(state multistep.StateBag) multistep.StepAction
fwRule, err := client.Firewall.CreateFirewallRule(p)
if err != nil {
state.Put("error", fmt.Errorf("Failed to create firewall rule: %s", err))
err := fmt.Errorf("Failed to create firewall rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

View File

@ -62,7 +62,9 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
// Retrieve the zone object.
zone, _, err := client.Zone.GetZoneByID(config.Zone)
if err != nil {
err := fmt.Errorf("Failed to get zone %s by ID: %s", config.Zone, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -80,7 +82,9 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
httpPort := state.Get("http_port").(uint)
httpIP, err := hostIP()
if err != nil {
err := fmt.Errorf("Failed to determine host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
common.SetHTTPIP(httpIP)
@ -93,6 +97,7 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
ud, err := s.generateUserData(config.UserData, config.HTTPGetOnly)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -102,7 +107,9 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
// Create the new instance.
instance, err := client.VirtualMachine.DeployVirtualMachine(p)
if err != nil {
state.Put("error", fmt.Errorf("Error creating new instance %s: %s", config.InstanceName, err))
err := fmt.Errorf("Error creating new instance %s: %s", config.InstanceName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

View File

@ -21,7 +21,9 @@ func (s *stepCreateTemplate) Run(state multistep.StateBag) multistep.StepAction
// Retrieve the instance ID from the previously saved state.
instanceID, ok := state.Get("instance_id").(string)
if !ok || instanceID == "" {
state.Put("error", fmt.Errorf("Could not retrieve instance_id from state!"))
err := fmt.Errorf("Could not retrieve instance_id from state!")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -51,6 +53,7 @@ func (s *stepCreateTemplate) Run(state multistep.StateBag) multistep.StepAction
volumeID, err := getRootVolumeID(client, instanceID)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
@ -60,7 +63,9 @@ func (s *stepCreateTemplate) Run(state multistep.StateBag) multistep.StepAction
ui.Message("Creating the new template...")
template, err := client.Template.CreateTemplate(p)
if err != nil {
state.Put("error", fmt.Errorf("Error creating the new template %s: %s", config.TemplateName, err))
err := fmt.Errorf("Error creating the new template %s: %s", config.TemplateName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

View File

@ -22,6 +22,15 @@ func (s *stepPrepareConfig) Run(state multistep.StateBag) multistep.StepAction {
var err error
var errs *packer.MultiError
if config.Comm.SSHPrivateKey != "" {
privateKey, err := ioutil.ReadFile(config.Comm.SSHPrivateKey)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error loading configured private key file: %s", err))
}
state.Put("privateKey", privateKey)
}
// First get the project and zone UUID's so we can use them in other calls when needed.
if config.Project != "" && !isUUID(config.Project) {
config.Project, _, err = client.Project.GetProjectID(config.Project)
@ -134,6 +143,7 @@ func (s *stepPrepareConfig) Run(state multistep.StateBag) multistep.StepAction {
// nil interface.
if errs != nil && len(errs.Errors) > 0 {
state.Put("error", errs)
ui.Error(errs.Error())
return multistep.ActionHalt
}

View File

@ -30,7 +30,9 @@ func (s *stepShutdownInstance) Run(state multistep.StateBag) multistep.StepActio
// Shutdown the virtual machine.
_, err := client.VirtualMachine.StopVirtualMachine(p)
if err != nil {
state.Put("error", fmt.Errorf("Error shutting down instance %s: %s", config.InstanceName, err))
err := fmt.Errorf("Error shutting down instance %s: %s", config.InstanceName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

View File

@ -85,12 +85,12 @@ func (s *stepSnapshot) Run(state multistep.StateBag) multistep.StepAction {
}
imageTransfer, _, err := client.ImageActions.Transfer(context.TODO(), images[0].ID, transferRequest)
if err != nil {
err := fmt.Errorf("Error transfering snapshot: %s", err)
err := fmt.Errorf("Error transferring snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Transfering Snapshot ID: %d", imageTransfer.ID))
ui.Say(fmt.Sprintf("transferring Snapshot ID: %d", imageTransfer.ID))
if err := waitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
client, 20*time.Minute); err != nil {
// If we get an error the first time, actually report it

View File

@ -60,40 +60,85 @@ func (c *Communicator) Start(remote *packer.RemoteCmd) error {
return nil
}
// Upload uploads a file to the docker container
func (c *Communicator) Upload(dst string, src io.Reader, fi *os.FileInfo) error {
if fi == nil {
return c.uploadReader(dst, src)
}
return c.uploadFile(dst, src, fi)
}
// uploadReader writes an io.Reader to a temporary file before uploading
func (c *Communicator) uploadReader(dst string, src io.Reader) error {
// Create a temporary file to store the upload
tempfile, err := ioutil.TempFile(c.HostDir, "upload")
if err != nil {
return err
return fmt.Errorf("Failed to open temp file for writing: %s", err)
}
defer os.Remove(tempfile.Name())
defer tempfile.Close()
// Copy the contents to the temporary file
_, err = io.Copy(tempfile, src)
if _, err := io.Copy(tempfile, src); err != nil {
return fmt.Errorf("Failed to copy upload file to tempfile: %s", err)
}
tempfile.Seek(0, 0)
fi, err := tempfile.Stat()
if err != nil {
return fmt.Errorf("Error getting tempfile info: %s", err)
}
return c.uploadFile(dst, tempfile, &fi)
}
// uploadFile uses docker cp to copy the file from the host to the container
func (c *Communicator) uploadFile(dst string, src io.Reader, fi *os.FileInfo) error {
// command format: docker cp /path/to/infile containerid:/path/to/outfile
log.Printf("Copying to %s on container %s.", dst, c.ContainerId)
localCmd := exec.Command("docker", "cp", "-",
fmt.Sprintf("%s:%s", c.ContainerId, filepath.Dir(dst)))
stderrP, err := localCmd.StderrPipe()
if err != nil {
return fmt.Errorf("Failed to open pipe: %s", err)
}
stdin, err := localCmd.StdinPipe()
if err != nil {
return fmt.Errorf("Failed to open pipe: %s", err)
}
if err := localCmd.Start(); err != nil {
return err
}
archive := tar.NewWriter(stdin)
header, err := tar.FileInfoHeader(*fi, "")
if err != nil {
return err
}
header.Name = filepath.Base(dst)
archive.WriteHeader(header)
numBytes, err := io.Copy(archive, src)
if err != nil {
return fmt.Errorf("Failed to pipe upload: %s", err)
}
log.Printf("Copied %d bytes for %s", numBytes, dst)
if err := archive.Close(); err != nil {
return fmt.Errorf("Failed to close archive: %s", err)
}
if err := stdin.Close(); err != nil {
return fmt.Errorf("Failed to close stdin: %s", err)
}
stderrOut, err := ioutil.ReadAll(stderrP)
if err != nil {
return err
}
if fi != nil {
tempfile.Chmod((*fi).Mode())
}
tempfile.Close()
// Copy the file into place by copying the temporary file we put
// into the shared folder into the proper location in the container
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("command cp %s/%s %s", c.ContainerDir,
filepath.Base(tempfile.Name()), dst),
}
if err := c.Start(cmd); err != nil {
return err
}
// Wait for the copy to complete
cmd.Wait()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Upload failed with non-zero exit status: %d", cmd.ExitStatus)
if err := localCmd.Wait(); err != nil {
return fmt.Errorf("Failed to upload to '%s' in container: %s. %s.", dst, stderrOut, err)
}
return nil
@ -173,7 +218,7 @@ func (c *Communicator) UploadDir(dst string, src string, exclude []string) error
// Make the directory, then copy into it
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("set -e; mkdir -p %s; cd %s; command cp -R `ls -A .` %s",
Command: fmt.Sprintf("set -e; mkdir -p %s; command cp -R %s/. %s",
containerDst, containerSrc, containerDst),
}
if err := c.Start(cmd); err != nil {

View File

@ -71,6 +71,7 @@ func TestUploadDownload(t *testing.T) {
upload,
download,
},
ProvisionerTypes: []string{"", ""},
},
}
hook := &packer.DispatchHook{Mapping: hooks}
@ -161,6 +162,7 @@ func TestLargeDownload(t *testing.T) {
downloadCupcake,
downloadBigcake,
},
ProvisionerTypes: []string{"", "", ""},
},
}
hook := &packer.DispatchHook{Mapping: hooks}

View File

@ -35,6 +35,7 @@ type Config struct {
Author string
Changes []string
Message string
ContainerDir string `mapstructure:"container_dir"`
// This is used to login to dockerhub to pull a private base container. For
// pushing to dockerhub, see the docker post-processors
@ -112,6 +113,10 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
}
}
if c.ContainerDir == "" {
c.ContainerDir = "/packer-files"
}
if c.EcrLogin && c.LoginServer == "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("ECR login requires login server to be provided."))
}

View File

@ -24,7 +24,7 @@ func (s *StepConnectDocker) Run(state multistep.StateBag) multistep.StepAction {
comm := &Communicator{
ContainerId: containerId,
HostDir: tempDir,
ContainerDir: "/packer-files",
ContainerDir: config.ContainerDir,
Version: version,
Config: config,
}

View File

@ -26,7 +26,7 @@ func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
for host, container := range config.Volumes {
runConfig.Volumes[host] = container
}
runConfig.Volumes[tempDir] = "/packer-files"
runConfig.Volumes[tempDir] = config.ContainerDir
ui.Say("Starting docker container...")
containerId, err := driver.StartContainer(&runConfig)

View File

@ -21,7 +21,7 @@ GetMetadata () {
echo "$(curl -f -H "Metadata-Flavor: Google" ${BASEMETADATAURL}/${1} 2> /dev/null)"
}
ZONE=$(GetMetadata zone | grep -oP "[^/]*$")
ZONE=$(basename $(GetMetadata zone))
SetMetadata () {
gcloud compute instances add-metadata ${HOSTNAME} --metadata ${1}=${2} --zone ${ZONE}

View File

@ -229,10 +229,6 @@ func TestStepCreateInstance_errorTimeout(t *testing.T) {
state.Put("ssh_public_key", "key")
errCh := make(chan error, 1)
go func() {
<-time.After(10 * time.Millisecond)
errCh <- nil
}()
config := state.Get("config").(*Config)
config.stateTimeout = 1 * time.Microsecond

View File

@ -93,6 +93,7 @@ type Config struct {
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",

View File

@ -13,7 +13,8 @@ import (
"strings"
"time"
"gopkg.in/xmlpath.v2"
"github.com/ChrisTrenkamp/goxpath"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)
// Parallels9Driver is a base type for Parallels builders.
@ -78,13 +79,19 @@ func getConfigValueFromXpath(path, xpath string) (string, error) {
if err != nil {
return "", err
}
xpathComp := xmlpath.MustCompile(xpath)
root, err := xmlpath.Parse(file)
doc, err := xmltree.ParseXML(file)
if err != nil {
return "", err
}
value, _ := xpathComp.String(root)
return value, nil
xpExec := goxpath.MustParse(xpath)
node, err := xpExec.Exec(doc)
if err != nil {
return "", err
}
return node.String(), nil
}
// Finds an application bundle by identifier (for "darwin" platform only)

View File

@ -58,3 +58,29 @@ func TestIPAddress(t *testing.T) {
t.Fatalf("Should have found 10.211.55.124, not %s!\n", ip)
}
}
func TestXMLParseConfig(t *testing.T) {
td, err := ioutil.TempDir("", "configpvs")
if err != nil {
t.Fatalf("Error creating temp file: %s", err)
}
defer os.Remove(td)
config := []byte(`
<ExampleParallelsConfig>
<SystemConfig>
<DiskSize>20</DiskSize>
</SystemConfig>
</ExampleParallelsConfig>
`)
ioutil.WriteFile(td+"/config.pvs", config, 0666)
result, err := getConfigValueFromXpath(td, "//DiskSize")
if err != nil {
t.Fatalf("Error parsing XML: %s", err)
}
if result != "20" {
t.Fatalf("Expected %q, got %q", "20", result)
}
}

View File

@ -27,7 +27,6 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction {
"create", name,
"--distribution", config.GuestOSType,
"--dst", config.OutputDir,
"--vmtype", "vm",
"--no-hdd",
}

View File

@ -76,7 +76,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
}
if c.PBUrl == "" {
c.PBUrl = "https://api.profitbricks.com/rest/v2"
c.PBUrl = "https://api.profitbricks.com/cloudapi/v4"
}
if c.Cores == 0 {

View File

@ -26,16 +26,18 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
}
ui.Say("Creating Virtual Data Center...")
img := s.getImageId(c.Image, c)
alias := ""
if img == "" {
alias = s.getImageAlias(c.Image, c.Region, ui)
}
datacenter := profitbricks.Datacenter{
Properties: profitbricks.DatacenterProperties{
Name: c.SnapshotName,
Location: c.Region,
},
Entities: profitbricks.DatacenterEntities{
Servers: &profitbricks.Servers{
Items: []profitbricks.Server{
{
}
server := profitbricks.Server{
Properties: profitbricks.ServerProperties{
Name: c.SnapshotName,
Ram: c.Ram,
@ -49,23 +51,20 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
Type: c.DiskType,
Size: c.DiskSize,
Name: c.SnapshotName,
ImageAlias: alias,
Image: img,
},
},
},
},
},
},
},
},
},
}
if c.SSHKey != "" {
datacenter.Entities.Servers.Items[0].Entities.Volumes.Items[0].Properties.SshKeys = []string{c.SSHKey}
server.Entities.Volumes.Items[0].Properties.SshKeys = []string{c.SSHKey}
}
if c.Comm.SSHPassword != "" {
datacenter.Entities.Servers.Items[0].Entities.Volumes.Items[0].Properties.ImagePassword = c.Comm.SSHPassword
server.Entities.Volumes.Items[0].Properties.ImagePassword = c.Comm.SSHPassword
}
datacenter = profitbricks.CompositeCreateDatacenter(datacenter)
@ -94,8 +93,20 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
state.Put("datacenter_id", datacenter.Id)
lan := profitbricks.CreateLan(datacenter.Id, profitbricks.Lan{
Properties: profitbricks.LanProperties{
server = profitbricks.CreateServer(datacenter.Id, server)
if server.StatusCode > 299 {
ui.Error(fmt.Sprintf("Error occurred %s", parseErrorMessage(server.Response)))
return multistep.ActionHalt
}
err = s.waitTillProvisioned(server.Headers.Get("Location"), *c)
if err != nil {
ui.Error(fmt.Sprintf("Error occurred while creating a server %s", err.Error()))
return multistep.ActionHalt
}
lan := profitbricks.CreateLan(datacenter.Id, profitbricks.CreateLanRequest{
Properties: profitbricks.CreateLanProperties{
Public: true,
Name: c.SnapshotName,
},
@ -113,8 +124,8 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
}
lanId, _ := strconv.Atoi(lan.Id)
nic := profitbricks.CreateNic(datacenter.Id, datacenter.Entities.Servers.Items[0].Id, profitbricks.Nic{
Properties: profitbricks.NicProperties{
nic := profitbricks.CreateNic(datacenter.Id, server.Id, profitbricks.Nic{
Properties: &profitbricks.NicProperties{
Name: c.SnapshotName,
Lan: lanId,
Dhcp: true,
@ -132,9 +143,9 @@ func (s *stepCreateServer) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt
}
state.Put("volume_id", datacenter.Entities.Servers.Items[0].Entities.Volumes.Items[0].Id)
state.Put("volume_id", server.Entities.Volumes.Items[0].Id)
server := profitbricks.GetServer(datacenter.Id, datacenter.Entities.Servers.Items[0].Id)
server = profitbricks.GetServer(datacenter.Id, server.Id)
state.Put("server_ip", server.Entities.Nics.Items[0].Properties.Ips[0])
@ -225,6 +236,25 @@ func (d *stepCreateServer) getImageId(imageName string, c *Config) string {
return ""
}
func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui packer.Ui) string {
if imageAlias == "" {
return ""
}
locations := profitbricks.GetLocation(location)
if len(locations.Properties.ImageAliases) > 0 {
for _, i := range locations.Properties.ImageAliases {
alias := ""
if i != "" {
alias = i
}
if alias != "" && strings.ToLower(alias) == strings.ToLower(imageAlias) {
return alias
}
}
}
return ""
}
func parseErrorMessage(raw string) (toreturn string) {
var tmp map[string]interface{}
json.Unmarshal([]byte(raw), &tmp)

View File

@ -22,7 +22,7 @@ func (s *stepTakeSnapshot) Run(state multistep.StateBag) multistep.StepAction {
dcId := state.Get("datacenter_id").(string)
volumeId := state.Get("volume_id").(string)
snapshot := profitbricks.CreateSnapshot(dcId, volumeId, c.SnapshotName)
snapshot := profitbricks.CreateSnapshot(dcId, volumeId, c.SnapshotName, "")
state.Put("snapshotname", c.SnapshotName)

View File

@ -14,13 +14,14 @@ const BuilderId = "mitchellh.vmware"
// Artifact is the result of running the VMware builder, namely a set
// of files associated with the resulting machine.
type localArtifact struct {
id string
dir string
f []string
}
// NewLocalArtifact returns a VMware artifact containing the files
// in the given directory.
func NewLocalArtifact(dir string) (packer.Artifact, error) {
func NewLocalArtifact(id string, dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
@ -37,6 +38,7 @@ func NewLocalArtifact(dir string) (packer.Artifact, error) {
}
return &localArtifact{
id: id,
dir: dir,
f: files,
}, nil
@ -50,8 +52,8 @@ func (a *localArtifact) Files() []string {
return a.f
}
func (*localArtifact) Id() string {
return "VM"
func (a *localArtifact) Id() string {
return a.id
}
func (a *localArtifact) String() string {

View File

@ -29,7 +29,7 @@ func TestNewLocalArtifact(t *testing.T) {
t.Fatalf("err: %s", err)
}
a, err := NewLocalArtifact(td)
a, err := NewLocalArtifact("vm1", td)
if err != nil {
t.Fatalf("err: %s", err)
}
@ -37,6 +37,9 @@ func TestNewLocalArtifact(t *testing.T) {
if a.BuilderId() != BuilderId {
t.Fatalf("bad: %#v", a.BuilderId())
}
if a.Id() != "vm1" {
t.Fatalf("bad: %#v", a.Id())
}
if len(a.Files()) != 1 {
t.Fatalf("should length 1: %d", len(a.Files()))
}

View File

@ -8,6 +8,7 @@ import (
// of files associated with the resulting machine.
type Artifact struct {
builderId string
id string
dir OutputDir
f []string
}
@ -20,8 +21,8 @@ func (a *Artifact) Files() []string {
return a.f
}
func (*Artifact) Id() string {
return "VM"
func (a *Artifact) Id() string {
return a.id
}
func (a *Artifact) String() string {

View File

@ -349,6 +349,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return &Artifact{
builderId: builderId,
id: b.config.VMName,
dir: dir,
f: files,
}, nil

View File

@ -45,6 +45,7 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
}
if c.RemoteType != "esx5" || s.Format == "" {
ui.Say("Skipping export of virtual machine (export is allowed only for ESXi and the format needs to be specified)...")
return multistep.ActionContinue
}

View File

@ -142,7 +142,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
return nil, errors.New("Build was halted.")
}
return vmwcommon.NewLocalArtifact(b.config.OutputDir)
return vmwcommon.NewLocalArtifact(b.config.VMName, b.config.OutputDir)
}
// Cancel.

View File

@ -51,7 +51,8 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
// Defaults
if c.VMName == "" {
c.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", c.PackerBuildName)
c.VMName = fmt.Sprintf(
"packer-%s-%d", c.PackerBuildName, interpolate.InitTime.Unix())
}
// Prepare the errors

View File

@ -136,7 +136,13 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs [
}
func (c *ISOConfig) parseCheckSumFile(rd *bufio.Reader) error {
errNotFound := fmt.Errorf("No checksum for %q found at: %s", filepath.Base(c.ISOUrls[0]), c.ISOChecksumURL)
u, err := url.Parse(c.ISOUrls[0])
if err != nil {
return err
}
filename := filepath.Base(u.Path)
errNotFound := fmt.Errorf("No checksum for %q found at: %s", filename, c.ISOChecksumURL)
for {
line, err := rd.ReadString('\n')
if err != nil && line == "" {
@ -148,7 +154,7 @@ func (c *ISOConfig) parseCheckSumFile(rd *bufio.Reader) error {
}
if strings.ToLower(parts[0]) == c.ISOChecksumType {
// BSD-style checksum
if parts[1] == fmt.Sprintf("(%s)", filepath.Base(c.ISOUrls[0])) {
if parts[1] == fmt.Sprintf("(%s)", filename) {
c.ISOChecksum = parts[3]
return nil
}
@ -158,7 +164,7 @@ func (c *ISOConfig) parseCheckSumFile(rd *bufio.Reader) error {
// Binary mode
parts[1] = parts[1][1:]
}
if parts[1] == filepath.Base(c.ISOUrls[0]) {
if parts[1] == filename {
c.ISOChecksum = parts[0]
return nil
}

View File

@ -152,6 +152,25 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) {
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
}
// Test good - ISOChecksumURL GNU style with query parameters
i = testISOConfig()
i.ISOChecksum = ""
i.RawSingleISOUrl = "http://www.packer.io/the-OS.iso?stuff=boo"
cs_file, _ = ioutil.TempFile("", "packer-test-")
ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style), 0666)
i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name())
warns, err = i.Prepare(nil)
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}
if i.ISOChecksum != "bar0" {
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
}
}
func TestISOConfigPrepare_ISOChecksumType(t *testing.T) {

View File

@ -2,9 +2,10 @@ package none
import (
"errors"
"github.com/hashicorp/packer/packer"
"io"
"os"
"github.com/hashicorp/packer/packer"
)
type comm struct {

View File

@ -46,8 +46,8 @@ type Config struct {
// Pty, if true, will request a pty from the remote end.
Pty bool
// DisableAgent, if true, will not forward the SSH agent.
DisableAgent bool
// DisableAgentForwarding, if true, will not forward the SSH agent.
DisableAgentForwarding bool
// HandshakeTimeout limits the amount of time we'll wait to handshake before
// saying the connection failed.
@ -327,7 +327,7 @@ func (c *comm) connectToAgent() {
return
}
if c.config.DisableAgent {
if c.config.DisableAgentForwarding {
log.Printf("[INFO] SSH agent forwarding is disabled.")
return
}
@ -693,6 +693,11 @@ func (c *comm) scpSession(scpCommand string, f func(io.Writer, *bufio.Reader) er
// Otherwise, we have an ExitErorr, meaning we can just read
// the exit status
log.Printf("non-zero exit status: %d", exitErr.ExitStatus())
stdoutB, err := ioutil.ReadAll(stdoutR)
if err != nil {
return err
}
log.Printf("scp output: %s", stdoutB)
// If we exited with status 127, it means SCP isn't available.
// Return a more descriptive error for that.

View File

@ -1,7 +1,7 @@
#compdef packer
local -a _packer_cmds
_packer_cmds=(
_packer () {
local -a sub_commands && sub_commands=(
'build:Build image(s) from template'
'fix:Fixes templates from old versions of packer'
'inspect:See components of a template'
@ -10,57 +10,59 @@ _packer_cmds=(
'version:Prints the Packer version'
)
__build() {
_arguments \
'-debug[Debug mode enabled for builds]' \
'-force[Force a build to continue if artifacts exist, deletes existing artifacts]' \
'-machine-readable[Machine-readable output]' \
'-except=[(foo,bar,baz) Build all builds other than these]' \
'-only=[(foo,bar,baz) Only build the given builds by name]' \
'-parallel=[(false) Disable parallelization (on by default)]' \
'-var[("key=value") Variable for templates, can be used multiple times.]' \
'-var-file=[(path) JSON file containing user variables.]'
}
__inspect() {
_arguments \
local -a build_arguments && build_arguments=(
'-debug[Debug mode enabled for builds]'
'-force[Force a build to continue if artifacts exist, deletes existing artifacts]'
'-machine-readable[Machine-readable output]'
}
__push() {
_arguments \
'-name=[(<name>) The destination build in Atlas.]' \
'-token=[(<token>) Access token to use to upload.]' \
'-var[("key=value") Variable for templates, can be used multiple times.]' \
'-except=[(foo,bar,baz) Build all builds other than these]'
'-only=[(foo,bar,baz) Only build the given builds by name]'
'-parallel=[(false) Disable parallelization (on by default)]'
'-var[("key=value") Variable for templates, can be used multiple times.]'
'-var-file=[(path) JSON file containing user variables.]'
}
'(-)*:files:_files -g "*.json"'
)
__validate() {
_arguments \
'-syntax-only[Only check syntax. Do not verify config of the template.]' \
'-except=[(foo,bar,baz) Validate all builds other than these]' \
'-only=[(foo,bar,baz) Validate only these builds]' \
'-var[("key=value") Variable for templates, can be used multiple times.]' \
local -a inspect_arguments && inspect_arguments=(
'-machine-readable[Machine-readable output]'
'(-)*:files:_files -g "*.json"'
)
local -a push_arguments && push_arguments=(
'-name=[(<name>) The destination build in Atlas.]'
'-token=[(<token>) Access token to use to upload.]'
'-var[("key=value") Variable for templates, can be used multiple times.]'
'-var-file=[(path) JSON file containing user variables.]'
}
'(-)*:files:_files -g "*.json"'
)
local -a validate_arguments && validate_arguments=(
'-syntax-only[Only check syntax. Do not verify config of the template.]'
'-except=[(foo,bar,baz) Validate all builds other than these]'
'-only=[(foo,bar,baz) Validate only these builds]'
'-var[("key=value") Variable for templates, can be used multiple times.]'
'-var-file=[(path) JSON file containing user variables.]'
'(-)*:files:_files -g "*.json"'
)
_arguments '*:: :->command'
_arguments -C \
':command:->command' \
'*::options:->options'
if (( CURRENT == 1 )); then
_describe -t commands "packer command" _packer_cmds
return
fi
local -a _command_args
case "$words[1]" in
case $state in
command)
_describe -t commands 'command' sub_commands ;;
options)
case $line[1] in
build)
__build ;;
_arguments -s -S : $build_arguments ;;
inspect)
__inspect ;;
_arguments -s -S : $inspect_arguments ;;
push)
__push ;;
_arguments -s -S : $push_arguments ;;
validate)
__validate ;;
_arguments -s -S : $validate_arguments ;;
esac
;;
esac
}
_packer "$@"

View File

@ -8,8 +8,8 @@
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_basi",
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
"image_name":"packer_basic",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true"
@ -18,7 +18,7 @@
"type": "shell",
"inline": [
"sleep 30",
"apt-get update -yy"
"yum install redis.x86_64 -y"
]
}]
}

View File

@ -9,7 +9,7 @@
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_test",
"source_image":"win2012_64_datactr_r2_en_40G_alibase_20160622.vhd",
"source_image":"win2008r2_64_ent_sp1_zh-cn_40G_alibase_20170622.vhd",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
"image_force_delete":"true",

View File

@ -9,7 +9,7 @@
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_with_data_disk",
"source_image":"ubuntu_16_0402_64_40G_base_20170222.vhd",
"source_image":"centos_7_2_64_40G_base_20170222.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
@ -19,7 +19,7 @@
"type": "shell",
"inline": [
"sleep 30",
"apt-get update -yy"
"yum install redis.x86_64 -y"
]
}]
}

View File

@ -9,7 +9,7 @@
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_chef2",
"source_image":"ubuntu_14_0405_64_40G_base_20170222.vhd",
"source_image":"ubuntu_14_0405_64_40G_alibase_20170625.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.medium",
"io_optimized":"true",

View File

@ -1,4 +1,5 @@
#!/bin/sh
#if the related deb pkg not found, please replace with it other avaiable repository url
HOSTNAME=`ifconfig eth1|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`
if [ not $HOSTNAME ] ; then
HOSTNAME=`ifconfig eth0|grep 'inet addr'|cut -d ":" -f2|cut -d " " -f1`

View File

@ -0,0 +1,31 @@
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_jenkins",
"source_image":"ubuntu_14_0405_64_40G_alibase_20170625.vhd",
"ssh_username":"root",
"instance_type":"ecs.n1.medium",
"io_optimized":"true",
"image_force_delete":"true",
"ssh_password":"Test12345"
}],
"provisioners": [{
"type": "file",
"source": "examples/alicloud/jenkins/jenkins.sh",
"destination": "/root/"
},{
"type": "shell",
"inline": [
"cd /root/",
"chmod 755 jenkins.sh",
"./jenkins.sh"
]
}]
}

View File

@ -0,0 +1,48 @@
#!/bin/sh
JENKINS_URL='http://mirrors.jenkins.io/war-stable/2.32.2/jenkins.war'
TOMCAT_VERSION='7.0.77'
TOMCAT_NAME="apache-tomcat-$TOMCAT_VERSION"
TOMCAT_PACKAGE="$TOMCAT_NAME.tar.gz"
TOMCAT_URL="http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v$TOMCAT_VERSION/bin/$TOMCAT_PACKAGE"
TOMCAT_PATH="/opt/$TOMCAT_NAME"
#install jdk
if grep -Eqi "Ubuntu|Debian|Raspbian" /etc/issue || grep -Eq "Ubuntu|Debian|Raspbian" /etc/*-release; then
sudo apt-get update -y
sudo apt-get install -y openjdk-7-jdk
elif grep -Eqi "CentOS|Fedora|Red Hat Enterprise Linux Server" /etc/issue || grep -Eq "CentOS|Fedora|Red Hat Enterprise Linux Server" /etc/*-release; then
sudo yum update -y
sudo yum install -y openjdk-7-jdk
else
echo "Unknown OS type."
fi
#install jenkins server
mkdir ~/work
cd ~/work
#install tomcat
wget $TOMCAT_URL
tar -zxvf $TOMCAT_PACKAGE
mv $TOMCAT_NAME /opt
#install
wget $JENKINS_URL
mv jenkins.war $TOMCAT_PATH/webapps/
#set emvironment
echo "TOMCAT_PATH=\"$TOMCAT_PATH\"">>/etc/profile
echo "JENKINS_HOME=\"$TOMCAT_PATH/webapps/jenkins\"">>/etc/profile
echo PATH="\"\$PATH:\$TOMCAT_PATH:\$JENKINS_HOME\"">>/etc/profile
. /etc/profile
#start tomcat & jenkins
$TOMCAT_PATH/bin/startup.sh
#set start on boot
sed -i "/#!\/bin\/sh/a$TOMCAT_PATH/bin/startup.sh" /etc/rc.local
#clean
rm -rf ~/work

View File

@ -0,0 +1,60 @@
{"variables": {
"box_basename": "centos-6.8",
"build_timestamp": "{{isotime \"20060102150405\"}}",
"cpus": "1",
"disk_size": "4096",
"git_revision": "__unknown_git_revision__",
"headless": "",
"http_proxy": "{{env `http_proxy`}}",
"https_proxy": "{{env `https_proxy`}}",
"iso_checksum": "0ca12fe5f28c2ceed4f4084b41ff8a0b",
"iso_checksum_type": "md5",
"iso_name": "CentOS-6.8-x86_64-minimal.iso",
"ks_path": "centos-6.8/ks.cfg",
"memory": "512",
"metadata": "floppy/dummy_metadata.json",
"mirror": "http://mirrors.aliyun.com/centos",
"mirror_directory": "6.8/isos/x86_64",
"name": "centos-6.8",
"no_proxy": "{{env `no_proxy`}}",
"template": "centos-6.8-x86_64",
"version": "2.1.TIMESTAMP"
},
"builders":[
{
"boot_command": [
"<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/{{user `ks_path`}}<enter><wait>"
],
"boot_wait": "10s",
"disk_size": "{{user `disk_size`}}",
"headless": "{{ user `headless` }}",
"http_directory": "http",
"iso_checksum": "{{user `iso_checksum`}}",
"iso_checksum_type": "{{user `iso_checksum_type`}}",
"iso_url": "{{user `mirror`}}/{{user `mirror_directory`}}/{{user `iso_name`}}",
"output_directory": "packer-{{user `template`}}-qemu",
"shutdown_command": "echo 'vagrant'|sudo -S /sbin/halt -h -p",
"ssh_password": "vagrant",
"ssh_port": 22,
"ssh_username": "root",
"ssh_wait_timeout": "10000s",
"type": "qemu",
"vm_name": "{{ user `template` }}.raw",
"net_device": "virtio-net",
"disk_interface": "virtio",
"format": "raw"
}
],
"post-processors":[
{
"type":"alicloud-import",
"oss_bucket_name": "packer",
"image_name": "packer_import",
"image_os_type": "linux",
"image_platform": "CentOS",
"image_architecture": "x86_64",
"image_system_size": "40",
"region":"cn-beijing"
}
]
}

View File

@ -0,0 +1,69 @@
install
cdrom
lang en_US.UTF-8
keyboard us
network --bootproto=dhcp
rootpw vagrant
firewall --disabled
selinux --permissive
timezone UTC
unsupported_hardware
bootloader --location=mbr
text
skipx
zerombr
clearpart --all --initlabel
autopart
auth --enableshadow --passalgo=sha512 --kickstart
firstboot --disabled
reboot
user --name=vagrant --plaintext --password vagrant
key --skip
%packages --nobase --ignoremissing --excludedocs
# vagrant needs this to copy initial files via scp
openssh-clients
sudo
kernel-headers
kernel-devel
gcc
make
perl
wget
nfs-utils
-fprintd-pam
-intltool
# unnecessary firmware
-aic94xx-firmware
-atmel-firmware
-b43-openfwwf
-bfa-firmware
-ipw2100-firmware
-ipw2200-firmware
-ivtv-firmware
-iwl100-firmware
-iwl1000-firmware
-iwl3945-firmware
-iwl4965-firmware
-iwl5000-firmware
-iwl5150-firmware
-iwl6000-firmware
-iwl6000g2a-firmware
-iwl6050-firmware
-libertas-usb8388-firmware
-ql2100-firmware
-ql2200-firmware
-ql23xx-firmware
-ql2400-firmware
-ql2500-firmware
-rt61pci-firmware
-rt73usb-firmware
-xorg-x11-drv-ati-firmware
-zd1211-firmware
%post
# Force to set SELinux to a permissive mode
sed -i -e 's/\(^SELINUX=\).*$/\1permissive/' /etc/selinux/config
# sudo
echo "%vagrant ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/vagrant

View File

@ -4,7 +4,8 @@
"client_secret": "{{env `ARM_CLIENT_SECRET`}}",
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}",
"object_id": "{{env `ARM_OJBECT_ID`}}"
},
"builders": [
{
@ -15,6 +16,7 @@
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"object_id": "{{user `object_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",

View File

@ -29,8 +29,10 @@ func init() {
"parallels-headless": new(FixerParallelsHeadless),
"parallels-deprecations": new(FixerParallelsDeprecations),
"sshkeypath": new(FixerSSHKeyPath),
"sshdisableagent": new(FixerSSHDisableAgent),
"manifest-filename": new(FixerManifestFilename),
"amazon-shutdown_behavior": new(FixerAmazonShutdownBehavior),
"enhanced-networking": new(FixerEnhancedNetworking),
}
FixerOrder = []string{
@ -43,7 +45,9 @@ func init() {
"parallels-headless",
"parallels-deprecations",
"sshkeypath",
"sshdisableagent",
"manifest-filename",
"amazon-shutdown_behavior",
"enhanced-networking",
}
}

View File

@ -0,0 +1,45 @@
package fix
import (
"github.com/mitchellh/mapstructure"
)
// FixerEnhancedNetworking is a Fixer that replaces the "enhanced_networking" configuration key
// with the clearer "ena_support". This disambiguates ena_support from sriov_support.
type FixerEnhancedNetworking struct{}
func (FixerEnhancedNetworking) Fix(input map[string]interface{}) (map[string]interface{}, error) {
// Our template type we'll use for this fixer only
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
// Go through each builder and replace the enhanced_networking if we can
for _, builder := range tpl.Builders {
enhancedNetworkingRaw, ok := builder["enhanced_networking"]
if !ok {
continue
}
enhancedNetworkingString, ok := enhancedNetworkingRaw.(bool)
if !ok {
// TODO: error?
continue
}
delete(builder, "enhanced_networking")
builder["ena_support"] = enhancedNetworkingString
}
input["builders"] = tpl.Builders
return input, nil
}
func (FixerEnhancedNetworking) Synopsis() string {
return `Replaces "enhanced_networking" in builders with "ena_support"`
}

View File

@ -0,0 +1,64 @@
package fix
import (
"reflect"
"testing"
)
func TestFixerEnhancedNetworking_Impl(t *testing.T) {
var _ Fixer = new(FixerEnhancedNetworking)
}
func TestFixerEnhancedNetworking(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Expected map[string]interface{}
}{
// Attach field == false
{
Input: map[string]interface{}{
"type": "ebs",
"enhanced_networking": false,
},
Expected: map[string]interface{}{
"type": "ebs",
"ena_support": false,
},
},
// Attach field == true
{
Input: map[string]interface{}{
"type": "ebs",
"enhanced_networking": true,
},
Expected: map[string]interface{}{
"type": "ebs",
"ena_support": true,
},
},
}
for _, tc := range cases {
var f FixerEnhancedNetworking
input := map[string]interface{}{
"builders": []map[string]interface{}{tc.Input},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{tc.Expected},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(output, expected) {
t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected)
}
}
}

View File

@ -0,0 +1,50 @@
package fix
import (
"github.com/mitchellh/mapstructure"
)
// FixerSSHDisableAgent changes the "ssh_disable_agent" of a template
// to "ssh_disable_agent_forwarding".
type FixerSSHDisableAgent struct{}
func (FixerSSHDisableAgent) Fix(input map[string]interface{}) (map[string]interface{}, error) {
// The type we'll decode into; we only care about builders
type template struct {
Builders []map[string]interface{}
}
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.Decode(input, &tpl); err != nil {
return nil, err
}
for _, builder := range tpl.Builders {
sshDisableAgentRaw, ok := builder["ssh_disable_agent"]
if !ok {
continue
}
sshDisableAgent, ok := sshDisableAgentRaw.(bool)
if !ok {
continue
}
// only assign to ssh_disable_agent_forwarding if it doesn't
// already exist; otherwise we'll just ignore ssh_disable_agent
_, sshDisableAgentIncluded := builder["ssh_disable_agent_forwarding"]
if !sshDisableAgentIncluded {
builder["ssh_disable_agent_forwarding"] = sshDisableAgent
}
delete(builder, "ssh_disable_agent")
}
input["builders"] = tpl.Builders
return input, nil
}
func (FixerSSHDisableAgent) Synopsis() string {
return `Updates builders using "ssh_disable_agent" to use "ssh_disable_agent_forwarding"`
}

View File

@ -0,0 +1,83 @@
package fix
import (
"reflect"
"testing"
)
func TestFixerSSHDisableAgent_Impl(t *testing.T) {
var _ Fixer = new(FixerSSHDisableAgent)
}
func TestFixerSSHDisableAgent_Fix(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Expected map[string]interface{}
}{
// No disable_agent field
{
Input: map[string]interface{}{
"type": "virtualbox",
},
Expected: map[string]interface{}{
"type": "virtualbox",
},
},
// disable_agent_forwarding without disable_agent
{
Input: map[string]interface{}{
"ssh_disable_agent_forwarding": true,
},
Expected: map[string]interface{}{
"ssh_disable_agent_forwarding": true,
},
},
// disable_agent without disable_agent_forwarding
{
Input: map[string]interface{}{
"ssh_disable_agent": true,
},
Expected: map[string]interface{}{
"ssh_disable_agent_forwarding": true,
},
},
// disable_agent and disable_agent_forwarding
{
Input: map[string]interface{}{
"ssh_disable_agent": true,
"ssh_disable_agent_forwarding": false,
},
Expected: map[string]interface{}{
"ssh_disable_agent_forwarding": false,
},
},
}
for _, tc := range cases {
var f FixerSSHDisableAgent
input := map[string]interface{}{
"builders": []map[string]interface{}{tc.Input},
}
expected := map[string]interface{}{
"builders": []map[string]interface{}{tc.Expected},
}
output, err := f.Fix(input)
if err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(output, expected) {
t.Fatalf("unexpected: %#v\nexpected: %#v\n", output, expected)
}
}
}

View File

@ -24,7 +24,7 @@ type Config struct {
SSHPty bool `mapstructure:"ssh_pty"`
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
SSHAgentAuth bool `mapstructure:"ssh_agent_auth"`
SSHDisableAgent bool `mapstructure:"ssh_disable_agent"`
SSHDisableAgentForwarding bool `mapstructure:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts int `mapstructure:"ssh_handshake_attempts"`
SSHBastionHost string `mapstructure:"ssh_bastion_host"`
SSHBastionPort int `mapstructure:"ssh_bastion_port"`

View File

@ -163,7 +163,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
Connection: connFunc,
SSHConfig: sshConfig,
Pty: s.Config.SSHPty,
DisableAgent: s.Config.SSHDisableAgent,
DisableAgentForwarding: s.Config.SSHDisableAgentForwarding,
UseSftp: s.Config.SSHFileTransferMethod == "sftp",
}

View File

@ -5,6 +5,7 @@ import (
"os"
"strings"
"sync"
"unicode"
"github.com/mitchellh/iochan"
)
@ -154,11 +155,11 @@ OutputLoop:
// Make sure we finish off stdout/stderr because we may have gotten
// a message from the exit channel before finishing these first.
for output := range stdoutCh {
ui.Message(strings.TrimSpace(output))
ui.Message(r.cleanOutputLine(output))
}
for output := range stderrCh {
ui.Message(strings.TrimSpace(output))
ui.Message(r.cleanOutputLine(output))
}
return nil
@ -196,7 +197,7 @@ func (r *RemoteCmd) Wait() {
// UI output when we're reading from a remote command.
func (r *RemoteCmd) cleanOutputLine(line string) string {
// Trim surrounding whitespace
line = strings.TrimSpace(line)
line = strings.TrimRightFunc(line, unicode.IsSpace)
// Trim up to the first carriage return, since that text would be
// lost anyways.

View File

@ -1,4 +1,4 @@
// +build darwin freebsd linux netbsd openbsd
// +build darwin freebsd linux netbsd openbsd solaris
package packer

View File

@ -2,12 +2,13 @@ package plugin
import (
"fmt"
"github.com/hashicorp/packer/packer"
"log"
"os"
"os/exec"
"testing"
"time"
"github.com/hashicorp/packer/packer"
)
func helperProcess(s ...string) *exec.Cmd {
@ -48,7 +49,7 @@ func TestHelperProcess(*testing.T) {
os.Exit(2)
}
cmd, args := args[0], args[1:]
cmd, _ := args[0], args[1:]
switch cmd {
case "bad-version":
fmt.Printf("%s1|tcp|:1234\n", APIVersion)

View File

@ -71,10 +71,8 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
var wg sync.WaitGroup
if cmd.Stdin != nil {
wg.Add(1)
args.StdinStreamId = c.mux.NextId()
go func() {
defer wg.Done()
serveSingleCopy("stdin", c.mux, args.StdinStreamId, nil, cmd.Stdin)
}()
}
@ -113,8 +111,7 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
var finished CommandFinished
decoder := gob.NewDecoder(conn)
err = decoder.Decode(&finished)
if err != nil {
if err := decoder.Decode(&finished); err != nil {
log.Printf("[ERR] Error decoding response stream %d: %s",
responseStreamId, err)
cmd.SetExited(123)
@ -132,12 +129,7 @@ func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) {
func (c *communicator) Upload(path string, r io.Reader, fi *os.FileInfo) (err error) {
// Pipe the reader through to the connection
streamId := c.mux.NextId()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
serveSingleCopy("uploadData", c.mux, streamId, nil, r)
}()
go serveSingleCopy("uploadData", c.mux, streamId, nil, r)
args := CommunicatorUploadArgs{
Path: path,
@ -149,7 +141,6 @@ func (c *communicator) Upload(path string, r io.Reader, fi *os.FileInfo) (err er
}
err = c.client.Call("Communicator.Upload", &args, new(interface{}))
wg.Wait()
return
}

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