Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d597e93e70 | |||
| 1bb154de5a | |||
| 8069ae4f43 | |||
| e8f7076416 | |||
| c032d463d3 | |||
| caf65781d7 | |||
| 3739970b84 | |||
| ad4ed3c986 | |||
| f94e78077a | |||
| 53331644a6 | |||
| bcd3c33e49 | |||
| 31f08909b4 | |||
| e078bd6bf2 | |||
| 2837d56885 | |||
| 5ca9f6539f | |||
| 8b9009c571 | |||
| 8ffa0d2060 | |||
| ca650c9602 | |||
| 25b889216d | |||
| 81f9f4dd27 | |||
| 24d5824061 | |||
| ad0b413e2d | |||
| 9d5dedc3db | |||
| f8e31bff12 | |||
| 0719f906d4 | |||
| 8aaa1bd7a6 | |||
| 3a5beedddf | |||
| e5cd5a9360 | |||
| 26f3299fb7 | |||
| 40c128767e | |||
| dd54651125 | |||
| b4ff0ea4bc | |||
| 988ab66731 | |||
| 900100f591 | |||
| 254716a02a | |||
| f5dc260cf5 | |||
| 2da89db272 | |||
| e362b7f43f | |||
| 7335695c84 | |||
| 4ecade3908 | |||
| 39183d1b76 | |||
| 9a2908d1d9 | |||
| 611899f7b2 | |||
| 41a8a96821 | |||
| 3d2259a6d4 | |||
| 19f0114035 | |||
| 94a9beaac6 | |||
| 61af278b79 | |||
| bd2aa40d2c | |||
| c7356fb776 | |||
| 850fda6aa1 | |||
| f3633da200 | |||
| 00c6efd5cf | |||
| bca4439149 | |||
| d2dd158fe2 | |||
| dfe8aa51b0 | |||
| 5120ad9d9a | |||
| 20d62654ae | |||
| bce6fdf03a | |||
| 03a0beb683 | |||
| 6334fc2da4 | |||
| 7813ad5ef4 | |||
| ba74602d63 | |||
| 6c4e9e9e68 | |||
| d66f85e375 | |||
| 7a7d3ff350 | |||
| 62e1663846 | |||
| d1a61cac33 | |||
| 602729ac23 | |||
| c492199ba5 | |||
| ab23e839c8 | |||
| efd69aea2a | |||
| a70164f489 | |||
| f74a425661 | |||
| 9156563314 |
@@ -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:
|
||||
|
||||
@@ -422,7 +422,6 @@ func TestBuilderAcc_ECSImageSharing(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// share with catsby
|
||||
const testBuilderAccSharing = `
|
||||
{
|
||||
"builders": [{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -66,6 +66,7 @@ type Config struct {
|
||||
shouldUploadISO bool
|
||||
|
||||
AdditionalISOFiles []storageConfig `mapstructure:"additional_iso_files"`
|
||||
VMInterface string `mapstructure:"vm_interface"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}}},
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")},
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
+19
-20
@@ -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) {}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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"
|
||||
`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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())},
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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" {
|
||||
}
|
||||
@@ -64,5 +64,11 @@ func init() {
|
||||
Meta: *CommandMeta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"init": func() (cli.Command, error) {
|
||||
return &command.InitCommand{
|
||||
Meta: *CommandMeta,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user