Merge pull request #10 from hashicorp/master
merge with hashicorp/packer master branch
This commit is contained in:
commit
5f9718b589
127
CHANGELOG.md
127
CHANGELOG.md
|
@ -1,18 +1,129 @@
|
||||||
## (UNRELEASED)
|
## UNRELEASED
|
||||||
|
|
||||||
|
|
||||||
|
## 1.2.0 (February 9, 2018)
|
||||||
|
|
||||||
|
### BACKWARDS INCOMPATIBILITIES:
|
||||||
|
* 3rd party plugins: We have moved internal dependencies, meaning your 3rd
|
||||||
|
party plugins will no longer compile (however existing builds will still
|
||||||
|
work fine); the work to fix them is minimal and documented in GH-5810.
|
||||||
|
[GH-5810]
|
||||||
|
* builder/amazon: The `ssh_private_ip` option has been removed. Instead, please
|
||||||
|
use `"ssh_interface": "private"`. A fixer has been written for this, which
|
||||||
|
can be invoked with `packer fix`. [GH-5876]
|
||||||
|
* builder/openstack: Extension support has been removed. To use OpenStack
|
||||||
|
builder with the OpenStack Newton (Oct 2016) or earlier, we recommend you
|
||||||
|
use Packer v1.1.2 or earlier version.
|
||||||
|
* core: Affects Windows guests: User variables containing Powershell special
|
||||||
|
characters no longer need to be escaped.[GH-5376]
|
||||||
|
* provisioner/file: We've made destination semantics more consistent across the
|
||||||
|
various communicators. In general, if the destination is a directory, files
|
||||||
|
will be uploaded into the directory instead of failing. This mirrors the
|
||||||
|
behavior of `rsync`. There's a chance some users might be depending on the
|
||||||
|
previous buggy behavior, so it's worth ensuring your configuration is
|
||||||
|
correct. [GH-5426]
|
||||||
|
* provisioner/powershell: Regression from v1.1.1 forcing extra escaping of
|
||||||
|
environment variables in the non-elevated provisioner has been fixed.
|
||||||
|
[GH-5515] [GH-5872]
|
||||||
|
|
||||||
### IMPROVEMENTS:
|
### IMPROVEMENTS:
|
||||||
|
|
||||||
* builder/docker: Remove credentials from being shown in the log. [GH-5666]
|
* **New builder:** `ncloud` for building server images using the NAVER Cloud
|
||||||
* builder/triton: Triton RBAC is now supported. [GH-5741]
|
Platform. [GH-5791]
|
||||||
* provisioner/ansible: Improve user retrieval. [GH-5758]
|
* **New builder:** `oci-classic` for building new custom images for use with
|
||||||
* post-processor/docker: Remove credentials from being shown in the log. [GH-5666]
|
Oracle Cloud Infrastructure Classic Compute. [GH-5819]
|
||||||
* builder/amazon: Warn during prepare if we didn't get both an access key and a secret key when we were expecting one. [GH-5762]
|
* **New builder:** `scaleway` - The Scaleway Packer builder is able to create
|
||||||
* builder/amazon: Replace `InstanceStatusOK` check with `InstanceReady`. This reduces build times universally while still working for all instance types. [GH-5678]
|
new images for use with Scaleway BareMetal and Virtual cloud server.
|
||||||
|
[GH-4770]
|
||||||
* builder/amazon: Add `kms_key_id` option to block device mappings. [GH-5774]
|
* builder/amazon: Add `kms_key_id` option to block device mappings. [GH-5774]
|
||||||
|
* builder/amazon: Add `skip_metadata_api_check` option to skip consulting the
|
||||||
|
amazon metadata service. [GH-5764]
|
||||||
|
* builder/amazon: Add Paris region (eu-west-3) [GH-5718]
|
||||||
|
* builder/amazon: Give better error messages if we have trouble during
|
||||||
|
authentication. [GH-5764]
|
||||||
|
* builder/amazon: Remove Session Token (STS) from being shown in the log.
|
||||||
|
[GH-5665]
|
||||||
|
* builder/amazon: Replace `InstanceStatusOK` check with `InstanceReady`. This
|
||||||
|
reduces build times universally while still working for all instance types.
|
||||||
|
[GH-5678]
|
||||||
|
* builder/amazon: Report which authentication provider we're using. [GH-5764]
|
||||||
|
* builder/amazon: Timeout early if metadata service can't be reached. [GH-5764]
|
||||||
|
* builder/amazon: Warn during prepare if we didn't get both an access key and a
|
||||||
|
secret key when we were expecting one. [GH-5762]
|
||||||
|
* builder/azure: Add validation for incorrect VHD URLs [GH-5695]
|
||||||
|
* builder/docker: Remove credentials from being shown in the log. [GH-5666]
|
||||||
|
* builder/google: Support specifying licenses for images. [GH-5842]
|
||||||
|
* builder/hyper-v: Allow MAC address specification. [GH-5709]
|
||||||
|
* builder/hyper-v: New option to use differential disks and Inline disk
|
||||||
|
creation to improve build time and reduce disk usage [GH-5631]
|
||||||
|
* builder/qemu: Add Intel HAXM support to QEMU builder [GH-5738]
|
||||||
|
* builder/triton: Triton RBAC is now supported. [GH-5741]
|
||||||
|
* builder/triton: Updated triton-go dependencies, allowing better error
|
||||||
|
handling. [GH-5795]
|
||||||
|
* builder/vmware-iso: Add support for cdrom and disk adapter types. [GH-3417]
|
||||||
|
* builder/vmware-iso: Add support for setting network type and network adapter
|
||||||
|
type. [GH-3417]
|
||||||
|
* builder/vmware-iso: Add support for usb/serial/parallel ports. [GH-3417]
|
||||||
|
* builder/vmware-iso: Add support for virtual soundcards. [GH-3417]
|
||||||
|
* builder/vmware-iso: More reliably retrieve the guest networking
|
||||||
|
configuration. [GH-3417]
|
||||||
|
* builder/vmware: Add support for "super" key in `boot_command`. [GH-5681]
|
||||||
|
* communicator/ssh: Add session-level keep-alives [GH-5830]
|
||||||
|
* communicator/ssh: Detect dead connections. [GH-4709]
|
||||||
|
* core: Gracefully clean up resources on SIGTERM. [GH-5318]
|
||||||
|
* core: Improved error logging in floppy file handling. [GH-5802]
|
||||||
|
* core: Improved support for downloading and validating a uri containing a
|
||||||
|
Windows UNC path or a relative file:// scheme. [GH-2906]
|
||||||
|
* post-processor/amazon-import: Allow user to specify role name in amazon-
|
||||||
|
import [GH-5817]
|
||||||
|
* post-processor/docker: Remove credentials from being shown in the log.
|
||||||
|
[GH-5666]
|
||||||
|
* post-processor/google-export: Synchronize credential semantics with the
|
||||||
|
Google builder. [GH-4148]
|
||||||
|
* post-processor/vagrant: Add vagrant post-processor support for Google
|
||||||
|
[GH-5732]
|
||||||
|
* post-processor/vsphere-template: Now accepts artifacts from the vSphere post-
|
||||||
|
processor. [GH-5380]
|
||||||
|
* provisioner/amazon: Use Amazon SDK's InstanceRunning waiter instead of
|
||||||
|
InstanceStatusOK waiter [GH-5773]
|
||||||
|
* provisioner/ansible: Improve user retrieval. [GH-5758]
|
||||||
|
* provisioner/chef: Add support for 'trusted_certs_dir' chef-client
|
||||||
|
configuration option [GH-5790]
|
||||||
|
* provisioner/chef: Added Policyfile support to chef-client provisioner.
|
||||||
|
[GH-5831]
|
||||||
|
|
||||||
### BUG FIXES:
|
### BUG FIXES:
|
||||||
|
|
||||||
* builder/alicloud-ecs: Attach keypair before starting instance in alicloud builder [GH-5739]
|
* builder/alicloud-ecs: Attach keypair before starting instance in alicloud
|
||||||
|
builder [GH-5739]
|
||||||
|
* builder/amazon: Fix tagging support when building in us-gov/china. [GH-5841]
|
||||||
|
* builder/amazon: NewSession now inherits MaxRetries and other settings.
|
||||||
|
[GH-5719]
|
||||||
|
* builder/virtualbox: Fix interpolation ordering so that edge cases around
|
||||||
|
guest_additions_url are handled correctly [GH-5757]
|
||||||
|
* builder/virtualbox: Fix regression affecting users running Packer on a
|
||||||
|
Windows host that kept Packer from finding Virtualbox guest additions if
|
||||||
|
Packer ran on a different drive from the one where the guest additions were
|
||||||
|
stored. [GH-5761]
|
||||||
|
* builder/vmware: Fix case where artifacts might not be cleaned up correctly.
|
||||||
|
[GH-5835]
|
||||||
|
* builder/vmware: Fixed file handle leak that may have caused race conditions
|
||||||
|
in vmware builder [GH-5767]
|
||||||
|
* communicator/ssh: Add deadline to SSH connection to prevent Packer hangs
|
||||||
|
after script provisioner reboots vm [GH-4684]
|
||||||
|
* communicator/winrm: Fix issue copying empty directories. [GH-5763]
|
||||||
|
* provisioner/ansible-local: Fix support for `--extra-vars` in
|
||||||
|
`extra_arguments`. [GH-5703]
|
||||||
|
* provisioner/ansible-remote: Fixes an error where Packer's private key can be
|
||||||
|
overridden by inherited `ansible_ssh_private_key` options. [GH-5869]
|
||||||
|
* provisioner/ansible: The "default extra variables" feature added in Packer
|
||||||
|
v1.0.1 caused the ansible-local provisioner to fail when an --extra-vars
|
||||||
|
argument was specified in the extra_arguments configuration option; this
|
||||||
|
has been fixed. [GH-5335]
|
||||||
|
* provisioner/powershell: Regression from v1.1.1 forcing extra escaping of
|
||||||
|
environment variables in the non-elevated provisioner has been fixed.
|
||||||
|
[GH-5515] [GH-5872]
|
||||||
|
|
||||||
|
|
||||||
## 1.1.3 (December 8, 2017)
|
## 1.1.3 (December 8, 2017)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
/builder/oracle/ @prydie @owainlewis
|
/builder/oracle/ @prydie @owainlewis
|
||||||
/builder/profitbricks/ @jasmingacic
|
/builder/profitbricks/ @jasmingacic
|
||||||
/builder/triton/ @jen20 @sean-
|
/builder/triton/ @jen20 @sean-
|
||||||
|
/builder/ncloud/ @YuSungDuk
|
||||||
|
/builder/scaleway/ @dimtion @edouardb
|
||||||
|
|
||||||
# provisioners
|
# provisioners
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ comes out of the box with support for the following platforms:
|
||||||
* Parallels
|
* Parallels
|
||||||
* ProfitBricks
|
* ProfitBricks
|
||||||
* QEMU. Both KVM and Xen images.
|
* QEMU. Both KVM and Xen images.
|
||||||
|
* Scaleway
|
||||||
* Triton (Joyent Public Cloud)
|
* Triton (Joyent Public Cloud)
|
||||||
* VMware
|
* VMware
|
||||||
* VirtualBox
|
* VirtualBox
|
||||||
|
|
|
@ -71,11 +71,16 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("err: %s", err)
|
t.Errorf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
config["chroot_mounts"] = [][]string{
|
config["chroot_mounts"] = [][]string{
|
||||||
{"bad"},
|
{"bad"},
|
||||||
}
|
}
|
||||||
warnings, err = b.Prepare(config)
|
warnings, err := b.Prepare(config)
|
||||||
if len(warnings) > 0 {
|
if len(warnings) > 0 {
|
||||||
t.Fatalf("bad: %#v", warnings)
|
t.Fatalf("bad: %#v", warnings)
|
||||||
}
|
}
|
||||||
|
@ -135,9 +140,14 @@ func TestBuilderPrepare_CopyFiles(t *testing.T) {
|
||||||
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
|
if len(b.config.CopyFiles) != 1 && b.config.CopyFiles[0] != "/etc/resolv.conf" {
|
||||||
t.Errorf("Was expecting default value for copy_files.")
|
t.Errorf("Was expecting default value for copy_files.")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
config["copy_files"] = []string{}
|
config["copy_files"] = []string{}
|
||||||
warnings, err = b.Prepare(config)
|
warnings, err := b.Prepare(config)
|
||||||
if len(warnings) > 0 {
|
if len(warnings) > 0 {
|
||||||
t.Fatalf("bad: %#v", warnings)
|
t.Fatalf("bad: %#v", warnings)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package chroot
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDevicePrefixMatch(t *testing.T) {
|
||||||
|
/*
|
||||||
|
if devicePrefixMatch("nvme0n1") != "" {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -1,13 +1,17 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
|
@ -23,6 +27,7 @@ type AccessConfig struct {
|
||||||
RawRegion string `mapstructure:"region"`
|
RawRegion string `mapstructure:"region"`
|
||||||
SecretKey string `mapstructure:"secret_key"`
|
SecretKey string `mapstructure:"secret_key"`
|
||||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||||
|
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||||
Token string `mapstructure:"token"`
|
Token string `mapstructure:"token"`
|
||||||
session *session.Session
|
session *session.Session
|
||||||
}
|
}
|
||||||
|
@ -34,14 +39,78 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||||
return c.session, nil
|
return c.session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)
|
// build a chain provider, lazy-evaluated by aws-sdk
|
||||||
|
providers := []credentials.Provider{
|
||||||
|
&credentials.StaticProvider{Value: credentials.Value{
|
||||||
|
AccessKeyID: c.AccessKey,
|
||||||
|
SecretAccessKey: c.SecretKey,
|
||||||
|
SessionToken: c.Token,
|
||||||
|
}},
|
||||||
|
&credentials.EnvProvider{},
|
||||||
|
&credentials.SharedCredentialsProvider{
|
||||||
|
Filename: "",
|
||||||
|
Profile: c.ProfileName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if c.ProfileName != "" {
|
// Build isolated HTTP client to avoid issues with globally-shared settings
|
||||||
if err := os.Setenv("AWS_PROFILE", c.ProfileName); err != nil {
|
client := cleanhttp.DefaultClient()
|
||||||
return nil, fmt.Errorf("Set env error: %s", err)
|
|
||||||
|
// Keep the default timeout (100ms) low as we don't want to wait in non-EC2 environments
|
||||||
|
client.Timeout = 100 * time.Millisecond
|
||||||
|
|
||||||
|
const userTimeoutEnvVar = "AWS_METADATA_TIMEOUT"
|
||||||
|
userTimeout := os.Getenv(userTimeoutEnvVar)
|
||||||
|
if userTimeout != "" {
|
||||||
|
newTimeout, err := time.ParseDuration(userTimeout)
|
||||||
|
if err == nil {
|
||||||
|
if newTimeout.Nanoseconds() > 0 {
|
||||||
|
client.Timeout = newTimeout
|
||||||
|
} else {
|
||||||
|
log.Printf("[WARN] Non-positive value of %s (%s) is meaningless, ignoring", userTimeoutEnvVar, newTimeout.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[WARN] Error converting %s to time.Duration: %s", userTimeoutEnvVar, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Setting AWS metadata API timeout to %s", client.Timeout.String())
|
||||||
|
cfg := &aws.Config{
|
||||||
|
HTTPClient: client,
|
||||||
|
}
|
||||||
|
if !c.SkipMetadataApiCheck {
|
||||||
|
// Real AWS should reply to a simple metadata request.
|
||||||
|
// We check it actually does to ensure something else didn't just
|
||||||
|
// happen to be listening on the same IP:Port
|
||||||
|
metadataClient := ec2metadata.New(session.New(cfg))
|
||||||
|
if metadataClient.Available() {
|
||||||
|
providers = append(providers, &ec2rolecreds.EC2RoleProvider{
|
||||||
|
Client: metadataClient,
|
||||||
|
})
|
||||||
|
log.Print("[INFO] AWS EC2 instance detected via default metadata" +
|
||||||
|
" API endpoint, EC2RoleProvider added to the auth chain")
|
||||||
|
} else {
|
||||||
|
log.Printf("[INFO] Ignoring AWS metadata API endpoint " +
|
||||||
|
"as it doesn't return any instance-id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
creds := credentials.NewChainCredentials(providers)
|
||||||
|
cp, err := creds.Get()
|
||||||
|
if err != nil {
|
||||||
|
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoCredentialProviders" {
|
||||||
|
return nil, errors.New("No valid credential sources found for AWS Builder. " +
|
||||||
|
"Please see https://www.packer.io/docs/builders/amazon.html#specifying-amazon-credentials " +
|
||||||
|
"for more information on providing credentials for the AWS Builder.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Error loading credentials for AWS Provider: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] AWS Auth provider used: %q", cp.ProviderName)
|
||||||
|
|
||||||
|
config := aws.NewConfig().WithMaxRetries(11).WithCredentialsChainVerboseErrors(true)
|
||||||
|
config = config.WithCredentials(creds)
|
||||||
|
|
||||||
if c.RawRegion != "" {
|
if c.RawRegion != "" {
|
||||||
config = config.WithRegion(c.RawRegion)
|
config = config.WithRegion(c.RawRegion)
|
||||||
} else if region := c.metadataRegion(); region != "" {
|
} else if region := c.metadataRegion(); region != "" {
|
||||||
|
@ -52,11 +121,6 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||||
config = config.WithEndpoint(c.CustomEndpointEc2)
|
config = config.WithEndpoint(c.CustomEndpointEc2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.AccessKey != "" {
|
|
||||||
config = config.WithCredentials(
|
|
||||||
credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token))
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := session.Options{
|
opts := session.Options{
|
||||||
SharedConfigState: session.SharedConfigEnable,
|
SharedConfigState: session.SharedConfigEnable,
|
||||||
Config: *config,
|
Config: *config,
|
||||||
|
@ -80,6 +144,21 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
||||||
return c.session, nil
|
return c.session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AccessConfig) SessionRegion() string {
|
||||||
|
if c.session == nil {
|
||||||
|
panic("access config session should be set.")
|
||||||
|
}
|
||||||
|
return aws.StringValue(c.session.Config.Region)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessConfig) IsGovCloud() bool {
|
||||||
|
return strings.HasPrefix(c.SessionRegion(), "us-gov-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AccessConfig) IsChinaCloud() bool {
|
||||||
|
return strings.HasPrefix(c.SessionRegion(), "cn-")
|
||||||
|
}
|
||||||
|
|
||||||
// metadataRegion returns the region from the metadata service
|
// metadataRegion returns the region from the metadata service
|
||||||
func (c *AccessConfig) metadataRegion() string {
|
func (c *AccessConfig) metadataRegion() string {
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testAccessConfig() *AccessConfig {
|
func testAccessConfig() *AccessConfig {
|
||||||
|
@ -38,3 +41,20 @@ func TestAccessConfigPrepare_Region(t *testing.T) {
|
||||||
c.SkipValidation = false
|
c.SkipValidation = false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccessConfigPrepare_RegionRestrictd(t *testing.T) {
|
||||||
|
c := testAccessConfig()
|
||||||
|
|
||||||
|
// Create a Session with a custom region
|
||||||
|
c.session = session.Must(session.NewSession(&aws.Config{
|
||||||
|
Region: aws.String("us-gov-west-1"),
|
||||||
|
}))
|
||||||
|
|
||||||
|
if err := c.Prepare(nil); err != nil {
|
||||||
|
t.Fatalf("shouldn't have err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.IsGovCloud() {
|
||||||
|
t.Fatal("We should be in gov region.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ type AMIConfig struct {
|
||||||
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
AMIProductCodes []string `mapstructure:"ami_product_codes"`
|
||||||
AMIRegions []string `mapstructure:"ami_regions"`
|
AMIRegions []string `mapstructure:"ami_regions"`
|
||||||
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||||
AMITags map[string]string `mapstructure:"tags"`
|
AMITags TagMap `mapstructure:"tags"`
|
||||||
AMIENASupport bool `mapstructure:"ena_support"`
|
AMIENASupport bool `mapstructure:"ena_support"`
|
||||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||||
|
@ -25,7 +25,7 @@ type AMIConfig struct {
|
||||||
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
|
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
|
||||||
AMIKmsKeyId string `mapstructure:"kms_key_id"`
|
AMIKmsKeyId string `mapstructure:"kms_key_id"`
|
||||||
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
|
AMIRegionKMSKeyIDs map[string]string `mapstructure:"region_kms_key_ids"`
|
||||||
SnapshotTags map[string]string `mapstructure:"snapshot_tags"`
|
SnapshotTags TagMap `mapstructure:"snapshot_tags"`
|
||||||
SnapshotUsers []string `mapstructure:"snapshot_users"`
|
SnapshotUsers []string `mapstructure:"snapshot_users"`
|
||||||
SnapshotGroups []string `mapstructure:"snapshot_groups"`
|
SnapshotGroups []string `mapstructure:"snapshot_groups"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,6 @@ type RunConfig struct {
|
||||||
// Communicator settings
|
// Communicator settings
|
||||||
Comm communicator.Config `mapstructure:",squash"`
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
||||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
|
||||||
SSHInterface string `mapstructure:"ssh_interface"`
|
SSHInterface string `mapstructure:"ssh_interface"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,14 +77,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
errs := c.Comm.Prepare(ctx)
|
errs := c.Comm.Prepare(ctx)
|
||||||
if c.SSHPrivateIp && c.SSHInterface != "" {
|
|
||||||
errs = append(errs, errors.New("ssh_interface and ssh_private_ip should not both be specified"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy configurable
|
|
||||||
if c.SSHPrivateIp {
|
|
||||||
c.SSHInterface = "private_ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valadating ssh_interface
|
// Valadating ssh_interface
|
||||||
if c.SSHInterface != "public_ip" &&
|
if c.SSHInterface != "public_ip" &&
|
||||||
|
|
|
@ -15,8 +15,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type StepCreateTags struct {
|
type StepCreateTags struct {
|
||||||
Tags map[string]string
|
Tags TagMap
|
||||||
SnapshotTags map[string]string
|
SnapshotTags TagMap
|
||||||
Ctx interpolate.Context
|
Ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||||
sourceAMI = ""
|
sourceAMI = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
|
if !s.Tags.IsSet() && !s.SnapshotTags.IsSet() {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,22 +79,22 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||||
|
|
||||||
// Convert tags to ec2.Tag format
|
// Convert tags to ec2.Tag format
|
||||||
ui.Say("Creating AMI tags")
|
ui.Say("Creating AMI tags")
|
||||||
amiTags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, sourceAMI, s.Ctx)
|
amiTags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, sourceAMI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
ReportTags(ui, amiTags)
|
amiTags.Report(ui)
|
||||||
|
|
||||||
ui.Say("Creating snapshot tags")
|
ui.Say("Creating snapshot tags")
|
||||||
snapshotTags, err := ConvertToEC2Tags(s.SnapshotTags, *ec2conn.Config.Region, sourceAMI, s.Ctx)
|
snapshotTags, err := s.SnapshotTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, sourceAMI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
ReportTags(ui, snapshotTags)
|
snapshotTags.Report(ui)
|
||||||
|
|
||||||
// Retry creating tags for about 2.5 minutes
|
// Retry creating tags for about 2.5 minutes
|
||||||
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||||
|
@ -142,36 +142,3 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
||||||
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
|
func (s *StepCreateTags) Cleanup(state multistep.StateBag) {
|
||||||
// No cleanup...
|
// No cleanup...
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReportTags(ui packer.Ui, tags []*ec2.Tag) {
|
|
||||||
for _, tag := range tags {
|
|
||||||
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
|
|
||||||
aws.StringValue(tag.Key), aws.StringValue(tag.Value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertToEC2Tags(tags map[string]string, region, sourceAmiId string, ctx interpolate.Context) ([]*ec2.Tag, error) {
|
|
||||||
var ec2Tags []*ec2.Tag
|
|
||||||
for key, value := range tags {
|
|
||||||
|
|
||||||
ctx.Data = &BuildInfoTemplate{
|
|
||||||
SourceAMI: sourceAmiId,
|
|
||||||
BuildRegion: region,
|
|
||||||
}
|
|
||||||
interpolatedKey, err := interpolate.Render(key, &ctx)
|
|
||||||
if err != nil {
|
|
||||||
return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
|
|
||||||
}
|
|
||||||
interpolatedValue, err := interpolate.Render(value, &ctx)
|
|
||||||
if err != nil {
|
|
||||||
return ec2Tags, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ec2Tags = append(ec2Tags, &ec2.Tag{
|
|
||||||
Key: aws.String(interpolatedKey),
|
|
||||||
Value: aws.String(interpolatedValue),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ec2Tags, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,8 +8,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
|
||||||
|
retry "github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
@ -19,19 +21,20 @@ type StepRunSourceInstance struct {
|
||||||
AssociatePublicIpAddress bool
|
AssociatePublicIpAddress bool
|
||||||
AvailabilityZone string
|
AvailabilityZone string
|
||||||
BlockDevices BlockDevices
|
BlockDevices BlockDevices
|
||||||
|
Ctx interpolate.Context
|
||||||
Debug bool
|
Debug bool
|
||||||
EbsOptimized bool
|
EbsOptimized bool
|
||||||
ExpectedRootDevice string
|
ExpectedRootDevice string
|
||||||
IamInstanceProfile string
|
IamInstanceProfile string
|
||||||
InstanceInitiatedShutdownBehavior string
|
InstanceInitiatedShutdownBehavior string
|
||||||
InstanceType string
|
InstanceType string
|
||||||
|
IsRestricted bool
|
||||||
SourceAMI string
|
SourceAMI string
|
||||||
SubnetId string
|
SubnetId string
|
||||||
Tags map[string]string
|
Tags TagMap
|
||||||
VolumeTags map[string]string
|
|
||||||
UserData string
|
UserData string
|
||||||
UserDataFile string
|
UserDataFile string
|
||||||
Ctx interpolate.Context
|
VolumeTags TagMap
|
||||||
|
|
||||||
instanceId string
|
instanceId string
|
||||||
}
|
}
|
||||||
|
@ -85,16 +88,15 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
||||||
s.Tags["Name"] = "Packer Builder"
|
s.Tags["Name"] = "Packer Builder"
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2Tags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
|
ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, s.SourceAMI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error tagging source instance: %s", err)
|
err := fmt.Errorf("Error tagging source instance: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
ReportTags(ui, ec2Tags)
|
|
||||||
|
|
||||||
volTags, err := ConvertToEC2Tags(s.VolumeTags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
|
volTags, err := s.VolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, s.SourceAMI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error tagging volumes: %s", err)
|
err := fmt.Errorf("Error tagging volumes: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -114,6 +116,7 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
||||||
EbsOptimized: &s.EbsOptimized,
|
EbsOptimized: &s.EbsOptimized,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect tags for tagging on resource creation
|
||||||
var tagSpecs []*ec2.TagSpecification
|
var tagSpecs []*ec2.TagSpecification
|
||||||
|
|
||||||
if len(ec2Tags) > 0 {
|
if len(ec2Tags) > 0 {
|
||||||
|
@ -134,8 +137,11 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
||||||
tagSpecs = append(tagSpecs, runVolTags)
|
tagSpecs = append(tagSpecs, runVolTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tagSpecs) > 0 {
|
// If our region supports it, set tag specifications
|
||||||
|
if len(tagSpecs) > 0 && !s.IsRestricted {
|
||||||
runOpts.SetTagSpecifications(tagSpecs)
|
runOpts.SetTagSpecifications(tagSpecs)
|
||||||
|
ec2Tags.Report(ui)
|
||||||
|
volTags.Report(ui)
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyName != "" {
|
if keyName != "" {
|
||||||
|
@ -212,6 +218,70 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
||||||
|
|
||||||
state.Put("instance", instance)
|
state.Put("instance", instance)
|
||||||
|
|
||||||
|
// If we're in a region that doesn't support tagging on instance creation,
|
||||||
|
// do that now.
|
||||||
|
|
||||||
|
if s.IsRestricted {
|
||||||
|
ec2Tags.Report(ui)
|
||||||
|
// Retry creating tags for about 2.5 minutes
|
||||||
|
err = retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||||
|
_, err := ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||||
|
Tags: ec2Tags,
|
||||||
|
Resources: []*string{instance.InstanceId},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if awsErr, ok := err.(awserr.Error); ok {
|
||||||
|
if awsErr.Code() == "InvalidInstanceID.NotFound" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error tagging source instance: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now tag volumes
|
||||||
|
|
||||||
|
volumeIds := make([]*string, 0)
|
||||||
|
for _, v := range instance.BlockDeviceMappings {
|
||||||
|
if ebs := v.Ebs; ebs != nil {
|
||||||
|
volumeIds = append(volumeIds, ebs.VolumeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(volumeIds) > 0 && s.VolumeTags.IsSet() {
|
||||||
|
ui.Say("Adding tags to source EBS Volumes")
|
||||||
|
|
||||||
|
volumeTags, err := s.VolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, s.SourceAMI)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
volumeTags.Report(ui)
|
||||||
|
|
||||||
|
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||||
|
Resources: volumeIds,
|
||||||
|
Tags: volumeTags,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@ type StepRunSpotInstance struct {
|
||||||
SpotPrice string
|
SpotPrice string
|
||||||
SpotPriceProduct string
|
SpotPriceProduct string
|
||||||
SubnetId string
|
SubnetId string
|
||||||
Tags map[string]string
|
Tags TagMap
|
||||||
VolumeTags map[string]string
|
VolumeTags TagMap
|
||||||
UserData string
|
UserData string
|
||||||
UserDataFile string
|
UserDataFile string
|
||||||
Ctx interpolate.Context
|
Ctx interpolate.Context
|
||||||
|
@ -143,14 +143,14 @@ func (s *StepRunSpotInstance) Run(_ context.Context, state multistep.StateBag) m
|
||||||
s.Tags["Name"] = "Packer Builder"
|
s.Tags["Name"] = "Packer Builder"
|
||||||
}
|
}
|
||||||
|
|
||||||
ec2Tags, err := ConvertToEC2Tags(s.Tags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
|
ec2Tags, err := s.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, s.SourceAMI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error tagging source instance: %s", err)
|
err := fmt.Errorf("Error tagging source instance: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
ReportTags(ui, ec2Tags)
|
ec2Tags.Report(ui)
|
||||||
|
|
||||||
ui.Message(fmt.Sprintf(
|
ui.Message(fmt.Sprintf(
|
||||||
"Requesting spot instance '%s' for: %s",
|
"Requesting spot instance '%s' for: %s",
|
||||||
|
@ -284,21 +284,21 @@ func (s *StepRunSpotInstance) Run(_ context.Context, state multistep.StateBag) m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(volumeIds) > 0 && len(s.VolumeTags) > 0 {
|
if len(volumeIds) > 0 && s.VolumeTags.IsSet() {
|
||||||
ui.Say("Adding tags to source EBS Volumes")
|
ui.Say("Adding tags to source EBS Volumes")
|
||||||
tags, err := ConvertToEC2Tags(s.VolumeTags, *ec2conn.Config.Region, s.SourceAMI, s.Ctx)
|
|
||||||
|
volumeTags, err := s.VolumeTags.EC2Tags(s.Ctx, *ec2conn.Config.Region, s.SourceAMI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
|
err := fmt.Errorf("Error tagging source EBS Volumes on %s: %s", *instance.InstanceId, err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
volumeTags.Report(ui)
|
||||||
ReportTags(ui, tags)
|
|
||||||
|
|
||||||
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
|
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||||
Resources: volumeIds,
|
Resources: volumeIds,
|
||||||
Tags: tags,
|
Tags: volumeTags,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TagMap map[string]string
|
||||||
|
type EC2Tags []*ec2.Tag
|
||||||
|
|
||||||
|
func (t EC2Tags) Report(ui packer.Ui) {
|
||||||
|
for _, tag := range t {
|
||||||
|
ui.Message(fmt.Sprintf("Adding tag: \"%s\": \"%s\"",
|
||||||
|
aws.StringValue(tag.Key), aws.StringValue(tag.Value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TagMap) IsSet() bool {
|
||||||
|
return len(t) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TagMap) EC2Tags(ctx interpolate.Context, region, sourceAMIID string) (EC2Tags, error) {
|
||||||
|
var ec2Tags []*ec2.Tag
|
||||||
|
ctx.Data = &BuildInfoTemplate{
|
||||||
|
SourceAMI: sourceAMIID,
|
||||||
|
BuildRegion: region,
|
||||||
|
}
|
||||||
|
for key, value := range t {
|
||||||
|
interpolatedKey, err := interpolate.Render(key, &ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
|
||||||
|
}
|
||||||
|
interpolatedValue, err := interpolate.Render(value, &ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error processing tag: %s:%s - %s", key, value, err)
|
||||||
|
}
|
||||||
|
ec2Tags = append(ec2Tags, &ec2.Tag{
|
||||||
|
Key: aws.String(interpolatedKey),
|
||||||
|
Value: aws.String(interpolatedValue),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ec2Tags, nil
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ type Config struct {
|
||||||
awscommon.AMIConfig `mapstructure:",squash"`
|
awscommon.AMIConfig `mapstructure:",squash"`
|
||||||
awscommon.BlockDevices `mapstructure:",squash"`
|
awscommon.BlockDevices `mapstructure:",squash"`
|
||||||
awscommon.RunConfig `mapstructure:",squash"`
|
awscommon.RunConfig `mapstructure:",squash"`
|
||||||
VolumeRunTags map[string]string `mapstructure:"run_volume_tags"`
|
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||||
InstanceType: b.config.InstanceType,
|
InstanceType: b.config.InstanceType,
|
||||||
|
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||||
SourceAMI: b.config.SourceAmi,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SubnetId: b.config.SubnetId,
|
SubnetId: b.config.SubnetId,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
|
|
|
@ -27,7 +27,7 @@ type Config struct {
|
||||||
awscommon.AMIConfig `mapstructure:",squash"`
|
awscommon.AMIConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
|
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
|
||||||
VolumeRunTags map[string]string `mapstructure:"run_volume_tags"`
|
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||||
InstanceType: b.config.InstanceType,
|
InstanceType: b.config.InstanceType,
|
||||||
|
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||||
SourceAMI: b.config.SourceAmi,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SubnetId: b.config.SubnetId,
|
SubnetId: b.config.SubnetId,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
type BlockDevice struct {
|
type BlockDevice struct {
|
||||||
awscommon.BlockDevice `mapstructure:"-,squash"`
|
awscommon.BlockDevice `mapstructure:"-,squash"`
|
||||||
Tags map[string]string `mapstructure:"tags"`
|
Tags awscommon.TagMap `mapstructure:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonBlockDevices(mappings []BlockDevice, ctx *interpolate.Context) (awscommon.BlockDevices, error) {
|
func commonBlockDevices(mappings []BlockDevice, ctx *interpolate.Context) (awscommon.BlockDevices, error) {
|
||||||
|
|
|
@ -149,6 +149,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||||
InstanceType: b.config.InstanceType,
|
InstanceType: b.config.InstanceType,
|
||||||
|
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||||
SourceAMI: b.config.SourceAmi,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SubnetId: b.config.SubnetId,
|
SubnetId: b.config.SubnetId,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
awscommon "github.com/hashicorp/packer/builder/amazon/common"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
@ -44,14 +43,14 @@ func (s *stepTagEBSVolumes) Run(_ context.Context, state multistep.StateBag) mul
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, err := awscommon.ConvertToEC2Tags(mapping.Tags, *ec2conn.Config.Region, *sourceAMI.ImageId, s.Ctx)
|
tags, err := mapping.Tags.EC2Tags(s.Ctx, *ec2conn.Config.Region, *sourceAMI.ImageId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
|
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
awscommon.ReportTags(ui, tags)
|
tags.Report(ui)
|
||||||
|
|
||||||
for _, v := range instance.BlockDeviceMappings {
|
for _, v := range instance.BlockDeviceMappings {
|
||||||
if *v.DeviceName == mapping.DeviceName {
|
if *v.DeviceName == mapping.DeviceName {
|
||||||
|
|
|
@ -232,6 +232,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
EbsOptimized: b.config.EbsOptimized,
|
EbsOptimized: b.config.EbsOptimized,
|
||||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||||
InstanceType: b.config.InstanceType,
|
InstanceType: b.config.InstanceType,
|
||||||
|
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||||
SourceAMI: b.config.SourceAmi,
|
SourceAMI: b.config.SourceAmi,
|
||||||
SubnetId: b.config.SubnetId,
|
SubnetId: b.config.SubnetId,
|
||||||
Tags: b.config.RunTags,
|
Tags: b.config.RunTags,
|
||||||
|
|
|
@ -105,6 +105,21 @@ func (c *Communicator) uploadReader(dst string, src io.Reader) error {
|
||||||
|
|
||||||
// uploadFile uses docker cp to copy the file from the host to the container
|
// uploadFile uses docker cp to copy the file from the host to the container
|
||||||
func (c *Communicator) uploadFile(dst string, src io.Reader, fi *os.FileInfo) error {
|
func (c *Communicator) uploadFile(dst string, src io.Reader, fi *os.FileInfo) error {
|
||||||
|
// find out if it's a directory
|
||||||
|
testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, dst)
|
||||||
|
cmd := &packer.RemoteCmd{Command: testDirectoryCommand}
|
||||||
|
|
||||||
|
err := c.Start(cmd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to check whether remote path is a dir: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Wait()
|
||||||
|
if cmd.ExitStatus == 0 {
|
||||||
|
log.Printf("path is a directory; copying file into directory.")
|
||||||
|
dst = filepath.Join(dst, filepath.Base((*fi).Name()))
|
||||||
|
}
|
||||||
|
|
||||||
// command format: docker cp /path/to/infile containerid:/path/to/outfile
|
// command format: docker cp /path/to/infile containerid:/path/to/outfile
|
||||||
log.Printf("Copying to %s on container %s.", dst, c.ContainerID)
|
log.Printf("Copying to %s on container %s.", dst, c.ContainerID)
|
||||||
|
|
|
@ -36,6 +36,7 @@ type Config struct {
|
||||||
ImageDescription string `mapstructure:"image_description"`
|
ImageDescription string `mapstructure:"image_description"`
|
||||||
ImageFamily string `mapstructure:"image_family"`
|
ImageFamily string `mapstructure:"image_family"`
|
||||||
ImageLabels map[string]string `mapstructure:"image_labels"`
|
ImageLabels map[string]string `mapstructure:"image_labels"`
|
||||||
|
ImageLicenses []string `mapstructure:"image_licenses"`
|
||||||
InstanceName string `mapstructure:"instance_name"`
|
InstanceName string `mapstructure:"instance_name"`
|
||||||
Labels map[string]string `mapstructure:"labels"`
|
Labels map[string]string `mapstructure:"labels"`
|
||||||
MachineType string `mapstructure:"machine_type"`
|
MachineType string `mapstructure:"machine_type"`
|
||||||
|
|
|
@ -309,6 +309,9 @@ func testConfig(t *testing.T) map[string]interface{} {
|
||||||
"label-1": "value-1",
|
"label-1": "value-1",
|
||||||
"label-2": "value-2",
|
"label-2": "value-2",
|
||||||
},
|
},
|
||||||
|
"image_licenses": []string{
|
||||||
|
"test-license",
|
||||||
|
},
|
||||||
"zone": "us-east1-a",
|
"zone": "us-east1-a",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
// CreateImage creates an image from the given disk in Google Compute
|
// CreateImage creates an image from the given disk in Google Compute
|
||||||
// Engine.
|
// Engine.
|
||||||
CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error)
|
CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string) (<-chan *Image, <-chan error)
|
||||||
|
|
||||||
// DeleteImage deletes the image with the given name.
|
// DeleteImage deletes the image with the given name.
|
||||||
DeleteImage(name string) <-chan error
|
DeleteImage(name string) <-chan error
|
||||||
|
|
|
@ -97,12 +97,13 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driverGCE) CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error) {
|
func (d *driverGCE) CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string) (<-chan *Image, <-chan error) {
|
||||||
gce_image := &compute.Image{
|
gce_image := &compute.Image{
|
||||||
Description: description,
|
Description: description,
|
||||||
Name: name,
|
Name: name,
|
||||||
Family: family,
|
Family: family,
|
||||||
Labels: image_labels,
|
Labels: image_labels,
|
||||||
|
Licenses: image_licenses,
|
||||||
SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk),
|
SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk),
|
||||||
SourceType: "RAW",
|
SourceType: "RAW",
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ type DriverMock struct {
|
||||||
CreateImageDesc string
|
CreateImageDesc string
|
||||||
CreateImageFamily string
|
CreateImageFamily string
|
||||||
CreateImageLabels map[string]string
|
CreateImageLabels map[string]string
|
||||||
|
CreateImageLicenses []string
|
||||||
CreateImageZone string
|
CreateImageZone string
|
||||||
CreateImageDisk string
|
CreateImageDisk string
|
||||||
CreateImageResultLicenses []string
|
|
||||||
CreateImageResultProjectId string
|
CreateImageResultProjectId string
|
||||||
CreateImageResultSelfLink string
|
CreateImageResultSelfLink string
|
||||||
CreateImageResultSizeGb int64
|
CreateImageResultSizeGb int64
|
||||||
|
@ -82,11 +82,12 @@ type DriverMock struct {
|
||||||
WaitForInstanceErrCh <-chan error
|
WaitForInstanceErrCh <-chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error) {
|
func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string) (<-chan *Image, <-chan error) {
|
||||||
d.CreateImageName = name
|
d.CreateImageName = name
|
||||||
d.CreateImageDesc = description
|
d.CreateImageDesc = description
|
||||||
d.CreateImageFamily = family
|
d.CreateImageFamily = family
|
||||||
d.CreateImageLabels = image_labels
|
d.CreateImageLabels = image_labels
|
||||||
|
d.CreateImageLicenses = image_licenses
|
||||||
d.CreateImageZone = zone
|
d.CreateImageZone = zone
|
||||||
d.CreateImageDisk = disk
|
d.CreateImageDisk = disk
|
||||||
if d.CreateImageResultProjectId == "" {
|
if d.CreateImageResultProjectId == "" {
|
||||||
|
@ -106,7 +107,7 @@ func (d *DriverMock) CreateImage(name, description, family, zone, disk string, i
|
||||||
ch := make(chan *Image, 1)
|
ch := make(chan *Image, 1)
|
||||||
ch <- &Image{
|
ch <- &Image{
|
||||||
Labels: d.CreateImageLabels,
|
Labels: d.CreateImageLabels,
|
||||||
Licenses: d.CreateImageResultLicenses,
|
Licenses: d.CreateImageLicenses,
|
||||||
Name: name,
|
Name: name,
|
||||||
ProjectId: d.CreateImageResultProjectId,
|
ProjectId: d.CreateImageResultProjectId,
|
||||||
SelfLink: d.CreateImageResultSelfLink,
|
SelfLink: d.CreateImageResultSelfLink,
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (s *StepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
||||||
|
|
||||||
imageCh, errCh := driver.CreateImage(
|
imageCh, errCh := driver.CreateImage(
|
||||||
config.ImageName, config.ImageDescription, config.ImageFamily, config.Zone,
|
config.ImageName, config.ImageDescription, config.ImageFamily, config.Zone,
|
||||||
config.DiskName, config.ImageLabels)
|
config.DiskName, config.ImageLabels, config.ImageLicenses)
|
||||||
var err error
|
var err error
|
||||||
select {
|
select {
|
||||||
case err = <-errCh:
|
case err = <-errCh:
|
||||||
|
|
|
@ -22,7 +22,6 @@ func TestStepCreateImage(t *testing.T) {
|
||||||
d := state.Get("driver").(*DriverMock)
|
d := state.Get("driver").(*DriverMock)
|
||||||
|
|
||||||
// These are the values of the image the driver will return.
|
// These are the values of the image the driver will return.
|
||||||
d.CreateImageResultLicenses = []string{"test-license"}
|
|
||||||
d.CreateImageResultProjectId = "test-project"
|
d.CreateImageResultProjectId = "test-project"
|
||||||
d.CreateImageResultSizeGb = 100
|
d.CreateImageResultSizeGb = 100
|
||||||
|
|
||||||
|
@ -36,7 +35,6 @@ func TestStepCreateImage(t *testing.T) {
|
||||||
assert.True(t, ok, "Image in state is not an Image.")
|
assert.True(t, ok, "Image in state is not an Image.")
|
||||||
|
|
||||||
// Verify created Image results.
|
// Verify created Image results.
|
||||||
assert.Equal(t, image.Licenses, d.CreateImageResultLicenses, "Created image licenses don't match the licenses returned by the driver.")
|
|
||||||
assert.Equal(t, image.Name, c.ImageName, "Created image does not match config name.")
|
assert.Equal(t, image.Name, c.ImageName, "Created image does not match config name.")
|
||||||
assert.Equal(t, image.ProjectId, d.CreateImageResultProjectId, "Created image project does not match driver project.")
|
assert.Equal(t, image.ProjectId, d.CreateImageResultProjectId, "Created image project does not match driver project.")
|
||||||
assert.Equal(t, image.SizeGb, d.CreateImageResultSizeGb, "Created image size does not match the size returned by the driver.")
|
assert.Equal(t, image.SizeGb, d.CreateImageResultSizeGb, "Created image size does not match the size returned by the driver.")
|
||||||
|
@ -48,6 +46,7 @@ func TestStepCreateImage(t *testing.T) {
|
||||||
assert.Equal(t, d.CreateImageZone, c.Zone, "Incorrect image zone passed to driver.")
|
assert.Equal(t, d.CreateImageZone, c.Zone, "Incorrect image zone passed to driver.")
|
||||||
assert.Equal(t, d.CreateImageDisk, c.DiskName, "Incorrect disk passed to driver.")
|
assert.Equal(t, d.CreateImageDisk, c.DiskName, "Incorrect disk passed to driver.")
|
||||||
assert.Equal(t, d.CreateImageLabels, c.ImageLabels, "Incorrect image_labels passed to driver.")
|
assert.Equal(t, d.CreateImageLabels, c.ImageLabels, "Incorrect image_labels passed to driver.")
|
||||||
|
assert.Equal(t, d.CreateImageLicenses, c.ImageLicenses, "Incorrect image_licenses passed to driver.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
||||||
|
|
|
@ -52,6 +52,8 @@ type Driver interface {
|
||||||
//Set the vlan to use for machine
|
//Set the vlan to use for machine
|
||||||
SetVirtualMachineVlanId(string, string) error
|
SetVirtualMachineVlanId(string, string) error
|
||||||
|
|
||||||
|
SetVmNetworkAdapterMacAddress(string, string) error
|
||||||
|
|
||||||
UntagVirtualMachineNetworkAdapterVlan(string, string) error
|
UntagVirtualMachineNetworkAdapterVlan(string, string) error
|
||||||
|
|
||||||
CreateExternalVirtualSwitch(string, string) error
|
CreateExternalVirtualSwitch(string, string) error
|
||||||
|
|
|
@ -67,6 +67,11 @@ type DriverMock struct {
|
||||||
SetNetworkAdapterVlanId_VlanId string
|
SetNetworkAdapterVlanId_VlanId string
|
||||||
SetNetworkAdapterVlanId_Err error
|
SetNetworkAdapterVlanId_Err error
|
||||||
|
|
||||||
|
SetVmNetworkAdapterMacAddress_Called bool
|
||||||
|
SetVmNetworkAdapterMacAddress_VmName string
|
||||||
|
SetVmNetworkAdapterMacAddress_Mac string
|
||||||
|
SetVmNetworkAdapterMacAddress_Err error
|
||||||
|
|
||||||
SetVirtualMachineVlanId_Called bool
|
SetVirtualMachineVlanId_Called bool
|
||||||
SetVirtualMachineVlanId_VmName string
|
SetVirtualMachineVlanId_VmName string
|
||||||
SetVirtualMachineVlanId_VlanId string
|
SetVirtualMachineVlanId_VlanId string
|
||||||
|
@ -318,6 +323,13 @@ func (d *DriverMock) SetNetworkAdapterVlanId(switchName string, vlanId string) e
|
||||||
return d.SetNetworkAdapterVlanId_Err
|
return d.SetNetworkAdapterVlanId_Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) SetVmNetworkAdapterMacAddress(vmName string, mac string) error {
|
||||||
|
d.SetVmNetworkAdapterMacAddress_Called = true
|
||||||
|
d.SetVmNetworkAdapterMacAddress_VmName = vmName
|
||||||
|
d.SetVmNetworkAdapterMacAddress_Mac = mac
|
||||||
|
return d.SetVmNetworkAdapterMacAddress_Err
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) SetVirtualMachineVlanId(vmName string, vlanId string) error {
|
func (d *DriverMock) SetVirtualMachineVlanId(vmName string, vlanId string) error {
|
||||||
d.SetVirtualMachineVlanId_Called = true
|
d.SetVirtualMachineVlanId_Called = true
|
||||||
d.SetVirtualMachineVlanId_VmName = vmName
|
d.SetVirtualMachineVlanId_VmName = vmName
|
||||||
|
|
|
@ -146,6 +146,10 @@ func (d *HypervPS4Driver) SetVirtualMachineVlanId(vmName string, vlanId string)
|
||||||
return hyperv.SetVirtualMachineVlanId(vmName, vlanId)
|
return hyperv.SetVirtualMachineVlanId(vmName, vlanId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *HypervPS4Driver) SetVmNetworkAdapterMacAddress(vmName string, mac string) error {
|
||||||
|
return hyperv.SetVmNetworkAdapterMacAddress(vmName, mac)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *HypervPS4Driver) UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error {
|
func (d *HypervPS4Driver) UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error {
|
||||||
return hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName)
|
return hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ type StepCloneVM struct {
|
||||||
EnableDynamicMemory bool
|
EnableDynamicMemory bool
|
||||||
EnableSecureBoot bool
|
EnableSecureBoot bool
|
||||||
EnableVirtualizationExtensions bool
|
EnableVirtualizationExtensions bool
|
||||||
|
MacAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepCloneVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCloneVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
@ -118,6 +119,16 @@ func (s *StepCloneVM) Run(_ context.Context, state multistep.StateBag) multistep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.MacAddress != "" {
|
||||||
|
err = driver.SetVmNetworkAdapterMacAddress(s.VMName, s.MacAddress)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error setting MAC address: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the final name in the state bag so others can use it
|
// Set the final name in the state bag so others can use it
|
||||||
state.Put("vmName", s.VMName)
|
state.Put("vmName", s.VMName)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,9 @@ type StepCreateVM struct {
|
||||||
EnableVirtualizationExtensions bool
|
EnableVirtualizationExtensions bool
|
||||||
AdditionalDiskSize []uint
|
AdditionalDiskSize []uint
|
||||||
DifferencingDisk bool
|
DifferencingDisk bool
|
||||||
|
MacAddress string
|
||||||
|
SkipExport bool
|
||||||
|
OutputDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
@ -52,6 +55,12 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
}
|
}
|
||||||
|
|
||||||
vhdPath := state.Get("packerVhdTempDir").(string)
|
vhdPath := state.Get("packerVhdTempDir").(string)
|
||||||
|
|
||||||
|
// inline vhd path if export is skipped
|
||||||
|
if s.SkipExport {
|
||||||
|
vhdPath = filepath.Join(s.OutputDir, "Virtual Hard Disks")
|
||||||
|
}
|
||||||
|
|
||||||
// convert the MB to bytes
|
// convert the MB to bytes
|
||||||
ramSize := int64(s.RamSize * 1024 * 1024)
|
ramSize := int64(s.RamSize * 1024 * 1024)
|
||||||
diskSize := int64(s.DiskSize * 1024 * 1024)
|
diskSize := int64(s.DiskSize * 1024 * 1024)
|
||||||
|
@ -125,6 +134,16 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.MacAddress != "" {
|
||||||
|
err = driver.SetVmNetworkAdapterMacAddress(s.VMName, s.MacAddress)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error setting MAC address: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the final name in the state bag so others can use it
|
// Set the final name in the state bag so others can use it
|
||||||
state.Put("vmName", s.VMName)
|
state.Put("vmName", s.VMName)
|
||||||
|
|
||||||
|
|
|
@ -18,18 +18,19 @@ const (
|
||||||
type StepExportVm struct {
|
type StepExportVm struct {
|
||||||
OutputDir string
|
OutputDir string
|
||||||
SkipCompaction bool
|
SkipCompaction bool
|
||||||
|
SkipExport bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var errorMsg string
|
var errorMsg string
|
||||||
|
|
||||||
vmName := state.Get("vmName").(string)
|
vmName := state.Get("vmName").(string)
|
||||||
tmpPath := state.Get("packerTempDir").(string)
|
tmpPath := state.Get("packerTempDir").(string)
|
||||||
outputPath := s.OutputDir
|
outputPath := s.OutputDir
|
||||||
|
expPath := s.OutputDir
|
||||||
|
|
||||||
// create temp path to export vm
|
// create temp path to export vm
|
||||||
errorMsg = "Error creating temp export path: %s"
|
errorMsg = "Error creating temp export path: %s"
|
||||||
|
@ -40,7 +41,7 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
if !s.SkipExport {
|
||||||
ui.Say("Exporting vm...")
|
ui.Say("Exporting vm...")
|
||||||
|
|
||||||
err = driver.ExportVirtualMachine(vmName, vmExportPath)
|
err = driver.ExportVirtualMachine(vmName, vmExportPath)
|
||||||
|
@ -51,9 +52,9 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy to output dir
|
// copy to output dir
|
||||||
expPath := filepath.Join(vmExportPath, vmName)
|
expPath = filepath.Join(vmExportPath, vmName)
|
||||||
|
}
|
||||||
|
|
||||||
if s.SkipCompaction {
|
if s.SkipCompaction {
|
||||||
ui.Say("Skipping disk compaction...")
|
ui.Say("Skipping disk compaction...")
|
||||||
|
@ -69,6 +70,7 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !s.SkipExport {
|
||||||
ui.Say("Copying to output dir...")
|
ui.Say("Copying to output dir...")
|
||||||
err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
|
err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -78,7 +80,7 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,7 @@ type Config struct {
|
||||||
BootCommand []string `mapstructure:"boot_command"`
|
BootCommand []string `mapstructure:"boot_command"`
|
||||||
SwitchName string `mapstructure:"switch_name"`
|
SwitchName string `mapstructure:"switch_name"`
|
||||||
SwitchVlanId string `mapstructure:"switch_vlan_id"`
|
SwitchVlanId string `mapstructure:"switch_vlan_id"`
|
||||||
|
MacAddress string `mapstructure:"mac_address"`
|
||||||
VlanId string `mapstructure:"vlan_id"`
|
VlanId string `mapstructure:"vlan_id"`
|
||||||
Cpu uint `mapstructure:"cpu"`
|
Cpu uint `mapstructure:"cpu"`
|
||||||
Generation uint `mapstructure:"generation"`
|
Generation uint `mapstructure:"generation"`
|
||||||
|
@ -94,6 +95,8 @@ type Config struct {
|
||||||
|
|
||||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||||
|
|
||||||
|
SkipExport bool `mapstructure:"skip_export"`
|
||||||
|
|
||||||
// Use differencing disk
|
// Use differencing disk
|
||||||
DifferencingDisk bool `mapstructure:"differencing_disk"`
|
DifferencingDisk bool `mapstructure:"differencing_disk"`
|
||||||
|
|
||||||
|
@ -357,6 +360,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
|
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
|
||||||
AdditionalDiskSize: b.config.AdditionalDiskSize,
|
AdditionalDiskSize: b.config.AdditionalDiskSize,
|
||||||
DifferencingDisk: b.config.DifferencingDisk,
|
DifferencingDisk: b.config.DifferencingDisk,
|
||||||
|
SkipExport: b.config.SkipExport,
|
||||||
|
OutputDir: b.config.OutputDir,
|
||||||
},
|
},
|
||||||
&hypervcommon.StepEnableIntegrationService{},
|
&hypervcommon.StepEnableIntegrationService{},
|
||||||
|
|
||||||
|
@ -422,6 +427,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
&hypervcommon.StepExportVm{
|
&hypervcommon.StepExportVm{
|
||||||
OutputDir: b.config.OutputDir,
|
OutputDir: b.config.OutputDir,
|
||||||
SkipCompaction: b.config.SkipCompaction,
|
SkipCompaction: b.config.SkipCompaction,
|
||||||
|
SkipExport: b.config.SkipExport,
|
||||||
},
|
},
|
||||||
|
|
||||||
// the clean up actions for each step will be executed reverse order
|
// the clean up actions for each step will be executed reverse order
|
||||||
|
|
|
@ -82,6 +82,7 @@ type Config struct {
|
||||||
BootCommand []string `mapstructure:"boot_command"`
|
BootCommand []string `mapstructure:"boot_command"`
|
||||||
SwitchName string `mapstructure:"switch_name"`
|
SwitchName string `mapstructure:"switch_name"`
|
||||||
SwitchVlanId string `mapstructure:"switch_vlan_id"`
|
SwitchVlanId string `mapstructure:"switch_vlan_id"`
|
||||||
|
MacAddress string `mapstructure:"mac_address"`
|
||||||
VlanId string `mapstructure:"vlan_id"`
|
VlanId string `mapstructure:"vlan_id"`
|
||||||
Cpu uint `mapstructure:"cpu"`
|
Cpu uint `mapstructure:"cpu"`
|
||||||
Generation uint
|
Generation uint
|
||||||
|
@ -94,6 +95,8 @@ type Config struct {
|
||||||
|
|
||||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||||
|
|
||||||
|
SkipExport bool `mapstructure:"skip_export"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,6 +406,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
EnableDynamicMemory: b.config.EnableDynamicMemory,
|
EnableDynamicMemory: b.config.EnableDynamicMemory,
|
||||||
EnableSecureBoot: b.config.EnableSecureBoot,
|
EnableSecureBoot: b.config.EnableSecureBoot,
|
||||||
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
|
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
|
||||||
|
MacAddress: b.config.MacAddress,
|
||||||
},
|
},
|
||||||
|
|
||||||
&hypervcommon.StepEnableIntegrationService{},
|
&hypervcommon.StepEnableIntegrationService{},
|
||||||
|
@ -469,6 +473,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
&hypervcommon.StepExportVm{
|
&hypervcommon.StepExportVm{
|
||||||
OutputDir: b.config.OutputDir,
|
OutputDir: b.config.OutputDir,
|
||||||
SkipCompaction: b.config.SkipCompaction,
|
SkipCompaction: b.config.SkipCompaction,
|
||||||
|
SkipExport: b.config.SkipExport,
|
||||||
},
|
},
|
||||||
|
|
||||||
// the clean up actions for each step will be executed reverse order
|
// the clean up actions for each step will be executed reverse order
|
||||||
|
|
|
@ -73,6 +73,24 @@ func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fi != nil {
|
||||||
|
tfDir := filepath.Dir(tf.Name())
|
||||||
|
// rename tempfile to match original file name. This makes sure that if file is being
|
||||||
|
// moved into a directory, the filename is preserved instead of a temp name.
|
||||||
|
adjustedTempName := filepath.Join(tfDir, (*fi).Name())
|
||||||
|
mvCmd, err := c.CmdWrapper(fmt.Sprintf("sudo mv %s %s", tf.Name(), adjustedTempName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(adjustedTempName)
|
||||||
|
ShellCommand(mvCmd).Run()
|
||||||
|
// change cpCmd to use new file name as source
|
||||||
|
cpCmd, err = c.CmdWrapper(fmt.Sprintf("sudo cp %s %s", adjustedTempName, dst))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("Running copy command: %s", dst)
|
log.Printf("Running copy command: %s", dst)
|
||||||
|
|
||||||
return ShellCommand(cpCmd).Run()
|
return ShellCommand(cpCmd).Run()
|
||||||
|
|
|
@ -55,7 +55,24 @@ func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
||||||
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file push - %s", filepath.Join(c.ContainerName, dst)))
|
fileDestination := filepath.Join(c.ContainerName, dst)
|
||||||
|
// find out if the place we are pushing to is a directory
|
||||||
|
testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, dst)
|
||||||
|
cmd := &packer.RemoteCmd{Command: testDirectoryCommand}
|
||||||
|
err := c.Start(cmd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to check whether remote path is a dir: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Wait()
|
||||||
|
|
||||||
|
if cmd.ExitStatus == 0 {
|
||||||
|
log.Printf("path is a directory; copying file into directory.")
|
||||||
|
fileDestination = filepath.Join(c.ContainerName, dst, (*fi).Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file push - %s", fileDestination))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
|
|
||||||
// Build the steps
|
// Build the steps
|
||||||
steps := []multistep.Step{
|
steps := []multistep.Step{
|
||||||
&StepLoadExtensions{},
|
|
||||||
&StepLoadFlavor{
|
&StepLoadFlavor{
|
||||||
Flavor: b.config.Flavor,
|
Flavor: b.config.Flavor,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
package openstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions"
|
|
||||||
"github.com/gophercloud/gophercloud/pagination"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
|
||||||
"github.com/hashicorp/packer/packer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StepLoadExtensions gets the FlavorRef from a Flavor. It first assumes
|
|
||||||
// that the Flavor is a ref and verifies it. Otherwise, it tries to find
|
|
||||||
// the flavor by name.
|
|
||||||
type StepLoadExtensions struct{}
|
|
||||||
|
|
||||||
func (s *StepLoadExtensions) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
|
||||||
config := state.Get("config").(Config)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
|
||||||
|
|
||||||
// We need the v2 compute client
|
|
||||||
client, err := config.computeV2Client()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error initializing compute client: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Discovering enabled extensions...")
|
|
||||||
result := make(map[string]struct{}, 15)
|
|
||||||
pager := extensions.List(client)
|
|
||||||
err = pager.EachPage(func(p pagination.Page) (bool, error) {
|
|
||||||
// Extract the extensions from this page
|
|
||||||
exts, err := extensions.ExtractExtensions(p)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ext := range exts {
|
|
||||||
log.Printf("[DEBUG] Discovered extension: %s", ext.Alias)
|
|
||||||
result[ext.Alias] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error loading extensions: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Put("extensions", result)
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StepLoadExtensions) Cleanup(state multistep.StateBag) {
|
|
||||||
}
|
|
|
@ -15,15 +15,8 @@ type StepStopServer struct{}
|
||||||
func (s *StepStopServer) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepStopServer) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
config := state.Get("config").(Config)
|
config := state.Get("config").(Config)
|
||||||
extensions := state.Get("extensions").(map[string]struct{})
|
|
||||||
server := state.Get("server").(*servers.Server)
|
server := state.Get("server").(*servers.Server)
|
||||||
|
|
||||||
// Verify we have the extension
|
|
||||||
if _, ok := extensions["os-server-start-stop"]; !ok {
|
|
||||||
ui.Say("OpenStack cluster doesn't support stop, skipping...")
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need the v2 compute client
|
// We need the v2 compute client
|
||||||
client, err := config.computeV2Client()
|
client, err := config.computeV2Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Artifact is an artifact implementation that contains Image List
|
||||||
|
// and Machine Image info.
|
||||||
|
type Artifact struct {
|
||||||
|
MachineImageName string
|
||||||
|
MachineImageFile string
|
||||||
|
ImageListVersion int
|
||||||
|
driver *compute.ComputeClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuilderId uniquely identifies the builder.
|
||||||
|
func (a *Artifact) BuilderId() string {
|
||||||
|
return BuilderId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files lists the files associated with an artifact. We don't have any files
|
||||||
|
// as the custom image is stored server side.
|
||||||
|
func (a *Artifact) Files() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Id() string {
|
||||||
|
return a.MachineImageName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) String() string {
|
||||||
|
return fmt.Sprintf("An image list entry was created: \n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"File: %s\n"+
|
||||||
|
"Version: %d",
|
||||||
|
a.MachineImageName, a.MachineImageFile, a.ImageListVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) State(name string) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy deletes the custom image associated with the artifact.
|
||||||
|
func (a *Artifact) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-cleanhttp"
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/opc"
|
||||||
|
ocommon "github.com/hashicorp/packer/builder/oracle/common"
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuilderId uniquely identifies the builder
|
||||||
|
const BuilderId = "packer.oracle.classic"
|
||||||
|
|
||||||
|
// Builder is a builder implementation that creates Oracle OCI custom images.
|
||||||
|
type Builder struct {
|
||||||
|
config *Config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(rawConfig ...interface{}) ([]string, error) {
|
||||||
|
config, err := NewConfig(rawConfig...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.config = config
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
loggingEnabled := os.Getenv("PACKER_OCI_CLASSIC_LOGGING") != ""
|
||||||
|
httpClient := cleanhttp.DefaultClient()
|
||||||
|
config := &opc.Config{
|
||||||
|
Username: opc.String(b.config.Username),
|
||||||
|
Password: opc.String(b.config.Password),
|
||||||
|
IdentityDomain: opc.String(b.config.IdentityDomain),
|
||||||
|
APIEndpoint: b.config.apiEndpointURL,
|
||||||
|
LogLevel: opc.LogDebug,
|
||||||
|
Logger: &Logger{loggingEnabled},
|
||||||
|
// Logger: # Leave blank to use the default logger, or provide your own
|
||||||
|
HTTPClient: httpClient,
|
||||||
|
}
|
||||||
|
// Create the Compute Client
|
||||||
|
client, err := compute.NewComputeClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error creating OPC Compute Client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the state bag
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("config", b.config)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
state.Put("client", client)
|
||||||
|
|
||||||
|
// Build the steps
|
||||||
|
steps := []multistep.Step{
|
||||||
|
&ocommon.StepKeyPair{
|
||||||
|
Debug: b.config.PackerDebug,
|
||||||
|
DebugKeyPath: fmt.Sprintf("oci_classic_%s.pem", b.config.PackerBuildName),
|
||||||
|
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
||||||
|
},
|
||||||
|
&stepCreateIPReservation{},
|
||||||
|
&stepAddKeysToAPI{},
|
||||||
|
&stepSecurity{},
|
||||||
|
&stepCreateInstance{},
|
||||||
|
&communicator.StepConnect{
|
||||||
|
Config: &b.config.Comm,
|
||||||
|
Host: ocommon.CommHost,
|
||||||
|
SSHConfig: ocommon.SSHConfig(
|
||||||
|
b.config.Comm.SSHUsername,
|
||||||
|
b.config.Comm.SSHPassword),
|
||||||
|
},
|
||||||
|
&common.StepProvision{},
|
||||||
|
&stepSnapshot{},
|
||||||
|
&stepListImages{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the steps
|
||||||
|
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no snapshot, then just return
|
||||||
|
if _, ok := state.GetOk("snapshot"); !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the artifact and return it
|
||||||
|
artifact := &Artifact{
|
||||||
|
ImageListVersion: state.Get("image_list_version").(int),
|
||||||
|
MachineImageName: state.Get("machine_image_name").(string),
|
||||||
|
MachineImageFile: state.Get("machine_image_file").(string),
|
||||||
|
driver: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifact, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel terminates a running build.
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
"github.com/hashicorp/packer/helper/config"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
attribs map[string]interface{}
|
||||||
|
|
||||||
|
// Access config overrides
|
||||||
|
Username string `mapstructure:"username"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
IdentityDomain string `mapstructure:"identity_domain"`
|
||||||
|
APIEndpoint string `mapstructure:"api_endpoint"`
|
||||||
|
apiEndpointURL *url.URL
|
||||||
|
|
||||||
|
// Image
|
||||||
|
ImageName string `mapstructure:"image_name"`
|
||||||
|
Shape string `mapstructure:"shape"`
|
||||||
|
SourceImageList string `mapstructure:"source_image_list"`
|
||||||
|
DestImageList string `mapstructure:"dest_image_list"`
|
||||||
|
// Attributes and Atributes file are both optional and mutually exclusive.
|
||||||
|
Attributes string `mapstructure:"attributes"`
|
||||||
|
AttributesFile string `mapstructure:"attributes_file"`
|
||||||
|
// Optional; if you don't enter anything, the image list description
|
||||||
|
// will read "Packer-built image list"
|
||||||
|
DestImageListDescription string `mapstructure:"image_description"`
|
||||||
|
// Optional. Describes what computers are allowed to reach your instance
|
||||||
|
// via SSH. This whitelist must contain the computer you're running Packer
|
||||||
|
// from. It defaults to public-internet, meaning that you can SSH into your
|
||||||
|
// instance from anywhere as long as you have the right keys
|
||||||
|
SSHSourceList string `mapstructure:"ssh_source_list"`
|
||||||
|
|
||||||
|
ctx interpolate.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(raws ...interface{}) (*Config, error) {
|
||||||
|
c := &Config{}
|
||||||
|
|
||||||
|
// Decode from template
|
||||||
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
Interpolate: true,
|
||||||
|
InterpolateContext: &c.ctx,
|
||||||
|
}, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.apiEndpointURL, err = url.Parse(c.APIEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing API Endpoint: %s", err)
|
||||||
|
}
|
||||||
|
// Set default source list
|
||||||
|
if c.SSHSourceList == "" {
|
||||||
|
c.SSHSourceList = "seciplist:/oracle/public/public-internet"
|
||||||
|
}
|
||||||
|
// Use default oracle username with sudo privileges
|
||||||
|
if c.Comm.SSHUsername == "" {
|
||||||
|
c.Comm.SSHUsername = "opc"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that all required fields are present
|
||||||
|
var errs *packer.MultiError
|
||||||
|
required := map[string]string{
|
||||||
|
"username": c.Username,
|
||||||
|
"password": c.Password,
|
||||||
|
"api_endpoint": c.APIEndpoint,
|
||||||
|
"identity_domain": c.IdentityDomain,
|
||||||
|
"source_image_list": c.SourceImageList,
|
||||||
|
"dest_image_list": c.DestImageList,
|
||||||
|
"shape": c.Shape,
|
||||||
|
}
|
||||||
|
for k, v := range required {
|
||||||
|
if v == "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You must specify a %s.", k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Attributes != "" && c.AttributesFile != "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
|
||||||
|
} else if c.AttributesFile != "" {
|
||||||
|
if _, err := os.Stat(c.AttributesFile); err != nil {
|
||||||
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("attributes_file not found: %s", c.AttributesFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||||
|
errs = packer.MultiErrorAppend(errs, es...)
|
||||||
|
}
|
||||||
|
if c.Comm.Type == "winrm" {
|
||||||
|
err = fmt.Errorf("winRM is not supported with the oracle-classic builder yet.")
|
||||||
|
errs = packer.MultiErrorAppend(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpack attributes from json into config
|
||||||
|
var data map[string]interface{}
|
||||||
|
|
||||||
|
if c.Attributes != "" {
|
||||||
|
err := json.Unmarshal([]byte(c.Attributes), &data)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem parsing json from attributes: %s", err)
|
||||||
|
packer.MultiErrorAppend(errs, err)
|
||||||
|
}
|
||||||
|
c.attribs = data
|
||||||
|
} else if c.AttributesFile != "" {
|
||||||
|
fidata, err := ioutil.ReadFile(c.AttributesFile)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem reading attributes_file: %s", err)
|
||||||
|
packer.MultiErrorAppend(errs, err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(fidata, &data)
|
||||||
|
c.attribs = data
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem parsing json from attrinutes_file: %s", err)
|
||||||
|
packer.MultiErrorAppend(errs, err)
|
||||||
|
}
|
||||||
|
c.attribs = data
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"identity_domain": "abc12345",
|
||||||
|
"username": "test@hashicorp.com",
|
||||||
|
"password": "testpassword123",
|
||||||
|
"api_endpoint": "https://api-test.compute.test.oraclecloud.com/",
|
||||||
|
"dest_image_list": "/Config-thing/myuser/myimage",
|
||||||
|
"source_image_list": "/oracle/public/whatever",
|
||||||
|
"shape": "oc3",
|
||||||
|
"image_name": "TestImageName",
|
||||||
|
"ssh_username": "opc",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigAutoFillsSourceList(t *testing.T) {
|
||||||
|
tc := testConfig()
|
||||||
|
conf, err := NewConfig(tc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Should not have error: %s", err.Error())
|
||||||
|
}
|
||||||
|
if conf.SSHSourceList != "seciplist:/oracle/public/public-internet" {
|
||||||
|
t.Fatalf("conf.SSHSourceList should have been "+
|
||||||
|
"\"seciplist:/oracle/public/public-internet\" but is \"%s\"",
|
||||||
|
conf.SSHSourceList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigValidationCatchesMissing(t *testing.T) {
|
||||||
|
required := []string{
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
"api_endpoint",
|
||||||
|
"identity_domain",
|
||||||
|
"dest_image_list",
|
||||||
|
"source_image_list",
|
||||||
|
"shape",
|
||||||
|
}
|
||||||
|
for _, key := range required {
|
||||||
|
tc := testConfig()
|
||||||
|
delete(tc, key)
|
||||||
|
_, err := NewConfig(tc)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Test should have failed when config lacked %s!", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidationsIgnoresOptional(t *testing.T) {
|
||||||
|
tc := testConfig()
|
||||||
|
delete(tc, "ssh_username")
|
||||||
|
_, err := NewConfig(tc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Shouldn't care if ssh_username is missing: err: %#v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Log(input ...interface{}) {
|
||||||
|
if !l.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println(input...)
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepAddKeysToAPI struct{}
|
||||||
|
|
||||||
|
func (s *stepAddKeysToAPI) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
// get variables from state
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
|
||||||
|
// grab packer-generated key from statebag context.
|
||||||
|
sshPublicKey := strings.TrimSpace(state.Get("publicKey").(string))
|
||||||
|
|
||||||
|
// form API call to add key to compute cloud
|
||||||
|
sshKeyName := fmt.Sprintf("/Compute-%s/%s/packer_generated_key_%s",
|
||||||
|
config.IdentityDomain, config.Username, uuid.TimeOrderedUUID())
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Creating temporary key: %s", sshKeyName))
|
||||||
|
|
||||||
|
sshKeysClient := client.SSHKeys()
|
||||||
|
sshKeysInput := compute.CreateSSHKeyInput{
|
||||||
|
Name: sshKeyName,
|
||||||
|
Key: sshPublicKey,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the packer-generated SSH key into the Oracle Compute cloud.
|
||||||
|
keyInfo, err := sshKeysClient.CreateSSHKey(&sshKeysInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem adding Public SSH key through Oracle's API: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
state.Put("key_name", keyInfo.Name)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepAddKeysToAPI) Cleanup(state multistep.StateBag) {
|
||||||
|
// Delete the keys we created during this run
|
||||||
|
keyName := state.Get("key_name").(string)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("Deleting SSH keys...")
|
||||||
|
deleteInput := compute.DeleteSSHKeyInput{Name: keyName}
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
deleteClient := client.SSHKeys()
|
||||||
|
err := deleteClient.DeleteSSHKey(&deleteInput)
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error deleting SSH keys: %s", err.Error()))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepCreateInstance struct{}
|
||||||
|
|
||||||
|
func (s *stepCreateInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
// get variables from state
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("Creating Instance...")
|
||||||
|
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
keyName := state.Get("key_name").(string)
|
||||||
|
ipAddName := state.Get("ipres_name").(string)
|
||||||
|
secListName := state.Get("security_list").(string)
|
||||||
|
|
||||||
|
netInfo := compute.NetworkingInfo{
|
||||||
|
Nat: []string{ipAddName},
|
||||||
|
SecLists: []string{secListName},
|
||||||
|
}
|
||||||
|
|
||||||
|
// get instances client
|
||||||
|
instanceClient := client.Instances()
|
||||||
|
|
||||||
|
// Instances Input
|
||||||
|
input := &compute.CreateInstanceInput{
|
||||||
|
Name: config.ImageName,
|
||||||
|
Shape: config.Shape,
|
||||||
|
ImageList: config.SourceImageList,
|
||||||
|
SSHKeys: []string{keyName},
|
||||||
|
Networking: map[string]compute.NetworkingInfo{"eth0": netInfo},
|
||||||
|
Attributes: config.attribs,
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceInfo, err := instanceClient.CreateInstance(input)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem creating instance: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put("instance_id", instanceInfo.ID)
|
||||||
|
ui.Message(fmt.Sprintf("Created instance: %s.", instanceInfo.ID))
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateInstance) Cleanup(state multistep.StateBag) {
|
||||||
|
// terminate instance
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
imID := state.Get("instance_id").(string)
|
||||||
|
|
||||||
|
ui.Say("Terminating source instance...")
|
||||||
|
|
||||||
|
instanceClient := client.Instances()
|
||||||
|
input := &compute.DeleteInstanceInput{
|
||||||
|
Name: config.ImageName,
|
||||||
|
ID: imID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := instanceClient.DeleteInstance(input)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem destroying instance: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO wait for instance state to change to deleted?
|
||||||
|
ui.Say("Terminated instance.")
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepCreateIPReservation struct{}
|
||||||
|
|
||||||
|
func (s *stepCreateIPReservation) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
iprClient := client.IPReservations()
|
||||||
|
// TODO: add optional Name and Tags
|
||||||
|
|
||||||
|
ipresName := fmt.Sprintf("ipres_%s_%s", config.ImageName, uuid.TimeOrderedUUID())
|
||||||
|
ui.Message(fmt.Sprintf("Creating temporary IP reservation: %s", ipresName))
|
||||||
|
|
||||||
|
IPInput := &compute.CreateIPReservationInput{
|
||||||
|
ParentPool: compute.PublicReservationPool,
|
||||||
|
Permanent: true,
|
||||||
|
Name: ipresName,
|
||||||
|
}
|
||||||
|
ipRes, err := iprClient.CreateIPReservation(IPInput)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating IP Reservation: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
state.Put("instance_ip", ipRes.IP)
|
||||||
|
state.Put("ipres_name", ipresName)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateIPReservation) Cleanup(state multistep.StateBag) {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("Cleaning up IP reservations...")
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
|
||||||
|
ipResName := state.Get("ipres_name").(string)
|
||||||
|
input := compute.DeleteIPReservationInput{Name: ipResName}
|
||||||
|
ipClient := client.IPReservations()
|
||||||
|
err := ipClient.DeleteIPReservation(&input)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error deleting IP reservation: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepListImages struct{}
|
||||||
|
|
||||||
|
func (s *stepListImages) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
// get variables from state
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
ui.Say("Adding image to image list...")
|
||||||
|
|
||||||
|
imageListClient := client.ImageList()
|
||||||
|
getInput := compute.GetImageListInput{
|
||||||
|
Name: config.DestImageList,
|
||||||
|
}
|
||||||
|
imList, err := imageListClient.GetImageList(&getInput)
|
||||||
|
if err != nil {
|
||||||
|
// If the list didn't exist, create it.
|
||||||
|
ui.Say(fmt.Sprintf(err.Error()))
|
||||||
|
ui.Say(fmt.Sprintf("Destination image list %s does not exist; Creating it...",
|
||||||
|
config.DestImageList))
|
||||||
|
|
||||||
|
ilInput := compute.CreateImageListInput{
|
||||||
|
Name: config.DestImageList,
|
||||||
|
Description: "Packer-built image list",
|
||||||
|
}
|
||||||
|
|
||||||
|
imList, err = imageListClient.CreateImageList(&ilInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem creating image list: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
ui.Message(fmt.Sprintf("Image list %s created!", imList.URI))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now create and image list entry for the image into that list.
|
||||||
|
snap := state.Get("snapshot").(*compute.Snapshot)
|
||||||
|
version := len(imList.Entries) + 1
|
||||||
|
entriesClient := client.ImageListEntries()
|
||||||
|
entriesInput := compute.CreateImageListEntryInput{
|
||||||
|
Name: config.DestImageList,
|
||||||
|
MachineImages: []string{fmt.Sprintf("/Compute-%s/%s/%s",
|
||||||
|
config.IdentityDomain, config.Username, snap.MachineImage)},
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
entryInfo, err := entriesClient.CreateImageListEntry(&entriesInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem creating an image list entry: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
state.Put("image_list_entry", entryInfo)
|
||||||
|
ui.Message(fmt.Sprintf("created image list entry %s", entryInfo.Name))
|
||||||
|
|
||||||
|
machineImagesClient := client.MachineImages()
|
||||||
|
getImagesInput := compute.GetMachineImageInput{
|
||||||
|
Name: config.ImageName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update image list default to use latest version
|
||||||
|
updateInput := compute.UpdateImageListInput{
|
||||||
|
Default: version,
|
||||||
|
Description: config.DestImageListDescription,
|
||||||
|
Name: config.DestImageList,
|
||||||
|
}
|
||||||
|
_, err = imageListClient.UpdateImageList(&updateInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem updating default image list version: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab info about the machine image to return with the artifact
|
||||||
|
imInfo, err := machineImagesClient.GetMachineImage(&getImagesInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem getting machine image info: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
state.Put("machine_image_file", imInfo.File)
|
||||||
|
state.Put("machine_image_name", imInfo.Name)
|
||||||
|
state.Put("image_list_version", version)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepListImages) Cleanup(state multistep.StateBag) {
|
||||||
|
// Nothing to do
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepSecurity struct{}
|
||||||
|
|
||||||
|
func (s *stepSecurity) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
ui.Say("Configuring security lists and rules to enable SSH access...")
|
||||||
|
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
|
||||||
|
secListName := fmt.Sprintf("/Compute-%s/%s/Packer_SSH_Allow_%s",
|
||||||
|
config.IdentityDomain, config.Username, config.ImageName)
|
||||||
|
secListClient := client.SecurityLists()
|
||||||
|
secListInput := compute.CreateSecurityListInput{
|
||||||
|
Description: "Packer-generated security list to give packer ssh access",
|
||||||
|
Name: secListName,
|
||||||
|
}
|
||||||
|
_, err := secListClient.CreateSecurityList(&secListInput)
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "already exists") {
|
||||||
|
err = fmt.Errorf("Error creating security List to"+
|
||||||
|
" allow Packer to connect to Oracle instance via SSH: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DOCS NOTE: user must have Compute_Operations role
|
||||||
|
// Create security rule that allows Packer to connect via SSH
|
||||||
|
secRulesClient := client.SecRules()
|
||||||
|
secRulesInput := compute.CreateSecRuleInput{
|
||||||
|
Action: "PERMIT",
|
||||||
|
Application: "/oracle/public/ssh",
|
||||||
|
Description: "Packer-generated security rule to allow ssh",
|
||||||
|
DestinationList: fmt.Sprintf("seclist:%s", secListName),
|
||||||
|
Name: fmt.Sprintf("Packer-allow-SSH-Rule_%s", config.ImageName),
|
||||||
|
SourceList: config.SSHSourceList,
|
||||||
|
}
|
||||||
|
|
||||||
|
secRuleName := fmt.Sprintf("/Compute-%s/%s/Packer-allow-SSH-Rule_%s",
|
||||||
|
config.IdentityDomain, config.Username, config.ImageName)
|
||||||
|
_, err = secRulesClient.CreateSecRule(&secRulesInput)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error creating security rule to allow SSH: %s", err.Error())
|
||||||
|
if !strings.Contains(err.Error(), "already exists") {
|
||||||
|
err = fmt.Errorf("Error creating security rule to"+
|
||||||
|
" allow Packer to connect to Oracle instance via SSH: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.Put("security_rule_name", secRuleName)
|
||||||
|
state.Put("security_list", secListName)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepSecurity) Cleanup(state multistep.StateBag) {
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("Deleting temporary rules and lists...")
|
||||||
|
|
||||||
|
// delete security rules that Packer generated
|
||||||
|
secRuleName := state.Get("security_rule_name").(string)
|
||||||
|
secRulesClient := client.SecRules()
|
||||||
|
ruleInput := compute.DeleteSecRuleInput{Name: secRuleName}
|
||||||
|
err := secRulesClient.DeleteSecRule(&ruleInput)
|
||||||
|
if err != nil {
|
||||||
|
ui.Say(fmt.Sprintf("Error deleting the packer-generated security rule %s; "+
|
||||||
|
"please delete manually. (error: %s)", secRuleName, err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete security list that Packer generated
|
||||||
|
secListName := state.Get("security_list").(string)
|
||||||
|
secListClient := client.SecurityLists()
|
||||||
|
input := compute.DeleteSecurityListInput{Name: secListName}
|
||||||
|
err = secListClient.DeleteSecurityList(&input)
|
||||||
|
if err != nil {
|
||||||
|
ui.Say(fmt.Sprintf("Error deleting the packer-generated security list %s; "+
|
||||||
|
"please delete manually. (error : %s)", secListName, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package classic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-oracle-terraform/compute"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepSnapshot struct{}
|
||||||
|
|
||||||
|
func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
// get variables from state
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("Creating Snapshot...")
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
instanceID := state.Get("instance_id").(string)
|
||||||
|
|
||||||
|
// get instances client
|
||||||
|
snapshotClient := client.Snapshots()
|
||||||
|
|
||||||
|
// Instances Input
|
||||||
|
snapshotInput := &compute.CreateSnapshotInput{
|
||||||
|
Instance: fmt.Sprintf("%s/%s", config.ImageName, instanceID),
|
||||||
|
MachineImage: config.ImageName,
|
||||||
|
}
|
||||||
|
|
||||||
|
snap, err := snapshotClient.CreateSnapshot(snapshotInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem creating snapshot: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put("snapshot", snap)
|
||||||
|
ui.Message(fmt.Sprintf("Created snapshot: %s.", snap.Name))
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
|
||||||
|
// Delete the snapshot
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
ui.Say("Deleting Snapshot...")
|
||||||
|
client := state.Get("client").(*compute.ComputeClient)
|
||||||
|
snap := state.Get("snapshot").(*compute.Snapshot)
|
||||||
|
snapClient := client.Snapshots()
|
||||||
|
snapInput := compute.DeleteSnapshotInput{
|
||||||
|
Snapshot: snap.Name,
|
||||||
|
MachineImage: snap.MachineImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := snapClient.DeleteSnapshotResourceOnly(&snapInput)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Problem deleting snapshot: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
state.Put("error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package oci
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func commHost(state multistep.StateBag) (string, error) {
|
func CommHost(state multistep.StateBag) (string, error) {
|
||||||
ipAddress := state.Get("instance_ip").(string)
|
ipAddress := state.Get("instance_ip").(string)
|
||||||
return ipAddress, nil
|
return ipAddress, nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package oci
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -16,13 +16,13 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepKeyPair struct {
|
type StepKeyPair struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
DebugKeyPath string
|
DebugKeyPath string
|
||||||
PrivateKeyFile string
|
PrivateKeyFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
if s.PrivateKeyFile != "" {
|
if s.PrivateKeyFile != "" {
|
||||||
|
@ -112,6 +112,6 @@ func (s *stepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepKeyPair) Cleanup(state multistep.StateBag) {
|
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
ocommon "github.com/hashicorp/packer/builder/oracle/common"
|
||||||
client "github.com/hashicorp/packer/builder/oracle/oci/client"
|
client "github.com/hashicorp/packer/builder/oracle/oci/client"
|
||||||
"github.com/hashicorp/packer/common"
|
"github.com/hashicorp/packer/common"
|
||||||
"github.com/hashicorp/packer/helper/communicator"
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
@ -50,7 +51,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
|
|
||||||
// Build the steps
|
// Build the steps
|
||||||
steps := []multistep.Step{
|
steps := []multistep.Step{
|
||||||
&stepKeyPair{
|
&ocommon.StepKeyPair{
|
||||||
Debug: b.config.PackerDebug,
|
Debug: b.config.PackerDebug,
|
||||||
DebugKeyPath: fmt.Sprintf("oci_%s.pem", b.config.PackerBuildName),
|
DebugKeyPath: fmt.Sprintf("oci_%s.pem", b.config.PackerBuildName),
|
||||||
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
||||||
|
@ -59,8 +60,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
||||||
&stepInstanceInfo{},
|
&stepInstanceInfo{},
|
||||||
&communicator.StepConnect{
|
&communicator.StepConnect{
|
||||||
Config: &b.config.Comm,
|
Config: &b.config.Comm,
|
||||||
Host: commHost,
|
Host: ocommon.CommHost,
|
||||||
SSHConfig: SSHConfig(
|
SSHConfig: ocommon.SSHConfig(
|
||||||
b.config.Comm.SSHUsername,
|
b.config.Comm.SSHUsername,
|
||||||
b.config.Comm.SSHPassword),
|
b.config.Comm.SSHPassword),
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,6 +25,7 @@ var accels = map[string]struct{}{
|
||||||
"kvm": {},
|
"kvm": {},
|
||||||
"tcg": {},
|
"tcg": {},
|
||||||
"xen": {},
|
"xen": {},
|
||||||
|
"hax": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var netDevice = map[string]bool{
|
var netDevice = map[string]bool{
|
||||||
|
@ -259,7 +260,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
if _, ok := accels[b.config.Accelerator]; !ok {
|
if _, ok := accels[b.config.Accelerator]; !ok {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', or 'none' are allowed"))
|
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', or 'none' are allowed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := netDevice[b.config.NetDevice]; !ok {
|
if _, ok := netDevice[b.config.NetDevice]; !ok {
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Artifact struct {
|
||||||
|
// The name of the image
|
||||||
|
imageName string
|
||||||
|
|
||||||
|
// The ID of the image
|
||||||
|
imageID string
|
||||||
|
|
||||||
|
// The name of the snapshot
|
||||||
|
snapshotName string
|
||||||
|
|
||||||
|
// The ID of the snapshot
|
||||||
|
snapshotID string
|
||||||
|
|
||||||
|
// The name of the region
|
||||||
|
regionName string
|
||||||
|
|
||||||
|
// The client for making API calls
|
||||||
|
client *api.ScalewayAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Artifact) BuilderId() string {
|
||||||
|
return BuilderId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Artifact) Files() []string {
|
||||||
|
// No files with Scaleway
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Id() string {
|
||||||
|
return fmt.Sprintf("%s:%s", a.regionName, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) State(name string) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Destroy() error {
|
||||||
|
log.Printf("Destroying image: %s (%s)", a.imageID, a.imageName)
|
||||||
|
if err := a.client.DeleteImage(a.imageID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Destroying snapshot: %s (%s)", a.snapshotID, a.snapshotName)
|
||||||
|
if err := a.client.DeleteSnapshot(a.snapshotID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArtifact_Impl(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Artifact{}
|
||||||
|
if _, ok := raw.(packer.Artifact); !ok {
|
||||||
|
t.Fatalf("Artifact should be artifact")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactId(t *testing.T) {
|
||||||
|
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
||||||
|
expected := "ams1:cc586e45-5156-4f71-b223-cf406b10dd1d"
|
||||||
|
|
||||||
|
if a.Id() != expected {
|
||||||
|
t.Fatalf("artifact ID should match: %v", expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArtifactString(t *testing.T) {
|
||||||
|
a := &Artifact{"packer-foobar-image", "cc586e45-5156-4f71-b223-cf406b10dd1d", "packer-foobar-snapshot", "cc586e45-5156-4f71-b223-cf406b10dd1c", "ams1", nil}
|
||||||
|
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)"
|
||||||
|
|
||||||
|
if a.String() != expected {
|
||||||
|
t.Fatalf("artifact string should match: %v", expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// The scaleway package contains a packer.Builder implementation
|
||||||
|
// that builds Scaleway images (snapshots).
|
||||||
|
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The unique id for the builder
|
||||||
|
const BuilderId = "hashicorp.scaleway"
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config Config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
c, warnings, errs := NewConfig(raws...)
|
||||||
|
if errs != nil {
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
b.config = *c
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
client, err := api.NewScalewayAPI(b.config.Organization, b.config.Token, b.config.UserAgent, b.config.Region)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("config", b.config)
|
||||||
|
state.Put("client", client)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
steps := []multistep.Step{
|
||||||
|
&stepCreateSSHKey{
|
||||||
|
Debug: b.config.PackerDebug,
|
||||||
|
DebugKeyPath: fmt.Sprintf("scw_%s.pem", b.config.PackerBuildName),
|
||||||
|
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
||||||
|
},
|
||||||
|
new(stepCreateServer),
|
||||||
|
new(stepServerInfo),
|
||||||
|
&communicator.StepConnect{
|
||||||
|
Config: &b.config.Comm,
|
||||||
|
Host: commHost,
|
||||||
|
SSHConfig: sshConfig,
|
||||||
|
},
|
||||||
|
new(common.StepProvision),
|
||||||
|
new(stepShutdown),
|
||||||
|
new(stepSnapshot),
|
||||||
|
new(stepImage),
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were interrupted or cancelled, then just exit.
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
return nil, errors.New("Build was cancelled.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
||||||
|
return nil, errors.New("Build was halted.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := state.GetOk("snapshot_name"); !ok {
|
||||||
|
return nil, errors.New("Cannot find snapshot_name in state.")
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact := &Artifact{
|
||||||
|
imageName: state.Get("image_name").(string),
|
||||||
|
imageID: state.Get("image_id").(string),
|
||||||
|
snapshotName: state.Get("snapshot_name").(string),
|
||||||
|
snapshotID: state.Get("snapshot_id").(string),
|
||||||
|
regionName: state.Get("region").(string),
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifact, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"api_access_key": "foo",
|
||||||
|
"api_token": "bar",
|
||||||
|
"region": "ams1",
|
||||||
|
"commercial_type": "VC1S",
|
||||||
|
"ssh_username": "root",
|
||||||
|
"image": "image-uuid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Builder{}
|
||||||
|
if _, ok := raw.(packer.Builder); !ok {
|
||||||
|
t.Fatalf("Builder should be a builder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_Prepare_BadType(t *testing.T) {
|
||||||
|
b := &Builder{}
|
||||||
|
c := map[string]interface{}{
|
||||||
|
"api_token": []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings, err := b.Prepare(c)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("prepare should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_InvalidKey(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
config["i_should_not_be_valid"] = true
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_Region(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "region")
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should error")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "ams1"
|
||||||
|
|
||||||
|
config["region"] = expected
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.Region != expected {
|
||||||
|
t.Errorf("found %s, expected %s", b.config.Region, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_CommercialType(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "commercial_type")
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should error")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "VC1S"
|
||||||
|
|
||||||
|
config["commercial_type"] = expected
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.CommercialType != expected {
|
||||||
|
t.Errorf("found %s, expected %s", b.config.CommercialType, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_Image(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
delete(config, "image")
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "cc586e45-5156-4f71-b223-cf406b10dd1c"
|
||||||
|
|
||||||
|
config["image"] = expected
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.Image != expected {
|
||||||
|
t.Errorf("found %s, expected %s", b.config.Image, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_SnapshotName(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.SnapshotName == "" {
|
||||||
|
t.Errorf("invalid: %s", b.config.SnapshotName)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["snapshot_name"] = "foobarbaz"
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["snapshot_name"] = "{{timestamp}}"
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = strconv.ParseInt(b.config.SnapshotName, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to parse int in template: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ServerName(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
config := testConfig()
|
||||||
|
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.config.ServerName == "" {
|
||||||
|
t.Errorf("invalid: %s", b.config.ServerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["server_name"] = "foobar"
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["server_name"] = "foobar-{{timestamp}}"
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config["server_name"] = "foobar-{{"
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/common/uuid"
|
||||||
|
"github.com/hashicorp/packer/helper/communicator"
|
||||||
|
"github.com/hashicorp/packer/helper/config"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
Comm communicator.Config `mapstructure:",squash"`
|
||||||
|
|
||||||
|
Token string `mapstructure:"api_token"`
|
||||||
|
Organization string `mapstructure:"api_access_key"`
|
||||||
|
|
||||||
|
Region string `mapstructure:"region"`
|
||||||
|
Image string `mapstructure:"image"`
|
||||||
|
CommercialType string `mapstructure:"commercial_type"`
|
||||||
|
|
||||||
|
SnapshotName string `mapstructure:"snapshot_name"`
|
||||||
|
ImageName string `mapstructure:"image_name"`
|
||||||
|
ServerName string `mapstructure:"server_name"`
|
||||||
|
|
||||||
|
UserAgent string
|
||||||
|
ctx interpolate.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
|
c := new(Config)
|
||||||
|
|
||||||
|
var md mapstructure.Metadata
|
||||||
|
err := config.Decode(c, &config.DecodeOpts{
|
||||||
|
Metadata: &md,
|
||||||
|
Interpolate: true,
|
||||||
|
InterpolateContext: &c.ctx,
|
||||||
|
InterpolateFilter: &interpolate.RenderFilter{
|
||||||
|
Exclude: []string{
|
||||||
|
"run_command",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.UserAgent = "Packer - Scaleway builder"
|
||||||
|
|
||||||
|
if c.Organization == "" {
|
||||||
|
c.Organization = os.Getenv("SCALEWAY_API_ACCESS_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Token == "" {
|
||||||
|
c.Token = os.Getenv("SCALEWAY_API_TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SnapshotName == "" {
|
||||||
|
def, err := interpolate.Render("snapshot-packer-{{timestamp}}", nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SnapshotName = def
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ImageName == "" {
|
||||||
|
def, err := interpolate.Render("image-packer-{{timestamp}}", nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ImageName = def
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ServerName == "" {
|
||||||
|
// Default to packer-[time-ordered-uuid]
|
||||||
|
c.ServerName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs *packer.MultiError
|
||||||
|
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||||
|
errs = packer.MultiErrorAppend(errs, es...)
|
||||||
|
}
|
||||||
|
if c.Organization == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("Scaleway Organization ID must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Token == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("Scaleway Token must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Region == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("region is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CommercialType == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("commercial type is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Image == "" {
|
||||||
|
errs = packer.MultiErrorAppend(
|
||||||
|
errs, errors.New("image is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
common.ScrubConfig(c, c.Token)
|
||||||
|
return c, nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
packerssh "github.com/hashicorp/packer/communicator/ssh"
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func commHost(state multistep.StateBag) (string, error) {
|
||||||
|
ipAddress := state.Get("server_ip").(string)
|
||||||
|
return ipAddress, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) {
|
||||||
|
config := state.Get("config").(Config)
|
||||||
|
var privateKey string
|
||||||
|
|
||||||
|
var auth []ssh.AuthMethod
|
||||||
|
|
||||||
|
if config.Comm.SSHAgentAuth {
|
||||||
|
authSock := os.Getenv("SSH_AUTH_SOCK")
|
||||||
|
if authSock == "" {
|
||||||
|
return nil, fmt.Errorf("SSH_AUTH_SOCK is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
sshAgent, err := net.Dial("unix", authSock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err)
|
||||||
|
}
|
||||||
|
auth = []ssh.AuthMethod{
|
||||||
|
ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Comm.SSHPassword != "" {
|
||||||
|
auth = append(auth,
|
||||||
|
ssh.Password(config.Comm.SSHPassword),
|
||||||
|
ssh.KeyboardInteractive(
|
||||||
|
packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Comm.SSHPrivateKey != "" {
|
||||||
|
if priv, ok := state.GetOk("privateKey"); ok {
|
||||||
|
privateKey = priv.(string)
|
||||||
|
}
|
||||||
|
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||||
|
}
|
||||||
|
auth = append(auth, ssh.PublicKeys(signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ssh.ClientConfig{
|
||||||
|
User: config.Comm.SSHUsername,
|
||||||
|
Auth: auth,
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepImage struct{}
|
||||||
|
|
||||||
|
func (s *stepImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
c := state.Get("config").(Config)
|
||||||
|
snapshotID := state.Get("snapshot_id").(string)
|
||||||
|
bootscriptID := ""
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Creating image: %v", c.ImageName))
|
||||||
|
|
||||||
|
image, err := client.GetImage(c.Image)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error getting initial image info: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
if image.DefaultBootscript != nil {
|
||||||
|
bootscriptID = image.DefaultBootscript.Identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
imageID, err := client.PostImage(snapshotID, c.ImageName, bootscriptID, image.Arch)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating image: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Image ID: %s", imageID)
|
||||||
|
state.Put("image_id", imageID)
|
||||||
|
state.Put("image_name", c.ImageName)
|
||||||
|
state.Put("region", c.Region)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepImage) Cleanup(state multistep.StateBag) {
|
||||||
|
// no cleanup
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepCreateServer struct {
|
||||||
|
serverID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateServer) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
c := state.Get("config").(Config)
|
||||||
|
sshPubKey := state.Get("ssh_pubkey").(string)
|
||||||
|
tags := []string{}
|
||||||
|
|
||||||
|
ui.Say("Creating server...")
|
||||||
|
|
||||||
|
if sshPubKey != "" {
|
||||||
|
tags = []string{fmt.Sprintf("AUTHORIZED_KEY=%s", strings.TrimSpace(sshPubKey))}
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := client.PostServer(api.ScalewayServerDefinition{
|
||||||
|
Name: c.ServerName,
|
||||||
|
Image: &c.Image,
|
||||||
|
Organization: c.Organization,
|
||||||
|
CommercialType: c.CommercialType,
|
||||||
|
Tags: tags,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating server: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.PostServerAction(server, "poweron")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error starting server: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
s.serverID = server
|
||||||
|
|
||||||
|
state.Put("server_id", server)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
|
||||||
|
if s.serverID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
ui.Say("Destroying server...")
|
||||||
|
|
||||||
|
err := client.DeleteServerForce(s.serverID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error destroying server. Please destroy it manually: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepCreateSSHKey struct {
|
||||||
|
Debug bool
|
||||||
|
DebugKeyPath string
|
||||||
|
PrivateKeyFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateSSHKey) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if s.PrivateKeyFile != "" {
|
||||||
|
ui.Say("Using existing SSH private key")
|
||||||
|
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf(
|
||||||
|
"Error loading configured private key file: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Put("privateKey", string(privateKeyBytes))
|
||||||
|
state.Put("ssh_pubkey", "")
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating temporary ssh key for server...")
|
||||||
|
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASN.1 DER encoded form
|
||||||
|
priv_der := x509.MarshalPKCS1PrivateKey(priv)
|
||||||
|
priv_blk := pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Headers: nil,
|
||||||
|
Bytes: priv_der,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the private key in the statebag for later
|
||||||
|
state.Put("privateKey", string(pem.EncodeToMemory(&priv_blk)))
|
||||||
|
|
||||||
|
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
|
||||||
|
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
|
||||||
|
pub_sshformat = strings.Replace(pub_sshformat, " ", "_", -1)
|
||||||
|
|
||||||
|
log.Printf("temporary ssh key created")
|
||||||
|
|
||||||
|
// Remember some state for the future
|
||||||
|
state.Put("ssh_pubkey", string(pub_sshformat))
|
||||||
|
|
||||||
|
// If we're in debug mode, output the private key to the working directory.
|
||||||
|
if s.Debug {
|
||||||
|
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||||
|
f, err := os.Create(s.DebugKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Write the key out
|
||||||
|
if _, err := f.Write(pem.EncodeToMemory(&priv_blk)); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod it so that it is SSH ready
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if err := f.Chmod(0600); err != nil {
|
||||||
|
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
|
||||||
|
// SSH key is passed via tag. Nothing to do here.
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepServerInfo struct{}
|
||||||
|
|
||||||
|
func (s *stepServerInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
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")
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error waiting for server to become booted: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := client.GetServer(serverID)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error retrieving server: %s", err)
|
||||||
|
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)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepServerInfo) Cleanup(state multistep.StateBag) {
|
||||||
|
// no cleanup
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepShutdown struct{}
|
||||||
|
|
||||||
|
func (s *stepShutdown) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
serverID := state.Get("server_id").(string)
|
||||||
|
|
||||||
|
ui.Say("Shutting down server...")
|
||||||
|
|
||||||
|
err := client.PostServerAction(serverID, "poweroff")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error stopping server: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.WaitForServerState(client, serverID, "stopped")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error shutting down server: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepShutdown) Cleanup(state multistep.StateBag) {
|
||||||
|
// no cleanup
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package scaleway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/scaleway/scaleway-cli/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepSnapshot struct{}
|
||||||
|
|
||||||
|
func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
|
client := state.Get("client").(*api.ScalewayAPI)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating snapshot: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Snapshot ID: %s", snapshot)
|
||||||
|
state.Put("snapshot_id", snapshot)
|
||||||
|
state.Put("snapshot_name", c.SnapshotName)
|
||||||
|
state.Put("region", c.Region)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepSnapshot) Cleanup(state multistep.StateBag) {
|
||||||
|
// no cleanup
|
||||||
|
}
|
|
@ -122,7 +122,7 @@ func (s *StepDownloadGuestAdditions) Run(ctx context.Context, state multistep.St
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the file/url to an actual URL for step_download to process.
|
// Convert the file/url to an actual URL for step_download to process.
|
||||||
url, err = common.DownloadableURL(url)
|
url, err = common.ValidatedURL(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error preparing guest additions url: %s", err)
|
err := fmt.Errorf("Error preparing guest additions url: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
|
|
@ -97,7 +97,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
if c.SourcePath == "" {
|
if c.SourcePath == "" {
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
|
||||||
} else {
|
} else {
|
||||||
c.SourcePath, err = common.DownloadableURL(c.SourcePath)
|
c.SourcePath, err = common.ValidatedURL(c.SourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ func TestNewConfig_sourcePath(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", warns)
|
t.Fatalf("bad: %#v", warns)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("should error")
|
t.Fatalf("should error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Good
|
// Good
|
||||||
|
|
|
@ -2,13 +2,18 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
)
|
)
|
||||||
|
@ -24,15 +29,11 @@ type Driver interface {
|
||||||
CompactDisk(string) error
|
CompactDisk(string) error
|
||||||
|
|
||||||
// CreateDisk creates a virtual disk with the given size.
|
// CreateDisk creates a virtual disk with the given size.
|
||||||
CreateDisk(string, string, string) error
|
CreateDisk(string, string, string, string) error
|
||||||
|
|
||||||
// Checks if the VMX file at the given path is running.
|
// Checks if the VMX file at the given path is running.
|
||||||
IsRunning(string) (bool, error)
|
IsRunning(string) (bool, error)
|
||||||
|
|
||||||
// CommHost returns the host address for the VM that is being
|
|
||||||
// managed by this driver.
|
|
||||||
CommHost(multistep.StateBag) (string, error)
|
|
||||||
|
|
||||||
// Start starts a VM specified by the path to the VMX given.
|
// Start starts a VM specified by the path to the VMX given.
|
||||||
Start(string, bool) error
|
Start(string, bool) error
|
||||||
|
|
||||||
|
@ -49,14 +50,33 @@ type Driver interface {
|
||||||
// Attach the VMware tools ISO
|
// Attach the VMware tools ISO
|
||||||
ToolsInstall() error
|
ToolsInstall() error
|
||||||
|
|
||||||
// Get the path to the DHCP leases file for the given device.
|
|
||||||
DhcpLeasesPath(string) string
|
|
||||||
|
|
||||||
// Verify checks to make sure that this driver should function
|
// Verify checks to make sure that this driver should function
|
||||||
// properly. This should check that all the files it will use
|
// properly. This should check that all the files it will use
|
||||||
// appear to exist and so on. If everything is okay, this doesn't
|
// appear to exist and so on. If everything is okay, this doesn't
|
||||||
// return an error. Otherwise, this returns an error.
|
// return an error. Otherwise, this returns an error. Each vmware
|
||||||
|
// driver should assign the VmwareMachine callback functions for locating
|
||||||
|
// paths within this function.
|
||||||
Verify() error
|
Verify() error
|
||||||
|
|
||||||
|
/// This is to establish a connection to the guest
|
||||||
|
CommHost(multistep.StateBag) (string, error)
|
||||||
|
|
||||||
|
/// These methods are generally implemented by the VmwareDriver
|
||||||
|
/// structure within this file. A driver implementation can
|
||||||
|
/// reimplement these, though, if it wants.
|
||||||
|
GetVmwareDriver() VmwareDriver
|
||||||
|
|
||||||
|
// Get the guest hw address for the vm
|
||||||
|
GuestAddress(multistep.StateBag) (string, error)
|
||||||
|
|
||||||
|
// Get the guest ip address for the vm
|
||||||
|
GuestIP(multistep.StateBag) (string, error)
|
||||||
|
|
||||||
|
// Get the host hw address for the vm
|
||||||
|
HostAddress(multistep.StateBag) (string, error)
|
||||||
|
|
||||||
|
// Get the host ip address for the vm
|
||||||
|
HostIP(multistep.StateBag) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDriver returns a new driver implementation for this operating
|
// NewDriver returns a new driver implementation for this operating
|
||||||
|
@ -192,3 +212,305 @@ func compareVersions(versionFound string, versionWanted string, product string)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// helper functions that read configuration information from a file
|
||||||
|
// read the network<->device configuration out of the specified path
|
||||||
|
func ReadNetmapConfig(path string) (NetworkMap, error) {
|
||||||
|
fd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
return ReadNetworkMap(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the dhcp configuration out of the specified path
|
||||||
|
func ReadDhcpConfig(path string) (DhcpConfiguration, error) {
|
||||||
|
fd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
return ReadDhcpConfiguration(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the VMX configuration from the specified path
|
||||||
|
func readVMXConfig(path string) (map[string]string, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
vmxBytes, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}, err
|
||||||
|
}
|
||||||
|
return ParseVMX(string(vmxBytes)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the connection type out of a vmx configuration
|
||||||
|
func readCustomDeviceName(vmxData map[string]string) (string, error) {
|
||||||
|
|
||||||
|
connectionType, ok := vmxData["ethernet0.connectiontype"]
|
||||||
|
if !ok || connectionType != "custom" {
|
||||||
|
return "", fmt.Errorf("Unable to determine the device name for the connection type : %s", connectionType)
|
||||||
|
}
|
||||||
|
|
||||||
|
device, ok := vmxData["ethernet0.vnet"]
|
||||||
|
if !ok || device == "" {
|
||||||
|
return "", fmt.Errorf("Unable to determine the device name for the connection type \"%s\" : %s", connectionType, device)
|
||||||
|
}
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This VmwareDriver is a base class that contains default methods
|
||||||
|
// that a Driver can use or implement themselves.
|
||||||
|
type VmwareDriver struct {
|
||||||
|
/// These methods define paths that are utilized by the driver
|
||||||
|
/// A driver must overload these in order to point to the correct
|
||||||
|
/// files so that the address detection (ip and ethernet) machinery
|
||||||
|
/// works.
|
||||||
|
DhcpLeasesPath func(string) string
|
||||||
|
DhcpConfPath func(string) string
|
||||||
|
VmnetnatConfPath func(string) string
|
||||||
|
|
||||||
|
/// This method returns an object with the NetworkNameMapper interface
|
||||||
|
/// that maps network to device and vice-versa.
|
||||||
|
NetworkMapper func() (NetworkNameMapper, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) {
|
||||||
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
|
||||||
|
log.Println("Lookup up IP information...")
|
||||||
|
vmxData, err := readVMXConfig(vmxPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
macAddress := ""
|
||||||
|
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
|
||||||
|
if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
|
||||||
|
return "", errors.New("couldn't find MAC address in VMX")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := net.ParseMAC(macAddress)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
|
||||||
|
|
||||||
|
// grab network mapper
|
||||||
|
netmap, err := d.NetworkMapper()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the stashed network to a device
|
||||||
|
network := state.Get("vmnetwork").(string)
|
||||||
|
device, err := netmap.NameIntoDevice(network)
|
||||||
|
|
||||||
|
// we were unable to find the device, maybe it's a custom one...
|
||||||
|
// so, check to see if it's in the .vmx configuration
|
||||||
|
if err != nil || network == "custom" {
|
||||||
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
vmxData, err := readVMXConfig(vmxPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err = readCustomDeviceName(vmxData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out our MAC address for looking up the guest address
|
||||||
|
MACAddress, err := d.GuestAddress(state)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out the correct dhcp leases
|
||||||
|
dhcpLeasesPath := d.DhcpLeasesPath(device)
|
||||||
|
log.Printf("DHCP leases path: %s", dhcpLeasesPath)
|
||||||
|
if dhcpLeasesPath == "" {
|
||||||
|
return "", errors.New("no DHCP leases path found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// open up the lease and read its contents
|
||||||
|
fh, err := os.Open(dhcpLeasesPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
dhcpBytes, err := ioutil.ReadAll(fh)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start grepping through the file looking for fields that we care about
|
||||||
|
var lastIp string
|
||||||
|
var lastLeaseEnd time.Time
|
||||||
|
|
||||||
|
var curIp string
|
||||||
|
var curLeaseEnd time.Time
|
||||||
|
|
||||||
|
ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
|
||||||
|
endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
|
||||||
|
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
|
||||||
|
|
||||||
|
for _, line := range strings.Split(string(dhcpBytes), "\n") {
|
||||||
|
// Need to trim off CR character when running in windows
|
||||||
|
line = strings.TrimRight(line, "\r")
|
||||||
|
|
||||||
|
matches := ipLineRe.FindStringSubmatch(line)
|
||||||
|
if matches != nil {
|
||||||
|
lastIp = matches[1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = endTimeLineRe.FindStringSubmatch(line)
|
||||||
|
if matches != nil {
|
||||||
|
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the mac address matches and this lease ends farther in the
|
||||||
|
// future than the last match we might have, then choose it.
|
||||||
|
matches = macLineRe.FindStringSubmatch(line)
|
||||||
|
if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
|
||||||
|
curIp = lastIp
|
||||||
|
curLeaseEnd = lastLeaseEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if curIp == "" {
|
||||||
|
return "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", MACAddress, dhcpLeasesPath)
|
||||||
|
}
|
||||||
|
return curIp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
|
||||||
|
|
||||||
|
// grab mapper for converting network<->device
|
||||||
|
netmap, err := d.NetworkMapper()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert network to name
|
||||||
|
network := state.Get("vmnetwork").(string)
|
||||||
|
device, err := netmap.NameIntoDevice(network)
|
||||||
|
|
||||||
|
// we were unable to find the device, maybe it's a custom one...
|
||||||
|
// so, check to see if it's in the .vmx configuration
|
||||||
|
if err != nil || network == "custom" {
|
||||||
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
vmxData, err := readVMXConfig(vmxPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err = readCustomDeviceName(vmxData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse dhcpd configuration
|
||||||
|
pathDhcpConfig := d.DhcpConfPath(device)
|
||||||
|
if _, err := os.Stat(pathDhcpConfig); err != nil {
|
||||||
|
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := ReadDhcpConfig(pathDhcpConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the entry configured in the dhcpd
|
||||||
|
interfaceConfig, err := config.HostByName(device)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally grab the hardware address
|
||||||
|
address, err := interfaceConfig.Hardware()
|
||||||
|
if err == nil {
|
||||||
|
return address.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// we didn't find it, so search through our interfaces for the device name
|
||||||
|
interfaceList, err := net.Interfaces()
|
||||||
|
if err == nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]string, 0)
|
||||||
|
for _, intf := range interfaceList {
|
||||||
|
if strings.HasSuffix(strings.ToLower(intf.Name), device) {
|
||||||
|
return intf.HardwareAddr.String(), nil
|
||||||
|
}
|
||||||
|
names = append(names, intf.Name)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Unable to find device %s : %v", device, names)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
|
||||||
|
|
||||||
|
// grab mapper for converting network<->device
|
||||||
|
netmap, err := d.NetworkMapper()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert network to name
|
||||||
|
network := state.Get("vmnetwork").(string)
|
||||||
|
device, err := netmap.NameIntoDevice(network)
|
||||||
|
|
||||||
|
// we were unable to find the device, maybe it's a custom one...
|
||||||
|
// so, check to see if it's in the .vmx configuration
|
||||||
|
if err != nil || network == "custom" {
|
||||||
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
vmxData, err := readVMXConfig(vmxPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err = readCustomDeviceName(vmxData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse dhcpd configuration
|
||||||
|
pathDhcpConfig := d.DhcpConfPath(device)
|
||||||
|
if _, err := os.Stat(pathDhcpConfig); err != nil {
|
||||||
|
return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
|
||||||
|
}
|
||||||
|
config, err := ReadDhcpConfig(pathDhcpConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the entry configured in the dhcpd
|
||||||
|
interfaceConfig, err := config.HostByName(device)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := interfaceConfig.IP4()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return address.String(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
|
|
||||||
// Fusion5Driver is a driver that can run VMware Fusion 5.
|
// Fusion5Driver is a driver that can run VMware Fusion 5.
|
||||||
type Fusion5Driver struct {
|
type Fusion5Driver struct {
|
||||||
|
VmwareDriver
|
||||||
|
|
||||||
// This is the path to the "VMware Fusion.app"
|
// This is the path to the "VMware Fusion.app"
|
||||||
AppPath string
|
AppPath string
|
||||||
|
|
||||||
|
@ -39,8 +41,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error {
|
func (d *Fusion5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
|
||||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
|
||||||
if _, _, err := runAndLog(cmd); err != nil {
|
if _, _, err := runAndLog(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -139,6 +141,32 @@ func (d *Fusion5Driver) Verify() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion")
|
||||||
|
|
||||||
|
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
|
||||||
|
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
|
||||||
|
}
|
||||||
|
d.VmwareDriver.DhcpConfPath = func(device string) string {
|
||||||
|
return filepath.Join(libpath, device, "dhcpd.conf")
|
||||||
|
}
|
||||||
|
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
|
||||||
|
return filepath.Join(libpath, device, "nat.conf")
|
||||||
|
}
|
||||||
|
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
|
||||||
|
pathNetworking := filepath.Join(libpath, "networking")
|
||||||
|
if _, err := os.Stat(pathNetworking); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(pathNetworking)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
return ReadNetworkingConfig(fd)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,10 +186,6 @@ func (d *Fusion5Driver) ToolsInstall() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Fusion5Driver) DhcpLeasesPath(device string) string {
|
|
||||||
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
|
|
||||||
}
|
|
||||||
|
|
||||||
const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
|
@ -170,3 +194,7 @@ const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`
|
</plist>`
|
||||||
|
|
||||||
|
func (d *Fusion5Driver) GetVmwareDriver() VmwareDriver {
|
||||||
|
return d.VmwareDriver
|
||||||
|
}
|
||||||
|
|
|
@ -66,5 +66,36 @@ func (d *Fusion6Driver) Verify() error {
|
||||||
}
|
}
|
||||||
log.Printf("Detected VMware version: %s", matches[1])
|
log.Printf("Detected VMware version: %s", matches[1])
|
||||||
|
|
||||||
|
libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion")
|
||||||
|
|
||||||
|
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
|
||||||
|
return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases"
|
||||||
|
}
|
||||||
|
d.VmwareDriver.DhcpConfPath = func(device string) string {
|
||||||
|
return filepath.Join(libpath, device, "dhcpd.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
|
||||||
|
return filepath.Join(libpath, device, "nat.conf")
|
||||||
|
}
|
||||||
|
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
|
||||||
|
pathNetworking := filepath.Join(libpath, "networking")
|
||||||
|
if _, err := os.Stat(pathNetworking); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(pathNetworking)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
return ReadNetworkingConfig(fd)
|
||||||
|
}
|
||||||
|
|
||||||
return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional")
|
return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Fusion6Driver) GetVmwareDriver() VmwareDriver {
|
||||||
|
return d.Fusion5Driver.VmwareDriver
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
@ -21,6 +22,7 @@ type DriverMock struct {
|
||||||
CreateDiskCalled bool
|
CreateDiskCalled bool
|
||||||
CreateDiskOutput string
|
CreateDiskOutput string
|
||||||
CreateDiskSize string
|
CreateDiskSize string
|
||||||
|
CreateDiskAdapterType string
|
||||||
CreateDiskTypeId string
|
CreateDiskTypeId string
|
||||||
CreateDiskErr error
|
CreateDiskErr error
|
||||||
|
|
||||||
|
@ -34,6 +36,26 @@ type DriverMock struct {
|
||||||
CommHostResult string
|
CommHostResult string
|
||||||
CommHostErr error
|
CommHostErr error
|
||||||
|
|
||||||
|
HostAddressCalled bool
|
||||||
|
HostAddressState multistep.StateBag
|
||||||
|
HostAddressResult string
|
||||||
|
HostAddressErr error
|
||||||
|
|
||||||
|
HostIPCalled bool
|
||||||
|
HostIPState multistep.StateBag
|
||||||
|
HostIPResult string
|
||||||
|
HostIPErr error
|
||||||
|
|
||||||
|
GuestAddressCalled bool
|
||||||
|
GuestAddressState multistep.StateBag
|
||||||
|
GuestAddressResult string
|
||||||
|
GuestAddressErr error
|
||||||
|
|
||||||
|
GuestIPCalled bool
|
||||||
|
GuestIPState multistep.StateBag
|
||||||
|
GuestIPResult string
|
||||||
|
GuestIPErr error
|
||||||
|
|
||||||
StartCalled bool
|
StartCalled bool
|
||||||
StartPath string
|
StartPath string
|
||||||
StartHeadless bool
|
StartHeadless bool
|
||||||
|
@ -58,10 +80,33 @@ type DriverMock struct {
|
||||||
DhcpLeasesPathDevice string
|
DhcpLeasesPathDevice string
|
||||||
DhcpLeasesPathResult string
|
DhcpLeasesPathResult string
|
||||||
|
|
||||||
|
DhcpConfPathCalled bool
|
||||||
|
DhcpConfPathResult string
|
||||||
|
|
||||||
|
VmnetnatConfPathCalled bool
|
||||||
|
VmnetnatConfPathResult string
|
||||||
|
|
||||||
|
NetmapConfPathCalled bool
|
||||||
|
NetmapConfPathResult string
|
||||||
|
|
||||||
VerifyCalled bool
|
VerifyCalled bool
|
||||||
VerifyErr error
|
VerifyErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NetworkMapperMock struct {
|
||||||
|
NameIntoDeviceCalled int
|
||||||
|
DeviceIntoNameCalled int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) {
|
||||||
|
m.NameIntoDeviceCalled += 1
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) {
|
||||||
|
m.DeviceIntoNameCalled += 1
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) Clone(dst string, src string) error {
|
func (d *DriverMock) Clone(dst string, src string) error {
|
||||||
d.CloneCalled = true
|
d.CloneCalled = true
|
||||||
d.CloneDst = dst
|
d.CloneDst = dst
|
||||||
|
@ -75,10 +120,11 @@ func (d *DriverMock) CompactDisk(path string) error {
|
||||||
return d.CompactDiskErr
|
return d.CompactDiskErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) CreateDisk(output string, size string, typeId string) error {
|
func (d *DriverMock) CreateDisk(output string, size string, adapterType string, typeId string) error {
|
||||||
d.CreateDiskCalled = true
|
d.CreateDiskCalled = true
|
||||||
d.CreateDiskOutput = output
|
d.CreateDiskOutput = output
|
||||||
d.CreateDiskSize = size
|
d.CreateDiskSize = size
|
||||||
|
d.CreateDiskAdapterType = adapterType
|
||||||
d.CreateDiskTypeId = typeId
|
d.CreateDiskTypeId = typeId
|
||||||
return d.CreateDiskErr
|
return d.CreateDiskErr
|
||||||
}
|
}
|
||||||
|
@ -98,6 +144,58 @@ func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) {
|
||||||
return d.CommHostResult, d.CommHostErr
|
return d.CommHostResult, d.CommHostErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MockInterface() net.Interface {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
|
||||||
|
// Build a dummy interface due to being unable to enumerate interfaces
|
||||||
|
if err != nil || len(interfaces) == 0 {
|
||||||
|
return net.Interface{
|
||||||
|
Index: 0,
|
||||||
|
MTU: -1,
|
||||||
|
Name: "dummy",
|
||||||
|
HardwareAddr: net.HardwareAddr{0, 0, 0, 0, 0, 0},
|
||||||
|
Flags: net.FlagLoopback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first loopback interface
|
||||||
|
for _, intf := range interfaces {
|
||||||
|
if intf.Flags&net.FlagLoopback == net.FlagLoopback {
|
||||||
|
return intf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-back to just the first one
|
||||||
|
return interfaces[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) HostAddress(state multistep.StateBag) (string, error) {
|
||||||
|
intf := MockInterface()
|
||||||
|
d.HostAddressResult = intf.HardwareAddr.String()
|
||||||
|
d.HostAddressCalled = true
|
||||||
|
d.HostAddressState = state
|
||||||
|
return d.HostAddressResult, d.HostAddressErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) HostIP(state multistep.StateBag) (string, error) {
|
||||||
|
d.HostIPResult = "127.0.0.1"
|
||||||
|
d.HostIPCalled = true
|
||||||
|
d.HostIPState = state
|
||||||
|
return d.HostIPResult, d.HostIPErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) GuestAddress(state multistep.StateBag) (string, error) {
|
||||||
|
d.GuestAddressCalled = true
|
||||||
|
d.GuestAddressState = state
|
||||||
|
return d.GuestAddressResult, d.GuestAddressErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) GuestIP(state multistep.StateBag) (string, error) {
|
||||||
|
d.GuestIPCalled = true
|
||||||
|
d.GuestIPState = state
|
||||||
|
return d.GuestIPResult, d.GuestIPErr
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) Start(path string, headless bool) error {
|
func (d *DriverMock) Start(path string, headless bool) error {
|
||||||
d.StartCalled = true
|
d.StartCalled = true
|
||||||
d.StartPath = path
|
d.StartPath = path
|
||||||
|
@ -134,7 +232,39 @@ func (d *DriverMock) DhcpLeasesPath(device string) string {
|
||||||
return d.DhcpLeasesPathResult
|
return d.DhcpLeasesPathResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) DhcpConfPath(device string) string {
|
||||||
|
d.DhcpConfPathCalled = true
|
||||||
|
return d.DhcpConfPathResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) VmnetnatConfPath(device string) string {
|
||||||
|
d.VmnetnatConfPathCalled = true
|
||||||
|
return d.VmnetnatConfPathResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) NetmapConfPath() string {
|
||||||
|
d.NetmapConfPathCalled = true
|
||||||
|
return d.NetmapConfPathResult
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DriverMock) Verify() error {
|
func (d *DriverMock) Verify() error {
|
||||||
d.VerifyCalled = true
|
d.VerifyCalled = true
|
||||||
return d.VerifyErr
|
return d.VerifyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DriverMock) GetVmwareDriver() VmwareDriver {
|
||||||
|
var state VmwareDriver
|
||||||
|
state.DhcpLeasesPath = func(string) string {
|
||||||
|
return "/path/to/dhcp.leases"
|
||||||
|
}
|
||||||
|
state.DhcpConfPath = func(string) string {
|
||||||
|
return "/path/to/dhcp.conf"
|
||||||
|
}
|
||||||
|
state.VmnetnatConfPath = func(string) string {
|
||||||
|
return "/path/to/vmnetnat.conf"
|
||||||
|
}
|
||||||
|
state.NetworkMapper = func() (NetworkNameMapper, error) {
|
||||||
|
return NetworkMapperMock{}, nil
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,6 +14,8 @@ import (
|
||||||
|
|
||||||
// Player5Driver is a driver that can run VMware Player 5 on Linux.
|
// Player5Driver is a driver that can run VMware Player 5 on Linux.
|
||||||
type Player5Driver struct {
|
type Player5Driver struct {
|
||||||
|
VmwareDriver
|
||||||
|
|
||||||
AppPath string
|
AppPath string
|
||||||
VdiskManagerPath string
|
VdiskManagerPath string
|
||||||
QemuImgPath string
|
QemuImgPath string
|
||||||
|
@ -62,12 +64,12 @@ func (d *Player5Driver) qemuCompactDisk(diskPath string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Player5Driver) CreateDisk(output string, size string, type_id string) error {
|
func (d *Player5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if d.QemuImgPath != "" {
|
if d.QemuImgPath != "" {
|
||||||
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
|
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
|
||||||
}
|
}
|
||||||
if _, _, err := runAndLog(cmd); err != nil {
|
if _, _, err := runAndLog(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -181,6 +183,33 @@ func (d *Player5Driver) Verify() error {
|
||||||
"One of these is required to configure disks for VMware Player.")
|
"One of these is required to configure disks for VMware Player.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assigning the path callbacks to VmwareDriver
|
||||||
|
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
|
||||||
|
return playerDhcpLeasesPath(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.DhcpConfPath = func(device string) string {
|
||||||
|
return playerVmDhcpConfPath(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
|
||||||
|
return playerVmnetnatConfPath(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
|
||||||
|
pathNetmap := playerNetmapConfPath()
|
||||||
|
if _, err := os.Stat(pathNetmap); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(pathNetmap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
return ReadNetworkMap(fd)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,10 +221,6 @@ func (d *Player5Driver) ToolsInstall() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Player5Driver) DhcpLeasesPath(device string) string {
|
func (d *Player5Driver) GetVmwareDriver() VmwareDriver {
|
||||||
return playerDhcpLeasesPath(device)
|
return d.VmwareDriver
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Player5Driver) VmnetnatConfPath() string {
|
|
||||||
return playerVmnetnatConfPath()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,14 +57,29 @@ func playerDhcpLeasesPath(device string) string {
|
||||||
} else if _, err := os.Stat(path); err == nil {
|
} else if _, err := os.Stat(path); err == nil {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
return findFile("vmnetdhcp.leases", playerDataFilePaths())
|
return findFile("vmnetdhcp.leases", playerDataFilePaths())
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerVmnetnatConfPath() string {
|
func playerVmDhcpConfPath(device string) string {
|
||||||
|
// the device isn't actually used on windows hosts
|
||||||
|
path, err := playerDhcpConfigPathRegistry()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding configuration in registry: %s", err)
|
||||||
|
} else if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return findFile("vmnetdhcp.conf", playerDataFilePaths())
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerVmnetnatConfPath(device string) string {
|
||||||
|
// the device isn't actually used on windows hosts
|
||||||
return findFile("vmnetnat.conf", playerDataFilePaths())
|
return findFile("vmnetnat.conf", playerDataFilePaths())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func playerNetmapConfPath() string {
|
||||||
|
return findFile("netmap.conf", playerDataFilePaths())
|
||||||
|
}
|
||||||
|
|
||||||
// This reads the VMware installation path from the Windows registry.
|
// This reads the VMware installation path from the Windows registry.
|
||||||
func playerVMwareRoot() (s string, err error) {
|
func playerVMwareRoot() (s string, err error) {
|
||||||
key := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmplayer.exe`
|
key := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmplayer.exe`
|
||||||
|
@ -87,7 +102,18 @@ func playerDhcpLeasesPathRegistry() (s string, err error) {
|
||||||
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
|
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return normalizePath(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This reads the VMware DHCP configuration path from the Windows registry.
|
||||||
|
func playerDhcpConfigPathRegistry() (s string, err error) {
|
||||||
|
key := "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters"
|
||||||
|
subkey := "ConfFile"
|
||||||
|
s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(`Unable to read registry key %s\%s`, key, subkey)
|
||||||
|
return
|
||||||
|
}
|
||||||
return normalizePath(s), nil
|
return normalizePath(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,3 +35,7 @@ func (d *Player6Driver) Verify() error {
|
||||||
|
|
||||||
return playerVerifyVersion(VMWARE_PLAYER_VERSION)
|
return playerVerifyVersion(VMWARE_PLAYER_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Player6Driver) GetVmwareDriver() VmwareDriver {
|
||||||
|
return d.Player5Driver.VmwareDriver
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
@ -28,16 +29,49 @@ func playerFindVmrun() (string, error) {
|
||||||
return exec.LookPath("vmrun")
|
return exec.LookPath("vmrun")
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerDhcpLeasesPath(device string) string {
|
|
||||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
|
||||||
}
|
|
||||||
|
|
||||||
func playerToolsIsoPath(flavor string) string {
|
func playerToolsIsoPath(flavor string) string {
|
||||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerVmnetnatConfPath() string {
|
// return the base path to vmware's config on the host
|
||||||
|
func playerVMwareRoot() (s string, err error) {
|
||||||
|
return "/etc/vmware", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerDhcpLeasesPath(device string) string {
|
||||||
|
base, err := playerVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
return ""
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, device, "dhcpd/dhcpd.leases")
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerVmDhcpConfPath(device string) string {
|
||||||
|
base, err := playerVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, device, "dhcp/dhcp.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerVmnetnatConfPath(device string) string {
|
||||||
|
base, err := playerVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, device, "nat/nat.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerNetmapConfPath() string {
|
||||||
|
base, err := playerVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, "netmap.conf")
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerVerifyVersion(version string) error {
|
func playerVerifyVersion(version string) error {
|
||||||
|
|
|
@ -33,3 +33,7 @@ func (d *Workstation10Driver) Verify() error {
|
||||||
|
|
||||||
return workstationVerifyVersion(VMWARE_WS_VERSION)
|
return workstationVerifyVersion(VMWARE_WS_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Workstation10Driver) GetVmwareDriver() VmwareDriver {
|
||||||
|
return d.Workstation9Driver.VmwareDriver
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
|
|
||||||
// Workstation9Driver is a driver that can run VMware Workstation 9
|
// Workstation9Driver is a driver that can run VMware Workstation 9
|
||||||
type Workstation9Driver struct {
|
type Workstation9Driver struct {
|
||||||
|
VmwareDriver
|
||||||
|
|
||||||
AppPath string
|
AppPath string
|
||||||
VdiskManagerPath string
|
VdiskManagerPath string
|
||||||
VmrunPath string
|
VmrunPath string
|
||||||
|
@ -40,8 +42,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error {
|
func (d *Workstation9Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
|
||||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
|
||||||
if _, _, err := runAndLog(cmd); err != nil {
|
if _, _, err := runAndLog(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -142,6 +144,33 @@ func (d *Workstation9Driver) Verify() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assigning the path callbacks to VmwareDriver
|
||||||
|
d.VmwareDriver.DhcpLeasesPath = func(device string) string {
|
||||||
|
return workstationDhcpLeasesPath(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.DhcpConfPath = func(device string) string {
|
||||||
|
return workstationDhcpConfPath(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.VmnetnatConfPath = func(device string) string {
|
||||||
|
return workstationVmnetnatConfPath(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) {
|
||||||
|
pathNetmap := workstationNetmapConfPath()
|
||||||
|
if _, err := os.Stat(pathNetmap); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find netmap conf file: %s", pathNetmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(pathNetmap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
return ReadNetworkMap(fd)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,10 +182,6 @@ func (d *Workstation9Driver) ToolsInstall() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Workstation9Driver) DhcpLeasesPath(device string) string {
|
func (d *Workstation9Driver) GetVmwareDriver() VmwareDriver {
|
||||||
return workstationDhcpLeasesPath(device)
|
return d.VmwareDriver
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Workstation9Driver) VmnetnatConfPath() string {
|
|
||||||
return workstationVmnetnatConfPath()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,10 +59,20 @@ func workstationDhcpLeasesPath(device string) string {
|
||||||
return findFile("vmnetdhcp.leases", workstationDataFilePaths())
|
return findFile("vmnetdhcp.leases", workstationDataFilePaths())
|
||||||
}
|
}
|
||||||
|
|
||||||
func workstationVmnetnatConfPath() string {
|
func workstationDhcpConfPath(device string) string {
|
||||||
|
// device isn't used on a windows host
|
||||||
|
return findFile("vmnetdhcp.conf", workstationDataFilePaths())
|
||||||
|
}
|
||||||
|
|
||||||
|
func workstationVmnetnatConfPath(device string) string {
|
||||||
|
// device isn't used on a windows host
|
||||||
return findFile("vmnetnat.conf", workstationDataFilePaths())
|
return findFile("vmnetnat.conf", workstationDataFilePaths())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func workstationNetmapConfPath() string {
|
||||||
|
return findFile("netmap.conf", workstationDataFilePaths())
|
||||||
|
}
|
||||||
|
|
||||||
// See http://blog.natefinch.com/2012/11/go-win-stuff.html
|
// See http://blog.natefinch.com/2012/11/go-win-stuff.html
|
||||||
//
|
//
|
||||||
// This is used by workstationVMwareRoot in order to read some registry data.
|
// This is used by workstationVMwareRoot in order to read some registry data.
|
||||||
|
|
|
@ -39,18 +39,51 @@ func workstationFindVmrun() (string, error) {
|
||||||
return exec.LookPath("vmrun")
|
return exec.LookPath("vmrun")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return the base path to vmware's config on the host
|
||||||
|
func workstationVMwareRoot() (s string, err error) {
|
||||||
|
return "/etc/vmware", nil
|
||||||
|
}
|
||||||
|
|
||||||
func workstationDhcpLeasesPath(device string) string {
|
func workstationDhcpLeasesPath(device string) string {
|
||||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
base, err := workstationVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, device, "dhcpd/dhcpd.leases")
|
||||||
|
}
|
||||||
|
|
||||||
|
func workstationDhcpConfPath(device string) string {
|
||||||
|
base, err := workstationVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, device, "dhcp/dhcpd.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func workstationVmnetnatConfPath(device string) string {
|
||||||
|
base, err := workstationVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, device, "nat/nat.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
func workstationNetmapConfPath() string {
|
||||||
|
base, err := workstationVMwareRoot()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error finding VMware root: %s", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return filepath.Join(base, "netmap.conf")
|
||||||
}
|
}
|
||||||
|
|
||||||
func workstationToolsIsoPath(flavor string) string {
|
func workstationToolsIsoPath(flavor string) string {
|
||||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||||
}
|
}
|
||||||
|
|
||||||
func workstationVmnetnatConfPath() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func workstationVerifyVersion(version string) error {
|
func workstationVerifyVersion(version string) error {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
return fmt.Errorf("The VMware WS version %s driver is only supported on Linux, and Windows, at the moment. Your OS: %s", version, runtime.GOOS)
|
return fmt.Errorf("The VMware WS version %s driver is only supported on Linux, and Windows, at the moment. Your OS: %s", version, runtime.GOOS)
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Interface to help find the IP address of a running virtual machine.
|
|
||||||
type GuestIPFinder interface {
|
|
||||||
GuestIP() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DHCPLeaseGuestLookup looks up the IP address of a guest using DHCP
|
|
||||||
// lease information from the VMware network devices.
|
|
||||||
type DHCPLeaseGuestLookup struct {
|
|
||||||
// Driver that is being used (to find leases path)
|
|
||||||
Driver Driver
|
|
||||||
|
|
||||||
// Device that the guest is connected to.
|
|
||||||
Device string
|
|
||||||
|
|
||||||
// MAC address of the guest.
|
|
||||||
MACAddress string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) {
|
|
||||||
dhcpLeasesPath := f.Driver.DhcpLeasesPath(f.Device)
|
|
||||||
log.Printf("DHCP leases path: %s", dhcpLeasesPath)
|
|
||||||
if dhcpLeasesPath == "" {
|
|
||||||
return "", errors.New("no DHCP leases path found.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fh, err := os.Open(dhcpLeasesPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer fh.Close()
|
|
||||||
|
|
||||||
dhcpBytes, err := ioutil.ReadAll(fh)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastIp string
|
|
||||||
var lastLeaseEnd time.Time
|
|
||||||
|
|
||||||
var curIp string
|
|
||||||
var curLeaseEnd time.Time
|
|
||||||
|
|
||||||
ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
|
|
||||||
endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
|
|
||||||
macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
|
|
||||||
|
|
||||||
for _, line := range strings.Split(string(dhcpBytes), "\n") {
|
|
||||||
// Need to trim off CR character when running in windows
|
|
||||||
line = strings.TrimRight(line, "\r")
|
|
||||||
|
|
||||||
matches := ipLineRe.FindStringSubmatch(line)
|
|
||||||
if matches != nil {
|
|
||||||
lastIp = matches[1]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matches = endTimeLineRe.FindStringSubmatch(line)
|
|
||||||
if matches != nil {
|
|
||||||
lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the mac address matches and this lease ends farther in the
|
|
||||||
// future than the last match we might have, then choose it.
|
|
||||||
matches = macLineRe.FindStringSubmatch(line)
|
|
||||||
if matches != nil && strings.EqualFold(matches[1], f.MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
|
|
||||||
curIp = lastIp
|
|
||||||
curLeaseEnd = lastLeaseEnd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if curIp == "" {
|
|
||||||
return "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", f.MACAddress, dhcpLeasesPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return curIp, nil
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDHCPLeaseGuestLookup_impl(t *testing.T) {
|
|
||||||
var _ GuestIPFinder = new(DHCPLeaseGuestLookup)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDHCPLeaseGuestLookup(t *testing.T) {
|
|
||||||
tf, err := ioutil.TempFile("", "packer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if _, err := tf.Write([]byte(testLeaseContents)); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
tf.Close()
|
|
||||||
defer os.Remove(tf.Name())
|
|
||||||
|
|
||||||
driver := new(DriverMock)
|
|
||||||
driver.DhcpLeasesPathResult = tf.Name()
|
|
||||||
|
|
||||||
finder := &DHCPLeaseGuestLookup{
|
|
||||||
Driver: driver,
|
|
||||||
Device: "vmnet8",
|
|
||||||
MACAddress: "00:0c:29:59:91:02",
|
|
||||||
}
|
|
||||||
|
|
||||||
ip, err := finder.GuestIP()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !driver.DhcpLeasesPathCalled {
|
|
||||||
t.Fatal("should ask for DHCP leases path")
|
|
||||||
}
|
|
||||||
if driver.DhcpLeasesPathDevice != "vmnet8" {
|
|
||||||
t.Fatal("should be vmnet8")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip != "192.168.126.130" {
|
|
||||||
t.Fatalf("bad: %#v", ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const testLeaseContents = `
|
|
||||||
# All times in this file are in UTC (GMT), not your local timezone. This is
|
|
||||||
# not a bug, so please don't ask about it. There is no portable way to
|
|
||||||
# store leases in the local timezone, so please don't request this as a
|
|
||||||
# feature. If this is inconvenient or confusing to you, we sincerely
|
|
||||||
# apologize. Seriously, though - don't ask.
|
|
||||||
# The format of this file is documented in the dhcpd.leases(5) manual page.
|
|
||||||
|
|
||||||
lease 192.168.126.129 {
|
|
||||||
starts 0 2013/09/15 23:58:51;
|
|
||||||
ends 1 2013/09/16 00:28:51;
|
|
||||||
hardware ethernet 00:0c:29:59:91:02;
|
|
||||||
client-hostname "precise64";
|
|
||||||
}
|
|
||||||
lease 192.168.126.130 {
|
|
||||||
starts 2 2013/09/17 21:39:07;
|
|
||||||
ends 2 2013/09/17 22:09:07;
|
|
||||||
hardware ethernet 00:0c:29:59:91:02;
|
|
||||||
client-hostname "precise64";
|
|
||||||
}
|
|
||||||
lease 192.168.126.128 {
|
|
||||||
starts 0 2013/09/15 20:09:59;
|
|
||||||
ends 0 2013/09/15 20:21:58;
|
|
||||||
hardware ethernet 00:0c:29:59:91:02;
|
|
||||||
client-hostname "precise64";
|
|
||||||
}
|
|
||||||
lease 192.168.126.127 {
|
|
||||||
starts 0 2013/09/15 20:09:59;
|
|
||||||
ends 0 2013/09/15 20:21:58;
|
|
||||||
hardware ethernet 01:0c:29:59:91:02;
|
|
||||||
client-hostname "precise64";
|
|
||||||
|
|
||||||
`
|
|
|
@ -1,7 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
// Interface to help find the host IP that is available from within
|
|
||||||
// the VMware virtual machines.
|
|
||||||
type HostIPFinder interface {
|
|
||||||
HostIP() (string, error)
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestIfconfigIPFinder_Impl(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &IfconfigIPFinder{}
|
|
||||||
if _, ok := raw.(HostIPFinder); !ok {
|
|
||||||
t.Fatalf("IfconfigIPFinder is not a host IP finder")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VMnetNatConfIPFinder finds the IP address of the host machine by
|
|
||||||
// retrieving the IP from the vmnetnat.conf. This isn't a full proof
|
|
||||||
// technique but so far it has not failed.
|
|
||||||
type VMnetNatConfIPFinder struct{}
|
|
||||||
|
|
||||||
func (*VMnetNatConfIPFinder) HostIP() (string, error) {
|
|
||||||
driver := &Workstation9Driver{}
|
|
||||||
|
|
||||||
vmnetnat := driver.VmnetnatConfPath()
|
|
||||||
if vmnetnat == "" {
|
|
||||||
return "", errors.New("Could not find NAT vmnet conf file")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(vmnetnat); err != nil {
|
|
||||||
return "", fmt.Errorf("Could not find NAT vmnet conf file: %s", vmnetnat)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(vmnetnat)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
ipRe := regexp.MustCompile(`^\s*ip\s*=\s*(.+?)\s*$`)
|
|
||||||
|
|
||||||
r := bufio.NewReader(f)
|
|
||||||
for {
|
|
||||||
line, err := r.ReadString('\n')
|
|
||||||
if line != "" {
|
|
||||||
matches := ipRe.FindStringSubmatch(line)
|
|
||||||
if matches != nil {
|
|
||||||
ip := matches[1]
|
|
||||||
dotIndex := strings.LastIndex(ip, ".")
|
|
||||||
if dotIndex == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = ip[0:dotIndex] + ".1"
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("host IP not found in " + vmnetnat)
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package common
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestVMnetNatConfIPFinder_Impl(t *testing.T) {
|
|
||||||
var raw interface{}
|
|
||||||
raw = &VMnetNatConfIPFinder{}
|
|
||||||
if _, ok := raw.(HostIPFinder); !ok {
|
|
||||||
t.Fatalf("VMnetNatConfIPFinder is not a host IP finder")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,7 @@ package common
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
|
|
||||||
commonssh "github.com/hashicorp/packer/common/ssh"
|
commonssh "github.com/hashicorp/packer/common/ssh"
|
||||||
"github.com/hashicorp/packer/communicator/ssh"
|
"github.com/hashicorp/packer/communicator/ssh"
|
||||||
|
@ -16,41 +14,12 @@ import (
|
||||||
func CommHost(config *SSHConfig) func(multistep.StateBag) (string, error) {
|
func CommHost(config *SSHConfig) func(multistep.StateBag) (string, error) {
|
||||||
return func(state multistep.StateBag) (string, error) {
|
return func(state multistep.StateBag) (string, error) {
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
|
||||||
|
|
||||||
if config.Comm.SSHHost != "" {
|
if config.Comm.SSHHost != "" {
|
||||||
return config.Comm.SSHHost, nil
|
return config.Comm.SSHHost, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Lookup up IP information...")
|
ipAddress, err := driver.GuestIP(state)
|
||||||
f, err := os.Open(vmxPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
vmxBytes, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
vmxData := ParseVMX(string(vmxBytes))
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
macAddress := ""
|
|
||||||
if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
|
|
||||||
if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
|
|
||||||
return "", errors.New("couldn't find MAC address in VMX")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ipLookup := &DHCPLeaseGuestLookup{
|
|
||||||
Driver: driver,
|
|
||||||
Device: "vmnet8",
|
|
||||||
MACAddress: macAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
ipAddress, err := ipLookup.GuestIP()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("IP lookup failed: %s", err)
|
log.Printf("IP lookup failed: %s", err)
|
||||||
return "", fmt.Errorf("IP lookup failed: %s", err)
|
return "", fmt.Errorf("IP lookup failed: %s", err)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package common
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -26,7 +25,7 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
|
||||||
vmxContents, err := ioutil.ReadFile(vmxPath)
|
vmxData, err := ReadVMX(vmxPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error reading VMX file: %s", err)
|
err := fmt.Errorf("Error reading VMX file: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -34,8 +33,6 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
vmxData := ParseVMX(string(vmxContents))
|
|
||||||
|
|
||||||
// Set this so that no dialogs ever appear from Packer.
|
// Set this so that no dialogs ever appear from Packer.
|
||||||
vmxData["msg.autoanswer"] = "true"
|
vmxData["msg.autoanswer"] = "true"
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,9 @@ package common
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
|
@ -87,17 +85,9 @@ func (s *StepConfigureVNC) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmxPath := state.Get("vmx_path").(string)
|
vmxPath := state.Get("vmx_path").(string)
|
||||||
|
|
||||||
f, err := os.Open(vmxPath)
|
vmxData, err := ReadVMX(vmxPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error reading VMX data: %s", err)
|
err := fmt.Errorf("Error reading VMX file: %s", err)
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
vmxBytes, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("Error reading VMX data: %s", err)
|
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
|
@ -121,7 +111,6 @@ func (s *StepConfigureVNC) Run(_ context.Context, state multistep.StateBag) mult
|
||||||
|
|
||||||
log.Printf("Found available VNC port: %d", vncPort)
|
log.Printf("Found available VNC port: %d", vncPort)
|
||||||
|
|
||||||
vmxData := ParseVMX(string(vmxBytes))
|
|
||||||
vncFinder.UpdateVMX(vncBindAddress, vncPassword, vncPort, vmxData)
|
vncFinder.UpdateVMX(vncBindAddress, vncPassword, vncPort, vmxData)
|
||||||
|
|
||||||
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
||||||
|
|
|
@ -116,6 +116,10 @@ func TestStepShutdown_noCommand(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepShutdown_locks(t *testing.T) {
|
func TestStepShutdown_locks(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 VMware binaries.")
|
||||||
|
}
|
||||||
|
|
||||||
state := testStepShutdownState(t)
|
state := testStepShutdownState(t)
|
||||||
step := new(StepShutdown)
|
step := new(StepShutdown)
|
||||||
step.Testing = true
|
step.Testing = true
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
@ -63,7 +63,7 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to VNC
|
// Connect to VNC
|
||||||
ui.Say("Connecting to VM via VNC")
|
ui.Say(fmt.Sprintf("Connecting to VM via VNC (%s:%d)", vncIp, vncPort))
|
||||||
nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIp, vncPort))
|
nc, err := net.Dial("tcp", fmt.Sprintf("%s:%d", vncIp, vncPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error connecting to VNC: %s", err)
|
err := fmt.Errorf("Error connecting to VNC: %s", err)
|
||||||
|
@ -93,16 +93,7 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
||||||
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
|
log.Printf("Connected to VNC desktop: %s", c.DesktopName)
|
||||||
|
|
||||||
// Determine the host IP
|
// Determine the host IP
|
||||||
var ipFinder HostIPFinder
|
hostIP, err := driver.HostIP(state)
|
||||||
if finder, ok := driver.(HostIPFinder); ok {
|
|
||||||
ipFinder = finder
|
|
||||||
} else if runtime.GOOS == "windows" {
|
|
||||||
ipFinder = new(VMnetNatConfIPFinder)
|
|
||||||
} else {
|
|
||||||
ipFinder = &IfconfigIPFinder{Device: "vmnet8"}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostIP, err := ipFinder.HostIP()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error detecting host IP: %s", err)
|
err := fmt.Errorf("Error detecting host IP: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
|
@ -184,6 +175,8 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
special["<rightAlt>"] = 0xFFEA
|
special["<rightAlt>"] = 0xFFEA
|
||||||
special["<rightCtrl>"] = 0xFFE4
|
special["<rightCtrl>"] = 0xFFE4
|
||||||
special["<rightShift>"] = 0xFFE2
|
special["<rightShift>"] = 0xFFE2
|
||||||
|
special["<leftSuper>"] = 0xFFEB
|
||||||
|
special["<rightSuper>"] = 0xFFEC
|
||||||
|
|
||||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||||
|
|
||||||
|
@ -194,6 +187,9 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
keyInterval = delay
|
keyInterval = delay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
azOnRegex := regexp.MustCompile("^<(?P<ordinary>[a-zA-Z])On>")
|
||||||
|
azOffRegex := regexp.MustCompile("^<(?P<ordinary>[a-zA-Z])Off>")
|
||||||
|
|
||||||
// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
|
// TODO(mitchellh): Ripe for optimizations of some point, perhaps.
|
||||||
for len(original) > 0 {
|
for len(original) > 0 {
|
||||||
var keyCode uint32
|
var keyCode uint32
|
||||||
|
@ -232,6 +228,36 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(original, "<leftSuperOn>") {
|
||||||
|
keyCode = special["<leftSuper>"]
|
||||||
|
original = original[len("<leftSuperOn>"):]
|
||||||
|
log.Printf("Special code '<leftSuperOn>' found, replacing with: %d", keyCode)
|
||||||
|
|
||||||
|
c.KeyEvent(keyCode, true)
|
||||||
|
time.Sleep(keyInterval)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if azOnRegex.MatchString(original) {
|
||||||
|
m := azOnRegex.FindStringSubmatch(original)
|
||||||
|
r, _ := utf8.DecodeRuneInString(m[1])
|
||||||
|
original = original[len("<aOn>"):]
|
||||||
|
keyCode = uint32(r)
|
||||||
|
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||||
|
|
||||||
|
log.Printf("Special code '%s' found, replacing with %d, shift %v", m[0], keyCode, keyShift)
|
||||||
|
|
||||||
|
if keyShift {
|
||||||
|
c.KeyEvent(KeyLeftShift, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.KeyEvent(keyCode, true)
|
||||||
|
time.Sleep(keyInterval)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(original, "<leftAltOff>") {
|
if strings.HasPrefix(original, "<leftAltOff>") {
|
||||||
keyCode = special["<leftAlt>"]
|
keyCode = special["<leftAlt>"]
|
||||||
original = original[len("<leftAltOff>"):]
|
original = original[len("<leftAltOff>"):]
|
||||||
|
@ -265,6 +291,36 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(original, "<leftSuperOff>") {
|
||||||
|
keyCode = special["<leftSuper>"]
|
||||||
|
original = original[len("<leftSuperOff>"):]
|
||||||
|
log.Printf("Special code '<leftSuperOff>' found, replacing with: %d", keyCode)
|
||||||
|
|
||||||
|
c.KeyEvent(keyCode, false)
|
||||||
|
time.Sleep(keyInterval)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if azOffRegex.MatchString(original) {
|
||||||
|
m := azOffRegex.FindStringSubmatch(original)
|
||||||
|
r, _ := utf8.DecodeRuneInString(m[1])
|
||||||
|
original = original[len("<aOff>"):]
|
||||||
|
keyCode = uint32(r)
|
||||||
|
keyShift = unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r)
|
||||||
|
|
||||||
|
log.Printf("Special code '%s' found, replacing with %d, shift %v", m[0], keyCode, keyShift)
|
||||||
|
|
||||||
|
if keyShift {
|
||||||
|
c.KeyEvent(KeyLeftShift, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.KeyEvent(keyCode, false)
|
||||||
|
time.Sleep(keyInterval)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(original, "<rightAltOn>") {
|
if strings.HasPrefix(original, "<rightAltOn>") {
|
||||||
keyCode = special["<rightAlt>"]
|
keyCode = special["<rightAlt>"]
|
||||||
original = original[len("<rightAltOn>"):]
|
original = original[len("<rightAltOn>"):]
|
||||||
|
@ -298,6 +354,17 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(original, "<rightSuperOn>") {
|
||||||
|
keyCode = special["<rightSuper>"]
|
||||||
|
original = original[len("<rightSuperOn>"):]
|
||||||
|
log.Printf("Special code '<rightSuperOn>' found, replacing with: %d", keyCode)
|
||||||
|
|
||||||
|
c.KeyEvent(keyCode, true)
|
||||||
|
time.Sleep(keyInterval)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(original, "<rightAltOff>") {
|
if strings.HasPrefix(original, "<rightAltOff>") {
|
||||||
keyCode = special["<rightAlt>"]
|
keyCode = special["<rightAlt>"]
|
||||||
original = original[len("<rightAltOff>"):]
|
original = original[len("<rightAltOff>"):]
|
||||||
|
@ -331,6 +398,17 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(original, "<rightSuperOff>") {
|
||||||
|
keyCode = special["<rightSuper>"]
|
||||||
|
original = original[len("<rightSuperOff>"):]
|
||||||
|
log.Printf("Special code '<rightSuperOff>' found, replacing with: %d", keyCode)
|
||||||
|
|
||||||
|
c.KeyEvent(keyCode, false)
|
||||||
|
time.Sleep(keyInterval)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(original, "<wait>") {
|
if strings.HasPrefix(original, "<wait>") {
|
||||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
|
@ -38,21 +38,43 @@ type Config struct {
|
||||||
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
||||||
vmwcommon.VMXConfig `mapstructure:",squash"`
|
vmwcommon.VMXConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
|
// disk drives
|
||||||
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
|
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
|
||||||
|
DiskAdapterType string `mapstructure:"disk_adapter_type"`
|
||||||
DiskName string `mapstructure:"vmdk_name"`
|
DiskName string `mapstructure:"vmdk_name"`
|
||||||
DiskSize uint `mapstructure:"disk_size"`
|
DiskSize uint `mapstructure:"disk_size"`
|
||||||
DiskTypeId string `mapstructure:"disk_type_id"`
|
DiskTypeId string `mapstructure:"disk_type_id"`
|
||||||
Format string `mapstructure:"format"`
|
Format string `mapstructure:"format"`
|
||||||
|
|
||||||
|
// cdrom drive
|
||||||
|
CdromAdapterType string `mapstructure:"cdrom_adapter_type"`
|
||||||
|
|
||||||
|
// platform information
|
||||||
GuestOSType string `mapstructure:"guest_os_type"`
|
GuestOSType string `mapstructure:"guest_os_type"`
|
||||||
|
Version string `mapstructure:"version"`
|
||||||
|
VMName string `mapstructure:"vm_name"`
|
||||||
|
|
||||||
|
// Network adapter and type
|
||||||
|
NetworkAdapterType string `mapstructure:"network_adapter_type"`
|
||||||
|
Network string `mapstructure:"network"`
|
||||||
|
|
||||||
|
// device presence
|
||||||
|
Sound bool `mapstructure:"sound"`
|
||||||
|
USB bool `mapstructure:"usb"`
|
||||||
|
|
||||||
|
// communication ports
|
||||||
|
Serial string `mapstructure:"serial"`
|
||||||
|
Parallel string `mapstructure:"parallel"`
|
||||||
|
|
||||||
|
// booting a guest
|
||||||
KeepRegistered bool `mapstructure:"keep_registered"`
|
KeepRegistered bool `mapstructure:"keep_registered"`
|
||||||
OVFToolOptions []string `mapstructure:"ovftool_options"`
|
OVFToolOptions []string `mapstructure:"ovftool_options"`
|
||||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||||
SkipExport bool `mapstructure:"skip_export"`
|
SkipExport bool `mapstructure:"skip_export"`
|
||||||
VMName string `mapstructure:"vm_name"`
|
|
||||||
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
|
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
|
||||||
VMXTemplatePath string `mapstructure:"vmx_template_path"`
|
VMXTemplatePath string `mapstructure:"vmx_template_path"`
|
||||||
Version string `mapstructure:"version"`
|
|
||||||
|
|
||||||
|
// remote vsphere
|
||||||
RemoteType string `mapstructure:"remote_type"`
|
RemoteType string `mapstructure:"remote_type"`
|
||||||
RemoteDatastore string `mapstructure:"remote_datastore"`
|
RemoteDatastore string `mapstructure:"remote_datastore"`
|
||||||
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
|
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
|
||||||
|
@ -109,6 +131,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
b.config.DiskSize = 40000
|
b.config.DiskSize = 40000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.config.DiskAdapterType == "" {
|
||||||
|
// Default is lsilogic
|
||||||
|
b.config.DiskAdapterType = "lsilogic"
|
||||||
|
}
|
||||||
|
|
||||||
if b.config.DiskTypeId == "" {
|
if b.config.DiskTypeId == "" {
|
||||||
// Default is growable virtual disk split in 2GB files.
|
// Default is growable virtual disk split in 2GB files.
|
||||||
b.config.DiskTypeId = "1"
|
b.config.DiskTypeId = "1"
|
||||||
|
@ -158,6 +185,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.config.Network == "" {
|
||||||
|
b.config.Network = "nat"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.config.Sound {
|
||||||
|
b.config.Sound = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.config.USB {
|
||||||
|
b.config.USB = false
|
||||||
|
}
|
||||||
|
|
||||||
// Remote configuration validation
|
// Remote configuration validation
|
||||||
if b.config.RemoteType != "" {
|
if b.config.RemoteType != "" {
|
||||||
if b.config.RemoteHost == "" {
|
if b.config.RemoteHost == "" {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
||||||
commonssh "github.com/hashicorp/packer/common/ssh"
|
commonssh "github.com/hashicorp/packer/common/ssh"
|
||||||
"github.com/hashicorp/packer/communicator/ssh"
|
"github.com/hashicorp/packer/communicator/ssh"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
@ -24,6 +25,8 @@ import (
|
||||||
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
|
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
|
||||||
// virtual machines. This driver can only manage one machine at a time.
|
// virtual machines. This driver can only manage one machine at a time.
|
||||||
type ESX5Driver struct {
|
type ESX5Driver struct {
|
||||||
|
base vmwcommon.VmwareDriver
|
||||||
|
|
||||||
Host string
|
Host string
|
||||||
Port uint
|
Port uint
|
||||||
Username string
|
Username string
|
||||||
|
@ -46,9 +49,9 @@ func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string) error {
|
func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, adapter_type string, typeId string) error {
|
||||||
diskPath := d.datastorePath(diskPathLocal)
|
diskPath := d.datastorePath(diskPathLocal)
|
||||||
return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", "lsilogic", diskPath)
|
return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", adapter_type, diskPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) IsRunning(string) (bool, error) {
|
func (d *ESX5Driver) IsRunning(string) (bool, error) {
|
||||||
|
@ -143,10 +146,6 @@ func (d *ESX5Driver) ToolsInstall() error {
|
||||||
return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
|
return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) DhcpLeasesPath(string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ESX5Driver) Verify() error {
|
func (d *ESX5Driver) Verify() error {
|
||||||
checks := []func() error{
|
checks := []func() error{
|
||||||
d.connect,
|
d.connect,
|
||||||
|
@ -159,11 +158,10 @@ func (d *ESX5Driver) Verify() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) HostIP() (string, error) {
|
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
|
||||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -174,6 +172,101 @@ func (d *ESX5Driver) HostIP() (string, error) {
|
||||||
return host, err
|
return host, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *ESX5Driver) GuestIP(multistep.StateBag) (string, error) {
|
||||||
|
// GuestIP is defined by the user as d.Host..but let's validate it just to be sure
|
||||||
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
||||||
|
defer conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
return host, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ESX5Driver) HostAddress(multistep.StateBag) (string, error) {
|
||||||
|
// make a connection
|
||||||
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
||||||
|
defer conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the local address (the host)
|
||||||
|
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to determine host address for ESXi: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate through all the interfaces..
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to enumerate host interfaces : %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, intf := range interfaces {
|
||||||
|
addrs, err := intf.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ..checking to see if any if it's addrs match the host address
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if addr.String() == host { // FIXME: Is this the proper way to compare two HardwareAddrs?
|
||||||
|
return intf.HardwareAddr.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ..unfortunately nothing was found
|
||||||
|
return "", fmt.Errorf("Unable to locate interface matching host address in ESXi: %v", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ESX5Driver) GuestAddress(multistep.StateBag) (string, error) {
|
||||||
|
// list all the interfaces on the esx host
|
||||||
|
r, err := d.esxcli("network", "ip", "interface", "list")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Could not retrieve network interfaces for ESXi: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rip out the interface name and the MAC address from the csv output
|
||||||
|
addrs := make(map[string]string)
|
||||||
|
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
|
||||||
|
if strings.ToUpper(record["Enabled"]) != "TRUE" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addrs[record["Name"]] = record["MAC Address"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all the addresses on the esx host
|
||||||
|
r, err = d.esxcli("network", "ip", "interface", "ipv4", "get")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Could not retrieve network addresses for ESXi: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out the interface name that matches the specified d.Host address
|
||||||
|
var intf string
|
||||||
|
intf = ""
|
||||||
|
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
|
||||||
|
if record["IPv4 Address"] == d.Host && record["Name"] != "" {
|
||||||
|
intf = record["Name"]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if intf == "" {
|
||||||
|
return "", fmt.Errorf("Unable to find matching address for ESXi guest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the MAC address according to the interface name
|
||||||
|
result, ok := addrs[intf]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("Unable to find address for ESXi guest interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ..and we're good
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint, error) {
|
func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint, error) {
|
||||||
var vncPort uint
|
var vncPort uint
|
||||||
|
|
||||||
|
@ -531,6 +624,10 @@ func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
|
||||||
return &esxcliReader{r, header}, nil
|
return &esxcliReader{r, header}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver {
|
||||||
|
return d.base
|
||||||
|
}
|
||||||
|
|
||||||
type esxcliReader struct {
|
type esxcliReader struct {
|
||||||
cr *csv.Reader
|
cr *csv.Reader
|
||||||
header []string
|
header []string
|
||||||
|
|
|
@ -50,8 +50,9 @@ func TestESX5Driver_HostIP(t *testing.T) {
|
||||||
defer listen.Close()
|
defer listen.Close()
|
||||||
|
|
||||||
driver := ESX5Driver{Host: "localhost", Port: uint(port)}
|
driver := ESX5Driver{Host: "localhost", Port: uint(port)}
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
|
||||||
if host, _ := driver.HostIP(); host != expected_host {
|
if host, _ := driver.HostIP(state); host != expected_host {
|
||||||
t.Error(fmt.Sprintf("Expected string, %s but got %s", expected_host, host))
|
t.Error(fmt.Sprintf("Expected string, %s but got %s", expected_host, host))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep
|
||||||
|
|
||||||
ui.Say("Creating virtual machine disk")
|
ui.Say("Creating virtual machine disk")
|
||||||
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
full_disk_path := filepath.Join(config.OutputDir, config.DiskName+".vmdk")
|
||||||
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskTypeId); err != nil {
|
if err := driver.CreateDisk(full_disk_path, fmt.Sprintf("%dM", config.DiskSize), config.DiskAdapterType, config.DiskTypeId); err != nil {
|
||||||
err := fmt.Errorf("Error creating disk: %s", err)
|
err := fmt.Errorf("Error creating disk: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
@ -46,7 +46,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep
|
||||||
additionalpath := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.vmdk", config.DiskName, i+1))
|
additionalpath := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.vmdk", config.DiskName, i+1))
|
||||||
size := fmt.Sprintf("%dM", uint64(additionalsize))
|
size := fmt.Sprintf("%dM", uint64(additionalsize))
|
||||||
|
|
||||||
if err := driver.CreateDisk(additionalpath, size, config.DiskTypeId); err != nil {
|
if err := driver.CreateDisk(additionalpath, size, config.DiskAdapterType, config.DiskTypeId); err != nil {
|
||||||
err := fmt.Errorf("Error creating additional disk: %s", err)
|
err := fmt.Errorf("Error creating additional disk: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
|
@ -16,9 +18,38 @@ import (
|
||||||
type vmxTemplateData struct {
|
type vmxTemplateData struct {
|
||||||
Name string
|
Name string
|
||||||
GuestOS string
|
GuestOS string
|
||||||
DiskName string
|
|
||||||
ISOPath string
|
ISOPath string
|
||||||
Version string
|
Version string
|
||||||
|
|
||||||
|
SCSI_Present string
|
||||||
|
SCSI_diskAdapterType string
|
||||||
|
SATA_Present string
|
||||||
|
NVME_Present string
|
||||||
|
|
||||||
|
DiskName string
|
||||||
|
DiskType string
|
||||||
|
CDROMType string
|
||||||
|
CDROMType_MasterSlave string
|
||||||
|
|
||||||
|
Network_Type string
|
||||||
|
Network_Device string
|
||||||
|
Network_Adapter string
|
||||||
|
|
||||||
|
Sound_Present string
|
||||||
|
Usb_Present string
|
||||||
|
|
||||||
|
Serial_Present string
|
||||||
|
Serial_Type string
|
||||||
|
Serial_Endpoint string
|
||||||
|
Serial_Host string
|
||||||
|
Serial_Yield string
|
||||||
|
Serial_Filename string
|
||||||
|
Serial_Auto string
|
||||||
|
|
||||||
|
Parallel_Present string
|
||||||
|
Parallel_Bidirectional string
|
||||||
|
Parallel_Filename string
|
||||||
|
Parallel_Auto string
|
||||||
}
|
}
|
||||||
|
|
||||||
type additionalDiskTemplateData struct {
|
type additionalDiskTemplateData struct {
|
||||||
|
@ -39,11 +70,238 @@ type stepCreateVMX struct {
|
||||||
tempDir string
|
tempDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* serial conversions */
|
||||||
|
type serialConfigPipe struct {
|
||||||
|
filename string
|
||||||
|
endpoint string
|
||||||
|
host string
|
||||||
|
yield string
|
||||||
|
}
|
||||||
|
|
||||||
|
type serialConfigFile struct {
|
||||||
|
filename string
|
||||||
|
yield string
|
||||||
|
}
|
||||||
|
|
||||||
|
type serialConfigDevice struct {
|
||||||
|
devicename string
|
||||||
|
yield string
|
||||||
|
}
|
||||||
|
|
||||||
|
type serialConfigAuto struct {
|
||||||
|
devicename string
|
||||||
|
yield string
|
||||||
|
}
|
||||||
|
|
||||||
|
type serialUnion struct {
|
||||||
|
serialType interface{}
|
||||||
|
pipe *serialConfigPipe
|
||||||
|
file *serialConfigFile
|
||||||
|
device *serialConfigDevice
|
||||||
|
auto *serialConfigAuto
|
||||||
|
}
|
||||||
|
|
||||||
|
func unformat_serial(config string) (*serialUnion, error) {
|
||||||
|
var defaultSerialPort string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultSerialPort = "COM1"
|
||||||
|
} else {
|
||||||
|
defaultSerialPort = "/dev/ttyS0"
|
||||||
|
}
|
||||||
|
|
||||||
|
input := strings.SplitN(config, ":", 2)
|
||||||
|
if len(input) < 1 {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port: %s", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatType, formatOptions string
|
||||||
|
formatType = input[0]
|
||||||
|
if len(input) == 2 {
|
||||||
|
formatOptions = input[1]
|
||||||
|
} else {
|
||||||
|
formatOptions = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToUpper(formatType) {
|
||||||
|
case "PIPE":
|
||||||
|
comp := strings.Split(formatOptions, ",")
|
||||||
|
if len(comp) < 3 || len(comp) > 4 {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : pipe : %s", config)
|
||||||
|
}
|
||||||
|
if res := strings.ToLower(comp[1]); res != "client" && res != "server" {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : pipe : endpoint : %s : %s", res, config)
|
||||||
|
}
|
||||||
|
if res := strings.ToLower(comp[2]); res != "app" && res != "vm" {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : pipe : host : %s : %s", res, config)
|
||||||
|
}
|
||||||
|
res := &serialConfigPipe{
|
||||||
|
filename: comp[0],
|
||||||
|
endpoint: comp[1],
|
||||||
|
host: map[string]string{"app": "TRUE", "vm": "FALSE"}[strings.ToLower(comp[2])],
|
||||||
|
yield: "FALSE",
|
||||||
|
}
|
||||||
|
if len(comp) == 4 {
|
||||||
|
res.yield = strings.ToUpper(comp[3])
|
||||||
|
}
|
||||||
|
if res.yield != "TRUE" && res.yield != "FALSE" {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : pipe : yield : %s : %s", res.yield, config)
|
||||||
|
}
|
||||||
|
return &serialUnion{serialType: res, pipe: res}, nil
|
||||||
|
|
||||||
|
case "FILE":
|
||||||
|
comp := strings.Split(formatOptions, ",")
|
||||||
|
if len(comp) > 2 {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : file : %s", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &serialConfigFile{yield: "FALSE"}
|
||||||
|
|
||||||
|
res.filename = filepath.FromSlash(comp[0])
|
||||||
|
|
||||||
|
res.yield = map[bool]string{true: strings.ToUpper(comp[0]), false: "FALSE"}[len(comp) > 1]
|
||||||
|
if res.yield != "TRUE" && res.yield != "FALSE" {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : file : yield : %s : %s", res.yield, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &serialUnion{serialType: res, file: res}, nil
|
||||||
|
|
||||||
|
case "DEVICE":
|
||||||
|
comp := strings.Split(formatOptions, ",")
|
||||||
|
if len(comp) > 2 {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : device : %s", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := new(serialConfigDevice)
|
||||||
|
|
||||||
|
if len(comp) == 2 {
|
||||||
|
res.devicename = map[bool]string{true: filepath.FromSlash(comp[0]), false: defaultSerialPort}[len(comp[0]) > 0]
|
||||||
|
res.yield = strings.ToUpper(comp[1])
|
||||||
|
} else if len(comp) == 1 {
|
||||||
|
res.devicename = map[bool]string{true: filepath.FromSlash(comp[0]), false: defaultSerialPort}[len(comp[0]) > 0]
|
||||||
|
res.yield = "FALSE"
|
||||||
|
} else if len(comp) == 0 {
|
||||||
|
res.devicename = defaultSerialPort
|
||||||
|
res.yield = "FALSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.yield != "TRUE" && res.yield != "FALSE" {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : device : yield : %s : %s", res.yield, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &serialUnion{serialType: res, device: res}, nil
|
||||||
|
|
||||||
|
case "AUTO":
|
||||||
|
res := new(serialConfigAuto)
|
||||||
|
res.devicename = defaultSerialPort
|
||||||
|
|
||||||
|
if len(formatOptions) > 0 {
|
||||||
|
res.yield = strings.ToUpper(formatOptions)
|
||||||
|
} else {
|
||||||
|
res.yield = "FALSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.yield != "TRUE" && res.yield != "FALSE" {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for serial port : auto : yield : %s : %s", res.yield, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &serialUnion{serialType: res, auto: res}, nil
|
||||||
|
|
||||||
|
case "NONE":
|
||||||
|
return &serialUnion{serialType: nil}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown serial type : %s : %s", strings.ToUpper(formatType), config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parallel port */
|
||||||
|
type parallelUnion struct {
|
||||||
|
parallelType interface{}
|
||||||
|
file *parallelPortFile
|
||||||
|
device *parallelPortDevice
|
||||||
|
auto *parallelPortAuto
|
||||||
|
}
|
||||||
|
type parallelPortFile struct {
|
||||||
|
filename string
|
||||||
|
}
|
||||||
|
type parallelPortDevice struct {
|
||||||
|
bidirectional string
|
||||||
|
devicename string
|
||||||
|
}
|
||||||
|
type parallelPortAuto struct {
|
||||||
|
bidirectional string
|
||||||
|
}
|
||||||
|
|
||||||
|
func unformat_parallel(config string) (*parallelUnion, error) {
|
||||||
|
input := strings.SplitN(config, ":", 2)
|
||||||
|
if len(input) < 1 {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for parallel port: %s", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatType, formatOptions string
|
||||||
|
formatType = input[0]
|
||||||
|
if len(input) == 2 {
|
||||||
|
formatOptions = input[1]
|
||||||
|
} else {
|
||||||
|
formatOptions = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToUpper(formatType) {
|
||||||
|
case "FILE":
|
||||||
|
res := ¶llelPortFile{filename: filepath.FromSlash(formatOptions)}
|
||||||
|
return ¶llelUnion{parallelType: res, file: res}, nil
|
||||||
|
case "DEVICE":
|
||||||
|
comp := strings.Split(formatOptions, ",")
|
||||||
|
if len(comp) < 1 || len(comp) > 2 {
|
||||||
|
return nil, fmt.Errorf("Unexpected format for parallel port: %s", config)
|
||||||
|
}
|
||||||
|
res := new(parallelPortDevice)
|
||||||
|
res.bidirectional = "FALSE"
|
||||||
|
res.devicename = filepath.FromSlash(comp[0])
|
||||||
|
if len(comp) > 1 {
|
||||||
|
switch strings.ToUpper(comp[1]) {
|
||||||
|
case "BI":
|
||||||
|
res.bidirectional = "TRUE"
|
||||||
|
case "UNI":
|
||||||
|
res.bidirectional = "FALSE"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(comp[0]), config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ¶llelUnion{parallelType: res, device: res}, nil
|
||||||
|
|
||||||
|
case "AUTO":
|
||||||
|
res := new(parallelPortAuto)
|
||||||
|
switch strings.ToUpper(formatOptions) {
|
||||||
|
case "":
|
||||||
|
fallthrough
|
||||||
|
case "UNI":
|
||||||
|
res.bidirectional = "FALSE"
|
||||||
|
case "BI":
|
||||||
|
res.bidirectional = "TRUE"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown parallel port direction : %s : %s", strings.ToUpper(formatOptions), config)
|
||||||
|
}
|
||||||
|
return ¶llelUnion{parallelType: res, auto: res}, nil
|
||||||
|
|
||||||
|
case "NONE":
|
||||||
|
return ¶llelUnion{parallelType: nil}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Unexpected format for parallel port: %s", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* regular steps */
|
||||||
func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
isoPath := state.Get("iso_path").(string)
|
isoPath := state.Get("iso_path").(string)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
// Convert the iso_path into a path relative to the .vmx file if possible
|
||||||
|
if relativeIsoPath, err := filepath.Rel(config.VMXTemplatePath, filepath.FromSlash(isoPath)); err == nil {
|
||||||
|
isoPath = relativeIsoPath
|
||||||
|
}
|
||||||
|
|
||||||
ui.Say("Building and writing VMX file")
|
ui.Say("Building and writing VMX file")
|
||||||
|
|
||||||
vmxTemplate := DefaultVMXTemplate
|
vmxTemplate := DefaultVMXTemplate
|
||||||
|
@ -111,14 +369,211 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data = &vmxTemplateData{
|
templateData := vmxTemplateData{
|
||||||
Name: config.VMName,
|
Name: config.VMName,
|
||||||
GuestOS: config.GuestOSType,
|
GuestOS: config.GuestOSType,
|
||||||
DiskName: config.DiskName,
|
DiskName: config.DiskName,
|
||||||
Version: config.Version,
|
Version: config.Version,
|
||||||
ISOPath: isoPath,
|
ISOPath: isoPath,
|
||||||
|
|
||||||
|
SCSI_Present: "FALSE",
|
||||||
|
SCSI_diskAdapterType: "lsilogic",
|
||||||
|
SATA_Present: "FALSE",
|
||||||
|
NVME_Present: "FALSE",
|
||||||
|
|
||||||
|
DiskType: "scsi",
|
||||||
|
CDROMType: "ide",
|
||||||
|
CDROMType_MasterSlave: "0",
|
||||||
|
|
||||||
|
Network_Adapter: "e1000",
|
||||||
|
|
||||||
|
Sound_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.Sound)],
|
||||||
|
Usb_Present: map[bool]string{true: "TRUE", false: "FALSE"}[bool(config.USB)],
|
||||||
|
|
||||||
|
Serial_Present: "FALSE",
|
||||||
|
Parallel_Present: "FALSE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the disk adapter type that the user specified to tweak the .vmx
|
||||||
|
// Also sync the cdrom adapter type according to what's common for that disk type.
|
||||||
|
diskAdapterType := strings.ToLower(config.DiskAdapterType)
|
||||||
|
switch diskAdapterType {
|
||||||
|
case "ide":
|
||||||
|
templateData.DiskType = "ide"
|
||||||
|
templateData.CDROMType = "ide"
|
||||||
|
templateData.CDROMType_MasterSlave = "1"
|
||||||
|
case "sata":
|
||||||
|
templateData.SATA_Present = "TRUE"
|
||||||
|
templateData.DiskType = "sata"
|
||||||
|
templateData.CDROMType = "sata"
|
||||||
|
templateData.CDROMType_MasterSlave = "1"
|
||||||
|
case "nvme":
|
||||||
|
templateData.NVME_Present = "TRUE"
|
||||||
|
templateData.DiskType = "nvme"
|
||||||
|
templateData.SATA_Present = "TRUE"
|
||||||
|
templateData.CDROMType = "sata"
|
||||||
|
templateData.CDROMType_MasterSlave = "0"
|
||||||
|
case "scsi":
|
||||||
|
diskAdapterType = "lsilogic"
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
templateData.SCSI_Present = "TRUE"
|
||||||
|
templateData.SCSI_diskAdapterType = diskAdapterType
|
||||||
|
templateData.DiskType = "scsi"
|
||||||
|
templateData.CDROMType = "ide"
|
||||||
|
templateData.CDROMType_MasterSlave = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the cdrom adapter type. If the disk adapter type and the
|
||||||
|
// cdrom adapter type are the same, then ensure that the cdrom is the
|
||||||
|
// slave device on whatever bus the disk adapter is on.
|
||||||
|
cdromAdapterType := strings.ToLower(config.CdromAdapterType)
|
||||||
|
if cdromAdapterType == "" {
|
||||||
|
cdromAdapterType = templateData.CDROMType
|
||||||
|
} else if cdromAdapterType == diskAdapterType {
|
||||||
|
templateData.CDROMType_MasterSlave = "1"
|
||||||
|
} else {
|
||||||
|
templateData.CDROMType_MasterSlave = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cdromAdapterType {
|
||||||
|
case "ide":
|
||||||
|
templateData.CDROMType = "ide"
|
||||||
|
case "sata":
|
||||||
|
templateData.SATA_Present = "TRUE"
|
||||||
|
templateData.CDROMType = "sata"
|
||||||
|
case "scsi":
|
||||||
|
templateData.SCSI_Present = "TRUE"
|
||||||
|
templateData.CDROMType = "scsi"
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Error procesing VMX template: %s", cdromAdapterType)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assign the network adapter type into the template if one was specified.
|
||||||
|
network_adapter := strings.ToLower(config.NetworkAdapterType)
|
||||||
|
if network_adapter != "" {
|
||||||
|
templateData.Network_Adapter = network_adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the network type that the user specified
|
||||||
|
network := config.Network
|
||||||
|
driver := state.Get("driver").(vmwcommon.Driver).GetVmwareDriver()
|
||||||
|
|
||||||
|
// read netmap config
|
||||||
|
netmap, err := driver.NetworkMapper()
|
||||||
|
if err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// try and convert the specified network to a device
|
||||||
|
device, err := netmap.NameIntoDevice(network)
|
||||||
|
|
||||||
|
// success. so we know that it's an actual network type inside netmap.conf
|
||||||
|
if err == nil {
|
||||||
|
templateData.Network_Type = network
|
||||||
|
templateData.Network_Device = device
|
||||||
|
// we were unable to find the type, so assume it's a custom network device.
|
||||||
|
} else {
|
||||||
|
templateData.Network_Type = "custom"
|
||||||
|
templateData.Network_Device = network
|
||||||
|
}
|
||||||
|
// store the network so that we can later figure out what ip address to bind to
|
||||||
|
state.Put("vmnetwork", network)
|
||||||
|
|
||||||
|
/// check if serial port has been configured
|
||||||
|
if config.Serial == "" {
|
||||||
|
templateData.Serial_Present = "FALSE"
|
||||||
|
} else {
|
||||||
|
serial, err := unformat_serial(config.Serial)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error procesing VMX template: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData.Serial_Present = "TRUE"
|
||||||
|
templateData.Serial_Filename = ""
|
||||||
|
templateData.Serial_Yield = ""
|
||||||
|
templateData.Serial_Endpoint = ""
|
||||||
|
templateData.Serial_Host = ""
|
||||||
|
templateData.Serial_Auto = "FALSE"
|
||||||
|
|
||||||
|
switch serial.serialType.(type) {
|
||||||
|
case *serialConfigPipe:
|
||||||
|
templateData.Serial_Type = "pipe"
|
||||||
|
templateData.Serial_Endpoint = serial.pipe.endpoint
|
||||||
|
templateData.Serial_Host = serial.pipe.host
|
||||||
|
templateData.Serial_Yield = serial.pipe.yield
|
||||||
|
templateData.Serial_Filename = filepath.FromSlash(serial.pipe.filename)
|
||||||
|
case *serialConfigFile:
|
||||||
|
templateData.Serial_Type = "file"
|
||||||
|
templateData.Serial_Filename = filepath.FromSlash(serial.file.filename)
|
||||||
|
case *serialConfigDevice:
|
||||||
|
templateData.Serial_Type = "device"
|
||||||
|
templateData.Serial_Filename = filepath.FromSlash(serial.device.devicename)
|
||||||
|
case *serialConfigAuto:
|
||||||
|
templateData.Serial_Type = "device"
|
||||||
|
templateData.Serial_Filename = filepath.FromSlash(serial.auto.devicename)
|
||||||
|
templateData.Serial_Yield = serial.auto.yield
|
||||||
|
templateData.Serial_Auto = "TRUE"
|
||||||
|
case nil:
|
||||||
|
templateData.Serial_Present = "FALSE"
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Error procesing VMX template: %v", serial)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// check if parallel port has been configured
|
||||||
|
if config.Parallel == "" {
|
||||||
|
templateData.Parallel_Present = "FALSE"
|
||||||
|
} else {
|
||||||
|
parallel, err := unformat_parallel(config.Parallel)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error procesing VMX template: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData.Parallel_Auto = "FALSE"
|
||||||
|
switch parallel.parallelType.(type) {
|
||||||
|
case *parallelPortFile:
|
||||||
|
templateData.Parallel_Present = "TRUE"
|
||||||
|
templateData.Parallel_Filename = filepath.FromSlash(parallel.file.filename)
|
||||||
|
case *parallelPortDevice:
|
||||||
|
templateData.Parallel_Present = "TRUE"
|
||||||
|
templateData.Parallel_Bidirectional = parallel.device.bidirectional
|
||||||
|
templateData.Parallel_Filename = filepath.FromSlash(parallel.device.devicename)
|
||||||
|
case *parallelPortAuto:
|
||||||
|
templateData.Parallel_Present = "TRUE"
|
||||||
|
templateData.Parallel_Auto = "TRUE"
|
||||||
|
templateData.Parallel_Bidirectional = parallel.auto.bidirectional
|
||||||
|
case nil:
|
||||||
|
templateData.Parallel_Present = "FALSE"
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("Error procesing VMX template: %v", parallel)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data = &templateData
|
||||||
|
|
||||||
|
/// render the .vmx template
|
||||||
vmxContents, err := interpolate.Render(vmxTemplate, &ctx)
|
vmxContents, err := interpolate.Render(vmxTemplate, &ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error procesing VMX template: %s", err)
|
err := fmt.Errorf("Error procesing VMX template: %s", err)
|
||||||
|
@ -176,12 +631,13 @@ ehci.pciSlotNumber = "34"
|
||||||
ehci.present = "TRUE"
|
ehci.present = "TRUE"
|
||||||
ethernet0.addressType = "generated"
|
ethernet0.addressType = "generated"
|
||||||
ethernet0.bsdName = "en0"
|
ethernet0.bsdName = "en0"
|
||||||
ethernet0.connectionType = "nat"
|
ethernet0.connectionType = "{{ .Network_Type }}"
|
||||||
|
ethernet0.vnet = "{{ .Network_Device }}"
|
||||||
ethernet0.displayName = "Ethernet"
|
ethernet0.displayName = "Ethernet"
|
||||||
ethernet0.linkStatePropagation.enable = "FALSE"
|
ethernet0.linkStatePropagation.enable = "FALSE"
|
||||||
ethernet0.pciSlotNumber = "33"
|
ethernet0.pciSlotNumber = "33"
|
||||||
ethernet0.present = "TRUE"
|
ethernet0.present = "TRUE"
|
||||||
ethernet0.virtualDev = "e1000"
|
ethernet0.virtualDev = "{{ .Network_Adapter }}"
|
||||||
ethernet0.wakeOnPcktRcv = "FALSE"
|
ethernet0.wakeOnPcktRcv = "FALSE"
|
||||||
extendedConfigFile = "{{ .Name }}.vmxf"
|
extendedConfigFile = "{{ .Name }}.vmxf"
|
||||||
floppy0.present = "FALSE"
|
floppy0.present = "FALSE"
|
||||||
|
@ -190,9 +646,21 @@ gui.fullScreenAtPowerOn = "FALSE"
|
||||||
gui.viewModeAtPowerOn = "windowed"
|
gui.viewModeAtPowerOn = "windowed"
|
||||||
hgfs.linkRootShare = "TRUE"
|
hgfs.linkRootShare = "TRUE"
|
||||||
hgfs.mapRootShare = "TRUE"
|
hgfs.mapRootShare = "TRUE"
|
||||||
ide1:0.present = "TRUE"
|
|
||||||
ide1:0.fileName = "{{ .ISOPath }}"
|
scsi0.present = "{{ .SCSI_Present }}"
|
||||||
ide1:0.deviceType = "cdrom-image"
|
scsi0.virtualDev = "{{ .SCSI_diskAdapterType }}"
|
||||||
|
scsi0.pciSlotNumber = "16"
|
||||||
|
scsi0:0.redo = ""
|
||||||
|
sata0.present = "{{ .SATA_Present }}"
|
||||||
|
nvme0.present = "{{ .NVME_Present }}"
|
||||||
|
|
||||||
|
{{ .DiskType }}0:0.present = "TRUE"
|
||||||
|
{{ .DiskType }}0:0.fileName = "{{ .DiskName }}.vmdk"
|
||||||
|
|
||||||
|
{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.present = "TRUE"
|
||||||
|
{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.fileName = "{{ .ISOPath }}"
|
||||||
|
{{ .CDROMType }}0:{{ .CDROMType_MasterSlave }}.deviceType = "cdrom-image"
|
||||||
|
|
||||||
isolation.tools.hgfs.disable = "FALSE"
|
isolation.tools.hgfs.disable = "FALSE"
|
||||||
memsize = "512"
|
memsize = "512"
|
||||||
nvram = "{{ .Name }}.nvram"
|
nvram = "{{ .Name }}.nvram"
|
||||||
|
@ -221,17 +689,38 @@ powerType.suspend = "soft"
|
||||||
proxyApps.publishToHost = "FALSE"
|
proxyApps.publishToHost = "FALSE"
|
||||||
replay.filename = ""
|
replay.filename = ""
|
||||||
replay.supported = "FALSE"
|
replay.supported = "FALSE"
|
||||||
scsi0.pciSlotNumber = "16"
|
|
||||||
scsi0.present = "TRUE"
|
// Sound
|
||||||
scsi0.virtualDev = "lsilogic"
|
sound.startConnected = "{{ .Sound_Present }}"
|
||||||
scsi0:0.fileName = "{{ .DiskName }}.vmdk"
|
sound.present = "{{ .Sound_Present }}"
|
||||||
scsi0:0.present = "TRUE"
|
sound.fileName = "-1"
|
||||||
scsi0:0.redo = ""
|
sound.autodetect = "TRUE"
|
||||||
sound.startConnected = "FALSE"
|
|
||||||
tools.syncTime = "TRUE"
|
tools.syncTime = "TRUE"
|
||||||
tools.upgrade.policy = "upgradeAtPowerCycle"
|
tools.upgrade.policy = "upgradeAtPowerCycle"
|
||||||
|
|
||||||
|
// USB
|
||||||
usb.pciSlotNumber = "32"
|
usb.pciSlotNumber = "32"
|
||||||
usb.present = "FALSE"
|
usb.present = "{{ .Usb_Present }}"
|
||||||
|
usb_xhci.present = "TRUE"
|
||||||
|
|
||||||
|
// Serial
|
||||||
|
serial0.present = "{{ .Serial_Present }}"
|
||||||
|
serial0.startConnected = "{{ .Serial_Present }}"
|
||||||
|
serial0.fileName = "{{ .Serial_Filename }}"
|
||||||
|
serial0.autodetect = "{{ .Serial_Auto }}"
|
||||||
|
serial0.fileType = "{{ .Serial_Type }}"
|
||||||
|
serial0.yieldOnMsrRead = "{{ .Serial_Yield }}"
|
||||||
|
serial0.pipe.endPoint = "{{ .Serial_Endpoint }}"
|
||||||
|
serial0.tryNoRxLoss = "{{ .Serial_Host }}"
|
||||||
|
|
||||||
|
// Parallel
|
||||||
|
parallel0.present = "{{ .Parallel_Present }}"
|
||||||
|
parallel0.startConnected = "{{ .Parallel_Present }}"
|
||||||
|
parallel0.fileName = "{{ .Parallel_Filename }}"
|
||||||
|
parallel0.autodetect = "{{ .Parallel_Auto }}"
|
||||||
|
parallel0.bidirectional = "{{ .Parallel_Bidirectional }}"
|
||||||
|
|
||||||
virtualHW.productCompatibility = "hosted"
|
virtualHW.productCompatibility = "hosted"
|
||||||
virtualHW.version = "{{ .Version }}"
|
virtualHW.version = "{{ .Version }}"
|
||||||
vmci0.id = "1861462627"
|
vmci0.id = "1861462627"
|
||||||
|
|
|
@ -0,0 +1,409 @@
|
||||||
|
package iso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/provisioner/shell"
|
||||||
|
"github.com/hashicorp/packer/template"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var vmxTestBuilderConfig = map[string]string{
|
||||||
|
"type": `"vmware-iso"`,
|
||||||
|
"iso_url": `"https://archive.org/download/ut-ttylinux-i686-12.6/ut-ttylinux-i686-12.6.iso"`,
|
||||||
|
"iso_checksum_type": `"md5"`,
|
||||||
|
"iso_checksum": `"43c1feeae55a44c6ef694b8eb18408a6"`,
|
||||||
|
"ssh_username": `"root"`,
|
||||||
|
"ssh_password": `"password"`,
|
||||||
|
"ssh_wait_timeout": `"45s"`,
|
||||||
|
"boot_command": `["<enter><wait5><wait10>","root<enter><wait>password<enter><wait>","udhcpc<enter><wait>"]`,
|
||||||
|
"shutdown_command": `"/sbin/shutdown -h; exit 0"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var vmxTestProvisionerConfig = map[string]string{
|
||||||
|
"type": `"shell"`,
|
||||||
|
"inline": `["echo hola mundo"]`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}`
|
||||||
|
|
||||||
|
func tmpnam(prefix string) string {
|
||||||
|
var path string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
const length = 16
|
||||||
|
|
||||||
|
dir := os.TempDir()
|
||||||
|
max := int(math.Pow(2, float64(length)))
|
||||||
|
|
||||||
|
n, err := rand.Intn(max), nil
|
||||||
|
for path = filepath.Join(dir, prefix+strconv.Itoa(n)); err == nil; _, err = os.Stat(path) {
|
||||||
|
n = rand.Intn(max)
|
||||||
|
path = filepath.Join(dir, prefix+strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFloppyOutput(prefix string) (string, string, error) {
|
||||||
|
output := tmpnam(prefix)
|
||||||
|
f, err := os.Create(output)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Unable to create empty %s: %s", output, err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
vmxData := []string{
|
||||||
|
`"floppy0.present":"TRUE"`,
|
||||||
|
`"floppy0.fileType":"file"`,
|
||||||
|
`"floppy0.clientDevice":"FALSE"`,
|
||||||
|
`"floppy0.fileName":"%s"`,
|
||||||
|
`"floppy0.startConnected":"TRUE"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile := strings.Replace(output, "\\", "\\\\", -1)
|
||||||
|
vmxString := fmt.Sprintf("{"+strings.Join(vmxData, ",")+"}", outputFile)
|
||||||
|
return output, vmxString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFloppyOutput(path string) (string, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to open file %s", path)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
data, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to read file: %s", err)
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return string(data[:bytes.IndexByte(data, 0)]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisionerConfig map[string]string) error {
|
||||||
|
ui := packer.TestUi(t)
|
||||||
|
|
||||||
|
// create builder config and update with user-supplied options
|
||||||
|
cfgBuilder := map[string]string{}
|
||||||
|
for k, v := range vmxTestBuilderConfig {
|
||||||
|
cfgBuilder[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range builderConfig {
|
||||||
|
cfgBuilder[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert our builder config into a single sprintfable string
|
||||||
|
builderLines := []string{}
|
||||||
|
for k, v := range cfgBuilder {
|
||||||
|
builderLines = append(builderLines, fmt.Sprintf(`"%s":%s`, k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// create provisioner config and update with user-supplied options
|
||||||
|
cfgProvisioner := map[string]string{}
|
||||||
|
for k, v := range vmxTestProvisionerConfig {
|
||||||
|
cfgProvisioner[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range provisionerConfig {
|
||||||
|
cfgProvisioner[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert our provisioner config into a single sprintfable string
|
||||||
|
provisionerLines := []string{}
|
||||||
|
for k, v := range cfgProvisioner {
|
||||||
|
provisionerLines = append(provisionerLines, fmt.Sprintf(`"%s":%s`, k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// and now parse them into a template
|
||||||
|
configString := fmt.Sprintf(vmxTestTemplate, strings.Join(builderLines, `,`), strings.Join(provisionerLines, `,`))
|
||||||
|
|
||||||
|
tpl, err := template.Parse(strings.NewReader(configString))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to parse test config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create our config to test the vmware-iso builder
|
||||||
|
components := packer.ComponentFinder{
|
||||||
|
Builder: func(n string) (packer.Builder, error) {
|
||||||
|
return &Builder{}, nil
|
||||||
|
},
|
||||||
|
Hook: func(n string) (packer.Hook, error) {
|
||||||
|
return &packer.DispatchHook{}, nil
|
||||||
|
},
|
||||||
|
PostProcessor: func(n string) (packer.PostProcessor, error) {
|
||||||
|
return &packer.MockPostProcessor{}, nil
|
||||||
|
},
|
||||||
|
Provisioner: func(n string) (packer.Provisioner, error) {
|
||||||
|
return &shell.Provisioner{}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config := packer.CoreConfig{
|
||||||
|
Template: tpl,
|
||||||
|
Components: components,
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a core using our template
|
||||||
|
core, err := packer.NewCore(&config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create core: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we can prepare our build
|
||||||
|
b, err := core.Build("vmware-iso")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create build: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
warn, err := b.Prepare()
|
||||||
|
if len(warn) > 0 {
|
||||||
|
for _, w := range warn {
|
||||||
|
t.Logf("Configuration warning: %s", w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// and then finally build it
|
||||||
|
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||||
|
artifacts, err := b.Run(ui, cache)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to build artifact: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to see that we only got one artifact back
|
||||||
|
if len(artifacts) == 1 {
|
||||||
|
return artifacts[0].Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise some number of errors happened
|
||||||
|
t.Logf("Unexpected number of artifacts returned: %d", len(artifacts))
|
||||||
|
errors := make([]error, 0)
|
||||||
|
for _, artifact := range artifacts {
|
||||||
|
if err := artifact.Destroy(); err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Errorf("%d Errors returned while trying to destroy artifacts", len(errors))
|
||||||
|
return fmt.Errorf("Error while trying to destroy artifacts: %v", errors)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepCreateVmx_SerialFile(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 VMware binaries.")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpfile := tmpnam("SerialFileInput.")
|
||||||
|
|
||||||
|
serialConfig := map[string]string{
|
||||||
|
"serial": fmt.Sprintf(`"file:%s"`, filepath.ToSlash(tmpfile)),
|
||||||
|
}
|
||||||
|
|
||||||
|
error := setupVMwareBuild(t, serialConfig, map[string]string{})
|
||||||
|
if error != nil {
|
||||||
|
t.Errorf("Unable to read file: %s", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Stat(tmpfile)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("VMware builder did not create a file for serial port: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f != nil {
|
||||||
|
if err := os.Remove(tmpfile); err != nil {
|
||||||
|
t.Fatalf("Unable to remove file %s: %s", tmpfile, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepCreateVmx_SerialPort(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 VMware binaries.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultSerial string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultSerial = "COM1"
|
||||||
|
} else {
|
||||||
|
defaultSerial = "/dev/ttyS0"
|
||||||
|
}
|
||||||
|
|
||||||
|
config := map[string]string{
|
||||||
|
"serial": fmt.Sprintf(`"device:%s"`, filepath.ToSlash(defaultSerial)),
|
||||||
|
}
|
||||||
|
provision := map[string]string{
|
||||||
|
"inline": `"dmesg | egrep -o '^serial8250: ttyS1 at' > /dev/fd0"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// where to write output
|
||||||
|
output, vmxData, err := createFloppyOutput("SerialPortOutput.")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating output: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if _, err := os.Stat(output); err == nil {
|
||||||
|
os.Remove(output)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
config["vmx_data"] = vmxData
|
||||||
|
t.Logf("Preparing to write output to %s", output)
|
||||||
|
|
||||||
|
// whee
|
||||||
|
err = setupVMwareBuild(t, config, provision)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the output
|
||||||
|
data, err := readFloppyOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != "serial8250: ttyS1 at\n" {
|
||||||
|
t.Errorf("Serial port not detected : %v", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepCreateVmx_ParallelPort(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 VMware binaries.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultParallel string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultParallel = "LPT1"
|
||||||
|
} else {
|
||||||
|
defaultParallel = "/dev/lp0"
|
||||||
|
}
|
||||||
|
|
||||||
|
config := map[string]string{
|
||||||
|
"parallel": fmt.Sprintf(`"device:%s,uni"`, filepath.ToSlash(defaultParallel)),
|
||||||
|
}
|
||||||
|
provision := map[string]string{
|
||||||
|
"inline": `"cat /proc/modules | egrep -o '^parport ' > /dev/fd0"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// where to write output
|
||||||
|
output, vmxData, err := createFloppyOutput("ParallelPortOutput.")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating output: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if _, err := os.Stat(output); err == nil {
|
||||||
|
os.Remove(output)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
config["vmx_data"] = vmxData
|
||||||
|
t.Logf("Preparing to write output to %s", output)
|
||||||
|
|
||||||
|
// whee
|
||||||
|
error := setupVMwareBuild(t, config, provision)
|
||||||
|
if error != nil {
|
||||||
|
t.Errorf("%s", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the output
|
||||||
|
data, err := readFloppyOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != "parport \n" {
|
||||||
|
t.Errorf("Parallel port not detected : %v", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepCreateVmx_Usb(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 VMware binaries.")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := map[string]string{
|
||||||
|
"usb": `"TRUE"`,
|
||||||
|
}
|
||||||
|
provision := map[string]string{
|
||||||
|
"inline": `"dmesg | egrep -m1 -o 'USB hub found$' > /dev/fd0"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// where to write output
|
||||||
|
output, vmxData, err := createFloppyOutput("UsbOutput.")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating output: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if _, err := os.Stat(output); err == nil {
|
||||||
|
os.Remove(output)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
config["vmx_data"] = vmxData
|
||||||
|
t.Logf("Preparing to write output to %s", output)
|
||||||
|
|
||||||
|
// whee
|
||||||
|
error := setupVMwareBuild(t, config, provision)
|
||||||
|
if error != nil {
|
||||||
|
t.Errorf("%s", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the output
|
||||||
|
data, err := readFloppyOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != "USB hub found\n" {
|
||||||
|
t.Errorf("USB support not detected : %v", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepCreateVmx_Sound(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 VMware binaries.")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := map[string]string{
|
||||||
|
"sound": `"TRUE"`,
|
||||||
|
}
|
||||||
|
provision := map[string]string{
|
||||||
|
"inline": `"cat /proc/modules | egrep -o '^soundcore' > /dev/fd0"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// where to write output
|
||||||
|
output, vmxData, err := createFloppyOutput("SoundOutput.")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating output: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if _, err := os.Stat(output); err == nil {
|
||||||
|
os.Remove(output)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
config["vmx_data"] = vmxData
|
||||||
|
t.Logf("Preparing to write output to %s", output)
|
||||||
|
|
||||||
|
// whee
|
||||||
|
error := setupVMwareBuild(t, config, provision)
|
||||||
|
if error != nil {
|
||||||
|
t.Errorf("Unable to read file: %s", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the output
|
||||||
|
data, err := readFloppyOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != "soundcore\n" {
|
||||||
|
t.Errorf("Soundcard not detected : %v", data)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue