Merge branch 'master' into ovfexportpath-localoutputdir

This commit is contained in:
Jimmy The Dog 2017-03-10 08:12:26 +00:00
commit 9eb9abdce9
133 changed files with 3265 additions and 597 deletions

View File

@ -7,7 +7,7 @@ language: go
go:
- 1.7.4
- 1.8beta1
- 1.8
install:
- make deps

View File

@ -1,6 +1,36 @@
## (Unreleased)
* builder/docker: create export dir if needed [GH-4439]
### FEATURES:
* **New builder:** `ebs-surrogate` for building AMIs from EBS volumes. [GH-4351]
### IMPROVEMENTS:
* builder/amazon: Add BuildRegion and SourceAMI template variables. [GH-4399]
* builder/amazon: Change EC2 Windows password timeout to 20 minutes. [GH-4590]
* builder/amazon-chroot: support encrypted boot volume. [GH-4584]
* builder/docker: create export dir if needed. [GH-4439]
* builder/googlecompute: Add `on_host_maintenance` option. [GH-4544]
* builder/openstack: add reuse_ips option to try to re-use existing IPs. [GH-4564]
* communicator/docker: preserve file mode. [GH-4443]
* communicator/winrm: support ProxyFromEnvironment. [GH-4463]
* core: make VNC links clickable in terminal. [GH-4497] [GH-4498]
* post-processor/amazon-import: support AMI attributes on import [GH-4216]
* provisioner/ansible: use randomized staging dir [GH-4472]
* communicator/ssh: Use SSH agent when enabled for bastion step. [GH-4598]
* builder/amazon: enable ena when `enhanced_networking` is set. [GH-4578]
### BUG FIXES:
* builder/amazon: Fix ssh agent authentication. [GH-4597]
* builder/amazon-ebsvolume: Fix interpolation of block_device. [GH-4464]
* builder/googlecompute: fix bug when creating image from custom image_family.
[GH-4518]
* builder/virtualbox: remove guest additions before saving image. [GH-4496]
* builder/vmware: ESXi: VNC port timeout increased to 5 s. [GH-4480]
* core: always check for an error first when walking a path. [GH-4467]
* builder/docker: Don't force tag if using a docker version that doesn't
support it. [GH-4560]
## 0.12.2 (January 20, 2017)
@ -33,6 +63,7 @@
* provisioner/powershell: Allow equals sign in value of environment
variables. [GH-4328]
* provisioner/puppet-server: Add default facts. [GH-4286]
* builder/qemu: Detect input disk image format during copy/convert. [GH-4343]
### BUG FIXES:
@ -650,38 +681,38 @@
### FEATURES:
* **WinRM:** You can now connect via WinRM with almost every builder.
See the docs for more info. [GH-2239]
See the docs for more info. [GH-2239]
* **Windows AWS Support:** Windows AMIs can now be built without any
external plugins: Packer will start a Windows instance, get the
admin password, and can use WinRM (above) to connect through. [GH-2240]
external plugins: Packer will start a Windows instance, get the
admin password, and can use WinRM (above) to connect through. [GH-2240]
* **Disable SSH:** Set `communicator` to "none" in any builder to disable SSH
connections. Note that provisioners won't work if this is done. [GH-1591]
connections. Note that provisioners won't work if this is done. [GH-1591]
* **SSH Agent Forwarding:** SSH Agent Forwarding will now be enabled
to allow access to remote servers such as private git repos. [GH-1066]
to allow access to remote servers such as private git repos. [GH-1066]
* **SSH Bastion Hosts:** You can now specify a bastion host for
SSH access (works with all builders). [GH-387]
SSH access (works with all builders). [GH-387]
* **OpenStack v3 Identity:** The OpenStack builder now supports the
v3 identity API.
v3 identity API.
* **Docker builder supports SSH**: The Docker builder now supports containers
with SSH, just set `communicator` to "ssh" [GH-2244]
with SSH, just set `communicator` to "ssh" [GH-2244]
* **File provisioner can download**: The file provisioner can now download
files out of the build process. [GH-1909]
files out of the build process. [GH-1909]
* **New config function: `build_name`**: The name of the currently running
build. [GH-2232]
build. [GH-2232]
* **New config function: `build_type`**: The type of the currently running
builder. This is useful for provisioners. [GH-2232]
builder. This is useful for provisioners. [GH-2232]
* **New config function: `template_dir`**: The directory to the template
being built. This should be used for template-relative paths. [GH-54]
being built. This should be used for template-relative paths. [GH-54]
* **New provisioner: shell-local**: Runs a local shell script. [GH-770]
* **New provisioner: powershell**: Provision Windows machines
with PowerShell scripts. [GH-2243]
with PowerShell scripts. [GH-2243]
* **New provisioner: windows-shell**: Provision Windows machines with
batch files. [GH-2243]
batch files. [GH-2243]
* **New provisioner: windows-restart**: Restart a Windows machines and
wait for it to come back online. [GH-2243]
wait for it to come back online. [GH-2243]
* **Compress post-processor supports multiple algorithms:** The compress
post-processor now supports lz4 compression and compresses gzip in
parallel for much faster throughput.
post-processor now supports lz4 compression and compresses gzip in
parallel for much faster throughput.
### IMPROVEMENTS:
@ -809,10 +840,10 @@
### FEATURES:
* **New command: `packer push`**: Push template and files to HashiCorp's
Atlas for building your templates automatically.
Atlas for building your templates automatically.
* **New post-processor: `atlas`**: Send artifact to HashiCorp's Atlas for
versioning and storing artifacts. These artifacts can then be queried
using the API, Terraform, etc.
versioning and storing artifacts. These artifacts can then be queried
using the API, Terraform, etc.
### IMPROVEMENTS:

View File

@ -100,6 +100,18 @@ From there, open your fork in your browser to open a new pull-request.
### Tips for Working on Packer
#### Working on forks
The easiest way to work on a fork is to set it as a remote of the packer project. After following the steps in "Setting up Go to work on Packer":
1. Navigate to $GOPATH/src/github.com/mitchellh/packer
2. Add the remote `git remote add <name of remote> <github url of fork>`. For example `git remote add mwhooker https://github.com/mwhooker/packer.git`.
3. Checkout a feature branch: `git checkout -b new-feature`
4. Make changes
5. (Optional) Push your changes to the fork: `git push -u <name of remote> new-feature`
This way you can push to your fork to create a PR, but the code on disk still lives in the spot where the go cli tools are expecting to find it.
#### Govendor
If you are submitting a change that requires new or updated dependencies, please include them in `vendor/vendor.json` and in the `vendor/` folder. This helps everything get tested properly in CI.

View File

@ -73,8 +73,8 @@ file as `quick-start.json`. Export your AWS credentials as the
"access_key": "{{user `access_key`}}",
"secret_key": "{{user `secret_key`}}",
"region": "us-east-1",
"source_ami": "ami-de0d9eb7",
"instance_type": "t1.micro",
"source_ami": "ami-af22d9b9",
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-example {{timestamp}}"
}]

View File

@ -64,6 +64,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"snapshot_tags",
"tags",
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
@ -251,6 +254,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepRegisterAMI{
RootVolumeSize: b.config.RootVolumeSize,
},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
@ -263,10 +271,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
Ctx: b.config.ctx,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
)

View File

@ -75,9 +75,14 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerOpts = buildRegisterOpts(config, image, newMappings)
}
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
if config.AMIEnhancedNetworking {
// 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
// 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)

View File

@ -0,0 +1,6 @@
package common
type BuildInfoTemplate struct {
SourceAMI string
BuildRegion string
}

View File

@ -66,7 +66,7 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
}
if c.WindowsPasswordTimeout == 0 {
c.WindowsPasswordTimeout = 10 * time.Minute
c.WindowsPasswordTimeout = 20 * time.Minute
}
if c.RunTags == nil {

View File

@ -11,11 +11,13 @@ import (
"github.com/mitchellh/multistep"
retry "github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type StepCreateTags struct {
Tags map[string]string
SnapshotTags map[string]string
Ctx interpolate.Context
}
func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
@ -23,6 +25,13 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
var sourceAMI string
if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI {
sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId
} else {
sourceAMI = ""
}
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
return multistep.ActionContinue
}
@ -79,9 +88,20 @@ func (s *StepCreateTags) Run(state multistep.StateBag) multistep.StepAction {
// Convert tags to ec2.Tag format
ui.Say("Creating AMI tags")
amiTags := ConvertToEC2Tags(s.Tags)
amiTags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, sourceAMI, s.Ctx)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Creating snapshot tags")
snapshotTags := ConvertToEC2Tags(s.SnapshotTags)
snapshotTags, err := ConvertToEC2Tags(s.SnapshotTags, *ec2conn.Config.Region, sourceAMI, s.Ctx)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Retry creating tags for about 2.5 minutes
err = retry.Retry(0.2, 30, 11, func() (bool, error) {
@ -130,14 +150,25 @@ func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
// No cleanup...
}
func ConvertToEC2Tags(tags map[string]string) []*ec2.Tag {
var ec2tags []*ec2.Tag
func ConvertToEC2Tags(tags map[string]string, region, sourceAmiId string, ctx interpolate.Context) ([]*ec2.Tag, error) {
var ec2Tags []*ec2.Tag
for key, value := range tags {
log.Printf("[DEBUG] Creating tag %s=%s", key, value)
ec2tags = append(ec2tags, &ec2.Tag{
ctx.Data = &BuildInfoTemplate{
SourceAMI: sourceAmiId,
BuildRegion: region,
}
interpolatedValue, err := interpolate.Render(value, &ctx)
if err != nil {
return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
}
log.Printf("Adding tag: \"%s\": \"%s\"", key, interpolatedValue)
ec2Tags = append(ec2Tags, &ec2.Tag{
Key: aws.String(key),
Value: aws.String(value),
Value: aws.String(interpolatedValue),
})
}
return ec2tags
return ec2Tags, nil
}

View File

@ -1,4 +1,4 @@
package ebs
package common
import (
"fmt"
@ -7,22 +7,23 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
type stepCreateEncryptedAMICopy struct {
image *ec2.Image
type StepCreateEncryptedAMICopy struct {
image *ec2.Image
KeyID string
EncryptBootVolume bool
Name string
}
func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(Config)
func (s *StepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
kmsKeyId := config.AMIConfig.AMIKmsKeyId
kmsKeyId := s.KeyID
// Encrypt boot not set, so skip step
if !config.AMIConfig.AMIEncryptBootVolume {
if !s.EncryptBootVolume {
if kmsKeyId != "" {
log.Printf(fmt.Sprintf("Ignoring KMS Key ID: %s, encrypted=false", kmsKeyId))
}
@ -46,7 +47,7 @@ func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
}
copyOpts := &ec2.CopyImageInput{
Name: &config.AMIName, // Try to overwrite existing AMI
Name: &s.Name, // Try to overwrite existing AMI
SourceImageId: aws.String(id),
SourceRegion: aws.String(region),
Encrypted: aws.Bool(true),
@ -62,15 +63,15 @@ func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
}
// Wait for the copy to become ready
stateChange := awscommon.StateChangeConf{
stateChange := StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *copyResp.ImageId),
Refresh: AMIStateRefreshFunc(ec2conn, *copyResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI copy to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
if _, err := WaitForState(&stateChange); err != nil {
err := fmt.Errorf("Error waiting for AMI Copy: %s", err)
state.Put("error", err)
ui.Error(err.Error())
@ -146,7 +147,7 @@ func (s *stepCreateEncryptedAMICopy) Run(state multistep.StateBag) multistep.Ste
return multistep.ActionContinue
}
func (s *stepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
func (s *StepCreateEncryptedAMICopy) Cleanup(state multistep.StateBag) {
if s.image == nil {
return
}

View File

@ -107,7 +107,7 @@ func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
// If no key name is set, then we never created it, so just return
// If we used an SSH private key file, do not go about deleting
// keypairs
if s.PrivateKeyFile != "" || s.KeyPairName != "" {
if s.PrivateKeyFile != "" || (s.KeyPairName == "" && s.keyName == "") {
return
}

View File

@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type StepModifyAMIAttributes struct {
@ -17,12 +18,20 @@ type StepModifyAMIAttributes struct {
SnapshotGroups []string
ProductCodes []string
Description string
Ctx interpolate.Context
}
func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
amis := state.Get("amis").(map[string]string)
var sourceAMI string
if rawSourceAMI, hasSourceAMI := state.GetOk("source_image"); hasSourceAMI {
sourceAMI = *rawSourceAMI.(*ec2.Image).ImageId
} else {
sourceAMI = ""
}
snapshots := state.Get("snapshots").(map[string][]string)
// Determine if there is any work to do.
@ -38,6 +47,18 @@ func (s *StepModifyAMIAttributes) Run(state multistep.StateBag) multistep.StepAc
return multistep.ActionContinue
}
var err error
s.Ctx.Data = &BuildInfoTemplate{
SourceAMI: sourceAMI,
BuildRegion: *ec2conn.Config.Region,
}
s.Description, err = interpolate.Render(s.Description, &s.Ctx)
if err != nil {
err = fmt.Errorf("Error interpolating AMI description: %s", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Construct the modify image and snapshot attribute requests we're going
// to make. We need to make each separately since the EC2 API only allows
// changing one type at a kind currently.

View File

@ -3,6 +3,7 @@ package common
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
@ -17,16 +18,31 @@ func (s *StepModifyEBSBackedInstance) Run(state multistep.StateBag) multistep.St
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packer.Ui)
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
if s.EnableEnhancedNetworking {
ui.Say("Enabling Enhanced Networking...")
// 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)
ui.Say("Enabling Enhanced Networking (SR-IOV)...")
simple := "simple"
_, err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
InstanceId: instance.InstanceId,
SriovNetSupport: &ec2.AttributeValue{Value: &simple},
})
if err != nil {
err := fmt.Errorf("Error enabling Enhanced Networking on %s: %s", *instance.InstanceId, err)
err := fmt.Errorf("Error enabling Enhanced Networking (SR-IOV) on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
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
ui.Say("Enabling Enhanced Networking (ENA)...")
_, err = ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
InstanceId: instance.InstanceId,
EnaSupport: &ec2.AttributeBooleanValue{Value: aws.Bool(true)},
})
if err != nil {
err := fmt.Errorf("Error enabling Enhanced Networking (ENA) on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt

View File

@ -13,6 +13,7 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type StepRunSourceInstance struct {
@ -32,6 +33,7 @@ type StepRunSourceInstance struct {
Tags map[string]string
UserData string
UserDataFile string
Ctx interpolate.Context
instanceId string
spotRequest *ec2.SpotInstanceRequest
@ -39,7 +41,10 @@ type StepRunSourceInstance struct {
func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
keyName := state.Get("keyPair").(string)
var keyName string
if name, ok := state.GetOk("keyPair"); ok {
keyName = name.(string)
}
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
ui := state.Get("ui").(packer.Ui)
@ -275,7 +280,14 @@ func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepActi
if _, exists := s.Tags["Name"]; !exists {
s.Tags["Name"] = "Packer Builder"
}
ec2Tags := ConvertToEC2Tags(s.Tags)
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
}
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
Tags: ec2Tags,

View File

@ -101,7 +101,7 @@ func (s *StepSourceAMIInfo) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("Found Image ID: %s", *image.ImageId))
// Enhanced Networking (SriovNetSupport) can only be enabled on HVM AMIs.
// Enhanced Networking can only be enabled on HVM AMIs.
// See http://goo.gl/icuXh5
if s.EnhancedNetworking && *image.VirtualizationType != "hvm" {
err := fmt.Errorf("Cannot enable enhanced networking, source AMI '%s' is not HVM", s.SourceAmi)

View File

@ -44,6 +44,15 @@ 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{
"ami_description",
"run_tags",
"run_volume_tags",
"snapshot_tags",
"tags",
},
},
}, raws...)
if err != nil {
return nil, err
@ -137,10 +146,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&stepTagEBSVolumes{
VolumeRunTags: b.config.VolumeRunTags,
Ctx: b.config.ctx,
},
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
@ -171,7 +182,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AMIName: b.config.AMIName,
},
&stepCreateAMI{},
&stepCreateEncryptedAMICopy{},
&awscommon.StepCreateEncryptedAMICopy{
KeyID: b.config.AMIKmsKeyId,
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
},
&awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig,
Regions: b.config.AMIRegions,
@ -184,10 +199,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
Ctx: b.config.ctx,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
}

View File

@ -7,15 +7,18 @@ import (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type stepTagEBSVolumes struct {
VolumeRunTags map[string]string
Ctx interpolate.Context
}
func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
sourceAMI := state.Get("source_image").(*ec2.Image)
ui := state.Get("ui").(packer.Ui)
if len(s.VolumeRunTags) == 0 {
@ -34,9 +37,15 @@ func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
}
ui.Say("Adding tags to source EBS Volumes")
tags := common.ConvertToEC2Tags(s.VolumeRunTags)
tags, err := common.ConvertToEC2Tags(s.VolumeRunTags, *ec2conn.Config.Region, *sourceAMI.ImageId, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
Resources: volumeIds,
Tags: tags,
})

View File

@ -0,0 +1,218 @@
// The ebssurrogate package contains a packer.Builder implementation that
// builds a new EBS-backed AMI using an ephemeral instance.
package ebssurrogate
import (
"errors"
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/errwrap"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/helper/communicator"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
const BuilderId = "mitchellh.amazon.ebssurrogate"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
awscommon.BlockDevices `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
ctx interpolate.Context
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = awscommon.TemplateFuncs
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"run_tags",
"tags",
},
},
}, raws...)
if err != nil {
return nil, err
}
// Accumulate any errors
var errs *packer.MultiError
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.AMIConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.BlockDevices.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RootDevice.Prepare(&b.config.ctx)...)
if b.config.AMIVirtType == "" {
errs = packer.MultiErrorAppend(errs, errors.New("ami_virtualization_type is required."))
}
foundRootVolume := false
for _, launchDevice := range b.config.BlockDevices.LaunchMappings {
if launchDevice.DeviceName == b.config.RootDevice.SourceDeviceName {
foundRootVolume = true
}
}
if !foundRootVolume {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("no volume with name '%s' is found", b.config.RootDevice.SourceDeviceName))
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
log.Println(common.ScrubConfig(b.config, b.config.AccessKey, b.config.SecretKey))
return nil, nil
}
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
awsConfig, err := b.config.Config()
if err != nil {
return nil, err
}
awsSession, err := session.NewSession(awsConfig)
if err != nil {
return nil, errwrap.Wrapf("Error creating AWS Session: {{err}}", err)
}
ec2conn := ec2.New(awsSession)
// If the subnet is specified but not the AZ, try to determine the AZ automatically
if b.config.SubnetId != "" && b.config.AvailabilityZone == "" {
log.Printf("[INFO] Finding AZ for the given subnet '%s'", b.config.SubnetId)
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
if err != nil {
return nil, err
}
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
log.Printf("[INFO] AZ found: '%s'", b.config.AvailabilityZone)
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("ec2", ec2conn)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps
steps := []multistep.Step{
&awscommon.StepSourceAMIInfo{
SourceAmi: b.config.SourceAmi,
EnhancedNetworking: b.config.AMIEnhancedNetworking,
AmiFilters: b.config.SourceAmiFilter,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
KeyPairName: b.config.SSHKeyPairName,
TemporaryKeyPairName: b.config.TemporaryKeyPairName,
PrivateKeyFile: b.config.RunConfig.Comm.SSHPrivateKey,
},
&awscommon.StepSecurityGroup{
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
},
&awscommon.StepRunSourceInstance{
Debug: b.config.PackerDebug,
ExpectedRootDevice: "ebs",
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
InstanceType: b.config.InstanceType,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
SourceAMI: b.config.SourceAmi,
IamInstanceProfile: b.config.IamInstanceProfile,
SubnetId: b.config.SubnetId,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
Timeout: b.config.WindowsPasswordTimeout,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: awscommon.SSHHost(
ec2conn,
b.config.SSHPrivateIp),
SSHConfig: awscommon.SSHConfig(
b.config.RunConfig.Comm.SSHAgentAuth,
b.config.RunConfig.Comm.SSHUsername,
b.config.RunConfig.Comm.SSHPassword),
},
&common.StepProvision{},
&awscommon.StepStopEBSBackedInstance{
SpotPrice: b.config.SpotPrice,
DisableStopInstance: b.config.DisableStopInstance,
},
&awscommon.StepModifyEBSBackedInstance{
EnableEnhancedNetworking: b.config.AMIEnhancedNetworking,
},
&StepSnapshotNewRootVolume{
NewRootMountPoint: b.config.RootDevice.SourceDeviceName,
},
&StepRegisterAMI{
RootDevice: b.config.RootDevice,
BlockDevices: b.config.BlockDevices.BuildLaunchDevices(),
},
}
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
if amis, ok := state.GetOk("amis"); ok {
// Build the artifact and return it
artifact := &awscommon.Artifact{
Amis: amis.(map[string]string),
BuilderIdValue: BuilderId,
Conn: ec2conn,
}
return artifact, nil
}
return nil, nil
}
func (b *Builder) Cancel() {
if b.runner != nil {
log.Println("Cancelling the step runner...")
b.runner.Cancel()
}
}

View File

@ -0,0 +1,56 @@
package ebssurrogate
import (
"testing"
"github.com/mitchellh/packer/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"access_key": "foo",
"secret_key": "bar",
"source_ami": "foo",
"instance_type": "foo",
"region": "us-east-1",
"ssh_username": "root",
}
}
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packer.Builder); !ok {
t.Fatal("Builder should be a builder")
}
}
func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
"access_key": []string{},
}
warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("prepare should fail")
}
}
func TestBuilderPrepare_InvalidKey(t *testing.T) {
var b Builder
config := testConfig()
// Add a random key
config["i_should_not_be_valid"] = true
warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err == nil {
t.Fatal("should have error")
}
}

View File

@ -0,0 +1,65 @@
package ebssurrogate
import (
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/packer/template/interpolate"
)
type RootBlockDevice struct {
SourceDeviceName string `mapstructure:"source_device_name"`
DeviceName string `mapstructure:"device_name"`
DeleteOnTermination bool `mapstructure:"delete_on_termination"`
IOPS int64 `mapstructure:"iops"`
VolumeType string `mapstructure:"volume_type"`
VolumeSize int64 `mapstructure:"volume_size"`
}
func (c *RootBlockDevice) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.SourceDeviceName == "" {
errs = append(errs, errors.New("source_device_name for the root_device must be specified"))
}
if c.DeviceName == "" {
errs = append(errs, errors.New("device_name for the root_device must be specified"))
}
if c.VolumeType == "gp2" && c.IOPS != 0 {
errs = append(errs, errors.New("iops may not be specified for a gp2 volume"))
}
if c.IOPS < 0 {
errs = append(errs, errors.New("iops must be greater than 0"))
}
if c.VolumeSize < 0 {
errs = append(errs, errors.New("volume_size must be greater than 0"))
}
if len(errs) > 0 {
return errs
}
return nil
}
func (d *RootBlockDevice) createBlockDeviceMapping(snapshotId string) *ec2.BlockDeviceMapping {
rootBlockDevice := &ec2.EbsBlockDevice{
SnapshotId: aws.String(snapshotId),
VolumeType: aws.String(d.VolumeType),
VolumeSize: aws.Int64(d.VolumeSize),
DeleteOnTermination: aws.Bool(d.DeleteOnTermination),
}
if d.IOPS != 0 {
rootBlockDevice.Iops = aws.Int64(d.IOPS)
}
return &ec2.BlockDeviceMapping{
DeviceName: aws.String(d.DeviceName),
Ebs: rootBlockDevice,
}
}

View File

@ -0,0 +1,80 @@
package ebssurrogate
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepRegisterAMI creates the AMI.
type StepRegisterAMI struct {
RootDevice RootBlockDevice
BlockDevices []*ec2.BlockDeviceMapping
}
func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ec2conn := state.Get("ec2").(*ec2.EC2)
snapshotId := state.Get("snapshot_id").(string)
ui := state.Get("ui").(packer.Ui)
ui.Say("Registering the AMI...")
blockDevices := s.BlockDevices
blockDevices = append(blockDevices, s.RootDevice.createBlockDeviceMapping(snapshotId))
registerOpts := &ec2.RegisterImageInput{
Name: &config.AMIName,
Architecture: aws.String(ec2.ArchitectureValuesX8664),
RootDeviceName: aws.String(s.RootDevice.DeviceName),
VirtualizationType: aws.String(config.AMIVirtType),
BlockDeviceMappings: blockDevices,
}
if config.AMIEnhancedNetworking {
// 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
// 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))
ui.Error(state.Get("error").(error).Error())
return multistep.ActionHalt
}
// Set the AMI ID in the state
ui.Say(fmt.Sprintf("AMI: %s", *registerResp.ImageId))
amis := make(map[string]string)
amis[*ec2conn.Config.Region] = *registerResp.ImageId
state.Put("amis", amis)
// Wait for the image to become ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
Target: "available",
Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *registerResp.ImageId),
StepState: state,
}
ui.Say("Waiting for AMI to become ready...")
if _, err := awscommon.WaitForState(&stateChange); err != nil {
err := fmt.Errorf("Error waiting for AMI: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,102 @@
package ebssurrogate
import (
"errors"
"fmt"
"time"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
)
// StepSnapshotNewRootVolume creates a snapshot of the created volume.
//
// Produces:
// snapshot_id string - ID of the created snapshot
type StepSnapshotNewRootVolume struct {
NewRootMountPoint string
snapshotId string
}
func (s *StepSnapshotNewRootVolume) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
instance := state.Get("instance").(*ec2.Instance)
var newRootVolume string
for _, volume := range instance.BlockDeviceMappings {
if *volume.DeviceName == s.NewRootMountPoint {
newRootVolume = *volume.Ebs.VolumeId
}
}
ui.Say(fmt.Sprintf("Creating snapshot of EBS Volume %s...", newRootVolume))
description := fmt.Sprintf("Packer: %s", time.Now().String())
createSnapResp, err := ec2conn.CreateSnapshot(&ec2.CreateSnapshotInput{
VolumeId: &newRootVolume,
Description: &description,
})
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the snapshot ID so we can delete it later
s.snapshotId = *createSnapResp.SnapshotId
ui.Message(fmt.Sprintf("Snapshot ID: %s", s.snapshotId))
// Wait for the snapshot to be ready
stateChange := awscommon.StateChangeConf{
Pending: []string{"pending"},
StepState: state,
Target: "completed",
Refresh: func() (interface{}, string, error) {
resp, err := ec2conn.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{&s.snapshotId}})
if err != nil {
return nil, "", err
}
if len(resp.Snapshots) == 0 {
return nil, "", errors.New("No snapshots found.")
}
s := resp.Snapshots[0]
return s, *s.State, nil
},
}
_, err = awscommon.WaitForState(&stateChange)
if err != nil {
err := fmt.Errorf("Error waiting for snapshot: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("snapshot_id", s.snapshotId)
return multistep.ActionContinue
}
func (s *StepSnapshotNewRootVolume) Cleanup(state multistep.StateBag) {
if s.snapshotId == "" {
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
ui.Say("Removing snapshot since we cancelled or halted...")
_, err := ec2conn.DeleteSnapshot(&ec2.DeleteSnapshotInput{SnapshotId: &s.snapshotId})
if err != nil {
ui.Error(fmt.Sprintf("Error: %s", err))
}
}
}

View File

@ -2,6 +2,7 @@ package ebsvolume
import (
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/template/interpolate"
)
type BlockDevice struct {
@ -9,15 +10,20 @@ type BlockDevice struct {
Tags map[string]string `mapstructure:"tags"`
}
func commonBlockDevices(mappings []BlockDevice) awscommon.BlockDevices {
func commonBlockDevices(mappings []BlockDevice, ctx *interpolate.Context) (awscommon.BlockDevices, error) {
result := make([]awscommon.BlockDevice, len(mappings))
for i, mapping := range mappings {
result[i] = mapping.BlockDevice
interpolateBlockDev, err := interpolate.RenderInterface(&mapping.BlockDevice, ctx)
if err != nil {
return awscommon.BlockDevices{}, err
}
result[i] = *interpolateBlockDev.(*awscommon.BlockDevice)
}
return awscommon.BlockDevices{
LaunchBlockDevices: awscommon.LaunchBlockDevices{
LaunchMappings: result,
},
}
}, nil
}

View File

@ -28,7 +28,8 @@ type Config struct {
VolumeMappings []BlockDevice `mapstructure:"ebs_volumes"`
AMIEnhancedNetworking bool `mapstructure:"enhanced_networking"`
ctx interpolate.Context
launchBlockDevices awscommon.BlockDevices
ctx interpolate.Context
}
type Builder struct {
@ -41,6 +42,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"run_tags",
"ebs_volumes",
},
},
}, raws...)
if err != nil {
return nil, err
@ -51,6 +58,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...)
errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...)
b.config.launchBlockDevices, err = commonBlockDevices(b.config.VolumeMappings, &b.config.ctx)
if err != nil {
errs = packer.MultiErrorAppend(errs, err)
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
}
@ -90,8 +102,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
state.Put("hook", hook)
state.Put("ui", ui)
launchBlockDevices := commonBlockDevices(b.config.VolumeMappings)
// Build the steps
steps := []multistep.Step{
&awscommon.StepSourceAMIInfo{
@ -126,12 +136,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
EbsOptimized: b.config.EbsOptimized,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: launchBlockDevices,
BlockDevices: b.config.launchBlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
},
&stepTagEBSVolumes{
VolumeMapping: b.config.VolumeMappings,
Ctx: b.config.ctx,
},
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,

View File

@ -3,19 +3,22 @@ package ebsvolume
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mitchellh/multistep"
awscommon "github.com/mitchellh/packer/builder/amazon/common"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
)
type stepTagEBSVolumes struct {
VolumeMapping []BlockDevice
Ctx interpolate.Context
}
func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
instance := state.Get("instance").(*ec2.Instance)
sourceAMI := state.Get("source_image").(*ec2.Image)
ui := state.Get("ui").(packer.Ui)
volumes := make(EbsVolumes)
@ -40,12 +43,12 @@ func (s *stepTagEBSVolumes) Run(state multistep.StateBag) multistep.StepAction {
continue
}
tags := make([]*ec2.Tag, 0, len(mapping.Tags))
for key, value := range mapping.Tags {
tags = append(tags, &ec2.Tag{
Key: aws.String(fmt.Sprintf("%s", key)),
Value: aws.String(fmt.Sprintf("%s", value)),
})
tags, err := awscommon.ConvertToEC2Tags(mapping.Tags, *ec2conn.Config.Region, *sourceAMI.ImageId, s.Ctx)
if err != nil {
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, v := range instance.BlockDeviceMappings {

View File

@ -63,8 +63,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"ami_description",
"bundle_upload_command",
"bundle_vol_command",
"run_tags",
"run_volume_tags",
"snapshot_tags",
"tags",
},
},
}, configs...)
@ -223,6 +228,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
},
&awscommon.StepGetPassword{
Debug: b.config.PackerDebug,
@ -265,10 +271,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
Ctx: b.config.ctx,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
}

View File

@ -29,9 +29,14 @@ func (s *StepRegisterAMI) Run(state multistep.StateBag) multistep.StepAction {
registerOpts.VirtualizationType = aws.String(config.AMIVirtType)
}
// Set SriovNetSupport to "simple". See http://goo.gl/icuXh5
if config.AMIEnhancedNetworking {
// 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.
// 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)

View File

@ -72,6 +72,8 @@ type Config struct {
AzureTags map[string]*string `mapstructure:"azure_tags"`
ResourceGroupName string `mapstructure:"resource_group_name"`
StorageAccount string `mapstructure:"storage_account"`
TempComputeName string `mapstructure:"temp_compute_name"`
TempResourceGroupName string `mapstructure:"temp_resource_group_name"`
storageAccountBlobEndpoint string
CloudEnvironmentName string `mapstructure:"cloud_environment_name"`
cloudEnvironment *azure.Environment
@ -288,9 +290,17 @@ func setRuntimeValues(c *Config) {
c.tmpAdminPassword = tempName.AdminPassword
c.tmpCertificatePassword = tempName.CertificatePassword
c.tmpComputeName = tempName.ComputeName
if c.TempComputeName == "" {
c.tmpComputeName = tempName.ComputeName
} else {
c.tmpComputeName = c.TempComputeName
}
c.tmpDeploymentName = tempName.DeploymentName
c.tmpResourceGroupName = tempName.ResourceGroupName
if c.TempResourceGroupName == "" {
c.tmpResourceGroupName = tempName.ResourceGroupName
} else {
c.tmpResourceGroupName = c.TempResourceGroupName
}
c.tmpOSDiskName = tempName.OSDiskName
c.tmpKeyVaultName = tempName.KeyVaultName
}

View File

@ -70,11 +70,15 @@ func (c *Communicator) Upload(dst string, src io.Reader, fi *os.FileInfo) error
// Copy the contents to the temporary file
_, err = io.Copy(tempfile, src)
tempfile.Close()
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{

View File

@ -97,9 +97,10 @@ func (d *DockerDriver) Export(id string, dst io.Writer) error {
}
func (d *DockerDriver) Import(path string, repo string) (string, error) {
var stdout bytes.Buffer
var stdout, stderr bytes.Buffer
cmd := exec.Command("docker", "import", "-", repo)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
@ -122,8 +123,7 @@ func (d *DockerDriver) Import(path string, repo string) (string, error) {
}()
if err := cmd.Wait(); err != nil {
err = fmt.Errorf("Error importing container: %s", err)
return "", err
return "", fmt.Errorf("Error importing container: %s\n\nStderr: %s", err, stderr.String())
}
return strings.TrimSpace(stdout.String()), nil
@ -275,8 +275,34 @@ func (d *DockerDriver) StopContainer(id string) error {
func (d *DockerDriver) TagImage(id string, repo string, force bool) error {
args := []string{"tag"}
// detect running docker version before tagging
// flag `force` for docker tagging was removed after Docker 1.12.0
// to keep its backward compatibility, we are not going to remove `force`
// option, but to ignore it when Docker version >= 1.12.0
//
// for more detail, please refer to the following links:
// - https://docs.docker.com/engine/deprecated/#/f-flag-on-docker-tag
// - https://github.com/docker/docker/pull/23090
version_running, err := d.Version()
if err != nil {
return err
}
version_deprecated, err := version.NewVersion(string("1.12.0"))
if err != nil {
// should never reach this line
return err
}
if force {
args = append(args, "-f")
if version_running.LessThan(version_deprecated) {
args = append(args, "-f")
} else {
// do nothing if Docker version >= 1.12.0
log.Printf("[WARN] option: \"force\" will be ignored here")
log.Printf("since it was removed after Docker 1.12.0 released")
}
}
args = append(args, id, repo)

View File

@ -39,6 +39,7 @@ type Config struct {
Network string `mapstructure:"network"`
NetworkProjectId string `mapstructure:"network_project_id"`
OmitExternalIP bool `mapstructure:"omit_external_ip"`
OnHostMaintenance string `mapstructure:"on_host_maintenance"`
Preemptible bool `mapstructure:"preemptible"`
RawStateTimeout string `mapstructure:"state_timeout"`
Region string `mapstructure:"region"`
@ -92,6 +93,22 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
if c.ImageDescription == "" {
c.ImageDescription = "Created by Packer"
}
// Setting OnHostMaintenance Correct Defaults
// "MIGRATE" : Possible if Preemptible is false
// "TERMINATE": Posssible if Preemptible is true
if c.OnHostMaintenance == "" && c.Preemptible {
c.OnHostMaintenance = "MIGRATE"
}
if c.OnHostMaintenance == "" && !c.Preemptible {
c.OnHostMaintenance = "TERMINATE"
}
// Make sure user sets a valid value for on_host_maintenance option
if !(c.OnHostMaintenance == "MIGRATE" || c.OnHostMaintenance == "TERMINATE") {
errs = packer.MultiErrorAppend(errs,
errors.New("on_host_maintenance must be one of MIGRATE or TERMINATE."))
}
if c.ImageName == "" {
img, err := interpolate.Render("packer-{{timestamp}}", nil)

View File

@ -104,6 +104,21 @@ func TestConfigPrepare(t *testing.T) {
"SO VERY BAD",
true,
},
{
"on_host_maintenance",
nil,
false,
},
{
"on_host_maintenance",
"TERMINATE",
false,
},
{
"on_host_maintenance",
"SO VERY BAD",
true,
},
{
"preemptible",
nil,

View File

@ -69,6 +69,7 @@ type InstanceConfig struct {
Network string
NetworkProjectId string
OmitExternalIP bool
OnHostMaintenance string
Preemptible bool
Region string
Scopes []string

View File

@ -386,7 +386,8 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
},
},
Scheduling: &compute.Scheduling{
Preemptible: c.Preemptible,
OnHostMaintenance: c.OnHostMaintenance,
Preemptible: c.Preemptible,
},
ServiceAccounts: []*compute.ServiceAccount{
{

View File

@ -67,7 +67,7 @@ func getImage(c *Config, d Driver) (*Image, error) {
if c.SourceImageProjectId == "" {
return d.GetImage(name, fromFamily)
} else {
return d.GetImageFromProject(c.SourceImageProjectId, c.SourceImage, fromFamily)
return d.GetImageFromProject(c.SourceImageProjectId, name, fromFamily)
}
}
@ -110,6 +110,7 @@ func (s *StepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
Network: c.Network,
NetworkProjectId: c.NetworkProjectId,
OmitExternalIP: c.OmitExternalIP,
OnHostMaintenance: c.OnHostMaintenance,
Preemptible: c.Preemptible,
Region: c.Region,
ServiceAccountEmail: c.Account.ClientEmail,

View File

@ -23,11 +23,14 @@ type artifact struct {
func NewArtifact(dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return err
return nil
}
if err := filepath.Walk(dir, visit); err != nil {

View File

@ -102,6 +102,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepAllocateIp{
FloatingIpPool: b.config.FloatingIpPool,
FloatingIp: b.config.FloatingIp,
ReuseIps: b.config.ReuseIps,
},
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,

View File

@ -23,6 +23,7 @@ type RunConfig struct {
RackconnectWait bool `mapstructure:"rackconnect_wait"`
FloatingIpPool string `mapstructure:"floating_ip_pool"`
FloatingIp string `mapstructure:"floating_ip"`
ReuseIps bool `mapstructure:"reuse_ips"`
SecurityGroups []string `mapstructure:"security_groups"`
Networks []string `mapstructure:"networks"`
UserData string `mapstructure:"user_data"`

View File

@ -13,6 +13,7 @@ import (
type StepAllocateIp struct {
FloatingIpPool string
FloatingIp string
ReuseIps bool
}
func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
@ -37,36 +38,38 @@ func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
if s.FloatingIp != "" {
instanceIp.IP = s.FloatingIp
} else if s.FloatingIpPool != "" {
// If we have a free floating IP in the pool, use it first
// rather than creating one
ui.Say(fmt.Sprintf("Searching for unassociated floating IP in pool %s", s.FloatingIpPool))
pager := floatingips.List(client)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
candidates, err := floatingips.ExtractFloatingIPs(page)
// If ReuseIps is set to true and we have a free floating IP in
// the pool, use it first rather than creating one
if s.ReuseIps {
ui.Say(fmt.Sprintf("Searching for unassociated floating IP in pool %s", s.FloatingIpPool))
pager := floatingips.List(client)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
candidates, err := floatingips.ExtractFloatingIPs(page)
if err != nil {
return false, err // stop and throw error out
}
for _, candidate := range candidates {
if candidate.Pool != s.FloatingIpPool || candidate.InstanceID != "" {
continue // move to next in list
if err != nil {
return false, err // stop and throw error out
}
// In correct pool and able to be allocated
instanceIp.IP = candidate.IP
ui.Message(fmt.Sprintf("Selected floating IP: %s", instanceIp.IP))
state.Put("floatingip_istemp", false)
return false, nil // stop iterating over pages
}
return true, nil // try the next page
})
for _, candidate := range candidates {
if candidate.Pool != s.FloatingIpPool || candidate.InstanceID != "" {
continue // move to next in list
}
if err != nil {
err := fmt.Errorf("Error searching for floating ip from pool '%s'", s.FloatingIpPool)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
// In correct pool and able to be allocated
instanceIp.IP = candidate.IP
ui.Message(fmt.Sprintf("Selected floating IP: %s", instanceIp.IP))
state.Put("floatingip_istemp", false)
return false, nil // stop iterating over pages
}
return true, nil // try the next page
})
if err != nil {
err := fmt.Errorf("Error searching for floating ip from pool '%s'", s.FloatingIpPool)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if instanceIp.IP == "" {

View File

@ -28,6 +28,9 @@ type artifact struct {
func NewArtifact(dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
for _, unnecessaryFile := range unnecessaryFiles {
if unnecessary, _ := regexp.MatchString(unnecessaryFile, path); unnecessary {
return os.RemoveAll(path)
@ -38,7 +41,7 @@ func NewArtifact(dir string) (packer.Artifact, error) {
files = append(files, path)
}
return err
return nil
}
if err := filepath.Walk(dir, visit); err != nil {

View File

@ -444,11 +444,14 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
// Compile the artifact list
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return err
return nil
}
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {

View File

@ -38,7 +38,6 @@ func (s *stepConvertDisk) Run(state multistep.StateBag) multistep.StepAction {
}
command = append(command, []string{
"-f", config.Format,
"-O", config.Format,
sourcePath,
targetPath,

View File

@ -22,7 +22,6 @@ func (s *stepCopyDisk) Run(state multistep.StateBag) multistep.StepAction {
command := []string{
"convert",
"-f", config.Format,
"-O", config.Format,
isoPath,
path,

View File

@ -116,7 +116,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
ui.Message(fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC without a password to\n"+
"%s:%d", vncIp, vncPort))
"vnc://%s:%d", vncIp, vncPort))
} else {
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
"If the run isn't succeeding as you expect, please enable the GUI\n" +

View File

@ -23,6 +23,9 @@ type artifact struct {
func NewArtifact(dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}

View File

@ -56,6 +56,7 @@ func (s *StepAttachGuestAdditions) Run(state multistep.StateBag) multistep.StepA
// Track the path so that we can unregister it from VirtualBox later
s.attachedPath = guestAdditionsPath
state.Put("guest_additions_attached", true)
return multistep.ActionContinue
}
@ -66,7 +67,6 @@ func (s *StepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
}
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
vmName := state.Get("vmName").(string)
command := []string{
@ -77,7 +77,7 @@ func (s *StepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
ui.Error(fmt.Sprintf("Error unregistering guest additions: %s", err))
}
// Remove the ISO. Note that this will probably fail since
// stepRemoveDevices does this as well. No big deal.
driver.VBoxManage(command...)
}

View File

@ -79,6 +79,23 @@ func (s *StepRemoveDevices) Run(state multistep.StateBag) multistep.StepAction {
}
}
if _, ok := state.GetOk("guest_additions_attached"); ok {
ui.Message("Removing guest additions drive...")
command := []string{
"storageattach", vmName,
"--storagectl", "IDE Controller",
"--port", "1",
"--device", "0",
"--medium", "none",
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error removing guest additions: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}

View File

@ -40,7 +40,7 @@ func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VRDP without a password to\n"+
"%s:%d", vrdpIp, vrdpPort))
"rdp://%s:%d", vrdpIp, vrdpPort))
} else {
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
"If the run isn't succeeding as you expect, please enable the GUI\n" +

View File

@ -76,7 +76,7 @@ func (s *stepCreateVM) Cleanup(state multistep.StateBag) {
ui.Say("Unregistering and deleting virtual machine...")
var err error = nil
for i := 0; i < 5; i++ {
err = driver.VBoxManage("unregistervm", s.vmName, "--delete")
err = driver.Delete(s.vmName)
if err == nil {
break
}

View File

@ -23,11 +23,13 @@ type localArtifact struct {
func NewLocalArtifact(dir string) (packer.Artifact, error) {
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return err
return nil
}
if err := filepath.Walk(dir, visit); err != nil {

View File

@ -63,7 +63,7 @@ func VNCPassword(skipPassword bool) string {
}
length := int(8)
charSet := []byte("1234567890-=qwertyuiop[]asdfghjkl;zxcvbnm,./!@#%^*()_+QWERTYUIOP{}|ASDFGHJKL:XCVBNM<>?")
charSet := []byte("012345689abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
charSetLength := len(charSet)
password := make([]byte, length)

View File

@ -1,46 +0,0 @@
diff a/builder/vmware/common/step_configure_vnc.go b/builder/vmware/common/step_configure_vnc.go (rejected hunks)
@@ -52,6 +52,21 @@ func (StepConfigureVNC) VNCAddress(portMin, portMax uint) (string, uint, error)
return "127.0.0.1", vncPort, nil
}
+func VNCPassword() (string) {
+ length := int(8)
+
+ charSet := []byte("1234567890-=qwertyuiop[]asdfghjkl;zxcvbnm,./!@#%^*()_+QWERTYUIOP{}|ASDFGHJKL:XCVBNM<>?")
+ charSetLength := len(charSet)
+
+ password := make([]byte, length)
+
+ for i := 0; i < length; i++ {
+ password[i] = charSet[ rand.Intn(charSetLength) ]
+ }
+
+ return string(password)
+}
+
func (s *StepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(Driver)
ui := state.Get("ui").(packer.Ui)
@@ -86,12 +101,14 @@ func (s *StepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
ui.Error(err.Error())
return multistep.ActionHalt
}
+ vncPassword := VNCPassword()
log.Printf("Found available VNC port: %d", vncPort)
vmxData := ParseVMX(string(vmxBytes))
vmxData["remotedisplay.vnc.enabled"] = "TRUE"
vmxData["remotedisplay.vnc.port"] = fmt.Sprintf("%d", vncPort)
+ vmxData["remotedisplay.vnc.password"] = vncPassword
if err := WriteVMX(vmxPath, vmxData); err != nil {
err := fmt.Errorf("Error writing VMX data: %s", err)
@@ -102,6 +119,7 @@ func (s *StepConfigureVNC) Run(state multistep.StateBag) multistep.StepAction {
state.Put("vnc_port", vncPort)
state.Put("vnc_ip", vncIp)
+ state.Put("vnc_password", vncPassword)
return multistep.ActionContinue
}

View File

@ -48,7 +48,7 @@ func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf(
"The VM will be run headless, without a GUI. If you want to\n"+
"view the screen of the VM, connect via VNC with the password \"%s\" to\n"+
"%s:%d", vncPassword, vncIp, vncPort))
"vnc://%s:%d", vncPassword, vncIp, vncPort))
} else {
ui.Message("The VM will be run headless, without a GUI, as configured.\n" +
"If the run isn't succeeding as you expect, please enable the GUI\n" +

View File

@ -1,26 +0,0 @@
diff a/builder/vmware/common/step_type_boot_command.go b/builder/vmware/common/step_type_boot_command.go (rejected hunks)
@@ -45,6 +45,7 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
ui := state.Get("ui").(packer.Ui)
vncIp := state.Get("vnc_ip").(string)
vncPort := state.Get("vnc_port").(uint)
+ vncPassword := state.Get("vnc_password")
// Connect to VNC
ui.Say("Connecting to VM via VNC")
@@ -57,7 +58,15 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction
}
defer nc.Close()
- c, err := vnc.Client(nc, &vnc.ClientConfig{Exclusive: true})
+ var auth []vnc.ClientAuth
+
+ if vncPassword != nil {
+ auth = []vnc.ClientAuth{&vnc.PasswordAuth{Password: vncPassword.(string)}}
+ } else {
+ auth = []vnc.ClientAuth{}
+ }
+
+ c, err := vnc.Client(nc, &vnc.ClientConfig{Auth: auth, Exclusive: true})
if err != nil {
err := fmt.Errorf("Error handshaking with VNC: %s", err)
state.Put("error", err)

View File

@ -38,19 +38,20 @@ type Config struct {
vmwcommon.VMXConfig `mapstructure:",squash"`
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
BootCommand []string `mapstructure:"boot_command"`
DiskName string `mapstructure:"vmdk_name"`
DiskSize uint `mapstructure:"disk_size"`
DiskTypeId string `mapstructure:"disk_type_id"`
Format string `mapstructure:"format"`
GuestOSType string `mapstructure:"guest_os_type"`
Version string `mapstructure:"version"`
VMName string `mapstructure:"vm_name"`
BootCommand []string `mapstructure:"boot_command"`
KeepRegistered bool `mapstructure:"keep_registered"`
OVFToolOptions []string `mapstructure:"ovftool_options"`
SkipCompaction bool `mapstructure:"skip_compaction"`
SkipExport bool `mapstructure:"skip_export"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
VMName string `mapstructure:"vm_name"`
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
VMXTemplatePath string `mapstructure:"vmx_template_path"`
Version string `mapstructure:"version"`
RemoteType string `mapstructure:"remote_type"`
RemoteDatastore string `mapstructure:"remote_datastore"`

View File

@ -205,7 +205,7 @@ func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint,
}
address := fmt.Sprintf("%s:%d", d.Host, port)
log.Printf("Trying address: %s...", address)
l, err := net.DialTimeout("tcp", address, 1*time.Second)
l, err := net.DialTimeout("tcp", address, 5*time.Second)
if err != nil {
if e, ok := err.(*net.OpError); ok {
@ -248,12 +248,7 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
port = sshc.WinRMPort
}
if address, ok := state.GetOk("vm_address"); ok {
return address.(string), nil
}
if address := config.CommConfig.Host(); address != "" {
state.Put("vm_address", address)
return address, nil
}
@ -302,7 +297,6 @@ func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
} else {
defer conn.Close()
address := record["IPAddress"]
state.Put("vm_address", address)
return address, nil
}
}

View File

@ -88,11 +88,4 @@ func TestESX5Driver_CommHost(t *testing.T) {
if host != expected_host {
t.Errorf("bad host name: %s", host)
}
address, ok := state.GetOk("vm_address")
if !ok {
t.Error("state not updated with vm_address")
}
if address.(string) != expected_host {
t.Errorf("bad vm_address: %s", address.(string))
}
}

View File

@ -3,13 +3,14 @@ package iso
import (
"bytes"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"net/url"
"os"
"os/exec"
"runtime"
"strings"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type StepExport struct {
@ -22,13 +23,14 @@ func (s *StepExport) generateArgs(c *Config, outputPath string, hidePassword boo
if hidePassword {
password = "****"
}
return []string{
args := []string{
"--noSSLVerify=true",
"--skipManifestCheck",
"-tt=" + s.Format,
"vi://" + c.RemoteUser + ":" + password + "@" + c.RemoteHost + "/" + c.VMName,
outputPath,
}
return append(c.OVFToolOptions, args...)
}
func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {

View File

@ -92,15 +92,18 @@ func (c BuildCommand) Run(args []string) int {
Color: colors[i%len(colors)],
Ui: ui,
}
if _, ok := c.Ui.(*packer.MachineReadableUi); !ok {
ui.Say(fmt.Sprintf("%s output will be in this color.", b))
if i+1 == len(buildNames) {
// Add a newline between the color output and the actual output
c.Ui.Say("")
}
}
}
buildUis[b] = ui
ui.Say(fmt.Sprintf("%s output will be in this color.", b))
}
// Add a newline between the color output and the actual output
c.Ui.Say("")
log.Printf("Build debug mode: %v", cfgDebug)
log.Printf("Force build: %v", cfgForce)
log.Printf("On error: %v", cfgOnError)

View File

@ -15,6 +15,7 @@ import (
amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot"
amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs"
amazonebssurrogatebuilder "github.com/mitchellh/packer/builder/amazon/ebssurrogate"
amazonebsvolumebuilder "github.com/mitchellh/packer/builder/amazon/ebsvolume"
amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance"
azurearmbuilder "github.com/mitchellh/packer/builder/azure/arm"
@ -72,29 +73,30 @@ type PluginCommand struct {
}
var Builders = map[string]packer.Builder{
"amazon-chroot": new(amazonchrootbuilder.Builder),
"amazon-ebs": new(amazonebsbuilder.Builder),
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
"amazon-instance": new(amazoninstancebuilder.Builder),
"azure-arm": new(azurearmbuilder.Builder),
"cloudstack": new(cloudstackbuilder.Builder),
"digitalocean": new(digitaloceanbuilder.Builder),
"docker": new(dockerbuilder.Builder),
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"null": new(nullbuilder.Builder),
"oneandone": new(oneandonebuilder.Builder),
"openstack": new(openstackbuilder.Builder),
"parallels-iso": new(parallelsisobuilder.Builder),
"parallels-pvm": new(parallelspvmbuilder.Builder),
"profitbricks": new(profitbricksbuilder.Builder),
"qemu": new(qemubuilder.Builder),
"triton": new(tritonbuilder.Builder),
"virtualbox-iso": new(virtualboxisobuilder.Builder),
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
"vmware-iso": new(vmwareisobuilder.Builder),
"vmware-vmx": new(vmwarevmxbuilder.Builder),
"amazon-chroot": new(amazonchrootbuilder.Builder),
"amazon-ebs": new(amazonebsbuilder.Builder),
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
"amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder),
"amazon-instance": new(amazoninstancebuilder.Builder),
"azure-arm": new(azurearmbuilder.Builder),
"cloudstack": new(cloudstackbuilder.Builder),
"digitalocean": new(digitaloceanbuilder.Builder),
"docker": new(dockerbuilder.Builder),
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hyperv-iso": new(hypervisobuilder.Builder),
"null": new(nullbuilder.Builder),
"oneandone": new(oneandonebuilder.Builder),
"openstack": new(openstackbuilder.Builder),
"parallels-iso": new(parallelsisobuilder.Builder),
"parallels-pvm": new(parallelspvmbuilder.Builder),
"profitbricks": new(profitbricksbuilder.Builder),
"qemu": new(qemubuilder.Builder),
"triton": new(tritonbuilder.Builder),
"virtualbox-iso": new(virtualboxisobuilder.Builder),
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
"vmware-iso": new(vmwareisobuilder.Builder),
"vmware-vmx": new(vmwarevmxbuilder.Builder),
}
var Provisioners = map[string]packer.Provisioner{

View File

@ -52,18 +52,16 @@ func (c *VersionCommand) Run(args []string) int {
// If we have a version check function, then let's check for
// the latest version as well.
if c.CheckFunc != nil {
// Separate the prior output with a newline
c.Ui.Say("")
// Check the latest version
info, err := c.CheckFunc()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error checking latest version: %s", err))
"\nError checking latest version: %s", err))
}
if info.Outdated {
c.Ui.Say(fmt.Sprintf(
"Your version of Packer is out of date! The latest version\n"+
"\nYour version of Packer is out of date! The latest version\n"+
"is %s. You can update by downloading from www.packer.io",
info.Latest))
}

View File

@ -9,7 +9,7 @@ import (
"github.com/mitchellh/packer/version"
)
// Commands is the mapping of all the available Terraform commands.
// Commands is the mapping of all the available Packer commands.
var Commands map[string]cli.CommandFactory
// CommandMeta is the Meta to use for the commands. This must be written

View File

@ -114,6 +114,9 @@ func (s *StepCreateFloppy) Run(state multistep.StateBag) multistep.StepAction {
var crawlDirectoryFiles []string
crawlDirectory := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
crawlDirectoryFiles = append(crawlDirectoryFiles, path)
ui.Message(fmt.Sprintf("Adding file: %s", path))

View File

@ -21,6 +21,9 @@ const TestFixtures = "test-fixtures"
func getDirectory(path string) []string {
var result []string
walk := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() && !strings.HasSuffix(path, "/") {
path = path + "/"
}

View File

@ -87,7 +87,7 @@ func TestUpload(t *testing.T) {
t.Fatalf("error creating communicator: %s", err)
}
err = c.Upload("C:/Temp/terraform.cmd", bytes.NewReader([]byte("something")), nil)
err = c.Upload("C:/Temp/packer.cmd", bytes.NewReader([]byte("something")), nil)
if err != nil {
t.Fatalf("error uploading file: %s", err)
}

View File

@ -41,6 +41,7 @@
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"skip_clean": true,
"type": "shell"
}]
}

View File

@ -1,35 +1,35 @@
{
"variables": {
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
},
"builders": [{
"type": "azure-arm",
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "16.04.0-LTS",
"location": "West US",
"vm_size": "Standard_A2"
}],
"provisioners": [{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}]
}
{
"variables": {
"resource_group": "{{env `ARM_RESOURCE_GROUP`}}",
"storage_account": "{{env `ARM_STORAGE_ACCOUNT`}}",
"subscription_id": "{{env `ARM_SUBSCRIPTION_ID`}}"
},
"builders": [{
"type": "azure-arm",
"resource_group_name": "{{user `resource_group`}}",
"storage_account": "{{user `storage_account`}}",
"subscription_id": "{{user `subscription_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "16.04-LTS",
"location": "West US",
"vm_size": "Standard_DS1_v2"
}],
"provisioners": [{
"execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
"inline": [
"apt-get update",
"apt-get upgrade -y",
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
],
"inline_shebang": "/bin/sh -x",
"type": "shell"
}]
}

View File

@ -17,32 +17,29 @@
"subscription_id": "{{user `subscription_id`}}",
"object_id": "{{user `object_id`}}",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"capture_container_name": "images",
"capture_name_prefix": "packer",
"os_type": "Windows",
"image_publisher": "MicrosoftWindowsServer",
"image_offer": "WindowsServer",
"image_sku": "2012-R2-Datacenter",
"os_type": "Windows",
"image_publisher": "MicrosoftWindowsServer",
"image_offer": "WindowsServer",
"image_sku": "2012-R2-Datacenter",
"communicator": "winrm",
"winrm_use_ssl": "true",
"winrm_insecure": "true",
"winrm_timeout": "3m",
"winrm_username": "packer",
"communicator": "winrm",
"winrm_use_ssl": "true",
"winrm_insecure": "true",
"winrm_timeout": "3m",
"winrm_username": "packer",
"location": "West US",
"vm_size": "Standard_A2"
}
],
"provisioners": [
{
"type": "powershell",
"inline": [
"dir c:\\"
]
}
"location": "West US",
"vm_size": "Standard_A2"
}],
"provisioners": [{
"type": "powershell",
"inline": [
"if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}",
"& $Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet"
]
}]
}

View File

@ -32,9 +32,10 @@
}
],
"provisioners": [{
"type": "powershell",
"inline": [
"dir c:\\"
]
"type": "powershell",
"inline": [
"if( Test-Path $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml ){ rm $Env:SystemRoot\\windows\\system32\\Sysprep\\unattend.xml -Force}",
"& $Env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /shutdown /quiet"
]
}]
}

View File

@ -12,6 +12,7 @@ import (
commonssh "github.com/mitchellh/packer/common/ssh"
"github.com/mitchellh/packer/communicator/ssh"
"github.com/mitchellh/packer/packer"
"github.com/xanzy/ssh-agent"
gossh "golang.org/x/crypto/ssh"
)
@ -94,6 +95,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
conf, err := sshBastionConfig(s.Config)
if err != nil {
log.Printf("[ERROR] Error calling sshBastionConfig: %v", err)
return nil, fmt.Errorf("Error configuring bastion: %s", err)
}
bConf = conf
@ -196,7 +198,15 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
}
func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) {
auth := make([]gossh.AuthMethod, 0, 2)
var auth []gossh.AuthMethod
if !config.SSHDisableAgent {
log.Printf("[INFO] SSH agent forwarding enabled.")
if sshAgent := sshAgent(); sshAgent != nil {
auth = append(auth, sshAgent)
}
}
if config.SSHBastionPassword != "" {
auth = append(auth,
gossh.Password(config.SSHBastionPassword),
@ -218,3 +228,19 @@ func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) {
Auth: auth,
}, nil
}
func sshAgent() gossh.AuthMethod {
if !sshagent.Available() {
log.Println("[DEBUG] Error fetching SSH_AUTH_SOCK.")
return nil
}
agent, _, err := sshagent.New()
if err != nil {
log.Printf("[WARN] sshagent.New: %v", err)
return nil
}
log.Println("[INFO] Using SSH Agent.")
return gossh.PublicKeysCallback(agent.Signers)
}

View File

@ -0,0 +1,151 @@
package communicator
import (
"bytes"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
)
// startAgent sets ssh-agent environment variables
func startAgent(t *testing.T) func() {
if testing.Short() {
// ssh-agent is not always available, and the key
// types supported vary by platform.
t.Skip("skipping test due to -short or availability")
}
bin, err := exec.LookPath("ssh-agent")
if err != nil {
t.Skip("could not find ssh-agent")
}
cmd := exec.Command(bin, "-s")
out, err := cmd.Output()
if err != nil {
t.Fatalf("cmd.Output: %v", err)
}
/* Output looks like:
SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
SSH_AGENT_PID=15542; export SSH_AGENT_PID;
echo Agent pid 15542;
*/
fields := bytes.Split(out, []byte(";"))
line := bytes.SplitN(fields[0], []byte("="), 2)
line[0] = bytes.TrimLeft(line[0], "\n")
if string(line[0]) != "SSH_AUTH_SOCK" {
t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
}
socket := string(line[1])
t.Logf("Socket value: %v", socket)
origSocket := os.Getenv("SSH_AUTH_SOCK")
if err := os.Setenv("SSH_AUTH_SOCK", socket); err != nil {
t.Fatalf("could not set SSH_AUTH_SOCK environment variable: %v", err)
}
line = bytes.SplitN(fields[2], []byte("="), 2)
line[0] = bytes.TrimLeft(line[0], "\n")
if string(line[0]) != "SSH_AGENT_PID" {
t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
}
pidStr := line[1]
t.Logf("Agent PID: %v", string(pidStr))
pid, err := strconv.Atoi(string(pidStr))
if err != nil {
t.Fatalf("Atoi(%q): %v", pidStr, err)
}
return func() {
proc, _ := os.FindProcess(pid)
if proc != nil {
proc.Kill()
}
os.Setenv("SSH_AUTH_SOCK", origSocket)
os.RemoveAll(filepath.Dir(socket))
}
}
func TestSSHAgent(t *testing.T) {
cleanup := startAgent(t)
defer cleanup()
if auth := sshAgent(); auth == nil {
t.Error("Want `ssh.AuthMethod`, got `nil`")
}
}
func TestSSHBastionConfig(t *testing.T) {
pemPath := TestPEM(t)
tests := []struct {
in *Config
errStr string
want int
fn func() func()
}{
{
in: &Config{SSHDisableAgent: true},
want: 0,
},
{
in: &Config{SSHDisableAgent: false},
want: 0,
fn: func() func() {
cleanup := startAgent(t)
os.Unsetenv("SSH_AUTH_SOCK")
return cleanup
},
},
{
in: &Config{
SSHDisableAgent: false,
SSHBastionPassword: "foobar",
SSHBastionPrivateKey: pemPath,
},
want: 4,
fn: func() func() {
cleanup := startAgent(t)
return cleanup
},
},
{
in: &Config{
SSHBastionPrivateKey: pemPath,
},
want: 0,
errStr: "Failed to read key '" + pemPath + "': no key found",
fn: func() func() {
os.Truncate(pemPath, 0)
return func() {
if err := os.Remove(pemPath); err != nil {
t.Fatalf("os.Remove: %v", err)
}
}
},
},
}
for _, c := range tests {
func() {
if c.fn != nil {
defered := c.fn()
defer defered()
}
bConf, err := sshBastionConfig(c.in)
if err != nil {
if err.Error() != c.errStr {
t.Errorf("want error %v, got %q", c.errStr, err)
}
return
}
if len(bConf.Auth) != c.want {
t.Errorf("want %v ssh.AuthMethod, got %v ssh.AuthMethod", c.want, len(bConf.Auth))
}
}()
}
}

View File

@ -1,6 +1,7 @@
package packer
import (
"bufio"
"bytes"
"errors"
"fmt"
@ -64,6 +65,7 @@ type BasicUi struct {
ErrorWriter io.Writer
l sync.Mutex
interrupted bool
scanner *bufio.Scanner
}
// MachineReadableUi is a UI that only outputs machine-readable output
@ -174,6 +176,9 @@ func (rw *BasicUi) Ask(query string) (string, error) {
return "", errors.New("interrupted")
}
if rw.scanner == nil {
rw.scanner = bufio.NewScanner(rw.Reader)
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
defer signal.Stop(sigCh)
@ -188,10 +193,13 @@ func (rw *BasicUi) Ask(query string) (string, error) {
result := make(chan string, 1)
go func() {
var line string
if _, err := fmt.Fscanln(rw.Reader, &line); err != nil {
log.Printf("ui: scan err: %s", err)
if rw.scanner.Scan() {
line = rw.scanner.Text()
}
if err := rw.scanner.Err(); err != nil {
log.Printf("ui: scan err: %s", err)
return
}
result <- line
}()

View File

@ -16,6 +16,12 @@ func readWriter(ui *BasicUi) (result string) {
return
}
// Reset the input Reader than add some input to it.
func writeReader(ui *BasicUi, input string) {
buffer := ui.Reader.(*bytes.Buffer)
buffer.WriteString(input)
}
func readErrorWriter(ui *BasicUi) (result string) {
buffer := ui.ErrorWriter.(*bytes.Buffer)
result = buffer.String()
@ -192,6 +198,45 @@ func TestBasicUi_Say(t *testing.T) {
}
}
func TestBasicUi_Ask(t *testing.T) {
var actual, expected string
var err error
var testCases = []struct {
Prompt, Input, Answer string
}{
{"[c]ontinue or [a]bort", "c\n", "c"},
{"[c]ontinue or [a]bort", "c", "c"},
// Empty input shouldn't give an error
{"Name", "Joe Bloggs\n", "Joe Bloggs"},
{"Name", "Joe Bloggs", "Joe Bloggs"},
{"Name", "\n", ""},
}
for _, testCase := range testCases {
// Because of the internal bufio we can't eaily reset the input, so create a new one each time
bufferUi := testUi()
writeReader(bufferUi, testCase.Input)
actual, err = bufferUi.Ask(testCase.Prompt)
if err != nil {
t.Fatal(err)
}
if actual != testCase.Answer {
t.Fatalf("bad answer: %#v", actual)
}
actual = readWriter(bufferUi)
expected = testCase.Prompt + " "
if actual != expected {
t.Fatalf("bad prompt: %#v", actual)
}
}
}
func TestMachineReadableUi_ImplUi(t *testing.T) {
var raw interface{}
raw = &MachineReadableUi{}

View File

@ -26,11 +26,14 @@ type Config struct {
awscommon.AccessConfig `mapstructure:",squash"`
// Variables specific to this post processor
S3Bucket string `mapstructure:"s3_bucket_name"`
S3Key string `mapstructure:"s3_key_name"`
SkipClean bool `mapstructure:"skip_clean"`
Tags map[string]string `mapstructure:"tags"`
Name string `mapstructure:"ami_name"`
S3Bucket string `mapstructure:"s3_bucket_name"`
S3Key string `mapstructure:"s3_key_name"`
SkipClean bool `mapstructure:"skip_clean"`
Tags map[string]string `mapstructure:"tags"`
Name string `mapstructure:"ami_name"`
Description string `mapstructure:"ami_description"`
Users []string `mapstructure:"ami_users"`
Groups []string `mapstrcuture:"ami_groups"`
ctx interpolate.Context
}
@ -304,6 +307,60 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
}
// Apply atttributes for AMI specified in config
// (duped from builder/amazon/common/step_modify_ami_attributes.go)
options := make(map[string]*ec2.ModifyImageAttributeInput)
if p.config.Description != "" {
options["description"] = &ec2.ModifyImageAttributeInput{
Description: &ec2.AttributeValue{Value: &p.config.Description},
}
}
if len(p.config.Groups) > 0 {
groups := make([]*string, len(p.config.Groups))
adds := make([]*ec2.LaunchPermission, len(p.config.Groups))
addGroups := &ec2.ModifyImageAttributeInput{
LaunchPermission: &ec2.LaunchPermissionModifications{},
}
for i, g := range p.config.Groups {
groups[i] = aws.String(g)
adds[i] = &ec2.LaunchPermission{
Group: aws.String(g),
}
}
addGroups.UserGroups = groups
addGroups.LaunchPermission.Add = adds
options["groups"] = addGroups
}
if len(p.config.Users) > 0 {
users := make([]*string, len(p.config.Users))
adds := make([]*ec2.LaunchPermission, len(p.config.Users))
for i, u := range p.config.Users {
users[i] = aws.String(u)
adds[i] = &ec2.LaunchPermission{UserId: aws.String(u)}
}
options["users"] = &ec2.ModifyImageAttributeInput{
UserIds: users,
LaunchPermission: &ec2.LaunchPermissionModifications{
Add: adds,
},
}
}
if len(options) > 0 {
for name, input := range options {
ui.Message(fmt.Sprintf("Modifying: %s", name))
input.ImageId = &createdami
_, err := ec2conn.ModifyImageAttribute(input)
if err != nil {
return nil, false, fmt.Errorf("Error modifying AMI attributes: %s", err)
}
}
}
// Add the reported AMI ID to the artifact list
log.Printf("Adding created AMI ID %s in region %s to output artifacts", createdami, *config.Region)
artifact = &awscommon.Artifact{

View File

@ -110,9 +110,10 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
return nil, false, fmt.Errorf("VMX, OVF or OVA file not found")
}
password := url.QueryEscape(p.config.Password)
ovftool_uri := fmt.Sprintf("vi://%s:%s@%s/%s/host/%s",
url.QueryEscape(p.config.Username),
url.QueryEscape(p.config.Password),
password,
p.config.Host,
p.config.Datacenter,
p.config.Cluster)
@ -128,7 +129,12 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
ui.Message(fmt.Sprintf("Uploading %s to vSphere", source))
log.Printf("Starting ovftool with parameters: %s", strings.Join(args, " "))
log.Printf("Starting ovftool with parameters: %s",
strings.Replace(
strings.Join(args, " "),
password,
"<password>",
-1))
cmd := exec.Command("ovftool", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

View File

@ -8,6 +8,7 @@ import (
"strings"
"github.com/mitchellh/packer/common"
"github.com/mitchellh/packer/common/uuid"
"github.com/mitchellh/packer/helper/config"
"github.com/mitchellh/packer/packer"
"github.com/mitchellh/packer/template/interpolate"
@ -85,7 +86,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
if p.config.StagingDir == "" {
p.config.StagingDir = DefaultStagingDir
p.config.StagingDir = filepath.Join(DefaultStagingDir, uuid.TimeOrderedUUID())
}
// Validation

View File

@ -1,10 +1,13 @@
package ansiblelocal
import (
"github.com/mitchellh/packer/packer"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/mitchellh/packer/packer"
)
func testConfig() map[string]interface{} {
@ -36,7 +39,7 @@ func TestProvisionerPrepare_Defaults(t *testing.T) {
t.Fatalf("err: %s", err)
}
if p.config.StagingDir != DefaultStagingDir {
if !strings.HasPrefix(filepath.ToSlash(p.config.StagingDir), DefaultStagingDir) {
t.Fatalf("unexpected staging dir %s, expected %s",
p.config.StagingDir, DefaultStagingDir)
}

View File

@ -280,20 +280,25 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
err = p.executeChef(ui, comm, configPath, jsonPath)
knifeConfigPath, err2 := p.createKnifeConfig(
ui, comm, nodeName, serverUrl, p.config.ClientKey, p.config.SslVerifyMode)
if err2 != nil {
return fmt.Errorf("Error creating knife config on node: %s", err2)
}
if !p.config.SkipCleanNode {
if err2 := p.cleanNode(ui, comm, nodeName, knifeConfigPath); err2 != nil {
return fmt.Errorf("Error cleaning up chef node: %s", err2)
}
}
if !(p.config.SkipCleanNode && p.config.SkipCleanClient) {
if !p.config.SkipCleanClient {
if err2 := p.cleanClient(ui, comm, nodeName, knifeConfigPath); err2 != nil {
return fmt.Errorf("Error cleaning up chef client: %s", err2)
knifeConfigPath, knifeErr := p.createKnifeConfig(
ui, comm, nodeName, serverUrl, p.config.ClientKey, p.config.SslVerifyMode)
if knifeErr != nil {
return fmt.Errorf("Error creating knife config on node: %s", knifeErr)
}
if !p.config.SkipCleanNode {
if err := p.cleanNode(ui, comm, nodeName, knifeConfigPath); err != nil {
return fmt.Errorf("Error cleaning up chef node: %s", err)
}
}
if !p.config.SkipCleanClient {
if err := p.cleanClient(ui, comm, nodeName, knifeConfigPath); err != nil {
return fmt.Errorf("Error cleaning up chef client: %s", err)
}
}
}

View File

@ -14,7 +14,7 @@ type elevatedOptions struct {
var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
$name = "{{.TaskName}}"
$log = "$env:TEMP\$name.out"
$log = "$env:SystemRoot\Temp\$name.out"
$s = New-Object -ComObject "Schedule.Service"
$s.Connect()
$t = $s.NewTask($null)
@ -53,7 +53,7 @@ $t.XmlText = @'
<Actions Context="Author">
<Exec>
<Command>cmd</Command>
<Arguments>/c powershell.exe -EncodedCommand {{.EncodedCommand}} &gt; %TEMP%\{{.TaskName}}.out 2&gt;&amp;1</Arguments>
<Arguments>/c powershell.exe -EncodedCommand {{.EncodedCommand}} &gt; %SYSTEMROOT%\Temp\{{.TaskName}}.out 2&gt;&amp;1</Arguments>
</Exec>
</Actions>
</Task>
@ -81,5 +81,8 @@ do {
}
} while (!($t.state -eq 3))
$result = $t.LastTaskResult
if (Test-Path $log) {
Remove-Item $log -Force -ErrorAction SilentlyContinue | Out-Null
}
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
exit $result`))

View File

@ -32,11 +32,11 @@ func TestProvisionerPrepare_Defaults(t *testing.T) {
}
if p.config.RestartTimeout != 5*time.Minute {
t.Errorf("unexpected remote path: %s", p.config.RestartTimeout)
t.Errorf("unexpected restart timeout: %s", p.config.RestartTimeout)
}
if p.config.RestartCommand != "shutdown /r /f /t 0 /c \"packer restart\"" {
t.Errorf("unexpected remote path: %s", p.config.RestartCommand)
t.Errorf("unexpected restart command: %s", p.config.RestartCommand)
}
}
@ -51,7 +51,7 @@ func TestProvisionerPrepare_ConfigRetryTimeout(t *testing.T) {
}
if p.config.RestartTimeout != 1*time.Minute {
t.Errorf("unexpected remote path: %s", p.config.RestartTimeout)
t.Errorf("unexpected restart timeout: %s", p.config.RestartTimeout)
}
}

View File

@ -138,12 +138,12 @@ func funcGenTimestamp(ctx *Context) interface{} {
}
func funcGenUser(ctx *Context) interface{} {
return func(k string) string {
return func(k string) (string, error) {
if ctx == nil || ctx.UserVariables == nil {
return ""
return "", errors.New("test")
}
return ctx.UserVariables[k]
return ctx.UserVariables[k], nil
}
}

View File

@ -41,6 +41,7 @@ type clientRequest struct {
func (c *clientRequest) Transport(endpoint *Endpoint) error {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: endpoint.Insecure,
},

202
vendor/github.com/xanzy/ssh-agent/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

23
vendor/github.com/xanzy/ssh-agent/README.md generated vendored Normal file
View File

@ -0,0 +1,23 @@
# ssh-agent
Create a new [agent.Agent](https://godoc.org/golang.org/x/crypto/ssh/agent#Agent) on any type of OS (so including Windows) from any [Go](https://golang.org) application.
## Limitations
When compiled for Windows, it will only support [Pageant](http://the.earth.li/~sgtatham/putty/0.66/htmldoc/Chapter9.html#pageant) as the SSH authentication agent.
## Credits
Big thanks to [Давид Мзареулян (David Mzareulyan)](https://github.com/davidmz) for creating the [go-pageant](https://github.com/davidmz/go-pageant) package!
## Issues
If you have an issue: report it on the [issue tracker](https://github.com/xanzy/ssh-agent/issues)
## Author
Sander van Harmelen (<sander@xanzy.io>)
## License
The files `pageant_windows.go` and `sshagent_windows.go` have their own license (see file headers). The rest of this package is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>

146
vendor/github.com/xanzy/ssh-agent/pageant_windows.go generated vendored Normal file
View File

@ -0,0 +1,146 @@
//
// Copyright (c) 2014 David Mzareulyan
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// +build windows
package sshagent
// see https://github.com/Yasushi/putty/blob/master/windows/winpgntc.c#L155
// see https://github.com/paramiko/paramiko/blob/master/paramiko/win_pageant.py
import (
"encoding/binary"
"errors"
"fmt"
"sync"
. "syscall"
. "unsafe"
)
// Maximum size of message can be sent to pageant
const MaxMessageLen = 8192
var (
ErrPageantNotFound = errors.New("pageant process not found")
ErrSendMessage = errors.New("error sending message")
ErrMessageTooLong = errors.New("message too long")
ErrInvalidMessageFormat = errors.New("invalid message format")
ErrResponseTooLong = errors.New("response too long")
)
const (
agentCopydataID = 0x804e50ba
wmCopydata = 74
)
type copyData struct {
dwData uintptr
cbData uint32
lpData Pointer
}
var (
lock sync.Mutex
winFindWindow = winAPI("user32.dll", "FindWindowW")
winGetCurrentThreadID = winAPI("kernel32.dll", "GetCurrentThreadId")
winSendMessage = winAPI("user32.dll", "SendMessageW")
)
func winAPI(dllName, funcName string) func(...uintptr) (uintptr, uintptr, error) {
proc := MustLoadDLL(dllName).MustFindProc(funcName)
return func(a ...uintptr) (uintptr, uintptr, error) { return proc.Call(a...) }
}
// Available returns true if Pageant is running
func Available() bool { return pageantWindow() != 0 }
// Query sends message msg to Pageant and returns response or error.
// 'msg' is raw agent request with length prefix
// Response is raw agent response with length prefix
func query(msg []byte) ([]byte, error) {
if len(msg) > MaxMessageLen {
return nil, ErrMessageTooLong
}
msgLen := binary.BigEndian.Uint32(msg[:4])
if len(msg) != int(msgLen)+4 {
return nil, ErrInvalidMessageFormat
}
lock.Lock()
defer lock.Unlock()
paWin := pageantWindow()
if paWin == 0 {
return nil, ErrPageantNotFound
}
thID, _, _ := winGetCurrentThreadID()
mapName := fmt.Sprintf("PageantRequest%08x", thID)
pMapName, _ := UTF16PtrFromString(mapName)
mmap, err := CreateFileMapping(InvalidHandle, nil, PAGE_READWRITE, 0, MaxMessageLen+4, pMapName)
if err != nil {
return nil, err
}
defer CloseHandle(mmap)
ptr, err := MapViewOfFile(mmap, FILE_MAP_WRITE, 0, 0, 0)
if err != nil {
return nil, err
}
defer UnmapViewOfFile(ptr)
mmSlice := (*(*[MaxMessageLen]byte)(Pointer(ptr)))[:]
copy(mmSlice, msg)
mapNameBytesZ := append([]byte(mapName), 0)
cds := copyData{
dwData: agentCopydataID,
cbData: uint32(len(mapNameBytesZ)),
lpData: Pointer(&(mapNameBytesZ[0])),
}
resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(Pointer(&cds)))
if resp == 0 {
return nil, ErrSendMessage
}
respLen := binary.BigEndian.Uint32(mmSlice[:4])
if respLen > MaxMessageLen-4 {
return nil, ErrResponseTooLong
}
respData := make([]byte, respLen+4)
copy(respData, mmSlice)
return respData, nil
}
func pageantWindow() uintptr {
nameP, _ := UTF16PtrFromString("Pageant")
h, _, _ := winFindWindow(uintptr(Pointer(nameP)), uintptr(Pointer(nameP)))
return h
}

49
vendor/github.com/xanzy/ssh-agent/sshagent.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
//
// Copyright 2015, Sander van Harmelen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// +build !windows
package sshagent
import (
"errors"
"fmt"
"net"
"os"
"golang.org/x/crypto/ssh/agent"
)
// New returns a new agent.Agent that uses a unix socket
func New() (agent.Agent, net.Conn, error) {
if !Available() {
return nil, nil, errors.New("SSH agent requested but SSH_AUTH_SOCK not-specified")
}
sshAuthSock := os.Getenv("SSH_AUTH_SOCK")
conn, err := net.Dial("unix", sshAuthSock)
if err != nil {
return nil, nil, fmt.Errorf("Error connecting to SSH_AUTH_SOCK: %v", err)
}
return agent.NewClient(conn), conn, nil
}
// Available returns true is a auth socket is defined
func Available() bool {
return os.Getenv("SSH_AUTH_SOCK") != ""
}

80
vendor/github.com/xanzy/ssh-agent/sshagent_windows.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
//
// Copyright (c) 2014 David Mzareulyan
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// +build windows
package sshagent
import (
"errors"
"io"
"net"
"sync"
"golang.org/x/crypto/ssh/agent"
)
// New returns a new agent.Agent and the (custom) connection it uses
// to communicate with a running pagent.exe instance (see README.md)
func New() (agent.Agent, net.Conn, error) {
if !Available() {
return nil, nil, errors.New("SSH agent requested but Pageant not running")
}
return agent.NewClient(&conn{}), nil, nil
}
type conn struct {
sync.Mutex
buf []byte
}
func (c *conn) Close() {
c.Lock()
defer c.Unlock()
c.buf = nil
}
func (c *conn) Write(p []byte) (int, error) {
c.Lock()
defer c.Unlock()
resp, err := query(p)
if err != nil {
return 0, err
}
c.buf = append(c.buf, resp...)
return len(p), nil
}
func (c *conn) Read(p []byte) (int, error) {
c.Lock()
defer c.Unlock()
if len(c.buf) == 0 {
return 0, io.EOF
}
n := copy(p, c.buf)
c.buf = c.buf[n:]
return n, nil
}

View File

@ -461,8 +461,8 @@ func (m *mux) newChannel(chanType string, direction channelDirection, extraData
pending: newBuffer(),
extPending: newBuffer(),
direction: direction,
incomingRequests: make(chan *Request, 16),
msg: make(chan interface{}, 16),
incomingRequests: make(chan *Request, chanSize),
msg: make(chan interface{}, chanSize),
chanType: chanType,
extraData: extraData,
mux: m,

View File

@ -135,6 +135,7 @@ const prefixLen = 5
type streamPacketCipher struct {
mac hash.Hash
cipher cipher.Stream
etm bool
// The following members are to avoid per-packet allocations.
prefix [prefixLen]byte
@ -150,7 +151,14 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
return nil, err
}
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
var encryptedPaddingLength [1]byte
if s.mac != nil && s.etm {
copy(encryptedPaddingLength[:], s.prefix[4:5])
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
} else {
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
}
length := binary.BigEndian.Uint32(s.prefix[0:4])
paddingLength := uint32(s.prefix[4])
@ -159,7 +167,12 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
s.mac.Write(s.prefix[:])
if s.etm {
s.mac.Write(s.prefix[:4])
s.mac.Write(encryptedPaddingLength[:])
} else {
s.mac.Write(s.prefix[:])
}
macSize = uint32(s.mac.Size())
}
@ -184,10 +197,17 @@ func (s *streamPacketCipher) readPacket(seqNum uint32, r io.Reader) ([]byte, err
}
mac := s.packetData[length-1:]
data := s.packetData[:length-1]
if s.mac != nil && s.etm {
s.mac.Write(data)
}
s.cipher.XORKeyStream(data, data)
if s.mac != nil {
s.mac.Write(data)
if !s.etm {
s.mac.Write(data)
}
s.macResult = s.mac.Sum(s.macResult[:0])
if subtle.ConstantTimeCompare(s.macResult, mac) != 1 {
return nil, errors.New("ssh: MAC failure")
@ -203,7 +223,13 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
return errors.New("ssh: packet too large")
}
paddingLength := packetSizeMultiple - (prefixLen+len(packet))%packetSizeMultiple
aadlen := 0
if s.mac != nil && s.etm {
// packet length is not encrypted for EtM modes
aadlen = 4
}
paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple
if paddingLength < 4 {
paddingLength += packetSizeMultiple
}
@ -220,15 +246,37 @@ func (s *streamPacketCipher) writePacket(seqNum uint32, w io.Writer, rand io.Rea
s.mac.Reset()
binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum)
s.mac.Write(s.seqNumBytes[:])
if s.etm {
// For EtM algorithms, the packet length must stay unencrypted,
// but the following data (padding length) must be encrypted
s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5])
}
s.mac.Write(s.prefix[:])
if !s.etm {
// For non-EtM algorithms, the algorithm is applied on unencrypted data
s.mac.Write(packet)
s.mac.Write(padding)
}
}
if !(s.mac != nil && s.etm) {
// For EtM algorithms, the padding length has already been encrypted
// and the packet length must remain unencrypted
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
}
s.cipher.XORKeyStream(packet, packet)
s.cipher.XORKeyStream(padding, padding)
if s.mac != nil && s.etm {
// For EtM algorithms, packet and padding must be encrypted
s.mac.Write(packet)
s.mac.Write(padding)
}
s.cipher.XORKeyStream(s.prefix[:], s.prefix[:])
s.cipher.XORKeyStream(packet, packet)
s.cipher.XORKeyStream(padding, padding)
if _, err := w.Write(s.prefix[:]); err != nil {
return err
}

View File

@ -40,7 +40,7 @@ func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel {
return nil
}
ch = make(chan NewChannel, 16)
ch = make(chan NewChannel, chanSize)
c.channelHandlers[channelType] = ch
return ch
}
@ -97,13 +97,11 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
c.transport = newClientTransport(
newTransport(c.sshConn.conn, config.Rand, true /* is client */),
c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr())
if err := c.transport.requestInitialKeyChange(); err != nil {
if err := c.transport.waitSession(); err != nil {
return err
}
// We just did the key change, so the session ID is established.
c.sessionID = c.transport.getSessionID()
return c.clientAuthenticate(config)
}

View File

@ -30,8 +30,10 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
// then any untried methods suggested by the server.
tried := make(map[string]bool)
var lastMethods []string
sessionID := c.transport.getSessionID()
for auth := AuthMethod(new(noneAuth)); auth != nil; {
ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand)
ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
if err != nil {
return err
}

View File

@ -56,7 +56,7 @@ var supportedHostKeyAlgos = []string{
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
// because they have reached the end of their useful life.
var supportedMACs = []string{
"hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96",
}
var supportedCompressions = []string{compressionNone}
@ -104,6 +104,21 @@ type directionAlgorithms struct {
Compression string
}
// rekeyBytes returns a rekeying intervals in bytes.
func (a *directionAlgorithms) rekeyBytes() int64 {
// According to RFC4344 block ciphers should rekey after
// 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is
// 128.
switch a.Cipher {
case "aes128-ctr", "aes192-ctr", "aes256-ctr", gcmCipherID, aes128cbcID:
return 16 * (1 << 32)
}
// For others, stick with RFC4253 recommendation to rekey after 1 Gb of data.
return 1 << 30
}
type algorithms struct {
kex string
hostKey string

View File

@ -19,6 +19,11 @@ import (
// messages are wrong when using ECDH.
const debugHandshake = false
// chanSize sets the amount of buffering SSH connections. This is
// primarily for testing: setting chanSize=0 uncovers deadlocks more
// quickly.
const chanSize = 16
// keyingTransport is a packet based transport that supports key
// changes. It need not be thread-safe. It should pass through
// msgNewKeys in both directions.
@ -53,34 +58,58 @@ type handshakeTransport struct {
incoming chan []byte
readError error
mu sync.Mutex
writeError error
sentInitPacket []byte
sentInitMsg *kexInitMsg
pendingPackets [][]byte // Used when a key exchange is in progress.
// If the read loop wants to schedule a kex, it pings this
// channel, and the write loop will send out a kex
// message.
requestKex chan struct{}
// If the other side requests or confirms a kex, its kexInit
// packet is sent here for the write loop to find it.
startKex chan *pendingKex
// data for host key checking
hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error
dialAddress string
remoteAddr net.Addr
readSinceKex uint64
// Algorithms agreed in the last key exchange.
algorithms *algorithms
// Protects the writing side of the connection
mu sync.Mutex
cond *sync.Cond
sentInitPacket []byte
sentInitMsg *kexInitMsg
writtenSinceKex uint64
writeError error
readPacketsLeft uint32
readBytesLeft int64
writePacketsLeft uint32
writeBytesLeft int64
// The session ID or nil if first kex did not complete yet.
sessionID []byte
}
type pendingKex struct {
otherInit []byte
done chan error
}
func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport {
t := &handshakeTransport{
conn: conn,
serverVersion: serverVersion,
clientVersion: clientVersion,
incoming: make(chan []byte, 16),
config: config,
incoming: make(chan []byte, chanSize),
requestKex: make(chan struct{}, 1),
startKex: make(chan *pendingKex, 1),
config: config,
}
t.cond = sync.NewCond(&t.mu)
// We always start with a mandatory key exchange.
t.requestKex <- struct{}{}
return t
}
@ -95,6 +124,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
t.hostKeyAlgorithms = supportedHostKeyAlgos
}
go t.readLoop()
go t.kexLoop()
return t
}
@ -102,6 +132,7 @@ func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byt
t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion)
t.hostKeys = config.hostKeys
go t.readLoop()
go t.kexLoop()
return t
}
@ -109,6 +140,20 @@ func (t *handshakeTransport) getSessionID() []byte {
return t.sessionID
}
// waitSession waits for the session to be established. This should be
// the first thing to call after instantiating handshakeTransport.
func (t *handshakeTransport) waitSession() error {
p, err := t.readPacket()
if err != nil {
return err
}
if p[0] != msgNewKeys {
return fmt.Errorf("ssh: first packet should be msgNewKeys")
}
return nil
}
func (t *handshakeTransport) id() string {
if len(t.hostKeys) > 0 {
return "server"
@ -116,6 +161,20 @@ func (t *handshakeTransport) id() string {
return "client"
}
func (t *handshakeTransport) printPacket(p []byte, write bool) {
action := "got"
if write {
action = "sent"
}
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p))
} else {
msg, err := decode(p)
log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err)
}
}
func (t *handshakeTransport) readPacket() ([]byte, error) {
p, ok := <-t.incoming
if !ok {
@ -125,8 +184,10 @@ func (t *handshakeTransport) readPacket() ([]byte, error) {
}
func (t *handshakeTransport) readLoop() {
first := true
for {
p, err := t.readOnePacket()
p, err := t.readOnePacket(first)
first = false
if err != nil {
t.readError = err
close(t.incoming)
@ -138,67 +199,204 @@ func (t *handshakeTransport) readLoop() {
t.incoming <- p
}
// If we can't read, declare the writing part dead too.
t.mu.Lock()
defer t.mu.Unlock()
if t.writeError == nil {
t.writeError = t.readError
}
t.cond.Broadcast()
// Stop writers too.
t.recordWriteError(t.readError)
// Unblock the writer should it wait for this.
close(t.startKex)
// Don't close t.requestKex; it's also written to from writePacket.
}
func (t *handshakeTransport) readOnePacket() ([]byte, error) {
if t.readSinceKex > t.config.RekeyThreshold {
if err := t.requestKeyChange(); err != nil {
return nil, err
func (t *handshakeTransport) pushPacket(p []byte) error {
if debugHandshake {
t.printPacket(p, true)
}
return t.conn.writePacket(p)
}
func (t *handshakeTransport) getWriteError() error {
t.mu.Lock()
defer t.mu.Unlock()
return t.writeError
}
func (t *handshakeTransport) recordWriteError(err error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.writeError == nil && err != nil {
t.writeError = err
}
}
func (t *handshakeTransport) requestKeyExchange() {
select {
case t.requestKex <- struct{}{}:
default:
// something already requested a kex, so do nothing.
}
}
func (t *handshakeTransport) kexLoop() {
write:
for t.getWriteError() == nil {
var request *pendingKex
var sent bool
for request == nil || !sent {
var ok bool
select {
case request, ok = <-t.startKex:
if !ok {
break write
}
case <-t.requestKex:
break
}
if !sent {
if err := t.sendKexInit(); err != nil {
t.recordWriteError(err)
break
}
sent = true
}
}
if err := t.getWriteError(); err != nil {
if request != nil {
request.done <- err
}
break
}
// We're not servicing t.requestKex, but that is OK:
// we never block on sending to t.requestKex.
// We're not servicing t.startKex, but the remote end
// has just sent us a kexInitMsg, so it can't send
// another key change request, until we close the done
// channel on the pendingKex request.
err := t.enterKeyExchange(request.otherInit)
t.mu.Lock()
t.writeError = err
t.sentInitPacket = nil
t.sentInitMsg = nil
t.writePacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 {
t.writeBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
}
// we have completed the key exchange. Since the
// reader is still blocked, it is safe to clear out
// the requestKex channel. This avoids the situation
// where: 1) we consumed our own request for the
// initial kex, and 2) the kex from the remote side
// caused another send on the requestKex channel,
clear:
for {
select {
case <-t.requestKex:
//
default:
break clear
}
}
request.done <- t.writeError
// kex finished. Push packets that we received while
// the kex was in progress. Don't look at t.startKex
// and don't increment writtenSinceKex: if we trigger
// another kex while we are still busy with the last
// one, things will become very confusing.
for _, p := range t.pendingPackets {
t.writeError = t.pushPacket(p)
if t.writeError != nil {
break
}
}
t.pendingPackets = t.pendingPackets[:0]
t.mu.Unlock()
}
// drain startKex channel. We don't service t.requestKex
// because nobody does blocking sends there.
go func() {
for init := range t.startKex {
init.done <- t.writeError
}
}()
// Unblock reader.
t.conn.Close()
}
// The protocol uses uint32 for packet counters, so we can't let them
// reach 1<<32. We will actually read and write more packets than
// this, though: the other side may send more packets, and after we
// hit this limit on writing we will send a few more packets for the
// key exchange itself.
const packetRekeyThreshold = (1 << 31)
func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) {
p, err := t.conn.readPacket()
if err != nil {
return nil, err
}
t.readSinceKex += uint64(len(p))
if debugHandshake {
if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
log.Printf("%s got data (packet %d bytes)", t.id(), len(p))
} else {
msg, err := decode(p)
log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err)
}
if t.readPacketsLeft > 0 {
t.readPacketsLeft--
} else {
t.requestKeyExchange()
}
if t.readBytesLeft > 0 {
t.readBytesLeft -= int64(len(p))
} else {
t.requestKeyExchange()
}
if debugHandshake {
t.printPacket(p, false)
}
if first && p[0] != msgKexInit {
return nil, fmt.Errorf("ssh: first packet should be msgKexInit")
}
if p[0] != msgKexInit {
return p, nil
}
t.mu.Lock()
firstKex := t.sessionID == nil
err = t.enterKeyExchangeLocked(p)
if err != nil {
// drop connection
t.conn.Close()
t.writeError = err
kex := pendingKex{
done: make(chan error, 1),
otherInit: p,
}
t.startKex <- &kex
err = <-kex.done
if debugHandshake {
log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err)
}
// Unblock writers.
t.sentInitMsg = nil
t.sentInitPacket = nil
t.cond.Broadcast()
t.writtenSinceKex = 0
t.mu.Unlock()
if err != nil {
return nil, err
}
t.readSinceKex = 0
t.readPacketsLeft = packetRekeyThreshold
if t.config.RekeyThreshold > 0 {
t.readBytesLeft = int64(t.config.RekeyThreshold)
} else {
t.readBytesLeft = t.algorithms.r.rekeyBytes()
}
// By default, a key exchange is hidden from higher layers by
// translating it into msgIgnore.
@ -213,61 +411,16 @@ func (t *handshakeTransport) readOnePacket() ([]byte, error) {
return successPacket, nil
}
// keyChangeCategory describes whether a key exchange is the first on a
// connection, or a subsequent one.
type keyChangeCategory bool
const (
firstKeyExchange keyChangeCategory = true
subsequentKeyExchange keyChangeCategory = false
)
// sendKexInit sends a key change message, and returns the message
// that was sent. After initiating the key change, all writes will be
// blocked until the change is done, and a failed key change will
// close the underlying transport. This function is safe for
// concurrent use by multiple goroutines.
func (t *handshakeTransport) sendKexInit(isFirst keyChangeCategory) error {
var err error
// sendKexInit sends a key change message.
func (t *handshakeTransport) sendKexInit() error {
t.mu.Lock()
// If this is the initial key change, but we already have a sessionID,
// then do nothing because the key exchange has already completed
// asynchronously.
if !isFirst || t.sessionID == nil {
_, _, err = t.sendKexInitLocked(isFirst)
}
t.mu.Unlock()
if err != nil {
return err
}
if isFirst {
if packet, err := t.readPacket(); err != nil {
return err
} else if packet[0] != msgNewKeys {
return unexpectedMessageError(msgNewKeys, packet[0])
}
}
return nil
}
func (t *handshakeTransport) requestInitialKeyChange() error {
return t.sendKexInit(firstKeyExchange)
}
func (t *handshakeTransport) requestKeyChange() error {
return t.sendKexInit(subsequentKeyExchange)
}
// sendKexInitLocked sends a key change message. t.mu must be locked
// while this happens.
func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexInitMsg, []byte, error) {
// kexInits may be sent either in response to the other side,
// or because our side wants to initiate a key change, so we
// may have already sent a kexInit. In that case, don't send a
// second kexInit.
defer t.mu.Unlock()
if t.sentInitMsg != nil {
return t.sentInitMsg, t.sentInitPacket, nil
// kexInits may be sent either in response to the other side,
// or because our side wants to initiate a key change, so we
// may have already sent a kexInit. In that case, don't send a
// second kexInit.
return nil
}
msg := &kexInitMsg{
@ -295,53 +448,65 @@ func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexI
packetCopy := make([]byte, len(packet))
copy(packetCopy, packet)
if err := t.conn.writePacket(packetCopy); err != nil {
return nil, nil, err
if err := t.pushPacket(packetCopy); err != nil {
return err
}
t.sentInitMsg = msg
t.sentInitPacket = packet
return msg, packet, nil
return nil
}
func (t *handshakeTransport) writePacket(p []byte) error {
t.mu.Lock()
defer t.mu.Unlock()
if t.writtenSinceKex > t.config.RekeyThreshold {
t.sendKexInitLocked(subsequentKeyExchange)
}
for t.sentInitMsg != nil && t.writeError == nil {
t.cond.Wait()
}
if t.writeError != nil {
return t.writeError
}
t.writtenSinceKex += uint64(len(p))
switch p[0] {
case msgKexInit:
return errors.New("ssh: only handshakeTransport can send kexInit")
case msgNewKeys:
return errors.New("ssh: only handshakeTransport can send newKeys")
default:
return t.conn.writePacket(p)
}
t.mu.Lock()
defer t.mu.Unlock()
if t.writeError != nil {
return t.writeError
}
if t.sentInitMsg != nil {
// Copy the packet so the writer can reuse the buffer.
cp := make([]byte, len(p))
copy(cp, p)
t.pendingPackets = append(t.pendingPackets, cp)
return nil
}
if t.writeBytesLeft > 0 {
t.writeBytesLeft -= int64(len(p))
} else {
t.requestKeyExchange()
}
if t.writePacketsLeft > 0 {
t.writePacketsLeft--
} else {
t.requestKeyExchange()
}
if err := t.pushPacket(p); err != nil {
t.writeError = err
}
return nil
}
func (t *handshakeTransport) Close() error {
return t.conn.Close()
}
// enterKeyExchange runs the key exchange. t.mu must be held while running this.
func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) error {
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
if debugHandshake {
log.Printf("%s entered key exchange", t.id())
}
myInit, myInitPacket, err := t.sendKexInitLocked(subsequentKeyExchange)
if err != nil {
return err
}
otherInit := &kexInitMsg{}
if err := Unmarshal(otherInitPacket, otherInit); err != nil {
@ -352,20 +517,20 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro
clientVersion: t.clientVersion,
serverVersion: t.serverVersion,
clientKexInit: otherInitPacket,
serverKexInit: myInitPacket,
serverKexInit: t.sentInitPacket,
}
clientInit := otherInit
serverInit := myInit
serverInit := t.sentInitMsg
if len(t.hostKeys) == 0 {
clientInit = myInit
serverInit = otherInit
clientInit, serverInit = serverInit, clientInit
magics.clientKexInit = myInitPacket
magics.clientKexInit = t.sentInitPacket
magics.serverKexInit = otherInitPacket
}
algs, err := findAgreedAlgorithms(clientInit, serverInit)
var err error
t.algorithms, err = findAgreedAlgorithms(clientInit, serverInit)
if err != nil {
return err
}
@ -388,16 +553,16 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro
}
}
kex, ok := kexAlgoMap[algs.kex]
kex, ok := kexAlgoMap[t.algorithms.kex]
if !ok {
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex)
}
var result *kexResult
if len(t.hostKeys) > 0 {
result, err = t.server(kex, algs, &magics)
result, err = t.server(kex, t.algorithms, &magics)
} else {
result, err = t.client(kex, algs, &magics)
result, err = t.client(kex, t.algorithms, &magics)
}
if err != nil {
@ -409,7 +574,7 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro
}
result.SessionID = t.sessionID
t.conn.prepareKeyChange(algs, result)
t.conn.prepareKeyChange(t.algorithms, result)
if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil {
return err
}

View File

@ -10,10 +10,13 @@ import (
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/md5"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
@ -795,8 +798,8 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
P *big.Int
Q *big.Int
G *big.Int
Priv *big.Int
Pub *big.Int
Priv *big.Int
}
rest, err := asn1.Unmarshal(der, &k)
if err != nil {
@ -813,9 +816,9 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
Q: k.Q,
G: k.G,
},
Y: k.Priv,
Y: k.Pub,
},
X: k.Pub,
X: k.Priv,
}, nil
}
@ -878,3 +881,25 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) {
copy(pk, pk1.Priv)
return &pk, nil
}
// FingerprintLegacyMD5 returns the user presentation of the key's
// fingerprint as described by RFC 4716 section 4.
func FingerprintLegacyMD5(pubKey PublicKey) string {
md5sum := md5.Sum(pubKey.Marshal())
hexarray := make([]string, len(md5sum))
for i, c := range md5sum {
hexarray[i] = hex.EncodeToString([]byte{c})
}
return strings.Join(hexarray, ":")
}
// FingerprintSHA256 returns the user presentation of the key's
// fingerprint as unpadded base64 encoded sha256 hash.
// This format was introduced from OpenSSH 6.8.
// https://www.openssh.com/txt/release-6.8
// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding)
func FingerprintSHA256(pubKey PublicKey) string {
sha256sum := sha256.Sum256(pubKey.Marshal())
hash := base64.RawStdEncoding.EncodeToString(sha256sum[:])
return "SHA256:" + hash
}

View File

@ -15,6 +15,7 @@ import (
type macMode struct {
keySize int
etm bool
new func(key []byte) hash.Hash
}
@ -45,13 +46,16 @@ func (t truncatingMAC) Size() int {
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
var macModes = map[string]*macMode{
"hmac-sha2-256": {32, func(key []byte) hash.Hash {
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha1": {20, func(key []byte) hash.Hash {
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha1": {20, false, func(key []byte) hash.Hash {
return hmac.New(sha1.New, key)
}},
"hmac-sha1-96": {20, func(key []byte) hash.Hash {
"hmac-sha1-96": {20, false, func(key []byte) hash.Hash {
return truncatingMAC{12, hmac.New(sha1.New, key)}
}},
}

View File

@ -116,9 +116,9 @@ func (m *mux) Wait() error {
func newMux(p packetConn) *mux {
m := &mux{
conn: p,
incomingChannels: make(chan NewChannel, 16),
incomingChannels: make(chan NewChannel, chanSize),
globalResponses: make(chan interface{}, 1),
incomingRequests: make(chan *Request, 16),
incomingRequests: make(chan *Request, chanSize),
errCond: newCond(),
}
if debugMux {

View File

@ -10,6 +10,7 @@ import (
"fmt"
"io"
"net"
"strings"
)
// The Permissions type holds fine-grained permissions that are
@ -188,7 +189,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
if err := s.transport.requestInitialKeyChange(); err != nil {
if err := s.transport.waitSession(); err != nil {
return nil, err
}
@ -231,7 +232,7 @@ func isAcceptableAlgo(algo string) bool {
return false
}
func checkSourceAddress(addr net.Addr, sourceAddr string) error {
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
if addr == nil {
return errors.New("ssh: no address known for client, but source-address match required")
}
@ -241,18 +242,20 @@ func checkSourceAddress(addr net.Addr, sourceAddr string) error {
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
}
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
if bytes.Equal(allowedIP, tcpAddr.IP) {
return nil
}
} else {
_, ipNet, err := net.ParseCIDR(sourceAddr)
if err != nil {
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
}
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
if allowedIP.Equal(tcpAddr.IP) {
return nil
}
} else {
_, ipNet, err := net.ParseCIDR(sourceAddr)
if err != nil {
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
}
if ipNet.Contains(tcpAddr.IP) {
return nil
if ipNet.Contains(tcpAddr.IP) {
return nil
}
}
}
@ -260,7 +263,7 @@ func checkSourceAddress(addr net.Addr, sourceAddr string) error {
}
func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
var err error
sessionID := s.transport.getSessionID()
var cache pubKeyCache
var perms *Permissions
@ -385,7 +388,7 @@ userAuthLoop:
if !isAcceptableAlgo(sig.Format) {
break
}
signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData)
signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData)
if err := pubKey.Verify(signedData, sig); err != nil {
return nil, err
@ -421,12 +424,12 @@ userAuthLoop:
return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
}
if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil {
if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil {
return nil, err
}
}
if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
return nil, err
}
return perms, nil

View File

@ -8,8 +8,13 @@ import (
"bufio"
"errors"
"io"
"log"
)
// debugTransport if set, will print packet types as they go over the
// wire. No message decoding is done, to minimize the impact on timing.
const debugTransport = false
const (
gcmCipherID = "aes128-gcm@openssh.com"
aes128cbcID = "aes128-cbc"
@ -22,7 +27,9 @@ type packetConn interface {
// Encrypt and send a packet of data to the remote peer.
writePacket(packet []byte) error
// Read a packet from the connection
// Read a packet from the connection. The read is blocking,
// i.e. if error is nil, then the returned byte slice is
// always non-empty.
readPacket() ([]byte, error)
// Close closes the write-side of the connection.
@ -38,7 +45,7 @@ type transport struct {
bufReader *bufio.Reader
bufWriter *bufio.Writer
rand io.Reader
isClient bool
io.Closer
}
@ -84,9 +91,38 @@ func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) err
return nil
}
func (t *transport) printPacket(p []byte, write bool) {
if len(p) == 0 {
return
}
who := "server"
if t.isClient {
who = "client"
}
what := "read"
if write {
what = "write"
}
log.Println(what, who, p[0])
}
// Read and decrypt next packet.
func (t *transport) readPacket() ([]byte, error) {
return t.reader.readPacket(t.bufReader)
func (t *transport) readPacket() (p []byte, err error) {
for {
p, err = t.reader.readPacket(t.bufReader)
if err != nil {
break
}
if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) {
break
}
}
if debugTransport {
t.printPacket(p, false)
}
return p, err
}
func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
@ -129,6 +165,9 @@ func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) {
}
func (t *transport) writePacket(packet []byte) error {
if debugTransport {
t.printPacket(packet, true)
}
return t.writer.writePacket(t.bufWriter, t.rand, packet)
}
@ -169,6 +208,8 @@ func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transp
},
Closer: rwc,
}
t.isClient = isClient
if isClient {
t.reader.dir = serverKeys
t.writer.dir = clientKeys
@ -226,6 +267,7 @@ func newPacketCipher(d direction, algs directionAlgorithms, kex *kexResult) (pac
c := &streamPacketCipher{
mac: macModes[algs.MAC].new(macKey),
etm: macModes[algs.MAC].etm,
}
c.macResult = make([]byte, c.mac.Size())

22
vendor/vendor.json vendored
View File

@ -575,10 +575,10 @@
"revision": "95ba30457eb1121fa27753627c774c7cd4e90083"
},
{
"checksumSHA1": "24M48du3803/HG1uZTo5aQxmPNk=",
"checksumSHA1": "6uNzRk3ScsTC+olpCXkW4M/L3fg=",
"path": "github.com/masterzen/winrm",
"revision": "a6cd420bebdc21d10c97296556295fcb99dd9635",
"revisionTime": "2017-01-16T07:57:11Z"
"revision": "b7e3d2de4979ce5eae5c1c1ef450040c5d675a89",
"revisionTime": "2017-01-26T07:02:57Z"
},
{
"checksumSHA1": "KTsgWipT3ennAAtaKxEZairxero=",
@ -751,6 +751,12 @@
"revision": "7d6a4449b586546246087e96e5c97dbc450f4917",
"revisionTime": "2016-09-28T15:38:44Z"
},
{
"checksumSHA1": "iHiMTBffQvWYlOLu3130JXuQpgQ=",
"path": "github.com/xanzy/ssh-agent",
"revision": "ba9c9e33906f58169366275e3450db66139a31a9",
"revisionTime": "2015-12-15T15:34:51Z"
},
{
"checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=",
"path": "golang.org/x/crypto/curve25519",
@ -774,16 +780,16 @@
"revision": "1f22c0103821b9390939b6776727195525381532"
},
{
"checksumSHA1": "LlElMHeTC34ng8eHzjvtUhAgrr8=",
"checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=",
"path": "golang.org/x/crypto/ssh",
"revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd",
"revisionTime": "2016-10-31T15:37:30Z"
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
"revisionTime": "2017-02-08T20:51:15Z"
},
{
"checksumSHA1": "SJ3Ma3Ozavxpbh1usZWBCnzMKIc=",
"path": "golang.org/x/crypto/ssh/agent",
"revision": "7682e7e3945130cf3cde089834664f68afdd1523",
"revisionTime": "2016-10-03T20:54:26Z"
"revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8",
"revisionTime": "2017-02-08T20:51:15Z"
},
{
"checksumSHA1": "5ARrN3Zq+E9zazFb/N+b08Serys=",

View File

@ -4,9 +4,9 @@ init:
bundle
docker-dev:
docker run -it --expose 4567 -P -v "$PWD":/usr/src/app -w /usr/src/app ruby:2.3.1 \
docker run -it --expose 4567 -p 4567:4567 -v "$(PWD)":/usr/src/app -w /usr/src/app ruby:2.3.1 \
bash -c "apt-get update && apt-get -qy install curl git libgmp3-dev nodejs && \
gem install bundler && make dev"
gem install bundler && bundle install && make dev"
dev: init
PACKER_DISABLE_DOWNLOAD_FETCH=true PACKER_VERSION=1.0 bundle exec middleman server

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