Merge branch 'master' into issue8178

This commit is contained in:
Kevin Puetz 2019-10-23 00:34:38 -05:00
commit 00d0297d26
919 changed files with 158818 additions and 47315 deletions

View File

@ -1,7 +1,7 @@
--- ---
name: Bug Report name: Bug Report
about: You're experiencing an issue with Packer that is different than the documented behavior. about: You're experiencing an issue with Packer that is different than the documented behavior.
labels: bug
--- ---
When filing a bug, please include the following headings if possible. Any When filing a bug, please include the following headings if possible. Any

View File

@ -1,7 +1,7 @@
--- ---
name: Feature Request name: Feature Request
about: If you have something you think Packer could improve or add support for. about: If you have something you think Packer could improve or add support for.
labels: enhancement
--- ---
Please search the existing issues for relevant feature requests, and use the Please search the existing issues for relevant feature requests, and use the

View File

@ -1,7 +1,7 @@
--- ---
name: Question name: Question
about: If you have a question, please check out our other community resources instead of opening an issue. about: If you have a question, please check out our other community resources instead of opening an issue.
labels: question
--- ---
Issues on GitHub are intended to be related to bugs or feature requests, so we Issues on GitHub are intended to be related to bugs or feature requests, so we

View File

@ -0,0 +1,24 @@
---
name: SSH or WinRM times out
about: I have a waiting SSH or WinRM error.
labels: communicator-question
---
Got one of the following errors ? See if the related guides can help.
* `Waiting for WinRM to become available` ?
- See our basic WinRm Packer guide: https://www.packer.io/guides/automatic-operating-system-installs/autounattend_windows.html
* `Waiting for SSH to become available` ?
- See our basic SSH Packer guide: https://www.packer.io/guides/automatic-operating-system-installs/preseed_ubuntu.html
Issues on GitHub are intended to be related to bugs or feature requests, so we recommend using our other community resources instead of asking here if you have a question.
- Packer Guides: https://www.packer.io/guides/index.html
- Discussion List: https://groups.google.com/group/packer-tool
- Any other questions can be sent to the packer section of the HashiCorp
forum: https://discuss.hashicorp.com/c/packer
- Packer community links: https://www.packer.io/community.html

View File

@ -1,3 +1,38 @@
## 1.4.5 (Upcoming)
### IMPROVEMENTS:
* builder/azure-arm: Allow specification of polling duration [GH-8226]
* builder/azure-chroot: Add Azure chroot builder [GH-8185]
* builder/azure: Deploy NSG if list of IP addresses is provided in config
[GH-8203]
* builder/oracle-oci: Support defined tags for oci builder [GH-8172]
* builder/proxmox: Add ability to select CPU type [GH-8201]
* builder/proxmox: Add support for SCSI controller selection [GH-8199]
* builder/tencent: Add retry on remote api call [GH-8250]
* communicator/winrm: Prevent busy loop while waiting for WinRM connection
[GH-8213]
* core: Add strftime function in templates [GH-8208]
### BUG FIXES:
* builder/amazon: Fix region copy for non-ebs amazon builders [GH-8212]
* builder/amazon: Fix spot instance bug where builder would fail if one
availability zone could not support the requested spot instance type, even
if another AZ could do so. [GH-8184]
* builder/azure: Fix build failure after a retry config generation error.
[GH-8209]
* builder/docker: Use a unique temp dir for each build to prevent concurrent
builds from stomping on each other [GH-8192]
* builder/hyperv: Improve filter for determining which files to compact
[GH-8248]
* builder/hyperv: Use first adapter, rather than failing, when multiple
adapters are attached to host OS's VM switch [GH-8234]
* builder/openstack: Warn instead of failing on terminate if instance is
already shut down [GH-8176]
* post-processor/digitalocean-import: Fix panic when 'image_regions' not set
[GH-8179]
* provisioner/powershell: Fix powershell syntax error causing failed builds
[GH-8195]
## 1.4.4 (October 1, 2019) ## 1.4.4 (October 1, 2019)
### IMPROVEMENTS: ### IMPROVEMENTS:

View File

@ -3,24 +3,62 @@
# builders # builders
/builder/alicloud/ @chhaj5236 /builder/alicloud/ @chhaj5236
/builder/azure/ @paulmey /website/source/docs/builders/alicloud* @chhaj5236
/builder/hyperv/ @taliesins
/builder/jdcloud/ @XiaohanLiang @remrain /builder/azure/ @paulmey
/builder/linode/ @displague @ctreatma @stvnjacobs /website/source/docs/builders/azure* @paulmey
/builder/lxc/ @ChrisLundquist
/builder/lxd/ @ChrisLundquist /builder/hyperv/ @taliesins
/builder/oneandone/ @jasmingacic /website/source/docs/builders/hyperv* @taliesins
/builder/oracle/ @prydie @owainlewis
/builder/profitbricks/ @jasmingacic /builder/jdcloud/ @XiaohanLiang @remrain
/builder/triton/ @sean- /website/source/docs/builders/jdcloud* @XiaohanLiang @remrain
/builder/ncloud/ @YuSungDuk
/builder/proxmox/ @carlpett /builder/linode/ @displague @ctreatma @stvnjacobs
/website/source/docs/builders/linode* @displague @ctreatma @stvnjacobs
/builder/lxc/ @ChrisLundquist
/website/source/docs/builders/lxc* @ChrisLundquist
/builder/lxd/ @ChrisLundquist
/website/source/docs/builders/lxd* @ChrisLundquist
/builder/oneandone/ @jasmingacic
/website/source/docs/builders/oneandone* @jasmingacic
/builder/oracle/ @prydie @owainlewis
/website/source/docs/builders/oracle* @prydie @owainlewis
/builder/profitbricks/ @jasmingacic
/website/source/docs/builders/profitbricks* @jasmingacic
/builder/triton/ @sean-
/website/source/docs/builders/triton* @sean-
/builder/ncloud/ @YuSungDuk
/website/source/docs/builders/ncloud* @YuSungDuk
/builder/proxmox/ @carlpett
/website/source/docs/builders/proxmox* @carlpett
/builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw /builder/scaleway/ @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hcloud/ @LKaemmerling /website/source/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
/builder/hyperone @m110 @gregorybrzeski @ad-m
/builder/ucloud/ @shawnmssu /builder/hcloud/ @LKaemmerling
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso /website/source/docs/builders/hcloud* @LKaemmerling
/builder/osc @marinsalinas
/builder/hyperone/ @m110 @gregorybrzeski @ad-m
/website/source/docs/builders/hyperone* @m110 @gregorybrzeski @ad-m
/builder/ucloud/ @shawnmssu
/website/source/docs/builders/ucloud* @shawnmssu
/builder/yandex/ @GennadySpb @alexanderKhaustov @seukyaso
/website/source/docs/builders/yandex* @GennadySpb @alexanderKhaustov @seukyaso
/builder/osc/ @marinsalinas
/website/source/docs/builders/osc* @marinsalinas
# provisioners # provisioners

View File

@ -53,6 +53,7 @@ install-gen-deps: ## Install dependencies for code generation
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/mna/pigeon@master) @(cd $(TEMPDIR) && GO111MODULE=on go get github.com/mna/pigeon@master)
@(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master) @(cd $(TEMPDIR) && GO111MODULE=on go get github.com/alvaroloes/enumer@master)
@go install ./cmd/struct-markdown @go install ./cmd/struct-markdown
@go install ./cmd/mapstructure-to-hcl2
dev: ## Build and install a development build dev: ## Build and install a development build
@grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \ @grep 'const VersionPrerelease = ""' version/version.go > /dev/null ; if [ $$? -eq 0 ]; then \
@ -97,6 +98,8 @@ fmt-examples:
generate: install-gen-deps ## Generate dynamically generated code generate: install-gen-deps ## Generate dynamically generated code
@echo "==> removing autogenerated markdown..." @echo "==> removing autogenerated markdown..."
@find website/source/ -type f | xargs grep -l '^<!-- Code generated' | xargs rm @find website/source/ -type f | xargs grep -l '^<!-- Code generated' | xargs rm
@echo "==> removing autogenerated code..."
@find post-processor common helper template builder provisioner -type f | xargs grep -l '^// Code generated' | xargs rm
go generate ./... go generate ./...
go fmt common/bootcommand/boot_command.go go fmt common/bootcommand/boot_command.go
go fmt command/plugin.go go fmt command/plugin.go

View File

@ -1,3 +1,5 @@
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
// The alicloud contains a packer.Builder implementation that // The alicloud contains a packer.Builder implementation that
// builds ecs images for alicloud. // builds ecs images for alicloud.
package ecs package ecs

View File

@ -0,0 +1,237 @@
// Code generated by "mapstructure-to-hcl2 -type Config,AlicloudDiskDevice"; DO NOT EDIT.
package ecs
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatAlicloudDiskDevice struct {
DiskName *string `mapstructure:"disk_name" required:"false" cty:"disk_name"`
DiskCategory *string `mapstructure:"disk_category" required:"false" cty:"disk_category"`
DiskSize *int `mapstructure:"disk_size" required:"false" cty:"disk_size"`
SnapshotId *string `mapstructure:"disk_snapshot_id" required:"false" cty:"disk_snapshot_id"`
Description *string `mapstructure:"disk_description" required:"false" cty:"disk_description"`
DeleteWithInstance *bool `mapstructure:"disk_delete_with_instance" required:"false" cty:"disk_delete_with_instance"`
Device *string `mapstructure:"disk_device" required:"false" cty:"disk_device"`
Encrypted *bool `mapstructure:"disk_encrypted" required:"false" cty:"disk_encrypted"`
}
// FlatMapstructure returns a new FlatAlicloudDiskDevice.
// FlatAlicloudDiskDevice is an auto-generated flat version of AlicloudDiskDevice.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*AlicloudDiskDevice) FlatMapstructure() interface{} { return new(FlatAlicloudDiskDevice) }
// HCL2Spec returns the hcldec.Spec of a FlatAlicloudDiskDevice.
// This spec is used by HCL to read the fields of FlatAlicloudDiskDevice.
func (*FlatAlicloudDiskDevice) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"disk_name": &hcldec.AttrSpec{Name: "disk_name", Type: cty.String, Required: false},
"disk_category": &hcldec.AttrSpec{Name: "disk_category", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"disk_snapshot_id": &hcldec.AttrSpec{Name: "disk_snapshot_id", Type: cty.String, Required: false},
"disk_description": &hcldec.AttrSpec{Name: "disk_description", Type: cty.String, Required: false},
"disk_delete_with_instance": &hcldec.AttrSpec{Name: "disk_delete_with_instance", Type: cty.Bool, Required: false},
"disk_device": &hcldec.AttrSpec{Name: "disk_device", Type: cty.String, Required: false},
"disk_encrypted": &hcldec.AttrSpec{Name: "disk_encrypted", Type: cty.Bool, Required: false},
}
return s
}
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
AlicloudAccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"`
AlicloudSecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"`
AlicloudRegion *string `mapstructure:"region" required:"true" cty:"region"`
AlicloudSkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token"`
AlicloudImageName *string `mapstructure:"image_name" required:"true" cty:"image_name"`
AlicloudImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"`
AlicloudImageDescription *string `mapstructure:"image_description" required:"false" cty:"image_description"`
AlicloudImageShareAccounts []string `mapstructure:"image_share_account" required:"false" cty:"image_share_account"`
AlicloudImageUNShareAccounts []string `mapstructure:"image_unshare_account" cty:"image_unshare_account"`
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions" required:"false" cty:"image_copy_regions"`
AlicloudImageDestinationNames []string `mapstructure:"image_copy_names" required:"false" cty:"image_copy_names"`
ImageEncrypted *bool `mapstructure:"image_encrypted" required:"false" cty:"image_encrypted"`
AlicloudImageForceDelete *bool `mapstructure:"image_force_delete" required:"false" cty:"image_force_delete"`
AlicloudImageForceDeleteSnapshots *bool `mapstructure:"image_force_delete_snapshots" required:"false" cty:"image_force_delete_snapshots"`
AlicloudImageForceDeleteInstances *bool `mapstructure:"image_force_delete_instances" cty:"image_force_delete_instances"`
AlicloudImageIgnoreDataDisks *bool `mapstructure:"image_ignore_data_disks" required:"false" cty:"image_ignore_data_disks"`
AlicloudImageTags map[string]string `mapstructure:"tags" required:"false" cty:"tags"`
ECSSystemDiskMapping *FlatAlicloudDiskDevice `mapstructure:"system_disk_mapping" required:"false" cty:"system_disk_mapping"`
ECSImagesDiskMappings []FlatAlicloudDiskDevice `mapstructure:"image_disk_mappings" required:"false" cty:"image_disk_mappings"`
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" cty:"associate_public_ip_address"`
ZoneId *string `mapstructure:"zone_id" required:"false" cty:"zone_id"`
IOOptimized *bool `mapstructure:"io_optimized" required:"false" cty:"io_optimized"`
InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"`
Description *string `mapstructure:"description" cty:"description"`
AlicloudSourceImage *string `mapstructure:"source_image" required:"true" cty:"source_image"`
ForceStopInstance *bool `mapstructure:"force_stop_instance" required:"false" cty:"force_stop_instance"`
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"`
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"`
SecurityGroupName *string `mapstructure:"security_group_name" required:"false" cty:"security_group_name"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"`
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"`
VpcName *string `mapstructure:"vpc_name" required:"false" cty:"vpc_name"`
CidrBlock *string `mapstructure:"vpc_cidr_block" required:"false" cty:"vpc_cidr_block"`
VSwitchId *string `mapstructure:"vswitch_id" required:"false" cty:"vswitch_id"`
VSwitchName *string `mapstructure:"vswitch_name" required:"false" cty:"vswitch_name"`
InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name"`
InternetChargeType *string `mapstructure:"internet_charge_type" required:"false" cty:"internet_charge_type"`
InternetMaxBandwidthOut *int `mapstructure:"internet_max_bandwidth_out" required:"false" cty:"internet_max_bandwidth_out"`
WaitSnapshotReadyTimeout *int `mapstructure:"wait_snapshot_ready_timeout" required:"false" cty:"wait_snapshot_ready_timeout"`
Type *string `mapstructure:"communicator" cty:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
SSHPublicKey []byte `cty:"ssh_public_key"`
SSHPrivateKey []byte `cty:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
SSHPrivateIp *bool `mapstructure:"ssh_private_ip" required:"false" cty:"ssh_private_ip"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
// This spec is used by HCL to read the fields of FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false},
"image_share_account": &hcldec.AttrSpec{Name: "image_share_account", Type: cty.List(cty.String), Required: false},
"image_unshare_account": &hcldec.AttrSpec{Name: "image_unshare_account", Type: cty.List(cty.String), Required: false},
"image_copy_regions": &hcldec.AttrSpec{Name: "image_copy_regions", Type: cty.List(cty.String), Required: false},
"image_copy_names": &hcldec.AttrSpec{Name: "image_copy_names", Type: cty.List(cty.String), Required: false},
"image_encrypted": &hcldec.AttrSpec{Name: "image_encrypted", Type: cty.Bool, Required: false},
"image_force_delete": &hcldec.AttrSpec{Name: "image_force_delete", Type: cty.Bool, Required: false},
"image_force_delete_snapshots": &hcldec.AttrSpec{Name: "image_force_delete_snapshots", Type: cty.Bool, Required: false},
"image_force_delete_instances": &hcldec.AttrSpec{Name: "image_force_delete_instances", Type: cty.Bool, Required: false},
"image_ignore_data_disks": &hcldec.AttrSpec{Name: "image_ignore_data_disks", Type: cty.Bool, Required: false},
"tags": &hcldec.BlockAttrsSpec{TypeName: "tags", ElementType: cty.String, Required: false},
"system_disk_mapping": &hcldec.BlockSpec{TypeName: "system_disk_mapping", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())},
"image_disk_mappings": &hcldec.BlockListSpec{TypeName: "image_disk_mappings", Nested: &hcldec.BlockSpec{TypeName: "image_disk_mappings", Nested: hcldec.ObjectSpec((*FlatAlicloudDiskDevice)(nil).HCL2Spec())}},
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
"zone_id": &hcldec.AttrSpec{Name: "zone_id", Type: cty.String, Required: false},
"io_optimized": &hcldec.AttrSpec{Name: "io_optimized", Type: cty.Bool, Required: false},
"instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false},
"description": &hcldec.AttrSpec{Name: "description", Type: cty.String, Required: false},
"source_image": &hcldec.AttrSpec{Name: "source_image", Type: cty.String, Required: false},
"force_stop_instance": &hcldec.AttrSpec{Name: "force_stop_instance", Type: cty.Bool, Required: false},
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
"security_group_name": &hcldec.AttrSpec{Name: "security_group_name", Type: cty.String, Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"vpc_name": &hcldec.AttrSpec{Name: "vpc_name", Type: cty.String, Required: false},
"vpc_cidr_block": &hcldec.AttrSpec{Name: "vpc_cidr_block", Type: cty.String, Required: false},
"vswitch_id": &hcldec.AttrSpec{Name: "vswitch_id", Type: cty.String, Required: false},
"vswitch_name": &hcldec.AttrSpec{Name: "vswitch_name", Type: cty.String, Required: false},
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
"internet_charge_type": &hcldec.AttrSpec{Name: "internet_charge_type", Type: cty.String, Required: false},
"internet_max_bandwidth_out": &hcldec.AttrSpec{Name: "internet_max_bandwidth_out", Type: cty.Number, Required: false},
"wait_snapshot_ready_timeout": &hcldec.AttrSpec{Name: "wait_snapshot_ready_timeout", Type: cty.Number, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"ssh_private_ip": &hcldec.AttrSpec{Name: "ssh_private_ip", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,69 +0,0 @@
//go:generate struct-markdown
package chroot
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/template/interpolate"
)
type BlockDevice struct {
awscommon.BlockDevice `mapstructure:",squash"`
// ID, alias or ARN of the KMS key to use for boot volume encryption. This
// only applies to the main region, other regions where the AMI will be
// copied will be encrypted by the default EBS KMS key. For valid formats
// see KmsKeyId in the [AWS API docs -
// CopyImage](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CopyImage.html)
// This field is validated by Packer, when using an alias, you will have to
// prefix kms_key_id with alias/.
KmsKeyId string `mapstructure:"kms_key_id" required:"false"`
}
type BlockDevices []BlockDevice
func (bds BlockDevices) BuildEC2BlockDeviceMappings() []*ec2.BlockDeviceMapping {
var blockDevices []*ec2.BlockDeviceMapping
for _, blockDevice := range bds {
blockDevices = append(blockDevices, blockDevice.BuildEC2BlockDeviceMapping())
}
return blockDevices
}
func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapping {
mapping := blockDevice.BlockDevice.BuildEC2BlockDeviceMapping()
if blockDevice.KmsKeyId != "" {
mapping.Ebs.KmsKeyId = aws.String(blockDevice.KmsKeyId)
}
return mapping
}
func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
err := b.BlockDevice.Prepare(ctx)
if err != nil {
return err
}
// Warn that encrypted must be true when setting kms_key_id
if b.KmsKeyId != "" && b.Encrypted.True() {
return fmt.Errorf("The device %v, must also have `encrypted: "+
"true` when setting a kms_key_id.", b.DeviceName)
}
_, err = interpolate.RenderInterface(&b, ctx)
return err
}
func (bds BlockDevices) Prepare(ctx *interpolate.Context) (errs []error) {
for _, block := range bds {
if err := block.Prepare(ctx); err != nil {
errs = append(errs, err)
}
}
return errs
}

View File

@ -1,4 +1,5 @@
//go:generate struct-markdown //go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config,BlockDevices,BlockDevice
// The chroot package is able to create an Amazon AMI without requiring the // The chroot package is able to create an Amazon AMI without requiring the
// launch of a new instance for every build. It does this by attaching and // launch of a new instance for every build. It does this by attaching and
@ -37,7 +38,7 @@ type Config struct {
// entry for your root volume, `root_volume_size` and `root_device_name`. // entry for your root volume, `root_volume_size` and `root_device_name`.
// See the [BlockDevices](#block-devices-configuration) documentation for // See the [BlockDevices](#block-devices-configuration) documentation for
// fields. // fields.
AMIMappings BlockDevices `mapstructure:"ami_block_device_mappings" required:"false"` AMIMappings awscommon.BlockDevices `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false"`
// This is a list of devices to mount into the chroot environment. This // This is a list of devices to mount into the chroot environment. This
// configuration parameter requires some additional documentation which is // configuration parameter requires some additional documentation which is
// in the Chroot Mounts section. Please read that section for more // in the Chroot Mounts section. Please read that section for more
@ -168,6 +169,14 @@ type Config struct {
ctx interpolate.Context ctx interpolate.Context
} }
func (c *Config) GetContext() interpolate.Context {
return c.ctx
}
type interpolateContextProvider interface {
GetContext() interpolate.Context
}
type wrappedCommandTemplate struct { type wrappedCommandTemplate struct {
Command string Command string
} }
@ -392,8 +401,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepPostMountCommands{ &StepPostMountCommands{
Commands: b.config.PostMountCommands, Commands: b.config.PostMountCommands,
}, },
&StepMountExtra{}, &StepMountExtra{
&StepCopyFiles{}, ChrootMounts: b.config.ChrootMounts,
},
&StepCopyFiles{
Files: b.config.CopyFiles,
},
&StepChrootProvision{}, &StepChrootProvision{},
&StepEarlyCleanup{}, &StepEarlyCleanup{},
&StepSnapshot{}, &StepSnapshot{},
@ -408,6 +421,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
RootVolumeSize: b.config.RootVolumeSize, RootVolumeSize: b.config.RootVolumeSize,
EnableAMISriovNetSupport: b.config.AMISriovNetSupport, EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport, EnableAMIENASupport: b.config.AMIENASupport,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
}, },
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,

View File

@ -0,0 +1,140 @@
// Code generated by "mapstructure-to-hcl2 -type Config,BlockDevices,BlockDevice"; DO NOT EDIT.
package chroot
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/amazon/common"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name"`
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description"`
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type"`
AMIUsers []string `mapstructure:"ami_users" required:"false" cty:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups" required:"false" cty:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false" cty:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions" required:"false" cty:"ami_regions"`
AMISkipRegionValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
AMITags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"`
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"`
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"`
AMIForceDeregister *bool `mapstructure:"force_deregister" required:"false" cty:"force_deregister"`
AMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" required:"false" cty:"force_delete_snapshot"`
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot" required:"false" cty:"encrypt_boot"`
AMIKmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"`
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false" cty:"region_kms_key_ids"`
AMISkipBuildRegion *bool `mapstructure:"skip_save_build_region" cty:"skip_save_build_region"`
SnapshotTags common.TagMap `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags"`
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"`
MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"`
ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"`
RawRegion *string `mapstructure:"region" required:"true" cty:"region"`
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"`
Token *string `mapstructure:"token" required:"false" cty:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" hcl2-schema-generator:"ami_block_device_mappings,direct" required:"false" cty:"ami_block_device_mappings"`
ChrootMounts [][]string `mapstructure:"chroot_mounts" required:"false" cty:"chroot_mounts"`
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper"`
CopyFiles []string `mapstructure:"copy_files" required:"false" cty:"copy_files"`
DevicePath *string `mapstructure:"device_path" required:"false" cty:"device_path"`
NVMEDevicePath *string `mapstructure:"nvme_device_path" required:"false" cty:"nvme_device_path"`
FromScratch *bool `mapstructure:"from_scratch" required:"false" cty:"from_scratch"`
MountOptions []string `mapstructure:"mount_options" required:"false" cty:"mount_options"`
MountPartition *string `mapstructure:"mount_partition" required:"false" cty:"mount_partition"`
MountPath *string `mapstructure:"mount_path" required:"false" cty:"mount_path"`
PostMountCommands []string `mapstructure:"post_mount_commands" required:"false" cty:"post_mount_commands"`
PreMountCommands []string `mapstructure:"pre_mount_commands" required:"false" cty:"pre_mount_commands"`
RootDeviceName *string `mapstructure:"root_device_name" required:"false" cty:"root_device_name"`
RootVolumeSize *int64 `mapstructure:"root_volume_size" required:"false" cty:"root_volume_size"`
RootVolumeType *string `mapstructure:"root_volume_type" required:"false" cty:"root_volume_type"`
SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"`
SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"`
RootVolumeTags common.TagMap `mapstructure:"root_volume_tags" required:"false" cty:"root_volume_tags"`
Architecture *string `mapstructure:"ami_architecture" required:"false" cty:"ami_architecture"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
// This spec is used by HCL to read the fields of FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
"ami_users": &hcldec.AttrSpec{Name: "ami_users", Type: cty.List(cty.String), Required: false},
"ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false},
"ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false},
"ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
"force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false},
"force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false},
"encrypt_boot": &hcldec.AttrSpec{Name: "encrypt_boot", Type: cty.Bool, Required: false},
"kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false},
"region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false},
"skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false},
"snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false},
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}},
"chroot_mounts": &hcldec.BlockListSpec{TypeName: "chroot_mounts", Nested: &hcldec.AttrSpec{Name: "chroot_mounts", Type: cty.List(cty.String), Required: false}},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
"copy_files": &hcldec.AttrSpec{Name: "copy_files", Type: cty.List(cty.String), Required: false},
"device_path": &hcldec.AttrSpec{Name: "device_path", Type: cty.String, Required: false},
"nvme_device_path": &hcldec.AttrSpec{Name: "nvme_device_path", Type: cty.String, Required: false},
"from_scratch": &hcldec.AttrSpec{Name: "from_scratch", Type: cty.Bool, Required: false},
"mount_options": &hcldec.AttrSpec{Name: "mount_options", Type: cty.List(cty.String), Required: false},
"mount_partition": &hcldec.AttrSpec{Name: "mount_partition", Type: cty.String, Required: false},
"mount_path": &hcldec.AttrSpec{Name: "mount_path", Type: cty.String, Required: false},
"post_mount_commands": &hcldec.AttrSpec{Name: "post_mount_commands", Type: cty.List(cty.String), Required: false},
"pre_mount_commands": &hcldec.AttrSpec{Name: "pre_mount_commands", Type: cty.List(cty.String), Required: false},
"root_device_name": &hcldec.AttrSpec{Name: "root_device_name", Type: cty.String, Required: false},
"root_volume_size": &hcldec.AttrSpec{Name: "root_volume_size", Type: cty.Number, Required: false},
"root_volume_type": &hcldec.AttrSpec{Name: "root_volume_type", Type: cty.String, Required: false},
"source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false},
"source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())},
"root_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
"ami_architecture": &hcldec.AttrSpec{Name: "ami_architecture", Type: cty.String, Required: false},
}
return s
}

View File

@ -17,20 +17,20 @@ import (
// copy_files_cleanup CleanupFunc - A function to clean up the copied files // copy_files_cleanup CleanupFunc - A function to clean up the copied files
// early. // early.
type StepCopyFiles struct { type StepCopyFiles struct {
Files []string
files []string files []string
} }
func (s *StepCopyFiles) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepCopyFiles) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string) mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
s.files = make([]string, 0, len(config.CopyFiles)) s.files = make([]string, 0, len(s.Files))
if len(config.CopyFiles) > 0 { if len(s.Files) > 0 {
ui.Say("Copying files from host to chroot...") ui.Say("Copying files from host to chroot...")
for _, path := range config.CopyFiles { for _, path := range s.Files {
ui.Message(path) ui.Message(path)
chrootPath := filepath.Join(mountPath, path) chrootPath := filepath.Join(mountPath, path)
log.Printf("Copying '%s' to '%s'", path, chrootPath) log.Printf("Copying '%s' to '%s'", path, chrootPath)

View File

@ -17,19 +17,19 @@ import (
// Produces: // Produces:
// mount_extra_cleanup CleanupFunc - To perform early cleanup // mount_extra_cleanup CleanupFunc - To perform early cleanup
type StepMountExtra struct { type StepMountExtra struct {
mounts []string ChrootMounts [][]string
mounts []string
} }
func (s *StepMountExtra) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepMountExtra) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string) mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
s.mounts = make([]string, 0, len(config.ChrootMounts)) s.mounts = make([]string, 0, len(s.ChrootMounts))
ui.Say("Mounting additional paths within the chroot...") ui.Say("Mounting additional paths within the chroot...")
for _, mountInfo := range config.ChrootMounts { for _, mountInfo := range s.ChrootMounts {
innerPath := mountPath + mountInfo[2] innerPath := mountPath + mountInfo[2]
if err := os.MkdirAll(innerPath, 0755); err != nil { if err := os.MkdirAll(innerPath, 0755); err != nil {

View File

@ -19,7 +19,7 @@ type StepPostMountCommands struct {
} }
func (s *StepPostMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepPostMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(interpolateContextProvider)
device := state.Get("device").(string) device := state.Get("device").(string)
mountPath := state.Get("mount_path").(string) mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
@ -29,7 +29,7 @@ func (s *StepPostMountCommands) Run(ctx context.Context, state multistep.StateBa
return multistep.ActionContinue return multistep.ActionContinue
} }
ictx := config.ctx ictx := config.GetContext()
ictx.Data = &postMountCommandsData{ ictx.Data = &postMountCommandsData{
Device: device, Device: device,
MountPath: mountPath, MountPath: mountPath,

View File

@ -17,7 +17,7 @@ type StepPreMountCommands struct {
} }
func (s *StepPreMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepPreMountCommands) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config) config := state.Get("config").(interpolateContextProvider)
device := state.Get("device").(string) device := state.Get("device").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper) wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
@ -26,7 +26,7 @@ func (s *StepPreMountCommands) Run(ctx context.Context, state multistep.StateBag
return multistep.ActionContinue return multistep.ActionContinue
} }
ictx := config.ctx ictx := config.GetContext()
ictx.Data = &preMountCommandsData{Device: device} ictx.Data = &preMountCommandsData{Device: device}
ui.Say("Running device setup commands...") ui.Say("Running device setup commands...")

View File

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common" awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common/random"
confighelper "github.com/hashicorp/packer/helper/config" confighelper "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -17,6 +18,7 @@ type StepRegisterAMI struct {
RootVolumeSize int64 RootVolumeSize int64
EnableAMIENASupport confighelper.Trilean EnableAMIENASupport confighelper.Trilean
EnableAMISriovNetSupport bool EnableAMISriovNetSupport bool
AMISkipBuildRegion bool
} }
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -29,12 +31,29 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
var registerOpts *ec2.RegisterImageInput var registerOpts *ec2.RegisterImageInput
// Create the image
amiName := config.AMIName
state.Put("intermediary_image", false)
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
state.Put("intermediary_image", true)
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
// but you cannot use it to create an unencrypted copy of an encrypted
// snapshot. Your default CMK for EBS is used unless you specify a
// non-default key using KmsKeyId.
// If encrypt_boot is nil or true, we need to create a temporary image
// so that in step_region_copy, we can copy it with the correct
// encryption
amiName = random.AlphaNum(7)
}
// Source Image is only required to be passed if the image is not from scratch // Source Image is only required to be passed if the image is not from scratch
if config.FromScratch { if config.FromScratch {
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID) registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID, amiName)
} else { } else {
image := state.Get("source_image").(*ec2.Image) image := state.Get("source_image").(*ec2.Image)
registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID) registerOpts = buildBaseRegisterOpts(config, image, s.RootVolumeSize, snapshotID, amiName)
} }
if s.EnableAMISriovNetSupport { if s.EnableAMISriovNetSupport {
@ -75,7 +94,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {} func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
// Builds the base register opts with architecture, name, root block device, mappings, virtualizationtype // Builds the base register opts with architecture, name, root block device, mappings, virtualizationtype
func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSize int64, snapshotID string) *ec2.RegisterImageInput { func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSize int64, snapshotID string, amiName string) *ec2.RegisterImageInput {
var ( var (
mappings []*ec2.BlockDeviceMapping mappings []*ec2.BlockDeviceMapping
rootDeviceName string rootDeviceName string
@ -117,7 +136,7 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
if config.FromScratch { if config.FromScratch {
return &ec2.RegisterImageInput{ return &ec2.RegisterImageInput{
Name: &config.AMIName, Name: &amiName,
Architecture: aws.String(config.Architecture), Architecture: aws.String(config.Architecture),
RootDeviceName: aws.String(rootDeviceName), RootDeviceName: aws.String(rootDeviceName),
VirtualizationType: aws.String(config.AMIVirtType), VirtualizationType: aws.String(config.AMIVirtType),
@ -125,12 +144,12 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
} }
} }
return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName) return buildRegisterOptsFromExistingImage(config, sourceImage, newMappings, rootDeviceName, amiName)
} }
func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string) *ec2.RegisterImageInput { func buildRegisterOptsFromExistingImage(config *Config, image *ec2.Image, mappings []*ec2.BlockDeviceMapping, rootDeviceName string, amiName string) *ec2.RegisterImageInput {
registerOpts := &ec2.RegisterImageInput{ registerOpts := &ec2.RegisterImageInput{
Name: &config.AMIName, Name: &amiName,
Architecture: image.Architecture, Architecture: image.Architecture,
RootDeviceName: &rootDeviceName, RootDeviceName: &rootDeviceName,
BlockDeviceMappings: mappings, BlockDeviceMappings: mappings,

View File

@ -30,7 +30,7 @@ func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
blockDevices := []*ec2.BlockDeviceMapping{} blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName) opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
expected := config.AMIVirtType expected := config.AMIVirtType
if *opts.VirtualizationType != expected { if *opts.VirtualizationType != expected {
@ -64,7 +64,7 @@ func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
blockDevices := []*ec2.BlockDeviceMapping{} blockDevices := []*ec2.BlockDeviceMapping{}
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName) opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
expected := config.AMIVirtType expected := config.AMIVirtType
if *opts.VirtualizationType != expected { if *opts.VirtualizationType != expected {
@ -92,14 +92,14 @@ func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
config := Config{ config := Config{
FromScratch: true, FromScratch: true,
PackerConfig: common.PackerConfig{}, PackerConfig: common.PackerConfig{},
AMIMappings: []BlockDevice{ AMIMappings: []amazon.BlockDevice{
{BlockDevice: amazon.BlockDevice{ amazon.BlockDevice{
DeviceName: rootDeviceName, DeviceName: rootDeviceName,
}}, },
}, },
RootDeviceName: rootDeviceName, RootDeviceName: rootDeviceName,
} }
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID) registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 1 { if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1") t.Fatal("Expected block device mapping of length 1")
@ -138,7 +138,7 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImage(t *testing.T) {
}, },
}, },
} }
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID) registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotID, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 2 { if len(registerOpts.BlockDeviceMappings) != 2 {
t.Fatal("Expected block device mapping of length 2") t.Fatal("Expected block device mapping of length 2")
@ -167,10 +167,10 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
config := Config{ config := Config{
FromScratch: false, FromScratch: false,
PackerConfig: common.PackerConfig{}, PackerConfig: common.PackerConfig{},
AMIMappings: []BlockDevice{ AMIMappings: []amazon.BlockDevice{
{BlockDevice: amazon.BlockDevice{ amazon.BlockDevice{
DeviceName: rootDeviceName, DeviceName: rootDeviceName,
}}, },
}, },
RootDeviceName: rootDeviceName, RootDeviceName: rootDeviceName,
} }
@ -196,7 +196,7 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
}, },
}, },
} }
registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId) registerOpts := buildBaseRegisterOpts(&config, &sourceImage, 15, snapshotId, config.AMIName)
if len(registerOpts.BlockDeviceMappings) != 1 { if len(registerOpts.BlockDeviceMappings) != 1 {
t.Fatal("Expected block device mapping of length 1") t.Fatal("Expected block device mapping of length 1")

View File

@ -1,4 +1,5 @@
//go:generate struct-markdown //go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
package common package common

View File

@ -0,0 +1,33 @@
// Code generated by "mapstructure-to-hcl2 -type VaultAWSEngineOptions"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatVaultAWSEngineOptions struct {
Name *string `mapstructure:"name" cty:"name"`
RoleARN *string `mapstructure:"role_arn" cty:"role_arn"`
TTL *string `mapstructure:"ttl" required:"false" cty:"ttl"`
EngineName *string `mapstructure:"engine_name" cty:"engine_name"`
}
// FlatMapstructure returns a new FlatVaultAWSEngineOptions.
// FlatVaultAWSEngineOptions is an auto-generated flat version of VaultAWSEngineOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*VaultAWSEngineOptions) FlatMapstructure() interface{} { return new(FlatVaultAWSEngineOptions) }
// HCL2Spec returns the hcldec.Spec of a FlatVaultAWSEngineOptions.
// This spec is used by HCL to read the fields of FlatVaultAWSEngineOptions.
func (*FlatVaultAWSEngineOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
"role_arn": &hcldec.AttrSpec{Name: "role_arn", Type: cty.String, Required: false},
"ttl": &hcldec.AttrSpec{Name: "ttl", Type: cty.String, Required: false},
"engine_name": &hcldec.AttrSpec{Name: "engine_name", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,4 +1,5 @@
//go:generate struct-markdown //go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type BlockDevice
package common package common

View File

@ -0,0 +1,45 @@
// Code generated by "mapstructure-to-hcl2 -type BlockDevice"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatBlockDevice is an auto-generated flat version of BlockDevice.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatBlockDevice struct {
DeleteOnTermination *bool `mapstructure:"delete_on_termination" required:"false" cty:"delete_on_termination"`
DeviceName *string `mapstructure:"device_name" required:"false" cty:"device_name"`
Encrypted *bool `mapstructure:"encrypted" required:"false" cty:"encrypted"`
IOPS *int64 `mapstructure:"iops" required:"false" cty:"iops"`
NoDevice *bool `mapstructure:"no_device" required:"false" cty:"no_device"`
SnapshotId *string `mapstructure:"snapshot_id" required:"false" cty:"snapshot_id"`
VirtualName *string `mapstructure:"virtual_name" required:"false" cty:"virtual_name"`
VolumeType *string `mapstructure:"volume_type" required:"false" cty:"volume_type"`
VolumeSize *int64 `mapstructure:"volume_size" required:"false" cty:"volume_size"`
KmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"`
}
// FlatMapstructure returns a new FlatBlockDevice.
// FlatBlockDevice is an auto-generated flat version of BlockDevice.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*BlockDevice) FlatMapstructure() interface{} { return new(FlatBlockDevice) }
// HCL2Spec returns the hcldec.Spec of a FlatBlockDevice.
// This spec is used by HCL to read the fields of FlatBlockDevice.
func (*FlatBlockDevice) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"delete_on_termination": &hcldec.AttrSpec{Name: "delete_on_termination", Type: cty.Bool, Required: false},
"device_name": &hcldec.AttrSpec{Name: "device_name", Type: cty.String, Required: false},
"encrypted": &hcldec.AttrSpec{Name: "encrypted", Type: cty.Bool, Required: false},
"iops": &hcldec.AttrSpec{Name: "iops", Type: cty.Number, Required: false},
"no_device": &hcldec.AttrSpec{Name: "no_device", Type: cty.Bool, Required: false},
"snapshot_id": &hcldec.AttrSpec{Name: "snapshot_id", Type: cty.String, Required: false},
"virtual_name": &hcldec.AttrSpec{Name: "virtual_name", Type: cty.String, Required: false},
"volume_type": &hcldec.AttrSpec{Name: "volume_type", Type: cty.String, Required: false},
"volume_size": &hcldec.AttrSpec{Name: "volume_size", Type: cty.Number, Required: false},
"kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false},
}
return s
}

View File

@ -5,12 +5,12 @@ import (
) )
// Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided. // Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided.
func buildEc2Filters(input map[*string]*string) []*ec2.Filter { func buildEc2Filters(input map[string]string) []*ec2.Filter {
var filters []*ec2.Filter var filters []*ec2.Filter
for k, v := range input { for k, v := range input {
filters = append(filters, &ec2.Filter{ filters = append(filters, &ec2.Filter{
Name: k, Name: &k,
Values: []*string{v}, Values: []*string{&v},
}) })
} }
return filters return filters

View File

@ -1,4 +1,5 @@
//go:generate struct-markdown //go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions
package common package common
@ -18,11 +19,19 @@ import (
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$") var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
type AmiFilterOptions struct { type AmiFilterOptions struct {
Filters map[*string]*string Filters map[string]string
Owners []*string Owners []string
MostRecent bool `mapstructure:"most_recent"` MostRecent bool `mapstructure:"most_recent"`
} }
func (d *AmiFilterOptions) GetOwners() []*string {
res := make([]*string, 0, len(d.Owners))
for _, owner := range d.Owners {
res = append(res, &owner)
}
return res
}
func (d *AmiFilterOptions) Empty() bool { func (d *AmiFilterOptions) Empty() bool {
return len(d.Owners) == 0 && len(d.Filters) == 0 return len(d.Owners) == 0 && len(d.Filters) == 0
} }
@ -32,7 +41,7 @@ func (d *AmiFilterOptions) NoOwner() bool {
} }
type SubnetFilterOptions struct { type SubnetFilterOptions struct {
Filters map[*string]*string Filters map[string]string
MostFree bool `mapstructure:"most_free"` MostFree bool `mapstructure:"most_free"`
Random bool `mapstructure:"random"` Random bool `mapstructure:"random"`
} }
@ -42,7 +51,16 @@ func (d *SubnetFilterOptions) Empty() bool {
} }
type VpcFilterOptions struct { type VpcFilterOptions struct {
Filters map[*string]*string Filters map[string]string
}
type PolicyDocument struct {
Version string
Statement []struct {
Effect string
Action []string
Resource string
}
} }
func (d *VpcFilterOptions) Empty() bool { func (d *VpcFilterOptions) Empty() bool {
@ -50,7 +68,7 @@ func (d *VpcFilterOptions) Empty() bool {
} }
type SecurityGroupFilterOptions struct { type SecurityGroupFilterOptions struct {
Filters map[*string]*string Filters map[string]string
} }
func (d *SecurityGroupFilterOptions) Empty() bool { func (d *SecurityGroupFilterOptions) Empty() bool {
@ -124,6 +142,25 @@ type RunConfig struct {
// profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html) // profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html)
// to launch the EC2 instance with. // to launch the EC2 instance with.
IamInstanceProfile string `mapstructure:"iam_instance_profile" required:"false"` IamInstanceProfile string `mapstructure:"iam_instance_profile" required:"false"`
// Temporary IAM instance profile policy document
// If IamInstanceProfile is specified it will be used instead. Example:
//
// ```json
//{
// "Version": "2012-10-17",
// "Statement": [
// {
// "Action": [
// "logs:*"
// ],
// "Effect": "Allow",
// "Resource": "*"
// }
// ]
//}
// ```
//
TemporaryIamInstanceProfilePolicyDocument *PolicyDocument `mapstructure:"temporary_iam_instance_profile_policy_document" required:"false"`
// Automatically terminate instances on // Automatically terminate instances on
// shutdown in case Packer exits ungracefully. Possible values are stop and // shutdown in case Packer exits ungracefully. Possible values are stop and
// terminate. Defaults to stop. // terminate. Defaults to stop.

View File

@ -0,0 +1,97 @@
// Code generated by "mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions"; DO NOT EDIT.
package common
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatAmiFilterOptions is an auto-generated flat version of AmiFilterOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatAmiFilterOptions struct {
Filters map[string]string `cty:"filters"`
Owners []string `cty:"owners"`
MostRecent *bool `mapstructure:"most_recent" cty:"most_recent"`
}
// FlatMapstructure returns a new FlatAmiFilterOptions.
// FlatAmiFilterOptions is an auto-generated flat version of AmiFilterOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*AmiFilterOptions) FlatMapstructure() interface{} { return new(FlatAmiFilterOptions) }
// HCL2Spec returns the hcldec.Spec of a FlatAmiFilterOptions.
// This spec is used by HCL to read the fields of FlatAmiFilterOptions.
func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
"owners": &hcldec.AttrSpec{Name: "owners", Type: cty.List(cty.String), Required: false},
"most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false},
}
return s
}
// FlatSecurityGroupFilterOptions is an auto-generated flat version of SecurityGroupFilterOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSecurityGroupFilterOptions struct {
Filters map[string]string `cty:"filters"`
}
// FlatMapstructure returns a new FlatSecurityGroupFilterOptions.
// FlatSecurityGroupFilterOptions is an auto-generated flat version of SecurityGroupFilterOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*SecurityGroupFilterOptions) FlatMapstructure() interface{} {
return new(FlatSecurityGroupFilterOptions)
}
// HCL2Spec returns the hcldec.Spec of a FlatSecurityGroupFilterOptions.
// This spec is used by HCL to read the fields of FlatSecurityGroupFilterOptions.
func (*FlatSecurityGroupFilterOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
}
return s
}
// FlatSubnetFilterOptions is an auto-generated flat version of SubnetFilterOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSubnetFilterOptions struct {
Filters map[string]string `cty:"filters"`
MostFree *bool `mapstructure:"most_free" cty:"most_free"`
Random *bool `mapstructure:"random" cty:"random"`
}
// FlatMapstructure returns a new FlatSubnetFilterOptions.
// FlatSubnetFilterOptions is an auto-generated flat version of SubnetFilterOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*SubnetFilterOptions) FlatMapstructure() interface{} { return new(FlatSubnetFilterOptions) }
// HCL2Spec returns the hcldec.Spec of a FlatSubnetFilterOptions.
// This spec is used by HCL to read the fields of FlatSubnetFilterOptions.
func (*FlatSubnetFilterOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
"most_free": &hcldec.AttrSpec{Name: "most_free", Type: cty.Bool, Required: false},
"random": &hcldec.AttrSpec{Name: "random", Type: cty.Bool, Required: false},
}
return s
}
// FlatVpcFilterOptions is an auto-generated flat version of VpcFilterOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatVpcFilterOptions struct {
Filters map[string]string `cty:"filters"`
}
// FlatMapstructure returns a new FlatVpcFilterOptions.
// FlatVpcFilterOptions is an auto-generated flat version of VpcFilterOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*VpcFilterOptions) FlatMapstructure() interface{} { return new(FlatVpcFilterOptions) }
// HCL2Spec returns the hcldec.Spec of a FlatVpcFilterOptions.
// This spec is used by HCL to read the fields of FlatVpcFilterOptions.
func (*FlatVpcFilterOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"filters": &hcldec.BlockAttrsSpec{TypeName: "filters", ElementType: cty.String, Required: false},
}
return s
}

View File

@ -73,7 +73,7 @@ func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
c := testConfigFilter() c := testConfigFilter()
filter_key := "name" filter_key := "name"
filter_value := "foo" filter_value := "foo"
c.SourceAmiFilter = AmiFilterOptions{Filters: map[*string]*string{&filter_key: &filter_value}} c.SourceAmiFilter = AmiFilterOptions{Filters: map[string]string{filter_key: filter_value}}
if err := c.Prepare(nil); len(err) != 1 { if err := c.Prepare(nil); len(err) != 1 {
t.Fatalf("Should error if Owners is not specified)") t.Fatalf("Should error if Owners is not specified)")
} }
@ -84,7 +84,7 @@ func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
owner := "123" owner := "123"
filter_key := "name" filter_key := "name"
filter_value := "foo" filter_value := "foo"
goodFilter := AmiFilterOptions{Owners: []*string{&owner}, Filters: map[*string]*string{&filter_key: &filter_value}} goodFilter := AmiFilterOptions{Owners: []string{owner}, Filters: map[string]string{filter_key: filter_value}}
c.SourceAmiFilter = goodFilter c.SourceAmiFilter = goodFilter
if err := c.Prepare(nil); len(err) != 0 { if err := c.Prepare(nil); len(err) != 0 {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)

View File

@ -0,0 +1,194 @@
package common
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"log"
"time"
"encoding/json"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepIamInstanceProfile struct {
IamInstanceProfile string
TemporaryIamInstanceProfilePolicyDocument *PolicyDocument
createdInstanceProfileName string
createdRoleName string
createdPolicyName string
roleIsAttached bool
}
func (s *StepIamInstanceProfile) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
iamsvc := state.Get("iam").(*iam.IAM)
ui := state.Get("ui").(packer.Ui)
state.Put("iamInstanceProfile", "")
if len(s.IamInstanceProfile) > 0 {
_, err := iamsvc.GetInstanceProfile(
&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(s.IamInstanceProfile),
},
)
if err != nil {
err := fmt.Errorf("Couldn't find specified instance profile: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
log.Printf("Using specified instance profile: %v", s.IamInstanceProfile)
state.Put("iamInstanceProfile", s.IamInstanceProfile)
return multistep.ActionContinue
}
if s.TemporaryIamInstanceProfilePolicyDocument != nil {
// Create the profile
profileName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
policy, err := json.Marshal(s.TemporaryIamInstanceProfilePolicyDocument)
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Creating temporary instance profile for this instance: %s", profileName))
profileResp, err := iamsvc.CreateInstanceProfile(&iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(profileName),
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
s.createdInstanceProfileName = aws.StringValue(profileResp.InstanceProfile.InstanceProfileName)
log.Printf("[DEBUG] Waiting for temporary instance profile: %s", s.createdInstanceProfileName)
err = iamsvc.WaitUntilInstanceProfileExists(&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(s.createdInstanceProfileName),
})
if err == nil {
log.Printf("[DEBUG] Found instance profile %s", s.createdInstanceProfileName)
} else {
err := fmt.Errorf("Timed out waiting for instance profile %s: %s", s.createdInstanceProfileName, err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Creating temporary role for this instance: %s", profileName))
roleResp, err := iamsvc.CreateRole(&iam.CreateRoleInput{
RoleName: aws.String(profileName),
Description: aws.String("Temporary role for Packer"),
AssumeRolePolicyDocument: aws.String("{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Principal\": {\"Service\": \"ec2.amazonaws.com\"},\"Action\": \"sts:AssumeRole\"}]}"),
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
s.createdRoleName = aws.StringValue(roleResp.Role.RoleName)
log.Printf("[DEBUG] Waiting for temporary role: %s", s.createdInstanceProfileName)
err = iamsvc.WaitUntilRoleExists(&iam.GetRoleInput{
RoleName: aws.String(s.createdRoleName),
})
if err == nil {
log.Printf("[DEBUG] Found temporary role %s", s.createdRoleName)
} else {
err := fmt.Errorf("Timed out waiting for temporary role %s: %s", s.createdRoleName, err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Attaching policy to the temporary role: %s", profileName))
_, err = iamsvc.PutRolePolicy(&iam.PutRolePolicyInput{
RoleName: roleResp.Role.RoleName,
PolicyName: aws.String(profileName),
PolicyDocument: aws.String(string(policy)),
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
s.createdPolicyName = aws.StringValue(roleResp.Role.RoleName)
_, err = iamsvc.AddRoleToInstanceProfile(&iam.AddRoleToInstanceProfileInput{
RoleName: roleResp.Role.RoleName,
InstanceProfileName: profileResp.InstanceProfile.InstanceProfileName,
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
s.roleIsAttached = true
state.Put("iamInstanceProfile", aws.StringValue(profileResp.InstanceProfile.InstanceProfileName))
time.Sleep(5 * time.Second)
}
return multistep.ActionContinue
}
func (s *StepIamInstanceProfile) Cleanup(state multistep.StateBag) {
iamsvc := state.Get("iam").(*iam.IAM)
ui := state.Get("ui").(packer.Ui)
var err error
if s.roleIsAttached == true {
ui.Say("Detaching temporary role from instance profile...")
_, err := iamsvc.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{
InstanceProfileName: aws.String(s.createdInstanceProfileName),
RoleName: aws.String(s.createdRoleName),
})
if err != nil {
ui.Error(fmt.Sprintf(
"Error %s. Please delete the role manually: %s", err.Error(), s.createdRoleName))
}
}
if s.createdPolicyName != "" {
ui.Say("Removing policy from temporary role...")
iamsvc.DeleteRolePolicy(&iam.DeleteRolePolicyInput{
PolicyName: aws.String(s.createdPolicyName),
RoleName: aws.String(s.createdRoleName),
})
}
if s.createdRoleName != "" {
ui.Say("Deleting temporary role...")
_, err = iamsvc.DeleteRole(&iam.DeleteRoleInput{RoleName: &s.createdRoleName})
if err != nil {
ui.Error(fmt.Sprintf(
"Error %s. Please delete the role manually: %s", err.Error(), s.createdRoleName))
}
}
if s.createdInstanceProfileName != "" {
ui.Say("Deleting temporary instance profile...")
_, err = iamsvc.DeleteInstanceProfile(&iam.DeleteInstanceProfileInput{
InstanceProfileName: &s.createdInstanceProfileName})
if err != nil {
ui.Error(fmt.Sprintf(
"Error %s. Please delete the instance profile manually: %s", err.Error(), s.createdInstanceProfileName))
}
}
}

View File

@ -7,7 +7,6 @@ import (
"math/rand" "math/rand"
"sort" "sort"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -53,7 +52,7 @@ func (s *StepNetworkInfo) Run(ctx context.Context, state multistep.StateBag) mul
if s.VpcId == "" && !s.VpcFilter.Empty() { if s.VpcId == "" && !s.VpcFilter.Empty() {
params := &ec2.DescribeVpcsInput{} params := &ec2.DescribeVpcsInput{}
params.Filters = buildEc2Filters(s.VpcFilter.Filters) params.Filters = buildEc2Filters(s.VpcFilter.Filters)
s.VpcFilter.Filters[aws.String("state")] = aws.String("available") s.VpcFilter.Filters["state"] = "available"
log.Printf("Using VPC Filters %v", params) log.Printf("Using VPC Filters %v", params)
@ -79,13 +78,13 @@ func (s *StepNetworkInfo) Run(ctx context.Context, state multistep.StateBag) mul
// Subnet // Subnet
if s.SubnetId == "" && !s.SubnetFilter.Empty() { if s.SubnetId == "" && !s.SubnetFilter.Empty() {
params := &ec2.DescribeSubnetsInput{} params := &ec2.DescribeSubnetsInput{}
s.SubnetFilter.Filters[aws.String("state")] = aws.String("available") s.SubnetFilter.Filters["state"] = "available"
if s.VpcId != "" { if s.VpcId != "" {
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId s.SubnetFilter.Filters["vpc-id"] = s.VpcId
} }
if s.AvailabilityZone != "" { if s.AvailabilityZone != "" {
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone s.SubnetFilter.Filters["availabilityZone"] = s.AvailabilityZone
} }
params.Filters = buildEc2Filters(s.SubnetFilter.Filters) params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
log.Printf("Using Subnet Filters %v", params) log.Printf("Using Subnet Filters %v", params)

View File

@ -28,7 +28,6 @@ type StepRunSourceInstance struct {
EbsOptimized bool EbsOptimized bool
EnableT2Unlimited bool EnableT2Unlimited bool
ExpectedRootDevice string ExpectedRootDevice string
IamInstanceProfile string
InstanceInitiatedShutdownBehavior string InstanceInitiatedShutdownBehavior string
InstanceType string InstanceType string
IsRestricted bool IsRestricted bool
@ -45,6 +44,8 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
ec2conn := state.Get("ec2").(*ec2.EC2) ec2conn := state.Get("ec2").(*ec2.EC2)
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string)) securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
userData := s.UserData userData := s.UserData
@ -110,7 +111,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
UserData: &userData, UserData: &userData,
MaxCount: aws.Int64(1), MaxCount: aws.Int64(1),
MinCount: aws.Int64(1), MinCount: aws.Int64(1),
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile}, IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: iamInstanceProfile},
BlockDeviceMappings: s.LaunchMappings.BuildEC2BlockDeviceMappings(), BlockDeviceMappings: s.LaunchMappings.BuildEC2BlockDeviceMappings(),
Placement: &ec2.Placement{AvailabilityZone: &az}, Placement: &ec2.Placement{AvailabilityZone: &az},
EbsOptimized: &s.EbsOptimized, EbsOptimized: &s.EbsOptimized,

View File

@ -31,7 +31,6 @@ type StepRunSpotInstance struct {
Comm *communicator.Config Comm *communicator.Config
EbsOptimized bool EbsOptimized bool
ExpectedRootDevice string ExpectedRootDevice string
IamInstanceProfile string
InstanceInitiatedShutdownBehavior string InstanceInitiatedShutdownBehavior string
InstanceType string InstanceType string
SourceAMI string SourceAMI string
@ -69,12 +68,14 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
launchMappingRequests = append(launchMappingRequests, launchRequest) launchMappingRequests = append(launchMappingRequests, launchRequest)
} }
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
// Create a launch template. // Create a launch template.
templateData := ec2.RequestLaunchTemplateData{ templateData := ec2.RequestLaunchTemplateData{
BlockDeviceMappings: launchMappingRequests, BlockDeviceMappings: launchMappingRequests,
DisableApiTermination: aws.Bool(false), DisableApiTermination: aws.Bool(false),
EbsOptimized: &s.EbsOptimized, EbsOptimized: &s.EbsOptimized,
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: &s.IamInstanceProfile}, IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: iamInstanceProfile},
ImageId: &s.SourceAMI, ImageId: &s.SourceAMI,
InstanceMarketOptions: marketOptions, InstanceMarketOptions: marketOptions,
Placement: &ec2.LaunchTemplatePlacementRequest{ Placement: &ec2.LaunchTemplatePlacementRequest{
@ -274,15 +275,20 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt return multistep.ActionHalt
} }
if len(createOutput.Errors) > 0 { if len(createOutput.Instances) == 0 {
errString := fmt.Sprintf("Error waiting for fleet request (%s) to become ready:", *createOutput.FleetId) // We can end up with errors because one of the allowed availability
for _, outErr := range createOutput.Errors { // zones doesn't have one of the allowed instance types; as long as
errString = errString + fmt.Sprintf("%s", *outErr.ErrorMessage) // an instance is launched, these errors aren't important.
if len(createOutput.Errors) > 0 {
errString := fmt.Sprintf("Error waiting for fleet request (%s) to become ready:", *createOutput.FleetId)
for _, outErr := range createOutput.Errors {
errString = errString + fmt.Sprintf("%s", *outErr.ErrorMessage)
}
err = fmt.Errorf(errString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} }
err = fmt.Errorf(errString)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
} }
instanceId = *createOutput.Instances[0].InstanceIds[0] instanceId = *createOutput.Instances[0].InstanceIds[0]

View File

@ -2,6 +2,7 @@ package common
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"time" "time"
@ -73,6 +74,7 @@ func tStateSpot() multistep.StateBag {
}) })
state.Put("availability_zone", "us-east-1c") state.Put("availability_zone", "us-east-1c")
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"}) state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
state.Put("iamInstanceProfile", "packer-123")
state.Put("subnet_id", "subnet-077fde4e") state.Put("subnet_id", "subnet-077fde4e")
state.Put("source_image", "") state.Put("source_image", "")
return state return state
@ -91,7 +93,6 @@ func getBasicStep() *StepRunSpotInstance {
}, },
EbsOptimized: false, EbsOptimized: false,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: "",
InstanceInitiatedShutdownBehavior: "stop", InstanceInitiatedShutdownBehavior: "stop",
InstanceType: "t2.micro", InstanceType: "t2.micro",
SourceAMI: "", SourceAMI: "",
@ -125,6 +126,10 @@ func TestCreateTemplateData(t *testing.T) {
t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces) t.Fatalf("Template should have contained a networkInterface object: recieved %#v", template.NetworkInterfaces)
} }
if *template.IamInstanceProfile.Name != state.Get("iamInstanceProfile") {
t.Fatalf("Template should have contained a InstanceProfile name: recieved %#v", template.IamInstanceProfile.Name)
}
// Rerun, this time testing that we set security group IDs // Rerun, this time testing that we set security group IDs
state.Put("subnet_id", "") state.Put("subnet_id", "")
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state, template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
@ -132,4 +137,13 @@ func TestCreateTemplateData(t *testing.T) {
if template.NetworkInterfaces != nil { if template.NetworkInterfaces != nil {
t.Fatalf("Template shouldn't contain network interfaces object if subnet_id is unset.") t.Fatalf("Template shouldn't contain network interfaces object if subnet_id is unset.")
} }
// Rerun, this time testing that instance doesn't have instance profile is iamInstanceProfile is unset
state.Put("iamInstanceProfile", "")
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
fmt.Println(template.IamInstanceProfile)
if *template.IamInstanceProfile.Name != "" {
t.Fatalf("Template shouldn't contain instance profile if iamInstanceProfile is unset.")
}
} }

View File

@ -51,7 +51,7 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
params := &ec2.DescribeSecurityGroupsInput{} params := &ec2.DescribeSecurityGroupsInput{}
if vpcId != "" { if vpcId != "" {
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId s.SecurityGroupFilter.Filters["vpc-id"] = vpcId
} }
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters) params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)

View File

@ -58,7 +58,7 @@ func (s *StepSourceAMIInfo) Run(ctx context.Context, state multistep.StateBag) m
params.Filters = buildEc2Filters(s.AmiFilters.Filters) params.Filters = buildEc2Filters(s.AmiFilters.Filters)
} }
if len(s.AmiFilters.Owners) > 0 { if len(s.AmiFilters.Owners) > 0 {
params.Owners = s.AmiFilters.Owners params.Owners = s.AmiFilters.GetOwners()
} }
log.Printf("Using AMI Filters %v", params) log.Printf("Using AMI Filters %v", params)

View File

@ -1,4 +1,5 @@
//go:generate struct-markdown //go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config
// The amazonebs package contains a packer.Builder implementation that // The amazonebs package contains a packer.Builder implementation that
// builds AMIs for Amazon EC2. // builds AMIs for Amazon EC2.
@ -12,6 +13,7 @@ import (
"fmt" "fmt"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
awscommon "github.com/hashicorp/packer/builder/amazon/common" awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/communicator"
@ -126,13 +128,14 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
} }
ec2conn := ec2.New(session) ec2conn := ec2.New(session)
iam := iam.New(session)
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
state.Put("config", &b.config) state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig) state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig) state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn) state.Put("ec2", ec2conn)
state.Put("iam", iam)
state.Put("awsSession", session) state.Put("awsSession", session)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
@ -149,7 +152,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
SourceAMI: b.config.SourceAmi, SourceAMI: b.config.SourceAmi,
@ -171,7 +173,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited, EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
@ -217,6 +218,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
CommConfig: &b.config.RunConfig.Comm, CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs, TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
}, },
&awscommon.StepIamInstanceProfile{
IamInstanceProfile: b.config.IamInstanceProfile,
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
&awscommon.StepCleanupVolumes{ &awscommon.StepCleanupVolumes{
LaunchMappings: b.config.LaunchMappings, LaunchMappings: b.config.LaunchMappings,
}, },

View File

@ -0,0 +1,244 @@
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
package ebs
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/builder/amazon/common"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key"`
CustomEndpointEc2 *string `mapstructure:"custom_endpoint_ec2" required:"false" cty:"custom_endpoint_ec2"`
DecodeAuthZMessages *bool `mapstructure:"decode_authorization_messages" required:"false" cty:"decode_authorization_messages"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" required:"false" cty:"insecure_skip_tls_verify"`
MFACode *string `mapstructure:"mfa_code" required:"false" cty:"mfa_code"`
ProfileName *string `mapstructure:"profile" required:"false" cty:"profile"`
RawRegion *string `mapstructure:"region" required:"true" cty:"region"`
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key"`
SkipValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation"`
SkipMetadataApiCheck *bool `mapstructure:"skip_metadata_api_check" cty:"skip_metadata_api_check"`
Token *string `mapstructure:"token" required:"false" cty:"token"`
VaultAWSEngine *common.FlatVaultAWSEngineOptions `mapstructure:"vault_aws_engine" required:"false" cty:"vault_aws_engine"`
AMIName *string `mapstructure:"ami_name" required:"true" cty:"ami_name"`
AMIDescription *string `mapstructure:"ami_description" required:"false" cty:"ami_description"`
AMIVirtType *string `mapstructure:"ami_virtualization_type" required:"false" cty:"ami_virtualization_type"`
AMIUsers []string `mapstructure:"ami_users" required:"false" cty:"ami_users"`
AMIGroups []string `mapstructure:"ami_groups" required:"false" cty:"ami_groups"`
AMIProductCodes []string `mapstructure:"ami_product_codes" required:"false" cty:"ami_product_codes"`
AMIRegions []string `mapstructure:"ami_regions" required:"false" cty:"ami_regions"`
AMITags common.TagMap `mapstructure:"tags" required:"false" cty:"tags"`
AMIENASupport *bool `mapstructure:"ena_support" required:"false" cty:"ena_support"`
AMISriovNetSupport *bool `mapstructure:"sriov_support" required:"false" cty:"sriov_support"`
AMIForceDeregister *bool `mapstructure:"force_deregister" required:"false" cty:"force_deregister"`
AMIForceDeleteSnapshot *bool `mapstructure:"force_delete_snapshot" required:"false" cty:"force_delete_snapshot"`
AMIEncryptBootVolume *bool `mapstructure:"encrypt_boot" required:"false" cty:"encrypt_boot"`
AMIKmsKeyId *string `mapstructure:"kms_key_id" required:"false" cty:"kms_key_id"`
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids" required:"false" cty:"region_kms_key_ids"`
AMISkipBuildRegion *bool `mapstructure:"skip_save_build_region" cty:"skip_save_build_region"`
SnapshotTags common.TagMap `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags"`
SnapshotUsers []string `mapstructure:"snapshot_users" required:"false" cty:"snapshot_users"`
SnapshotGroups []string `mapstructure:"snapshot_groups" required:"false" cty:"snapshot_groups"`
AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address"`
AvailabilityZone *string `mapstructure:"availability_zone" required:"false" cty:"availability_zone"`
BlockDurationMinutes *int64 `mapstructure:"block_duration_minutes" required:"false" cty:"block_duration_minutes"`
DisableStopInstance *bool `mapstructure:"disable_stop_instance" required:"false" cty:"disable_stop_instance"`
EbsOptimized *bool `mapstructure:"ebs_optimized" required:"false" cty:"ebs_optimized"`
EnableT2Unlimited *bool `mapstructure:"enable_t2_unlimited" required:"false" cty:"enable_t2_unlimited"`
IamInstanceProfile *string `mapstructure:"iam_instance_profile" required:"false" cty:"iam_instance_profile"`
InstanceInitiatedShutdownBehavior *string `mapstructure:"shutdown_behavior" required:"false" cty:"shutdown_behavior"`
InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type"`
SecurityGroupFilter *common.FlatSecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false" cty:"security_group_filter"`
RunTags map[string]string `mapstructure:"run_tags" required:"false" cty:"run_tags"`
SecurityGroupId *string `mapstructure:"security_group_id" required:"false" cty:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false" cty:"security_group_ids"`
SourceAmi *string `mapstructure:"source_ami" required:"true" cty:"source_ami"`
SourceAmiFilter *common.FlatAmiFilterOptions `mapstructure:"source_ami_filter" required:"false" cty:"source_ami_filter"`
SpotInstanceTypes []string `mapstructure:"spot_instance_types" required:"false" cty:"spot_instance_types"`
SpotPrice *string `mapstructure:"spot_price" required:"false" cty:"spot_price"`
SpotPriceAutoProduct *string `mapstructure:"spot_price_auto_product" required:"false" cty:"spot_price_auto_product"`
SpotTags map[string]string `mapstructure:"spot_tags" required:"false" cty:"spot_tags"`
SubnetFilter *common.FlatSubnetFilterOptions `mapstructure:"subnet_filter" required:"false" cty:"subnet_filter"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id"`
TemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" required:"false" cty:"temporary_key_pair_name"`
TemporarySGSourceCidrs []string `mapstructure:"temporary_security_group_source_cidrs" required:"false" cty:"temporary_security_group_source_cidrs"`
UserData *string `mapstructure:"user_data" required:"false" cty:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" required:"false" cty:"user_data_file"`
VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter"`
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id"`
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout"`
Type *string `mapstructure:"communicator" cty:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
SSHPublicKey []byte `cty:"ssh_public_key"`
SSHPrivateKey []byte `cty:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings"`
VolumeRunTags common.TagMap `mapstructure:"run_volume_tags" cty:"run_volume_tags"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
// This spec is used by HCL to read the fields of FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"access_key": &hcldec.AttrSpec{Name: "access_key", Type: cty.String, Required: false},
"custom_endpoint_ec2": &hcldec.AttrSpec{Name: "custom_endpoint_ec2", Type: cty.String, Required: false},
"decode_authorization_messages": &hcldec.AttrSpec{Name: "decode_authorization_messages", Type: cty.Bool, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"mfa_code": &hcldec.AttrSpec{Name: "mfa_code", Type: cty.String, Required: false},
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
"secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false},
"skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false},
"skip_metadata_api_check": &hcldec.AttrSpec{Name: "skip_metadata_api_check", Type: cty.Bool, Required: false},
"token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false},
"vault_aws_engine": &hcldec.BlockSpec{TypeName: "vault_aws_engine", Nested: hcldec.ObjectSpec((*common.FlatVaultAWSEngineOptions)(nil).HCL2Spec())},
"ami_name": &hcldec.AttrSpec{Name: "ami_name", Type: cty.String, Required: false},
"ami_description": &hcldec.AttrSpec{Name: "ami_description", Type: cty.String, Required: false},
"ami_virtualization_type": &hcldec.AttrSpec{Name: "ami_virtualization_type", Type: cty.String, Required: false},
"ami_users": &hcldec.AttrSpec{Name: "ami_users", Type: cty.List(cty.String), Required: false},
"ami_groups": &hcldec.AttrSpec{Name: "ami_groups", Type: cty.List(cty.String), Required: false},
"ami_product_codes": &hcldec.AttrSpec{Name: "ami_product_codes", Type: cty.List(cty.String), Required: false},
"ami_regions": &hcldec.AttrSpec{Name: "ami_regions", Type: cty.List(cty.String), Required: false},
"tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
"ena_support": &hcldec.AttrSpec{Name: "ena_support", Type: cty.Bool, Required: false},
"sriov_support": &hcldec.AttrSpec{Name: "sriov_support", Type: cty.Bool, Required: false},
"force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false},
"force_delete_snapshot": &hcldec.AttrSpec{Name: "force_delete_snapshot", Type: cty.Bool, Required: false},
"encrypt_boot": &hcldec.AttrSpec{Name: "encrypt_boot", Type: cty.Bool, Required: false},
"kms_key_id": &hcldec.AttrSpec{Name: "kms_key_id", Type: cty.String, Required: false},
"region_kms_key_ids": &hcldec.BlockAttrsSpec{TypeName: "region_kms_key_ids", ElementType: cty.String, Required: false},
"skip_save_build_region": &hcldec.AttrSpec{Name: "skip_save_build_region", Type: cty.Bool, Required: false},
"snapshot_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
"snapshot_users": &hcldec.AttrSpec{Name: "snapshot_users", Type: cty.List(cty.String), Required: false},
"snapshot_groups": &hcldec.AttrSpec{Name: "snapshot_groups", Type: cty.List(cty.String), Required: false},
"associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false},
"availability_zone": &hcldec.AttrSpec{Name: "availability_zone", Type: cty.String, Required: false},
"block_duration_minutes": &hcldec.AttrSpec{Name: "block_duration_minutes", Type: cty.Number, Required: false},
"disable_stop_instance": &hcldec.AttrSpec{Name: "disable_stop_instance", Type: cty.Bool, Required: false},
"ebs_optimized": &hcldec.AttrSpec{Name: "ebs_optimized", Type: cty.Bool, Required: false},
"enable_t2_unlimited": &hcldec.AttrSpec{Name: "enable_t2_unlimited", Type: cty.Bool, Required: false},
"iam_instance_profile": &hcldec.AttrSpec{Name: "iam_instance_profile", Type: cty.String, Required: false},
"shutdown_behavior": &hcldec.AttrSpec{Name: "shutdown_behavior", Type: cty.String, Required: false},
"instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false},
"security_group_filter": &hcldec.BlockSpec{TypeName: "security_group_filter", Nested: hcldec.ObjectSpec((*common.FlatSecurityGroupFilterOptions)(nil).HCL2Spec())},
"run_tags": &hcldec.BlockAttrsSpec{TypeName: "run_tags", ElementType: cty.String, Required: false},
"security_group_id": &hcldec.AttrSpec{Name: "security_group_id", Type: cty.String, Required: false},
"security_group_ids": &hcldec.AttrSpec{Name: "security_group_ids", Type: cty.List(cty.String), Required: false},
"source_ami": &hcldec.AttrSpec{Name: "source_ami", Type: cty.String, Required: false},
"source_ami_filter": &hcldec.BlockSpec{TypeName: "source_ami_filter", Nested: hcldec.ObjectSpec((*common.FlatAmiFilterOptions)(nil).HCL2Spec())},
"spot_instance_types": &hcldec.AttrSpec{Name: "spot_instance_types", Type: cty.List(cty.String), Required: false},
"spot_price": &hcldec.AttrSpec{Name: "spot_price", Type: cty.String, Required: false},
"spot_price_auto_product": &hcldec.AttrSpec{Name: "spot_price_auto_product", Type: cty.String, Required: false},
"spot_tags": &hcldec.BlockAttrsSpec{TypeName: "spot_tags", ElementType: cty.String, Required: false},
"subnet_filter": &hcldec.BlockSpec{TypeName: "subnet_filter", Nested: hcldec.ObjectSpec((*common.FlatSubnetFilterOptions)(nil).HCL2Spec())},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_security_group_source_cidrs": &hcldec.AttrSpec{Name: "temporary_security_group_source_cidrs", Type: cty.List(cty.String), Required: false},
"user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false},
"user_data_file": &hcldec.AttrSpec{Name: "user_data_file", Type: cty.String, Required: false},
"vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: &hcldec.BlockSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())}},
"run_volume_tags": &hcldec.BlockAttrsSpec{TypeName: "common.TagMap", ElementType: cty.String, Required: false},
}
return s
}

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common" awscommon "github.com/hashicorp/packer/builder/amazon/common"
@ -164,7 +165,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
if err != nil { if err != nil {
return nil, err return nil, err
} }
ec2conn := ec2.New(session) ec2conn := ec2.New(session)
iam := iam.New(session)
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
@ -172,6 +175,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("access_config", &b.config.AccessConfig) state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig) state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn) state.Put("ec2", ec2conn)
state.Put("iam", iam)
state.Put("awsSession", session) state.Put("awsSession", session)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
@ -188,7 +192,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
SourceAMI: b.config.SourceAmi, SourceAMI: b.config.SourceAmi,
@ -210,7 +213,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited, EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
@ -258,6 +260,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
CommConfig: &b.config.RunConfig.Comm, CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs, TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
}, },
&awscommon.StepIamInstanceProfile{
IamInstanceProfile: b.config.IamInstanceProfile,
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
&awscommon.StepCleanupVolumes{ &awscommon.StepCleanupVolumes{
LaunchMappings: b.config.LaunchMappings.Common(), LaunchMappings: b.config.LaunchMappings.Common(),
}, },
@ -308,6 +314,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EnableAMIENASupport: b.config.AMIENASupport, EnableAMIENASupport: b.config.AMIENASupport,
Architecture: b.config.Architecture, Architecture: b.config.Architecture,
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(), LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
}, },
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,

View File

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common" awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common/random"
confighelper "github.com/hashicorp/packer/helper/config" confighelper "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -22,6 +23,7 @@ type StepRegisterAMI struct {
Architecture string Architecture string
image *ec2.Image image *ec2.Image
LaunchOmitMap map[string]bool LaunchOmitMap map[string]bool
AMISkipBuildRegion bool
} }
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -34,8 +36,25 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
blockDevices := s.combineDevices(snapshotIds) blockDevices := s.combineDevices(snapshotIds)
// Create the image
amiName := config.AMIName
state.Put("intermediary_image", false)
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
state.Put("intermediary_image", true)
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
// but you cannot use it to create an unencrypted copy of an encrypted
// snapshot. Your default CMK for EBS is used unless you specify a
// non-default key using KmsKeyId.
// If encrypt_boot is nil or true, we need to create a temporary image
// so that in step_region_copy, we can copy it with the correct
// encryption
amiName = random.AlphaNum(7)
}
registerOpts := &ec2.RegisterImageInput{ registerOpts := &ec2.RegisterImageInput{
Name: &config.AMIName, Name: &amiName,
Architecture: aws.String(s.Architecture), Architecture: aws.String(s.Architecture),
RootDeviceName: aws.String(s.RootDevice.DeviceName), RootDeviceName: aws.String(s.RootDevice.DeviceName),
VirtualizationType: aws.String(config.AMIVirtType), VirtualizationType: aws.String(config.AMIVirtType),

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam"
awscommon "github.com/hashicorp/packer/builder/amazon/common" awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/communicator"
@ -141,12 +142,14 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, err return nil, err
} }
ec2conn := ec2.New(session) ec2conn := ec2.New(session)
iam := iam.New(session)
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
state.Put("config", &b.config) state.Put("config", &b.config)
state.Put("access_config", &b.config.AccessConfig) state.Put("access_config", &b.config.AccessConfig)
state.Put("ec2", ec2conn) state.Put("ec2", ec2conn)
state.Put("iam", iam)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
@ -162,7 +165,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
SourceAMI: b.config.SourceAmi, SourceAMI: b.config.SourceAmi,
@ -184,7 +186,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited, EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs", ExpectedRootDevice: "ebs",
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior, InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
@ -224,6 +225,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
CommConfig: &b.config.RunConfig.Comm, CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs, TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
}, },
&awscommon.StepIamInstanceProfile{
IamInstanceProfile: b.config.IamInstanceProfile,
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
instanceStep, instanceStep,
&stepTagEBSVolumes{ &stepTagEBSVolumes{
VolumeMapping: b.config.VolumeMappings, VolumeMapping: b.config.VolumeMappings,

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/aws/aws-sdk-go/service/iam"
"os" "os"
"strings" "strings"
@ -229,6 +230,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return nil, err return nil, err
} }
ec2conn := ec2.New(session) ec2conn := ec2.New(session)
iam := iam.New(session)
// Setup the state bag and initial state for the steps // Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag) state := new(multistep.BasicStateBag)
@ -236,6 +238,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("access_config", &b.config.AccessConfig) state.Put("access_config", &b.config.AccessConfig)
state.Put("ami_config", &b.config.AMIConfig) state.Put("ami_config", &b.config.AMIConfig)
state.Put("ec2", ec2conn) state.Put("ec2", ec2conn)
state.Put("iam", iam)
state.Put("awsSession", session) state.Put("awsSession", session)
state.Put("hook", hook) state.Put("hook", hook)
state.Put("ui", ui) state.Put("ui", ui)
@ -251,7 +254,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Comm: &b.config.RunConfig.Comm, Comm: &b.config.RunConfig.Comm,
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
SourceAMI: b.config.SourceAmi, SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice, SpotPrice: b.config.SpotPrice,
@ -270,7 +272,6 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized, EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited, EnableT2Unlimited: b.config.EnableT2Unlimited,
IamInstanceProfile: b.config.IamInstanceProfile,
InstanceType: b.config.InstanceType, InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(), IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi, SourceAMI: b.config.SourceAmi,
@ -313,6 +314,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SecurityGroupIds: b.config.SecurityGroupIds, SecurityGroupIds: b.config.SecurityGroupIds,
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs, TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
}, },
&awscommon.StepIamInstanceProfile{
IamInstanceProfile: b.config.IamInstanceProfile,
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
instanceStep, instanceStep,
&awscommon.StepGetPassword{ &awscommon.StepGetPassword{
Debug: b.config.PackerDebug, Debug: b.config.PackerDebug,
@ -350,6 +355,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
&StepRegisterAMI{ &StepRegisterAMI{
EnableAMISriovNetSupport: b.config.AMISriovNetSupport, EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
EnableAMIENASupport: b.config.AMIENASupport, EnableAMIENASupport: b.config.AMIENASupport,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
}, },
&awscommon.StepAMIRegionCopy{ &awscommon.StepAMIRegionCopy{
AccessConfig: &b.config.AccessConfig, AccessConfig: &b.config.AccessConfig,

View File

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
awscommon "github.com/hashicorp/packer/builder/amazon/common" awscommon "github.com/hashicorp/packer/builder/amazon/common"
"github.com/hashicorp/packer/common/random"
confighelper "github.com/hashicorp/packer/helper/config" confighelper "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -15,6 +16,7 @@ import (
type StepRegisterAMI struct { type StepRegisterAMI struct {
EnableAMIENASupport confighelper.Trilean EnableAMIENASupport confighelper.Trilean
EnableAMISriovNetSupport bool EnableAMISriovNetSupport bool
AMISkipBuildRegion bool
} }
func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
@ -24,9 +26,27 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
ui.Say("Registering the AMI...") ui.Say("Registering the AMI...")
// Create the image
amiName := config.AMIName
state.Put("intermediary_image", false)
if config.AMIEncryptBootVolume.True() || s.AMISkipBuildRegion {
state.Put("intermediary_image", true)
// From AWS SDK docs: You can encrypt a copy of an unencrypted snapshot,
// but you cannot use it to create an unencrypted copy of an encrypted
// snapshot. Your default CMK for EBS is used unless you specify a
// non-default key using KmsKeyId.
// If encrypt_boot is nil or true, we need to create a temporary image
// so that in step_region_copy, we can copy it with the correct
// encryption
amiName = random.AlphaNum(7)
}
registerOpts := &ec2.RegisterImageInput{ registerOpts := &ec2.RegisterImageInput{
ImageLocation: &manifestPath, ImageLocation: &manifestPath,
Name: aws.String(config.AMIName), Name: aws.String(amiName),
BlockDeviceMappings: config.AMIMappings.BuildEC2BlockDeviceMappings(), BlockDeviceMappings: config.AMIMappings.BuildEC2BlockDeviceMappings(),
} }

View File

@ -37,6 +37,7 @@ type AzureClient struct {
network.InterfacesClient network.InterfacesClient
network.SubnetsClient network.SubnetsClient
network.VirtualNetworksClient network.VirtualNetworksClient
network.SecurityGroupsClient
compute.ImagesClient compute.ImagesClient
compute.VirtualMachinesClient compute.VirtualMachinesClient
common.VaultClient common.VaultClient
@ -127,7 +128,7 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
} }
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string, func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, SharedGalleryTimeout time.Duration, cloud *azure.Environment, SharedGalleryTimeout time.Duration, PollingDuration time.Duration,
servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) { servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) {
var azureClient = &AzureClient{} var azureClient = &AzureClient{}
@ -139,72 +140,90 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen) azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DeploymentsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentsClient.UserAgent) azureClient.DeploymentsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentsClient.UserAgent)
azureClient.DeploymentsClient.Client.PollingDuration = PollingDuration
azureClient.DeploymentOperationsClient = resources.NewDeploymentOperationsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.DeploymentOperationsClient = resources.NewDeploymentOperationsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DeploymentOperationsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.DeploymentOperationsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen) azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen)
azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentOperationsClient.UserAgent) azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentOperationsClient.UserAgent)
azureClient.DeploymentOperationsClient.Client.PollingDuration = PollingDuration
azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.DisksClient.RequestInspector = withInspection(maxlen) azureClient.DisksClient.RequestInspector = withInspection(maxlen)
azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.DisksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DisksClient.UserAgent) azureClient.DisksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DisksClient.UserAgent)
azureClient.DisksClient.Client.PollingDuration = PollingDuration
azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GroupsClient.RequestInspector = withInspection(maxlen) azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GroupsClient.UserAgent) azureClient.GroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GroupsClient.UserAgent)
azureClient.GroupsClient.Client.PollingDuration = PollingDuration
azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.ImagesClient.RequestInspector = withInspection(maxlen) azureClient.ImagesClient.RequestInspector = withInspection(maxlen)
azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.ImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.ImagesClient.UserAgent) azureClient.ImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.ImagesClient.UserAgent)
azureClient.ImagesClient.Client.PollingDuration = PollingDuration
azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen) azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.InterfacesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.InterfacesClient.UserAgent) azureClient.InterfacesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.InterfacesClient.UserAgent)
azureClient.InterfacesClient.Client.PollingDuration = PollingDuration
azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen) azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SubnetsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SubnetsClient.UserAgent) azureClient.SubnetsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SubnetsClient.UserAgent)
azureClient.SubnetsClient.Client.PollingDuration = PollingDuration
azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen) azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualNetworksClient.UserAgent) azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualNetworksClient.UserAgent)
azureClient.VirtualNetworksClient.Client.PollingDuration = PollingDuration
azureClient.SecurityGroupsClient = network.NewSecurityGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SecurityGroupsClient.RequestInspector = withInspection(maxlen)
azureClient.SecurityGroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SecurityGroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SecurityGroupsClient.UserAgent)
azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen) azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.PublicIPAddressesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.PublicIPAddressesClient.UserAgent) azureClient.PublicIPAddressesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.PublicIPAddressesClient.UserAgent)
azureClient.PublicIPAddressesClient.Client.PollingDuration = PollingDuration
azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen) azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient)) azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent) azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent)
azureClient.VirtualMachinesClient.Client.PollingDuration = PollingDuration
azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.SnapshotsClient = compute.NewSnapshotsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.SnapshotsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen) azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SnapshotsClient.UserAgent) azureClient.SnapshotsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SnapshotsClient.UserAgent)
azureClient.SnapshotsClient.Client.PollingDuration = PollingDuration
azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.AccountsClient.RequestInspector = withInspection(maxlen) azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.AccountsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.AccountsClient.UserAgent) azureClient.AccountsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.AccountsClient.UserAgent)
azureClient.AccountsClient.Client.PollingDuration = PollingDuration
azureClient.GalleryImageVersionsClient = newCompute.NewGalleryImageVersionsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.GalleryImageVersionsClient = newCompute.NewGalleryImageVersionsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImageVersionsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.GalleryImageVersionsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
@ -218,6 +237,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen) azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen)
azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImagesClient.UserAgent) azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImagesClient.UserAgent)
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint) keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
if err != nil { if err != nil {
@ -229,6 +249,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
azureClient.VaultClient.RequestInspector = withInspection(maxlen) azureClient.VaultClient.RequestInspector = withInspection(maxlen)
azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClient.UserAgent) azureClient.VaultClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClient.UserAgent)
azureClient.VaultClient.Client.PollingDuration = PollingDuration
// TODO(boumenot) - SDK still does not have a full KeyVault client. // TODO(boumenot) - SDK still does not have a full KeyVault client.
// There are two ways that KeyVault has to be accessed, and each one has their own SPN. An authenticated SPN // There are two ways that KeyVault has to be accessed, and each one has their own SPN. An authenticated SPN
@ -243,6 +264,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen) azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen)
azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.VaultClientDelete.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClientDelete.UserAgent) azureClient.VaultClientDelete.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClientDelete.UserAgent)
azureClient.VaultClientDelete.Client.PollingDuration = PollingDuration
// If this is a managed disk build, this should be ignored. // If this is a managed disk build, this should be ignored.
if resourceGroupName != "" && storageAccountName != "" { if resourceGroupName != "" && storageAccountName != "" {

View File

@ -79,8 +79,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
b.config.ClientConfig.SubscriptionID, b.config.ClientConfig.SubscriptionID,
b.config.ResourceGroupName, b.config.ResourceGroupName,
b.config.StorageAccount, b.config.StorageAccount,
b.config.ClientConfig.CloudEnvironment, b.config.ClientConfig.CloudEnvironment(),
b.config.SharedGalleryTimeout, b.config.SharedGalleryTimeout,
b.config.PollingDurationTimeout,
spnCloud, spnCloud,
spnKeyVault) spnKeyVault)

View File

@ -1,4 +1,5 @@
//go:generate struct-markdown //go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
package arm package arm
@ -12,6 +13,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"net"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -311,6 +313,14 @@ type Config struct {
// 4. PlanPromotionCode // 4. PlanPromotionCode
// //
PlanInfo PlanInformation `mapstructure:"plan_info" required:"false"` PlanInfo PlanInformation `mapstructure:"plan_info" required:"false"`
// The default PollingDuration for azure is 15mins, this property will override
// that value. See [Azure DefaultPollingDuration](https://godoc.org/github.com/Azure/go-autorest/autorest#pkg-constants)
// If your Packer build is failing on the
// ARM deployment step with the error `Original Error:
// context deadline exceeded`, then you probably need to increase this timeout from
// its default of "15m" (valid time units include `s` for seconds, `m` for
// minutes, and `h` for hours.)
PollingDurationTimeout time.Duration `mapstructure:"polling_duration_timeout" required:"false"`
// If either Linux or Windows is specified Packer will // If either Linux or Windows is specified Packer will
// automatically configure authentication credentials for the provisioned // automatically configure authentication credentials for the provisioned
// machine. For Linux this configures an SSH authorized key. For Windows // machine. For Linux this configures an SSH authorized key. For Windows
@ -342,6 +352,13 @@ type Config struct {
// are None, ReadOnly, and ReadWrite. The default value is ReadWrite. // are None, ReadOnly, and ReadWrite. The default value is ReadWrite.
DiskCachingType string `mapstructure:"disk_caching_type" required:"false"` DiskCachingType string `mapstructure:"disk_caching_type" required:"false"`
diskCachingType compute.CachingTypes diskCachingType compute.CachingTypes
// Specify the list of IP addresses and CIDR blocks that should be
// allowed access to the VM. If provided, an Azure Network Security
// Group will be created with corresponding rules and be bound to
// the subnet of the VM.
// Providing `allowed_inbound_ip_addresses` in combination with
// `virtual_network_name` is not allowed.
AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"`
// Runtime Values // Runtime Values
UserName string UserName string
@ -357,6 +374,7 @@ type Config struct {
tmpOSDiskName string tmpOSDiskName string
tmpSubnetName string tmpSubnetName string
tmpVirtualNetworkName string tmpVirtualNetworkName string
tmpNsgName string
tmpWinRMCertificateUrl string tmpWinRMCertificateUrl string
// Authentication with the VM via SSH // Authentication with the VM via SSH
@ -604,6 +622,7 @@ func setRuntimeValues(c *Config) {
c.tmpOSDiskName = tempName.OSDiskName c.tmpOSDiskName = tempName.OSDiskName
c.tmpSubnetName = tempName.SubnetName c.tmpSubnetName = tempName.SubnetName
c.tmpVirtualNetworkName = tempName.VirtualNetworkName c.tmpVirtualNetworkName = tempName.VirtualNetworkName
c.tmpNsgName = tempName.NsgName
c.tmpKeyVaultName = tempName.KeyVaultName c.tmpKeyVaultName = tempName.KeyVaultName
} }
@ -872,6 +891,16 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name")) errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name"))
} }
if c.AllowedInboundIpAddresses != nil && len(c.AllowedInboundIpAddresses) >= 1 {
if c.VirtualNetworkName != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_name is specified, allowed_inbound_ip_addresses cannot be specified"))
} else {
if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok {
errs = packer.MultiErrorAppend(errs, err)
}
}
}
///////////////////////////////////////////// /////////////////////////////////////////////
// Plan Info // Plan Info
if c.PlanInfo.PlanName != "" || c.PlanInfo.PlanProduct != "" || c.PlanInfo.PlanPublisher != "" || c.PlanInfo.PlanPromotionCode != "" { if c.PlanInfo.PlanName != "" || c.PlanInfo.PlanProduct != "" || c.PlanInfo.PlanPublisher != "" || c.PlanInfo.PlanPromotionCode != "" {
@ -889,6 +918,13 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
} }
} }
/////////////////////////////////////////////
// Polling Duration Timeout
if c.PollingDurationTimeout == 0 {
// In the sdk, the default is 15 m.
c.PollingDurationTimeout = 15 * time.Minute
}
///////////////////////////////////////////// /////////////////////////////////////////////
// OS // OS
if strings.EqualFold(c.OSType, constants.Target_Linux) { if strings.EqualFold(c.OSType, constants.Target_Linux) {
@ -943,6 +979,17 @@ func assertManagedImageDataDiskSnapshotName(name, setting string) (bool, error)
return true, nil return true, nil
} }
func assertAllowedInboundIpAddresses(ipAddresses []string, setting string) (bool, error) {
for _, ipAddress := range ipAddresses {
if net.ParseIP(ipAddress) == nil {
if _, _, err := net.ParseCIDR(ipAddress); err != nil {
return false, fmt.Errorf("The setting %s must only contain valid IP addresses or CIDR blocks", setting)
}
}
}
return true, nil
}
func assertResourceGroupName(rgn, setting string) (bool, error) { func assertResourceGroupName(rgn, setting string) (bool, error) {
if !isValidAzureName(reResourceGroupName, rgn) { if !isValidAzureName(reResourceGroupName, rgn) {
return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe) return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe)

View File

@ -0,0 +1,299 @@
// Code generated by "mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation"; DO NOT EDIT.
package arm
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
CloudEnvironmentName *string `mapstructure:"cloud_environment_name" required:"false" cty:"cloud_environment_name"`
ClientID *string `mapstructure:"client_id" cty:"client_id"`
ClientSecret *string `mapstructure:"client_secret" cty:"client_secret"`
ClientCertPath *string `mapstructure:"client_cert_path" cty:"client_cert_path"`
ClientJWT *string `mapstructure:"client_jwt" cty:"client_jwt"`
ObjectID *string `mapstructure:"object_id" cty:"object_id"`
TenantID *string `mapstructure:"tenant_id" required:"false" cty:"tenant_id"`
SubscriptionID *string `mapstructure:"subscription_id" cty:"subscription_id"`
CaptureNamePrefix *string `mapstructure:"capture_name_prefix" cty:"capture_name_prefix"`
CaptureContainerName *string `mapstructure:"capture_container_name" cty:"capture_container_name"`
SharedGallery *FlatSharedImageGallery `mapstructure:"shared_image_gallery" required:"false" cty:"shared_image_gallery"`
SharedGalleryDestination *FlatSharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination" cty:"shared_image_gallery_destination"`
SharedGalleryTimeout *string `mapstructure:"shared_image_gallery_timeout" cty:"shared_image_gallery_timeout"`
ImagePublisher *string `mapstructure:"image_publisher" required:"true" cty:"image_publisher"`
ImageOffer *string `mapstructure:"image_offer" required:"true" cty:"image_offer"`
ImageSku *string `mapstructure:"image_sku" required:"true" cty:"image_sku"`
ImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"`
ImageUrl *string `mapstructure:"image_url" required:"false" cty:"image_url"`
CustomManagedImageResourceGroupName *string `mapstructure:"custom_managed_image_resource_group_name" required:"false" cty:"custom_managed_image_resource_group_name"`
CustomManagedImageName *string `mapstructure:"custom_managed_image_name" required:"false" cty:"custom_managed_image_name"`
Location *string `mapstructure:"location" cty:"location"`
VMSize *string `mapstructure:"vm_size" required:"false" cty:"vm_size"`
ManagedImageResourceGroupName *string `mapstructure:"managed_image_resource_group_name" cty:"managed_image_resource_group_name"`
ManagedImageName *string `mapstructure:"managed_image_name" cty:"managed_image_name"`
ManagedImageStorageAccountType *string `mapstructure:"managed_image_storage_account_type" required:"false" cty:"managed_image_storage_account_type"`
ManagedImageOSDiskSnapshotName *string `mapstructure:"managed_image_os_disk_snapshot_name" required:"false" cty:"managed_image_os_disk_snapshot_name"`
ManagedImageDataDiskSnapshotPrefix *string `mapstructure:"managed_image_data_disk_snapshot_prefix" required:"false" cty:"managed_image_data_disk_snapshot_prefix"`
ManagedImageZoneResilient *bool `mapstructure:"managed_image_zone_resilient" required:"false" cty:"managed_image_zone_resilient"`
AzureTags map[string]*string `mapstructure:"azure_tags" required:"false" cty:"azure_tags"`
ResourceGroupName *string `mapstructure:"resource_group_name" cty:"resource_group_name"`
StorageAccount *string `mapstructure:"storage_account" cty:"storage_account"`
TempComputeName *string `mapstructure:"temp_compute_name" required:"false" cty:"temp_compute_name"`
TempResourceGroupName *string `mapstructure:"temp_resource_group_name" cty:"temp_resource_group_name"`
BuildResourceGroupName *string `mapstructure:"build_resource_group_name" cty:"build_resource_group_name"`
PrivateVirtualNetworkWithPublicIp *bool `mapstructure:"private_virtual_network_with_public_ip" required:"false" cty:"private_virtual_network_with_public_ip"`
VirtualNetworkName *string `mapstructure:"virtual_network_name" required:"false" cty:"virtual_network_name"`
VirtualNetworkSubnetName *string `mapstructure:"virtual_network_subnet_name" required:"false" cty:"virtual_network_subnet_name"`
VirtualNetworkResourceGroupName *string `mapstructure:"virtual_network_resource_group_name" required:"false" cty:"virtual_network_resource_group_name"`
CustomDataFile *string `mapstructure:"custom_data_file" required:"false" cty:"custom_data_file"`
PlanInfo *FlatPlanInformation `mapstructure:"plan_info" required:"false" cty:"plan_info"`
PollingDurationTimeout *string `mapstructure:"polling_duration_timeout" required:"false" cty:"polling_duration_timeout"`
OSType *string `mapstructure:"os_type" required:"false" cty:"os_type"`
OSDiskSizeGB *int32 `mapstructure:"os_disk_size_gb" required:"false" cty:"os_disk_size_gb"`
AdditionalDiskSize []int32 `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size"`
DiskCachingType *string `mapstructure:"disk_caching_type" required:"false" cty:"disk_caching_type"`
UserName *string `cty:"user_name"`
Password *string `cty:"password"`
Type *string `mapstructure:"communicator" cty:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
SSHPublicKey []byte `cty:"ssh_public_key"`
SSHPrivateKey []byte `cty:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
AsyncResourceGroupDelete *bool `mapstructure:"async_resourcegroup_delete" required:"false" cty:"async_resourcegroup_delete"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{} { return new(FlatConfig) }
// HCL2Spec returns the hcldec.Spec of a FlatConfig.
// This spec is used by HCL to read the fields of FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.BlockAttrsSpec{TypeName: "packer_user_variables", ElementType: cty.String, Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"cloud_environment_name": &hcldec.AttrSpec{Name: "cloud_environment_name", Type: cty.String, Required: false},
"client_id": &hcldec.AttrSpec{Name: "client_id", Type: cty.String, Required: false},
"client_secret": &hcldec.AttrSpec{Name: "client_secret", Type: cty.String, Required: false},
"client_cert_path": &hcldec.AttrSpec{Name: "client_cert_path", Type: cty.String, Required: false},
"client_jwt": &hcldec.AttrSpec{Name: "client_jwt", Type: cty.String, Required: false},
"object_id": &hcldec.AttrSpec{Name: "object_id", Type: cty.String, Required: false},
"tenant_id": &hcldec.AttrSpec{Name: "tenant_id", Type: cty.String, Required: false},
"subscription_id": &hcldec.AttrSpec{Name: "subscription_id", Type: cty.String, Required: false},
"capture_name_prefix": &hcldec.AttrSpec{Name: "capture_name_prefix", Type: cty.String, Required: false},
"capture_container_name": &hcldec.AttrSpec{Name: "capture_container_name", Type: cty.String, Required: false},
"shared_image_gallery": &hcldec.BlockSpec{TypeName: "shared_image_gallery", Nested: hcldec.ObjectSpec((*FlatSharedImageGallery)(nil).HCL2Spec())},
"shared_image_gallery_destination": &hcldec.BlockSpec{TypeName: "shared_image_gallery_destination", Nested: hcldec.ObjectSpec((*FlatSharedImageGalleryDestination)(nil).HCL2Spec())},
"shared_image_gallery_timeout": &hcldec.AttrSpec{Name: "shared_image_gallery_timeout", Type: cty.String, Required: false},
"image_publisher": &hcldec.AttrSpec{Name: "image_publisher", Type: cty.String, Required: false},
"image_offer": &hcldec.AttrSpec{Name: "image_offer", Type: cty.String, Required: false},
"image_sku": &hcldec.AttrSpec{Name: "image_sku", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"image_url": &hcldec.AttrSpec{Name: "image_url", Type: cty.String, Required: false},
"custom_managed_image_resource_group_name": &hcldec.AttrSpec{Name: "custom_managed_image_resource_group_name", Type: cty.String, Required: false},
"custom_managed_image_name": &hcldec.AttrSpec{Name: "custom_managed_image_name", Type: cty.String, Required: false},
"location": &hcldec.AttrSpec{Name: "location", Type: cty.String, Required: false},
"vm_size": &hcldec.AttrSpec{Name: "vm_size", Type: cty.String, Required: false},
"managed_image_resource_group_name": &hcldec.AttrSpec{Name: "managed_image_resource_group_name", Type: cty.String, Required: false},
"managed_image_name": &hcldec.AttrSpec{Name: "managed_image_name", Type: cty.String, Required: false},
"managed_image_storage_account_type": &hcldec.AttrSpec{Name: "managed_image_storage_account_type", Type: cty.String, Required: false},
"managed_image_os_disk_snapshot_name": &hcldec.AttrSpec{Name: "managed_image_os_disk_snapshot_name", Type: cty.String, Required: false},
"managed_image_data_disk_snapshot_prefix": &hcldec.AttrSpec{Name: "managed_image_data_disk_snapshot_prefix", Type: cty.String, Required: false},
"managed_image_zone_resilient": &hcldec.AttrSpec{Name: "managed_image_zone_resilient", Type: cty.Bool, Required: false},
"azure_tags": &hcldec.BlockAttrsSpec{TypeName: "azure_tags", ElementType: cty.String, Required: false},
"resource_group_name": &hcldec.AttrSpec{Name: "resource_group_name", Type: cty.String, Required: false},
"storage_account": &hcldec.AttrSpec{Name: "storage_account", Type: cty.String, Required: false},
"temp_compute_name": &hcldec.AttrSpec{Name: "temp_compute_name", Type: cty.String, Required: false},
"temp_resource_group_name": &hcldec.AttrSpec{Name: "temp_resource_group_name", Type: cty.String, Required: false},
"build_resource_group_name": &hcldec.AttrSpec{Name: "build_resource_group_name", Type: cty.String, Required: false},
"private_virtual_network_with_public_ip": &hcldec.AttrSpec{Name: "private_virtual_network_with_public_ip", Type: cty.Bool, Required: false},
"virtual_network_name": &hcldec.AttrSpec{Name: "virtual_network_name", Type: cty.String, Required: false},
"virtual_network_subnet_name": &hcldec.AttrSpec{Name: "virtual_network_subnet_name", Type: cty.String, Required: false},
"virtual_network_resource_group_name": &hcldec.AttrSpec{Name: "virtual_network_resource_group_name", Type: cty.String, Required: false},
"custom_data_file": &hcldec.AttrSpec{Name: "custom_data_file", Type: cty.String, Required: false},
"plan_info": &hcldec.BlockSpec{TypeName: "plan_info", Nested: hcldec.ObjectSpec((*FlatPlanInformation)(nil).HCL2Spec())},
"polling_duration_timeout": &hcldec.AttrSpec{Name: "polling_duration_timeout", Type: cty.String, Required: false},
"os_type": &hcldec.AttrSpec{Name: "os_type", Type: cty.String, Required: false},
"os_disk_size_gb": &hcldec.AttrSpec{Name: "os_disk_size_gb", Type: cty.Number, Required: false},
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
"disk_caching_type": &hcldec.AttrSpec{Name: "disk_caching_type", Type: cty.String, Required: false},
"user_name": &hcldec.AttrSpec{Name: "user_name", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"async_resourcegroup_delete": &hcldec.AttrSpec{Name: "async_resourcegroup_delete", Type: cty.Bool, Required: false},
}
return s
}
// FlatPlanInformation is an auto-generated flat version of PlanInformation.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatPlanInformation struct {
PlanName *string `mapstructure:"plan_name" cty:"plan_name"`
PlanProduct *string `mapstructure:"plan_product" cty:"plan_product"`
PlanPublisher *string `mapstructure:"plan_publisher" cty:"plan_publisher"`
PlanPromotionCode *string `mapstructure:"plan_promotion_code" cty:"plan_promotion_code"`
}
// FlatMapstructure returns a new FlatPlanInformation.
// FlatPlanInformation is an auto-generated flat version of PlanInformation.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*PlanInformation) FlatMapstructure() interface{} { return new(FlatPlanInformation) }
// HCL2Spec returns the hcldec.Spec of a FlatPlanInformation.
// This spec is used by HCL to read the fields of FlatPlanInformation.
func (*FlatPlanInformation) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"plan_name": &hcldec.AttrSpec{Name: "plan_name", Type: cty.String, Required: false},
"plan_product": &hcldec.AttrSpec{Name: "plan_product", Type: cty.String, Required: false},
"plan_publisher": &hcldec.AttrSpec{Name: "plan_publisher", Type: cty.String, Required: false},
"plan_promotion_code": &hcldec.AttrSpec{Name: "plan_promotion_code", Type: cty.String, Required: false},
}
return s
}
// FlatSharedImageGallery is an auto-generated flat version of SharedImageGallery.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSharedImageGallery struct {
Subscription *string `mapstructure:"subscription" cty:"subscription"`
ResourceGroup *string `mapstructure:"resource_group" cty:"resource_group"`
GalleryName *string `mapstructure:"gallery_name" cty:"gallery_name"`
ImageName *string `mapstructure:"image_name" cty:"image_name"`
ImageVersion *string `mapstructure:"image_version" required:"false" cty:"image_version"`
}
// FlatMapstructure returns a new FlatSharedImageGallery.
// FlatSharedImageGallery is an auto-generated flat version of SharedImageGallery.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*SharedImageGallery) FlatMapstructure() interface{} { return new(FlatSharedImageGallery) }
// HCL2Spec returns the hcldec.Spec of a FlatSharedImageGallery.
// This spec is used by HCL to read the fields of FlatSharedImageGallery.
func (*FlatSharedImageGallery) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"subscription": &hcldec.AttrSpec{Name: "subscription", Type: cty.String, Required: false},
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
}
return s
}
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatSharedImageGalleryDestination struct {
SigDestinationResourceGroup *string `mapstructure:"resource_group" cty:"resource_group"`
SigDestinationGalleryName *string `mapstructure:"gallery_name" cty:"gallery_name"`
SigDestinationImageName *string `mapstructure:"image_name" cty:"image_name"`
SigDestinationImageVersion *string `mapstructure:"image_version" cty:"image_version"`
SigDestinationReplicationRegions []string `mapstructure:"replication_regions" cty:"replication_regions"`
}
// FlatMapstructure returns a new FlatSharedImageGalleryDestination.
// FlatSharedImageGalleryDestination is an auto-generated flat version of SharedImageGalleryDestination.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*SharedImageGalleryDestination) FlatMapstructure() interface{} {
return new(FlatSharedImageGalleryDestination)
}
// HCL2Spec returns the hcldec.Spec of a FlatSharedImageGalleryDestination.
// This spec is used by HCL to read the fields of FlatSharedImageGalleryDestination.
func (*FlatSharedImageGalleryDestination) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"resource_group": &hcldec.AttrSpec{Name: "resource_group", Type: cty.String, Required: false},
"gallery_name": &hcldec.AttrSpec{Name: "gallery_name", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"image_version": &hcldec.AttrSpec{Name: "image_version", Type: cty.String, Required: false},
"replication_regions": &hcldec.AttrSpec{Name: "replication_regions", Type: cty.List(cty.String), Required: false},
}
return s
}

View File

@ -270,6 +270,132 @@ func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testin
} }
} }
func TestConfigAllowedInboundIpAddressesIsOptional(t *testing.T) {
config := map[string]string{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"virtual_network_name": "MyVirtualNetwork",
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if c.AllowedInboundIpAddresses != nil {
t.Errorf("Expected Config to set allowed_inbound_ip_addresses to nil, but got %v", c.AllowedInboundIpAddresses)
}
}
func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) {
ipValue0 := "127.0.0.1"
ipValue1 := "127.0.0.2"
cidrValue2 := "192.168.100.0/24"
cidrValue3 := "10.10.1.16/32"
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
config["allowed_inbound_ip_addresses"] = ipValue0
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 1 ||
c.AllowedInboundIpAddresses[0] != ipValue0 {
t.Errorf("Expected 'allowed_inbound_ip_addresses' to have one element (%s), but got '%v'.", ipValue0, c.AllowedInboundIpAddresses)
}
config["allowed_inbound_ip_addresses"] = cidrValue2
c, _, err = newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 1 ||
c.AllowedInboundIpAddresses[0] != cidrValue2 {
t.Errorf("Expected 'allowed_inbound_ip_addresses' to have one element (%s), but got '%v'.", cidrValue2, c.AllowedInboundIpAddresses)
}
config["allowed_inbound_ip_addresses"] = []string{ipValue0, cidrValue2, ipValue1, cidrValue3}
c, _, err = newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 4 ||
c.AllowedInboundIpAddresses[0] != ipValue0 || c.AllowedInboundIpAddresses[1] != cidrValue2 ||
c.AllowedInboundIpAddresses[2] != ipValue1 || c.AllowedInboundIpAddresses[3] != cidrValue3 {
t.Errorf("Expected 'allowed_inbound_ip_addresses' to have four elements (%s %s %s %s), but got '%v'.", ipValue0, cidrValue2, ipValue1,
cidrValue3, c.AllowedInboundIpAddresses)
}
}
func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
}
config["allowed_inbound_ip_addresses"] = []string{"127.0.0.1", "127.0.0.two"}
c, _, err := newConfig(config, getPackerConfiguration())
if err == nil {
t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses)
}
config["allowed_inbound_ip_addresses"] = []string{"192.168.100.1000/24", "10.10.1.16/32"}
c, _, err = newConfig(config, getPackerConfiguration())
if err == nil {
// 192.168.100.1000/24 is invalid
t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses)
}
}
func TestConfigShouldRejectInboundIpAddressesWithVirtualNetwork(t *testing.T) {
config := map[string]interface{}{
"capture_name_prefix": "ignore",
"capture_container_name": "ignore",
"location": "ignore",
"image_url": "ignore",
"storage_account": "ignore",
"resource_group_name": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Linux,
"communicator": "none",
"allowed_inbound_ip_addresses": "127.0.0.1",
}
_, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
config["virtual_network_name"] = "some_vnet_name"
_, _, err = newConfig(config, getPackerConfiguration())
if err == nil {
t.Errorf("Expected configuration creation to fail, but it succeeded with allowed_inbound_ip_addresses and virtual_network_name both specified")
}
}
func TestConfigShouldDefaultToPublicCloud(t *testing.T) { func TestConfigShouldDefaultToPublicCloud(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
@ -277,8 +403,8 @@ func TestConfigShouldDefaultToPublicCloud(t *testing.T) {
t.Errorf("Expected 'CloudEnvironmentName' to default to 'Public', but got '%s'.", c.ClientConfig.CloudEnvironmentName) t.Errorf("Expected 'CloudEnvironmentName' to default to 'Public', but got '%s'.", c.ClientConfig.CloudEnvironmentName)
} }
if c.ClientConfig.CloudEnvironment == nil || c.ClientConfig.CloudEnvironment.Name != "AzurePublicCloud" { if c.ClientConfig.CloudEnvironment() == nil || c.ClientConfig.CloudEnvironment().Name != "AzurePublicCloud" {
t.Errorf("Expected 'cloudEnvironment' to be set to 'AzurePublicCloud', but got '%s'.", c.ClientConfig.CloudEnvironment) t.Errorf("Expected 'cloudEnvironment' to be set to 'AzurePublicCloud', but got '%s'.", c.ClientConfig.CloudEnvironment())
} }
} }
@ -327,8 +453,8 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if c.ClientConfig.CloudEnvironment == nil || c.ClientConfig.CloudEnvironment.Name != x.environmentName { if c.ClientConfig.CloudEnvironment() == nil || c.ClientConfig.CloudEnvironment().Name != x.environmentName {
t.Errorf("Expected 'cloudEnvironment' to be set to '%s', but got '%s'.", x.environmentName, c.ClientConfig.CloudEnvironment) t.Errorf("Expected 'cloudEnvironment' to be set to '%s', but got '%s'.", x.environmentName, c.ClientConfig.CloudEnvironment())
} }
} }
} }
@ -380,6 +506,10 @@ func TestSystemShouldDefineRuntimeValues(t *testing.T) {
if c.tmpOSDiskName == "" { if c.tmpOSDiskName == "" {
t.Errorf("Expected tmpOSDiskName to not be empty, but it was '%s'!", c.tmpOSDiskName) t.Errorf("Expected tmpOSDiskName to not be empty, but it was '%s'!", c.tmpOSDiskName)
} }
if c.tmpNsgName == "" {
t.Errorf("Expected tmpNsgName to not be empty, but it was '%s'!", c.tmpNsgName)
}
} }
func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) { func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) {

View File

@ -95,7 +95,7 @@ func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context,
resourceType, resourceType,
resourceName)) resourceName))
err := retry.Config{ retry.Config{
Tries: 10, Tries: 10,
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear, RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error { }.Run(ctx, func(ctx context.Context) error {
@ -106,7 +106,7 @@ func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context,
if err != nil { if err != nil {
s.reportIfError(err, resourceName) s.reportIfError(err, resourceName)
} }
return err return nil
}) })
if err = deploymentOperations.Next(); err != nil { if err = deploymentOperations.Next(); err != nil {

View File

@ -115,6 +115,12 @@ func deleteResource(ctx context.Context, client *AzureClient, resourceType strin
err = f.WaitForCompletionRef(ctx, client.VirtualNetworksClient.Client) err = f.WaitForCompletionRef(ctx, client.VirtualNetworksClient.Client)
} }
return err return err
case "Microsoft.Network/networkSecurityGroups":
f, err := client.SecurityGroupsClient.Delete(ctx, resourceGroupName, resourceName)
if err == nil {
err = f.WaitForCompletionRef(ctx, client.SecurityGroupsClient.Client)
}
return err
case "Microsoft.Network/publicIPAddresses": case "Microsoft.Network/publicIPAddresses":
f, err := client.PublicIPAddressesClient.Delete(ctx, resourceGroupName, resourceName) f, err := client.PublicIPAddressesClient.Delete(ctx, resourceGroupName, resourceName)
if err == nil { if err == nil {

View File

@ -40,6 +40,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
SubnetName: &template.TemplateParameter{Value: config.tmpSubnetName}, SubnetName: &template.TemplateParameter{Value: config.tmpSubnetName},
StorageAccountBlobEndpoint: &template.TemplateParameter{Value: config.storageAccountBlobEndpoint}, StorageAccountBlobEndpoint: &template.TemplateParameter{Value: config.storageAccountBlobEndpoint},
VirtualNetworkName: &template.TemplateParameter{Value: config.tmpVirtualNetworkName}, VirtualNetworkName: &template.TemplateParameter{Value: config.tmpVirtualNetworkName},
NsgName: &template.TemplateParameter{Value: config.tmpNsgName},
VMSize: &template.TemplateParameter{Value: config.VMSize}, VMSize: &template.TemplateParameter{Value: config.VMSize},
VMName: &template.TemplateParameter{Value: config.tmpComputeName}, VMName: &template.TemplateParameter{Value: config.tmpComputeName},
} }
@ -117,6 +118,13 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
config.VirtualNetworkSubnetName) config.VirtualNetworkSubnetName)
} }
if config.AllowedInboundIpAddresses != nil && len(config.AllowedInboundIpAddresses) >= 1 && config.Comm.Port() != 0 {
err = builder.SetNetworkSecurityGroup(config.AllowedInboundIpAddresses, config.Comm.Port())
if err != nil {
return nil, err
}
}
builder.SetTags(&config.AzureTags) builder.SetTags(&config.AzureTags)
doc, _ := builder.ToJSON() doc, _ := builder.ToJSON()
return createDeploymentParameters(*doc, params) return createDeploymentParameters(*doc, params)

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -189,6 +192,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -194,6 +197,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -160,6 +163,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -158,6 +161,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -119,6 +122,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -178,6 +181,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -159,6 +162,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -158,6 +161,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -161,6 +164,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -139,6 +142,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -172,6 +175,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -173,6 +176,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -0,0 +1,227 @@
{
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json",
"contentVersion": "1.0.0.0",
"parameters": {
"adminPassword": {
"type": "string"
},
"adminUsername": {
"type": "string"
},
"dnsNameForPublicIP": {
"type": "string"
},
"nicName": {
"type": "string"
},
"nsgName": {
"type": "string"
},
"osDiskName": {
"type": "string"
},
"publicIPAddressName": {
"type": "string"
},
"storageAccountBlobEndpoint": {
"type": "string"
},
"subnetName": {
"type": "string"
},
"virtualNetworkName": {
"type": "string"
},
"vmName": {
"type": "string"
},
"vmSize": {
"type": "string"
}
},
"resources": [
{
"apiVersion": "[variables('publicIPAddressApiVersion')]",
"location": "[variables('location')]",
"name": "[parameters('publicIPAddressName')]",
"properties": {
"dnsSettings": {
"domainNameLabel": "[parameters('dnsNameForPublicIP')]"
},
"publicIPAllocationMethod": "[variables('publicIPAddressType')]"
},
"type": "Microsoft.Network/publicIPAddresses"
},
{
"apiVersion": "[variables('networkInterfacesApiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]",
"[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('nicName')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]"
},
"subnet": {
"id": "[variables('subnetRef')]"
}
}
}
]
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "[variables('apiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]"
],
"location": "[variables('location')]",
"name": "[parameters('vmName')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": false
}
},
"hardwareProfile": {
"vmSize": "[parameters('vmSize')]"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"
}
]
},
"osProfile": {
"adminPassword": "[parameters('adminPassword')]",
"adminUsername": "[parameters('adminUsername')]",
"computerName": "[parameters('vmName')]",
"secrets": [
{
"sourceVault": {
"id": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults', '--keyvault-name--')]"
},
"vaultCertificates": [
{
"certificateStore": "My",
"certificateUrl": ""
}
]
}
],
"windowsConfiguration": {
"provisionVMAgent": true,
"winRM": {
"listeners": [
{
"certificateUrl": "",
"protocol": "https"
}
]
}
}
},
"storageProfile": {
"imageReference": {
"offer": "--image-offer--",
"publisher": "--image-publisher--",
"sku": "--image-sku--",
"version": "--version--"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"managedDisk": {
"storageAccountType": "Standard_LRS"
},
"name": "[parameters('osDiskName')]",
"osType": "Windows"
}
}
},
"type": "Microsoft.Compute/virtualMachines"
},
{
"apiVersion": "[variables('networkSecurityGroupsApiVersion')]",
"location": "[variables('location')]",
"name": "[parameters('nsgName')]",
"properties": {
"securityRules": [
{
"name": "AllowIPsToSshWinRMInbound",
"properties": {
"access": "Allow",
"description": "Allow inbound traffic from specified IP addresses",
"destinationAddressPrefix": "VirtualNetwork",
"destinationPortRange": "5985",
"direction": "Inbound",
"priority": 100,
"protocol": "Tcp",
"sourceAddressPrefixes": [
"127.0.0.1",
"192.168.100.0/24"
],
"sourcePortRange": "*"
}
}
]
},
"type": "Microsoft.Network/networkSecurityGroups"
},
{
"apiVersion": "[variables('virtualNetworksApiVersion')]",
"dependsOn": [
"[concat('Microsoft.Network/networkSecurityGroups/', parameters('nsgName'))]"
],
"location": "[variables('location')]",
"name": "[variables('virtualNetworkName')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('addressPrefix')]"
]
},
"subnets": [
{
"name": "[variables('subnetName')]",
"properties": {
"addressPrefix": "[variables('subnetAddressPrefix')]",
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsgName'))]"
}
}
}
]
},
"type": "Microsoft.Network/virtualNetworks"
}
],
"variables": {
"addressPrefix": "10.0.0.0/16",
"apiVersion": "2017-03-30",
"location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
"subnetAddressPrefix": "10.0.0.0/24",
"subnetName": "[parameters('subnetName')]",
"subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]",
"virtualNetworkName": "[parameters('virtualNetworkName')]",
"virtualNetworkResourceGroup": "[resourceGroup().name]",
"virtualNetworksApiVersion": "2017-04-01",
"vmStorageAccountContainerName": "images",
"vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]"
}
}

View File

@ -425,6 +425,40 @@ func TestVirtualMachineDeployment12(t *testing.T) {
} }
} }
// Ensure the VM template is correct when building with list of allowed IP addresses
func TestVirtualMachineDeployment13(t *testing.T) {
config := map[string]interface{}{
"location": "ignore",
"subscription_id": "ignore",
"os_type": constants.Target_Windows,
"communicator": "winrm",
"winrm_username": "ignore",
"image_publisher": "--image-publisher--",
"image_offer": "--image-offer--",
"image_sku": "--image-sku--",
"image_version": "--version--",
"managed_image_name": "ManagedImageName",
"managed_image_resource_group_name": "ManagedImageResourceGroupName",
"allowed_inbound_ip_addresses": []string{"127.0.0.1", "192.168.100.0/24"},
}
c, _, err := newConfig(config, getPackerConfiguration())
if err != nil {
t.Fatal(err)
}
c.tmpKeyVaultName = "--keyvault-name--"
deployment, err := GetVirtualMachineDeployment(c)
if err != nil {
t.Fatal(err)
}
err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template)
if err != nil {
t.Fatal(err)
}
}
// Ensure the link values are not set, and the concrete values are set. // Ensure the link values are not set, and the concrete values are set.
func TestKeyVaultDeployment00(t *testing.T) { func TestKeyVaultDeployment00(t *testing.T) {
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())

View File

@ -19,6 +19,7 @@ type TempName struct {
SubnetName string SubnetName string
PublicIPAddressName string PublicIPAddressName string
VirtualNetworkName string VirtualNetworkName string
NsgName string
} }
func NewTempName() *TempName { func NewTempName() *TempName {
@ -33,6 +34,7 @@ func NewTempName() *TempName {
tempName.PublicIPAddressName = fmt.Sprintf("pkrip%s", suffix) tempName.PublicIPAddressName = fmt.Sprintf("pkrip%s", suffix)
tempName.SubnetName = fmt.Sprintf("pkrsn%s", suffix) tempName.SubnetName = fmt.Sprintf("pkrsn%s", suffix)
tempName.VirtualNetworkName = fmt.Sprintf("pkrvn%s", suffix) tempName.VirtualNetworkName = fmt.Sprintf("pkrvn%s", suffix)
tempName.NsgName = fmt.Sprintf("pkrsg%s", suffix)
tempName.ResourceGroupName = fmt.Sprintf("packer-Resource-Group-%s", suffix) tempName.ResourceGroupName = fmt.Sprintf("packer-Resource-Group-%s", suffix)
tempName.AdminPassword = generatePassword() tempName.AdminPassword = generatePassword()

View File

@ -41,6 +41,10 @@ func TestTempNameShouldCreatePrefixedRandomNames(t *testing.T) {
if strings.Index(tempName.VirtualNetworkName, "pkrvn") != 0 { if strings.Index(tempName.VirtualNetworkName, "pkrvn") != 0 {
t.Errorf("Expected VirtualNetworkName to begin with 'pkrvn', but got '%s'!", tempName.VirtualNetworkName) t.Errorf("Expected VirtualNetworkName to begin with 'pkrvn', but got '%s'!", tempName.VirtualNetworkName)
} }
if strings.Index(tempName.NsgName, "pkrsg") != 0 {
t.Errorf("Expected NsgName to begin with 'pkrsg', but got '%s'!", tempName.NsgName)
}
} }
func TestTempAdminPassword(t *testing.T) { func TestTempAdminPassword(t *testing.T) {
@ -92,4 +96,8 @@ func TestTempNameShouldHaveSameSuffix(t *testing.T) {
if strings.HasSuffix(tempName.VirtualNetworkName, suffix) != true { if strings.HasSuffix(tempName.VirtualNetworkName, suffix) != true {
t.Errorf("Expected VirtualNetworkName to end with '%s', but the value is '%s'!", suffix, tempName.VirtualNetworkName) t.Errorf("Expected VirtualNetworkName to end with '%s', but the value is '%s'!", suffix, tempName.VirtualNetworkName)
} }
if strings.HasSuffix(tempName.NsgName, suffix) != true {
t.Errorf("Expected NsgName to end with '%s', but the value is '%s'!", suffix, tempName.NsgName)
}
} }

View File

@ -0,0 +1,452 @@
//go:generate struct-markdown
// Package chroot is able to create an Azure managed image without requiring the
// launch of a new virtual machine for every build. It does this by attaching and
// mounting the root disk and chrooting into that directory.
// It then creates a managed image from that attached disk.
package chroot
import (
"context"
"errors"
"fmt"
"log"
"runtime"
"strings"
"github.com/hashicorp/packer/builder/amazon/chroot"
azcommon "github.com/hashicorp/packer/builder/azure/common"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
)
// BuilderId is the unique ID for this builder
const BuilderId = "azure.chroot"
// Config is the configuration that is chained through the steps and settable
// from the template.
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ClientConfig client.Config `mapstructure:",squash"`
// When set to `true`, starts with an empty, unpartitioned disk. Defaults to `false`.
FromScratch bool `mapstructure:"from_scratch"`
// Either a managed disk resource ID or a publisher:offer:sku:version specifier for plaform image sources.
Source string `mapstructure:"source" required:"true"`
sourceType sourceType
// How to run shell commands. This may be useful to set environment variables or perhaps run
// a command with sudo or so on. This is a configuration template where the `.Command` variable
// is replaced with the command to be run. Defaults to `{{.Command}}`.
CommandWrapper string `mapstructure:"command_wrapper"`
// A series of commands to execute after attaching the root volume and before mounting the chroot.
// This is not required unless using `from_scratch`. If so, this should include any partitioning
// and filesystem creation commands. The path to the device is provided by `{{.Device}}`.
PreMountCommands []string `mapstructure:"pre_mount_commands"`
// Options to supply the `mount` command when mounting devices. Each option will be prefixed with
// `-o` and supplied to the `mount` command ran by Packer. Because this command is ran in a shell,
// user discretion is advised. See this manual page for the `mount` command for valid file system specific options.
MountOptions []string `mapstructure:"mount_options"`
// The partition number containing the / partition. By default this is the first partition of the volume.
MountPartition string `mapstructure:"mount_partition"`
// The path where the volume will be mounted. This is where the chroot environment will be. This defaults
// to `/mnt/packer-amazon-chroot-volumes/{{.Device}}`. This is a configuration template where the `.Device`
// variable is replaced with the name of the device where the volume is attached.
MountPath string `mapstructure:"mount_path"`
// As `pre_mount_commands`, but the commands are executed after mounting the root device and before the
// extra mount and copy steps. The device and mount path are provided by `{{.Device}}` and `{{.MountPath}}`.
PostMountCommands []string `mapstructure:"post_mount_commands"`
// This is a list of devices to mount into the chroot environment. This configuration parameter requires
// some additional documentation which is in the "Chroot Mounts" section below. Please read that section
// for more information on how to use this.
ChrootMounts [][]string `mapstructure:"chroot_mounts"`
// Paths to files on the running Azure instance that will be copied into the chroot environment prior to
// provisioning. Defaults to `/etc/resolv.conf` so that DNS lookups work. Pass an empty list to skip copying
// `/etc/resolv.conf`. You may need to do this if you're building an image that uses systemd.
CopyFiles []string `mapstructure:"copy_files"`
// The name of the temporary disk that will be created in the resource group of the VM that Packer is
// running on. Will be generated if not set.
TemporaryOSDiskName string `mapstructure:"temporary_os_disk_name"`
// Try to resize the OS disk to this size on the first copy. Disks can only be englarged. If not specified,
// the disk will keep its original size. Required when using `from_scratch`
OSDiskSizeGB int32 `mapstructure:"os_disk_size_gb"`
// The [storage SKU](https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#diskstorageaccounttypes)
// to use for the OS Disk. Defaults to `Standard_LRS`.
OSDiskStorageAccountType string `mapstructure:"os_disk_storage_account_type"`
// The [cache type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#cachingtypes)
// specified in the resulting image and for attaching it to the Packer VM. Defaults to `ReadOnly`
OSDiskCacheType string `mapstructure:"os_disk_cache_type"`
// If set to `true`, leaves the temporary disk behind in the Packer VM resource group. Defaults to `false`
OSDiskSkipCleanup bool `mapstructure:"os_disk_skip_cleanup"`
// The image to create using this build.
ImageResourceID string `mapstructure:"image_resource_id" required:"true"`
// The [Hyper-V generation type](https://docs.microsoft.com/en-us/rest/api/compute/images/createorupdate#hypervgenerationtypes).
// Defaults to `V1`.
ImageHyperVGeneration string `mapstructure:"image_hyperv_generation"`
ctx interpolate.Context
}
type sourceType string
const (
sourcePlatformImage sourceType = "PlatformImage"
sourceDisk sourceType = "Disk"
)
// GetContext implements ContextProvider to allow steps to use the config context
// for template interpolation
func (c *Config) GetContext() interpolate.Context {
return c.ctx
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b.config.ctx.Funcs = azcommon.TemplateFuncs
b.config.ctx.Funcs["vm"] = CreateVMMetadataTemplateFunc()
err := config.Decode(&b.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
// these fields are interpolated in the steps,
// when more information is available
"command_wrapper",
"post_mount_commands",
"pre_mount_commands",
"mount_path",
},
},
}, raws...)
if err != nil {
return nil, err
}
var errs *packer.MultiError
var warns []string
// Defaults
err = b.config.ClientConfig.SetDefaultValues()
if err != nil {
return nil, err
}
if b.config.ChrootMounts == nil {
b.config.ChrootMounts = make([][]string, 0)
}
if len(b.config.ChrootMounts) == 0 {
b.config.ChrootMounts = [][]string{
{"proc", "proc", "/proc"},
{"sysfs", "sysfs", "/sys"},
{"bind", "/dev", "/dev"},
{"devpts", "devpts", "/dev/pts"},
{"binfmt_misc", "binfmt_misc", "/proc/sys/fs/binfmt_misc"},
}
}
// set default copy file if we're not giving our own
if b.config.CopyFiles == nil {
if !b.config.FromScratch {
b.config.CopyFiles = []string{"/etc/resolv.conf"}
}
}
if b.config.CommandWrapper == "" {
b.config.CommandWrapper = "{{.Command}}"
}
if b.config.MountPath == "" {
b.config.MountPath = "/mnt/packer-azure-chroot-disks/{{.Device}}"
}
if b.config.MountPartition == "" {
b.config.MountPartition = "1"
}
if b.config.TemporaryOSDiskName == "" {
if def, err := interpolate.Render("PackerTemp-{{timestamp}}", &b.config.ctx); err == nil {
b.config.TemporaryOSDiskName = def
} else {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("unable to render temporary disk name: %s", err))
}
}
if b.config.OSDiskStorageAccountType == "" {
b.config.OSDiskStorageAccountType = string(compute.PremiumLRS)
}
if b.config.OSDiskCacheType == "" {
b.config.OSDiskCacheType = string(compute.CachingTypesReadOnly)
}
if b.config.ImageHyperVGeneration == "" {
b.config.ImageHyperVGeneration = string(compute.V1)
}
// checks, accumulate any errors or warnings
if b.config.FromScratch {
if b.config.Source != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("source cannot be specified when building from_scratch"))
}
if b.config.OSDiskSizeGB == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("os_disk_size_gb is required with from_scratch"))
}
if len(b.config.PreMountCommands) == 0 {
errs = packer.MultiErrorAppend(
errs, errors.New("pre_mount_commands is required with from_scratch"))
}
} else {
if _, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
log.Println("Source is platform image:", b.config.Source)
b.config.sourceType = sourcePlatformImage
} else if id, err := azure.ParseResourceID(b.config.Source); err == nil &&
strings.EqualFold(id.Provider, "Microsoft.Compute") && strings.EqualFold(id.ResourceType, "disks") {
log.Println("Source is a disk resource ID:", b.config.Source)
b.config.sourceType = sourceDisk
} else {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("source: %q is not a valid platform image specifier, nor is it a disk resource ID", b.config.Source))
}
}
if err := checkDiskCacheType(b.config.OSDiskCacheType); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("os_disk_cache_type: %v", err))
}
if err := checkStorageAccountType(b.config.OSDiskStorageAccountType); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("os_disk_storage_account_type: %v", err))
}
if b.config.ImageResourceID == "" {
errs = packer.MultiErrorAppend(errs, errors.New("image_resource_id is required"))
} else {
r, err := azure.ParseResourceID(b.config.ImageResourceID)
if err != nil ||
!strings.EqualFold(r.Provider, "Microsoft.Compute") ||
!strings.EqualFold(r.ResourceType, "images") {
errs = packer.MultiErrorAppend(fmt.Errorf(
"image_resource_id: %q is not a valid image resource id", b.config.ImageResourceID))
}
}
if err := checkHyperVGeneration(b.config.ImageHyperVGeneration); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("image_hyperv_generation: %v", err))
}
if errs != nil {
return warns, errs
}
packer.LogSecretFilter.Set(b.config.ClientConfig.ClientSecret, b.config.ClientConfig.ClientJWT)
return warns, nil
}
func checkDiskCacheType(s string) interface{} {
for _, v := range compute.PossibleCachingTypesValues() {
if compute.CachingTypes(s) == v {
return nil
}
}
return fmt.Errorf("%q is not a valid value %v",
s, compute.PossibleCachingTypesValues())
}
func checkStorageAccountType(s string) interface{} {
for _, v := range compute.PossibleDiskStorageAccountTypesValues() {
if compute.DiskStorageAccountTypes(s) == v {
return nil
}
}
return fmt.Errorf("%q is not a valid value %v",
s, compute.PossibleDiskStorageAccountTypesValues())
}
func checkHyperVGeneration(s string) interface{} {
for _, v := range compute.PossibleHyperVGenerationValues() {
if compute.HyperVGeneration(s) == v {
return nil
}
}
return fmt.Errorf("%q is not a valid value %v",
s, compute.PossibleHyperVGenerationValues())
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
if runtime.GOOS != "linux" {
return nil, errors.New("the azure-chroot builder only works on Linux environments")
}
err := b.config.ClientConfig.FillParameters()
if err != nil {
return nil, fmt.Errorf("error setting Azure client defaults: %v", err)
}
azcli, err := client.New(b.config.ClientConfig, ui.Say)
if err != nil {
return nil, fmt.Errorf("error creating Azure client: %v", err)
}
wrappedCommand := func(command string) (string, error) {
ictx := b.config.ctx
ictx.Data = &struct{ Command string }{Command: command}
return interpolate.Render(b.config.CommandWrapper, &ictx)
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("azureclient", azcli)
state.Put("wrappedCommand", chroot.CommandWrapper(wrappedCommand))
info, err := azcli.MetadataClient().GetComputeInfo()
if err != nil {
log.Printf("MetadataClient().GetComputeInfo(): error: %+v", err)
err := fmt.Errorf(
"Error retrieving information ARM resource ID and location" +
"of the VM that Packer is running on.\n" +
"Please verify that Packer is running on a proper Azure VM.")
ui.Error(err.Error())
return nil, err
}
state.Put("instance", info)
// Build the steps
var steps []multistep.Step
if b.config.FromScratch {
steps = append(steps,
&StepCreateNewDisk{
SubscriptionID: info.SubscriptionID,
ResourceGroup: info.ResourceGroupName,
DiskName: b.config.TemporaryOSDiskName,
DiskSizeGB: b.config.OSDiskSizeGB,
DiskStorageAccountType: b.config.OSDiskStorageAccountType,
HyperVGeneration: b.config.ImageHyperVGeneration,
Location: info.Location,
})
} else {
switch b.config.sourceType {
case sourcePlatformImage:
if pi, err := client.ParsePlatformImageURN(b.config.Source); err == nil {
if strings.EqualFold(pi.Version, "latest") {
vmi, err := azcli.VirtualMachineImagesClient().GetLatest(ctx, pi.Publisher, pi.Offer, pi.Sku, info.Location)
if err != nil {
return nil, fmt.Errorf("error retieving latest version of %q: %v", b.config.Source, err)
}
pi.Version = to.String(vmi.Name)
log.Println("Resolved latest version of source image:", pi.Version)
}
steps = append(steps,
&StepCreateNewDisk{
SubscriptionID: info.SubscriptionID,
ResourceGroup: info.ResourceGroupName,
DiskName: b.config.TemporaryOSDiskName,
DiskSizeGB: b.config.OSDiskSizeGB,
DiskStorageAccountType: b.config.OSDiskStorageAccountType,
HyperVGeneration: b.config.ImageHyperVGeneration,
Location: info.Location,
PlatformImage: pi,
SkipCleanup: b.config.OSDiskSkipCleanup,
})
} else {
panic("Unknown image source: " + b.config.Source)
}
case sourceDisk:
steps = append(steps,
&StepVerifySourceDisk{
SourceDiskResourceID: b.config.Source,
SubscriptionID: info.SubscriptionID,
Location: info.Location,
},
&StepCreateNewDisk{
SubscriptionID: info.SubscriptionID,
ResourceGroup: info.ResourceGroupName,
DiskName: b.config.TemporaryOSDiskName,
DiskSizeGB: b.config.OSDiskSizeGB,
DiskStorageAccountType: b.config.OSDiskStorageAccountType,
HyperVGeneration: b.config.ImageHyperVGeneration,
SourceDiskResourceID: b.config.Source,
Location: info.Location,
SkipCleanup: b.config.OSDiskSkipCleanup,
})
default:
panic(fmt.Errorf("Unknown source type: %+q", b.config.sourceType))
}
}
steps = append(steps,
&StepAttachDisk{}, // uses os_disk_resource_id and sets 'device' in stateBag
&chroot.StepPreMountCommands{
Commands: b.config.PreMountCommands,
},
&StepMountDevice{
MountOptions: b.config.MountOptions,
MountPartition: b.config.MountPartition,
MountPath: b.config.MountPath,
},
&chroot.StepPostMountCommands{
Commands: b.config.PostMountCommands,
},
&chroot.StepMountExtra{
ChrootMounts: b.config.ChrootMounts,
},
&chroot.StepCopyFiles{
Files: b.config.CopyFiles,
},
&chroot.StepChrootProvision{},
&chroot.StepEarlyCleanup{},
&StepCreateImage{
ImageResourceID: b.config.ImageResourceID,
ImageOSState: string(compute.Generalized),
OSDiskCacheType: b.config.OSDiskCacheType,
OSDiskStorageAccountType: b.config.OSDiskStorageAccountType,
Location: info.Location,
},
)
// Run!
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// Build the artifact and return it
artifact := &azcommon.Artifact{
Resources: []string{b.config.ImageResourceID},
BuilderIdValue: BuilderId,
}
return artifact, nil
}
var _ packer.Builder = &Builder{}

View File

@ -0,0 +1,70 @@
package chroot
import (
"testing"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
)
func TestBuilder_Prepare(t *testing.T) {
type config map[string]interface{}
type regexMatchers map[string]string // map of regex : error message
tests := []struct {
name string
config config
validate func(Config)
wantErr bool
}{
{
name: "HappyPathFromPlatformImage",
config: config{
"client_id": "123",
"client_secret": "456",
"subscription_id": "789",
"image_resource_id": "/subscriptions/789/resourceGroups/otherrgname/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}",
"source": "credativ:Debian:9:latest",
},
validate: func(c Config) {
if c.OSDiskSizeGB != 0 {
t.Errorf("Expected OSDiskSizeGB to be 0, was %+v", c.OSDiskSizeGB)
}
if c.MountPartition != "1" {
t.Errorf("Expected MountPartition to be %s, but found %s", "1", c.MountPartition)
}
if c.OSDiskStorageAccountType != string(compute.PremiumLRS) {
t.Errorf("Expected OSDiskStorageAccountType to be %s, but found %s", string(compute.PremiumLRS), c.OSDiskStorageAccountType)
}
if c.OSDiskCacheType != string(compute.CachingTypesReadOnly) {
t.Errorf("Expected OSDiskCacheType to be %s, but found %s", string(compute.CachingTypesReadOnly), c.OSDiskCacheType)
}
if c.ImageHyperVGeneration != string(compute.V1) {
t.Errorf("Expected ImageHyperVGeneration to be %s, but found %s", string(compute.V1), c.ImageHyperVGeneration)
}
},
},
{
name: "HappyPathFromPlatformImage",
config: config{
"image_resource_id": "/subscriptions/789/resourceGroups/otherrgname/providers/Microsoft.Compute/images/MyDebianOSImage-{{timestamp}}",
"source": "/subscriptions/789/resourceGroups/testrg/providers/Microsoft.Compute/disks/diskname",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &Builder{}
_, err := b.Prepare(tt.config)
if (err != nil) != tt.wantErr {
t.Errorf("Builder.Prepare() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.validate != nil {
tt.validate(b.config)
}
})
}
}

View File

@ -0,0 +1,222 @@
package chroot
import (
"context"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
)
type DiskAttacher interface {
AttachDisk(ctx context.Context, disk string) (lun int32, err error)
DiskPathForLun(lun int32) string
WaitForDevice(ctx context.Context, i int32) (device string, err error)
DetachDisk(ctx context.Context, disk string) (err error)
WaitForDetach(ctx context.Context, diskID string) error
}
var NewDiskAttacher = func(azureClient client.AzureClientSet) DiskAttacher {
return &diskAttacher{
azcli: azureClient,
}
}
type diskAttacher struct {
azcli client.AzureClientSet
vm *client.ComputeInfo // store info about this VM so that we don't have to ask metadata service on every call
}
func (diskAttacher) DiskPathForLun(lun int32) string {
return fmt.Sprintf("/dev/disk/azure/scsi1/lun%d", lun)
}
func (da diskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
path := da.DiskPathForLun(lun)
for {
link, err := os.Readlink(path)
if err == nil {
return filepath.Abs("/dev/disk/azure/scsi1/" + link)
} else if err != os.ErrNotExist {
if pe, ok := err.(*os.PathError); ok && pe.Err != syscall.ENOENT {
return "", err
}
}
select {
case <-time.After(100 * time.Millisecond):
// continue
case <-ctx.Done():
return "", ctx.Err()
}
}
}
func (da *diskAttacher) DetachDisk(ctx context.Context, diskID string) error {
log.Println("Fetching list of disks currently attached to VM")
currentDisks, err := da.getDisks(ctx)
if err != nil {
return err
}
log.Printf("Removing %q from list of disks currently attached to VM", diskID)
newDisks := []compute.DataDisk{}
for _, disk := range currentDisks {
if disk.ManagedDisk != nil &&
!strings.EqualFold(to.String(disk.ManagedDisk.ID), diskID) {
newDisks = append(newDisks, disk)
}
}
if len(currentDisks) == len(newDisks) {
return DiskNotFoundError
}
log.Println("Updating new list of disks attached to VM")
err = da.setDisks(ctx, newDisks)
if err != nil {
return err
}
return nil
}
func (da *diskAttacher) WaitForDetach(ctx context.Context, diskID string) error {
for { // loop until disk is not attached, timeout or error
list, err := da.getDisks(ctx)
if err != nil {
return err
}
if findDiskInList(list, diskID) == nil {
log.Println("Disk is no longer in VM model, assuming detached")
return nil
}
select {
case <-time.After(time.Second): //continue
case <-ctx.Done():
return ctx.Err()
}
}
}
var DiskNotFoundError = errors.New("Disk not found")
func (da *diskAttacher) AttachDisk(ctx context.Context, diskID string) (int32, error) {
dataDisks, err := da.getDisks(ctx)
if err != nil {
return -1, err
}
// check to see if disk is already attached, remember lun if found
if disk := findDiskInList(dataDisks, diskID); disk != nil {
// disk is already attached, just take this lun
if disk.Lun == nil {
return -1, errors.New("disk is attached, but lun was not set in VM model (possibly an error in the Azure APIs)")
}
return to.Int32(disk.Lun), nil
}
// disk was not found on VM, go and actually attach it
var lun int32 = -1
findFreeLun:
for lun = 0; lun < 64; lun++ {
for _, v := range dataDisks {
if to.Int32(v.Lun) == lun {
continue findFreeLun
}
}
// no datadisk is using this lun
break
}
// append new data disk to collection
dataDisks = append(dataDisks, compute.DataDisk{
CreateOption: compute.DiskCreateOptionTypesAttach,
ManagedDisk: &compute.ManagedDiskParameters{
ID: to.StringPtr(diskID),
},
Lun: to.Int32Ptr(lun),
})
// prepare resource object for update operation
err = da.setDisks(ctx, dataDisks)
if err != nil {
return -1, err
}
return lun, nil
}
func (da *diskAttacher) getThisVM(ctx context.Context) (compute.VirtualMachine, error) {
// getting resource info for this VM
if da.vm == nil {
vm, err := da.azcli.MetadataClient().GetComputeInfo()
if err != nil {
return compute.VirtualMachine{}, err
}
da.vm = vm
}
// retrieve actual VM
vmResource, err := da.azcli.VirtualMachinesClient().Get(ctx, da.vm.ResourceGroupName, da.vm.Name, "")
if err != nil {
return compute.VirtualMachine{}, err
}
if vmResource.StorageProfile == nil {
return compute.VirtualMachine{}, errors.New("properties.storageProfile is not set on VM, this is unexpected")
}
return vmResource, nil
}
func (da diskAttacher) getDisks(ctx context.Context) ([]compute.DataDisk, error) {
vmResource, err := da.getThisVM(ctx)
if err != nil {
return []compute.DataDisk{}, err
}
return *vmResource.StorageProfile.DataDisks, nil
}
func (da diskAttacher) setDisks(ctx context.Context, disks []compute.DataDisk) error {
vmResource, err := da.getThisVM(ctx)
if err != nil {
return err
}
id, err := azure.ParseResourceID(to.String(vmResource.ID))
if err != nil {
return err
}
vmResource.StorageProfile.DataDisks = &disks
vmResource.Resources = nil
// update the VM resource, attach disk
_, err = da.azcli.VirtualMachinesClient().CreateOrUpdate(ctx, id.ResourceGroup, id.ResourceName, vmResource)
return err
}
func findDiskInList(list []compute.DataDisk, diskID string) *compute.DataDisk {
for _, disk := range list {
if disk.ManagedDisk != nil &&
strings.EqualFold(to.String(disk.ManagedDisk.ID), diskID) {
return &disk
}
}
return nil
}

View File

@ -0,0 +1,86 @@
package chroot
import (
"context"
"github.com/Azure/go-autorest/autorest/to"
"testing"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
testvm = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/Microsoft.Compute/virtualMachines/testVM"
testdisk = "/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/testGroup2/Microsoft.Compute/disks/testDisk"
)
// Tests assume current machine is capable of running chroot builder (i.e. an Azure VM)
func Test_DiskAttacherAttachesDiskToVM(t *testing.T) {
azcli, err := client.GetTestClientSet(t) // integration test
require.Nil(t, err)
da := NewDiskAttacher(azcli)
testDiskName := t.Name()
vm, err := azcli.MetadataClient().GetComputeInfo()
require.Nil(t, err, "Test needs to run on an Azure VM, unable to retrieve VM information")
t.Log("Creating new disk '", testDiskName, "' in ", vm.ResourceGroupName)
disk, err := azcli.DisksClient().Get(context.TODO(), vm.ResourceGroupName, testDiskName)
if err == nil {
t.Log("Disk already exists")
if disk.DiskState == compute.Attached {
t.Log("Disk is attached, assuming to this machine, trying to detach")
err = da.DetachDisk(context.TODO(), to.String(disk.ID))
require.Nil(t, err)
}
t.Log("Deleting disk")
result, err := azcli.DisksClient().Delete(context.TODO(), vm.ResourceGroupName, testDiskName)
require.Nil(t, err)
err = result.WaitForCompletionRef(context.TODO(), azcli.PollClient())
require.Nil(t, err)
}
t.Log("Creating disk")
r, err := azcli.DisksClient().CreateOrUpdate(context.TODO(), vm.ResourceGroupName, testDiskName, compute.Disk{
Location: to.StringPtr(vm.Location),
Sku: &compute.DiskSku{
Name: compute.StandardLRS,
},
DiskProperties: &compute.DiskProperties{
DiskSizeGB: to.Int32Ptr(30),
CreationData: &compute.CreationData{CreateOption: compute.Empty},
},
})
require.Nil(t, err)
err = r.WaitForCompletionRef(context.TODO(), azcli.PollClient())
require.Nil(t, err)
t.Log("Retrieving disk properties")
d, err := azcli.DisksClient().Get(context.TODO(), vm.ResourceGroupName, testDiskName)
require.Nil(t, err)
assert.NotNil(t, d)
t.Log("Attaching disk")
lun, err := da.AttachDisk(context.TODO(), to.String(d.ID))
assert.Nil(t, err)
t.Log("Waiting for device")
dev, err := da.WaitForDevice(context.TODO(), lun)
assert.Nil(t, err)
t.Log("Device path:", dev)
t.Log("Detaching disk")
err = da.DetachDisk(context.TODO(), to.String(d.ID))
require.Nil(t, err)
t.Log("Deleting disk")
result, err := azcli.DisksClient().Delete(context.TODO(), vm.ResourceGroupName, testDiskName)
if err == nil {
err = result.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
require.Nil(t, err)
}

View File

@ -0,0 +1,83 @@
package chroot
import (
"context"
"fmt"
"log"
"time"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
var _ multistep.Step = &StepAttachDisk{}
type StepAttachDisk struct {
attached bool
}
func (s *StepAttachDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get("os_disk_resource_id").(string)
ui.Say(fmt.Sprintf("Attaching disk '%s'", diskResourceID))
da := NewDiskAttacher(azcli)
lun, err := da.AttachDisk(ctx, diskResourceID)
if err != nil {
log.Printf("StepAttachDisk.Run: error: %+v", err)
err := fmt.Errorf(
"error attaching disk '%s': %v", diskResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say("Disk attached, waiting for device to show up")
ctx, cancel := context.WithTimeout(ctx, time.Minute*3) // in case is not configured correctly
defer cancel()
device, err := da.WaitForDevice(ctx, lun)
if err != nil {
log.Printf("StepAttachDisk.Run: error: %+v", err)
err := fmt.Errorf(
"error attaching disk '%s': %v", diskResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Disk available at %q", device))
s.attached = true
state.Put("device", device)
state.Put("attach_cleanup", s)
return multistep.ActionContinue
}
func (s *StepAttachDisk) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepAttachDisk) CleanupFunc(state multistep.StateBag) error {
if s.attached {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get("os_disk_resource_id").(string)
ui.Say(fmt.Sprintf("Detaching disk '%s'", diskResourceID))
da := NewDiskAttacher(azcli)
err := da.DetachDisk(context.Background(), diskResourceID)
if err != nil {
return fmt.Errorf("error detaching %q: %v", diskResourceID, err)
}
s.attached = false
}
return nil
}

View File

@ -0,0 +1,131 @@
package chroot
import (
"context"
"errors"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func TestStepAttachDisk_Run(t *testing.T) {
type fields struct {
GetDiskResponseCode int
GetDiskResponseBody string
attachError error
waitForDeviceError error
}
tests := []struct {
name string
fields fields
want multistep.StepAction
}{
{
name: "HappyPath",
want: multistep.ActionContinue,
},
{
name: "AttachError",
fields: fields{
attachError: errors.New("unit test"),
},
want: multistep.ActionHalt,
},
{
name: "WaitForDeviceError",
fields: fields{
waitForDeviceError: errors.New("unit test"),
},
want: multistep.ActionHalt,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &StepAttachDisk{}
NewDiskAttacher = func(azcli client.AzureClientSet) DiskAttacher {
return &fakeDiskAttacher{
attachError: tt.fields.attachError,
waitForDeviceError: tt.fields.waitForDeviceError,
}
}
dm := compute.NewDisksClient("subscriptionId")
dm.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
return &http.Response{
Request: r,
Body: ioutil.NopCloser(strings.NewReader(tt.fields.GetDiskResponseBody)),
StatusCode: tt.fields.GetDiskResponseCode,
}, nil
})
errorBuffer := &strings.Builder{}
ui := &packer.BasicUi{
Reader: strings.NewReader(""),
Writer: ioutil.Discard,
ErrorWriter: errorBuffer,
}
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{})
state.Put("ui", ui)
state.Put("os_disk_resource_id", "/subscriptions/12345/resourceGroups/group1/providers/Microsoft.Compute/disks/disk1")
got := s.Run(context.TODO(), state)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepAttachDisk.Run() = %v, want %v", got, tt.want)
}
if got == multistep.ActionHalt {
if _, ok := state.GetOk("error"); !ok {
t.Fatal("Expected 'error' to be set in statebag after failure")
}
}
})
}
}
type fakeDiskAttacher struct {
attachError error
waitForDeviceError error
}
var _ DiskAttacher = &fakeDiskAttacher{}
func (da *fakeDiskAttacher) AttachDisk(ctx context.Context, disk string) (lun int32, err error) {
if da.attachError != nil {
return 0, da.attachError
}
return 3, nil
}
func (da *fakeDiskAttacher) DiskPathForLun(lun int32) string {
panic("not implemented")
}
func (da *fakeDiskAttacher) WaitForDevice(ctx context.Context, lun int32) (device string, err error) {
if da.waitForDeviceError != nil {
return "", da.waitForDeviceError
}
if lun == 3 {
return "/dev/sdq", nil
}
panic("expected lun==3")
}
func (da *fakeDiskAttacher) DetachDisk(ctx context.Context, disk string) (err error) {
panic("not implemented")
}
func (da *fakeDiskAttacher) WaitForDetach(ctx context.Context, diskID string) error {
panic("not implemented")
}

View File

@ -0,0 +1,89 @@
package chroot
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"log"
)
var _ multistep.Step = &StepCreateImage{}
type StepCreateImage struct {
ImageResourceID string
ImageOSState string
OSDiskStorageAccountType string
OSDiskCacheType string
Location string
imageResource azure.Resource
}
func (s *StepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get("os_disk_resource_id").(string)
ui.Say(fmt.Sprintf("Creating image %s\n using %s for os disk.",
s.ImageResourceID,
diskResourceID))
var err error
s.imageResource, err = azure.ParseResourceID(s.ImageResourceID)
if err != nil {
log.Printf("StepCreateImage.Run: error: %+v", err)
err := fmt.Errorf(
"error parsing image resource id '%s': %v", s.ImageResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
image := compute.Image{
Location: to.StringPtr(s.Location),
ImageProperties: &compute.ImageProperties{
StorageProfile: &compute.ImageStorageProfile{
OsDisk: &compute.ImageOSDisk{
OsState: compute.OperatingSystemStateTypes(s.ImageOSState),
OsType: compute.Linux,
ManagedDisk: &compute.SubResource{
ID: &diskResourceID,
},
StorageAccountType: compute.StorageAccountTypes(s.OSDiskStorageAccountType),
Caching: compute.CachingTypes(s.OSDiskCacheType),
},
// DataDisks: nil,
// ZoneResilient: nil,
},
},
// Tags: nil,
}
f, err := azcli.ImagesClient().CreateOrUpdate(
ctx,
s.imageResource.ResourceGroup,
s.imageResource.ResourceName,
image)
if err == nil {
log.Println("Image creation in process...")
err = f.WaitForCompletionRef(ctx, azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateImage.Run: error: %+v", err)
err := fmt.Errorf(
"error creating image '%s': %v", s.ImageResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Image creation complete: %s", f.Status())
return multistep.ActionContinue
}
func (*StepCreateImage) Cleanup(bag multistep.StateBag) {} // this is the final artifact, don't delete

View File

@ -0,0 +1,110 @@
package chroot
import (
"context"
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute"
"github.com/Azure/go-autorest/autorest/to"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
var _ multistep.Step = &StepCreateNewDisk{}
type StepCreateNewDisk struct {
SubscriptionID, ResourceGroup, DiskName string
DiskSizeGB int32 // optional, ignored if 0
DiskStorageAccountType string // from compute.DiskStorageAccountTypes
HyperVGeneration string
Location string
PlatformImage *client.PlatformImage
SourceDiskResourceID string
SkipCleanup bool
}
func (s StepCreateNewDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/disks/%s",
s.SubscriptionID,
s.ResourceGroup,
s.DiskName)
state.Put("os_disk_resource_id", diskResourceID)
ui.Say(fmt.Sprintf("Creating disk '%s'", diskResourceID))
disk := compute.Disk{
Location: to.StringPtr(s.Location),
Sku: &compute.DiskSku{
Name: compute.DiskStorageAccountTypes(s.DiskStorageAccountType),
},
//Zones: nil,
DiskProperties: &compute.DiskProperties{
OsType: "Linux",
HyperVGeneration: compute.HyperVGeneration(s.HyperVGeneration),
CreationData: &compute.CreationData{},
},
//Tags: map[string]*string{
}
if s.DiskSizeGB > 0 {
disk.DiskProperties.DiskSizeGB = to.Int32Ptr(s.DiskSizeGB)
}
if s.SourceDiskResourceID != "" {
disk.CreationData.CreateOption = compute.Copy
disk.CreationData.SourceResourceID = to.StringPtr(s.SourceDiskResourceID)
} else if s.PlatformImage == nil {
disk.CreationData.CreateOption = compute.Empty
} else {
disk.CreationData.CreateOption = compute.FromImage
disk.CreationData.ImageReference = &compute.ImageDiskReference{
ID: to.StringPtr(fmt.Sprintf(
"/subscriptions/%s/providers/Microsoft.Compute/locations/%s/publishers/%s/artifacttypes/vmimage/offers/%s/skus/%s/versions/%s",
s.SubscriptionID, s.Location, s.PlatformImage.Publisher, s.PlatformImage.Offer, s.PlatformImage.Sku, s.PlatformImage.Version)),
}
}
f, err := azcli.DisksClient().CreateOrUpdate(ctx, s.ResourceGroup, s.DiskName, disk)
if err == nil {
err = f.WaitForCompletionRef(ctx, azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateNewDisk.Run: error: %+v", err)
err := fmt.Errorf(
"error creating new disk '%s': %v", diskResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s StepCreateNewDisk) Cleanup(state multistep.StateBag) {
if !s.SkipCleanup {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
diskResourceID := state.Get("os_disk_resource_id").(string)
ui.Say(fmt.Sprintf("Waiting for disk %q detach to complete", diskResourceID))
err := NewDiskAttacher(azcli).WaitForDetach(context.Background(), diskResourceID)
ui.Say(fmt.Sprintf("Deleting disk %q", diskResourceID))
f, err := azcli.DisksClient().Delete(context.TODO(), s.ResourceGroup, s.DiskName)
if err == nil {
err = f.WaitForCompletionRef(context.TODO(), azcli.PollClient())
}
if err != nil {
log.Printf("StepCreateNewDisk.Cleanup: error: %+v", err)
ui.Error(fmt.Sprintf("error deleting new disk '%s': %v.", diskResourceID, err))
}
}
}

View File

@ -0,0 +1,147 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"testing"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func TestStepCreateNewDisk_Run(t *testing.T) {
type fields struct {
SubscriptionID string
ResourceGroup string
DiskName string
DiskSizeGB int32
DiskStorageAccountType string
HyperVGeneration string
Location string
PlatformImage *client.PlatformImage
SourceDiskResourceID string
expectedPutDiskBody string
}
tests := []struct {
name string
fields fields
want multistep.StepAction
}{
{
name: "HappyPathDiskSource",
fields: fields{
SubscriptionID: "SubscriptionID",
ResourceGroup: "ResourceGroupName",
DiskName: "TemporaryOSDiskName",
DiskSizeGB: 42,
DiskStorageAccountType: string(compute.PremiumLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
SourceDiskResourceID: "SourceDisk",
expectedPutDiskBody: `
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption": "Copy",
"sourceResourceId": "SourceDisk"
},
"diskSizeGB": 42
},
"sku": {
"name": "Premium_LRS"
}
}`,
},
want: multistep.ActionContinue,
},
{
name: "HappyPathDiskSource",
fields: fields{
SubscriptionID: "SubscriptionID",
ResourceGroup: "ResourceGroupName",
DiskName: "TemporaryOSDiskName",
DiskStorageAccountType: string(compute.StandardLRS),
HyperVGeneration: string(compute.V1),
Location: "westus",
PlatformImage: &client.PlatformImage{
Publisher: "Microsoft",
Offer: "Windows",
Sku: "2016-DataCenter",
Version: "2016.1.4",
},
expectedPutDiskBody: `
{
"location": "westus",
"properties": {
"osType": "Linux",
"hyperVGeneration": "V1",
"creationData": {
"createOption":"FromImage",
"imageReference": {
"id":"/subscriptions/SubscriptionID/providers/Microsoft.Compute/locations/westus/publishers/Microsoft/artifacttypes/vmimage/offers/Windows/skus/2016-DataCenter/versions/2016.1.4"
}
}
},
"sku": {
"name": "Standard_LRS"
}
}`,
},
want: multistep.ActionContinue,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StepCreateNewDisk{
SubscriptionID: tt.fields.SubscriptionID,
ResourceGroup: tt.fields.ResourceGroup,
DiskName: tt.fields.DiskName,
DiskSizeGB: tt.fields.DiskSizeGB,
DiskStorageAccountType: tt.fields.DiskStorageAccountType,
HyperVGeneration: tt.fields.HyperVGeneration,
Location: tt.fields.Location,
PlatformImage: tt.fields.PlatformImage,
SourceDiskResourceID: tt.fields.SourceDiskResourceID,
}
expectedPutDiskBody := regexp.MustCompile(`[\s\n]`).ReplaceAllString(tt.fields.expectedPutDiskBody, "")
m := compute.NewDisksClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
if r.Method != "PUT" {
t.Fatal("Expected only a PUT disk call")
}
b, _ := ioutil.ReadAll(r.Body)
if string(b) != expectedPutDiskBody {
t.Fatalf("expected body to be %q, but got %q", expectedPutDiskBody, string(b))
}
return &http.Response{
Request: r,
StatusCode: 200,
}, nil
})
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
DisksClientMock: m,
})
state.Put("ui", packer.TestUi(t))
if got := s.Run(context.TODO(), state); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepCreateNewDisk.Run() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,132 @@
package chroot
// mostly borrowed from ./builder/amazon/chroot/step_mount_device.go
import (
"bytes"
"context"
"fmt"
"github.com/hashicorp/packer/builder/amazon/chroot"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"log"
"os"
"path/filepath"
"strings"
)
var _ multistep.Step = &StepMountDevice{}
type StepMountDevice struct {
MountOptions []string
MountPartition string
MountPath string
mountPath string
}
func (s *StepMountDevice) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
device := state.Get("device").(string)
config := state.Get("config").(*Config)
wrappedCommand := state.Get("wrappedCommand").(chroot.CommandWrapper)
ictx := config.ctx
ictx.Data = &struct{ Device string }{Device: filepath.Base(device)}
mountPath, err := interpolate.Render(s.MountPath, &ictx)
if err != nil {
err := fmt.Errorf("error preparing mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
mountPath, err = filepath.Abs(mountPath)
if err != nil {
err := fmt.Errorf("error preparing mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("Mount path: %s", mountPath)
if err := os.MkdirAll(mountPath, 0755); err != nil {
err := fmt.Errorf("error creating mount directory: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
deviceMount := fmt.Sprintf("%s%s", device, s.MountPartition)
state.Put("deviceMount", deviceMount)
ui.Say("Mounting the root device...")
stderr := new(bytes.Buffer)
// build mount options from mount_options config, useful for nouuid options
// or other specific device type settings for mount
opts := ""
if len(s.MountOptions) > 0 {
opts = "-o " + strings.Join(s.MountOptions, " -o ")
}
mountCommand, err := wrappedCommand(
fmt.Sprintf("mount %s %s %s", opts, deviceMount, mountPath))
if err != nil {
err := fmt.Errorf("error creating mount command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
log.Printf("[DEBUG] (step mount) mount command is %s", mountCommand)
cmd := chroot.ShellCommand(mountCommand)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
err := fmt.Errorf(
"error mounting root volume: %s\nStderr: %s", err, stderr.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Set the mount path so we remember to unmount it later
s.mountPath = mountPath
state.Put("mount_path", s.mountPath)
state.Put("mount_device_cleanup", s)
return multistep.ActionContinue
}
func (s *StepMountDevice) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui)
if err := s.CleanupFunc(state); err != nil {
ui.Error(err.Error())
}
}
func (s *StepMountDevice) CleanupFunc(state multistep.StateBag) error {
if s.mountPath == "" {
return nil
}
ui := state.Get("ui").(packer.Ui)
wrappedCommand := state.Get("wrappedCommand").(chroot.CommandWrapper)
ui.Say("Unmounting the root device...")
unmountCommand, err := wrappedCommand(fmt.Sprintf("umount %s", s.mountPath))
if err != nil {
return fmt.Errorf("error creating unmount command: %s", err)
}
cmd := chroot.ShellCommand(unmountCommand)
if err := cmd.Run(); err != nil {
return fmt.Errorf("error unmounting root device: %s", err)
}
s.mountPath = ""
return nil
}

View File

@ -0,0 +1,81 @@
package chroot
import (
"context"
"fmt"
"log"
"strings"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
type StepVerifySourceDisk struct {
SubscriptionID string
SourceDiskResourceID string
Location string
}
func (s StepVerifySourceDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
azcli := state.Get("azureclient").(client.AzureClientSet)
ui := state.Get("ui").(packer.Ui)
ui.Say("Checking source disk location")
resource, err := azure.ParseResourceID(s.SourceDiskResourceID)
if err != nil {
log.Printf("StepVerifySourceDisk.Run: error: %+v", err)
err := fmt.Errorf("Could not parse resource id %q: %s", s.SourceDiskResourceID, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if !strings.EqualFold(resource.SubscriptionID, s.SubscriptionID) {
err := fmt.Errorf("Source disk resource %q is in a different subscription than this VM (%q). "+
"Packer does not know how to handle that.",
s.SourceDiskResourceID, s.SubscriptionID)
log.Printf("StepVerifySourceDisk.Run: error: %+v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if !(strings.EqualFold(resource.Provider, "Microsoft.Compute") && strings.EqualFold(resource.ResourceType, "disks")) {
err := fmt.Errorf("Resource ID %q is not a managed disk resource", s.SourceDiskResourceID)
log.Printf("StepVerifySourceDisk.Run: error: %+v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
disk, err := azcli.DisksClient().Get(ctx,
resource.ResourceGroup, resource.ResourceName)
if err != nil {
err := fmt.Errorf("Unable to retrieve disk (%q): %s", s.SourceDiskResourceID, err)
log.Printf("StepVerifySourceDisk.Run: error: %+v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
location := to.String(disk.Location)
if !strings.EqualFold(location, s.Location) {
err := fmt.Errorf("Source disk resource %q is in a different location (%q) than this VM (%q). "+
"Packer does not know how to handle that.",
s.SourceDiskResourceID,
location,
s.Location)
log.Printf("StepVerifySourceDisk.Run: error: %+v", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s StepVerifySourceDisk) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,168 @@
package chroot
import (
"context"
"io/ioutil"
"net/http"
"reflect"
"regexp"
"strings"
"testing"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func Test_StepVerifySourceDisk_Run(t *testing.T) {
type fields struct {
SubscriptionID string
SourceDiskResourceID string
Location string
GetDiskResponseCode int
GetDiskResponseBody string
}
type args struct {
state multistep.StateBag
}
tests := []struct {
name string
fields fields
args args
want multistep.StepAction
errormatch string
}{
{
name: "HappyPath",
fields: fields{
SubscriptionID: "subid1",
SourceDiskResourceID: "/subscriptions/subid1/resourcegroups/rg1/providers/Microsoft.Compute/disks/disk1",
Location: "westus2",
GetDiskResponseCode: 200,
GetDiskResponseBody: `{"location":"westus2"}`,
},
want: multistep.ActionContinue,
},
{
name: "NotAResourceID",
fields: fields{
SubscriptionID: "subid1",
SourceDiskResourceID: "/other",
Location: "westus2",
GetDiskResponseCode: 200,
GetDiskResponseBody: `{"location":"westus2"}`,
},
want: multistep.ActionHalt,
errormatch: "Could not parse resource id",
},
{
name: "DiskNotFound",
fields: fields{
SubscriptionID: "subid1",
SourceDiskResourceID: "/subscriptions/subid1/resourcegroups/rg1/providers/Microsoft.Compute/disks/disk1",
Location: "westus2",
GetDiskResponseCode: 404,
GetDiskResponseBody: `{}`,
},
want: multistep.ActionHalt,
errormatch: "Unable to retrieve",
},
{
name: "NotADisk",
fields: fields{
SubscriptionID: "subid1",
SourceDiskResourceID: "/subscriptions/subid1/resourcegroups/rg1/providers/Microsoft.Compute/images/image1",
Location: "westus2",
GetDiskResponseCode: 404,
},
want: multistep.ActionHalt,
errormatch: "not a managed disk",
},
{
name: "OtherSubscription",
fields: fields{
SubscriptionID: "subid1",
SourceDiskResourceID: "/subscriptions/subid2/resourcegroups/rg1/providers/Microsoft.Compute/disks/disk1",
Location: "westus2",
GetDiskResponseCode: 200,
GetDiskResponseBody: `{"location":"westus2"}`,
},
want: multistep.ActionHalt,
errormatch: "different subscription",
},
{
name: "OtherLocation",
fields: fields{
SubscriptionID: "subid1",
SourceDiskResourceID: "/subscriptions/subid1/resourcegroups/rg1/providers/Microsoft.Compute/disks/disk1",
Location: "eastus",
GetDiskResponseCode: 200,
GetDiskResponseBody: `{"location":"westus2"}`,
},
want: multistep.ActionHalt,
errormatch: "different location",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StepVerifySourceDisk{
SubscriptionID: tt.fields.SubscriptionID,
SourceDiskResourceID: tt.fields.SourceDiskResourceID,
Location: tt.fields.Location,
}
m := compute.NewDisksClient("subscriptionId")
m.Sender = autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
return &http.Response{
Request: r,
Body: ioutil.NopCloser(strings.NewReader(tt.fields.GetDiskResponseBody)),
StatusCode: tt.fields.GetDiskResponseCode,
}, nil
})
errorBuffer := &strings.Builder{}
ui := &packer.BasicUi{
Reader: strings.NewReader(""),
Writer: ioutil.Discard,
ErrorWriter: errorBuffer,
}
state := new(multistep.BasicStateBag)
state.Put("azureclient", &client.AzureClientSetMock{
DisksClientMock: m,
})
state.Put("ui", ui)
got := s.Run(context.TODO(), state)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("StepVerifySourceDisk.Run() = %v, want %v", got, tt.want)
}
if tt.errormatch != "" {
if !regexp.MustCompile(tt.errormatch).MatchString(errorBuffer.String()) {
t.Errorf("Expected the error output (%q) to match %q", errorBuffer.String(), tt.errormatch)
}
}
if got == multistep.ActionHalt {
if _, ok := state.GetOk("error"); !ok {
t.Fatal("Expected 'error' to be set in statebag after failure")
}
}
})
}
}
type uiThatRemebersErrors struct {
packer.Ui
LastError string
}

View File

@ -0,0 +1,37 @@
package chroot
import (
"fmt"
"sync"
"github.com/hashicorp/packer/builder/azure/common/client"
)
// CreateVMMetadataTemplateFunc returns a template function that retrieves VM metadata. VM metadata is retrieved only once and reused for all executions of the function.
func CreateVMMetadataTemplateFunc() func(string) (string, error) {
var data *client.ComputeInfo
var dataErr error
once := sync.Once{}
return func(key string) (string, error) {
once.Do(func() {
data, dataErr = client.DefaultMetadataClient.GetComputeInfo()
})
if dataErr != nil {
return "", dataErr
}
switch key {
case "name":
return data.Name, nil
case "subscription_id":
return data.SubscriptionID, nil
case "resource_group":
return data.ResourceGroupName, nil
case "location":
return data.Location, nil
case "resource_id":
return data.ResourceID(), nil
default:
return "", fmt.Errorf("unknown metadata key: %s (supported: name, subscription_id, resource_group, location, resource_id)", key)
}
}
}

View File

@ -0,0 +1,103 @@
package common
import (
"context"
"fmt"
"log"
"sort"
"strings"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/packer/builder/azure/common/client"
"github.com/hashicorp/packer/packer"
)
// Artifact is an artifact implementation that contains built Managed Images or Disks.
type Artifact struct {
// Array of the Azure resource IDs that were created.
Resources []string
// BuilderId is the unique ID for the builder that created this AMI
BuilderIdValue string
// Azure client for performing API stuff.
AzureClientSet client.AzureClientSet
}
func (a *Artifact) BuilderId() string {
return a.BuilderIdValue
}
func (*Artifact) Files() []string {
// We have no files
return nil
}
func (a *Artifact) Id() string {
parts := make([]string, 0, len(a.Resources))
for _, resource := range a.Resources {
parts = append(parts, strings.ToLower(resource))
}
sort.Strings(parts)
return strings.Join(parts, ",")
}
func (a *Artifact) String() string {
parts := make([]string, 0, len(a.Resources))
for _, resource := range a.Resources {
parts = append(parts, strings.ToLower(resource))
}
sort.Strings(parts)
return fmt.Sprintf("Azure resources created:\n%s\n", strings.Join(parts, "\n"))
}
func (a *Artifact) State(name string) interface{} {
switch name {
default:
return nil
}
}
func (a *Artifact) Destroy() error {
errs := make([]error, 0)
for _, resource := range a.Resources {
log.Printf("Deleting resource %s", resource)
id, err := azure.ParseResourceID(resource)
if err != nil {
return fmt.Errorf("Unable to parse resource id (%s): %v", resource, err)
}
ctx := context.TODO()
restype := strings.ToLower(fmt.Sprintf("%s/%s", id.Provider, id.ResourceType))
switch restype {
case "microsoft.compute/images":
res, err := a.AzureClientSet.ImagesClient().Delete(ctx, id.ResourceGroup, id.ResourceName)
if err != nil {
errs = append(errs, fmt.Errorf("Unable to initiate deletion of resource (%s): %v", resource, err))
} else {
err := res.WaitForCompletionRef(ctx, a.AzureClientSet.PollClient())
if err != nil {
errs = append(errs, fmt.Errorf("Unable to complete deletion of resource (%s): %v", resource, err))
}
}
default:
errs = append(errs, fmt.Errorf("Don't know how to delete resources of type %s (%s)", resource, restype))
}
}
if len(errs) > 0 {
if len(errs) == 1 {
return errs[0]
} else {
return &packer.MultiError{Errors: errs}
}
}
return nil
}

View File

@ -0,0 +1,90 @@
package client
import (
"net/http"
"regexp"
"time"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi"
"github.com/Azure/go-autorest/autorest"
)
type AzureClientSet interface {
MetadataClient() MetadataClientAPI
DisksClient() computeapi.DisksClientAPI
ImagesClient() computeapi.ImagesClientAPI
VirtualMachinesClient() computeapi.VirtualMachinesClientAPI
VirtualMachineImagesClient() VirtualMachineImagesClientAPI
PollClient() autorest.Client
}
var subscriptionPathRegex = regexp.MustCompile(`/subscriptions/([[:xdigit:]]{8}(-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12})`)
var _ AzureClientSet = &azureClientSet{}
type azureClientSet struct {
sender autorest.Sender
authorizer autorest.Authorizer
subscriptionID string
PollingDelay time.Duration
}
func New(c Config, say func(string)) (AzureClientSet, error) {
token, err := c.GetServicePrincipalToken(say, c.CloudEnvironment().ResourceManagerEndpoint)
if err != nil {
return nil, err
}
return &azureClientSet{
authorizer: autorest.NewBearerAuthorizer(token),
subscriptionID: c.SubscriptionID,
sender: http.DefaultClient,
PollingDelay: time.Second,
}, nil
}
func (s azureClientSet) configureAutorestClient(c *autorest.Client) {
c.Authorizer = s.authorizer
c.Sender = s.sender
}
func (s azureClientSet) MetadataClient() MetadataClientAPI {
return metadataClient{s.sender}
}
func (s azureClientSet) DisksClient() computeapi.DisksClientAPI {
c := compute.NewDisksClient(s.subscriptionID)
s.configureAutorestClient(&c.Client)
c.PollingDelay = s.PollingDelay
return c
}
func (s azureClientSet) ImagesClient() computeapi.ImagesClientAPI {
c := compute.NewImagesClient(s.subscriptionID)
s.configureAutorestClient(&c.Client)
c.PollingDelay = s.PollingDelay
return c
}
func (s azureClientSet) VirtualMachinesClient() computeapi.VirtualMachinesClientAPI {
c := compute.NewVirtualMachinesClient(s.subscriptionID)
s.configureAutorestClient(&c.Client)
c.PollingDelay = s.PollingDelay
return c
}
func (s azureClientSet) VirtualMachineImagesClient() VirtualMachineImagesClientAPI {
c := compute.NewVirtualMachineImagesClient(s.subscriptionID)
s.configureAutorestClient(&c.Client)
c.PollingDelay = s.PollingDelay
return virtualMachineImagesClientAPI{c}
}
func (s azureClientSet) PollClient() autorest.Client {
c := autorest.NewClientWithUserAgent("Packer-Azure-ClientSet")
s.configureAutorestClient(&c)
c.PollingDelay = time.Second / 3
return c
}

View File

@ -0,0 +1,46 @@
package client
import (
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi"
"github.com/Azure/go-autorest/autorest"
)
// AzureClientSetMock provides a generic mock for AzureClientSet
type AzureClientSetMock struct {
DisksClientMock computeapi.DisksClientAPI
ImagesClientMock computeapi.ImagesClientAPI
VirtualMachineImagesClientMock VirtualMachineImagesClientAPI
VirtualMachinesClientMock computeapi.VirtualMachinesClientAPI
PollClientMock autorest.Client
MetadataClientMock MetadataClientAPI
}
// DisksClient returns a DisksClientAPI
func (m *AzureClientSetMock) DisksClient() computeapi.DisksClientAPI {
return m.DisksClientMock
}
// ImagesClient returns a ImagesClientAPI
func (m *AzureClientSetMock) ImagesClient() computeapi.ImagesClientAPI {
return m.ImagesClientMock
}
// VirtualMachineImagesClient returns a VirtualMachineImagesClientAPI
func (m *AzureClientSetMock) VirtualMachineImagesClient() VirtualMachineImagesClientAPI {
return m.VirtualMachineImagesClientMock
}
// VirtualMachinesClient returns a VirtualMachinesClientAPI
func (m *AzureClientSetMock) VirtualMachinesClient() computeapi.VirtualMachinesClientAPI {
return m.VirtualMachinesClientMock
}
// PollClient returns an autorest Client that can be used for polling async requests
func (m *AzureClientSetMock) PollClient() autorest.Client {
return m.PollClientMock
}
// MetadataClient returns a MetadataClientAPI
func (m *AzureClientSetMock) MetadataClient() MetadataClientAPI {
return m.MetadataClientMock
}

View File

@ -4,7 +4,6 @@ package client
import ( import (
"fmt" "fmt"
"github.com/hashicorp/packer/builder/azure/common"
"os" "os"
"strings" "strings"
"time" "time"
@ -28,7 +27,7 @@ type Config struct {
// USGovernment. Defaults to Public. Long forms such as // USGovernment. Defaults to Public. Long forms such as
// USGovernmentCloud and AzureUSGovernmentCloud are also supported. // USGovernmentCloud and AzureUSGovernmentCloud are also supported.
CloudEnvironmentName string `mapstructure:"cloud_environment_name" required:"false"` CloudEnvironmentName string `mapstructure:"cloud_environment_name" required:"false"`
CloudEnvironment *azure.Environment cloudEnvironment *azure.Environment
// Authentication fields // Authentication fields
@ -73,6 +72,10 @@ func (c *Config) SetDefaultValues() error {
return c.setCloudEnvironment() return c.setCloudEnvironment()
} }
func (c *Config) CloudEnvironment() *azure.Environment {
return c.cloudEnvironment
}
func (c *Config) setCloudEnvironment() error { func (c *Config) setCloudEnvironment() error {
lookup := map[string]string{ lookup := map[string]string{
"CHINA": "AzureChinaCloud", "CHINA": "AzureChinaCloud",
@ -103,7 +106,7 @@ func (c *Config) setCloudEnvironment() error {
} }
env, err := azure.EnvironmentFromName(envName) env, err := azure.EnvironmentFromName(envName)
c.CloudEnvironment = &env c.cloudEnvironment = &env
return err return err
} }
@ -198,60 +201,64 @@ func (c Config) UseMSI() bool {
c.TenantID == "" c.TenantID == ""
} }
func (c Config) GetServicePrincipalTokens( func (c Config) GetServicePrincipalTokens(say func(string)) (
say func(string)) (
servicePrincipalToken *adal.ServicePrincipalToken, servicePrincipalToken *adal.ServicePrincipalToken,
servicePrincipalTokenVault *adal.ServicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken,
err error) { err error) {
tenantID := c.TenantID servicePrincipalToken, err = c.GetServicePrincipalToken(say,
c.CloudEnvironment().ResourceManagerEndpoint)
if err != nil {
return nil, nil, err
}
servicePrincipalTokenVault, err = c.GetServicePrincipalToken(say,
strings.TrimRight(c.CloudEnvironment().KeyVaultEndpoint, "/"))
if err != nil {
return nil, nil, err
}
return servicePrincipalToken, servicePrincipalTokenVault, nil
}
func (c Config) GetServicePrincipalToken(
say func(string), forResource string) (
servicePrincipalToken *adal.ServicePrincipalToken,
err error) {
var auth oAuthTokenProvider var auth oAuthTokenProvider
switch c.authType { switch c.authType {
case authTypeDeviceLogin: case authTypeDeviceLogin:
say("Getting tokens using device flow") say("Getting tokens using device flow")
auth = NewDeviceFlowOAuthTokenProvider(*c.CloudEnvironment, say, tenantID) auth = NewDeviceFlowOAuthTokenProvider(*c.cloudEnvironment, say, c.TenantID)
case authTypeMSI: case authTypeMSI:
say("Getting tokens using Managed Identity for Azure") say("Getting tokens using Managed Identity for Azure")
auth = NewMSIOAuthTokenProvider(*c.CloudEnvironment) auth = NewMSIOAuthTokenProvider(*c.cloudEnvironment)
case authTypeClientSecret: case authTypeClientSecret:
say("Getting tokens using client secret") say("Getting tokens using client secret")
auth = NewSecretOAuthTokenProvider(*c.CloudEnvironment, c.ClientID, c.ClientSecret, tenantID) auth = NewSecretOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientSecret, c.TenantID)
case authTypeClientCert: case authTypeClientCert:
say("Getting tokens using client certificate") say("Getting tokens using client certificate")
auth, err = NewCertOAuthTokenProvider(*c.CloudEnvironment, c.ClientID, c.ClientCertPath, tenantID) auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, c.TenantID)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
case authTypeClientBearerJWT: case authTypeClientBearerJWT:
say("Getting tokens using client bearer JWT") say("Getting tokens using client bearer JWT")
auth = NewJWTOAuthTokenProvider(*c.CloudEnvironment, c.ClientID, c.ClientJWT, tenantID) auth = NewJWTOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientJWT, c.TenantID)
default: default:
panic("authType not set, call FillParameters, or set explicitly") panic("authType not set, call FillParameters, or set explicitly")
} }
servicePrincipalToken, err = auth.getServicePrincipalToken() servicePrincipalToken, err = auth.getServicePrincipalTokenWithResource(forResource)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
err = servicePrincipalToken.EnsureFresh() err = servicePrincipalToken.EnsureFresh()
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource( return servicePrincipalToken, nil
strings.TrimRight(c.CloudEnvironment.KeyVaultEndpoint, "/"))
if err != nil {
return nil, nil, err
}
err = servicePrincipalTokenVault.EnsureFresh()
if err != nil {
return nil, nil, err
}
return servicePrincipalToken, servicePrincipalTokenVault, nil
} }
// FillParameters capture the user intent from the supplied parameter set in authType, retrieves the TenantID and CloudEnvironment if not specified. // FillParameters capture the user intent from the supplied parameter set in authType, retrieves the TenantID and CloudEnvironment if not specified.
@ -280,7 +287,7 @@ func (c *Config) FillParameters() error {
c.SubscriptionID = subscriptionID c.SubscriptionID = subscriptionID
} }
if c.CloudEnvironment == nil { if c.cloudEnvironment == nil {
err := c.setCloudEnvironment() err := c.setCloudEnvironment()
if err != nil { if err != nil {
return err return err
@ -288,7 +295,7 @@ func (c *Config) FillParameters() error {
} }
if c.TenantID == "" { if c.TenantID == "" {
tenantID, err := findTenantID(*c.CloudEnvironment, c.SubscriptionID) tenantID, err := findTenantID(*c.cloudEnvironment, c.SubscriptionID)
if err != nil { if err != nil {
return err return err
} }
@ -299,4 +306,4 @@ func (c *Config) FillParameters() error {
} }
// allow override for unit tests // allow override for unit tests
var findTenantID = common.FindTenantID var findTenantID = FindTenantID

View File

@ -133,7 +133,7 @@ func Test_ClientConfig_DeviceLogin(t *testing.T) {
getEnvOrSkip(t, "AZURE_DEVICE_LOGIN") getEnvOrSkip(t, "AZURE_DEVICE_LOGIN")
cfg := Config{ cfg := Config{
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"), SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
CloudEnvironment: getCloud(), cloudEnvironment: getCloud(),
} }
assertValid(t, cfg) assertValid(t, cfg)
@ -164,7 +164,7 @@ func Test_ClientConfig_ClientPassword(t *testing.T) {
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"), ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
ClientSecret: getEnvOrSkip(t, "AZURE_CLIENTSECRET"), ClientSecret: getEnvOrSkip(t, "AZURE_CLIENTSECRET"),
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"), TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
CloudEnvironment: getCloud(), cloudEnvironment: getCloud(),
} }
assertValid(t, cfg) assertValid(t, cfg)
@ -194,7 +194,7 @@ func Test_ClientConfig_ClientCert(t *testing.T) {
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"), ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
ClientCertPath: getEnvOrSkip(t, "AZURE_CLIENTCERT"), ClientCertPath: getEnvOrSkip(t, "AZURE_CLIENTCERT"),
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"), TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
CloudEnvironment: getCloud(), cloudEnvironment: getCloud(),
} }
assertValid(t, cfg) assertValid(t, cfg)
@ -224,7 +224,7 @@ func Test_ClientConfig_ClientJWT(t *testing.T) {
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"), ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
ClientJWT: getEnvOrSkip(t, "AZURE_CLIENTJWT"), ClientJWT: getEnvOrSkip(t, "AZURE_CLIENTJWT"),
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"), TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
CloudEnvironment: getCloud(), cloudEnvironment: getCloud(),
} }
assertValid(t, cfg) assertValid(t, cfg)

View File

@ -0,0 +1,8 @@
// +build !linux
package client
// IsAzure returns true if Packer is running on Azure (currently only works on Linux)
func IsAzure() bool {
return false
}

View File

@ -0,0 +1,23 @@
package client
import (
"bytes"
"io/ioutil"
)
var (
smbiosAssetTagFile = "/sys/class/dmi/id/chassis_asset_tag"
azureAssetTag = []byte("7783-7084-3265-9085-8269-3286-77\n")
)
// IsAzure returns true if Packer is running on Azure
func IsAzure() bool {
return isAzureAssetTag(smbiosAssetTagFile)
}
func isAzureAssetTag(filename string) bool {
if d, err := ioutil.ReadFile(filename); err == nil {
return bytes.Compare(d, azureAssetTag) == 0
}
return false
}

View File

@ -0,0 +1,29 @@
package client
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsAzure(t *testing.T) {
f, err := ioutil.TempFile("", "TestIsAzure*")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())
f.Seek(0, 0)
f.Truncate(0)
f.Write([]byte("not the azure assettag"))
assert.False(t, isAzureAssetTag(f.Name()), "asset tag is not Azure's")
f.Seek(0, 0)
f.Truncate(0)
f.Write(azureAssetTag)
assert.True(t, isAzureAssetTag(f.Name()), "asset tag is Azure's")
}

View File

@ -1,4 +1,4 @@
package common package client
import ( import (
"context" "context"

View File

@ -0,0 +1,81 @@
package client
import (
"fmt"
"net/http"
"time"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
)
// DefaultMetadataClient is the default instance metadata client for Azure. Replace this variable for testing purposes only
var DefaultMetadataClient = NewMetadataClient()
// MetadataClient holds methods that Packer uses to get information about the current VM
type MetadataClientAPI interface {
GetComputeInfo() (*ComputeInfo, error)
}
type ComputeInfo struct {
Name string
ResourceGroupName string
SubscriptionID string
Location string
}
// metadataClient implements MetadataClient
type metadataClient struct {
autorest.Sender
}
var _ MetadataClientAPI = metadataClient{}
const imdsURL = "http://169.254.169.254/metadata/instance?api-version=2017-08-01"
// VMResourceID returns the resource ID of the current VM
func (client metadataClient) GetComputeInfo() (*ComputeInfo, error) {
req, err := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithHeader("Metadata", "true"),
autorest.WithBaseURL(imdsURL),
).Prepare((&http.Request{}))
if err != nil {
return nil, err
}
res, err := autorest.SendWithSender(client, req,
autorest.DoRetryForDuration(1*time.Minute, 5*time.Second))
if err != nil {
return nil, err
}
var vminfo struct {
ComputeInfo `json:"compute"`
}
err = autorest.Respond(
res,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&vminfo),
autorest.ByClosing())
if err != nil {
return nil, err
}
return &vminfo.ComputeInfo, nil
}
func (ci ComputeInfo) ResourceID() string {
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s",
ci.SubscriptionID,
ci.ResourceGroupName,
ci.Name,
)
}
// NewMetadataClient creates a new instance metadata client
func NewMetadataClient() MetadataClientAPI {
return metadataClient{
Sender: autorest.CreateSender(),
}
}

View File

@ -0,0 +1,32 @@
package client
import (
"fmt"
"testing"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/stretchr/testify/assert"
)
func Test_MetadataReturnsComputeInfo(t *testing.T) {
if !IsAzure() {
t.Skipf("Not running on Azure, skipping live IMDS test")
}
mdc := NewMetadataClient()
info, err := mdc.GetComputeInfo()
assert.Nil(t, err)
vm, err := azure.ParseResourceID(fmt.Sprintf(
"/subscriptions/%s"+
"/resourceGroups/%s"+
"/providers/Microsoft.Compute"+
"/virtualMachines/%s",
info.SubscriptionID,
info.ResourceGroupName,
info.Name))
assert.Nil(t, err, "%q is not parsable as an Azure resource info", info)
assert.Regexp(t, "^[0-9a-fA-F-]{36}$", vm.SubscriptionID)
t.Logf("VM: %+v", vm)
}

View File

@ -0,0 +1,57 @@
package client
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute"
"github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/compute/computeapi"
"github.com/Azure/go-autorest/autorest/to"
"regexp"
"strings"
)
var platformImageRegex = regexp.MustCompile(`^[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+:[-_.a-zA-Z0-9]+$`)
type VirtualMachineImagesClientAPI interface {
computeapi.VirtualMachineImagesClientAPI
// extensions
GetLatest(ctx context.Context, publisher, offer, sku, location string) (*compute.VirtualMachineImageResource, error)
}
var _ VirtualMachineImagesClientAPI = virtualMachineImagesClientAPI{}
type virtualMachineImagesClientAPI struct {
computeapi.VirtualMachineImagesClientAPI
}
func ParsePlatformImageURN(urn string) (image *PlatformImage, err error) {
if !platformImageRegex.Match([]byte(urn)) {
return nil, fmt.Errorf("%q is not a valid platform image specifier", urn)
}
parts := strings.Split(urn, ":")
return &PlatformImage{parts[0], parts[1], parts[2], parts[3]}, nil
}
func (c virtualMachineImagesClientAPI) GetLatest(ctx context.Context, publisher, offer, sku, location string) (*compute.VirtualMachineImageResource, error) {
result, err := c.List(ctx, location, publisher, offer, sku, "", to.Int32Ptr(1), "name desc")
if err != nil {
return nil, err
}
if result.Value == nil || len(*result.Value) == 0 {
return nil, fmt.Errorf("%s:%s:%s:latest could not be found in location %s", publisher, offer, sku, location)
}
return &(*result.Value)[0], nil
}
type PlatformImage struct {
Publisher, Offer, Sku, Version string
}
func (pi PlatformImage) URN() string {
return fmt.Sprintf("%s:%s:%s:%s",
pi.Publisher,
pi.Offer,
pi.Sku,
pi.Version)
}

View File

@ -0,0 +1,30 @@
package client
import (
"fmt"
"testing"
)
func Test_platformImageRegex(t *testing.T) {
for i, v := range []string{
"Publisher:Offer:Sku:Versions",
"Publisher:Offer-name:2.0_alpha:2.0.2019060122",
} {
t.Run(fmt.Sprintf("should_match_%d", i), func(t *testing.T) {
if !platformImageRegex.Match([]byte(v)) {
t.Fatalf("expected %q to match", v)
}
})
}
for i, v := range []string{
"Publ isher:Offer:Sku:Versions",
"Publ/isher:Offer-name:2.0_alpha:2.0.2019060122",
} {
t.Run(fmt.Sprintf("should_not_match_%d", i), func(t *testing.T) {
if platformImageRegex.Match([]byte(v)) {
t.Fatalf("did not expected %q to match", v)
}
})
}
}

View File

@ -0,0 +1,30 @@
package client
import (
"errors"
"net/http"
"os"
"testing"
"github.com/Azure/go-autorest/autorest/azure/auth"
)
func GetTestClientSet(t *testing.T) (AzureClientSet, error) {
if os.Getenv("AZURE_INTEGRATION_TEST") == "" {
t.Skip("AZURE_INTEGRATION_TEST not set")
} else {
a, err := auth.NewAuthorizerFromEnvironment()
if err == nil {
cli := azureClientSet{}
cli.authorizer = a
cli.subscriptionID = os.Getenv("AZURE_SUBSCRIPTION_ID")
cli.PollingDelay = 0
cli.sender = http.DefaultClient
return cli, nil
} else {
t.Skipf("Could not create Azure client: %v", err)
}
}
return nil, errors.New("Couldn't create client set")
}

View File

@ -6,7 +6,6 @@ import (
"github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure"
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
) )
func NewDeviceFlowOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider { func NewDeviceFlowOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider {
@ -36,5 +35,5 @@ func (tp *deviceflowOauthTokenProvider) getServicePrincipalTokenWithResource(res
tp.say(fmt.Sprintf("Getting token for %s", resource)) tp.say(fmt.Sprintf("Getting token for %s", resource))
} }
return packerAzureCommon.Authenticate(tp.env, tp.tenantID, tp.say, resource) return Authenticate(tp.env, tp.tenantID, tp.say, resource)
} }

View File

@ -91,10 +91,11 @@ type Properties struct {
PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"` PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"`
Sku *Sku `json:"sku,omitempty"` Sku *Sku `json:"sku,omitempty"`
//StorageProfile3 *compute.StorageProfile `json:"storageProfile,omitempty"` //StorageProfile3 *compute.StorageProfile `json:"storageProfile,omitempty"`
StorageProfile *StorageProfileUnion `json:"storageProfile,omitempty"` StorageProfile *StorageProfileUnion `json:"storageProfile,omitempty"`
Subnets *[]network.Subnet `json:"subnets,omitempty"` Subnets *[]network.Subnet `json:"subnets,omitempty"`
TenantId *string `json:"tenantId,omitempty"` SecurityRules *[]network.SecurityRule `json:"securityRules,omitempty"`
Value *string `json:"value,omitempty"` TenantId *string `json:"tenantId,omitempty"`
Value *string `json:"value,omitempty"`
} }
type AccessPolicies struct { type AccessPolicies struct {

View File

@ -3,9 +3,11 @@ package template
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network"
"github.com/Azure/go-autorest/autorest/to" "github.com/Azure/go-autorest/autorest/to"
) )
@ -13,11 +15,12 @@ const (
jsonPrefix = "" jsonPrefix = ""
jsonIndent = " " jsonIndent = " "
resourceKeyVaults = "Microsoft.KeyVault/vaults" resourceKeyVaults = "Microsoft.KeyVault/vaults"
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces" resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses" resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
resourceVirtualMachine = "Microsoft.Compute/virtualMachines" resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
resourceVirtualNetworks = "Microsoft.Network/virtualNetworks" resourceVirtualNetworks = "Microsoft.Network/virtualNetworks"
resourceNetworkSecurityGroups = "Microsoft.Network/networkSecurityGroups"
variableSshKeyPath = "sshKeyPath" variableSshKeyPath = "sshKeyPath"
) )
@ -309,6 +312,39 @@ func (s *TemplateBuilder) SetPrivateVirtualNetworkWithPublicIp(virtualNetworkRes
return nil return nil
} }
func (s *TemplateBuilder) SetNetworkSecurityGroup(ipAddresses []string, port int) error {
nsgResource, dependency, resourceId := s.createNsgResource(ipAddresses, port)
if err := s.addResource(nsgResource); err != nil {
return err
}
vnetResource, err := s.getResourceByType(resourceVirtualNetworks)
if err != nil {
return err
}
s.deleteResourceByType(resourceVirtualNetworks)
s.addResourceDependency(vnetResource, dependency)
if vnetResource.Properties == nil || vnetResource.Properties.Subnets == nil || len(*vnetResource.Properties.Subnets) != 1 {
return fmt.Errorf("template: could not find virtual network/subnet to add default network security group to")
}
subnet := ((*vnetResource.Properties.Subnets)[0])
if subnet.SubnetPropertiesFormat == nil {
subnet.SubnetPropertiesFormat = &network.SubnetPropertiesFormat{}
}
if subnet.SubnetPropertiesFormat.NetworkSecurityGroup != nil {
return fmt.Errorf("template: subnet already has an associated network security group")
}
subnet.SubnetPropertiesFormat.NetworkSecurityGroup = &network.SecurityGroup{
ID: to.StringPtr(resourceId),
}
s.addResource(vnetResource)
return nil
}
func (s *TemplateBuilder) SetTags(tags *map[string]*string) error { func (s *TemplateBuilder) SetTags(tags *map[string]*string) error {
if tags == nil || len(*tags) == 0 { if tags == nil || len(*tags) == 0 {
return nil return nil
@ -366,6 +402,18 @@ func (s *TemplateBuilder) toVariable(name string) string {
return fmt.Sprintf("[variables('%s')]", name) return fmt.Sprintf("[variables('%s')]", name)
} }
func (s *TemplateBuilder) addResource(newResource *Resource) error {
for _, resource := range *s.template.Resources {
if *resource.Type == *newResource.Type {
return fmt.Errorf("template: found an existing resource of type %s", *resource.Type)
}
}
resources := append(*s.template.Resources, *newResource)
s.template.Resources = &resources
return nil
}
func (s *TemplateBuilder) deleteResourceByType(resourceType string) { func (s *TemplateBuilder) deleteResourceByType(resourceType string) {
resources := make([]Resource, 0) resources := make([]Resource, 0)
@ -379,6 +427,15 @@ func (s *TemplateBuilder) deleteResourceByType(resourceType string) {
s.template.Resources = &resources s.template.Resources = &resources
} }
func (s *TemplateBuilder) addResourceDependency(resource *Resource, dep string) {
if resource.DependsOn != nil {
deps := append(*resource.DependsOn, dep)
resource.DependsOn = &deps
} else {
resource.DependsOn = &[]string{dep}
}
}
func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate func(string) bool) { func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate func(string) bool) {
deps := make([]string, 0) deps := make([]string, 0)
@ -391,6 +448,38 @@ func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate
*resource.DependsOn = deps *resource.DependsOn = deps
} }
func (s *TemplateBuilder) createNsgResource(srcIpAddresses []string, port int) (*Resource, string, string) {
resource := &Resource{
ApiVersion: to.StringPtr("[variables('networkSecurityGroupsApiVersion')]"),
Name: to.StringPtr("[parameters('nsgName')]"),
Type: to.StringPtr(resourceNetworkSecurityGroups),
Location: to.StringPtr("[variables('location')]"),
Properties: &Properties{
SecurityRules: &[]network.SecurityRule{
{
Name: to.StringPtr("AllowIPsToSshWinRMInbound"),
SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{
Description: to.StringPtr("Allow inbound traffic from specified IP addresses"),
Protocol: network.SecurityRuleProtocolTCP,
Priority: to.Int32Ptr(100),
Access: network.SecurityRuleAccessAllow,
Direction: network.SecurityRuleDirectionInbound,
SourceAddressPrefixes: &srcIpAddresses,
SourcePortRange: to.StringPtr("*"),
DestinationAddressPrefix: to.StringPtr("VirtualNetwork"),
DestinationPortRange: to.StringPtr(strconv.Itoa(port)),
},
},
},
},
}
dependency := fmt.Sprintf("[concat('%s/', parameters('nsgName'))]", resourceNetworkSecurityGroups)
resourceId := fmt.Sprintf("[resourceId('%s', parameters('nsgName'))]", resourceNetworkSecurityGroups)
return resource, dependency, resourceId
}
// See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates. // See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates.
// Template to deploy a KeyVault. // Template to deploy a KeyVault.
@ -496,6 +585,9 @@ const BasicTemplate = `{
"virtualNetworkName": { "virtualNetworkName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"vmSize": { "vmSize": {
"type": "string" "type": "string"
}, },
@ -510,6 +602,7 @@ const BasicTemplate = `{
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"virtualNetworksApiVersion": "2017-04-01", "virtualNetworksApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -160,6 +163,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -158,6 +161,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -120,6 +123,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -174,6 +177,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -197,6 +200,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

View File

@ -14,6 +14,9 @@
"nicName": { "nicName": {
"type": "string" "type": "string"
}, },
"nsgName": {
"type": "string"
},
"osDiskName": { "osDiskName": {
"type": "string" "type": "string"
}, },
@ -190,6 +193,7 @@
"location": "[resourceGroup().location]", "location": "[resourceGroup().location]",
"managedDiskApiVersion": "2017-03-30", "managedDiskApiVersion": "2017-03-30",
"networkInterfacesApiVersion": "2017-04-01", "networkInterfacesApiVersion": "2017-04-01",
"networkSecurityGroupsApiVersion": "2019-04-01",
"publicIPAddressApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01",
"publicIPAddressType": "Dynamic", "publicIPAddressType": "Dynamic",
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",

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