Compare commits

...

75 Commits

Author SHA1 Message Date
Wilken Rivera d597e93e70 improve error message 2020-09-25 12:12:31 -04:00
Wilken Rivera 1bb154de5a Add support for reading configs
This is the first step in attempting to read a Packer configuration file
via the packer init command. At the present moment it will try to read
one or more configuration templates from a given directory or path. Once
parsed it will error if parsing fails or exist successfully if it is
able to parse the file.

Looking at how the code is structured there will need to be changes made
to the following places:

- When no configuration file is found Packer will display an error. That
error should be bubbled up a bit so that the caller command can
determine if it should be displayed or not. For packer init no
configuration is not an error. Maybe it should be?

- After a configuration has been parsed there needs to be a single way
to determine a list of plugins associated with the configuration. HCL
and JSON configs have fields for this data but some is exported and some
is unexported. Adapting the packerHandler interface may be an option
here. More investigation needed.
2020-09-16 14:46:16 -04:00
Wilken Rivera 8069ae4f43 Add flagset to init command 2020-09-15 10:19:56 -04:00
Wilken Rivera e8f7076416 Add simple init command 2020-09-14 16:33:51 -04:00
Andreas Botzner c032d463d3 Bootcommand Fix For Proxmox Builder (#9885) 2020-09-14 17:17:38 +02:00
Sylvia Moss caf65781d7 Add vsphere-clone mac_address option (#9930)
* add vsphere-clone mac_address option

* check on prepare if network is set together with mac_address
2020-09-14 13:18:39 +02:00
Andreas Botzner 3739970b84 Adds ability to specify interfaces for http_directroy and VM for the Proxmox builder (#9874)
Adds two config options to the Proxmox builder:
```
http_interface
vm_interface
```
Both give the user the ability to specify an interface whos IP will be set as the `.HTTPIP` and VMIP respectively.

This is useful when the VM and or the machine executing Packer has multiple interfaces.
Until now Packer would accept the first non-loopback IP as the `.HTTPIP` and VMIP.

I'm open to suggestions and any kind of feedback. 


* Added ability to define the NIC where the HTTPIP is taken from.
* Added VM interface config option
* fmt and documentation
* Moved HTTPInterface into HTTPCommon
* Build Fix
* Documentation Fix
2020-09-14 12:24:01 +02:00
GennadySpb ad4ed3c986 Update CHANGELOG.md (#9935) 2020-09-14 12:14:19 +02:00
Wilken Rivera f94e78077a update CHANGELOG 2020-09-11 14:13:10 -04:00
Rui Lopes 53331644a6 add support for using xorriso to create the cdrom iso (#9919) 2020-09-11 14:11:19 -04:00
Megan Marsh bcd3c33e49 Merge pull request #9931 from GennadySpb/yndx-import-fix-url
yandex-import: Fix S3 URL construct process
2020-09-11 07:48:11 -07:00
Sylvia Moss 31f08909b4 Add usb_scan_codes option to use USB scancodes instead of VNC for remote builds (#9895) 2020-09-11 16:46:33 +02:00
Gennady Lipenkov e078bd6bf2 Fix S3 URL construct process 2020-09-11 16:49:30 +03:00
GennadySpb 2837d56885 Check for error after runner completes (#9925) 2020-09-11 11:14:49 +02:00
Megan Marsh 5ca9f6539f remove xen from qemu docs, since it turns out it has not been supported with solely qemu for some time (#9917) 2020-09-09 19:42:37 -04:00
Megan Marsh 8b9009c571 Merge pull request #9856 from OblateSpheroid/add_vnic_details
Feat (builder/oracle-oci): Add VNIC details to launch details
2020-09-09 16:15:48 -07:00
Megan Marsh 8ffa0d2060 Merge pull request #9842 from jason-azze/patch-1
Builders vsphere-iso and vsphere-clone - CPUs is actually for cores, not sockets.
2020-09-09 16:03:45 -07:00
Megan Marsh ca650c9602 fix struct comment for generation 2020-09-09 16:01:30 -07:00
Megan Marsh 25b889216d update changelog 2020-09-09 15:07:27 -07:00
Megan Marsh 81f9f4dd27 Merge pull request #9911 from hashicorp/ssh_redocument_options
Ssh redocument options
2020-09-09 08:44:32 -07:00
Megan Marsh 24d5824061 more docs update to add ssh private key file to all the builders that have them built into the create ssh key step 2020-09-09 08:33:38 -07:00
Megan Marsh ad0b413e2d add private key file docs to qemu builder 2020-09-09 07:41:16 -07:00
js-g 9d5dedc3db tests: add some fields to config_test 2020-09-09 09:25:58 -04:00
Patrik f8e31bff12 feat(builder): bump scaleway to new sdk (#9902) 2020-09-09 10:27:48 +02:00
Wilken Rivera 0719f906d4 Replace if/else with simple if (#9913) 2020-09-09 10:24:17 +02:00
Megan Marsh 8aaa1bd7a6 Merge pull request #9796 from hashicorp/do_8778
create secondary iso containing user's desired files
2020-09-08 15:05:48 -07:00
Kevin Pruett 3a5beedddf Merge pull request #9914 from hashicorp/pruett.update-search
Integrate @hashicorp/react-search/tools script
2020-09-08 17:24:41 -04:00
Kevin Pruett e5cd5a9360 Integrate @hashicorp/react-search/tools script 2020-09-08 17:16:42 -04:00
Megan Marsh 26f3299fb7 Merge pull request #9903 from artis3n/ansible-collections
Ansible Galaxy Collections support in Ansible provisioner
2020-09-08 12:34:39 -07:00
Megan Marsh 40c128767e regenerate docs 2020-09-08 12:17:58 -07:00
js-g dd54651125 fix (builder/oracle-oci): add freeform and defined tags to create_vnic_details 2020-09-08 14:40:21 -04:00
Richard Turc b4ff0ea4bc [builder/qemu] Skip resize step when skip_resize_disk is enable #9860 (#9896)
* [builder/qemu] Skip resize step when skip_resize_disk is enable #9860

* Update builder/qemu/builder_test.go

Improve the code quality

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>

* Update files for unit tests

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2020-09-08 14:32:08 -04:00
Megan Marsh 988ab66731 Update common/extra_iso_config.go
thanks for the copyediting :D

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2020-09-08 11:18:10 -07:00
Megan Marsh 900100f591 Update website/pages/docs/builders/hyperv/iso.mdx
Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2020-09-08 11:17:20 -07:00
js-g 254716a02a docs (oracle-oci): add description and example for create_vnic_details 2020-09-08 13:53:19 -04:00
js-g f5dc260cf5 refactor (builder/oracle-oci): pass CreateVnicDetails as struct instead of map 2020-09-08 12:03:18 -04:00
Wilken Rivera 2da89db272 Merge pull request #9905 from The-Loeki/patch-2
qemu vnc: hardcoded lowerbound leaves negative ports
2020-09-08 10:17:53 -04:00
Ronald van Zantvoort e362b7f43f build only only twice (#9904)
remove duplicate entry for build > --only
2020-09-07 11:56:28 +02:00
Ronald van Zantvoort 7335695c84 qemu vnc: hardcoded lowerbound leaves negative ports
```hcl2
  vnc_port_min = 5000
  vnc_port_max = 5500
```

Build returns faulty:
```
    qemu.one: view the screen of the VM, connect via VNC to vnc://127.0.0.1:5138
(...)
2020/09/06 19:09:52 packer-builder-qemu plugin: Qemu stderr: qemu-system-x86_64: -vnc 127.0.0.1:-762,password: can't convert to a number: -762
```
2020-09-06 21:18:12 +02:00
Artis3n 4ecade3908 collectionsPath appends to the correct slice
Fixes what would have been a failure if roles+collections were tested at
once
2020-09-05 18:43:10 -04:00
Artis3n 39183d1b76 Updated documentation with make generate 2020-09-05 18:29:52 -04:00
Artis3n 9a2908d1d9 Use the galaxy playbook in the galaxy packer file 2020-09-05 18:15:42 -04:00
Artis3n 611899f7b2 Actually use the collectionArgs, that would be helpful 2020-09-05 16:51:38 -04:00
Artis3n 41a8a96821 Added acceptance test for Galaxy Collection install 2020-09-05 16:48:20 -04:00
Artis3n 3d2259a6d4 Typo fix in comment 2020-09-05 16:19:05 -04:00
Artis3n 19f0114035 Added support for Ansible Collections to the Galaxy install
Fixes #8821
2020-09-05 16:10:02 -04:00
Megan Marsh 94a9beaac6 fix up documentation for virtualbox key pair ssh options 2020-09-04 16:55:58 -07:00
Megan Marsh 61af278b79 document previously undocumented ssh options for tencent cloud 2020-09-04 16:50:45 -07:00
Megan Marsh bd2aa40d2c document previously undocumented ssh options for scaleway 2020-09-04 16:50:31 -07:00
Megan Marsh c7356fb776 document previously undocumented ssh options for outscale 2020-09-04 16:49:48 -07:00
Megan Marsh 850fda6aa1 document previously undocumented ssh options for oracle classic 2020-09-04 16:49:31 -07:00
Megan Marsh f3633da200 document previously undocumented ssh options for jdcloud 2020-09-04 16:49:12 -07:00
Megan Marsh 00c6efd5cf document previously undocumented ssh options for cloudstack 2020-09-04 16:48:47 -07:00
Megan Marsh bca4439149 add undocumented ssh options to alicloud builder docs 2020-09-04 16:48:05 -07:00
Megan Marsh d2dd158fe2 Implement cd_files option. The Addfile code is modified from the floppy_files
tooling. Tested on OSX, `Windows, and Linux. Documented command line dependencies.
Added acceptance tests (behind testacc flag, since there are dependencies).
This option is only implemented for the vmware and hyperv iso builders. It will be
implemented for others in later PRs in order to make review easier.
2020-09-04 12:20:19 -07:00
Megan Marsh dfe8aa51b0 Merge pull request #9845 from hashicorp/run_and_stream_helper_func
move runAndStream into a generalized helper function to enable easy l…
2020-09-04 12:19:37 -07:00
Megan Marsh 5120ad9d9a add test file 2020-09-04 11:56:32 -07:00
Megan Marsh 20d62654ae move runAndStream into a generalized helper function to enable easy local exectution in other places than just the docker builder 2020-09-04 11:56:32 -07:00
Kevin Pruett bce6fdf03a Merge pull request #9870 from hashicorp/pruett.integrate-search
Integrate @hashicorp/react-search into layout
2020-09-04 10:52:20 -04:00
Lars Lehtonen 03a0beb683 builder/vmware/common: simplify type assertion switch (#9893) 2020-09-04 16:25:19 +02:00
Wilken Rivera 6334fc2da4 Merge pull request #9891 from hashicorp/d-ssh-communicator-supported-opts
communicator/ssh: Add note for builder specific SSH supported options
2020-09-03 13:51:18 -04:00
Wilken Rivera 7813ad5ef4 communicator/ssh: Add note for builder specific SSH supported options 2020-09-03 13:31:47 -04:00
Sylvia Moss ba74602d63 Allows publishing to a Shared Image Gallery with a different subscription id (#9875) 2020-09-03 17:13:32 +02:00
Kevin Pruett 6c4e9e9e68 Integrate @hashicorp/react-search into layout 2020-09-03 11:08:09 -04:00
Wilken Rivera d66f85e375 Merge pull request #9887 from catsby/no-more-catsby
Remove comments about sharing with catsby
2020-09-03 10:20:37 -04:00
catsby 7a7d3ff350 Remove comments about sharing with catsby 2020-09-03 09:14:40 -05:00
Sylvia Moss 62e1663846 update build command with bigger max_old_space_size (#9882) 2020-09-03 11:48:54 +02:00
Troy Lindsay d1a61cac33 Add NVMe controller support to vsphere-iso builder (#9880) 2020-09-03 11:14:58 +02:00
Troy Lindsay 602729ac23 Fix vsphere-iso acceptance tests (#9878) (#9881) 2020-09-03 10:17:49 +02:00
Wilken Rivera c492199ba5 Merge pull request #9877 from hashicorp/rae/move-aws-id-to-env-var
update sharing test to req aws account id
2020-09-02 15:57:50 -04:00
Rae Krantz ab23e839c8 update sharing test to req aws account id 2020-09-02 11:44:38 -04:00
Adrien Delorme efd69aea2a console: allow to use console when there are untyped vars (#9864)
* the var will simply be "unknown"
* add a test
2020-09-02 17:19:56 +02:00
Adrien Delorme a70164f489 HCL2: when the type of a variable is not known evaluate setting as a litteral. (#9863)
* tests
* docs
2020-09-02 16:26:50 +02:00
js-g f74a425661 feat (builder/oracle-oci): pass CreateVnicDetails to instance 2020-08-31 13:13:33 -04:00
Jason Azze 9156563314 Update HardwareConfig-not-required.mdx
I've tested the behavior of CPUs and cpu_cores against both vSphere 5.5 and 6.7. In both cases, CPUs gives you virtual cores, not sockets.
For example, I want 6 cores per socket across 2 sockets for 12 total cores. Based on the wording of this doc, I set CPUs to 2 and cpu_cores to 6. The documentation implies that will give me 2 sockets with 6 cores each. The actual behavior is you get 2 cores, and when you crack open the VMs configuration, you see that cores per socket is set to 6 -- which is meaningless.

Setting CPUs to 12 and cpu_cores to 6 gives me what I wanted. So the wording I propose is 
```
- `CPUs` (int32) - Number of CPU cores.
```
2020-08-27 15:10:33 -04:00
279 changed files with 13491 additions and 14468 deletions
+35
View File
@@ -1,5 +1,40 @@
## 1.6.3 (Upcoming)
### IMPROVEMENTS:
* builder/azure: Support publishing to a Shared Image Gallery with a different
subscription id [GH-9875]
* builder/oracle-oci: Allow freeform and defined tags to be added instance.
[GH-9802]
* builder/proxmox: Allow the mounting of multiple ISOs via the `cd_drive`
option. [GH-9653]
* builder/qemu: New `skip_resize_disk` option. [GH-9896]
* builder/scaleway: Change default boottype to local. [GH-9853]
* builder/scaleway: Update scaleway to use non-deprecated sdk. [GH-9902]
* builder/vsphere-iso: Add NVMe controller support. [GH-9880]
* builder/vsphere: Look for a default resource pool when root resource pool is
not found. [GH-9809]
* core: New `cd_files` option to mount iso for modern OSes which don't support
floppies. [GH-9796] [GH-9919]
* HCL2: When the type of a variable is not known evaluate setting as a literal
string instead of a variable name. [GH-9863]
* post-processor/yandex-import: Allow custom API endpoint. [GH-9850]
* provisioner/ansible: Add support for Ansible Galaxy Collections. [GH-9903]
### BUG FIXES:
* builder/qemu: Fix hardcoded lowerbound causing negative ports [GH-9905]
* builder/vsphere: Fix overly strict iso_path validation regex. [GH-9855]
* command/console: Prevent failure when there are unknown vars. [GH-9864]
* command/inspect: Allow unset variables in HCL2 and JSON. [GH-9832]
* core: use $APPDATA over $HOME on Windows hosts when determining homedir.
[GH-9830]
* post-processor/digitalocean-import: Fix crash caused by empty artifact.Files
slice. [GH-9857]
* post-processor/yandex-export: Set metadata key to expected value on error.
[GH-9849]
* post-processor/yandex-export: Check for error after runner completes.
[GH-9925]
* post-processor/yandex-import: Fix S3 URL construct process. [GH-9931]
## 1.6.2 (August 28, 2020)
### FEATURES:
-1
View File
@@ -422,7 +422,6 @@ func TestBuilderAcc_ECSImageSharing(t *testing.T) {
})
}
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
+15 -5
View File
@@ -6,6 +6,7 @@ package ebs
import (
"fmt"
"os"
"testing"
"github.com/aws/aws-sdk-go/aws"
@@ -104,10 +105,10 @@ func checkSnapshotsDeleted(snapshotIds []*string) builderT.TestCheckFunc {
func TestBuilderAcc_amiSharing(t *testing.T) {
builderT.Test(t, builderT.TestCase{
PreCheck: func() { testAccPreCheck(t) },
PreCheck: func() { testAccSharingPreCheck(t) },
Builder: &Builder{},
Template: testBuilderAccSharing,
Check: checkAMISharing(2, "932021504756", "all"),
Template: buildSharingConfig(os.Getenv("TESTACC_AWS_ACCOUNT_ID")),
Check: checkAMISharing(2, os.Getenv("TESTACC_AWS_ACCOUNT_ID"), "all"),
})
}
@@ -248,6 +249,12 @@ func checkBootEncrypted() builderT.TestCheckFunc {
func testAccPreCheck(t *testing.T) {
}
func testAccSharingPreCheck(t *testing.T) {
if v := os.Getenv("TESTACC_AWS_ACCOUNT_ID"); v == "" {
t.Fatal(fmt.Sprintf("TESTACC_AWS_ACCOUNT_ID must be set for acceptance tests"))
}
}
func testEC2Conn() (*ec2.EC2, error) {
access := &common.AccessConfig{RawRegion: "us-east-1"}
session, err := access.Session()
@@ -314,7 +321,6 @@ const testBuilderAccForceDeleteSnapshot = `
}
`
// share with catsby
const testBuilderAccSharing = `
{
"builders": [{
@@ -324,7 +330,7 @@ const testBuilderAccSharing = `
"source_ami": "ami-76b2a71e",
"ssh_username": "ubuntu",
"ami_name": "packer-test {{timestamp}}",
"ami_users":["932021504756"],
"ami_users":["%s"],
"ami_groups":["all"]
}]
}
@@ -351,3 +357,7 @@ func buildForceDeregisterConfig(val, name string) string {
func buildForceDeleteSnapshotConfig(val, name string) string {
return fmt.Sprintf(testBuilderAccForceDeleteSnapshot, val, val, name)
}
func buildSharingConfig(val string) string {
return fmt.Sprintf(testBuilderAccSharing, val)
}
+20 -18
View File
@@ -128,8 +128,8 @@ func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.Respon
}
}
func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, SharedGalleryTimeout time.Duration, PollingDuration time.Duration,
func NewAzureClient(subscriptionID, sigSubscriptionID, resourceGroupName, storageAccountName string,
cloud *azure.Environment, sharedGalleryTimeout time.Duration, pollingDuration time.Duration,
servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) {
var azureClient = &AzureClient{}
@@ -141,56 +141,56 @@ 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.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.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.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.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.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.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.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.VirtualNetworksClient.Client.PollingDuration = pollingDuration
azureClient.SecurityGroupsClient = network.NewSecurityGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
@@ -203,42 +203,44 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string
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.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.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.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.AccountsClient.Client.PollingDuration = pollingDuration
azureClient.GalleryImageVersionsClient = newCompute.NewGalleryImageVersionsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImageVersionsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
azureClient.GalleryImageVersionsClient.RequestInspector = withInspection(maxlen)
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GalleryImageVersionsClient.UserAgent)
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
azureClient.GalleryImageVersionsClient.Client.PollingDuration = sharedGalleryTimeout
azureClient.GalleryImageVersionsClient.SubscriptionID = sigSubscriptionID
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
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.GalleryImagesClient.Client.PollingDuration = PollingDuration
azureClient.GalleryImagesClient.Client.PollingDuration = pollingDuration
azureClient.GalleryImagesClient.SubscriptionID = sigSubscriptionID
keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
if err != nil {
@@ -250,7 +252,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
azureClient.VaultClient.Client.PollingDuration = pollingDuration
// This client is different than the above because it manages the vault
// itself rather than the contents of the vault.
@@ -259,7 +261,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
azureClient.VaultClientDelete.Client.PollingDuration = pollingDuration
// If this is a managed disk build, this should be ignored.
if resourceGroupName != "" && storageAccountName != "" {
+1
View File
@@ -84,6 +84,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
ui.Message("Creating Azure Resource Manager (ARM) client ...")
azureClient, err := NewAzureClient(
b.config.ClientConfig.SubscriptionID,
b.config.SharedGalleryDestination.SigDestinationSubscription,
b.config.ResourceGroupName,
b.config.StorageAccount,
b.config.ClientConfig.CloudEnvironment(),
+56 -19
View File
@@ -90,6 +90,7 @@ type SharedImageGallery struct {
}
type SharedImageGalleryDestination struct {
SigDestinationSubscription string `mapstructure:"subscription"`
SigDestinationResourceGroup string `mapstructure:"resource_group"`
SigDestinationGalleryName string `mapstructure:"gallery_name"`
SigDestinationImageName string `mapstructure:"image_name"`
@@ -118,31 +119,64 @@ type Config struct {
// Use a [Shared Gallery
// image](https://azure.microsoft.com/en-us/blog/announcing-the-public-preview-of-shared-image-gallery/)
// as the source for this build. *VHD targets are incompatible with this
// build type* - the target must be a *Managed Image*.
// build type* - the target must be a *Managed Image*. When using shared_image_gallery as a source, image_publisher,
// image_offer, image_sku, image_version, and custom_managed_image_name should not be set.
//
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// In JSON
// ```json
// "shared_image_gallery": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0"
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
SharedGallery SharedImageGallery `mapstructure:"shared_image_gallery" required:"false"`
// The name of the Shared Image Gallery under which the managed image will be published as Shared Gallery Image version.
//
// Following is an example.
//
// "shared_image_gallery_destination": {
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"]
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// In JSON
// ```json
// "shared_image_gallery_destination": {
// "subscription": "00000000-0000-0000-0000-00000000000",
// "resource_group": "ResourceGroup",
// "gallery_name": "GalleryName",
// "image_name": "ImageName",
// "image_version": "1.0.0",
// "replication_regions": ["regionA", "regionB", "regionC"]
// }
// "managed_image_name": "TargetImageName",
// "managed_image_resource_group_name": "TargetResourceGroup"
// ```
// In HCL2
// ```hcl
// shared_image_gallery_destination {
// subscription = "00000000-0000-0000-0000-00000000000"
// resource_group = "ResourceGroup"
// gallery_name = "GalleryName"
// image_name = "ImageName"
// image_version = "1.0.0"
// replication_regions = ["regionA", "regionB", "regionC"]
// }
// managed_image_name = "TargetImageName"
// managed_image_resource_group_name = "TargetResourceGroup"
// ```
SharedGalleryDestination SharedImageGalleryDestination `mapstructure:"shared_image_gallery_destination"`
// How long to wait for an image to be published to the shared image
// gallery before timing out. If your Packer build is failing on the
@@ -978,6 +1012,9 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) {
if len(c.SharedGalleryDestination.SigDestinationReplicationRegions) == 0 {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A list of replication_regions must be specified for shared_image_gallery_destination"))
}
if c.SharedGalleryDestination.SigDestinationSubscription == "" {
c.SharedGalleryDestination.SigDestinationSubscription = c.ClientConfig.SubscriptionID
}
}
if c.SharedGalleryTimeout == 0 {
// default to a one-hour timeout. In the sdk, the default is 15 m.
+2
View File
@@ -311,6 +311,7 @@ func (*FlatSharedImageGallery) HCL2Spec() map[string]hcldec.Spec {
// 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 {
SigDestinationSubscription *string `mapstructure:"subscription" cty:"subscription" hcl:"subscription"`
SigDestinationResourceGroup *string `mapstructure:"resource_group" cty:"resource_group" hcl:"resource_group"`
SigDestinationGalleryName *string `mapstructure:"gallery_name" cty:"gallery_name" hcl:"gallery_name"`
SigDestinationImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
@@ -330,6 +331,7 @@ func (*SharedImageGalleryDestination) FlatMapstructure() interface{ HCL2Spec() m
// The decoded values from this spec will then be applied to a FlatSharedImageGalleryDestination.
func (*FlatSharedImageGalleryDestination) 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},
+2
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -132,6 +133,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", 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},
+5 -93
View File
@@ -1,114 +1,26 @@
package docker
import (
"fmt"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"github.com/hashicorp/packer/common/iochan"
"github.com/hashicorp/packer/helper/builder/localexec"
"github.com/hashicorp/packer/packer"
)
func runAndStream(cmd *exec.Cmd, ui packer.Ui) error {
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
defer stderr_w.Close()
args := make([]string, len(cmd.Args)-1)
copy(args, cmd.Args[1:])
// Scrub password from the log output.
capturedPassword := ""
for i, v := range args {
if v == "-p" || v == "--password" {
args[i+1] = "<Filtered>"
capturedPassword = args[i+1]
break
}
}
log.Printf("Executing: %s %v", cmd.Path, args)
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
if err := cmd.Start(); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan int, 1)
stdoutCh := iochan.LineReader(stdout_r)
stderrCh := iochan.LineReader(stderr_r)
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
exitStatus := 0
err := cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
exitCh <- exitStatus
}()
// This waitgroup waits for the streaming to end
var streamWg sync.WaitGroup
streamWg.Add(2)
streamFunc := func(ch <-chan string) {
defer streamWg.Done()
for data := range ch {
data = cleanOutputLine(data)
if data != "" {
ui.Message(data)
}
}
}
// Stream stderr/stdout
go streamFunc(stderrCh)
go streamFunc(stdoutCh)
// Wait for the process to end and then wait for the streaming to end
exitStatus := <-exitCh
streamWg.Wait()
if exitStatus != 0 {
return fmt.Errorf("Bad exit status: %d", exitStatus)
}
return nil
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func cleanOutputLine(line string) string {
// Build a regular expression that will get rid of shell codes
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
line = re.ReplaceAllString(line, "")
// Trim surrounding whitespace
line = strings.TrimSpace(line)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
// run local command and stream output to UI.
return localexec.RunAndStream(cmd, ui, []string{capturedPassword})
}
+3 -2
View File
@@ -37,6 +37,7 @@ const (
type CommonConfig struct {
common.FloppyConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
// The block size of the VHD to be created.
// Recommended disk block size for Linux hyper-v guests is 1 MiB. This
// defaults to "32" MiB.
@@ -210,8 +211,8 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig
}
// Errors
floppyerrs := c.FloppyConfig.Prepare(ctx)
errs = append(errs, floppyerrs...)
errs = append(errs, c.FloppyConfig.Prepare(ctx)...)
errs = append(errs, c.CDConfig.Prepare(ctx)...)
if c.GuestAdditionsMode == "" {
if c.GuestAdditionsPath != "" {
c.GuestAdditionsMode = "attach"
@@ -34,7 +34,17 @@ func (s *StepMountSecondaryDvdImages) Run(ctx context.Context, state multistep.S
// For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1)
var dvdProperties []DvdControllerProperties
for _, isoPath := range s.IsoPaths {
isoPaths := s.IsoPaths
// Add our custom CD, if it exists
cd_path, ok := state.Get("cd_path").(string)
if ok {
if cd_path != "" {
isoPaths = append(isoPaths, cd_path)
}
}
for _, isoPath := range isoPaths {
var properties DvdControllerProperties
controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation)
+4 -1
View File
@@ -254,7 +254,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
GuestAdditionsPath: b.config.GuestAdditionsPath,
Generation: b.config.Generation,
},
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&hypervcommon.StepMountSecondaryDvdImages{
IsoPaths: b.config.SecondaryDvdImages,
Generation: b.config.Generation,
+6
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -79,6 +80,8 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"`
RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"`
@@ -136,6 +139,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -195,6 +199,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false},
+6
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -79,6 +80,8 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"`
RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"`
@@ -138,6 +141,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -197,6 +201,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false},
+17 -3
View File
@@ -1,4 +1,4 @@
//go:generate mapstructure-to-hcl2 -type Config
//go:generate mapstructure-to-hcl2 -type Config,CreateVNICDetails
package oci
@@ -22,6 +22,19 @@ import (
ociauth "github.com/oracle/oci-go-sdk/common/auth"
)
type CreateVNICDetails struct {
// fields that can be specified under "create_vnic_details"
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false"`
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false"`
DisplayName *string `mapstructure:"display_name" required:"false"`
FreeformTags map[string]string `mapstructure:"tags" required:"false"`
HostnameLabel *string `mapstructure:"hostname_label" required:"false"`
NsgIds []string `mapstructure:"nsg_ids" required:"false"`
PrivateIp *string `mapstructure:"private_ip" required:"false"`
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false"`
SubnetId *string `mapstructure:"subnet_id" required:"false"`
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
@@ -57,13 +70,13 @@ type Config struct {
// Image
BaseImageID string `mapstructure:"base_image_ocid"`
Shape string `mapstructure:"shape"`
ImageName string `mapstructure:"image_name"`
// Instance
InstanceName string `mapstructure:"instance_name"`
InstanceTags map[string]string `mapstructure:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags"`
Shape string `mapstructure:"shape"`
// Metadata optionally contains custom metadata key/value pairs provided in the
// configuration. While this can be used to set metadata["user_data"] the explicit
@@ -77,7 +90,8 @@ type Config struct {
UserDataFile string `mapstructure:"user_data_file"`
// Networking
SubnetID string `mapstructure:"subnet_ocid"`
SubnetID string `mapstructure:"subnet_ocid"`
CreateVnicDetails CreateVNICDetails `mapstructure:"create_vnic_details"`
// Tagging
Tags map[string]string `mapstructure:"tags"`
+44 -3
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type Config"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type Config,CreateVNICDetails"; DO NOT EDIT.
package oci
import (
@@ -76,15 +76,16 @@ type FlatConfig struct {
AvailabilityDomain *string `mapstructure:"availability_domain" cty:"availability_domain" hcl:"availability_domain"`
CompartmentID *string `mapstructure:"compartment_ocid" cty:"compartment_ocid" hcl:"compartment_ocid"`
BaseImageID *string `mapstructure:"base_image_ocid" cty:"base_image_ocid" hcl:"base_image_ocid"`
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
ImageName *string `mapstructure:"image_name" cty:"image_name" hcl:"image_name"`
InstanceName *string `mapstructure:"instance_name" cty:"instance_name" hcl:"instance_name"`
InstanceTags map[string]string `mapstructure:"instance_tags" cty:"instance_tags" hcl:"instance_tags"`
InstanceDefinedTags map[string]map[string]interface{} `mapstructure:"instance_defined_tags" cty:"instance_defined_tags" hcl:"instance_defined_tags"`
Shape *string `mapstructure:"shape" cty:"shape" hcl:"shape"`
Metadata map[string]string `mapstructure:"metadata" cty:"metadata" hcl:"metadata"`
UserData *string `mapstructure:"user_data" cty:"user_data" hcl:"user_data"`
UserDataFile *string `mapstructure:"user_data_file" cty:"user_data_file" hcl:"user_data_file"`
SubnetID *string `mapstructure:"subnet_ocid" cty:"subnet_ocid" hcl:"subnet_ocid"`
CreateVnicDetails *FlatCreateVNICDetails `mapstructure:"create_vnic_details" cty:"create_vnic_details" hcl:"create_vnic_details"`
Tags map[string]string `mapstructure:"tags" cty:"tags" hcl:"tags"`
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" cty:"defined_tags" hcl:"defined_tags"`
}
@@ -168,17 +169,57 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"availability_domain": &hcldec.AttrSpec{Name: "availability_domain", Type: cty.String, Required: false},
"compartment_ocid": &hcldec.AttrSpec{Name: "compartment_ocid", Type: cty.String, Required: false},
"base_image_ocid": &hcldec.AttrSpec{Name: "base_image_ocid", Type: cty.String, Required: false},
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
"instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false},
"instance_tags": &hcldec.AttrSpec{Name: "instance_tags", Type: cty.Map(cty.String), Required: false},
"instance_defined_tags": &hcldec.AttrSpec{Name: "instance_defined_tags", Type: cty.Map(cty.String), Required: false},
"shape": &hcldec.AttrSpec{Name: "shape", Type: cty.String, Required: false},
"metadata": &hcldec.AttrSpec{Name: "metadata", Type: cty.Map(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},
"subnet_ocid": &hcldec.AttrSpec{Name: "subnet_ocid", Type: cty.String, Required: false},
"create_vnic_details": &hcldec.BlockSpec{TypeName: "create_vnic_details", Nested: hcldec.ObjectSpec((*FlatCreateVNICDetails)(nil).HCL2Spec())},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"defined_tags": &hcldec.AttrSpec{Name: "defined_tags", Type: cty.Map(cty.String), Required: false},
}
return s
}
// FlatCreateVNICDetails is an auto-generated flat version of CreateVNICDetails.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatCreateVNICDetails struct {
AssignPublicIp *bool `mapstructure:"assign_public_ip" required:"false" cty:"assign_public_ip" hcl:"assign_public_ip"`
DefinedTags map[string]map[string]interface{} `mapstructure:"defined_tags" required:"false" cty:"defined_tags" hcl:"defined_tags"`
DisplayName *string `mapstructure:"display_name" required:"false" cty:"display_name" hcl:"display_name"`
FreeformTags map[string]string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
HostnameLabel *string `mapstructure:"hostname_label" required:"false" cty:"hostname_label" hcl:"hostname_label"`
NsgIds []string `mapstructure:"nsg_ids" required:"false" cty:"nsg_ids" hcl:"nsg_ids"`
PrivateIp *string `mapstructure:"private_ip" required:"false" cty:"private_ip" hcl:"private_ip"`
SkipSourceDestCheck *bool `mapstructure:"skip_source_dest_check" required:"false" cty:"skip_source_dest_check" hcl:"skip_source_dest_check"`
SubnetId *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"`
}
// FlatMapstructure returns a new FlatCreateVNICDetails.
// FlatCreateVNICDetails is an auto-generated flat version of CreateVNICDetails.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*CreateVNICDetails) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatCreateVNICDetails)
}
// HCL2Spec returns the hcl spec of a CreateVNICDetails.
// This spec is used by HCL to read the fields of CreateVNICDetails.
// The decoded values from this spec will then be applied to a FlatCreateVNICDetails.
func (*FlatCreateVNICDetails) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"assign_public_ip": &hcldec.AttrSpec{Name: "assign_public_ip", Type: cty.Bool, Required: false},
"defined_tags": &hcldec.AttrSpec{Name: "defined_tags", Type: cty.Map(cty.String), Required: false},
"display_name": &hcldec.AttrSpec{Name: "display_name", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.Map(cty.String), Required: false},
"hostname_label": &hcldec.AttrSpec{Name: "hostname_label", Type: cty.String, Required: false},
"nsg_ids": &hcldec.AttrSpec{Name: "nsg_ids", Type: cty.List(cty.String), Required: false},
"private_ip": &hcldec.AttrSpec{Name: "private_ip", Type: cty.String, Required: false},
"skip_source_dest_check": &hcldec.AttrSpec{Name: "skip_source_dest_check", Type: cty.Bool, Required: false},
"subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false},
}
return s
}
+10 -1
View File
@@ -21,7 +21,6 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
// Image
"base_image_ocid": "ocd1...",
"shape": "VM.Standard1.1",
"image_name": "HelloWorld",
// Networking
@@ -36,6 +35,16 @@ func testConfig(accessConfFile *os.File) map[string]interface{} {
"defined_tags": map[string]map[string]interface{}{
"namespace": {"key": "value"},
},
// Instance Details
"instance_name": "hello-world",
"instance_tags": map[string]string{
"key": "value",
},
"create_vnic_details": map[string]interface{}{
"nsg_ids": []string{"ocd1..."},
},
"shape": "VM.Standard1.1",
}
}
+15
View File
@@ -67,6 +67,21 @@ func (d *driverOCI) CreateInstance(ctx context.Context, publicKey string) (strin
instanceDetails.DisplayName = &d.cfg.InstanceName
}
// Pass VNIC details, if specified, to the instance
CreateVnicDetails := core.CreateVnicDetails{
AssignPublicIp: d.cfg.CreateVnicDetails.AssignPublicIp,
DisplayName: d.cfg.CreateVnicDetails.DisplayName,
HostnameLabel: d.cfg.CreateVnicDetails.HostnameLabel,
NsgIds: d.cfg.CreateVnicDetails.NsgIds,
PrivateIp: d.cfg.CreateVnicDetails.PrivateIp,
SkipSourceDestCheck: d.cfg.CreateVnicDetails.SkipSourceDestCheck,
SubnetId: d.cfg.CreateVnicDetails.SubnetId,
DefinedTags: d.cfg.CreateVnicDetails.DefinedTags,
FreeformTags: d.cfg.CreateVnicDetails.FreeformTags,
}
instanceDetails.CreateVnicDetails = &CreateVnicDetails
instance, err := d.computeClient.LaunchInstance(context.TODO(), core.LaunchInstanceRequest{LaunchInstanceDetails: instanceDetails})
if err != nil {
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -123,6 +124,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
+77 -28
View File
@@ -2,6 +2,7 @@ package proxmox
import (
"fmt"
"strings"
"time"
"unicode"
@@ -10,23 +11,33 @@ import (
)
type proxmoxDriver struct {
client commandTyper
vmRef *proxmox.VmRef
specialMap map[string]string
runeMap map[rune]string
interval time.Duration
client commandTyper
vmRef *proxmox.VmRef
specialMap map[string]string
runeMap map[rune]string
interval time.Duration
specialBuffer []string
normalBuffer []string
}
func NewProxmoxDriver(c commandTyper, vmRef *proxmox.VmRef, interval time.Duration) *proxmoxDriver {
// Mappings for packer shorthand to qemu qkeycodes
sMap := map[string]string{
"spacebar": "spc",
"bs": "backspace",
"del": "delete",
"return": "ret",
"enter": "ret",
"pageUp": "pgup",
"pageDown": "pgdn",
"spacebar": "spc",
"bs": "backspace",
"del": "delete",
"return": "ret",
"enter": "ret",
"pageUp": "pgup",
"pageDown": "pgdn",
"leftshift": "shift",
"rightshift": "shift",
"leftalt": "alt",
"rightalt": "alt_r",
"leftctrl": "ctrl",
"rightctrl": "ctrl_r",
"leftsuper": "meta_l",
"rightsuper": "meta_r",
}
// Mappings for runes that need to be translated to special qkeycodes
// Taken from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps/en-us
@@ -78,18 +89,26 @@ func NewProxmoxDriver(c commandTyper, vmRef *proxmox.VmRef, interval time.Durati
}
func (p *proxmoxDriver) SendKey(key rune, action bootcommand.KeyAction) error {
if special, ok := p.runeMap[key]; ok {
return p.send(special)
switch action.String() {
case "Press":
if special, ok := p.runeMap[key]; ok {
return p.send(special)
}
var keys string
if unicode.IsUpper(key) {
keys = fmt.Sprintf("shift-%c", unicode.ToLower(key))
} else {
keys = fmt.Sprintf("%c", key)
}
return p.send(keys)
case "On":
key := fmt.Sprintf("%c", key)
p.normalBuffer = addKeyToBuffer(p.normalBuffer, key)
case "Off":
key := fmt.Sprintf("%c", key)
p.normalBuffer = removeKeyFromBuffer(p.normalBuffer, key)
}
var keys string
if unicode.IsUpper(key) {
keys = fmt.Sprintf("shift-%c", unicode.ToLower(key))
} else {
keys = fmt.Sprintf("%c", key)
}
return p.send(keys)
return nil
}
func (p *proxmoxDriver) SendSpecial(special string, action bootcommand.KeyAction) error {
@@ -97,18 +116,48 @@ func (p *proxmoxDriver) SendSpecial(special string, action bootcommand.KeyAction
if replacement, ok := p.specialMap[special]; ok {
keys = replacement
}
return p.send(keys)
switch action.String() {
case "Press":
return p.send(keys)
case "On":
p.specialBuffer = addKeyToBuffer(p.specialBuffer, keys)
case "Off":
p.specialBuffer = removeKeyFromBuffer(p.specialBuffer, keys)
}
return nil
}
func (p *proxmoxDriver) send(keys string) error {
err := p.client.Sendkey(p.vmRef, keys)
func (p *proxmoxDriver) send(key string) error {
keys := append(p.specialBuffer, p.normalBuffer...)
keys = append(keys, key)
keyEventString := bufferToKeyEvent(keys)
err := p.client.Sendkey(p.vmRef, keyEventString)
if err != nil {
return err
}
time.Sleep(p.interval)
return nil
}
func (p *proxmoxDriver) Flush() error { return nil }
func bufferToKeyEvent(keys []string) string {
return strings.Join(keys, "-")
}
func addKeyToBuffer(buffer []string, key string) []string {
for _, value := range buffer {
if value == key {
return buffer
}
}
return append(buffer, key)
}
func removeKeyFromBuffer(buffer []string, key string) []string {
for index, value := range buffer {
if value == key {
buffer[index] = buffer[len(buffer)-1]
return buffer[:len(buffer)-1]
}
}
return buffer
}
+20 -4
View File
@@ -152,16 +152,32 @@ func commHost(host string) func(state multistep.StateBag) (string, error) {
// Reads the first non-loopback interface's IP address from the VM.
// qemu-guest-agent package must be installed on the VM
func getVMIP(state multistep.StateBag) (string, error) {
c := state.Get("proxmoxClient").(*proxmox.Client)
client := state.Get("proxmoxClient").(*proxmox.Client)
config := state.Get("config").(*Config)
vmRef := state.Get("vmRef").(*proxmox.VmRef)
ifs, err := c.GetVmAgentNetworkInterfaces(vmRef)
ifs, err := client.GetVmAgentNetworkInterfaces(vmRef)
if err != nil {
return "", err
}
// TODO: Do something smarter here? Allow specifying interface? Or address family?
// For now, just go for first non-loopback
if config.VMInterface != "" {
for _, iface := range ifs {
if config.VMInterface != iface.Name {
continue
}
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
continue
}
return addr.String(), nil
}
return "", fmt.Errorf("Interface %s only has loopback addresses", config.VMInterface)
}
return "", fmt.Errorf("Interface %s not found in VM", config.VMInterface)
}
for _, iface := range ifs {
for _, addr := range iface.IPAddresses {
if addr.IsLoopback() {
+1
View File
@@ -66,6 +66,7 @@ type Config struct {
shouldUploadISO bool
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
VMInterface string `mapstructure:"vm_interface"`
ctx interpolate.Context
}
+4
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -104,6 +105,7 @@ type FlatConfig struct {
CloudInit *bool `mapstructure:"cloud_init" cty:"cloud_init" hcl:"cloud_init"`
CloudInitStoragePool *string `mapstructure:"cloud_init_storage_pool" cty:"cloud_init_storage_pool" hcl:"cloud_init_storage_pool"`
AdditionalISOFiles []FlatstorageConfig `mapstructure:"additional_iso_files" cty:"additional_iso_files" hcl:"additional_iso_files"`
VMInterface *string `mapstructure:"vm_interface" cty:"vm_interface" hcl:"vm_interface"`
}
// FlatMapstructure returns a new FlatConfig.
@@ -129,6 +131,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -213,6 +216,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"cloud_init": &hcldec.AttrSpec{Name: "cloud_init", Type: cty.Bool, Required: false},
"cloud_init_storage_pool": &hcldec.AttrSpec{Name: "cloud_init_storage_pool", Type: cty.String, Required: false},
"additional_iso_files": &hcldec.BlockListSpec{TypeName: "additional_iso_files", Nested: hcldec.ObjectSpec((*FlatstorageConfig)(nil).HCL2Spec())},
"vm_interface": &hcldec.AttrSpec{Name: "vm_interface", Type: cty.String, Required: false},
}
return s
}
+31 -13
View File
@@ -53,14 +53,20 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt
}
}
httpIP, err := hostIP()
if err != nil {
err := fmt.Errorf("Failed to determine host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
var httpIP string
var err error
if c.HTTPAddress != "0.0.0.0" {
httpIP = c.HTTPAddress
} else {
httpIP, err = hostIP(c.HTTPInterface)
if err != nil {
err := fmt.Errorf("Failed to determine host IP: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
state.Put("http_ip", httpIP)
s.Ctx.Data = &bootCommandTemplateData{
HTTPIP: httpIP,
@@ -97,12 +103,25 @@ func (s *stepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
func (*stepTypeBootCommand) Cleanup(multistep.StateBag) {}
func hostIP() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
func hostIP(ifname string) (string, error) {
var addrs []net.Addr
var err error
if ifname != "" {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return "", err
}
addrs, err = iface.Addrs()
if err != nil {
return "", err
}
} else {
addrs, err = net.InterfaceAddrs()
if err != nil {
return "", err
}
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
@@ -110,6 +129,5 @@ func hostIP() (string, error) {
}
}
}
return "", errors.New("No host IP found")
}
@@ -52,6 +52,34 @@ func TestTypeBootCommand(t *testing.T) {
expectedKeysSent: "shift-hellospcshift-worldspc2dot0fooshift-1barshift-2baz",
expectedAction: multistep.ActionContinue,
},
{
name: "holding and releasing keys",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<leftShiftOn>hello<rightAltOn>world<leftShiftOff><rightAltOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-hshift-eshift-lshift-lshift-oshift-alt_r-wshift-alt_r-oshift-alt_r-rshift-alt_r-lshift-alt_r-d",
expectedAction: multistep.ActionContinue,
},
{
name: "holding multiple alphabetical keys and shift",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<cOn><leftShiftOn>n<leftShiftOff><cOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-c-n",
expectedAction: multistep.ActionContinue,
},
{
name: "noop keystrokes",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<cOn><leftShiftOn><cOff><leftAltOn><leftShiftOff><leftAltOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "",
expectedAction: multistep.ActionContinue,
},
{
name: "noop keystrokes mixed",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{"<cOn><leftShiftOn><cOff>h<leftShiftOff>"}}},
expectCallSendkey: true,
expectedKeysSent: "shift-h",
expectedAction: multistep.ActionContinue,
},
{
name: "without boot command sendkey should not be called",
builderConfig: &Config{BootConfig: bootcommand.BootConfig{BootCommand: []string{}}},
+9
View File
@@ -130,6 +130,10 @@ type Config struct {
// for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size
// number is provided with no units, Packer will default to Megabytes.
DiskSize string `mapstructure:"disk_size" required:"false"`
// Packer resizes the QCOW2 image using
// qemu-img resize. Set this option to true to disable resizing.
// Defaults to false.
SkipResizeDisk bool `mapstructure:"skip_resize_disk" required:"false"`
// The cache mode to use for disk. Allowed values include any of
// `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By
// default, this is set to `writeback`.
@@ -509,6 +513,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
errs, errors.New("disk_additional_size can only be used when disk_image is false"))
}
if b.config.SkipResizeDisk && !(b.config.DiskImage) {
errs = packer.MultiErrorAppend(
errs, errors.New("skip_resize_disk can only be used when disk_image is true"))
}
if _, ok := accels[b.config.Accelerator]; !ok {
errs = packer.MultiErrorAppend(
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
+4
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -93,6 +94,7 @@ type FlatConfig struct {
CpuCount *int `mapstructure:"cpus" required:"false" cty:"cpus" hcl:"cpus"`
DiskInterface *string `mapstructure:"disk_interface" required:"false" cty:"disk_interface" hcl:"disk_interface"`
DiskSize *string `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
SkipResizeDisk *bool `mapstructure:"skip_resize_disk" required:"false" cty:"skip_resize_disk" hcl:"skip_resize_disk"`
DiskCache *string `mapstructure:"disk_cache" required:"false" cty:"disk_cache" hcl:"disk_cache"`
DiskDiscard *string `mapstructure:"disk_discard" required:"false" cty:"disk_discard" hcl:"disk_discard"`
DetectZeroes *string `mapstructure:"disk_detect_zeroes" required:"false" cty:"disk_detect_zeroes" hcl:"disk_detect_zeroes"`
@@ -145,6 +147,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -218,6 +221,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"cpus": &hcldec.AttrSpec{Name: "cpus", Type: cty.Number, Required: false},
"disk_interface": &hcldec.AttrSpec{Name: "disk_interface", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false},
"skip_resize_disk": &hcldec.AttrSpec{Name: "skip_resize_disk", Type: cty.Bool, Required: false},
"disk_cache": &hcldec.AttrSpec{Name: "disk_cache", Type: cty.String, Required: false},
"disk_discard": &hcldec.AttrSpec{Name: "disk_discard", Type: cty.String, Required: false},
"disk_detect_zeroes": &hcldec.AttrSpec{Name: "disk_detect_zeroes", Type: cty.String, Required: false},
+15
View File
@@ -302,6 +302,21 @@ func TestBuilderPrepare_UseBackingFile(t *testing.T) {
}
}
func TestBuilderPrepare_SkipResizeDisk(t *testing.T) {
config := testConfig()
config["skip_resize_disk"] = true
config["disk_image"] = false
b := Builder{}
_, warns, err := b.Prepare(config)
if len(warns) > 0 {
t.Errorf("unexpected warns when calling prepare with skip_resize_disk set to true: %#v", warns)
}
if err == nil {
t.Errorf("setting skip_resize_disk to true when disk_image is false should have error")
}
}
func TestBuilderPrepare_FloppyFiles(t *testing.T) {
var b Builder
config := testConfig()
+64
View File
@@ -0,0 +1,64 @@
package qemu
import "sync"
type DriverMock struct {
sync.Mutex
StopCalled bool
StopErr error
QemuCalls [][]string
QemuErrs []error
WaitForShutdownCalled bool
WaitForShutdownState bool
QemuImgCalls [][]string
QemuImgErrs []error
VerifyCalled bool
VerifyErr error
VersionCalled bool
VersionResult string
VersionErr error
}
func (d *DriverMock) Stop() error {
d.StopCalled = true
return d.StopErr
}
func (d *DriverMock) Qemu(args ...string) error {
d.QemuCalls = append(d.QemuCalls, args)
if len(d.QemuErrs) >= len(d.QemuCalls) {
return d.QemuErrs[len(d.QemuCalls)-1]
}
return nil
}
func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool {
d.WaitForShutdownCalled = true
return d.WaitForShutdownState
}
func (d *DriverMock) QemuImg(args ...string) error {
d.QemuImgCalls = append(d.QemuImgCalls, args)
if len(d.QemuImgErrs) >= len(d.QemuImgCalls) {
return d.QemuImgErrs[len(d.QemuImgCalls)-1]
}
return nil
}
func (d *DriverMock) Verify() error {
d.VerifyCalled = true
return d.VerifyErr
}
func (d *DriverMock) Version() (string, error) {
d.VersionCalled = true
return d.VersionResult, d.VersionErr
}
+2 -1
View File
@@ -25,7 +25,8 @@ func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) mult
path,
config.DiskSize,
}
if config.DiskImage == false {
if config.DiskImage == false || config.SkipResizeDisk == true {
return multistep.ActionContinue
}
+82
View File
@@ -0,0 +1,82 @@
package qemu
import (
"context"
"testing"
"github.com/hashicorp/packer/helper/multistep"
)
func TestStepResizeDisk_Run(t *testing.T) {
state := testState(t)
driver := state.Get("driver").(*DriverMock)
config := &Config{
DiskImage: true,
SkipResizeDisk: false,
DiskSize: "4096M",
Format: "qcow2",
OutputDir: "/test/",
VMName: "test",
}
state.Put("config", config)
step := new(stepResizeDisk)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if len(driver.QemuImgCalls) == 0 {
t.Fatal("should qemu-img called")
}
if len(driver.QemuImgCalls[0]) != 5 {
t.Fatal("should 5 qemu-img parameters")
}
}
func TestStepResizeDisk_SkipIso(t *testing.T) {
state := testState(t)
driver := state.Get("driver").(*DriverMock)
config := &Config{
DiskImage: false,
SkipResizeDisk: false,
}
state.Put("config", config)
step := new(stepResizeDisk)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if len(driver.QemuImgCalls) > 0 {
t.Fatal("should NOT qemu-img called")
}
}
func TestStepResizeDisk_SkipOption(t *testing.T) {
state := testState(t)
driver := state.Get("driver").(*DriverMock)
config := &Config{
DiskImage: false,
SkipResizeDisk: true,
}
state.Put("config", config)
step := new(stepResizeDisk)
// Test the run
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
if _, ok := state.GetOk("error"); ok {
t.Fatal("should NOT have error")
}
if len(driver.QemuImgCalls) > 0 {
t.Fatal("should NOT qemu-img called")
}
}
+4 -5
View File
@@ -73,12 +73,11 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
var deviceArgs []string
var driveArgs []string
var commHostPort int
var vnc string
if !config.VNCUsePassword {
vnc = fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
} else {
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort-5900)
vncPort = vncPort - config.VNCPortMin
vnc := fmt.Sprintf("%s:%d", vncIP, vncPort)
if config.VNCUsePassword {
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort)
}
if config.QMPEnable {
+19
View File
@@ -0,0 +1,19 @@
package qemu
import (
"bytes"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("driver", new(DriverMock))
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
+18 -9
View File
@@ -4,7 +4,8 @@ import (
"fmt"
"log"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type Artifact struct {
@@ -20,11 +21,11 @@ type Artifact struct {
// The ID of the snapshot
snapshotID string
// The name of the region
regionName string
// The name of the zone
zoneName string
// The client for making API calls
client *api.ScalewayAPI
client *scw.Client
// StateData should store data such as GeneratedData
// to be shared with post-processors
@@ -41,12 +42,12 @@ func (*Artifact) Files() []string {
}
func (a *Artifact) Id() string {
return fmt.Sprintf("%s:%s", a.regionName, a.imageID)
return fmt.Sprintf("%s:%s", a.zoneName, a.imageID)
}
func (a *Artifact) String() string {
return fmt.Sprintf("An image was created: '%v' (ID: %v) in region '%v' based on snapshot '%v' (ID: %v)",
a.imageName, a.imageID, a.regionName, a.snapshotName, a.snapshotID)
return fmt.Sprintf("An image was created: '%v' (ID: %v) in zone '%v' based on snapshot '%v' (ID: %v)",
a.imageName, a.imageID, a.zoneName, a.snapshotName, a.snapshotID)
}
func (a *Artifact) State(name string) interface{} {
@@ -55,11 +56,19 @@ func (a *Artifact) State(name string) interface{} {
func (a *Artifact) Destroy() error {
log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName)
if err := a.client.DeleteImage(a.imageID); err != nil {
instanceAPI := instance.NewAPI(a.client)
err := instanceAPI.DeleteImage(&instance.DeleteImageRequest{
ImageID: a.imageID,
})
if err != nil {
return err
}
log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName)
if err := a.client.DeleteSnapshot(a.snapshotID); err != nil {
err = instanceAPI.DeleteSnapshot(&instance.DeleteSnapshotRequest{
SnapshotID: a.snapshotID,
})
if err != nil {
return err
}
return nil
+1 -1
View File
@@ -27,7 +27,7 @@ func TestArtifactId(t *testing.T) {
func TestArtifactString(t *testing.T) {
generatedData := make(map[string]interface{})
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil, generatedData}
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in region 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
expected := "An image was created: 'packer-foobar-image' (ID: cc586e45-5156-4f71-b223-cf406b10dd1d) in zone 'ams1' based on snapshot 'packer-foobar-snapshot' (ID: cc586e45-5156-4f71-b223-cf406b10dd1c)"
if a.String() != expected {
t.Fatalf("artifact string should match: %v", expected)
+19 -4
View File
@@ -13,7 +13,7 @@ import (
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/scw"
)
// The unique id for the builder
@@ -32,12 +32,27 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
return nil, warnings, errs
}
return nil, nil, nil
return nil, warnings, nil
}
func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
client, err := api.NewScalewayAPI(b.config.Organization, b.config.Token, b.config.UserAgent, b.config.Region)
scwZone, err := scw.ParseZone(b.config.Zone)
if err != nil {
ui.Error(err.Error())
return nil, err
}
clientOpts := []scw.ClientOption{
scw.WithDefaultProjectID(b.config.ProjectID),
scw.WithAuth(b.config.AccessKey, b.config.SecretKey),
scw.WithDefaultZone(scwZone),
}
if b.config.APIURL != "" {
clientOpts = append(clientOpts, scw.WithAPIURL(b.config.APIURL))
}
client, err := scw.NewClient(clientOpts...)
if err != nil {
ui.Error(err.Error())
return nil, err
@@ -96,7 +111,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
imageID: state.Get("image_id").(string),
snapshotName: state.Get("snapshot_name").(string),
snapshotID: state.Get("snapshot_id").(string),
regionName: state.Get("region").(string),
zoneName: b.config.Zone,
client: client,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
+11 -13
View File
@@ -9,9 +9,10 @@ import (
func testConfig() map[string]interface{} {
return map[string]interface{}{
"organization_id": "foo",
"api_token": "bar",
"region": "ams1",
"project_id": "00000000-1111-2222-3333-444444444444",
"access_key": "SCWABCXXXXXXXXXXXXXX",
"secret_key": "00000000-1111-2222-3333-444444444444",
"zone": "fr-par-1",
"commercial_type": "START1-S",
"ssh_username": "root",
"image": "image-uuid",
@@ -32,10 +33,7 @@ func TestBuilder_Prepare_BadType(t *testing.T) {
"api_token": []string{},
}
_, warnings, err := b.Prepare(c)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
_, _, err := b.Prepare(c)
if err == nil {
t.Fatalf("prepare should fail")
}
@@ -68,11 +66,11 @@ func TestBuilderPrepare_InvalidKey(t *testing.T) {
}
}
func TestBuilderPrepare_Region(t *testing.T) {
func TestBuilderPrepare_Zone(t *testing.T) {
var b Builder
config := testConfig()
delete(config, "region")
delete(config, "zone")
_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
@@ -81,9 +79,9 @@ func TestBuilderPrepare_Region(t *testing.T) {
t.Fatalf("should error")
}
expected := "ams1"
expected := "fr-par-1"
config["region"] = expected
config["zone"] = expected
b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
@@ -93,8 +91,8 @@ func TestBuilderPrepare_Region(t *testing.T) {
t.Fatalf("should not have error: %s", err)
}
if b.config.Region != expected {
t.Errorf("found %s, expected %s", b.config.Region, expected)
if b.config.Zone != expected {
t.Errorf("found %s, expected %s", b.config.Zone, expected)
}
}
+90 -25
View File
@@ -17,27 +17,29 @@ import (
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"github.com/mitchellh/mapstructure"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
// The token to use to authenticate with your account.
// It can also be specified via environment variable SCALEWAY_API_TOKEN. You
// can see and generate tokens in the "Credentials"
// section of the control panel.
Token string `mapstructure:"api_token" required:"true"`
// The organization id to use to identify your
// organization. It can also be specified via environment variable
// SCALEWAY_ORGANIZATION. Your organization id is available in the
// "Account" section of the
// control panel.
// Previously named: api_access_key with environment variable: SCALEWAY_API_ACCESS_KEY
Organization string `mapstructure:"organization_id" required:"true"`
// The name of the region to launch the server in (par1
// or ams1). Consequently, this is the region where the snapshot will be
// available.
Region string `mapstructure:"region" required:"true"`
// The AccessKey corresponding to the secret key.
// It can also be specified via the environment variable SCW_ACCESS_KEY.
AccessKey string `mapstructure:"access_key" required:"true"`
// The SecretKey to authenticate against the Scaleway API.
// It can also be specified via the environment variable SCW_SECRET_KEY.
SecretKey string `mapstructure:"secret_key" required:"true"`
// The Project ID in which the instances, volumes and snapshots will be created.
// It can also be specified via the environment variable SCW_DEFAULT_PROJECT_ID.
ProjectID string `mapstructure:"project_id" required:"true"`
// The Zone in which the instances, volumes and snapshots will be created.
// It can also be specified via the environment variable SCW_DEFAULT_ZONE
Zone string `mapstructure:"zone" required:"true"`
// The Scaleway API URL to use
// It can also be specified via the environment variable SCW_API_URL
APIURL string `mapstructure:"api_url"`
// The UUID of the base image to use. This is the image
// that will be used to launch a new server and provision it. See
// the images list
@@ -69,6 +71,28 @@ type Config struct {
UserAgent string `mapstructure-to-hcl2:",skip"`
ctx interpolate.Context
// Deprecated configs
// The token to use to authenticate with your account.
// It can also be specified via environment variable SCALEWAY_API_TOKEN. You
// can see and generate tokens in the "Credentials"
// section of the control panel.
// Deprecated, use SecretKey instead
Token string `mapstructure:"api_token" required:"false"`
// The organization id to use to identify your
// organization. It can also be specified via environment variable
// SCALEWAY_ORGANIZATION. Your organization id is available in the
// "Account" section of the
// control panel.
// Previously named: api_access_key with environment variable: SCALEWAY_API_ACCESS_KEY
// Deprecated, use ProjectID instead
Organization string `mapstructure:"organization_id" required:"false"`
// The name of the region to launch the server in (par1
// or ams1). Consequently, this is the region where the snapshot will be
// available.
// Deprecated, use Zone instead
Region string `mapstructure:"region" required:"false"`
}
func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
@@ -88,8 +112,11 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
return nil, err
}
var warnings []string
c.UserAgent = useragent.String()
// Deprecated variables
if c.Organization == "" {
if os.Getenv("SCALEWAY_ORGANIZATION") != "" {
c.Organization = os.Getenv("SCALEWAY_ORGANIZATION")
@@ -98,10 +125,43 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY")
}
}
if c.Organization != "" {
warnings = append(warnings, "organization_id is deprecated in favor of project_id")
c.ProjectID = c.Organization
}
if c.Token == "" {
c.Token = os.Getenv("SCALEWAY_API_TOKEN")
}
if c.Token != "" {
warnings = append(warnings, "token is deprecated in favor of secret_key")
c.SecretKey = c.Token
}
if c.Region != "" {
warnings = append(warnings, "region is deprecated in favor of zone")
c.Zone = c.Region
}
if c.AccessKey == "" {
c.AccessKey = os.Getenv(scw.ScwAccessKeyEnv)
}
if c.SecretKey == "" {
c.SecretKey = os.Getenv(scw.ScwSecretKeyEnv)
}
if c.ProjectID == "" {
c.ProjectID = os.Getenv(scw.ScwDefaultProjectIDEnv)
}
if c.Zone == "" {
c.Zone = os.Getenv(scw.ScwDefaultZoneEnv)
}
if c.APIURL == "" {
c.APIURL = os.Getenv(scw.ScwAPIURLEnv)
}
if c.SnapshotName == "" {
def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil)
@@ -127,26 +187,31 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
if c.BootType == "" {
c.BootType = "local"
c.BootType = instance.BootTypeLocal.String()
}
var errs *packer.MultiError
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
if c.Organization == "" {
if c.ProjectID == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Scaleway Organization ID must be specified"))
errs, errors.New("Scaleway Project ID must be specified"))
}
if c.Token == "" {
if c.SecretKey == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Scaleway Token must be specified"))
errs, errors.New("Scaleway Secret Key must be specified"))
}
if c.Region == "" {
if c.AccessKey == "" {
warnings = append(warnings, "access_key will be required in future versions")
c.AccessKey = "SCWXXXXXXXXXXXXXXXXX"
}
if c.Zone == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("region is required"))
errs, errors.New("Scaleway Zone is required"))
}
if c.CommercialType == "" {
@@ -160,9 +225,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
if errs != nil && len(errs.Errors) > 0 {
return nil, errs
return warnings, errs
}
packer.LogSecretFilter.Set(c.Token)
return nil, nil
return warnings, nil
}
+16 -6
View File
@@ -63,9 +63,11 @@ type FlatConfig struct {
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
Token *string `mapstructure:"api_token" required:"true" cty:"api_token" hcl:"api_token"`
Organization *string `mapstructure:"organization_id" required:"true" cty:"organization_id" hcl:"organization_id"`
Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"`
AccessKey *string `mapstructure:"access_key" required:"true" cty:"access_key" hcl:"access_key"`
SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"`
ProjectID *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"`
Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"`
APIURL *string `mapstructure:"api_url" cty:"api_url" hcl:"api_url"`
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
CommercialType *string `mapstructure:"commercial_type" required:"true" cty:"commercial_type" hcl:"commercial_type"`
SnapshotName *string `mapstructure:"snapshot_name" required:"false" cty:"snapshot_name" hcl:"snapshot_name"`
@@ -74,6 +76,9 @@ type FlatConfig struct {
Bootscript *string `mapstructure:"bootscript" required:"false" cty:"bootscript" hcl:"bootscript"`
BootType *string `mapstructure:"boottype" required:"false" cty:"boottype" hcl:"boottype"`
RemoveVolume *bool `mapstructure:"remove_volume" cty:"remove_volume" hcl:"remove_volume"`
Token *string `mapstructure:"api_token" required:"false" cty:"api_token" hcl:"api_token"`
Organization *string `mapstructure:"organization_id" required:"false" cty:"organization_id" hcl:"organization_id"`
Region *string `mapstructure:"region" required:"false" cty:"region" hcl:"region"`
}
// FlatMapstructure returns a new FlatConfig.
@@ -142,9 +147,11 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"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},
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
"organization_id": &hcldec.AttrSpec{Name: "organization_id", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: 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},
"project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false},
"zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false},
"api_url": &hcldec.AttrSpec{Name: "api_url", Type: cty.String, Required: false},
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
"commercial_type": &hcldec.AttrSpec{Name: "commercial_type", Type: cty.String, Required: false},
"snapshot_name": &hcldec.AttrSpec{Name: "snapshot_name", Type: cty.String, Required: false},
@@ -153,6 +160,9 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"bootscript": &hcldec.AttrSpec{Name: "bootscript", Type: cty.String, Required: false},
"boottype": &hcldec.AttrSpec{Name: "boottype", Type: cty.String, Required: false},
"remove_volume": &hcldec.AttrSpec{Name: "remove_volume", Type: cty.Bool, Required: false},
"api_token": &hcldec.AttrSpec{Name: "api_token", Type: cty.String, Required: false},
"organization_id": &hcldec.AttrSpec{Name: "organization_id", Type: cty.String, Required: false},
"region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false},
}
return s
}
+18 -9
View File
@@ -7,13 +7,14 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type stepImage struct{}
func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
snapshotID := state.Get("snapshot_id").(string)
@@ -21,7 +22,9 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
image, err := client.GetImage(c.Image)
imageResp, err := instanceAPI.GetImage(&instance.GetImageRequest{
ImageID: c.Image,
})
if err != nil {
err := fmt.Errorf("Error getting initial image info: %s", err)
state.Put("error", err)
@@ -29,11 +32,16 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
return multistep.ActionHalt
}
if image.DefaultBootscript != nil {
bootscriptID = image.DefaultBootscript.Identifier
if imageResp.Image.DefaultBootscript != nil {
bootscriptID = imageResp.Image.DefaultBootscript.ID
}
imageID, err := client.PostImage(snapshotID, c.ImageName, bootscriptID, image.Arch)
createImageResp, err := instanceAPI.CreateImage(&instance.CreateImageRequest{
Arch: imageResp.Image.Arch,
DefaultBootscript: bootscriptID,
Name: c.ImageName,
RootVolume: snapshotID,
})
if err != nil {
err := fmt.Errorf("Error creating image: %s", err)
state.Put("error", err)
@@ -41,10 +49,11 @@ func (s *stepImage) Run(ctx context.Context, state multistep.StateBag) multistep
return multistep.ActionHalt
}
log.Printf("Image ID: %s", imageID)
state.Put("image_id", imageID)
log.Printf("Image ID: %s", createImageResp.Image.ID)
state.Put("image_id", createImageResp.Image.ID)
state.Put("image_name", c.ImageName)
state.Put("region", c.Region)
state.Put("region", c.Zone) // Deprecated
state.Put("zone", c.Zone)
return multistep.ActionContinue
}
+30 -21
View File
@@ -7,7 +7,8 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type stepCreateServer struct {
@@ -15,7 +16,7 @@ type stepCreateServer struct {
}
func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
tags := []string{}
@@ -31,16 +32,16 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
tags = []string{fmt.Sprintf("AUTHORIZED_KEY=%s", strings.Replace(strings.TrimSpace(string(c.Comm.SSHPublicKey)), " ", "_", -1))}
}
server, err := client.PostServer(api.ScalewayServerDefinition{
Name: c.ServerName,
Image: &c.Image,
Organization: c.Organization,
CommercialType: c.CommercialType,
Tags: tags,
Bootscript: bootscript,
BootType: c.BootType,
})
bootType := instance.BootType(c.BootType)
createServerResp, err := instanceAPI.CreateServer(&instance.CreateServerRequest{
BootType: &bootType,
Bootscript: bootscript,
CommercialType: c.CommercialType,
Name: c.ServerName,
Image: c.Image,
Tags: tags,
})
if err != nil {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
@@ -48,8 +49,10 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
err = client.PostServerAction(server, "poweron")
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
Action: instance.ServerActionPoweron,
ServerID: createServerResp.Server.ID,
})
if err != nil {
err := fmt.Errorf("Error starting server: %s", err)
state.Put("error", err)
@@ -57,9 +60,9 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
s.serverID = server
s.serverID = createServerResp.Server.ID
state.Put("server_id", server)
state.Put("server_id", createServerResp.Server.ID)
// instance_id is the generic term used so that users can have access to the
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", s.serverID)
@@ -72,16 +75,22 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
ui.Say("Destroying server...")
err := client.DeleteServerForce(s.serverID)
err := instanceAPI.DeleteServer(&instance.DeleteServerRequest{
ServerID: s.serverID,
})
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying server. Please destroy it manually: %s", err))
_, err = instanceAPI.ServerAction(&instance.ServerActionRequest{
Action: instance.ServerActionTerminate,
ServerID: s.serverID,
})
if err != nil {
ui.Error(fmt.Sprintf(
"Error destroying server. Please destroy it manually: %s", err))
}
}
}
+6 -4
View File
@@ -4,10 +4,10 @@ import (
"context"
"fmt"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type stepRemoveVolume struct{}
@@ -24,7 +24,7 @@ func (s *stepRemoveVolume) Cleanup(state multistep.StateBag) {
return
}
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
volumeID := state.Get("root_volume_id").(string)
@@ -35,7 +35,9 @@ func (s *stepRemoveVolume) Cleanup(state multistep.StateBag) {
ui.Say("Removing Volume ...")
err := client.DeleteVolume(volumeID)
err := instanceAPI.DeleteVolume(&instance.DeleteVolumeRequest{
VolumeID: volumeID,
})
if err != nil {
err := fmt.Errorf("Error removing volume: %s", err)
state.Put("error", err)
+17 -8
View File
@@ -6,19 +6,22 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type stepServerInfo struct{}
func (s *stepServerInfo) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
serverID := state.Get("server_id").(string)
ui.Say("Waiting for server to become active...")
_, err := api.WaitForServerState(client, serverID, "running")
instanceResp, err := instanceAPI.WaitForServer(&instance.WaitForServerRequest{
ServerID: serverID,
})
if err != nil {
err := fmt.Errorf("Error waiting for server to become booted: %s", err)
state.Put("error", err)
@@ -26,16 +29,22 @@ func (s *stepServerInfo) Run(ctx context.Context, state multistep.StateBag) mult
return multistep.ActionHalt
}
server, err := client.GetServer(serverID)
if err != nil {
err := fmt.Errorf("Error retrieving server: %s", err)
if instanceResp.State != instance.ServerStateRunning {
err := fmt.Errorf("Server is in state %s", instanceResp.State.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("server_ip", server.PublicAddress.IP)
state.Put("root_volume_id", server.Volumes["0"].Identifier)
if instanceResp.PublicIP == nil {
err := fmt.Errorf("Server does not have a public IP")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
state.Put("server_ip", instanceResp.PublicIP.Address.String())
state.Put("root_volume_id", instanceResp.Volumes["0"].ID)
return multistep.ActionContinue
}
+17 -6
View File
@@ -6,20 +6,23 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type stepShutdown struct{}
func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
serverID := state.Get("server_id").(string)
ui.Say("Shutting down server...")
err := client.PostServerAction(serverID, "poweroff")
_, err := instanceAPI.ServerAction(&instance.ServerActionRequest{
Action: instance.ServerActionPoweroff,
ServerID: serverID,
})
if err != nil {
err := fmt.Errorf("Error stopping server: %s", err)
state.Put("error", err)
@@ -27,8 +30,9 @@ func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
return multistep.ActionHalt
}
_, err = api.WaitForServerState(client, serverID, "stopped")
instanceResp, err := instanceAPI.WaitForServer(&instance.WaitForServerRequest{
ServerID: serverID,
})
if err != nil {
err := fmt.Errorf("Error shutting down server: %s", err)
state.Put("error", err)
@@ -36,6 +40,13 @@ func (s *stepShutdown) Run(ctx context.Context, state multistep.StateBag) multis
return multistep.ActionHalt
}
if instanceResp.State != instance.ServerStateStopped {
err := fmt.Errorf("Server is in state %s instead of stopped", instanceResp.State.String())
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
+11 -6
View File
@@ -7,19 +7,23 @@ import (
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/scaleway/scaleway-cli/pkg/api"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
)
type stepSnapshot struct{}
func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*api.ScalewayAPI)
instanceAPI := instance.NewAPI(state.Get("client").(*scw.Client))
ui := state.Get("ui").(packer.Ui)
c := state.Get("config").(*Config)
volumeID := state.Get("root_volume_id").(string)
ui.Say(fmt.Sprintf("Creating snapshot: %v", c.SnapshotName))
snapshot, err := client.PostSnapshot(volumeID, c.SnapshotName)
createSnapshotResp, err := instanceAPI.CreateSnapshot(&instance.CreateSnapshotRequest{
Name: c.SnapshotName,
VolumeID: volumeID,
})
if err != nil {
err := fmt.Errorf("Error creating snapshot: %s", err)
state.Put("error", err)
@@ -27,10 +31,11 @@ func (s *stepSnapshot) Run(ctx context.Context, state multistep.StateBag) multis
return multistep.ActionHalt
}
log.Printf("Snapshot ID: %s", snapshot)
state.Put("snapshot_id", snapshot)
log.Printf("Snapshot ID: %s", createSnapshotResp.Snapshot.ID)
state.Put("snapshot_id", createSnapshotResp.Snapshot.ID)
state.Put("snapshot_name", c.SnapshotName)
state.Put("region", c.Region)
state.Put("region", c.Zone) // Deprecated
state.Put("zone", c.Zone)
return multistep.ActionContinue
}
+2
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -125,6 +126,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -146,6 +147,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
@@ -133,6 +134,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
+2
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
@@ -130,6 +131,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
+42
View File
@@ -0,0 +1,42 @@
//go:generate struct-markdown
package common
import (
"fmt"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/template/interpolate"
)
type BootConfigWrapper struct {
bootcommand.VNCConfig `mapstructure:",squash"`
// If set to true, Packer will use USB HID Keyboard scan codes to send the boot command to the VM and
// the [disable_vnc](#disable_vnc) option will be ignored and automatically set to true.
// This option is not supported by hosts with free license.
//
// ~> **Note:** The ESXi 6.5+ removes support to VNC. In this case, the `usb_keyboard` should be set to true
// in order to send boot command keystrokes to the VM.
USBKeyBoard bool `mapstructure:"usb_keyboard"`
}
func (c *BootConfigWrapper) Prepare(ctx *interpolate.Context, driverConfig *DriverConfig) (warnings []string, errs []error) {
if c.USBKeyBoard {
if driverConfig.RemoteType == "" {
warnings = append(warnings, "[WARN] `usb_keyboard` can only be used with remote VMWare builds. "+
"The `usb_keyboard` option will be ignored and automatically set to false.")
c.USBKeyBoard = false
} else if !c.DisableVNC {
warnings = append(warnings, "[WARN] `usb_keyboard` is set to true then the remote VMWare builds "+
"will not use VNC to connect to the host. The `disable_vnc` option will be ignored and automatically set to true.")
c.DisableVNC = true
return
}
}
if len(c.BootCommand) > 0 && c.DisableVNC {
errs = append(errs,
fmt.Errorf("A boot command cannot be used when vnc is disabled."))
}
errs = append(errs, c.BootConfig.Prepare(ctx)...)
return
}
+106
View File
@@ -0,0 +1,106 @@
package common
import (
"fmt"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/template/interpolate"
)
func TestVNCConfigWrapper_Prepare(t *testing.T) {
tc := []struct {
name string
config *BootConfigWrapper
expectedConfig *BootConfigWrapper
driver *DriverConfig
errs []error
warnings []string
}{
{
name: "VNC and boot command for local build",
config: &BootConfigWrapper{
VNCConfig: bootcommand.VNCConfig{
BootConfig: bootcommand.BootConfig{
BootCommand: []string{"<boot><command>"},
},
DisableVNC: true,
},
USBKeyBoard: false,
},
expectedConfig: nil,
driver: new(DriverConfig),
errs: []error{fmt.Errorf("A boot command cannot be used when vnc is disabled.")},
warnings: nil,
},
{
name: "Disable VNC warning for remote build",
config: &BootConfigWrapper{
VNCConfig: bootcommand.VNCConfig{
BootConfig: bootcommand.BootConfig{
BootCommand: []string{"<boot><command>"},
},
DisableVNC: false,
},
USBKeyBoard: true,
},
expectedConfig: &BootConfigWrapper{
VNCConfig: bootcommand.VNCConfig{
DisableVNC: true,
},
USBKeyBoard: true,
},
driver: &DriverConfig{
RemoteType: "esxi",
},
errs: nil,
warnings: []string{"[WARN] `usb_keyboard` is set to true then the remote VMWare builds " +
"will not use VNC to connect to the host. The `disable_vnc` option will be ignored and automatically set to true."},
},
{
name: "Disable USBKeyBoard warning for local build",
config: &BootConfigWrapper{
VNCConfig: bootcommand.VNCConfig{
BootConfig: bootcommand.BootConfig{
BootCommand: []string{"<boot><command>"},
},
DisableVNC: false,
},
USBKeyBoard: true,
},
expectedConfig: &BootConfigWrapper{
VNCConfig: bootcommand.VNCConfig{
DisableVNC: false,
},
USBKeyBoard: false,
},
driver: &DriverConfig{},
errs: nil,
warnings: []string{"[WARN] `usb_keyboard` can only be used with remote VMWare builds. " +
"The `usb_keyboard` option will be ignored and automatically set to false."},
},
}
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
warnings, errs := c.config.Prepare(interpolate.NewContext(), c.driver)
if !reflect.DeepEqual(errs, c.errs) {
t.Fatalf("bad: \n expected '%v' \nactual '%v'", c.errs, errs)
}
if diff := cmp.Diff(warnings, c.warnings); diff != "" {
t.Fatalf("unexpected warnings: %s", diff)
}
if len(c.errs) == 0 {
if diff := cmp.Diff(c.config, c.expectedConfig,
cmpopts.IgnoreFields(bootcommand.VNCConfig{},
"BootConfig",
)); diff != "" {
t.Fatalf("unexpected config: %s", diff)
}
}
})
}
}
+4 -13
View File
@@ -91,20 +91,11 @@ func NewDriver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver,
drivers := []Driver{}
if dconfig.RemoteType != "" {
drivers = []Driver{
&ESX5Driver{
Host: dconfig.RemoteHost,
Port: dconfig.RemotePort,
Username: dconfig.RemoteUser,
Password: dconfig.RemotePassword,
PrivateKeyFile: dconfig.RemotePrivateKey,
Datastore: dconfig.RemoteDatastore,
CacheDatastore: dconfig.RemoteCacheDatastore,
CacheDirectory: dconfig.RemoteCacheDirectory,
VMName: vmName,
CommConfig: config.Comm,
},
esx5Driver, err := NewESX5Driver(dconfig, config, vmName)
if err != nil {
return nil, err
}
drivers = []Driver{esx5Driver}
} else {
switch runtime.GOOS {
+15 -1
View File
@@ -52,6 +52,8 @@ type DriverConfig struct {
}
func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
if c.FusionAppPath == "" {
c.FusionAppPath = os.Getenv("FUSION_APP_PATH")
}
@@ -74,7 +76,19 @@ func (c *DriverConfig) Prepare(ctx *interpolate.Context) []error {
c.RemotePort = 22
}
return nil
if c.RemoteType != "" {
if c.RemoteHost == "" {
errs = append(errs,
fmt.Errorf("remote_host must be specified"))
}
if c.RemoteType != "esx5" {
errs = append(errs,
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
}
}
return errs
}
func (c *DriverConfig) Validate(SkipExport bool) error {
+71 -19
View File
@@ -1,32 +1,84 @@
package common
import (
"fmt"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer/template/interpolate"
)
func TestDriverConfigPrepare(t *testing.T) {
var c *DriverConfig
// Test a default boot_wait
c = new(DriverConfig)
errs := c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("bad: %#v", errs)
}
if c.FusionAppPath != "/Applications/VMware Fusion.app" {
t.Fatalf("bad value: %s", c.FusionAppPath)
tc := []struct {
name string
config *DriverConfig
expectedConfig *DriverConfig
errs []error
}{
{
name: "Set default values",
config: new(DriverConfig),
expectedConfig: &DriverConfig{
FusionAppPath: "/Applications/VMware Fusion.app",
RemoteDatastore: "datastore1",
RemoteCacheDatastore: "datastore1",
RemoteCacheDirectory: "packer_cache",
RemotePort: 22,
RemoteUser: "root",
},
errs: nil,
},
{
name: "Override default values",
config: &DriverConfig{
FusionAppPath: "foo",
RemoteDatastore: "set-datastore1",
RemoteCacheDatastore: "set-datastore1",
RemoteCacheDirectory: "set_packer_cache",
RemotePort: 443,
RemoteUser: "admin",
},
expectedConfig: &DriverConfig{
FusionAppPath: "foo",
RemoteDatastore: "set-datastore1",
RemoteCacheDatastore: "set-datastore1",
RemoteCacheDirectory: "set_packer_cache",
RemotePort: 443,
RemoteUser: "admin",
},
errs: nil,
},
{
name: "Invalid remote type",
config: &DriverConfig{
RemoteType: "invalid",
RemoteHost: "host",
},
expectedConfig: nil,
errs: []error{fmt.Errorf("Only 'esx5' value is accepted for remote_type")},
},
{
name: "Remote host not set",
config: &DriverConfig{
RemoteType: "esx5",
},
expectedConfig: nil,
errs: []error{fmt.Errorf("remote_host must be specified")},
},
}
// Test with a good one
c = new(DriverConfig)
c.FusionAppPath = "foo"
errs = c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("bad: %#v", errs)
}
if c.FusionAppPath != "foo" {
t.Fatalf("bad value: %s", c.FusionAppPath)
for _, c := range tc {
t.Run(c.name, func(t *testing.T) {
errs := c.config.Prepare(interpolate.NewContext())
if !reflect.DeepEqual(errs, c.errs) {
t.Fatalf("bad: \n expected '%v' \nactual '%v'", c.errs, errs)
}
if len(c.errs) == 0 {
if diff := cmp.Diff(c.config, c.expectedConfig); diff != "" {
t.Fatalf("bad value: %s", diff)
}
}
})
}
}
+98
View File
@@ -26,7 +26,15 @@ import (
"github.com/hashicorp/packer/helper/multistep"
helperssh "github.com/hashicorp/packer/helper/ssh"
"github.com/hashicorp/packer/packer"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/mobile/event/key"
)
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
@@ -45,11 +53,66 @@ type ESX5Driver struct {
VMName string
CommConfig communicator.Config
ctx context.Context
client *govmomi.Client
finder *find.Finder
comm packer.Communicator
outputDir string
vmId string
}
func NewESX5Driver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver, error) {
ctx := context.TODO()
vsphereUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", dconfig.RemoteHost))
if err != nil {
return nil, err
}
credentials := url.UserPassword(dconfig.RemoteUser, dconfig.RemotePassword)
vsphereUrl.User = credentials
soapClient := soap.NewClient(vsphereUrl, true)
vimClient, err := vim25.NewClient(ctx, soapClient)
if err != nil {
return nil, err
}
vimClient.RoundTripper = session.KeepAlive(vimClient.RoundTripper, 10*time.Minute)
client := &govmomi.Client{
Client: vimClient,
SessionManager: session.NewManager(vimClient),
}
err = client.SessionManager.Login(ctx, credentials)
if err != nil {
return nil, err
}
finder := find.NewFinder(client.Client, false)
datacenter, err := finder.DefaultDatacenter(ctx)
if err != nil {
return nil, err
}
finder.SetDatacenter(datacenter)
return &ESX5Driver{
Host: dconfig.RemoteHost,
Port: dconfig.RemotePort,
Username: dconfig.RemoteUser,
Password: dconfig.RemotePassword,
PrivateKeyFile: dconfig.RemotePrivateKey,
Datastore: dconfig.RemoteDatastore,
CacheDatastore: dconfig.RemoteCacheDatastore,
CacheDirectory: dconfig.RemoteCacheDirectory,
VMName: vmName,
CommConfig: config.Comm,
ctx: ctx,
client: client,
finder: finder,
}, nil
}
func (d *ESX5Driver) Clone(dst, src string, linked bool) error {
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\r\n"), "\n") }
@@ -887,3 +950,38 @@ func (r *esxcliReader) find(key, val string) (map[string]string, error) {
}
}
}
type KeyInput struct {
Scancode key.Code
Alt bool
Ctrl bool
Shift bool
}
func (d *ESX5Driver) TypeOnKeyboard(input KeyInput) (int32, error) {
vm, err := d.finder.VirtualMachine(d.ctx, d.VMName)
if err != nil {
return 0, err
}
var spec types.UsbScanCodeSpec
spec.KeyEvents = append(spec.KeyEvents, types.UsbScanCodeSpecKeyEvent{
UsbHidCode: int32(input.Scancode)<<16 | 7,
Modifiers: &types.UsbScanCodeSpecModifierType{
LeftControl: &input.Ctrl,
LeftAlt: &input.Alt,
LeftShift: &input.Shift,
},
})
req := &types.PutUsbScanCodes{
This: vm.Reference(),
Spec: spec,
}
resp, err := methods.PutUsbScanCodes(d.ctx, d.client.RoundTripper, req)
if err != nil {
return 0, err
}
return resp.Returnval, nil
}
+7 -7
View File
@@ -954,20 +954,20 @@ func createDeclaration(node pDeclaration) configDeclaration {
// update configDeclaration parameters
for _, p := range hierarchy[i].parameters {
switch p.(type) {
switch p := p.(type) {
case pParameterOption:
result.options[p.(pParameterOption).name] = p.(pParameterOption).value
result.options[p.name] = p.value
case pParameterGrant:
Grant := map[string]grant{"ignore": IGNORE, "allow": ALLOW, "deny": DENY}
result.grants[p.(pParameterGrant).attribute] = Grant[p.(pParameterGrant).verb]
result.grants[p.attribute] = Grant[p.verb]
case pParameterBoolean:
result.attributes[p.(pParameterBoolean).parameter] = p.(pParameterBoolean).truancy
result.attributes[p.parameter] = p.truancy
case pParameterClientMatch:
result.hostid = append(result.hostid, p.(pParameterClientMatch))
result.hostid = append(result.hostid, p)
case pParameterExpression:
result.expressions[p.(pParameterExpression).parameter] = p.(pParameterExpression).expression
result.expressions[p.parameter] = p.expression
case pParameterOther:
result.parameters[p.(pParameterOther).parameter] = p.(pParameterOther).value
result.parameters[p.parameter] = p.value
default:
result.address = append(result.address, p)
}
+18 -16
View File
@@ -8,6 +8,7 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)
// ~> **Note:** If [usb_keyboard](#usb_keyboard) is set to true, any VNC configuration will be ignored.
type RunConfig struct {
// Packer defaults to building VMware virtual machines
// by launching a GUI that shows the console of the machine being built. When
@@ -39,25 +40,26 @@ type RunConfig struct {
VNCDisablePassword bool `mapstructure:"vnc_disable_password" required:"false"`
}
func (c *RunConfig) Prepare(ctx *interpolate.Context) (errs []error) {
if c.VNCPortMin == 0 {
c.VNCPortMin = 5900
}
func (c *RunConfig) Prepare(_ *interpolate.Context, bootConfig *BootConfigWrapper) (errs []error) {
if !bootConfig.USBKeyBoard {
if c.VNCPortMin == 0 {
c.VNCPortMin = 5900
}
if c.VNCPortMax == 0 {
c.VNCPortMax = 6000
}
if c.VNCPortMax == 0 {
c.VNCPortMax = 6000
}
if c.VNCBindAddress == "" {
c.VNCBindAddress = "127.0.0.1"
}
if c.VNCBindAddress == "" {
c.VNCBindAddress = "127.0.0.1"
}
if c.VNCPortMin > c.VNCPortMax {
errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
if c.VNCPortMin > c.VNCPortMax {
errs = append(errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
}
if c.VNCPortMin < 0 {
errs = append(errs, fmt.Errorf("vnc_port_min must be positive"))
}
}
if c.VNCPortMin < 0 {
errs = append(errs, fmt.Errorf("vnc_port_min must be positive"))
}
return
}
@@ -0,0 +1,126 @@
package common
import (
"context"
"fmt"
"time"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
"golang.org/x/mobile/event/key"
)
// This step "types" the boot command into the VM using USB Scan Codes.
type StepUSBBootCommand struct {
Config bootcommand.BootConfig
KeyInterval time.Duration
VMName string
Ctx interpolate.Context
}
type USBBootCommandTemplateData struct {
HTTPIP string
HTTPPort int
Name string
}
func (s *StepUSBBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
debug := state.Get("debug").(bool)
ui := state.Get("ui").(packer.Ui)
driver := state.Get("driver").(*ESX5Driver)
if s.Config.BootCommand == nil {
return multistep.ActionContinue
}
// Wait the for the vm to boot.
if int64(s.Config.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String()))
select {
case <-time.After(s.Config.BootWait):
break
case <-ctx.Done():
return multistep.ActionHalt
}
}
var pauseFn multistep.DebugPauseFn
if debug {
pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn)
}
port := state.Get("http_port").(int)
if port > 0 {
ip := state.Get("http_ip").(string)
s.Ctx.Data = &USBBootCommandTemplateData{
HTTPIP: ip,
HTTPPort: port,
Name: s.VMName,
}
ui.Say(fmt.Sprintf("HTTP server is working at http://%v:%v/", ip, port))
}
var keyAlt, keyCtrl, keyShift bool
sendCodes := func(code key.Code, down bool) error {
switch code {
case key.CodeLeftAlt:
keyAlt = down
case key.CodeLeftControl:
keyCtrl = down
case key.CodeLeftShift:
keyShift = down
}
shift := down
if keyShift {
shift = keyShift
}
_, err := driver.TypeOnKeyboard(KeyInput{
Scancode: code,
Ctrl: keyCtrl,
Alt: keyAlt,
Shift: shift,
})
if err != nil {
return fmt.Errorf("error typing a boot command (code, down) `%d, %t`: %w", code, down, err)
}
return nil
}
d := bootcommand.NewUSBDriver(sendCodes, s.KeyInterval)
ui.Say("Typing boot command...")
flatBootCommand := s.Config.FlatBootCommand()
command, err := interpolate.Render(flatBootCommand, &s.Ctx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
seq, err := bootcommand.GenerateExpressionSequence(command)
if err != nil {
err := fmt.Errorf("Error generating boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := seq.Do(ctx, d); err != nil {
err := fmt.Errorf("Error running boot command: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if pauseFn != nil {
pauseFn(multistep.DebugLocationAfterRun, fmt.Sprintf("boot_command: %s", command), state)
}
return multistep.ActionContinue
}
func (*StepUSBBootCommand) Cleanup(multistep.StateBag) {}
@@ -23,22 +23,20 @@ import (
//
// Produces:
// <nothing>
type StepTypeBootCommand struct {
BootCommand string
VNCEnabled bool
BootWait time.Duration
VMName string
Ctx interpolate.Context
KeyInterval time.Duration
type StepVNCBootCommand struct {
Config bootcommand.VNCConfig
VMName string
Ctx interpolate.Context
}
type bootCommandTemplateData struct {
type VNCBootCommandTemplateData struct {
HTTPIP string
HTTPPort int
Name string
}
func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if !s.VNCEnabled {
func (s *StepVNCBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if s.Config.DisableVNC {
log.Println("Skipping boot command step...")
return multistep.ActionContinue
}
@@ -51,10 +49,10 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
vncPassword := state.Get("vnc_password")
// Wait the for the vm to boot.
if int64(s.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait.String()))
if int64(s.Config.BootWait) > 0 {
ui.Say(fmt.Sprintf("Waiting %s for boot...", s.Config.BootWait.String()))
select {
case <-time.After(s.BootWait):
case <-time.After(s.Config.BootWait):
break
case <-ctx.Done():
return multistep.ActionHalt
@@ -98,16 +96,17 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
hostIP := state.Get("http_ip").(string)
s.Ctx.Data = &bootCommandTemplateData{
hostIP,
httpPort,
s.VMName,
s.Ctx.Data = &VNCBootCommandTemplateData{
HTTPIP: hostIP,
HTTPPort: httpPort,
Name: s.VMName,
}
d := bootcommand.NewVNCDriver(c, s.KeyInterval)
d := bootcommand.NewVNCDriver(c, s.Config.BootKeyInterval)
ui.Say("Typing the boot command over VNC...")
command, err := interpolate.Render(s.BootCommand, &s.Ctx)
flatBootCommand := s.Config.FlatBootCommand()
command, err := interpolate.Render(flatBootCommand, &s.Ctx)
if err != nil {
err := fmt.Errorf("Error preparing boot command: %s", err)
state.Put("error", err)
@@ -138,4 +137,4 @@ func (s *StepTypeBootCommand) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionContinue
}
func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {}
func (*StepVNCBootCommand) Cleanup(multistep.StateBag) {}
+25 -8
View File
@@ -52,6 +52,20 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("driverConfig", &b.config.DriverConfig)
state.Put("temporaryDevices", []string{}) // Devices (in .vmx) created by packer during building
var stepBootCommand multistep.Step = &vmwcommon.StepVNCBootCommand{
Config: b.config.VNCConfig,
VMName: b.config.VMName,
Ctx: b.config.ctx,
}
if b.config.USBKeyBoard {
stepBootCommand = &vmwcommon.StepUSBBootCommand{
Config: b.config.VNCConfig.BootConfig,
KeyInterval: b.config.VNCConfig.BootKeyInterval,
VMName: b.config.VMName,
Ctx: b.config.ctx,
}
}
steps := []multistep.Step{
&vmwcommon.StepPrepareTools{
RemoteType: b.config.RemoteType,
@@ -76,12 +90,22 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel,
},
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&vmwcommon.StepRemoteUpload{
Key: "floppy_path",
Message: "Uploading Floppy to remote machine...",
DoCleanup: true,
Checksum: "none",
},
&vmwcommon.StepRemoteUpload{
Key: "cd_path",
Message: "Uploading CD to remote machine...",
DoCleanup: true,
Checksum: "none",
},
&vmwcommon.StepRemoteUpload{
Key: "iso_path",
Message: "Uploading ISO to remote machine...",
@@ -127,14 +151,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DurationBeforeStop: 5 * time.Second,
Headless: b.config.Headless,
},
&vmwcommon.StepTypeBootCommand{
BootWait: b.config.BootWait,
VNCEnabled: !b.config.DisableVNC,
BootCommand: b.config.FlatBootCommand(),
VMName: b.config.VMName,
Ctx: b.config.ctx,
KeyInterval: b.config.VNCConfig.BootKeyInterval,
},
stepBootCommand,
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: driver.CommHost,
+22 -32
View File
@@ -11,7 +11,6 @@ import (
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/common/shutdowncommand"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
@@ -23,7 +22,8 @@ type Config struct {
common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
bootcommand.VNCConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
vmwcommon.BootConfigWrapper `mapstructure:",squash"`
vmwcommon.DriverConfig `mapstructure:",squash"`
vmwcommon.HWConfig `mapstructure:",squash"`
vmwcommon.OutputConfig `mapstructure:",squash"`
@@ -93,24 +93,27 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
// Accumulate any errors and warnings
var warnings []string
var errs *packer.MultiError
warnings := make([]string, 0)
vncWarnings, vncErrs := c.BootConfigWrapper.Prepare(&c.ctx, &c.DriverConfig)
warnings = append(warnings, vncWarnings...)
errs = packer.MultiErrorAppend(errs, vncErrs...)
isoWarnings, isoErrs := c.ISOConfig.Prepare(&c.ctx)
warnings = append(warnings, isoWarnings...)
errs = packer.MultiErrorAppend(errs, isoErrs...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.HWConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, c.DriverConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs,
c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx, &c.BootConfigWrapper)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)
@@ -163,19 +166,6 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.HWConfig.Network = "nat"
}
// Remote configuration validation
if c.RemoteType != "" {
if c.RemoteHost == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("remote_host must be specified"))
}
if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
}
}
if c.Format == "" {
if c.RemoteType == "" {
c.Format = "vmx"
@@ -184,11 +174,18 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
}
}
if c.RemoteType == "" && c.Format == "vmx" {
// if we're building locally and want a vmx, there's nothing to export.
// Set skip export flag here to keep the export step from attempting
// an unneded export
c.SkipExport = true
if c.RemoteType == "" {
if c.Format == "vmx" {
// if we're building locally and want a vmx, there's nothing to export.
// Set skip export flag here to keep the export step from attempting
// an unneded export
c.SkipExport = true
}
if c.Headless && c.DisableVNC {
warnings = append(warnings,
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
"you won't be able to see any output.")
}
}
err = c.DriverConfig.Validate(c.SkipExport)
@@ -196,19 +193,12 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, err)
}
// Warnings
if c.ShutdownCommand == "" {
warnings = append(warnings,
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
"will forcibly halt the virtual machine, which may result in data loss.")
}
if c.Headless && c.DisableVNC {
warnings = append(warnings,
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
"you won't be able to see any output.")
}
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
}
+8
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
@@ -28,11 +29,14 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc" hcl:"disable_vnc"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
USBKeyBoard *bool `mapstructure:"usb_keyboard" cty:"usb_keyboard" hcl:"usb_keyboard"`
CleanUpRemoteCache *bool `mapstructure:"cleanup_remote_cache" required:"false" cty:"cleanup_remote_cache" hcl:"cleanup_remote_cache"`
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path" hcl:"fusion_app_path"`
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type" hcl:"remote_type"`
@@ -159,6 +163,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
@@ -167,11 +172,14 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
"usb_keyboard": &hcldec.AttrSpec{Name: "usb_keyboard", Type: cty.Bool, Required: false},
"cleanup_remote_cache": &hcldec.AttrSpec{Name: "cleanup_remote_cache", Type: cty.Bool, Required: false},
"fusion_app_path": &hcldec.AttrSpec{Name: "fusion_app_path", Type: cty.String, Required: false},
"remote_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
+17
View File
@@ -34,6 +34,7 @@ type vmxTemplateData struct {
DiskType string
CDROMType string
CDROMType_PrimarySecondary string
CDROM_PATH string
Network_Type string
Network_Device string
@@ -112,6 +113,7 @@ func (s *stepCreateVMX) Run(ctx context.Context, state multistep.StateBag) multi
ictx := config.ctx
// Mount extra vmdks we created earlier.
if len(config.AdditionalDiskSize) > 0 {
for i := range config.AdditionalDiskSize {
ictx.Data = &additionalDiskTemplateData{
@@ -238,6 +240,15 @@ func (s *stepCreateVMX) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
// Add our custom CD, if it exists
cd_path, ok := state.Get("cd_path").(string)
if ok {
if cd_path != "" {
vmxTemplate += ExtraCDRomTemplate
templateData.CDROM_PATH = cd_path
}
}
/// Now that we figured out the CDROM device to add, store it
/// to the list of temporary build devices in our statebag
tmpBuildDevices := state.Get("temporaryDevices").([]string)
@@ -596,3 +607,9 @@ scsi0:{{ .DiskNumber }}.fileName = "{{ .DiskName}}-{{ .DiskNumber }}.vmdk"
scsi0:{{ .DiskNumber }}.present = "TRUE"
scsi0:{{ .DiskNumber }}.redo = ""
`
const ExtraCDRomTemplate = `
{{ .CDROMType }}1:{{ .CDROMType_PrimarySecondary }}.present = "TRUE"
{{ .CDROMType }}1:{{ .CDROMType_PrimarySecondary }}.fileName = "{{ .CDROM_PATH }}"
{{ .CDROMType }}1:{{ .CDROMType_PrimarySecondary }}.deviceType = "cdrom-image"
`
+15 -10
View File
@@ -56,6 +56,20 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
state.Put("driverConfig", &b.config.DriverConfig)
state.Put("temporaryDevices", []string{}) // Devices (in .vmx) created by packer during building
var stepBootCommand multistep.Step = &vmwcommon.StepVNCBootCommand{
Config: b.config.VNCConfig,
VMName: b.config.VMName,
Ctx: b.config.ctx,
}
if b.config.USBKeyBoard {
stepBootCommand = &vmwcommon.StepUSBBootCommand{
Config: b.config.VNCConfig.BootConfig,
KeyInterval: b.config.VNCConfig.BootKeyInterval,
VMName: b.config.VMName,
Ctx: b.config.ctx,
}
}
// Build the steps.
steps := []multistep.Step{
&vmwcommon.StepPrepareTools{
@@ -126,14 +140,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
DurationBeforeStop: 5 * time.Second,
Headless: b.config.Headless,
},
&vmwcommon.StepTypeBootCommand{
BootWait: b.config.BootWait,
VNCEnabled: !b.config.DisableVNC,
BootCommand: b.config.FlatBootCommand(),
VMName: b.config.VMName,
Ctx: b.config.ctx,
KeyInterval: b.config.VNCConfig.BootKeyInterval,
},
stepBootCommand,
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: driver.CommHost,
@@ -203,5 +210,3 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
return vmwcommon.NewArtifact(b.config.RemoteType, b.config.Format, exportOutputPath,
b.config.VMName, b.config.SkipExport, b.config.KeepRegistered, state)
}
// Cancel.
+12 -23
View File
@@ -9,7 +9,6 @@ import (
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/bootcommand"
"github.com/hashicorp/packer/common/shutdowncommand"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
@@ -21,7 +20,7 @@ type Config struct {
common.PackerConfig `mapstructure:",squash"`
common.HTTPConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"`
bootcommand.VNCConfig `mapstructure:",squash"`
vmwcommon.BootConfigWrapper `mapstructure:",squash"`
vmwcommon.DriverConfig `mapstructure:",squash"`
vmwcommon.OutputConfig `mapstructure:",squash"`
vmwcommon.RunConfig `mapstructure:",squash"`
@@ -77,16 +76,20 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
"packer-%s-%d", c.PackerBuildName, interpolate.InitTime.Unix())
}
// Prepare the errors
// Accumulate any errors and warnings
var warnings []string
var errs *packer.MultiError
vncWarnings, vncErrs := c.BootConfigWrapper.Prepare(&c.ctx, &c.DriverConfig)
warnings = append(warnings, vncWarnings...)
errs = packer.MultiErrorAppend(errs, vncErrs...)
errs = packer.MultiErrorAppend(errs, c.DriverConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.HTTPConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.OutputConfig.Prepare(&c.ctx, &c.PackerConfig)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.RunConfig.Prepare(&c.ctx, &c.BootConfigWrapper)...)
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.SSHConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
@@ -101,16 +104,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
fmt.Errorf("source_path is invalid: %s", err))
}
}
} else {
// Remote configuration validation
if c.RemoteHost == "" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("remote_host must be specified"))
}
if c.RemoteType != "esx5" {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Only 'esx5' value is accepted for remote_type"))
if c.Headless && c.DisableVNC {
warnings = append(warnings,
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
"you won't be able to see any output.")
}
}
@@ -143,20 +140,12 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, err)
}
// Warnings
var warnings []string
if c.ShutdownCommand == "" {
warnings = append(warnings,
"A shutdown_command was not specified. Without a shutdown command, Packer\n"+
"will forcibly halt the virtual machine, which may result in data loss.")
}
if c.Headless && c.DisableVNC {
warnings = append(warnings,
"Headless mode uses VNC to retrieve output. Since VNC has been disabled,\n"+
"you won't be able to see any output.")
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {
return warnings, errs
+4
View File
@@ -20,6 +20,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
@@ -28,6 +29,7 @@ type FlatConfig struct {
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc" hcl:"disable_vnc"`
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval" hcl:"boot_key_interval"`
USBKeyBoard *bool `mapstructure:"usb_keyboard" cty:"usb_keyboard" hcl:"usb_keyboard"`
CleanUpRemoteCache *bool `mapstructure:"cleanup_remote_cache" required:"false" cty:"cleanup_remote_cache" hcl:"cleanup_remote_cache"`
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path" hcl:"fusion_app_path"`
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type" hcl:"remote_type"`
@@ -140,6 +142,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
@@ -148,6 +151,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
"usb_keyboard": &hcldec.AttrSpec{Name: "usb_keyboard", Type: cty.Bool, Required: false},
"cleanup_remote_cache": &hcldec.AttrSpec{Name: "cleanup_remote_cache", Type: cty.Bool, Required: false},
"fusion_app_path": &hcldec.AttrSpec{Name: "fusion_app_path", Type: cty.String, Required: false},
"remote_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
+4
View File
@@ -21,6 +21,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server" hcl:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
@@ -30,6 +31,7 @@ type FlatConfig struct {
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
VMName *string `mapstructure:"vm_name" cty:"vm_name" hcl:"vm_name"`
@@ -144,6 +146,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"vcenter_server": &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
@@ -153,6 +156,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
+7
View File
@@ -37,6 +37,8 @@ type CloneConfig struct {
// available network. If the network is inside a network folder in vCenter,
// you need to provide the full path to the network.
Network string `mapstructure:"network"`
// Sets a custom Mac Address to the network adapter. If set, the [network](#network) must be also specified.
MacAddress string `mapstructure:"mac_address"`
// VM notes.
Notes string `mapstructure:"notes"`
// Set the vApp Options to a virtual machine.
@@ -56,6 +58,10 @@ func (c *CloneConfig) Prepare() []error {
errs = append(errs, fmt.Errorf("'linked_clone' and 'disk_size' cannot be used together"))
}
if c.MacAddress != "" && c.Network == "" {
errs = append(errs, fmt.Errorf("'network' is required when 'mac_address' is specified"))
}
return errs
}
@@ -92,6 +98,7 @@ func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multist
Datastore: s.Location.Datastore,
LinkedClone: s.Config.LinkedClone,
Network: s.Config.Network,
MacAddress: s.Config.MacAddress,
Annotation: s.Config.Notes,
VAppProperties: s.Config.VAppConfig.Properties,
})
@@ -13,6 +13,7 @@ type FlatCloneConfig struct {
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size" hcl:"disk_size"`
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone" hcl:"linked_clone"`
Network *string `mapstructure:"network" cty:"network" hcl:"network"`
MacAddress *string `mapstructure:"mac_address" cty:"mac_address" hcl:"mac_address"`
Notes *string `mapstructure:"notes" cty:"notes" hcl:"notes"`
VAppConfig *FlatvAppConfig `mapstructure:"vapp" cty:"vapp" hcl:"vapp"`
}
@@ -33,6 +34,7 @@ func (*FlatCloneConfig) HCL2Spec() map[string]hcldec.Spec {
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"linked_clone": &hcldec.AttrSpec{Name: "linked_clone", Type: cty.Bool, Required: false},
"network": &hcldec.AttrSpec{Name: "network", Type: cty.String, Required: false},
"mac_address": &hcldec.AttrSpec{Name: "mac_address", Type: cty.String, Required: false},
"notes": &hcldec.AttrSpec{Name: "notes", Type: cty.String, Required: false},
"vapp": &hcldec.BlockSpec{TypeName: "vapp", Nested: hcldec.ObjectSpec((*FlatvAppConfig)(nil).HCL2Spec())},
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
)
type HardwareConfig struct {
// Number of CPU sockets.
// Number of CPU cores.
CPUs int32 `mapstructure:"CPUs"`
// Number of CPU cores per socket.
CpuCores int32 `mapstructure:"cpu_cores"`
+12 -3
View File
@@ -69,6 +69,7 @@ type CloneConfig struct {
Datastore string
LinkedClone bool
Network string
MacAddress string
Annotation string
VAppProperties map[string]string
}
@@ -98,7 +99,7 @@ type NIC struct {
}
type CreateConfig struct {
DiskControllerType []string // example: "scsi", "pvscsi", "lsilogic"
DiskControllerType []string // example: "scsi", "pvscsi", "nvme", "lsilogic"
Annotation string
Name string
@@ -344,7 +345,9 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
return nil, err
}
adapter.GetVirtualEthernetCard().Backing = backing
current := adapter.GetVirtualEthernetCard()
current.Backing = backing
current.MacAddress = config.MacAddress
config := &types.VirtualDeviceConfigSpec{
Device: adapter.(types.BaseVirtualDevice),
@@ -841,7 +844,13 @@ func addDisk(_ *VCenterDriver, devices object.VirtualDeviceList, config *CreateC
var controllers []types.BaseVirtualController
for _, controllerType := range config.DiskControllerType {
device, err := devices.CreateSCSIController(controllerType)
var device types.BaseVirtualDevice
var err error
if controllerType == "nvme" {
device, err = devices.CreateNVMEController()
} else {
device, err = devices.CreateSCSIController(controllerType)
}
if err != nil {
return nil, err
}
+22 -10
View File
@@ -42,8 +42,10 @@ func defaultConfig() map[string]interface{} {
"ssh_username": "root",
"ssh_password": "jetbrains",
"vm_name": commonT.NewVMName(),
"disk_size": 2048,
"vm_name": commonT.NewVMName(),
"storage": map[string]interface{}{
"disk_size": 2048,
},
"communicator": "none", // do not start the VM without any bootable devices
}
@@ -338,7 +340,9 @@ func TestISOBuilderAcc_networkCard(t *testing.T) {
func networkCardConfig() string {
config := defaultConfig()
config["network_card"] = "vmxnet3"
config["network_adapters"] = map[string]interface{}{
"network_card": "vmxnet3",
}
return commonT.RenderConfig(config)
}
@@ -421,12 +425,18 @@ func fullConfig() map[string]interface{} {
"vm_name": commonT.NewVMName(),
"host": "esxi-1.vsphere65.test",
"RAM": 512,
"disk_controller_type": "pvscsi",
"disk_size": 1024,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"guest_os_type": "other3xLinux64Guest",
"RAM": 512,
"disk_controller_type": []string{
"pvscsi",
},
"storage": map[string]interface{}{
"disk_size": 1024,
"disk_thin_provisioned": true,
},
"network_adapters": map[string]interface{}{
"network_card": "vmxnet3",
},
"guest_os_type": "other3xLinux64Guest",
"iso_paths": []string{
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso",
@@ -550,7 +560,9 @@ func clusterDRSConfig() string {
config["cluster"] = "cluster2"
config["host"] = ""
config["datastore"] = "datastore3" // bug #183
config["network"] = "VM Network" // bug #183
config["network_adapters"] = map[string]interface{}{
"network": "VM Network",
}
return commonT.RenderConfig(config)
}
+2
View File
@@ -21,6 +21,7 @@ type FlatConfig struct {
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server" hcl:"vcenter_server"`
Username *string `mapstructure:"username" cty:"username" hcl:"username"`
Password *string `mapstructure:"password" cty:"password" hcl:"password"`
@@ -156,6 +157,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"vcenter_server": &hcldec.AttrSpec{Name: "vcenter_server", Type: cty.String, Required: false},
"username": &hcldec.AttrSpec{Name: "username", Type: cty.String, Required: false},
"password": &hcldec.AttrSpec{Name: "password", Type: cty.String, Required: false},
+4 -1
View File
@@ -149,7 +149,10 @@ type CreateConfig struct {
// here](https://code.vmware.com/apis/358/vsphere/doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html)
// for a full list of possible values.
GuestOSType string `mapstructure:"guest_os_type"`
// Set VM disk controller type. Example `lsilogic`, pvscsi`, or `scsi`. Use a list to define additional controllers. Defaults to `lsilogic`
// Set VM disk controller type. Example `lsilogic`, `pvscsi`, `nvme`, or `scsi`. Use a list to define additional controllers.
// Defaults to `lsilogic`. See
// [SCSI, SATA, and NVMe Storage Controller Conditions, Limitations, and Compatibility](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html#GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362)
// for additional details.
DiskControllerType []string `mapstructure:"disk_controller_type"`
// A collection of one or more disks to be provisioned along with the VM.
Storage []DiskConfig `mapstructure:"storage"`
+15
View File
@@ -142,3 +142,18 @@ type HCL2UpgradeArgs struct {
MetaArgs
OutputFile string
}
func (ia *InitArgs) AddFlagSets(flags *flag.FlagSet) {
flags.StringVar(&ia.PluginDir, "plugin-dir", "", "")
flags.BoolVar(&ia.GetPlugins, "get-plugins", true, "")
flags.BoolVar(&ia.Upgrade, "upgrade", false, "")
ia.MetaArgs.AddFlagSets(flags)
}
//InitArgs respresents a parsed cli line for packer init
type InitArgs struct {
MetaArgs
GetPlugins bool
PluginDir string
Upgrade bool
}
+1 -5
View File
@@ -60,11 +60,7 @@ func (c *ConsoleCommand) RunContext(ctx context.Context, cla *ConsoleArgs) int {
return ret
}
diags := packerStarter.Initialize()
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}
_ = packerStarter.Initialize()
// Determine if stdin is a pipe. If so, we evaluate directly.
if c.StdinPiped() {
+5
View File
@@ -32,6 +32,11 @@ func Test_console(t *testing.T) {
{"var.images", []string{"console", filepath.Join(testFixture("var-arg"), "map.pkr.hcl")}, nil, "{\n" + ` "key" = "value"` + "\n}\n"},
{"path.cwd", []string{"console", filepath.Join(testFixture("var-arg"), "map.pkr.hcl")}, nil, strings.ReplaceAll(cwd, `\`, `/`) + "\n"},
{"path.root", []string{"console", filepath.Join(testFixture("var-arg"), "map.pkr.hcl")}, nil, strings.ReplaceAll(testFixture("var-arg"), `\`, `/`) + "\n"},
{"var.list_of_string[0]", []string{"console", `-var=list_of_string=["first"]`, filepath.Join(testFixture("hcl", "variables", "list_of_string"))}, nil, "first\n"},
{"var.untyped[2]", []string{"console", filepath.Join(testFixture("hcl", "variables", "untyped_var"))}, nil, "strings\n"},
{"var.untyped", []string{"console", `-var=untyped=just_a_string`, filepath.Join(testFixture("hcl", "variables", "untyped_var"))}, nil, "just_a_string\n"},
{"var.untyped", []string{"console", filepath.Join(testFixture("hcl", "variables", "untyped_var", "var.pkr.hcl"))}, nil, "<unknown>\n"},
{"var.untyped", []string{"console", filepath.Join(testFixture("hcl", "variables", "untyped_var", "var.pkr.hcl"))}, []string{"PKR_VAR_untyped=just_a_string"}, "just_a_string\n"},
}
for _, tc := range tc {
+86
View File
@@ -0,0 +1,86 @@
package command
import (
"context"
"os"
)
// InitCommand initializes a Packer working directory.
type InitCommand struct {
Meta
}
func (c *InitCommand) Run(args []string) int {
ctx, cleanup := handleTermInterrupt(c.Ui)
defer cleanup()
cla, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
return c.RunContext(ctx, cla)
}
func (c *InitCommand) ParseArgs(args []string) (*InitArgs, int) {
var cfg InitArgs
flags := c.Meta.FlagSet("init", FlagSetVars)
flags.Usage = func() { c.Ui.Say(c.Help()) }
cfg.AddFlagSets(flags)
if err := flags.Parse(args); err != nil {
return &cfg, 1
}
args = flags.Args()
if len(args) > 0 {
cfg.Path = args[0]
}
// Init command should treat an empty invocation as if it was run
// against the current working directory.
if cfg.Path == "" {
cfg.Path, _ = os.Getwd()
}
return &cfg, 0
}
func (c *InitCommand) RunContext(ctx context.Context, cla *InitArgs) int {
packerStarter, ret := c.GetConfig(&cla.MetaArgs)
if ret != 0 {
return ret
}
_ = packerStarter.Initialize()
return 0
}
func (c *InitCommand) Help() string {
helpText := `
Usage: packer init [options]
Initialize a new or existing Packer working directory by downloading
builders, provisioners, and post-processors defined in the template.
This is the first command that should be executed when working with a new
or existing template. Running this command in an empty directory will
will perform no operation, and will need to be executed once a template
has been created to initialize the working directory.
It is safe to run init multiple times on a template to update the builders,
provisioners, post-processors with changes in the template configuration file.
Options:
-get-plugins=false Skips plugin installation.
-plugin-dir=PATH Skips plugin installation and loads plugins only from the specified directory.
-upgrade=true Updates installed plugins to the latest available version.
`
return helpText
}
func (c *InitCommand) Synopsis() string {
return "Initializes a Packer working directory"
}
+9 -2
View File
@@ -69,13 +69,19 @@ local.fruit: "banana"
<no post-processor>
`},
{[]string{"inspect", "-var=fruit=peach", "-var=unknown_string=also_peach", `-var=unknown_unknown="peach_too"`, filepath.Join(testFixture("hcl"), "inspect", "fruit_string.pkr.hcl")}, nil, `Packer Inspect: HCL2 mode
{[]string{"inspect", "-var=fruit=peach",
"-var=unknown_string=also_peach",
`-var=unknown_unknown=["peach_too"]`,
`-var=unknown_list_of_string=["first_peach", "second_peach"]`,
filepath.Join(testFixture("hcl"), "inspect", "fruit_string.pkr.hcl")}, nil,
`Packer Inspect: HCL2 mode
> input-variables:
var.fruit: "peach"
var.unknown_list_of_string: "[\n \"first_peach\",\n \"second_peach\",\n]"
var.unknown_string: "also_peach"
var.unknown_unknown: "peach_too"
var.unknown_unknown: "[\"peach_too\"]"
> local-variables:
@@ -88,6 +94,7 @@ var.unknown_unknown: "peach_too"
> input-variables:
var.fruit: "peach"
var.unknown_list_of_string: "<unknown>"
var.unknown_string: "<unknown>"
var.unknown_unknown: "<unknown>"
@@ -8,5 +8,10 @@ variable "unknown_string" {
type = string
}
variable "unknown_list_of_string" {
type = list(string)
}
variable "unknown_unknown" {
}
@@ -0,0 +1,4 @@
variable "list_of_string" {
type = list(string)
}
@@ -0,0 +1,2 @@
untyped = ["slice", "of", "strings"]
@@ -0,0 +1,3 @@
variable "untyped" {
}
+6
View File
@@ -64,5 +64,11 @@ func init() {
Meta: *CommandMeta,
}, nil
},
"init": func() (cli.Command, error) {
return &command.InitCommand{
Meta: *CommandMeta,
}, nil
},
}
}
+99
View File
@@ -0,0 +1,99 @@
//go:generate struct-markdown
package common
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/packer/template/interpolate"
)
// An iso (CD) containing custom files can be made available for your build.
//
// By default, no extra CD will be attached. All files listed in this setting
// get placed into the root directory of the CD and the CD is attached as the
// second CD device.
//
// This config exists to work around modern operating systems that have no
// way to mount floppy disks, which was our previous go-to for adding files at
// boot time.
type CDConfig struct {
// A list of files to place onto a CD that is attached when the VM is
// booted. This can include either files or directories; any directories
// will be copied onto the CD recursively, preserving directory structure
// hierarchy. Symlinks will have the link's target copied into the directory
// tree on the CD where the symlink was. File globbing is allowed.
//
// Usage example (JSON):
//
// ```json
// "cd_files": ["./somedirectory/meta-data", "./somedirectory/user-data"],
// "cd_label": "cidata",
// ```
//
// Usage example (HCL):
//
// ```hcl
// cd_files = ["./somedirectory/meta-data", "./somedirectory/user-data"]
// cd_label = "cidata"
// ```
//
// The above will create a CD with two files, user-data and meta-data in the
// CD root. This specific example is how you would create a CD that can be
// used for an Ubuntu 20.04 autoinstall.
//
// Since globbing is also supported,
//
// ```hcl
// cd_files = ["./somedirectory/*"]
// cd_label = "cidata"
// ```
//
// Would also be an acceptable way to define the above cd. The difference
// between providing the directory with or without the glob is whether the
// directory itself or its contents will be at the CD root.
//
// Use of this option assumes that you have a command line tool installed
// that can handle the iso creation. Packer will use one of the following
// tools:
//
// * xorriso
// * mkisofs
// * hdiutil (normally found in macOS)
// * oscdimg (normally found in Windows as part of the Windows ADK)
CDFiles []string `mapstructure:"cd_files"`
CDLabel string `mapstructure:"cd_label"`
}
func (c *CDConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
var err error
if c.CDFiles == nil {
c.CDFiles = make([]string, 0)
}
// Create new file list based on globbing.
var files []string
for _, path := range c.CDFiles {
if strings.ContainsAny(path, "*?[") {
var globbedFiles []string
globbedFiles, err = filepath.Glob(path)
if len(globbedFiles) > 0 {
files = append(files, globbedFiles...)
}
} else {
_, err = os.Stat(path)
files = append(files, path)
}
if err != nil {
errs = append(errs, fmt.Errorf("Bad CD disk file '%s': %s", path, err))
}
c.CDFiles = files
}
return errs
}
+73
View File
@@ -0,0 +1,73 @@
package common
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCDPrepare(t *testing.T) {
type testCases struct {
CDConfig CDConfig
ErrExpected bool
Reason string
ExpectedCDFiles []string
}
tcs := []testCases{
{
CDConfig: CDConfig{},
ErrExpected: false,
Reason: "TestNilCD: nil CD array should not fail",
ExpectedCDFiles: []string{},
},
{
CDConfig: CDConfig{CDFiles: make([]string, 0)},
ErrExpected: false,
Reason: "TestEmptyArrayCD: empty CD array should never fail",
ExpectedCDFiles: []string{},
},
{
CDConfig: CDConfig{CDFiles: []string{"extra_iso_config.go"}},
ErrExpected: false,
Reason: "TestExistingCDFile: array with existing CD should not fail",
ExpectedCDFiles: []string{"extra_iso_config.go"},
},
{
CDConfig: CDConfig{CDFiles: []string{"does_not_exist.foo"}},
ErrExpected: true,
Reason: "TestNonExistingCDFile: array with non existing CD should return errors",
ExpectedCDFiles: []string{"does_not_exist.foo"},
},
{
CDConfig: CDConfig{CDFiles: []string{"extra_iso_config*"}},
ErrExpected: false,
Reason: "TestGlobbingCDFile: Glob should work",
ExpectedCDFiles: []string{"extra_iso_config.go", "extra_iso_config_test.go"},
},
}
for _, tc := range tcs {
c := tc.CDConfig
errs := c.Prepare(nil)
if (len(errs) != 0) != tc.ErrExpected {
t.Fatal(tc.Reason)
}
assert.Equal(t, c.CDFiles, tc.ExpectedCDFiles)
}
}
func TestMultiErrorCDFiles(t *testing.T) {
c := CDConfig{
CDFiles: []string{"extra_iso_config.foo", "extra_iso_config.go",
"extra_iso_config.bar", "extra_iso_config_test.go", "extra_iso_config.baz"},
}
errs := c.Prepare(nil)
if len(errs) == 0 {
t.Fatal("array with non existing CD should return errors")
}
expectedErrors := 3
if count := len(errs); count != expectedErrors {
t.Fatalf("array with %v non existing CD should return %v errors but it is returning %v", expectedErrors, expectedErrors, count)
}
}
+9
View File
@@ -34,6 +34,10 @@ type HTTPConfig struct {
// This is the bind address for the HTTP server. Defaults to 0.0.0.0 so that
// it will work with any network interface.
HTTPAddress string `mapstructure:"http_bind_address"`
// This is the bind interface for the HTTP server. Defaults to the first
// interface with a non-loopback address. Either `http_bind_address` or
// `http_interface` can be specified.
HTTPInterface string `mapstructure:"http_interface" undocumented:"true"`
}
func (c *HTTPConfig) Prepare(ctx *interpolate.Context) []error {
@@ -57,5 +61,10 @@ func (c *HTTPConfig) Prepare(ctx *interpolate.Context) []error {
errors.New("http_port_min must be less than http_port_max"))
}
if c.HTTPInterface != "" && c.HTTPAddress == "0.0.0.0" {
errs = append(errs,
errors.New("either http_interface of http_bind_address can be specified"))
}
return errs
}
+256
View File
@@ -0,0 +1,256 @@
package common
import (
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/hashicorp/packer/helper/builder/localexec"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
)
// StepCreateCD will create a CD disk with the given files.
type StepCreateCD struct {
// Files can be either files or directories. Any files provided here will
// be written to the root of the CD. Directories will be written to the
// root of the CD as well, but will retain their subdirectory structure.
Files []string
Label string
CDPath string
filesAdded map[string]bool
}
func (s *StepCreateCD) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if len(s.Files) == 0 {
log.Println("No CD files specified. CD disk will not be made.")
return multistep.ActionContinue
}
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating CD disk...")
if s.Label == "" {
s.Label = "packer"
} else {
log.Printf("CD label is set to %s", s.Label)
}
// Track what files are added. Used for testing step.
s.filesAdded = make(map[string]bool)
// Create a temporary file to be our CD drive
CDF, err := tmp.File("packer*.iso")
// Set the path so we can remove it later
CDPath := CDF.Name()
CDF.Close()
os.Remove(CDPath)
if err != nil {
state.Put("error",
fmt.Errorf("Error creating temporary file for CD: %s", err))
return multistep.ActionHalt
}
log.Printf("CD path: %s", CDPath)
s.CDPath = CDPath
// Consolidate all files provided into a single directory to become our
// "root" directory.
rootFolder, err := tmp.Dir("packer_to_cdrom")
if err != nil {
state.Put("error",
fmt.Errorf("Error creating temporary file for CD: %s", err))
return multistep.ActionHalt
}
for _, toAdd := range s.Files {
err = s.AddFile(rootFolder, toAdd)
if err != nil {
state.Put("error",
fmt.Errorf("Error creating temporary file for CD: %s", err))
return multistep.ActionHalt
}
}
cmd, err := retrieveCDISOCreationCommand(s.Label, rootFolder, CDPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
err = localexec.RunAndStream(cmd, ui, []string{})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message("Done copying paths from CD_dirs")
// Set the path to the CD so it can be used later
state.Put("cd_path", CDPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCreateCD) Cleanup(multistep.StateBag) {
if s.CDPath != "" {
log.Printf("Deleting CD disk: %s", s.CDPath)
os.Remove(s.CDPath)
}
}
type cdISOCreationCommand struct {
Name string
Command func(path string, label string, source string, dest string) *exec.Cmd
}
var supportedCDISOCreationCommands []cdISOCreationCommand = []cdISOCreationCommand{
{
"xorriso", func(path string, label string, source string, dest string) *exec.Cmd {
return exec.Command(
path,
"-as", "genisoimage",
"-rock",
"-joliet",
"-volid", label,
"-output", dest,
source)
},
},
{
"mkisofs", func(path string, label string, source string, dest string) *exec.Cmd {
return exec.Command(
path,
"-joliet",
"-volid", label,
"-o", dest,
source)
},
},
{
"hdiutil", func(path string, label string, source string, dest string) *exec.Cmd {
return exec.Command(
path,
"-o", dest,
"-hfs",
"-joliet",
"-iso",
"-default-volume-name", label,
source)
},
},
{
"oscdimg", func(path string, label string, source string, dest string) *exec.Cmd {
return exec.Command(
path,
"-j1",
"-o",
"-m",
"-l"+label,
source,
dest)
},
},
}
func retrieveCDISOCreationCommand(label string, source string, dest string) (*exec.Cmd, error) {
for _, c := range supportedCDISOCreationCommands {
path, err := exec.LookPath(c.Name)
if err != nil {
continue
}
return c.Command(path, label, source, dest), nil
}
var commands = make([]string, 0, len(supportedCDISOCreationCommands))
for _, c := range supportedCDISOCreationCommands {
commands = append(commands, c.Name)
}
return nil, fmt.Errorf(
"could not find a supported CD ISO creation command (the supported commands are: %s)",
strings.Join(commands, ", "))
}
func (s *StepCreateCD) AddFile(dst, src string) error {
finfo, err := os.Stat(src)
if err != nil {
return fmt.Errorf("Error adding path to CD: %s", err)
}
// add a file
if !finfo.IsDir() {
inputF, err := os.Open(src)
if err != nil {
return err
}
defer inputF.Close()
// Create a new file in the root directory
dest, err := os.Create(filepath.Join(dst, finfo.Name()))
if err != nil {
return fmt.Errorf("Error opening file for copy %s to CD root", src)
}
defer dest.Close()
nBytes, err := io.Copy(dest, inputF)
if err != nil {
return fmt.Errorf("Error copying %s to CD root", src)
}
s.filesAdded[src] = true
log.Printf("Wrote %d bytes to %s", nBytes, finfo.Name())
return err
}
// Add a directory and its subdirectories
visit := func(pathname string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// add a file
if !fi.IsDir() {
inputF, err := os.Open(pathname)
if err != nil {
return err
}
defer inputF.Close()
fileDst, err := os.Create(filepath.Join(dst, pathname))
if err != nil {
return fmt.Errorf("Error opening file %s on CD", src)
}
defer fileDst.Close()
nBytes, err := io.Copy(fileDst, inputF)
if err != nil {
return fmt.Errorf("Error copying %s to CD", src)
}
s.filesAdded[pathname] = true
log.Printf("Wrote %d bytes to %s", nBytes, pathname)
return err
}
if fi.Mode().IsDir() {
// create the directory on the CD, continue walk.
err := os.Mkdir(filepath.Join(dst, pathname), fi.Mode())
if err != nil {
err = fmt.Errorf("error creating new directory %s: %s",
filepath.Join(dst, pathname), err)
}
return err
}
return err
}
return filepath.Walk(src, visit)
}
+119
View File
@@ -0,0 +1,119 @@
package common
import (
"bytes"
"context"
"io/ioutil"
"os"
"path"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func TestStepCreateCD_Impl(t *testing.T) {
var raw interface{}
raw = new(StepCreateCD)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("StepCreateCD should be a step")
}
}
func testStepCreateCDState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
func TestStepCreateCD(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the disk management binaries.")
}
state := testStepCreateCDState(t)
step := new(StepCreateCD)
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
files := make([]string, 3)
tempFileNames := []string{"test_cd_roms.tmp", "test cd files.tmp",
"Test-Test-Test5.tmp"}
for i, fname := range tempFileNames {
files[i] = path.Join(dir, fname)
_, err := os.Create(files[i])
if err != nil {
t.Fatalf("err: %s", err)
}
}
step.Files = files
action := step.Run(context.Background(), state)
if err, ok := state.GetOk("error"); ok {
t.Fatalf("state should be ok for %v: %s", step.Files, err)
}
if action != multistep.ActionContinue {
t.Fatalf("bad action: %#v for %v", action, step.Files)
}
CD_path := state.Get("cd_path").(string)
if _, err := os.Stat(CD_path); err != nil {
t.Fatalf("file not found: %s for %v", CD_path, step.Files)
}
if len(step.filesAdded) != 3 {
t.Fatalf("expected 3 files, found %d for %v", len(step.filesAdded), step.Files)
}
step.Cleanup(state)
if _, err := os.Stat(CD_path); err == nil {
t.Fatalf("file found: %s for %v", CD_path, step.Files)
}
}
func TestStepCreateCD_missing(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the disk management binaries.")
}
state := testStepCreateCDState(t)
step := new(StepCreateCD)
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
expected := 0
step.Files = []string{"missing file.tmp"}
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v for %v", action, step.Files)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatalf("state should not be ok for %v", step.Files)
}
CD_path := state.Get("cd_path")
if CD_path != nil {
t.Fatalf("CD_path is not nil for %v", step.Files)
}
if len(step.filesAdded) != expected {
t.Fatalf("expected %d, found %d for %v", expected, len(step.filesAdded), step.Files)
}
}
+1 -8
View File
@@ -26,13 +26,10 @@ require (
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
github.com/cheggaaa/pb v1.0.27
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/creack/goselect v0.1.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 // indirect
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
github.com/digitalocean/godo v1.11.1
github.com/docker/docker v0.0.0-20180422163414-57142e89befe // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08
github.com/exoscale/egoscale v0.18.1
@@ -52,7 +49,6 @@ require (
github.com/gophercloud/gophercloud v0.12.0
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026
github.com/hashicorp/consul/api v1.4.0
@@ -103,8 +99,6 @@ require (
github.com/mitchellh/reflectwalk v1.0.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef // indirect
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b
github.com/oracle/oci-go-sdk v18.0.0+incompatible
@@ -115,9 +109,8 @@ require (
github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca
github.com/posener/complete v1.2.3
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200903143645-c0ce17a0443d
github.com/shirou/gopsutil v2.18.12+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
+7 -18
View File
@@ -143,8 +143,6 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -159,12 +157,10 @@ github.com/digitalocean/godo v1.11.1 h1:OsTh37YFKk+g6DnAOrkXJ9oDArTkRx5UTkBJ2EWA
github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/diskfs/go-diskfs v1.1.1 h1:rMjLpaydtXGVZb7mdkRGK1+//30i76nKAit89zUzeaI=
github.com/diskfs/go-diskfs v1.1.1/go.mod h1:afUPxxu+x1snp4aCY2bKR0CoZ/YFJewV3X2UEr2nPZE=
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/docker v0.0.0-20180422163414-57142e89befe h1:VW8TnWi0CZgg7oCv0wH6evNwkzcJg/emnw4HrVIWws4=
github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY=
@@ -282,8 +278,6 @@ github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ=
@@ -362,8 +356,6 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o=
github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@@ -503,10 +495,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef h1:E/seV1Rtsnr2juBw1Dfz4iDPT3/5s1H/BATx+ePmSyo=
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U=
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 h1:y6FZWUBBt1iPmJyGbGza3ncvVBMKzgd32oFChRZR7Do=
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
@@ -550,8 +538,6 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jO
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 h1:4qPms2txLWMLXKzqlnYSulKRS4cS9aYgPtAEpUelQok=
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
@@ -560,8 +546,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70 h1:DaqC32ZwOuO4ctgg9qAdKnlQxwFPkKmCOEqwSNwYy7c=
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200903143645-c0ce17a0443d h1:pK33AoOAlzj6gJs/V1vi2Ouj2u1Ww84pREwxFi1oxkM=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200903143645-c0ce17a0443d/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
@@ -761,6 +747,7 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -881,6 +868,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM=
gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+3 -3
View File
@@ -71,9 +71,9 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
if len(hclFiles)+len(jsonFiles) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Could not find any config file in " + filename,
Detail: "A config file must be suffixed with `.pkr.hcl` or " +
"`.pkr.json`. A folder can be referenced.",
Summary: "No configuration files found in " + filename,
Detail: "A valid configuration file must be present. Configuration \n" +
"files must be suffixed with `.pkr.hcl` or `.pkr.json`.",
})
}
for _, filename := range hclFiles {
+3 -1
View File
@@ -426,7 +426,9 @@ func (cfg *PackerConfig) collectInputVariableValues(env []string, files []*hcl.F
// The specified filename is to identify the source of where value originated from in the diagnostics report, if there is an error.
func expressionFromVariableDefinition(filename string, value string, variableType cty.Type) (hclsyntax.Expression, hcl.Diagnostics) {
switch variableType {
case cty.String, cty.Number:
case cty.String, cty.Number, cty.NilType:
// when the type is nil (not set in a variable block) we default to
// interpreting everything as a string literal.
return &hclsyntax.LiteralValueExpr{Val: cty.StringVal(value)}, nil
default:
return hclsyntax.ParseExpression([]byte(value), filename, hcl.Pos{Line: 1, Column: 1})
+109
View File
@@ -0,0 +1,109 @@
package localexec
import (
"fmt"
"io"
"log"
"os/exec"
"regexp"
"strings"
"sync"
"syscall"
"github.com/hashicorp/packer/common/iochan"
"github.com/hashicorp/packer/packer"
)
func RunAndStream(cmd *exec.Cmd, ui packer.Ui, sensitive []string) error {
stdout_r, stdout_w := io.Pipe()
stderr_r, stderr_w := io.Pipe()
defer stdout_w.Close()
defer stderr_w.Close()
// Scrub any sensitive values from being printed to Packer ui.
packer.LogSecretFilter.Set(sensitive...)
args := make([]string, len(cmd.Args)-1)
copy(args, cmd.Args[1:])
log.Printf("Executing: %s %v", cmd.Path, args)
cmd.Stdout = stdout_w
cmd.Stderr = stderr_w
if err := cmd.Start(); err != nil {
return err
}
// Create the channels we'll use for data
exitCh := make(chan int, 1)
stdoutCh := iochan.LineReader(stdout_r)
stderrCh := iochan.LineReader(stderr_r)
// Start the goroutine to watch for the exit
go func() {
defer stdout_w.Close()
defer stderr_w.Close()
exitStatus := 0
err := cmd.Wait()
if exitErr, ok := err.(*exec.ExitError); ok {
exitStatus = 1
// There is no process-independent way to get the REAL
// exit status so we just try to go deeper.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
exitStatus = status.ExitStatus()
}
}
exitCh <- exitStatus
}()
// This waitgroup waits for the streaming to end
var streamWg sync.WaitGroup
streamWg.Add(2)
streamFunc := func(ch <-chan string) {
defer streamWg.Done()
for data := range ch {
data = cleanOutputLine(data)
if data != "" {
ui.Message(data)
}
}
}
// Stream stderr/stdout
go streamFunc(stderrCh)
go streamFunc(stdoutCh)
// Wait for the process to end and then wait for the streaming to end
exitStatus := <-exitCh
streamWg.Wait()
if exitStatus != 0 {
return fmt.Errorf("Bad exit status: %d", exitStatus)
}
return nil
}
// cleanOutputLine cleans up a line so that '\r' don't muck up the
// UI output when we're reading from a remote command.
func cleanOutputLine(line string) string {
// Build a regular expression that will get rid of shell codes
re := regexp.MustCompile("(?i)\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[a|b|m|k]")
line = re.ReplaceAllString(line, "")
// Trim surrounding whitespace
line = strings.TrimSpace(line)
// Trim up to the first carriage return, since that text would be
// lost anyways.
idx := strings.LastIndex(line, "\r")
if idx > -1 {
line = line[idx+1:]
}
return line
}
@@ -1,4 +1,4 @@
package docker
package localexec
import (
"testing"
+2 -1
View File
@@ -119,7 +119,8 @@ type SSH struct {
// The time to wait for SSH to become available. Packer uses this to
// determine when the machine has booted so this is usually quite long.
// Example value: `10m`.
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
// Deprecated in favor of SSHTimeout
SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout" undocumented:"true"`
// If true, the local SSH agent will be used to authenticate connections to
// the source instance. No temporary keypair will be created, and the
@@ -218,6 +218,9 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact
// Run the steps.
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(ctx, state)
if rawErr, ok := state.GetOk("error"); ok {
return nil, false, false, rawErr.(error)
}
result := &Artifact{
paths: p.config.Paths,
+6
View File
@@ -55,6 +55,12 @@ func uploadToBucket(s3conn *s3.S3, ui packer.Ui, artifact packer.Artifact, bucke
// Compute service allow only `https://storage.yandexcloud.net/...` URLs for Image create process
req.Config.S3ForcePathStyle = aws.Bool(true)
err = req.Build()
if err != nil {
ui.Say(fmt.Sprintf("Failed to build S3 request: %v", err))
return nil, err
}
return &objectSource{
url: req.HTTPRequest.URL.String(),
}, nil
+37 -10
View File
@@ -162,8 +162,8 @@ type Config struct {
// test your playbook. this option is not used if you set an `inventory_file`.
KeepInventoryFile bool `mapstructure:"keep_inventory_file"`
// A requirements file which provides a way to
// install roles with the [ansible-galaxy
// cli](http://docs.ansible.com/ansible/galaxy.html#the-ansible-galaxy-command-line-tool)
// install roles or collections with the [ansible-galaxy
// cli](https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#the-ansible-galaxy-command-line-tool)
// on the local machine before executing `ansible-playbook`. By default, this is empty.
GalaxyFile string `mapstructure:"galaxy_file"`
// The command to invoke ansible-galaxy. By default, this is
@@ -173,11 +173,16 @@ type Config struct {
// Adds `--force` option to `ansible-galaxy` command. By default, this is
// `false`.
GalaxyForceInstall bool `mapstructure:"galaxy_force_install"`
// The path to the directory on your local system to
// install the roles in. Adds `--roles-path /path/to/your/roles` to
// The path to the directory on your local system in which to
// install the roles. Adds `--roles-path /path/to/your/roles` to
// `ansible-galaxy` command. By default, this is empty, and thus `--roles-path`
// option is not added to the command.
RolesPath string `mapstructure:"roles_path"`
// The path to the directory on your local system in which to
// install the collections. Adds `--collections-path /path/to/your/collections` to
// `ansible-galaxy` command. By default, this is empty, and thus `--collections-path`
// option is not added to the command.
CollectionsPath string `mapstructure:"collections_path"`
// When `true`, set up a localhost proxy adapter
// so that Ansible has an IP address to connect to, even if your guest does not
// have an IP address. For example, the adapter is necessary for Docker builds
@@ -624,16 +629,38 @@ func (p *Provisioner) executeGalaxy(ui packer.Ui, comm packer.Communicator) erro
galaxyFile := filepath.ToSlash(p.config.GalaxyFile)
// ansible-galaxy install -r requirements.yml
args := []string{"install", "-r", galaxyFile}
roleArgs := []string{"install", "-r", galaxyFile}
// Instead of modifying args depending on config values and removing or modifying values from
// the slice between role and collection installs, just use 2 slices and simplify everything
collectionArgs := []string{"collection", "install", "-r", galaxyFile}
// Add force to arguments
if p.config.GalaxyForceInstall {
args = append(args, "-f")
}
// Add roles_path argument if specified
if p.config.RolesPath != "" {
args = append(args, "-p", filepath.ToSlash(p.config.RolesPath))
roleArgs = append(roleArgs, "-f")
collectionArgs = append(collectionArgs, "-f")
}
// Add roles_path argument if specified
if p.config.RolesPath != "" {
roleArgs = append(roleArgs, "-p", filepath.ToSlash(p.config.RolesPath))
}
// Add collections_path argument if specified
if p.config.CollectionsPath != "" {
collectionArgs = append(collectionArgs, "-p", filepath.ToSlash(p.config.CollectionsPath))
}
roleInstallError := p.invokeGalaxyCommand(roleArgs, ui, comm)
// Return the error if the role installation failed before attempting the collection install
if roleInstallError != nil {
return roleInstallError
}
// If all is well, proceed with collection install
// This variable isn't strictly necessary but including for readability to match the role installation
collectionInstallError := p.invokeGalaxyCommand(collectionArgs, ui, comm)
return collectionInstallError
}
// Intended to be invoked from p.executeGalaxy depending on the Ansible Galaxy parameters passed to Packer
func (p *Provisioner) invokeGalaxyCommand(args []string, ui packer.Ui, comm packer.Communicator) error {
ui.Message(fmt.Sprintf("Executing Ansible Galaxy"))
cmd := exec.Command(p.config.GalaxyCommand, args...)
@@ -39,6 +39,7 @@ type FlatConfig struct {
GalaxyCommand *string `mapstructure:"galaxy_command" cty:"galaxy_command" hcl:"galaxy_command"`
GalaxyForceInstall *bool `mapstructure:"galaxy_force_install" cty:"galaxy_force_install" hcl:"galaxy_force_install"`
RolesPath *string `mapstructure:"roles_path" cty:"roles_path" hcl:"roles_path"`
CollectionsPath *string `mapstructure:"collections_path" cty:"collections_path" hcl:"collections_path"`
UseProxy *bool `mapstructure:"use_proxy" cty:"use_proxy" hcl:"use_proxy"`
}
@@ -84,6 +85,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"galaxy_command": &hcldec.AttrSpec{Name: "galaxy_command", Type: cty.String, Required: false},
"galaxy_force_install": &hcldec.AttrSpec{Name: "galaxy_force_install", Type: cty.Bool, Required: false},
"roles_path": &hcldec.AttrSpec{Name: "roles_path", Type: cty.String, Required: false},
"collections_path": &hcldec.AttrSpec{Name: "collections_path", Type: cty.String, Required: false},
"use_proxy": &hcldec.AttrSpec{Name: "use_proxy", Type: cty.Bool, Required: false},
}
return s

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