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:
|
||||
|
||||
* builder/docker: Remove credentials from being shown in the log. [GH-5666]
|
||||
* builder/triton: Triton RBAC is now supported. [GH-5741]
|
||||
* provisioner/ansible: Improve user retrieval. [GH-5758]
|
||||
* post-processor/docker: Remove credentials from being shown in the log. [GH-5666]
|
||||
* 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/amazon: Replace `InstanceStatusOK` check with `InstanceReady`. This reduces build times universally while still working for all instance types. [GH-5678]
|
||||
* **New builder:** `ncloud` for building server images using the NAVER Cloud
|
||||
Platform. [GH-5791]
|
||||
* **New builder:** `oci-classic` for building new custom images for use with
|
||||
Oracle Cloud Infrastructure Classic Compute. [GH-5819]
|
||||
* **New builder:** `scaleway` - The Scaleway Packer builder is able to create
|
||||
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 `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:
|
||||
|
||||
* 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)
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
/builder/oracle/ @prydie @owainlewis
|
||||
/builder/profitbricks/ @jasmingacic
|
||||
/builder/triton/ @jen20 @sean-
|
||||
/builder/ncloud/ @YuSungDuk
|
||||
/builder/scaleway/ @dimtion @edouardb
|
||||
|
||||
# provisioners
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ comes out of the box with support for the following platforms:
|
|||
* Parallels
|
||||
* ProfitBricks
|
||||
* QEMU. Both KVM and Xen images.
|
||||
* Scaleway
|
||||
* Triton (Joyent Public Cloud)
|
||||
* VMware
|
||||
* VirtualBox
|
||||
|
|
|
@ -71,11 +71,16 @@ func TestBuilderPrepare_ChrootMounts(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ChrootMountsBadDefaults(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["chroot_mounts"] = [][]string{
|
||||
{"bad"},
|
||||
}
|
||||
warnings, err = b.Prepare(config)
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
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" {
|
||||
t.Errorf("Was expecting default value for copy_files.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_CopyFilesNoDefault(t *testing.T) {
|
||||
b := &Builder{}
|
||||
config := testConfig()
|
||||
|
||||
config["copy_files"] = []string{}
|
||||
warnings, err = b.Prepare(config)
|
||||
warnings, err := b.Prepare(config)
|
||||
if len(warnings) > 0 {
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/ec2rolecreds"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
|
@ -16,15 +20,16 @@ import (
|
|||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
|
||||
MFACode string `mapstructure:"mfa_code"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
Token string `mapstructure:"token"`
|
||||
session *session.Session
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
CustomEndpointEc2 string `mapstructure:"custom_endpoint_ec2"`
|
||||
MFACode string `mapstructure:"mfa_code"`
|
||||
ProfileName string `mapstructure:"profile"`
|
||||
RawRegion string `mapstructure:"region"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
Token string `mapstructure:"token"`
|
||||
session *session.Session
|
||||
}
|
||||
|
||||
// Config returns a valid aws.Config object for access to AWS services, or
|
||||
|
@ -34,14 +39,78 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
|||
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 != "" {
|
||||
if err := os.Setenv("AWS_PROFILE", c.ProfileName); err != nil {
|
||||
return nil, fmt.Errorf("Set env error: %s", err)
|
||||
// Build isolated HTTP client to avoid issues with globally-shared settings
|
||||
client := cleanhttp.DefaultClient()
|
||||
|
||||
// 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 != "" {
|
||||
config = config.WithRegion(c.RawRegion)
|
||||
} else if region := c.metadataRegion(); region != "" {
|
||||
|
@ -52,11 +121,6 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
|||
config = config.WithEndpoint(c.CustomEndpointEc2)
|
||||
}
|
||||
|
||||
if c.AccessKey != "" {
|
||||
config = config.WithCredentials(
|
||||
credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token))
|
||||
}
|
||||
|
||||
opts := session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: *config,
|
||||
|
@ -80,6 +144,21 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
|||
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
|
||||
func (c *AccessConfig) metadataRegion() string {
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@ package common
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
)
|
||||
|
||||
func testAccessConfig() *AccessConfig {
|
||||
|
@ -38,3 +41,20 @@ func TestAccessConfigPrepare_Region(t *testing.T) {
|
|||
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"`
|
||||
AMIRegions []string `mapstructure:"ami_regions"`
|
||||
AMISkipRegionValidation bool `mapstructure:"skip_region_validation"`
|
||||
AMITags map[string]string `mapstructure:"tags"`
|
||||
AMITags TagMap `mapstructure:"tags"`
|
||||
AMIENASupport bool `mapstructure:"ena_support"`
|
||||
AMISriovNetSupport bool `mapstructure:"sriov_support"`
|
||||
AMIForceDeregister bool `mapstructure:"force_deregister"`
|
||||
|
@ -25,7 +25,7 @@ type AMIConfig struct {
|
|||
AMIEncryptBootVolume bool `mapstructure:"encrypt_boot"`
|
||||
AMIKmsKeyId string `mapstructure:"kms_key_id"`
|
||||
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"`
|
||||
SnapshotGroups []string `mapstructure:"snapshot_groups"`
|
||||
}
|
||||
|
|
|
@ -53,7 +53,6 @@ type RunConfig struct {
|
|||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHKeyPairName string `mapstructure:"ssh_keypair_name"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
SSHInterface string `mapstructure:"ssh_interface"`
|
||||
}
|
||||
|
||||
|
@ -78,14 +77,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
|
||||
// Validation
|
||||
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
|
||||
if c.SSHInterface != "public_ip" &&
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
)
|
||||
|
||||
type StepCreateTags struct {
|
||||
Tags map[string]string
|
||||
SnapshotTags map[string]string
|
||||
Tags TagMap
|
||||
SnapshotTags TagMap
|
||||
Ctx interpolate.Context
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
|||
sourceAMI = ""
|
||||
}
|
||||
|
||||
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
|
||||
if !s.Tags.IsSet() && !s.SnapshotTags.IsSet() {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
@ -79,22 +79,22 @@ func (s *StepCreateTags) Run(_ context.Context, state multistep.StateBag) multis
|
|||
|
||||
// Convert tags to ec2.Tag format
|
||||
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 {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ReportTags(ui, amiTags)
|
||||
amiTags.Report(ui)
|
||||
|
||||
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 {
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ReportTags(ui, snapshotTags)
|
||||
snapshotTags.Report(ui)
|
||||
|
||||
// Retry creating tags for about 2.5 minutes
|
||||
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) {
|
||||
// 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"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -19,19 +21,20 @@ type StepRunSourceInstance struct {
|
|||
AssociatePublicIpAddress bool
|
||||
AvailabilityZone string
|
||||
BlockDevices BlockDevices
|
||||
Ctx interpolate.Context
|
||||
Debug bool
|
||||
EbsOptimized bool
|
||||
ExpectedRootDevice string
|
||||
IamInstanceProfile string
|
||||
InstanceInitiatedShutdownBehavior string
|
||||
InstanceType string
|
||||
IsRestricted bool
|
||||
SourceAMI string
|
||||
SubnetId string
|
||||
Tags map[string]string
|
||||
VolumeTags map[string]string
|
||||
Tags TagMap
|
||||
UserData string
|
||||
UserDataFile string
|
||||
Ctx interpolate.Context
|
||||
VolumeTags TagMap
|
||||
|
||||
instanceId string
|
||||
}
|
||||
|
@ -85,16 +88,15 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
|||
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 {
|
||||
err := fmt.Errorf("Error tagging source instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
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 {
|
||||
err := fmt.Errorf("Error tagging volumes: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -114,6 +116,7 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
|||
EbsOptimized: &s.EbsOptimized,
|
||||
}
|
||||
|
||||
// Collect tags for tagging on resource creation
|
||||
var tagSpecs []*ec2.TagSpecification
|
||||
|
||||
if len(ec2Tags) > 0 {
|
||||
|
@ -134,8 +137,11 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
|||
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)
|
||||
ec2Tags.Report(ui)
|
||||
volTags.Report(ui)
|
||||
}
|
||||
|
||||
if keyName != "" {
|
||||
|
@ -212,6 +218,70 @@ func (s *StepRunSourceInstance) Run(_ context.Context, state multistep.StateBag)
|
|||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ type StepRunSpotInstance struct {
|
|||
SpotPrice string
|
||||
SpotPriceProduct string
|
||||
SubnetId string
|
||||
Tags map[string]string
|
||||
VolumeTags map[string]string
|
||||
Tags TagMap
|
||||
VolumeTags TagMap
|
||||
UserData string
|
||||
UserDataFile string
|
||||
Ctx interpolate.Context
|
||||
|
@ -143,14 +143,14 @@ func (s *StepRunSpotInstance) Run(_ context.Context, state multistep.StateBag) m
|
|||
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 {
|
||||
err := fmt.Errorf("Error tagging source instance: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
ReportTags(ui, ec2Tags)
|
||||
ec2Tags.Report(ui)
|
||||
|
||||
ui.Message(fmt.Sprintf(
|
||||
"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")
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
ReportTags(ui, tags)
|
||||
volumeTags.Report(ui)
|
||||
|
||||
_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
|
||||
Resources: volumeIds,
|
||||
Tags: tags,
|
||||
Tags: volumeTags,
|
||||
})
|
||||
|
||||
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.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.RunConfig `mapstructure:",squash"`
|
||||
VolumeRunTags map[string]string `mapstructure:"run_volume_tags"`
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
|
||||
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,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
|
|
|
@ -26,8 +26,8 @@ type Config struct {
|
|||
awscommon.BlockDevices `mapstructure:",squash"`
|
||||
awscommon.AMIConfig `mapstructure:",squash"`
|
||||
|
||||
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
|
||||
VolumeRunTags map[string]string `mapstructure:"run_volume_tags"`
|
||||
RootDevice RootBlockDevice `mapstructure:"ami_root_device"`
|
||||
VolumeRunTags awscommon.TagMap `mapstructure:"run_volume_tags"`
|
||||
|
||||
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,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
type BlockDevice struct {
|
||||
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) {
|
||||
|
|
|
@ -149,6 +149,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
Tags: b.config.RunTags,
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"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/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -44,14 +43,14 @@ func (s *stepTagEBSVolumes) Run(_ context.Context, state multistep.StateBag) mul
|
|||
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 {
|
||||
err := fmt.Errorf("Error tagging device %s with %s", mapping.DeviceName, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
awscommon.ReportTags(ui, tags)
|
||||
tags.Report(ui)
|
||||
|
||||
for _, v := range instance.BlockDeviceMappings {
|
||||
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,
|
||||
IamInstanceProfile: b.config.IamInstanceProfile,
|
||||
InstanceType: b.config.InstanceType,
|
||||
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
|
||||
SourceAMI: b.config.SourceAmi,
|
||||
SubnetId: b.config.SubnetId,
|
||||
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
|
||||
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
|
||||
log.Printf("Copying to %s on container %s.", dst, c.ContainerID)
|
||||
|
|
|
@ -36,6 +36,7 @@ type Config struct {
|
|||
ImageDescription string `mapstructure:"image_description"`
|
||||
ImageFamily string `mapstructure:"image_family"`
|
||||
ImageLabels map[string]string `mapstructure:"image_labels"`
|
||||
ImageLicenses []string `mapstructure:"image_licenses"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
Labels map[string]string `mapstructure:"labels"`
|
||||
MachineType string `mapstructure:"machine_type"`
|
||||
|
|
|
@ -309,6 +309,9 @@ func testConfig(t *testing.T) map[string]interface{} {
|
|||
"label-1": "value-1",
|
||||
"label-2": "value-2",
|
||||
},
|
||||
"image_licenses": []string{
|
||||
"test-license",
|
||||
},
|
||||
"zone": "us-east1-a",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
type Driver interface {
|
||||
// CreateImage creates an image from the given disk in Google Compute
|
||||
// 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(name string) <-chan error
|
||||
|
|
|
@ -97,12 +97,13 @@ func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
|
|||
}, 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{
|
||||
Description: description,
|
||||
Name: name,
|
||||
Family: family,
|
||||
Labels: image_labels,
|
||||
Licenses: image_licenses,
|
||||
SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk),
|
||||
SourceType: "RAW",
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ type DriverMock struct {
|
|||
CreateImageDesc string
|
||||
CreateImageFamily string
|
||||
CreateImageLabels map[string]string
|
||||
CreateImageLicenses []string
|
||||
CreateImageZone string
|
||||
CreateImageDisk string
|
||||
CreateImageResultLicenses []string
|
||||
CreateImageResultProjectId string
|
||||
CreateImageResultSelfLink string
|
||||
CreateImageResultSizeGb int64
|
||||
|
@ -82,11 +82,12 @@ type DriverMock struct {
|
|||
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.CreateImageDesc = description
|
||||
d.CreateImageFamily = family
|
||||
d.CreateImageLabels = image_labels
|
||||
d.CreateImageLicenses = image_licenses
|
||||
d.CreateImageZone = zone
|
||||
d.CreateImageDisk = disk
|
||||
if d.CreateImageResultProjectId == "" {
|
||||
|
@ -106,7 +107,7 @@ func (d *DriverMock) CreateImage(name, description, family, zone, disk string, i
|
|||
ch := make(chan *Image, 1)
|
||||
ch <- &Image{
|
||||
Labels: d.CreateImageLabels,
|
||||
Licenses: d.CreateImageResultLicenses,
|
||||
Licenses: d.CreateImageLicenses,
|
||||
Name: name,
|
||||
ProjectId: d.CreateImageResultProjectId,
|
||||
SelfLink: d.CreateImageResultSelfLink,
|
||||
|
|
|
@ -40,7 +40,7 @@ func (s *StepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
|||
|
||||
imageCh, errCh := driver.CreateImage(
|
||||
config.ImageName, config.ImageDescription, config.ImageFamily, config.Zone,
|
||||
config.DiskName, config.ImageLabels)
|
||||
config.DiskName, config.ImageLabels, config.ImageLicenses)
|
||||
var err error
|
||||
select {
|
||||
case err = <-errCh:
|
||||
|
|
|
@ -22,7 +22,6 @@ func TestStepCreateImage(t *testing.T) {
|
|||
d := state.Get("driver").(*DriverMock)
|
||||
|
||||
// These are the values of the image the driver will return.
|
||||
d.CreateImageResultLicenses = []string{"test-license"}
|
||||
d.CreateImageResultProjectId = "test-project"
|
||||
d.CreateImageResultSizeGb = 100
|
||||
|
||||
|
@ -36,7 +35,6 @@ func TestStepCreateImage(t *testing.T) {
|
|||
assert.True(t, ok, "Image in state is not an Image.")
|
||||
|
||||
// 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.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.")
|
||||
|
@ -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.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.CreateImageLicenses, c.ImageLicenses, "Incorrect image_licenses passed to driver.")
|
||||
}
|
||||
|
||||
func TestStepCreateImage_errorOnChannel(t *testing.T) {
|
||||
|
|
|
@ -52,6 +52,8 @@ type Driver interface {
|
|||
//Set the vlan to use for machine
|
||||
SetVirtualMachineVlanId(string, string) error
|
||||
|
||||
SetVmNetworkAdapterMacAddress(string, string) error
|
||||
|
||||
UntagVirtualMachineNetworkAdapterVlan(string, string) error
|
||||
|
||||
CreateExternalVirtualSwitch(string, string) error
|
||||
|
|
|
@ -67,6 +67,11 @@ type DriverMock struct {
|
|||
SetNetworkAdapterVlanId_VlanId string
|
||||
SetNetworkAdapterVlanId_Err error
|
||||
|
||||
SetVmNetworkAdapterMacAddress_Called bool
|
||||
SetVmNetworkAdapterMacAddress_VmName string
|
||||
SetVmNetworkAdapterMacAddress_Mac string
|
||||
SetVmNetworkAdapterMacAddress_Err error
|
||||
|
||||
SetVirtualMachineVlanId_Called bool
|
||||
SetVirtualMachineVlanId_VmName string
|
||||
SetVirtualMachineVlanId_VlanId string
|
||||
|
@ -318,6 +323,13 @@ func (d *DriverMock) SetNetworkAdapterVlanId(switchName string, vlanId string) e
|
|||
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 {
|
||||
d.SetVirtualMachineVlanId_Called = true
|
||||
d.SetVirtualMachineVlanId_VmName = vmName
|
||||
|
|
|
@ -146,6 +146,10 @@ func (d *HypervPS4Driver) SetVirtualMachineVlanId(vmName string, vlanId string)
|
|||
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 {
|
||||
return hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ type StepCloneVM struct {
|
|||
EnableDynamicMemory bool
|
||||
EnableSecureBoot bool
|
||||
EnableVirtualizationExtensions bool
|
||||
MacAddress string
|
||||
}
|
||||
|
||||
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
|
||||
state.Put("vmName", s.VMName)
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ type StepCreateVM struct {
|
|||
EnableVirtualizationExtensions bool
|
||||
AdditionalDiskSize []uint
|
||||
DifferencingDisk bool
|
||||
MacAddress string
|
||||
SkipExport bool
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// inline vhd path if export is skipped
|
||||
if s.SkipExport {
|
||||
vhdPath = filepath.Join(s.OutputDir, "Virtual Hard Disks")
|
||||
}
|
||||
|
||||
// convert the MB to bytes
|
||||
ramSize := int64(s.RamSize * 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
|
||||
state.Put("vmName", s.VMName)
|
||||
|
||||
|
|
|
@ -18,18 +18,19 @@ const (
|
|||
type StepExportVm struct {
|
||||
OutputDir string
|
||||
SkipCompaction bool
|
||||
SkipExport bool
|
||||
}
|
||||
|
||||
func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(Driver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var err error
|
||||
var errorMsg string
|
||||
|
||||
vmName := state.Get("vmName").(string)
|
||||
tmpPath := state.Get("packerTempDir").(string)
|
||||
outputPath := s.OutputDir
|
||||
expPath := s.OutputDir
|
||||
|
||||
// create temp path to export vm
|
||||
errorMsg = "Error creating temp export path: %s"
|
||||
|
@ -40,21 +41,21 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste
|
|||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if !s.SkipExport {
|
||||
ui.Say("Exporting vm...")
|
||||
|
||||
ui.Say("Exporting vm...")
|
||||
|
||||
err = driver.ExportVirtualMachine(vmName, vmExportPath)
|
||||
if err != nil {
|
||||
errorMsg = "Error exporting vm: %s"
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
err = driver.ExportVirtualMachine(vmName, vmExportPath)
|
||||
if err != nil {
|
||||
errorMsg = "Error exporting vm: %s"
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
// copy to output dir
|
||||
expPath = filepath.Join(vmExportPath, vmName)
|
||||
}
|
||||
|
||||
// copy to output dir
|
||||
expPath := filepath.Join(vmExportPath, vmName)
|
||||
|
||||
if s.SkipCompaction {
|
||||
ui.Say("Skipping disk compaction...")
|
||||
} else {
|
||||
|
@ -69,16 +70,17 @@ func (s *StepExportVm) Run(_ context.Context, state multistep.StateBag) multiste
|
|||
}
|
||||
}
|
||||
|
||||
ui.Say("Copying to output dir...")
|
||||
err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
|
||||
if err != nil {
|
||||
errorMsg = "Error exporting vm: %s"
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
if !s.SkipExport {
|
||||
ui.Say("Copying to output dir...")
|
||||
err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir)
|
||||
if err != nil {
|
||||
errorMsg = "Error exporting vm: %s"
|
||||
err := fmt.Errorf(errorMsg, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ type Config struct {
|
|||
BootCommand []string `mapstructure:"boot_command"`
|
||||
SwitchName string `mapstructure:"switch_name"`
|
||||
SwitchVlanId string `mapstructure:"switch_vlan_id"`
|
||||
MacAddress string `mapstructure:"mac_address"`
|
||||
VlanId string `mapstructure:"vlan_id"`
|
||||
Cpu uint `mapstructure:"cpu"`
|
||||
Generation uint `mapstructure:"generation"`
|
||||
|
@ -94,6 +95,8 @@ type Config struct {
|
|||
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
|
||||
// Use 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,
|
||||
AdditionalDiskSize: b.config.AdditionalDiskSize,
|
||||
DifferencingDisk: b.config.DifferencingDisk,
|
||||
SkipExport: b.config.SkipExport,
|
||||
OutputDir: b.config.OutputDir,
|
||||
},
|
||||
&hypervcommon.StepEnableIntegrationService{},
|
||||
|
||||
|
@ -422,6 +427,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&hypervcommon.StepExportVm{
|
||||
OutputDir: b.config.OutputDir,
|
||||
SkipCompaction: b.config.SkipCompaction,
|
||||
SkipExport: b.config.SkipExport,
|
||||
},
|
||||
|
||||
// the clean up actions for each step will be executed reverse order
|
||||
|
|
|
@ -82,6 +82,7 @@ type Config struct {
|
|||
BootCommand []string `mapstructure:"boot_command"`
|
||||
SwitchName string `mapstructure:"switch_name"`
|
||||
SwitchVlanId string `mapstructure:"switch_vlan_id"`
|
||||
MacAddress string `mapstructure:"mac_address"`
|
||||
VlanId string `mapstructure:"vlan_id"`
|
||||
Cpu uint `mapstructure:"cpu"`
|
||||
Generation uint
|
||||
|
@ -94,6 +95,8 @@ type Config struct {
|
|||
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
|
||||
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,
|
||||
EnableSecureBoot: b.config.EnableSecureBoot,
|
||||
EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions,
|
||||
MacAddress: b.config.MacAddress,
|
||||
},
|
||||
|
||||
&hypervcommon.StepEnableIntegrationService{},
|
||||
|
@ -469,6 +473,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&hypervcommon.StepExportVm{
|
||||
OutputDir: b.config.OutputDir,
|
||||
SkipCompaction: b.config.SkipCompaction,
|
||||
SkipExport: b.config.SkipExport,
|
||||
},
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&StepLoadExtensions{},
|
||||
&StepLoadFlavor{
|
||||
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 {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(Config)
|
||||
extensions := state.Get("extensions").(map[string]struct{})
|
||||
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
|
||||
client, err := config.computeV2Client()
|
||||
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 (
|
||||
"fmt"
|
||||
|
@ -8,7 +8,7 @@ import (
|
|||
"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)
|
||||
return ipAddress, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package oci
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -16,13 +16,13 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type stepKeyPair struct {
|
||||
type StepKeyPair struct {
|
||||
Debug bool
|
||||
DebugKeyPath 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)
|
||||
|
||||
if s.PrivateKeyFile != "" {
|
||||
|
@ -112,6 +112,6 @@ func (s *stepKeyPair) Run(_ context.Context, state multistep.StateBag) multistep
|
|||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepKeyPair) Cleanup(state multistep.StateBag) {
|
||||
func (s *StepKeyPair) Cleanup(state multistep.StateBag) {
|
||||
// Nothing to do
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
ocommon "github.com/hashicorp/packer/builder/oracle/common"
|
||||
client "github.com/hashicorp/packer/builder/oracle/oci/client"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"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
|
||||
steps := []multistep.Step{
|
||||
&stepKeyPair{
|
||||
&ocommon.StepKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("oci_%s.pem", b.config.PackerBuildName),
|
||||
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
|
||||
|
@ -59,8 +60,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&stepInstanceInfo{},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.Comm,
|
||||
Host: commHost,
|
||||
SSHConfig: SSHConfig(
|
||||
Host: ocommon.CommHost,
|
||||
SSHConfig: ocommon.SSHConfig(
|
||||
b.config.Comm.SSHUsername,
|
||||
b.config.Comm.SSHPassword),
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ var accels = map[string]struct{}{
|
|||
"kvm": {},
|
||||
"tcg": {},
|
||||
"xen": {},
|
||||
"hax": {},
|
||||
}
|
||||
|
||||
var netDevice = map[string]bool{
|
||||
|
@ -259,7 +260,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
if _, ok := accels[b.config.Accelerator]; !ok {
|
||||
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 {
|
||||
|
|
|
@ -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.
|
||||
url, err = common.DownloadableURL(url)
|
||||
url, err = common.ValidatedURL(url)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing guest additions url: %s", err)
|
||||
state.Put("error", err)
|
||||
|
|
|
@ -97,7 +97,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
if c.SourcePath == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
|
||||
} else {
|
||||
c.SourcePath, err = common.DownloadableURL(c.SourcePath)
|
||||
c.SourcePath, err = common.ValidatedURL(c.SourcePath)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
t.Fatalf("should error")
|
||||
}
|
||||
|
||||
// Good
|
||||
|
|
|
@ -2,13 +2,18 @@ package common
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
@ -24,15 +29,11 @@ type Driver interface {
|
|||
CompactDisk(string) error
|
||||
|
||||
// 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.
|
||||
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(string, bool) error
|
||||
|
||||
|
@ -49,14 +50,33 @@ type Driver interface {
|
|||
// Attach the VMware tools ISO
|
||||
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
|
||||
// properly. This should check that all the files it will use
|
||||
// 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
|
||||
|
||||
/// 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
|
||||
|
@ -192,3 +212,305 @@ func compareVersions(versionFound string, versionWanted string, product string)
|
|||
|
||||
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.
|
||||
type Fusion5Driver struct {
|
||||
VmwareDriver
|
||||
|
||||
// This is the path to the "VMware Fusion.app"
|
||||
AppPath string
|
||||
|
||||
|
@ -39,8 +41,8 @@ func (d *Fusion5Driver) CompactDisk(diskPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string, type_id string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
||||
func (d *Fusion5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
|
||||
cmd := exec.Command(d.vdiskManagerPath(), "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -139,6 +141,32 @@ func (d *Fusion5Driver) Verify() error {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -158,10 +186,6 @@ func (d *Fusion5Driver) ToolsInstall() error {
|
|||
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"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
|
@ -170,3 +194,7 @@ const fusionSuppressPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
<true/>
|
||||
</dict>
|
||||
</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])
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func (d *Fusion6Driver) GetVmwareDriver() VmwareDriver {
|
||||
return d.Fusion5Driver.VmwareDriver
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -18,11 +19,12 @@ type DriverMock struct {
|
|||
CompactDiskPath string
|
||||
CompactDiskErr error
|
||||
|
||||
CreateDiskCalled bool
|
||||
CreateDiskOutput string
|
||||
CreateDiskSize string
|
||||
CreateDiskTypeId string
|
||||
CreateDiskErr error
|
||||
CreateDiskCalled bool
|
||||
CreateDiskOutput string
|
||||
CreateDiskSize string
|
||||
CreateDiskAdapterType string
|
||||
CreateDiskTypeId string
|
||||
CreateDiskErr error
|
||||
|
||||
IsRunningCalled bool
|
||||
IsRunningPath string
|
||||
|
@ -34,6 +36,26 @@ type DriverMock struct {
|
|||
CommHostResult string
|
||||
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
|
||||
StartPath string
|
||||
StartHeadless bool
|
||||
|
@ -58,10 +80,33 @@ type DriverMock struct {
|
|||
DhcpLeasesPathDevice string
|
||||
DhcpLeasesPathResult string
|
||||
|
||||
DhcpConfPathCalled bool
|
||||
DhcpConfPathResult string
|
||||
|
||||
VmnetnatConfPathCalled bool
|
||||
VmnetnatConfPathResult string
|
||||
|
||||
NetmapConfPathCalled bool
|
||||
NetmapConfPathResult string
|
||||
|
||||
VerifyCalled bool
|
||||
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 {
|
||||
d.CloneCalled = true
|
||||
d.CloneDst = dst
|
||||
|
@ -75,10 +120,11 @@ func (d *DriverMock) CompactDisk(path string) error {
|
|||
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.CreateDiskOutput = output
|
||||
d.CreateDiskSize = size
|
||||
d.CreateDiskAdapterType = adapterType
|
||||
d.CreateDiskTypeId = typeId
|
||||
return d.CreateDiskErr
|
||||
}
|
||||
|
@ -98,6 +144,58 @@ func (d *DriverMock) CommHost(state multistep.StateBag) (string, error) {
|
|||
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 {
|
||||
d.StartCalled = true
|
||||
d.StartPath = path
|
||||
|
@ -134,7 +232,39 @@ func (d *DriverMock) DhcpLeasesPath(device string) string {
|
|||
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 {
|
||||
d.VerifyCalled = true
|
||||
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.
|
||||
type Player5Driver struct {
|
||||
VmwareDriver
|
||||
|
||||
AppPath string
|
||||
VdiskManagerPath string
|
||||
QemuImgPath string
|
||||
|
@ -62,12 +64,12 @@ func (d *Player5Driver) qemuCompactDisk(diskPath string) error {
|
|||
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
|
||||
if d.QemuImgPath != "" {
|
||||
cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size)
|
||||
} 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 {
|
||||
return err
|
||||
|
@ -181,6 +183,33 @@ func (d *Player5Driver) Verify() error {
|
|||
"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
|
||||
}
|
||||
|
||||
|
@ -192,10 +221,6 @@ func (d *Player5Driver) ToolsInstall() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Player5Driver) DhcpLeasesPath(device string) string {
|
||||
return playerDhcpLeasesPath(device)
|
||||
}
|
||||
|
||||
func (d *Player5Driver) VmnetnatConfPath() string {
|
||||
return playerVmnetnatConfPath()
|
||||
func (d *Player5Driver) GetVmwareDriver() VmwareDriver {
|
||||
return d.VmwareDriver
|
||||
}
|
||||
|
|
|
@ -57,14 +57,29 @@ func playerDhcpLeasesPath(device string) string {
|
|||
} else if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
func playerNetmapConfPath() string {
|
||||
return findFile("netmap.conf", playerDataFilePaths())
|
||||
}
|
||||
|
||||
// This reads the VMware installation path from the Windows registry.
|
||||
func playerVMwareRoot() (s string, err error) {
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -35,3 +35,7 @@ func (d *Player6Driver) Verify() error {
|
|||
|
||||
return playerVerifyVersion(VMWARE_PLAYER_VERSION)
|
||||
}
|
||||
|
||||
func (d *Player6Driver) GetVmwareDriver() VmwareDriver {
|
||||
return d.Player5Driver.VmwareDriver
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
@ -28,16 +29,49 @@ func playerFindVmrun() (string, error) {
|
|||
return exec.LookPath("vmrun")
|
||||
}
|
||||
|
||||
func playerDhcpLeasesPath(device string) string {
|
||||
return "/etc/vmware/" + device + "/dhcpd/dhcpd.leases"
|
||||
}
|
||||
|
||||
func playerToolsIsoPath(flavor string) string {
|
||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||
}
|
||||
|
||||
func playerVmnetnatConfPath() string {
|
||||
return ""
|
||||
// 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 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 {
|
||||
|
|
|
@ -33,3 +33,7 @@ func (d *Workstation10Driver) Verify() error {
|
|||
|
||||
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
|
||||
type Workstation9Driver struct {
|
||||
VmwareDriver
|
||||
|
||||
AppPath string
|
||||
VdiskManagerPath string
|
||||
VmrunPath string
|
||||
|
@ -40,8 +42,8 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) CreateDisk(output string, size string, type_id string) error {
|
||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", "lsilogic", "-t", type_id, output)
|
||||
func (d *Workstation9Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error {
|
||||
cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output)
|
||||
if _, _, err := runAndLog(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -142,6 +144,33 @@ func (d *Workstation9Driver) Verify() error {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -153,10 +182,6 @@ func (d *Workstation9Driver) ToolsInstall() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) DhcpLeasesPath(device string) string {
|
||||
return workstationDhcpLeasesPath(device)
|
||||
}
|
||||
|
||||
func (d *Workstation9Driver) VmnetnatConfPath() string {
|
||||
return workstationVmnetnatConfPath()
|
||||
func (d *Workstation9Driver) GetVmwareDriver() VmwareDriver {
|
||||
return d.VmwareDriver
|
||||
}
|
||||
|
|
|
@ -59,10 +59,20 @@ func workstationDhcpLeasesPath(device string) string {
|
|||
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())
|
||||
}
|
||||
|
||||
func workstationNetmapConfPath() string {
|
||||
return findFile("netmap.conf", workstationDataFilePaths())
|
||||
}
|
||||
|
||||
// See http://blog.natefinch.com/2012/11/go-win-stuff.html
|
||||
//
|
||||
// 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 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 {
|
||||
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 {
|
||||
return "/usr/lib/vmware/isoimages/" + flavor + ".iso"
|
||||
}
|
||||
|
||||
func workstationVmnetnatConfPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func workstationVerifyVersion(version string) error {
|
||||
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)
|
||||
|
|
|
@ -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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
commonssh "github.com/hashicorp/packer/common/ssh"
|
||||
"github.com/hashicorp/packer/communicator/ssh"
|
||||
|
@ -16,41 +14,12 @@ import (
|
|||
func CommHost(config *SSHConfig) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
driver := state.Get("driver").(Driver)
|
||||
vmxPath := state.Get("vmx_path").(string)
|
||||
|
||||
if config.Comm.SSHHost != "" {
|
||||
return config.Comm.SSHHost, nil
|
||||
}
|
||||
|
||||
log.Println("Lookup up IP information...")
|
||||
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()
|
||||
ipAddress, err := driver.GuestIP(state)
|
||||
if err != nil {
|
||||
log.Printf("IP lookup failed: %s", err)
|
||||
return "", fmt.Errorf("IP lookup failed: %s", err)
|
||||
|
|
|
@ -3,7 +3,6 @@ package common
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -26,7 +25,7 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult
|
|||
ui := state.Get("ui").(packer.Ui)
|
||||
vmxPath := state.Get("vmx_path").(string)
|
||||
|
||||
vmxContents, err := ioutil.ReadFile(vmxPath)
|
||||
vmxData, err := ReadVMX(vmxPath)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error reading VMX file: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -34,8 +33,6 @@ func (s *StepConfigureVMX) Run(_ context.Context, state multistep.StateBag) mult
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
vmxData := ParseVMX(string(vmxContents))
|
||||
|
||||
// Set this so that no dialogs ever appear from Packer.
|
||||
vmxData["msg.autoanswer"] = "true"
|
||||
|
||||
|
|
|
@ -3,11 +3,9 @@ package common
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"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)
|
||||
vmxPath := state.Get("vmx_path").(string)
|
||||
|
||||
f, err := os.Open(vmxPath)
|
||||
vmxData, err := ReadVMX(vmxPath)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error reading VMX data: %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)
|
||||
err := fmt.Errorf("Error reading VMX file: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
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)
|
||||
|
||||
vmxData := ParseVMX(string(vmxBytes))
|
||||
vncFinder.UpdateVMX(vncBindAddress, vncPassword, vncPort, vmxData)
|
||||
|
||||
if err := WriteVMX(vmxPath, vmxData); err != nil {
|
||||
|
|
|
@ -116,6 +116,10 @@ func TestStepShutdown_noCommand(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)
|
||||
step := new(StepShutdown)
|
||||
step.Testing = true
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
@ -63,7 +63,7 @@ func (s *StepTypeBootCommand) Run(_ context.Context, state multistep.StateBag) m
|
|||
}
|
||||
|
||||
// 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))
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
// Determine the host IP
|
||||
var ipFinder HostIPFinder
|
||||
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()
|
||||
hostIP, err := driver.HostIP(state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error detecting host IP: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -184,6 +175,8 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
special["<rightAlt>"] = 0xFFEA
|
||||
special["<rightCtrl>"] = 0xFFE4
|
||||
special["<rightShift>"] = 0xFFE2
|
||||
special["<leftSuper>"] = 0xFFEB
|
||||
special["<rightSuper>"] = 0xFFEC
|
||||
|
||||
shiftedChars := "~!@#$%^&*()_+{}|:\"<>?"
|
||||
|
||||
|
@ -194,6 +187,9 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
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.
|
||||
for len(original) > 0 {
|
||||
var keyCode uint32
|
||||
|
@ -232,6 +228,36 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
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>") {
|
||||
keyCode = special["<leftAlt>"]
|
||||
original = original[len("<leftAltOff>"):]
|
||||
|
@ -265,6 +291,36 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
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>") {
|
||||
keyCode = special["<rightAlt>"]
|
||||
original = original[len("<rightAltOn>"):]
|
||||
|
@ -298,6 +354,17 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
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>") {
|
||||
keyCode = special["<rightAlt>"]
|
||||
original = original[len("<rightAltOff>"):]
|
||||
|
@ -331,6 +398,17 @@ func vncSendString(c *vnc.ClientConn, original string) {
|
|||
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>") {
|
||||
log.Printf("Special code '<wait>' found, sleeping one second")
|
||||
time.Sleep(1 * time.Second)
|
||||
|
|
|
@ -38,21 +38,43 @@ type Config struct {
|
|||
vmwcommon.ToolsConfig `mapstructure:",squash"`
|
||||
vmwcommon.VMXConfig `mapstructure:",squash"`
|
||||
|
||||
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
DiskTypeId string `mapstructure:"disk_type_id"`
|
||||
Format string `mapstructure:"format"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
// disk drives
|
||||
AdditionalDiskSize []uint `mapstructure:"disk_additional_size"`
|
||||
DiskAdapterType string `mapstructure:"disk_adapter_type"`
|
||||
DiskName string `mapstructure:"vmdk_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
DiskTypeId string `mapstructure:"disk_type_id"`
|
||||
Format string `mapstructure:"format"`
|
||||
|
||||
// cdrom drive
|
||||
CdromAdapterType string `mapstructure:"cdrom_adapter_type"`
|
||||
|
||||
// platform information
|
||||
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"`
|
||||
OVFToolOptions []string `mapstructure:"ovftool_options"`
|
||||
SkipCompaction bool `mapstructure:"skip_compaction"`
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
|
||||
VMXTemplatePath string `mapstructure:"vmx_template_path"`
|
||||
Version string `mapstructure:"version"`
|
||||
|
||||
// remote vsphere
|
||||
RemoteType string `mapstructure:"remote_type"`
|
||||
RemoteDatastore string `mapstructure:"remote_datastore"`
|
||||
RemoteCacheDatastore string `mapstructure:"remote_cache_datastore"`
|
||||
|
@ -109,6 +131,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
b.config.DiskSize = 40000
|
||||
}
|
||||
|
||||
if b.config.DiskAdapterType == "" {
|
||||
// Default is lsilogic
|
||||
b.config.DiskAdapterType = "lsilogic"
|
||||
}
|
||||
|
||||
if b.config.DiskTypeId == "" {
|
||||
// Default is growable virtual disk split in 2GB files.
|
||||
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
|
||||
if b.config.RemoteType != "" {
|
||||
if b.config.RemoteHost == "" {
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
||||
commonssh "github.com/hashicorp/packer/common/ssh"
|
||||
"github.com/hashicorp/packer/communicator/ssh"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -24,6 +25,8 @@ import (
|
|||
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
|
||||
// virtual machines. This driver can only manage one machine at a time.
|
||||
type ESX5Driver struct {
|
||||
base vmwcommon.VmwareDriver
|
||||
|
||||
Host string
|
||||
Port uint
|
||||
Username string
|
||||
|
@ -46,9 +49,9 @@ func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
|
|||
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)
|
||||
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) {
|
||||
|
@ -143,10 +146,6 @@ func (d *ESX5Driver) ToolsInstall() error {
|
|||
return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) DhcpLeasesPath(string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) Verify() error {
|
||||
checks := []func() error{
|
||||
d.connect,
|
||||
|
@ -159,11 +158,10 @@ func (d *ESX5Driver) Verify() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -174,6 +172,101 @@ func (d *ESX5Driver) HostIP() (string, error) {
|
|||
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) {
|
||||
var vncPort uint
|
||||
|
||||
|
@ -531,6 +624,10 @@ func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
|
|||
return &esxcliReader{r, header}, nil
|
||||
}
|
||||
|
||||
func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver {
|
||||
return d.base
|
||||
}
|
||||
|
||||
type esxcliReader struct {
|
||||
cr *csv.Reader
|
||||
header []string
|
||||
|
|
|
@ -50,8 +50,9 @@ func TestESX5Driver_HostIP(t *testing.T) {
|
|||
defer listen.Close()
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func (stepCreateDisk) Run(_ context.Context, state multistep.StateBag) multistep
|
|||
|
||||
ui.Say("Creating virtual machine disk")
|
||||
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)
|
||||
state.Put("error", err)
|
||||
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))
|
||||
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)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -14,11 +16,40 @@ import (
|
|||
)
|
||||
|
||||
type vmxTemplateData struct {
|
||||
Name string
|
||||
GuestOS string
|
||||
DiskName string
|
||||
ISOPath string
|
||||
Version string
|
||||
Name string
|
||||
GuestOS string
|
||||
ISOPath 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 {
|
||||
|
@ -39,11 +70,238 @@ type stepCreateVMX struct {
|
|||
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 {
|
||||
config := state.Get("config").(*Config)
|
||||
isoPath := state.Get("iso_path").(string)
|
||||
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")
|
||||
|
||||
vmxTemplate := DefaultVMXTemplate
|
||||
|
@ -111,14 +369,211 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist
|
|||
}
|
||||
}
|
||||
|
||||
ctx.Data = &vmxTemplateData{
|
||||
templateData := vmxTemplateData{
|
||||
Name: config.VMName,
|
||||
GuestOS: config.GuestOSType,
|
||||
DiskName: config.DiskName,
|
||||
Version: config.Version,
|
||||
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)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error procesing VMX template: %s", err)
|
||||
|
@ -176,12 +631,13 @@ ehci.pciSlotNumber = "34"
|
|||
ehci.present = "TRUE"
|
||||
ethernet0.addressType = "generated"
|
||||
ethernet0.bsdName = "en0"
|
||||
ethernet0.connectionType = "nat"
|
||||
ethernet0.connectionType = "{{ .Network_Type }}"
|
||||
ethernet0.vnet = "{{ .Network_Device }}"
|
||||
ethernet0.displayName = "Ethernet"
|
||||
ethernet0.linkStatePropagation.enable = "FALSE"
|
||||
ethernet0.pciSlotNumber = "33"
|
||||
ethernet0.present = "TRUE"
|
||||
ethernet0.virtualDev = "e1000"
|
||||
ethernet0.virtualDev = "{{ .Network_Adapter }}"
|
||||
ethernet0.wakeOnPcktRcv = "FALSE"
|
||||
extendedConfigFile = "{{ .Name }}.vmxf"
|
||||
floppy0.present = "FALSE"
|
||||
|
@ -190,9 +646,21 @@ gui.fullScreenAtPowerOn = "FALSE"
|
|||
gui.viewModeAtPowerOn = "windowed"
|
||||
hgfs.linkRootShare = "TRUE"
|
||||
hgfs.mapRootShare = "TRUE"
|
||||
ide1:0.present = "TRUE"
|
||||
ide1:0.fileName = "{{ .ISOPath }}"
|
||||
ide1:0.deviceType = "cdrom-image"
|
||||
|
||||
scsi0.present = "{{ .SCSI_Present }}"
|
||||
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"
|
||||
memsize = "512"
|
||||
nvram = "{{ .Name }}.nvram"
|
||||
|
@ -221,17 +689,38 @@ powerType.suspend = "soft"
|
|||
proxyApps.publishToHost = "FALSE"
|
||||
replay.filename = ""
|
||||
replay.supported = "FALSE"
|
||||
scsi0.pciSlotNumber = "16"
|
||||
scsi0.present = "TRUE"
|
||||
scsi0.virtualDev = "lsilogic"
|
||||
scsi0:0.fileName = "{{ .DiskName }}.vmdk"
|
||||
scsi0:0.present = "TRUE"
|
||||
scsi0:0.redo = ""
|
||||
sound.startConnected = "FALSE"
|
||||
|
||||
// Sound
|
||||
sound.startConnected = "{{ .Sound_Present }}"
|
||||
sound.present = "{{ .Sound_Present }}"
|
||||
sound.fileName = "-1"
|
||||
sound.autodetect = "TRUE"
|
||||
|
||||
tools.syncTime = "TRUE"
|
||||
tools.upgrade.policy = "upgradeAtPowerCycle"
|
||||
|
||||
// USB
|
||||
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.version = "{{ .Version }}"
|
||||
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