Merge branch 'master' into issue8178
This commit is contained in:
commit
00d0297d26
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Bug Report
|
||||
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
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_requests.md
vendored
2
.github/ISSUE_TEMPLATE/feature_requests.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature Request
|
||||
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
|
||||
|
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Question
|
||||
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
|
||||
|
24
.github/ISSUE_TEMPLATE/ssh_or_winrm_times_out.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/ssh_or_winrm_times_out.md
vendored
Normal 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
|
35
CHANGELOG.md
35
CHANGELOG.md
@ -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)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
72
CODEOWNERS
72
CODEOWNERS
@ -3,24 +3,62 @@
|
||||
# builders
|
||||
|
||||
/builder/alicloud/ @chhaj5236
|
||||
/builder/azure/ @paulmey
|
||||
/builder/hyperv/ @taliesins
|
||||
/builder/jdcloud/ @XiaohanLiang @remrain
|
||||
/builder/linode/ @displague @ctreatma @stvnjacobs
|
||||
/builder/lxc/ @ChrisLundquist
|
||||
/builder/lxd/ @ChrisLundquist
|
||||
/builder/oneandone/ @jasmingacic
|
||||
/builder/oracle/ @prydie @owainlewis
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/builder/triton/ @sean-
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/builder/proxmox/ @carlpett
|
||||
/website/source/docs/builders/alicloud* @chhaj5236
|
||||
|
||||
/builder/azure/ @paulmey
|
||||
/website/source/docs/builders/azure* @paulmey
|
||||
|
||||
/builder/hyperv/ @taliesins
|
||||
/website/source/docs/builders/hyperv* @taliesins
|
||||
|
||||
/builder/jdcloud/ @XiaohanLiang @remrain
|
||||
/website/source/docs/builders/jdcloud* @XiaohanLiang @remrain
|
||||
|
||||
/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/hcloud/ @LKaemmerling
|
||||
/builder/hyperone @m110 @gregorybrzeski @ad-m
|
||||
/builder/ucloud/ @shawnmssu
|
||||
/builder/yandex @GennadySpb @alexanderKhaustov @seukyaso
|
||||
/builder/osc @marinsalinas
|
||||
/website/source/docs/builders/scaleway* @sieben @mvaude @jqueuniet @fflorens @brmzkw
|
||||
|
||||
/builder/hcloud/ @LKaemmerling
|
||||
/website/source/docs/builders/hcloud* @LKaemmerling
|
||||
|
||||
/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
|
||||
|
||||
|
3
Makefile
3
Makefile
@ -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/alvaroloes/enumer@master)
|
||||
@go install ./cmd/struct-markdown
|
||||
@go install ./cmd/mapstructure-to-hcl2
|
||||
|
||||
dev: ## Build and install a development build
|
||||
@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
|
||||
@echo "==> removing autogenerated markdown..."
|
||||
@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 fmt common/bootcommand/boot_command.go
|
||||
go fmt command/plugin.go
|
||||
|
@ -1,3 +1,5 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config,AlicloudDiskDevice
|
||||
|
||||
// The alicloud contains a packer.Builder implementation that
|
||||
// builds ecs images for alicloud.
|
||||
package ecs
|
||||
|
237
builder/alicloud/ecs/builder.hcl2spec.go
Normal file
237
builder/alicloud/ecs/builder.hcl2spec.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
//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
|
||||
// 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`.
|
||||
// See the [BlockDevices](#block-devices-configuration) documentation for
|
||||
// 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
|
||||
// configuration parameter requires some additional documentation which is
|
||||
// in the Chroot Mounts section. Please read that section for more
|
||||
@ -168,6 +169,14 @@ type Config struct {
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
func (c *Config) GetContext() interpolate.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
type interpolateContextProvider interface {
|
||||
GetContext() interpolate.Context
|
||||
}
|
||||
|
||||
type wrappedCommandTemplate struct {
|
||||
Command string
|
||||
}
|
||||
@ -392,8 +401,12 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&StepPostMountCommands{
|
||||
Commands: b.config.PostMountCommands,
|
||||
},
|
||||
&StepMountExtra{},
|
||||
&StepCopyFiles{},
|
||||
&StepMountExtra{
|
||||
ChrootMounts: b.config.ChrootMounts,
|
||||
},
|
||||
&StepCopyFiles{
|
||||
Files: b.config.CopyFiles,
|
||||
},
|
||||
&StepChrootProvision{},
|
||||
&StepEarlyCleanup{},
|
||||
&StepSnapshot{},
|
||||
@ -408,6 +421,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
RootVolumeSize: b.config.RootVolumeSize,
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
140
builder/amazon/chroot/builder.hcl2spec.go
Normal file
140
builder/amazon/chroot/builder.hcl2spec.go
Normal 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
|
||||
}
|
@ -17,20 +17,20 @@ import (
|
||||
// copy_files_cleanup CleanupFunc - A function to clean up the copied files
|
||||
// early.
|
||||
type StepCopyFiles struct {
|
||||
Files []string
|
||||
files []string
|
||||
}
|
||||
|
||||
func (s *StepCopyFiles) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
mountPath := state.Get("mount_path").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
stderr := new(bytes.Buffer)
|
||||
|
||||
s.files = make([]string, 0, len(config.CopyFiles))
|
||||
if len(config.CopyFiles) > 0 {
|
||||
s.files = make([]string, 0, len(s.Files))
|
||||
if len(s.Files) > 0 {
|
||||
ui.Say("Copying files from host to chroot...")
|
||||
for _, path := range config.CopyFiles {
|
||||
for _, path := range s.Files {
|
||||
ui.Message(path)
|
||||
chrootPath := filepath.Join(mountPath, path)
|
||||
log.Printf("Copying '%s' to '%s'", path, chrootPath)
|
||||
|
@ -17,19 +17,19 @@ import (
|
||||
// Produces:
|
||||
// mount_extra_cleanup CleanupFunc - To perform early cleanup
|
||||
type StepMountExtra struct {
|
||||
mounts []string
|
||||
ChrootMounts [][]string
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (s *StepMountExtra) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
mountPath := state.Get("mount_path").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
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...")
|
||||
for _, mountInfo := range config.ChrootMounts {
|
||||
for _, mountInfo := range s.ChrootMounts {
|
||||
innerPath := mountPath + mountInfo[2]
|
||||
|
||||
if err := os.MkdirAll(innerPath, 0755); err != nil {
|
||||
|
@ -19,7 +19,7 @@ type StepPostMountCommands struct {
|
||||
}
|
||||
|
||||
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)
|
||||
mountPath := state.Get("mount_path").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
@ -29,7 +29,7 @@ func (s *StepPostMountCommands) Run(ctx context.Context, state multistep.StateBa
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ictx := config.ctx
|
||||
ictx := config.GetContext()
|
||||
ictx.Data = &postMountCommandsData{
|
||||
Device: device,
|
||||
MountPath: mountPath,
|
||||
|
@ -17,7 +17,7 @@ type StepPreMountCommands struct {
|
||||
}
|
||||
|
||||
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)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||
@ -26,7 +26,7 @@ func (s *StepPreMountCommands) Run(ctx context.Context, state multistep.StateBag
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ictx := config.ctx
|
||||
ictx := config.GetContext()
|
||||
ictx.Data = &preMountCommandsData{Device: device}
|
||||
|
||||
ui.Say("Running device setup commands...")
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"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/common/random"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@ -17,6 +18,7 @@ type StepRegisterAMI struct {
|
||||
RootVolumeSize int64
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMISriovNetSupport bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
if config.FromScratch {
|
||||
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID)
|
||||
registerOpts = buildBaseRegisterOpts(config, nil, s.RootVolumeSize, snapshotID, amiName)
|
||||
} else {
|
||||
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 {
|
||||
@ -75,7 +94,7 @@ func (s *StepRegisterAMI) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
func (s *StepRegisterAMI) Cleanup(state multistep.StateBag) {}
|
||||
|
||||
// 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 (
|
||||
mappings []*ec2.BlockDeviceMapping
|
||||
rootDeviceName string
|
||||
@ -117,7 +136,7 @@ func buildBaseRegisterOpts(config *Config, sourceImage *ec2.Image, rootVolumeSiz
|
||||
|
||||
if config.FromScratch {
|
||||
return &ec2.RegisterImageInput{
|
||||
Name: &config.AMIName,
|
||||
Name: &amiName,
|
||||
Architecture: aws.String(config.Architecture),
|
||||
RootDeviceName: aws.String(rootDeviceName),
|
||||
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{
|
||||
Name: &config.AMIName,
|
||||
Name: &amiName,
|
||||
Architecture: image.Architecture,
|
||||
RootDeviceName: &rootDeviceName,
|
||||
BlockDeviceMappings: mappings,
|
||||
|
@ -30,7 +30,7 @@ func TestStepRegisterAmi_buildRegisterOpts_pv(t *testing.T) {
|
||||
|
||||
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName)
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if *opts.VirtualizationType != expected {
|
||||
@ -64,7 +64,7 @@ func TestStepRegisterAmi_buildRegisterOpts_hvm(t *testing.T) {
|
||||
|
||||
blockDevices := []*ec2.BlockDeviceMapping{}
|
||||
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName)
|
||||
opts := buildRegisterOptsFromExistingImage(&config, &image, blockDevices, rootDeviceName, config.AMIName)
|
||||
|
||||
expected := config.AMIVirtType
|
||||
if *opts.VirtualizationType != expected {
|
||||
@ -92,14 +92,14 @@ func TestStepRegisterAmi_buildRegisterOptsFromScratch(t *testing.T) {
|
||||
config := Config{
|
||||
FromScratch: true,
|
||||
PackerConfig: common.PackerConfig{},
|
||||
AMIMappings: []BlockDevice{
|
||||
{BlockDevice: amazon.BlockDevice{
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
amazon.BlockDevice{
|
||||
DeviceName: rootDeviceName,
|
||||
}},
|
||||
},
|
||||
},
|
||||
RootDeviceName: rootDeviceName,
|
||||
}
|
||||
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID)
|
||||
registerOpts := buildBaseRegisterOpts(&config, nil, 10, snapshotID, config.AMIName)
|
||||
|
||||
if len(registerOpts.BlockDeviceMappings) != 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 {
|
||||
t.Fatal("Expected block device mapping of length 2")
|
||||
@ -167,10 +167,10 @@ func TestStepRegisterAmi_buildRegisterOptFromExistingImageWithBlockDeviceMapping
|
||||
config := Config{
|
||||
FromScratch: false,
|
||||
PackerConfig: common.PackerConfig{},
|
||||
AMIMappings: []BlockDevice{
|
||||
{BlockDevice: amazon.BlockDevice{
|
||||
AMIMappings: []amazon.BlockDevice{
|
||||
amazon.BlockDevice{
|
||||
DeviceName: 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 {
|
||||
t.Fatal("Expected block device mapping of length 1")
|
||||
|
@ -1,4 +1,5 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type VaultAWSEngineOptions
|
||||
|
||||
package common
|
||||
|
||||
|
33
builder/amazon/common/access_config.hcl2spec.go
Normal file
33
builder/amazon/common/access_config.hcl2spec.go
Normal 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
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type BlockDevice
|
||||
|
||||
package common
|
||||
|
||||
|
45
builder/amazon/common/block_device.hcl2spec.go
Normal file
45
builder/amazon/common/block_device.hcl2spec.go
Normal 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
|
||||
}
|
@ -5,12 +5,12 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
for k, v := range input {
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: k,
|
||||
Values: []*string{v},
|
||||
Name: &k,
|
||||
Values: []*string{&v},
|
||||
})
|
||||
}
|
||||
return filters
|
||||
|
@ -1,4 +1,5 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions
|
||||
|
||||
package common
|
||||
|
||||
@ -18,11 +19,19 @@ import (
|
||||
var reShutdownBehavior = regexp.MustCompile("^(stop|terminate)$")
|
||||
|
||||
type AmiFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
Owners []*string
|
||||
Filters map[string]string
|
||||
Owners []string
|
||||
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 {
|
||||
return len(d.Owners) == 0 && len(d.Filters) == 0
|
||||
}
|
||||
@ -32,7 +41,7 @@ func (d *AmiFilterOptions) NoOwner() bool {
|
||||
}
|
||||
|
||||
type SubnetFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
Filters map[string]string
|
||||
MostFree bool `mapstructure:"most_free"`
|
||||
Random bool `mapstructure:"random"`
|
||||
}
|
||||
@ -42,7 +51,16 @@ func (d *SubnetFilterOptions) Empty() bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -50,7 +68,7 @@ func (d *VpcFilterOptions) Empty() bool {
|
||||
}
|
||||
|
||||
type SecurityGroupFilterOptions struct {
|
||||
Filters map[*string]*string
|
||||
Filters map[string]string
|
||||
}
|
||||
|
||||
func (d *SecurityGroupFilterOptions) Empty() bool {
|
||||
@ -124,6 +142,25 @@ type RunConfig struct {
|
||||
// profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html)
|
||||
// to launch the EC2 instance with.
|
||||
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
|
||||
// shutdown in case Packer exits ungracefully. Possible values are stop and
|
||||
// terminate. Defaults to stop.
|
||||
|
97
builder/amazon/common/run_config.hcl2spec.go
Normal file
97
builder/amazon/common/run_config.hcl2spec.go
Normal 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
|
||||
}
|
@ -73,7 +73,7 @@ func TestRunConfigPrepare_SourceAmiFilterOwnersBlank(t *testing.T) {
|
||||
c := testConfigFilter()
|
||||
filter_key := "name"
|
||||
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 {
|
||||
t.Fatalf("Should error if Owners is not specified)")
|
||||
}
|
||||
@ -84,7 +84,7 @@ func TestRunConfigPrepare_SourceAmiFilterGood(t *testing.T) {
|
||||
owner := "123"
|
||||
filter_key := "name"
|
||||
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
|
||||
if err := c.Prepare(nil); len(err) != 0 {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
194
builder/amazon/common/step_iam_instance_profile.go
Normal file
194
builder/amazon/common/step_iam_instance_profile.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"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() {
|
||||
params := &ec2.DescribeVpcsInput{}
|
||||
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)
|
||||
|
||||
@ -79,13 +78,13 @@ func (s *StepNetworkInfo) Run(ctx context.Context, state multistep.StateBag) mul
|
||||
// Subnet
|
||||
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
|
||||
params := &ec2.DescribeSubnetsInput{}
|
||||
s.SubnetFilter.Filters[aws.String("state")] = aws.String("available")
|
||||
s.SubnetFilter.Filters["state"] = "available"
|
||||
|
||||
if s.VpcId != "" {
|
||||
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId
|
||||
s.SubnetFilter.Filters["vpc-id"] = s.VpcId
|
||||
}
|
||||
if s.AvailabilityZone != "" {
|
||||
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone
|
||||
s.SubnetFilter.Filters["availabilityZone"] = s.AvailabilityZone
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
|
||||
log.Printf("Using Subnet Filters %v", params)
|
||||
|
@ -28,7 +28,6 @@ type StepRunSourceInstance struct {
|
||||
EbsOptimized bool
|
||||
EnableT2Unlimited bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
IsRestricted bool
|
||||
@ -45,6 +44,8 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
ec2conn := state.Get("ec2").(*ec2.EC2)
|
||||
|
||||
securityGroupIds := aws.StringSlice(state.Get("securityGroupIds").([]string))
|
||||
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
userData := s.UserData
|
||||
@ -110,7 +111,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
|
||||
UserData: &userData,
|
||||
MaxCount: aws.Int64(1),
|
||||
MinCount: aws.Int64(1),
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
|
||||
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: iamInstanceProfile},
|
||||
BlockDeviceMappings: s.LaunchMappings.BuildEC2BlockDeviceMappings(),
|
||||
Placement: &ec2.Placement{AvailabilityZone: &az},
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
|
@ -31,7 +31,6 @@ type StepRunSpotInstance struct {
|
||||
Comm *communicator.Config
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
SourceAMI string
|
||||
@ -69,12 +68,14 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||
launchMappingRequests = append(launchMappingRequests, launchRequest)
|
||||
}
|
||||
|
||||
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
|
||||
|
||||
// Create a launch template.
|
||||
templateData := ec2.RequestLaunchTemplateData{
|
||||
BlockDeviceMappings: launchMappingRequests,
|
||||
DisableApiTermination: aws.Bool(false),
|
||||
EbsOptimized: &s.EbsOptimized,
|
||||
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: &s.IamInstanceProfile},
|
||||
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecificationRequest{Name: iamInstanceProfile},
|
||||
ImageId: &s.SourceAMI,
|
||||
InstanceMarketOptions: marketOptions,
|
||||
Placement: &ec2.LaunchTemplatePlacementRequest{
|
||||
@ -274,15 +275,20 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
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)
|
||||
if len(createOutput.Instances) == 0 {
|
||||
// We can end up with errors because one of the allowed availability
|
||||
// zones doesn't have one of the allowed instance types; as long as
|
||||
// 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]
|
||||
|
@ -2,6 +2,7 @@ package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -73,6 +74,7 @@ func tStateSpot() multistep.StateBag {
|
||||
})
|
||||
state.Put("availability_zone", "us-east-1c")
|
||||
state.Put("securityGroupIds", []string{"sg-0b8984db72f213dc3"})
|
||||
state.Put("iamInstanceProfile", "packer-123")
|
||||
state.Put("subnet_id", "subnet-077fde4e")
|
||||
state.Put("source_image", "")
|
||||
return state
|
||||
@ -91,7 +93,6 @@ func getBasicStep() *StepRunSpotInstance {
|
||||
},
|
||||
EbsOptimized: false,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: "",
|
||||
InstanceInitiatedShutdownBehavior: "stop",
|
||||
InstanceType: "t2.micro",
|
||||
SourceAMI: "",
|
||||
@ -125,6 +126,10 @@ func TestCreateTemplateData(t *testing.T) {
|
||||
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
|
||||
state.Put("subnet_id", "")
|
||||
template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||
@ -132,4 +137,13 @@ func TestCreateTemplateData(t *testing.T) {
|
||||
if template.NetworkInterfaces != nil {
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
|
||||
|
||||
params := &ec2.DescribeSecurityGroupsInput{}
|
||||
if vpcId != "" {
|
||||
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId
|
||||
s.SecurityGroupFilter.Filters["vpc-id"] = vpcId
|
||||
}
|
||||
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)
|
||||
|
||||
|
@ -58,7 +58,7 @@ func (s *StepSourceAMIInfo) Run(ctx context.Context, state multistep.StateBag) m
|
||||
params.Filters = buildEc2Filters(s.AmiFilters.Filters)
|
||||
}
|
||||
if len(s.AmiFilters.Owners) > 0 {
|
||||
params.Owners = s.AmiFilters.Owners
|
||||
params.Owners = s.AmiFilters.GetOwners()
|
||||
}
|
||||
|
||||
log.Printf("Using AMI Filters %v", params)
|
||||
|
@ -1,4 +1,5 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
|
||||
// The amazonebs package contains a packer.Builder implementation that
|
||||
// builds AMIs for Amazon EC2.
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"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)
|
||||
|
||||
iam := iam.New(session)
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
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,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
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,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
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,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
LaunchMappings: b.config.LaunchMappings,
|
||||
},
|
||||
|
244
builder/amazon/ebs/builder.hcl2spec.go
Normal file
244
builder/amazon/ebs/builder.hcl2spec.go
Normal 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
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
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("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
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,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
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,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
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,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
&awscommon.StepCleanupVolumes{
|
||||
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,
|
||||
Architecture: b.config.Architecture,
|
||||
LaunchOmitMap: b.config.LaunchMappings.GetOmissions(),
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"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/common/random"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@ -22,6 +23,7 @@ type StepRegisterAMI struct {
|
||||
Architecture string
|
||||
image *ec2.Image
|
||||
LaunchOmitMap map[string]bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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{
|
||||
Name: &config.AMIName,
|
||||
Name: &amiName,
|
||||
Architecture: aws.String(s.Architecture),
|
||||
RootDeviceName: aws.String(s.RootDevice.DeviceName),
|
||||
VirtualizationType: aws.String(config.AMIVirtType),
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"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
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("hook", hook)
|
||||
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,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
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,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
ExpectedRootDevice: "ebs",
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
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,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
instanceStep,
|
||||
&stepTagEBSVolumes{
|
||||
VolumeMapping: b.config.VolumeMappings,
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@ -229,6 +230,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
return nil, err
|
||||
}
|
||||
ec2conn := ec2.New(session)
|
||||
iam := iam.New(session)
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
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("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("iam", iam)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
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,
|
||||
Debug: b.config.PackerDebug,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceType: b.config.InstanceType,
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
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,
|
||||
EbsOptimized: b.config.EbsOptimized,
|
||||
EnableT2Unlimited: b.config.EnableT2Unlimited,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
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,
|
||||
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
|
||||
},
|
||||
&awscommon.StepIamInstanceProfile{
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
|
||||
},
|
||||
instanceStep,
|
||||
&awscommon.StepGetPassword{
|
||||
Debug: b.config.PackerDebug,
|
||||
@ -350,6 +355,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
&StepRegisterAMI{
|
||||
EnableAMISriovNetSupport: b.config.AMISriovNetSupport,
|
||||
EnableAMIENASupport: b.config.AMIENASupport,
|
||||
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
|
||||
},
|
||||
&awscommon.StepAMIRegionCopy{
|
||||
AccessConfig: &b.config.AccessConfig,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"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/common/random"
|
||||
confighelper "github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@ -15,6 +16,7 @@ import (
|
||||
type StepRegisterAMI struct {
|
||||
EnableAMIENASupport confighelper.Trilean
|
||||
EnableAMISriovNetSupport bool
|
||||
AMISkipBuildRegion bool
|
||||
}
|
||||
|
||||
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.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{
|
||||
ImageLocation: &manifestPath,
|
||||
Name: aws.String(config.AMIName),
|
||||
Name: aws.String(amiName),
|
||||
BlockDeviceMappings: config.AMIMappings.BuildEC2BlockDeviceMappings(),
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ type AzureClient struct {
|
||||
network.InterfacesClient
|
||||
network.SubnetsClient
|
||||
network.VirtualNetworksClient
|
||||
network.SecurityGroupsClient
|
||||
compute.ImagesClient
|
||||
compute.VirtualMachinesClient
|
||||
common.VaultClient
|
||||
@ -127,7 +128,7 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
var azureClient = &AzureClient{}
|
||||
@ -139,72 +140,90 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.DisksClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.ImagesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.SnapshotsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.SnapshotsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
@ -218,6 +237,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
azureClient.GalleryImagesClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.GalleryImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
azureClient.GalleryImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImagesClient.UserAgent)
|
||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
|
||||
|
||||
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
|
||||
if err != nil {
|
||||
@ -229,6 +249,7 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
|
||||
azureClient.VaultClient.RequestInspector = withInspection(maxlen)
|
||||
azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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.
|
||||
// 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.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||
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 resourceGroupName != "" && storageAccountName != "" {
|
||||
|
@ -79,8 +79,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
||||
b.config.ClientConfig.SubscriptionID,
|
||||
b.config.ResourceGroupName,
|
||||
b.config.StorageAccount,
|
||||
b.config.ClientConfig.CloudEnvironment,
|
||||
b.config.ClientConfig.CloudEnvironment(),
|
||||
b.config.SharedGalleryTimeout,
|
||||
b.config.PollingDurationTimeout,
|
||||
spnCloud,
|
||||
spnKeyVault)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type Config,SharedImageGallery,SharedImageGalleryDestination,PlanInformation
|
||||
|
||||
package arm
|
||||
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@ -311,6 +313,14 @@ type Config struct {
|
||||
// 4. PlanPromotionCode
|
||||
//
|
||||
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
|
||||
// automatically configure authentication credentials for the provisioned
|
||||
// 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.
|
||||
DiskCachingType string `mapstructure:"disk_caching_type" required:"false"`
|
||||
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
|
||||
UserName string
|
||||
@ -357,6 +374,7 @@ type Config struct {
|
||||
tmpOSDiskName string
|
||||
tmpSubnetName string
|
||||
tmpVirtualNetworkName string
|
||||
tmpNsgName string
|
||||
tmpWinRMCertificateUrl string
|
||||
|
||||
// Authentication with the VM via SSH
|
||||
@ -604,6 +622,7 @@ func setRuntimeValues(c *Config) {
|
||||
c.tmpOSDiskName = tempName.OSDiskName
|
||||
c.tmpSubnetName = tempName.SubnetName
|
||||
c.tmpVirtualNetworkName = tempName.VirtualNetworkName
|
||||
c.tmpNsgName = tempName.NsgName
|
||||
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"))
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
if strings.EqualFold(c.OSType, constants.Target_Linux) {
|
||||
@ -943,6 +979,17 @@ func assertManagedImageDataDiskSnapshotName(name, setting string) (bool, error)
|
||||
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) {
|
||||
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)
|
||||
|
299
builder/azure/arm/config.hcl2spec.go
Normal file
299
builder/azure/arm/config.hcl2spec.go
Normal 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
|
||||
}
|
@ -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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,8 +453,8 @@ func TestConfigInstantiatesCorrectAzureEnvironment(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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)
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -380,6 +506,10 @@ func TestSystemShouldDefineRuntimeValues(t *testing.T) {
|
||||
if 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) {
|
||||
|
@ -95,7 +95,7 @@ func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context,
|
||||
resourceType,
|
||||
resourceName))
|
||||
|
||||
err := retry.Config{
|
||||
retry.Config{
|
||||
Tries: 10,
|
||||
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 600 * time.Second, Multiplier: 2}).Linear,
|
||||
}.Run(ctx, func(ctx context.Context) error {
|
||||
@ -106,7 +106,7 @@ func (s *StepDeleteResourceGroup) deleteDeploymentResources(ctx context.Context,
|
||||
if err != nil {
|
||||
s.reportIfError(err, resourceName)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
})
|
||||
|
||||
if err = deploymentOperations.Next(); err != nil {
|
||||
|
@ -115,6 +115,12 @@ func deleteResource(ctx context.Context, client *AzureClient, resourceType strin
|
||||
err = f.WaitForCompletionRef(ctx, client.VirtualNetworksClient.Client)
|
||||
}
|
||||
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":
|
||||
f, err := client.PublicIPAddressesClient.Delete(ctx, resourceGroupName, resourceName)
|
||||
if err == nil {
|
||||
|
@ -40,6 +40,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
|
||||
SubnetName: &template.TemplateParameter{Value: config.tmpSubnetName},
|
||||
StorageAccountBlobEndpoint: &template.TemplateParameter{Value: config.storageAccountBlobEndpoint},
|
||||
VirtualNetworkName: &template.TemplateParameter{Value: config.tmpVirtualNetworkName},
|
||||
NsgName: &template.TemplateParameter{Value: config.tmpNsgName},
|
||||
VMSize: &template.TemplateParameter{Value: config.VMSize},
|
||||
VMName: &template.TemplateParameter{Value: config.tmpComputeName},
|
||||
}
|
||||
@ -117,6 +118,13 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error)
|
||||
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)
|
||||
doc, _ := builder.ToJSON()
|
||||
return createDeploymentParameters(*doc, params)
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -189,6 +192,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -194,6 +197,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -160,6 +163,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -158,6 +161,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -119,6 +122,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -178,6 +181,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -159,6 +162,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -158,6 +161,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -161,6 +164,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -139,6 +142,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -172,6 +175,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -173,6 +176,7 @@
|
||||
"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')]",
|
||||
|
@ -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'))]"
|
||||
}
|
||||
}
|
@ -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.
|
||||
func TestKeyVaultDeployment00(t *testing.T) {
|
||||
c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration())
|
||||
|
@ -19,6 +19,7 @@ type TempName struct {
|
||||
SubnetName string
|
||||
PublicIPAddressName string
|
||||
VirtualNetworkName string
|
||||
NsgName string
|
||||
}
|
||||
|
||||
func NewTempName() *TempName {
|
||||
@ -33,6 +34,7 @@ func NewTempName() *TempName {
|
||||
tempName.PublicIPAddressName = fmt.Sprintf("pkrip%s", suffix)
|
||||
tempName.SubnetName = fmt.Sprintf("pkrsn%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.AdminPassword = generatePassword()
|
||||
|
@ -41,6 +41,10 @@ func TestTempNameShouldCreatePrefixedRandomNames(t *testing.T) {
|
||||
if strings.Index(tempName.VirtualNetworkName, "pkrvn") != 0 {
|
||||
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) {
|
||||
@ -92,4 +96,8 @@ func TestTempNameShouldHaveSameSuffix(t *testing.T) {
|
||||
if strings.HasSuffix(tempName.VirtualNetworkName, suffix) != true {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
452
builder/azure/chroot/builder.go
Normal file
452
builder/azure/chroot/builder.go
Normal 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{}
|
70
builder/azure/chroot/builder_test.go
Normal file
70
builder/azure/chroot/builder_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
222
builder/azure/chroot/diskattacher.go
Normal file
222
builder/azure/chroot/diskattacher.go
Normal 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
|
||||
}
|
86
builder/azure/chroot/diskattacher_test.go
Normal file
86
builder/azure/chroot/diskattacher_test.go
Normal 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)
|
||||
}
|
83
builder/azure/chroot/step_attach_disk.go
Normal file
83
builder/azure/chroot/step_attach_disk.go
Normal 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
|
||||
}
|
131
builder/azure/chroot/step_attach_disk_test.go
Normal file
131
builder/azure/chroot/step_attach_disk_test.go
Normal 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")
|
||||
}
|
89
builder/azure/chroot/step_create_image.go
Normal file
89
builder/azure/chroot/step_create_image.go
Normal 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
|
110
builder/azure/chroot/step_create_new_disk.go
Normal file
110
builder/azure/chroot/step_create_new_disk.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
147
builder/azure/chroot/step_create_new_disk_test.go
Normal file
147
builder/azure/chroot/step_create_new_disk_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
132
builder/azure/chroot/step_mount_device.go
Normal file
132
builder/azure/chroot/step_mount_device.go
Normal 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
|
||||
}
|
81
builder/azure/chroot/step_verify_source_disk.go
Normal file
81
builder/azure/chroot/step_verify_source_disk.go
Normal 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) {}
|
168
builder/azure/chroot/step_verify_source_disk_test.go
Normal file
168
builder/azure/chroot/step_verify_source_disk_test.go
Normal 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
|
||||
}
|
37
builder/azure/chroot/template_funcs.go
Normal file
37
builder/azure/chroot/template_funcs.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
103
builder/azure/common/artifact.go
Normal file
103
builder/azure/common/artifact.go
Normal 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
|
||||
}
|
90
builder/azure/common/client/azure_client_set.go
Normal file
90
builder/azure/common/client/azure_client_set.go
Normal 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
|
||||
}
|
46
builder/azure/common/client/azure_client_set_mock.go
Normal file
46
builder/azure/common/client/azure_client_set_mock.go
Normal 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
|
||||
}
|
@ -4,7 +4,6 @@ package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/packer/builder/azure/common"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@ -28,7 +27,7 @@ type Config struct {
|
||||
// USGovernment. Defaults to Public. Long forms such as
|
||||
// USGovernmentCloud and AzureUSGovernmentCloud are also supported.
|
||||
CloudEnvironmentName string `mapstructure:"cloud_environment_name" required:"false"`
|
||||
CloudEnvironment *azure.Environment
|
||||
cloudEnvironment *azure.Environment
|
||||
|
||||
// Authentication fields
|
||||
|
||||
@ -73,6 +72,10 @@ func (c *Config) SetDefaultValues() error {
|
||||
return c.setCloudEnvironment()
|
||||
}
|
||||
|
||||
func (c *Config) CloudEnvironment() *azure.Environment {
|
||||
return c.cloudEnvironment
|
||||
}
|
||||
|
||||
func (c *Config) setCloudEnvironment() error {
|
||||
lookup := map[string]string{
|
||||
"CHINA": "AzureChinaCloud",
|
||||
@ -103,7 +106,7 @@ func (c *Config) setCloudEnvironment() error {
|
||||
}
|
||||
|
||||
env, err := azure.EnvironmentFromName(envName)
|
||||
c.CloudEnvironment = &env
|
||||
c.cloudEnvironment = &env
|
||||
return err
|
||||
}
|
||||
|
||||
@ -198,60 +201,64 @@ func (c Config) UseMSI() bool {
|
||||
c.TenantID == ""
|
||||
}
|
||||
|
||||
func (c Config) GetServicePrincipalTokens(
|
||||
say func(string)) (
|
||||
func (c Config) GetServicePrincipalTokens(say func(string)) (
|
||||
servicePrincipalToken *adal.ServicePrincipalToken,
|
||||
servicePrincipalTokenVault *adal.ServicePrincipalToken,
|
||||
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
|
||||
switch c.authType {
|
||||
case authTypeDeviceLogin:
|
||||
say("Getting tokens using device flow")
|
||||
auth = NewDeviceFlowOAuthTokenProvider(*c.CloudEnvironment, say, tenantID)
|
||||
auth = NewDeviceFlowOAuthTokenProvider(*c.cloudEnvironment, say, c.TenantID)
|
||||
case authTypeMSI:
|
||||
say("Getting tokens using Managed Identity for Azure")
|
||||
auth = NewMSIOAuthTokenProvider(*c.CloudEnvironment)
|
||||
auth = NewMSIOAuthTokenProvider(*c.cloudEnvironment)
|
||||
case authTypeClientSecret:
|
||||
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:
|
||||
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 {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
case authTypeClientBearerJWT:
|
||||
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:
|
||||
panic("authType not set, call FillParameters, or set explicitly")
|
||||
}
|
||||
|
||||
servicePrincipalToken, err = auth.getServicePrincipalToken()
|
||||
servicePrincipalToken, err = auth.getServicePrincipalTokenWithResource(forResource)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = servicePrincipalToken.EnsureFresh()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
servicePrincipalTokenVault, err = auth.getServicePrincipalTokenWithResource(
|
||||
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
|
||||
return servicePrincipalToken, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if c.CloudEnvironment == nil {
|
||||
if c.cloudEnvironment == nil {
|
||||
err := c.setCloudEnvironment()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -288,7 +295,7 @@ func (c *Config) FillParameters() error {
|
||||
}
|
||||
|
||||
if c.TenantID == "" {
|
||||
tenantID, err := findTenantID(*c.CloudEnvironment, c.SubscriptionID)
|
||||
tenantID, err := findTenantID(*c.cloudEnvironment, c.SubscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -299,4 +306,4 @@ func (c *Config) FillParameters() error {
|
||||
}
|
||||
|
||||
// allow override for unit tests
|
||||
var findTenantID = common.FindTenantID
|
||||
var findTenantID = FindTenantID
|
||||
|
@ -133,7 +133,7 @@ func Test_ClientConfig_DeviceLogin(t *testing.T) {
|
||||
getEnvOrSkip(t, "AZURE_DEVICE_LOGIN")
|
||||
cfg := Config{
|
||||
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
|
||||
CloudEnvironment: getCloud(),
|
||||
cloudEnvironment: getCloud(),
|
||||
}
|
||||
assertValid(t, cfg)
|
||||
|
||||
@ -164,7 +164,7 @@ func Test_ClientConfig_ClientPassword(t *testing.T) {
|
||||
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
|
||||
ClientSecret: getEnvOrSkip(t, "AZURE_CLIENTSECRET"),
|
||||
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
|
||||
CloudEnvironment: getCloud(),
|
||||
cloudEnvironment: getCloud(),
|
||||
}
|
||||
assertValid(t, cfg)
|
||||
|
||||
@ -194,7 +194,7 @@ func Test_ClientConfig_ClientCert(t *testing.T) {
|
||||
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
|
||||
ClientCertPath: getEnvOrSkip(t, "AZURE_CLIENTCERT"),
|
||||
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
|
||||
CloudEnvironment: getCloud(),
|
||||
cloudEnvironment: getCloud(),
|
||||
}
|
||||
assertValid(t, cfg)
|
||||
|
||||
@ -224,7 +224,7 @@ func Test_ClientConfig_ClientJWT(t *testing.T) {
|
||||
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
|
||||
ClientJWT: getEnvOrSkip(t, "AZURE_CLIENTJWT"),
|
||||
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
|
||||
CloudEnvironment: getCloud(),
|
||||
cloudEnvironment: getCloud(),
|
||||
}
|
||||
assertValid(t, cfg)
|
||||
|
||||
|
8
builder/azure/common/client/detect_azure.go
Normal file
8
builder/azure/common/client/detect_azure.go
Normal 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
|
||||
}
|
23
builder/azure/common/client/detect_azure_linux.go
Normal file
23
builder/azure/common/client/detect_azure_linux.go
Normal 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
|
||||
}
|
29
builder/azure/common/client/detect_azure_linux_test.go
Normal file
29
builder/azure/common/client/detect_azure_linux_test.go
Normal 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")
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package common
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
81
builder/azure/common/client/metadata.go
Normal file
81
builder/azure/common/client/metadata.go
Normal 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(),
|
||||
}
|
||||
}
|
32
builder/azure/common/client/metadata_test.go
Normal file
32
builder/azure/common/client/metadata_test.go
Normal 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)
|
||||
}
|
57
builder/azure/common/client/platform_image.go
Normal file
57
builder/azure/common/client/platform_image.go
Normal 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)
|
||||
}
|
30
builder/azure/common/client/platform_image_test.go
Normal file
30
builder/azure/common/client/platform_image_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
30
builder/azure/common/client/testclient.go
Normal file
30
builder/azure/common/client/testclient.go
Normal 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")
|
||||
}
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"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 {
|
||||
@ -36,5 +35,5 @@ func (tp *deviceflowOauthTokenProvider) getServicePrincipalTokenWithResource(res
|
||||
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)
|
||||
}
|
||||
|
@ -91,10 +91,11 @@ type Properties struct {
|
||||
PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"`
|
||||
Sku *Sku `json:"sku,omitempty"`
|
||||
//StorageProfile3 *compute.StorageProfile `json:"storageProfile,omitempty"`
|
||||
StorageProfile *StorageProfileUnion `json:"storageProfile,omitempty"`
|
||||
Subnets *[]network.Subnet `json:"subnets,omitempty"`
|
||||
TenantId *string `json:"tenantId,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
StorageProfile *StorageProfileUnion `json:"storageProfile,omitempty"`
|
||||
Subnets *[]network.Subnet `json:"subnets,omitempty"`
|
||||
SecurityRules *[]network.SecurityRule `json:"securityRules,omitempty"`
|
||||
TenantId *string `json:"tenantId,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type AccessPolicies struct {
|
||||
|
@ -3,9 +3,11 @@ package template
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -13,11 +15,12 @@ const (
|
||||
jsonPrefix = ""
|
||||
jsonIndent = " "
|
||||
|
||||
resourceKeyVaults = "Microsoft.KeyVault/vaults"
|
||||
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
|
||||
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
|
||||
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
|
||||
resourceVirtualNetworks = "Microsoft.Network/virtualNetworks"
|
||||
resourceKeyVaults = "Microsoft.KeyVault/vaults"
|
||||
resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces"
|
||||
resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses"
|
||||
resourceVirtualMachine = "Microsoft.Compute/virtualMachines"
|
||||
resourceVirtualNetworks = "Microsoft.Network/virtualNetworks"
|
||||
resourceNetworkSecurityGroups = "Microsoft.Network/networkSecurityGroups"
|
||||
|
||||
variableSshKeyPath = "sshKeyPath"
|
||||
)
|
||||
@ -309,6 +312,39 @@ func (s *TemplateBuilder) SetPrivateVirtualNetworkWithPublicIp(virtualNetworkRes
|
||||
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 {
|
||||
if tags == nil || len(*tags) == 0 {
|
||||
return nil
|
||||
@ -366,6 +402,18 @@ func (s *TemplateBuilder) toVariable(name string) string {
|
||||
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) {
|
||||
resources := make([]Resource, 0)
|
||||
|
||||
@ -379,6 +427,15 @@ func (s *TemplateBuilder) deleteResourceByType(resourceType string) {
|
||||
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) {
|
||||
deps := make([]string, 0)
|
||||
|
||||
@ -391,6 +448,38 @@ func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate
|
||||
*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.
|
||||
|
||||
// Template to deploy a KeyVault.
|
||||
@ -496,6 +585,9 @@ const BasicTemplate = `{
|
||||
"virtualNetworkName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -510,6 +602,7 @@ const BasicTemplate = `{
|
||||
"networkInterfacesApiVersion": "2017-04-01",
|
||||
"publicIPAddressApiVersion": "2017-04-01",
|
||||
"virtualNetworksApiVersion": "2017-04-01",
|
||||
"networkSecurityGroupsApiVersion": "2019-04-01",
|
||||
"location": "[resourceGroup().location]",
|
||||
"publicIPAddressType": "Dynamic",
|
||||
"sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -160,6 +163,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -158,6 +161,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -120,6 +123,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -174,6 +177,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -197,6 +200,7 @@
|
||||
"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')]",
|
||||
|
@ -14,6 +14,9 @@
|
||||
"nicName": {
|
||||
"type": "string"
|
||||
},
|
||||
"nsgName": {
|
||||
"type": "string"
|
||||
},
|
||||
"osDiskName": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -190,6 +193,7 @@
|
||||
"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')]",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user