Extract vagrant (#10960)

* remove vagrant, rework website

* regenerate command/plugin, and go mod tidy
This commit is contained in:
Megan Marsh 2021-04-21 13:31:28 -07:00 committed by GitHub
parent ccbd0a29cc
commit af37f53439
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 18 additions and 6331 deletions

View File

@ -1,57 +0,0 @@
package vagrant
import (
"fmt"
"path/filepath"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// This is the common builder ID to all of these artifacts.
const BuilderId = "vagrant"
// Artifact is the result of running the vagrant builder, namely a set
// of files associated with the resulting machine.
type artifact struct {
OutputDir string
BoxName string
Provider string
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
}
// NewArtifact returns a vagrant artifact containing the .box file
func NewArtifact(provider, dir string, generatedData map[string]interface{}) packersdk.Artifact {
return &artifact{
OutputDir: dir,
BoxName: "package.box",
Provider: provider,
StateData: generatedData,
}
}
func (*artifact) BuilderId() string {
return BuilderId
}
func (a *artifact) Files() []string {
return []string{filepath.Join(a.OutputDir, a.BoxName)}
}
func (a *artifact) Id() string {
return a.Provider
}
func (a *artifact) String() string {
return fmt.Sprintf("Vagrant box '%s' for '%s' provider", a.BoxName, a.Provider)
}
func (a *artifact) State(name string) interface{} {
return a.StateData[name]
}
func (a *artifact) Destroy() error {
return nil
}

View File

@ -1,72 +0,0 @@
package vagrant
import (
"runtime"
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_Impl(t *testing.T) {
var raw interface{} = &artifact{}
if _, ok := raw.(packersdk.Artifact); !ok {
t.Fatalf("Artifact does not implement packersdk.Artifact")
}
}
func TestArtifactId(t *testing.T) {
a := &artifact{
OutputDir: "/my/dir",
BoxName: "package.box",
Provider: "virtualbox",
}
expected := "virtualbox"
if a.Id() != expected {
t.Fatalf("artifact ID should match: expected: %s received: %s", expected, a.Id())
}
}
func TestArtifactString(t *testing.T) {
a := &artifact{
OutputDir: "/my/dir",
BoxName: "package.box",
Provider: "virtualbox",
}
expected := "Vagrant box 'package.box' for 'virtualbox' provider"
if runtime.GOOS == "windows" {
expected = strings.Replace(expected, "/", "\\", -1)
}
if strings.Compare(a.String(), expected) != 0 {
t.Fatalf("artifact string should match: expected: %s received: %s", expected, a.String())
}
}
func TestArtifactState(t *testing.T) {
expectedData := "this is the data"
a := &artifact{
StateData: map[string]interface{}{"state_data": expectedData},
}
// Valid state
result := a.State("state_data")
if result != expectedData {
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
}
// Invalid state
result = a.State("invalid_key")
if result != nil {
t.Fatalf("Bad: State should be nil for invalid state data name")
}
// Nil StateData should not fail and should return nil
a = &artifact{}
result = a.State("key")
if result != nil {
t.Fatalf("Bad: State should be nil for nil StateData")
}
}

View File

@ -1,378 +0,0 @@
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
package vagrant
import (
"context"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// Builder implements packersdk.Builder and builds the actual VirtualBox
// images.
type Builder struct {
config Config
runner multistep.Runner
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
commonsteps.HTTPConfig `mapstructure:",squash"`
commonsteps.ISOConfig `mapstructure:",squash"`
commonsteps.FloppyConfig `mapstructure:",squash"`
bootcommand.BootConfig `mapstructure:",squash"`
Comm communicator.Config `mapstructure:",squash"`
// The directory to create that will contain your output box. We always
// create this directory and run from inside of it to prevent Vagrant init
// collisions. If unset, it will be set to packer- plus your buildname.
OutputDir string `mapstructure:"output_dir" required:"false"`
// URL of the vagrant box to use, or the name of the vagrant box.
// hashicorp/precise64, ./mylocalbox.box and https://example.com/my-box.box
// are all valid source boxes. If your source is a .box file, whether
// locally or from a URL like the latter example above, you will also need
// to provide a box_name. This option is required, unless you set
// global_id. You may only set one or the other, not both.
SourceBox string `mapstructure:"source_path" required:"true"`
// the global id of a Vagrant box already added to Vagrant on your system.
// You can find the global id of your Vagrant boxes using the command
// vagrant global-status; your global_id will be a 7-digit number and
// letter comination that you'll find in the leftmost column of the
// global-status output. If you choose to use global_id instead of
// source_box, Packer will skip the Vagrant initialize and add steps, and
// simply launch the box directly using the global id.
GlobalID string `mapstructure:"global_id" required:"true"`
// The checksum for the .box file. The type of the checksum is specified
// within the checksum field as a prefix, ex: "md5:{$checksum}". The type
// of the checksum can also be omitted and Packer will try to infer it
// based on string length. Valid values are "none", "{$checksum}",
// "md5:{$checksum}", "sha1:{$checksum}", "sha256:{$checksum}",
// "sha512:{$checksum}" or "file:{$path}". Here is a list of valid checksum
// values:
// * md5:090992ba9fd140077b0661cb75f7ce13
// * 090992ba9fd140077b0661cb75f7ce13
// * sha1:ebfb681885ddf1234c18094a45bbeafd91467911
// * ebfb681885ddf1234c18094a45bbeafd91467911
// * sha256:ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93
// * ed363350696a726b7932db864dda019bd2017365c9e299627830f06954643f93
// * file:http://releases.ubuntu.com/20.04/SHA256SUMS
// * file:file://./local/path/file.sum
// * file:./local/path/file.sum
// * none
// Although the checksum will not be verified when it is set to "none",
// this is not recommended since these files can be very large and
// corruption does happen from time to time.
Checksum string `mapstructure:"checksum" required:"false"`
// if your source_box is a boxfile that we need to add to Vagrant, this is
// the name to give it. If left blank, will default to "packer_" plus your
// buildname.
BoxName string `mapstructure:"box_name" required:"false"`
// If true, Vagrant will automatically insert a keypair to use for SSH,
// replacing Vagrant's default insecure key inside the machine if detected.
// By default, Packer sets this to false.
InsertKey bool `mapstructure:"insert_key" required:"false"`
// The vagrant provider.
// This parameter is required when source_path have more than one provider,
// or when using vagrant-cloud post-processor. Defaults to unset.
Provider string `mapstructure:"provider" required:"false"`
// Options for the "vagrant init" command
// What vagrantfile to use
VagrantfileTpl string `mapstructure:"vagrantfile_template"`
// Whether to halt, suspend, or destroy the box when the build has
// completed. Defaults to "halt"
TeardownMethod string `mapstructure:"teardown_method" required:"false"`
// What box version to use when initializing Vagrant.
BoxVersion string `mapstructure:"box_version" required:"false"`
// a path to a golang template for a vagrantfile. Our default template can
// be found here. The template variables available to you are
// `{{ .BoxName }}`, `{{ .SyncedFolder }}`, and `{{.InsertKey}}`, which
// correspond to the Packer options box_name, synced_folder, and insert_key.
Template string `mapstructure:"template" required:"false"`
// Path to the folder to be synced to the guest. The path can be absolute
// or relative to the directory Packer is being run from.
SyncedFolder string `mapstructure:"synced_folder"`
// Don't call "vagrant add" to add the box to your local environment; this
// is necessary if you want to launch a box that is already added to your
// vagrant environment.
SkipAdd bool `mapstructure:"skip_add" required:"false"`
// Equivalent to setting the
// --cacert
// option in vagrant add; defaults to unset.
AddCACert string `mapstructure:"add_cacert" required:"false"`
// Equivalent to setting the
// --capath option
// in vagrant add; defaults to unset.
AddCAPath string `mapstructure:"add_capath" required:"false"`
// Equivalent to setting the
// --cert option in
// vagrant add; defaults to unset.
AddCert string `mapstructure:"add_cert" required:"false"`
// Equivalent to setting the
// --clean flag in
// vagrant add; defaults to unset.
AddClean bool `mapstructure:"add_clean" required:"false"`
// Equivalent to setting the
// --force flag in
// vagrant add; defaults to unset.
AddForce bool `mapstructure:"add_force" required:"false"`
// Equivalent to setting the
// --insecure flag in
// vagrant add; defaults to unset.
AddInsecure bool `mapstructure:"add_insecure" required:"false"`
// if true, Packer will not call vagrant package to
// package your base box into its own standalone .box file.
SkipPackage bool `mapstructure:"skip_package" required:"false"`
OutputVagrantfile string `mapstructure:"output_vagrantfile"`
// Equivalent to setting the
// [`--include`](https://www.vagrantup.com/docs/cli/package.html#include-x-y-z) option
// in `vagrant package`; defaults to unset
PackageInclude []string `mapstructure:"package_include"`
ctx interpolate.Context
}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
err := config.Decode(&b.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &b.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"boot_command",
},
},
}, raws...)
if err != nil {
return nil, nil, err
}
// Accumulate any errors and warnings
var errs *packersdk.MultiError
warnings := make([]string, 0)
if b.config.OutputDir == "" {
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
}
if b.config.Comm.SSHTimeout == 0 {
b.config.Comm.SSHTimeout = 10 * time.Minute
}
if b.config.Comm.Type != "ssh" {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf(`The Vagrant builder currently only supports the ssh communicator"`))
}
// The box isn't a namespace like you'd pull from vagrant cloud
if b.config.BoxName == "" {
b.config.BoxName = fmt.Sprintf("packer_%s", b.config.PackerBuildName)
}
if b.config.SourceBox == "" {
if b.config.GlobalID == "" {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("source_path is required unless you have set global_id"))
}
} else {
if b.config.GlobalID != "" {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("You may either set global_id or source_path but not both"))
}
// We're about to open up an actual boxfile. If the file is local to the
// filesystem, let's make sure it exists before we get too far into the
// build.
if strings.HasSuffix(b.config.SourceBox, ".box") {
// If scheme is "file" or empty, then we need to check the
// filesystem to make sure the box is present locally.
u, err := url.Parse(b.config.SourceBox)
if err == nil && (u.Scheme == "" || u.Scheme == "file") {
if _, err := os.Stat(b.config.SourceBox); err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("Source box '%s' needs to exist at time of"+
" config validation! %v", b.config.SourceBox, err))
}
}
}
}
if b.config.OutputVagrantfile != "" {
b.config.OutputVagrantfile, err = filepath.Abs(b.config.OutputVagrantfile)
if err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("unable to determine absolute path for output vagrantfile: %s", err))
}
}
if len(b.config.PackageInclude) > 0 {
for i, rawFile := range b.config.PackageInclude {
inclFile, err := filepath.Abs(rawFile)
if err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("unable to determine absolute path for file to be included: %s", rawFile))
}
b.config.PackageInclude[i] = inclFile
}
}
if b.config.TeardownMethod == "" {
// If we're using a box that's already opened on the system, don't
// automatically destroy it. If we open the box ourselves, then go ahead
// and kill it by default.
if b.config.GlobalID != "" {
b.config.TeardownMethod = "halt"
} else {
b.config.TeardownMethod = "destroy"
}
} else {
matches := false
for _, name := range []string{"halt", "suspend", "destroy"} {
if strings.ToLower(b.config.TeardownMethod) == name {
matches = true
}
}
if !matches {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf(`TeardownMethod must be "halt", "suspend", or "destroy"`))
}
}
if b.config.SyncedFolder != "" {
b.config.SyncedFolder, err = filepath.Abs(b.config.SyncedFolder)
if err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("unable to determine absolute path for synced_folder: %s", b.config.SyncedFolder))
}
if _, err := os.Stat(b.config.SyncedFolder); err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("synced_folder \"%s\" does not exist on the Packer host.", b.config.SyncedFolder))
}
}
if errs != nil && len(errs.Errors) > 0 {
return nil, warnings, errs
}
return nil, warnings, nil
}
// Run executes a Packer build and returns a packersdk.Artifact representing
// a VirtualBox appliance.
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) {
// Create the driver that we'll use to communicate with VirtualBox
VagrantCWD, err := filepath.Abs(b.config.OutputDir)
if err != nil {
return nil, err
}
driver, err := NewDriver(VagrantCWD)
if err != nil {
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
}
// Set up the state.
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("debug", b.config.PackerDebug)
state.Put("driver", driver)
state.Put("hook", hook)
state.Put("ui", ui)
// Build the steps.
steps := []multistep.Step{}
// Download if source box isn't from vagrant cloud.
if strings.HasSuffix(b.config.SourceBox, ".box") {
steps = append(steps, &commonsteps.StepDownload{
Checksum: b.config.Checksum,
Description: "Box",
Extension: "box",
ResultKey: "box_path",
Url: []string{b.config.SourceBox},
})
}
steps = append(steps,
&commonsteps.StepOutputDir{
Force: b.config.PackerForce,
Path: b.config.OutputDir,
},
&StepCreateVagrantfile{
Template: b.config.Template,
SyncedFolder: b.config.SyncedFolder,
SourceBox: b.config.SourceBox,
BoxName: b.config.BoxName,
OutputDir: b.config.OutputDir,
GlobalID: b.config.GlobalID,
InsertKey: b.config.InsertKey,
},
&StepAddBox{
BoxVersion: b.config.BoxVersion,
CACert: b.config.AddCACert,
CAPath: b.config.AddCAPath,
DownloadCert: b.config.AddCert,
Clean: b.config.AddClean,
Force: b.config.AddForce,
Insecure: b.config.AddInsecure,
Provider: b.config.Provider,
SourceBox: b.config.SourceBox,
BoxName: b.config.BoxName,
GlobalID: b.config.GlobalID,
SkipAdd: b.config.SkipAdd,
},
&StepUp{
TeardownMethod: b.config.TeardownMethod,
Provider: b.config.Provider,
GlobalID: b.config.GlobalID,
},
&StepSSHConfig{
b.config.GlobalID,
},
&communicator.StepConnect{
Config: &b.config.Comm,
Host: CommHost(),
SSHConfig: b.config.Comm.SSHConfigFunc(),
},
new(commonsteps.StepProvision),
&StepPackage{
SkipPackage: b.config.SkipPackage,
Include: b.config.PackageInclude,
Vagrantfile: b.config.OutputVagrantfile,
GlobalID: b.config.GlobalID,
})
// Run the steps.
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(ctx, state)
// Report any errors.
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.")
}
generatedData := map[string]interface{}{"generated_data": state.Get("generated_data")}
return NewArtifact(b.config.Provider, b.config.OutputDir, generatedData), nil
}
// Cancel.

View File

@ -1,221 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package vagrant
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory" hcl:"http_directory"`
HTTPContent map[string]string `mapstructure:"http_content" cty:"http_content" hcl:"http_content"`
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min" hcl:"http_port_min"`
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max" hcl:"http_port_max"`
HTTPAddress *string `mapstructure:"http_bind_address" cty:"http_bind_address" hcl:"http_bind_address"`
HTTPInterface *string `mapstructure:"http_interface" undocumented:"true" cty:"http_interface" hcl:"http_interface"`
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum" hcl:"iso_checksum"`
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url" hcl:"iso_url"`
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls" hcl:"iso_urls"`
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path" hcl:"iso_target_path"`
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension" hcl:"iso_target_extension"`
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port" hcl:"ssh_port"`
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username" hcl:"ssh_username"`
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password" hcl:"ssh_password"`
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" undocumented:"true" cty:"ssh_keypair_name" hcl:"ssh_keypair_name"`
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" undocumented:"true" cty:"temporary_key_pair_name" hcl:"temporary_key_pair_name"`
SSHTemporaryKeyPairType *string `mapstructure:"temporary_key_pair_type" cty:"temporary_key_pair_type" hcl:"temporary_key_pair_type"`
SSHTemporaryKeyPairBits *int `mapstructure:"temporary_key_pair_bits" cty:"temporary_key_pair_bits" hcl:"temporary_key_pair_bits"`
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
SSHKEXAlgos []string `mapstructure:"ssh_key_exchange_algorithms" cty:"ssh_key_exchange_algorithms" hcl:"ssh_key_exchange_algorithms"`
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" undocumented:"true" cty:"ssh_agent_auth" hcl:"ssh_agent_auth"`
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding" hcl:"ssh_disable_agent_forwarding"`
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts" hcl:"ssh_handshake_attempts"`
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host" hcl:"ssh_bastion_host"`
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port" hcl:"ssh_bastion_port"`
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth" hcl:"ssh_bastion_agent_auth"`
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username" hcl:"ssh_bastion_username"`
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username" hcl:"ssh_proxy_username"`
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password" hcl:"ssh_proxy_password"`
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval" hcl:"ssh_keep_alive_interval"`
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout" hcl:"ssh_read_write_timeout"`
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels" hcl:"ssh_remote_tunnels"`
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels" hcl:"ssh_local_tunnels"`
SSHPublicKey []byte `mapstructure:"ssh_public_key" undocumented:"true" cty:"ssh_public_key" hcl:"ssh_public_key"`
SSHPrivateKey []byte `mapstructure:"ssh_private_key" undocumented:"true" cty:"ssh_private_key" hcl:"ssh_private_key"`
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username" hcl:"winrm_username"`
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password" hcl:"winrm_password"`
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host" hcl:"winrm_host"`
WinRMNoProxy *bool `mapstructure:"winrm_no_proxy" cty:"winrm_no_proxy" hcl:"winrm_no_proxy"`
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port" hcl:"winrm_port"`
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout" hcl:"winrm_timeout"`
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl" hcl:"winrm_use_ssl"`
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"`
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"`
OutputDir *string `mapstructure:"output_dir" required:"false" cty:"output_dir" hcl:"output_dir"`
SourceBox *string `mapstructure:"source_path" required:"true" cty:"source_path" hcl:"source_path"`
GlobalID *string `mapstructure:"global_id" required:"true" cty:"global_id" hcl:"global_id"`
Checksum *string `mapstructure:"checksum" required:"false" cty:"checksum" hcl:"checksum"`
BoxName *string `mapstructure:"box_name" required:"false" cty:"box_name" hcl:"box_name"`
InsertKey *bool `mapstructure:"insert_key" required:"false" cty:"insert_key" hcl:"insert_key"`
Provider *string `mapstructure:"provider" required:"false" cty:"provider" hcl:"provider"`
VagrantfileTpl *string `mapstructure:"vagrantfile_template" cty:"vagrantfile_template" hcl:"vagrantfile_template"`
TeardownMethod *string `mapstructure:"teardown_method" required:"false" cty:"teardown_method" hcl:"teardown_method"`
BoxVersion *string `mapstructure:"box_version" required:"false" cty:"box_version" hcl:"box_version"`
Template *string `mapstructure:"template" required:"false" cty:"template" hcl:"template"`
SyncedFolder *string `mapstructure:"synced_folder" cty:"synced_folder" hcl:"synced_folder"`
SkipAdd *bool `mapstructure:"skip_add" required:"false" cty:"skip_add" hcl:"skip_add"`
AddCACert *string `mapstructure:"add_cacert" required:"false" cty:"add_cacert" hcl:"add_cacert"`
AddCAPath *string `mapstructure:"add_capath" required:"false" cty:"add_capath" hcl:"add_capath"`
AddCert *string `mapstructure:"add_cert" required:"false" cty:"add_cert" hcl:"add_cert"`
AddClean *bool `mapstructure:"add_clean" required:"false" cty:"add_clean" hcl:"add_clean"`
AddForce *bool `mapstructure:"add_force" required:"false" cty:"add_force" hcl:"add_force"`
AddInsecure *bool `mapstructure:"add_insecure" required:"false" cty:"add_insecure" hcl:"add_insecure"`
SkipPackage *bool `mapstructure:"skip_package" required:"false" cty:"skip_package" hcl:"skip_package"`
OutputVagrantfile *string `mapstructure:"output_vagrantfile" cty:"output_vagrantfile" hcl:"output_vagrantfile"`
PackageInclude []string `mapstructure:"package_include" cty:"package_include" hcl:"package_include"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"http_directory": &hcldec.AttrSpec{Name: "http_directory", Type: cty.String, Required: false},
"http_content": &hcldec.AttrSpec{Name: "http_content", Type: cty.Map(cty.String), Required: false},
"http_port_min": &hcldec.AttrSpec{Name: "http_port_min", Type: cty.Number, Required: false},
"http_port_max": &hcldec.AttrSpec{Name: "http_port_max", Type: cty.Number, Required: false},
"http_bind_address": &hcldec.AttrSpec{Name: "http_bind_address", Type: cty.String, Required: false},
"http_interface": &hcldec.AttrSpec{Name: "http_interface", Type: cty.String, Required: false},
"iso_checksum": &hcldec.AttrSpec{Name: "iso_checksum", Type: cty.String, Required: false},
"iso_url": &hcldec.AttrSpec{Name: "iso_url", Type: cty.String, Required: false},
"iso_urls": &hcldec.AttrSpec{Name: "iso_urls", Type: cty.List(cty.String), Required: false},
"iso_target_path": &hcldec.AttrSpec{Name: "iso_target_path", Type: cty.String, Required: false},
"iso_target_extension": &hcldec.AttrSpec{Name: "iso_target_extension", Type: cty.String, Required: false},
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
"ssh_port": &hcldec.AttrSpec{Name: "ssh_port", Type: cty.Number, Required: false},
"ssh_username": &hcldec.AttrSpec{Name: "ssh_username", Type: cty.String, Required: false},
"ssh_password": &hcldec.AttrSpec{Name: "ssh_password", Type: cty.String, Required: false},
"ssh_keypair_name": &hcldec.AttrSpec{Name: "ssh_keypair_name", Type: cty.String, Required: false},
"temporary_key_pair_name": &hcldec.AttrSpec{Name: "temporary_key_pair_name", Type: cty.String, Required: false},
"temporary_key_pair_type": &hcldec.AttrSpec{Name: "temporary_key_pair_type", Type: cty.String, Required: false},
"temporary_key_pair_bits": &hcldec.AttrSpec{Name: "temporary_key_pair_bits", Type: cty.Number, Required: false},
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
"ssh_key_exchange_algorithms": &hcldec.AttrSpec{Name: "ssh_key_exchange_algorithms", Type: cty.List(cty.String), Required: false},
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
"ssh_agent_auth": &hcldec.AttrSpec{Name: "ssh_agent_auth", Type: cty.Bool, Required: false},
"ssh_disable_agent_forwarding": &hcldec.AttrSpec{Name: "ssh_disable_agent_forwarding", Type: cty.Bool, Required: false},
"ssh_handshake_attempts": &hcldec.AttrSpec{Name: "ssh_handshake_attempts", Type: cty.Number, Required: false},
"ssh_bastion_host": &hcldec.AttrSpec{Name: "ssh_bastion_host", Type: cty.String, Required: false},
"ssh_bastion_port": &hcldec.AttrSpec{Name: "ssh_bastion_port", Type: cty.Number, Required: false},
"ssh_bastion_agent_auth": &hcldec.AttrSpec{Name: "ssh_bastion_agent_auth", Type: cty.Bool, Required: false},
"ssh_bastion_username": &hcldec.AttrSpec{Name: "ssh_bastion_username", Type: cty.String, Required: false},
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
"ssh_proxy_username": &hcldec.AttrSpec{Name: "ssh_proxy_username", Type: cty.String, Required: false},
"ssh_proxy_password": &hcldec.AttrSpec{Name: "ssh_proxy_password", Type: cty.String, Required: false},
"ssh_keep_alive_interval": &hcldec.AttrSpec{Name: "ssh_keep_alive_interval", Type: cty.String, Required: false},
"ssh_read_write_timeout": &hcldec.AttrSpec{Name: "ssh_read_write_timeout", Type: cty.String, Required: false},
"ssh_remote_tunnels": &hcldec.AttrSpec{Name: "ssh_remote_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_local_tunnels": &hcldec.AttrSpec{Name: "ssh_local_tunnels", Type: cty.List(cty.String), Required: false},
"ssh_public_key": &hcldec.AttrSpec{Name: "ssh_public_key", Type: cty.List(cty.Number), Required: false},
"ssh_private_key": &hcldec.AttrSpec{Name: "ssh_private_key", Type: cty.List(cty.Number), Required: false},
"winrm_username": &hcldec.AttrSpec{Name: "winrm_username", Type: cty.String, Required: false},
"winrm_password": &hcldec.AttrSpec{Name: "winrm_password", Type: cty.String, Required: false},
"winrm_host": &hcldec.AttrSpec{Name: "winrm_host", Type: cty.String, Required: false},
"winrm_no_proxy": &hcldec.AttrSpec{Name: "winrm_no_proxy", Type: cty.Bool, Required: false},
"winrm_port": &hcldec.AttrSpec{Name: "winrm_port", Type: cty.Number, Required: false},
"winrm_timeout": &hcldec.AttrSpec{Name: "winrm_timeout", Type: cty.String, Required: false},
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
"output_dir": &hcldec.AttrSpec{Name: "output_dir", Type: cty.String, Required: false},
"source_path": &hcldec.AttrSpec{Name: "source_path", Type: cty.String, Required: false},
"global_id": &hcldec.AttrSpec{Name: "global_id", Type: cty.String, Required: false},
"checksum": &hcldec.AttrSpec{Name: "checksum", Type: cty.String, Required: false},
"box_name": &hcldec.AttrSpec{Name: "box_name", Type: cty.String, Required: false},
"insert_key": &hcldec.AttrSpec{Name: "insert_key", Type: cty.Bool, Required: false},
"provider": &hcldec.AttrSpec{Name: "provider", Type: cty.String, Required: false},
"vagrantfile_template": &hcldec.AttrSpec{Name: "vagrantfile_template", Type: cty.String, Required: false},
"teardown_method": &hcldec.AttrSpec{Name: "teardown_method", Type: cty.String, Required: false},
"box_version": &hcldec.AttrSpec{Name: "box_version", Type: cty.String, Required: false},
"template": &hcldec.AttrSpec{Name: "template", Type: cty.String, Required: false},
"synced_folder": &hcldec.AttrSpec{Name: "synced_folder", Type: cty.String, Required: false},
"skip_add": &hcldec.AttrSpec{Name: "skip_add", Type: cty.Bool, Required: false},
"add_cacert": &hcldec.AttrSpec{Name: "add_cacert", Type: cty.String, Required: false},
"add_capath": &hcldec.AttrSpec{Name: "add_capath", Type: cty.String, Required: false},
"add_cert": &hcldec.AttrSpec{Name: "add_cert", Type: cty.String, Required: false},
"add_clean": &hcldec.AttrSpec{Name: "add_clean", Type: cty.Bool, Required: false},
"add_force": &hcldec.AttrSpec{Name: "add_force", Type: cty.Bool, Required: false},
"add_insecure": &hcldec.AttrSpec{Name: "add_insecure", Type: cty.Bool, Required: false},
"skip_package": &hcldec.AttrSpec{Name: "skip_package", Type: cty.Bool, Required: false},
"output_vagrantfile": &hcldec.AttrSpec{Name: "output_vagrantfile", Type: cty.String, Required: false},
"package_include": &hcldec.AttrSpec{Name: "package_include", Type: cty.List(cty.String), Required: false},
}
return s
}

View File

@ -1,130 +0,0 @@
package vagrant
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}
func TestBuilder_Prepare_ValidateSource(t *testing.T) {
type testCase struct {
config map[string]interface{}
errExpected bool
reason string
}
cases := []testCase{
{
config: map[string]interface{}{
"global_id": "a3559ec",
},
errExpected: true,
reason: "Need to set SSH communicator.",
},
{
config: map[string]interface{}{
"global_id": "a3559ec",
"communicator": "ssh",
},
errExpected: false,
reason: "Shouldn't fail because we've set global_id",
},
{
config: map[string]interface{}{
"communicator": "ssh",
},
errExpected: true,
reason: "Should fail because we must set source_path or global_id",
},
{
config: map[string]interface{}{
"source_path": "./mybox",
"communicator": "ssh",
},
errExpected: false,
reason: "Source path is set; we should be fine",
},
{
config: map[string]interface{}{
"source_path": "./mybox",
"communicator": "ssh",
"global_id": "a3559ec",
},
errExpected: true,
reason: "Both source path and global are set: we should error.",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"global_id": "a3559ec",
"teardown_method": "suspend",
},
errExpected: false,
reason: "Valid argument for teardown method",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"global_id": "a3559ec",
"teardown_method": "surspernd",
},
errExpected: true,
reason: "Inalid argument for teardown method",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"source_path": "./my.box",
},
errExpected: true,
reason: "Should fail because path does not exist",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"source_path": "file://my.box",
},
errExpected: true,
reason: "Should fail because path does not exist",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"source_path": "http://my.box",
},
errExpected: false,
reason: "Should pass because path is not local",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"source_path": "https://my.box",
},
errExpected: false,
reason: "Should pass because path is not local",
},
{
config: map[string]interface{}{
"communicator": "ssh",
"source_path": "smb://my.box",
},
errExpected: false,
reason: "Should pass because path is not local",
},
}
for _, tc := range cases {
_, _, err := (&Builder{}).Prepare(tc.config)
if (err != nil) != tc.errExpected {
t.Fatalf("Unexpected behavior from test case %#v; %s.", tc.config, tc.reason)
}
}
}

View File

@ -1,67 +0,0 @@
package vagrant
import (
"fmt"
"os/exec"
"runtime"
)
// A driver is able to talk to Vagrant and perform certain
// operations with it.
type VagrantDriver interface {
// Calls "vagrant init"
Init([]string) error
// Calls "vagrant add"
Add([]string) error
// Calls "vagrant up"
Up([]string) (string, string, error)
// Calls "vagrant halt"
Halt(string) error
// Calls "vagrant suspend"
Suspend(string) error
SSHConfig(string) (*VagrantSSHConfig, error)
// Calls "vagrant destroy"
Destroy(string) error
// Calls "vagrant package"[
Package([]string) error
// Verify checks to make sure that this driver should function
// properly. If there is any indication the driver can't function,
// this will return an error.
Verify() error
// Version reads the version of VirtualBox that is installed.
Version() (string, error)
}
func NewDriver(outputDir string) (VagrantDriver, error) {
// Hardcode path for now while I'm developing. Obviously this path needs
// to be discovered based on OS.
vagrantBinary := "vagrant"
if runtime.GOOS == "windows" {
vagrantBinary = "vagrant.exe"
}
if _, err := exec.LookPath(vagrantBinary); err != nil {
return nil, fmt.Errorf("Error: Packer cannot find Vagrant in the path: %s", err.Error())
}
driver := &Vagrant_2_2_Driver{
vagrantBinary: vagrantBinary,
VagrantCWD: outputDir,
}
if err := driver.Verify(); err != nil {
return nil, err
}
return driver, nil
}

View File

@ -1,276 +0,0 @@
package vagrant
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"os/exec"
"regexp"
"strings"
"github.com/hashicorp/go-version"
)
const VAGRANT_MIN_VERSION = ">= 2.0.2"
type Vagrant_2_2_Driver struct {
vagrantBinary string
VagrantCWD string
}
// Calls "vagrant init"
func (d *Vagrant_2_2_Driver) Init(args []string) error {
_, _, err := d.vagrantCmd(append([]string{"init"}, args...)...)
return err
}
// Calls "vagrant add"
func (d *Vagrant_2_2_Driver) Add(args []string) error {
// vagrant box add partyvm ubuntu-14.04.vmware.box
_, _, err := d.vagrantCmd(append([]string{"box", "add"}, args...)...)
return err
}
// Calls "vagrant up"
func (d *Vagrant_2_2_Driver) Up(args []string) (string, string, error) {
stdout, stderr, err := d.vagrantCmd(append([]string{"up"}, args...)...)
return stdout, stderr, err
}
// Calls "vagrant halt"
func (d *Vagrant_2_2_Driver) Halt(id string) error {
args := []string{"halt"}
if id != "" {
args = append(args, id)
}
_, _, err := d.vagrantCmd(args...)
return err
}
// Calls "vagrant suspend"
func (d *Vagrant_2_2_Driver) Suspend(id string) error {
args := []string{"suspend"}
if id != "" {
args = append(args, id)
}
_, _, err := d.vagrantCmd(args...)
return err
}
// Calls "vagrant destroy"
func (d *Vagrant_2_2_Driver) Destroy(id string) error {
args := []string{"destroy", "-f"}
if id != "" {
args = append(args, id)
}
_, _, err := d.vagrantCmd(args...)
return err
}
// Calls "vagrant package"
func (d *Vagrant_2_2_Driver) Package(args []string) error {
// Ideally we'd pass vagrantCWD into the package command but
// we have to change directory into the vagrant cwd instead in order to
// work around an upstream bug with the vagrant-libvirt plugin.
// We can stop doing this when
// https://github.com/vagrant-libvirt/vagrant-libvirt/issues/765
// is fixed.
oldDir, _ := os.Getwd()
os.Chdir(d.VagrantCWD)
defer os.Chdir(oldDir)
args = append(args, "--output", "package.box")
_, _, err := d.vagrantCmd(append([]string{"package"}, args...)...)
return err
}
// Verify makes sure that Vagrant exists at the given path
func (d *Vagrant_2_2_Driver) Verify() error {
vagrantPath, err := exec.LookPath(d.vagrantBinary)
if err != nil {
return fmt.Errorf("Can't find Vagrant binary!")
}
_, err = os.Stat(vagrantPath)
if err != nil {
return fmt.Errorf("Can't find Vagrant binary.")
}
constraints, err := version.NewConstraint(VAGRANT_MIN_VERSION)
if err != nil {
return fmt.Errorf("error parsing vagrant minimum version: %v", err)
}
vers, err := d.Version()
if err != nil {
return fmt.Errorf("error getting virtualbox version: %v", err)
}
v, err := version.NewVersion(vers)
if err != nil {
return fmt.Errorf("Error figuring out Vagrant version.")
}
if !constraints.Check(v) {
return fmt.Errorf("installed Vagrant version must be >=2.0.2")
}
return nil
}
type VagrantSSHConfig struct {
Hostname string
User string
Port string
UserKnownHostsFile string
StrictHostKeyChecking bool
PasswordAuthentication bool
IdentityFile string
IdentitiesOnly bool
LogLevel string
}
func parseSSHConfig(lines []string, value string) string {
out := ""
for _, line := range lines {
if index := strings.Index(line, value); index != -1 {
out = line[index+len(value):]
}
}
return strings.Trim(out, "\r\n")
}
func yesno(yn string) bool {
if yn == "no" {
return false
}
return true
}
func (d *Vagrant_2_2_Driver) SSHConfig(id string) (*VagrantSSHConfig, error) {
// vagrant ssh-config --host 8df7860
args := []string{"ssh-config"}
if id != "" {
args = append(args, id)
}
sshConf := &VagrantSSHConfig{}
stdout, stderr, err := d.vagrantCmd(args...)
if err != nil {
if stderr != "" {
err = fmt.Errorf("ssh-config command returned errors: %s", stderr)
}
return sshConf, err
}
lines := strings.Split(stdout, "\n")
sshConf.Hostname = parseSSHConfig(lines, "HostName ")
sshConf.User = parseSSHConfig(lines, "User ")
sshConf.Port = parseSSHConfig(lines, "Port ")
if sshConf.Port == "" {
err := fmt.Errorf("error: SSH Port was not properly retrieved from SSHConfig.")
return sshConf, err
}
sshConf.UserKnownHostsFile = parseSSHConfig(lines, "UserKnownHostsFile ")
sshConf.IdentityFile = parseSSHConfig(lines, "IdentityFile ")
sshConf.LogLevel = parseSSHConfig(lines, "LogLevel ")
// handle the booleans
sshConf.StrictHostKeyChecking = yesno(parseSSHConfig(lines, "StrictHostKeyChecking "))
sshConf.PasswordAuthentication = yesno(parseSSHConfig(lines, "PasswordAuthentication "))
sshConf.IdentitiesOnly = yesno((parseSSHConfig(lines, "IdentitiesOnly ")))
return sshConf, err
}
// Version reads the version of VirtualBox that is installed.
func (d *Vagrant_2_2_Driver) Version() (string, error) {
stdoutString, _, err := d.vagrantCmd([]string{"--version"}...)
// Example stdout:
// Installed Version: 2.2.3
//
// Vagrant was unable to check for the latest version of Vagrant.
// Please check manually at https://www.vagrantup.com
// Use regex to find version
reg := regexp.MustCompile(`(\d+\.)?(\d+\.)?(\*|\d+)`)
version := reg.FindString(stdoutString)
if version == "" {
return "", err
}
return version, nil
}
// Copied and modified from Bufio; this will return data that contains a
// carriage return, not just data that contains a newline.
// This allows us to stream progress output from vagrant that would otherwise
// be smothered. It is a bit noisy, but probably prefereable to suppressing
// the output in a way that looks like Packer has hung.
func ScanLinesInclCR(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
// We have a full newline-terminated line.
return i + 1, data[0:i], nil
}
if i := bytes.IndexByte(data, '\r'); i >= 0 {
// We have a CR-terminated line.
return i + 1, data[0:i], nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
func (d *Vagrant_2_2_Driver) vagrantCmd(args ...string) (string, string, error) {
log.Printf("Calling Vagrant CLI: %#v", args)
cmd := exec.Command(d.vagrantBinary, args...)
cmd.Env = append(os.Environ(), fmt.Sprintf("VAGRANT_CWD=%s", d.VagrantCWD))
stderr, err := cmd.StderrPipe()
if err != nil {
log.Printf("error getting err pipe")
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Printf("error getting out pipe")
}
err = cmd.Start()
if err != nil {
return "", "", fmt.Errorf("Error starting vagrant command with args: %q",
strings.Join(args, " "))
}
stdoutString := ""
stderrString := ""
scanOut := bufio.NewScanner(stdout)
scanOut.Split(ScanLinesInclCR)
scanErr := bufio.NewScanner(stderr)
scanErr.Split(ScanLinesInclCR)
go func() {
for scanErr.Scan() {
line := scanErr.Text()
log.Printf("[vagrant driver] stderr: %s", line)
stderrString += line + "\n"
}
}()
for scanOut.Scan() {
line := scanOut.Text()
log.Printf("[vagrant driver] stdout: %s", line)
stdoutString += line + "\n"
}
cmd.Wait()
if _, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("Vagrant error: %s", stderrString)
}
return stdoutString, stderrString, err
}

View File

@ -1,88 +0,0 @@
package vagrant
// Create a mock driver so that we can test Vagrant builder steps
type MockVagrantDriver struct {
InitCalled bool
AddCalled bool
UpCalled bool
HaltCalled bool
SuspendCalled bool
SSHConfigCalled bool
DestroyCalled bool
PackageCalled bool
VerifyCalled bool
VersionCalled bool
ReturnError error
ReturnSSHConfig *VagrantSSHConfig
GlobalID string
}
func (d *MockVagrantDriver) Init([]string) error {
d.InitCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) Add([]string) error {
d.AddCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) Up([]string) (string, string, error) {
d.UpCalled = true
return "", "", nil
}
func (d *MockVagrantDriver) Halt(string) error {
d.HaltCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) Suspend(string) error {
d.SuspendCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) SSHConfig(gid string) (*VagrantSSHConfig, error) {
d.SSHConfigCalled = true
// track the input value
d.GlobalID = gid
if d.ReturnSSHConfig != nil {
return d.ReturnSSHConfig, nil
}
sshConfig := VagrantSSHConfig{
Hostname: "127.0.0.1",
User: "vagrant",
Port: "2222",
UserKnownHostsFile: "/dev/null",
StrictHostKeyChecking: false,
PasswordAuthentication: false,
IdentityFile: "\"/path with spaces/insecure_private_key\"",
IdentitiesOnly: true,
LogLevel: "FATAL"}
return &sshConfig, d.ReturnError
}
func (d *MockVagrantDriver) Destroy(string) error {
d.DestroyCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) Package([]string) error {
d.PackageCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) Verify() error {
d.VerifyCalled = true
return d.ReturnError
}
func (d *MockVagrantDriver) Version() (string, error) {
d.VersionCalled = true
return "", d.ReturnError
}
// End of mock definition

View File

@ -1,19 +0,0 @@
package vagrant
import (
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func CommHost() func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
config := state.Get("config").(*Config)
return config.Comm.SSHHost, nil
}
}
func SSHPort() func(multistep.StateBag) (int, error) {
return func(state multistep.StateBag) (int, error) {
config := state.Get("config").(*Config)
return config.Comm.SSHPort, nil
}
}

View File

@ -1,101 +0,0 @@
package vagrant
import (
"context"
"log"
"strings"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepAddBox struct {
BoxVersion string
CACert string
CAPath string
DownloadCert string
Clean bool
Force bool
Insecure bool
Provider string
SourceBox string
BoxName string
GlobalID string
SkipAdd bool
}
func (s *StepAddBox) generateAddArgs() []string {
addArgs := []string{}
if strings.HasSuffix(s.SourceBox, ".box") {
addArgs = append(addArgs, s.BoxName)
}
addArgs = append(addArgs, s.SourceBox)
if s.BoxVersion != "" {
addArgs = append(addArgs, "--box-version", s.BoxVersion)
}
if s.CACert != "" {
addArgs = append(addArgs, "--cacert", s.CACert)
}
if s.CAPath != "" {
addArgs = append(addArgs, "--capath", s.CAPath)
}
if s.DownloadCert != "" {
addArgs = append(addArgs, "--cert", s.DownloadCert)
}
if s.Clean {
addArgs = append(addArgs, "--clean")
}
if s.Force {
addArgs = append(addArgs, "--force")
}
if s.Insecure {
addArgs = append(addArgs, "--insecure")
}
if s.Provider != "" {
addArgs = append(addArgs, "--provider", s.Provider)
}
return addArgs
}
func (s *StepAddBox) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packersdk.Ui)
if s.SkipAdd {
ui.Say("skip_add was set so we assume the box is already in Vagrant...")
return multistep.ActionContinue
}
if s.GlobalID != "" {
ui.Say("Using a global-id; skipping Vagrant add command...")
return multistep.ActionContinue
}
ui.Say("Adding box using vagrant box add ...")
ui.Message("(this can take some time if we need to download the box)")
addArgs := s.generateAddArgs()
log.Printf("[vagrant] Calling box add with following args %s", strings.Join(addArgs, " "))
// Call vagrant using prepared arguments
err := driver.Add(addArgs)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepAddBox) Cleanup(state multistep.StateBag) {
}

View File

@ -1,62 +0,0 @@
package vagrant
import (
"strings"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepAdd_Impl(t *testing.T) {
var raw interface{}
raw = new(StepAddBox)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("initialize should be a step")
}
}
func TestPrepAddArgs(t *testing.T) {
type testArgs struct {
Step StepAddBox
Expected []string
}
addTests := []testArgs{
{
Step: StepAddBox{
SourceBox: "my_source_box.box",
BoxName: "AWESOME BOX",
},
Expected: []string{"AWESOME BOX", "my_source_box.box"},
},
{
Step: StepAddBox{
SourceBox: "my_source_box",
BoxName: "AWESOME BOX",
},
Expected: []string{"my_source_box"},
},
{
Step: StepAddBox{
BoxVersion: "eleventyone",
CACert: "adfasdf",
CAPath: "adfasdf",
DownloadCert: "adfasdf",
Clean: true,
Force: true,
Insecure: true,
Provider: "virtualbox",
SourceBox: "bananabox.box",
BoxName: "bananas",
},
Expected: []string{"bananas", "bananabox.box", "--box-version", "eleventyone", "--cacert", "adfasdf", "--capath", "adfasdf", "--cert", "adfasdf", "--clean", "--force", "--insecure", "--provider", "virtualbox"},
},
}
for _, addTest := range addTests {
addArgs := addTest.Step.generateAddArgs()
for i, val := range addTest.Expected {
if strings.Compare(addArgs[i], val) != 0 {
t.Fatalf("expected %#v but received %#v", addTest.Expected, addArgs)
}
}
}
}

View File

@ -1,110 +0,0 @@
package vagrant
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"text/template"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepCreateVagrantfile struct {
Template string
OutputDir string
SyncedFolder string
GlobalID string
SourceBox string
BoxName string
InsertKey bool
}
var DEFAULT_TEMPLATE = `Vagrant.configure("2") do |config|
config.vm.define "source", autostart: false do |source|
source.vm.box = "{{.SourceBox}}"
config.ssh.insert_key = {{.InsertKey}}
end
config.vm.define "output" do |output|
output.vm.box = "{{.BoxName}}"
output.vm.box_url = "file://package.box"
config.ssh.insert_key = {{.InsertKey}}
end
{{ if ne .SyncedFolder "" -}}
config.vm.synced_folder "{{.SyncedFolder}}", "/vagrant"
{{- else -}}
config.vm.synced_folder ".", "/vagrant", disabled: true
{{- end}}
end`
type VagrantfileOptions struct {
SyncedFolder string
SourceBox string
BoxName string
InsertKey bool
}
func (s *StepCreateVagrantfile) createVagrantfile() (string, error) {
tplPath := filepath.Join(s.OutputDir, "Vagrantfile")
templateFile, err := os.Create(tplPath)
if err != nil {
retErr := fmt.Errorf("Error creating vagrantfile %s", err.Error())
return "", retErr
}
var tpl *template.Template
if s.Template == "" {
// Generate vagrantfile template based on our default
tpl = template.Must(template.New("VagrantTpl").Parse(DEFAULT_TEMPLATE))
} else {
// Read in the template from provided file.
tpl, err = template.ParseFiles(s.Template)
if err != nil {
return "", err
}
}
opts := &VagrantfileOptions{
SyncedFolder: s.SyncedFolder,
BoxName: s.BoxName,
SourceBox: s.SourceBox,
InsertKey: s.InsertKey,
}
err = tpl.Execute(templateFile, opts)
if err != nil {
return "", err
}
abspath, err := filepath.Abs(tplPath)
if err != nil {
return "", err
}
return abspath, nil
}
func (s *StepCreateVagrantfile) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
// Skip the initialize step if we're trying to launch from a global ID.
if s.GlobalID != "" {
ui.Say("Using a global-id; skipping Vagrant init in this directory...")
return multistep.ActionContinue
}
ui.Say("Creating a Vagrantfile in the build directory...")
vagrantfilePath, err := s.createVagrantfile()
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
log.Printf("Created vagrantfile at %s", vagrantfilePath)
return multistep.ActionContinue
}
func (s *StepCreateVagrantfile) Cleanup(state multistep.StateBag) {
}

View File

@ -1,115 +0,0 @@
package vagrant
import (
"io/ioutil"
"os"
"strings"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepCreateVagrantfile_Impl(t *testing.T) {
var raw interface{}
raw = new(StepCreateVagrantfile)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("initialize should be a step")
}
}
func TestCreateFile(t *testing.T) {
testy := StepCreateVagrantfile{
OutputDir: "./",
SourceBox: "apples",
BoxName: "bananas",
}
templatePath, err := testy.createVagrantfile()
if err != nil {
t.Fatalf(err.Error())
}
defer os.Remove(templatePath)
contents, err := ioutil.ReadFile(templatePath)
if err != nil {
t.Fatalf(err.Error())
}
actual := string(contents)
expected := `Vagrant.configure("2") do |config|
config.vm.define "source", autostart: false do |source|
source.vm.box = "apples"
config.ssh.insert_key = false
end
config.vm.define "output" do |output|
output.vm.box = "bananas"
output.vm.box_url = "file://package.box"
config.ssh.insert_key = false
end
config.vm.synced_folder ".", "/vagrant", disabled: true
end`
if ok := strings.Compare(actual, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
}
}
func TestCreateFile_customSync(t *testing.T) {
testy := StepCreateVagrantfile{
OutputDir: "./",
SyncedFolder: "myfolder/foldertimes",
}
templatePath, err := testy.createVagrantfile()
if err != nil {
t.Fatalf(err.Error())
}
defer os.Remove(templatePath)
contents, err := ioutil.ReadFile(templatePath)
if err != nil {
t.Fatalf(err.Error())
}
actual := string(contents)
expected := `Vagrant.configure("2") do |config|
config.vm.define "source", autostart: false do |source|
source.vm.box = ""
config.ssh.insert_key = false
end
config.vm.define "output" do |output|
output.vm.box = ""
output.vm.box_url = "file://package.box"
config.ssh.insert_key = false
end
config.vm.synced_folder "myfolder/foldertimes", "/vagrant"
end`
if ok := strings.Compare(actual, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
}
}
func TestCreateFile_InsertKeyTrue(t *testing.T) {
testy := StepCreateVagrantfile{
OutputDir: "./",
InsertKey: true,
}
templatePath, err := testy.createVagrantfile()
if err != nil {
t.Fatalf(err.Error())
}
defer os.Remove(templatePath)
contents, err := ioutil.ReadFile(templatePath)
if err != nil {
t.Fatalf(err.Error())
}
actual := string(contents)
expected := `Vagrant.configure("2") do |config|
config.vm.define "source", autostart: false do |source|
source.vm.box = ""
config.ssh.insert_key = true
end
config.vm.define "output" do |output|
output.vm.box = ""
output.vm.box_url = "file://package.box"
config.ssh.insert_key = true
end
config.vm.synced_folder ".", "/vagrant", disabled: true
end`
if ok := strings.Compare(actual, expected); ok != 0 {
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
}
}

View File

@ -1,52 +0,0 @@
package vagrant
import (
"context"
"strings"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepPackage struct {
SkipPackage bool
Include []string
Vagrantfile string
GlobalID string
}
func (s *StepPackage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packersdk.Ui)
if s.SkipPackage {
ui.Say("skip_package flag set; not going to call Vagrant package on this box.")
return multistep.ActionContinue
}
ui.Say("Packaging box...")
packageArgs := []string{}
box := "source"
if s.GlobalID != "" {
box = s.GlobalID
}
packageArgs = append(packageArgs, box)
if len(s.Include) > 0 {
packageArgs = append(packageArgs, "--include", strings.Join(s.Include, ","))
}
if s.Vagrantfile != "" {
packageArgs = append(packageArgs, "--vagrantfile", s.Vagrantfile)
}
err := driver.Package(packageArgs)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepPackage) Cleanup(state multistep.StateBag) {
}

View File

@ -1,76 +0,0 @@
package vagrant
import (
"context"
"log"
"strconv"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
// Vagrant already sets up ssh on the guests; our job is to find out what
// it did. We can do that with the ssh-config command. Example output:
// $ vagrant ssh-config
// Host default
// HostName 172.16.41.194
// User vagrant
// Port 22
// UserKnownHostsFile /dev/null
// StrictHostKeyChecking no
// PasswordAuthentication no
// IdentityFile /Users/mmarsh/Projects/vagrant-boxes/ubuntu/.vagrant/machines/default/vmware_fusion/private_key
// IdentitiesOnly yes
// LogLevel FATAL
type StepSSHConfig struct {
GlobalID string
}
func (s *StepSSHConfig) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
config := state.Get("config").(*Config)
box := "source"
if s.GlobalID != "" {
box = s.GlobalID
}
sshConfig, err := driver.SSHConfig(box)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
if config.Comm.SSHHost == "" {
config.Comm.SSHHost = sshConfig.Hostname
}
if config.Comm.SSHPort == 0 {
port, err := strconv.Atoi(sshConfig.Port)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
config.Comm.SSHPort = port
}
if config.Comm.SSHUsername != "" {
// If user has set the username within the communicator, use the
// username, password, and/or keyfile auth provided there.
log.Printf("Overriding SSH config from Vagrant with the username, " +
"password, and private key information provided to the Packer template.")
return multistep.ActionContinue
}
log.Printf("identity file is %s", sshConfig.IdentityFile)
log.Printf("Removing quotes from identity file")
unquoted, err := strconv.Unquote(sshConfig.IdentityFile)
if err == nil {
sshConfig.IdentityFile = unquoted
}
config.Comm.SSHPrivateKeyFile = sshConfig.IdentityFile
config.Comm.SSHUsername = sshConfig.User
return multistep.ActionContinue
}
func (s *StepSSHConfig) Cleanup(state multistep.StateBag) {
}

View File

@ -1,176 +0,0 @@
package vagrant
import (
"context"
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepSSHConfig_Impl(t *testing.T) {
var raw interface{}
raw = new(StepSSHConfig)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("initialize should be a step")
}
}
func TestPrepStepSSHConfig_sshOverrides(t *testing.T) {
type testcase struct {
name string
inputSSHConfig communicator.SSH
expectedSSHConfig communicator.SSH
}
tcs := []testcase{
{
// defaults to overriding with the ssh config from vagrant\
name: "default",
inputSSHConfig: communicator.SSH{},
expectedSSHConfig: communicator.SSH{
SSHHost: "127.0.0.1",
SSHPort: 2222,
SSHUsername: "vagrant",
SSHPassword: "",
},
},
{
// respects SSH host and port overrides independent of credential
// overrides
name: "host_override",
inputSSHConfig: communicator.SSH{
SSHHost: "123.45.67.8",
SSHPort: 1234,
},
expectedSSHConfig: communicator.SSH{
SSHHost: "123.45.67.8",
SSHPort: 1234,
SSHUsername: "vagrant",
SSHPassword: "",
},
},
{
// respects credential overrides
name: "credential_override",
inputSSHConfig: communicator.SSH{
SSHUsername: "megan",
SSHPassword: "SoSecure",
},
expectedSSHConfig: communicator.SSH{
SSHHost: "127.0.0.1",
SSHPort: 2222,
SSHUsername: "megan",
SSHPassword: "SoSecure",
},
},
}
for _, tc := range tcs {
driver := &MockVagrantDriver{}
config := &Config{
Comm: communicator.Config{
SSH: tc.inputSSHConfig,
},
}
state := new(multistep.BasicStateBag)
state.Put("driver", driver)
state.Put("config", config)
step := StepSSHConfig{}
_ = step.Run(context.Background(), state)
if config.Comm.SSHHost != tc.expectedSSHConfig.SSHHost {
t.Fatalf("unexpected sshconfig host: name: %s, recieved %s", tc.name, config.Comm.SSHHost)
}
if config.Comm.SSHPort != tc.expectedSSHConfig.SSHPort {
t.Fatalf("unexpected sshconfig port: name: %s, recieved %d", tc.name, config.Comm.SSHPort)
}
if config.Comm.SSHUsername != tc.expectedSSHConfig.SSHUsername {
t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHUsername)
}
if config.Comm.SSHPassword != tc.expectedSSHConfig.SSHPassword {
t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHPassword)
}
}
}
func TestPrepStepSSHConfig_GlobalID(t *testing.T) {
driver := &MockVagrantDriver{}
config := &Config{}
state := new(multistep.BasicStateBag)
state.Put("driver", driver)
state.Put("config", config)
step := StepSSHConfig{
GlobalID: "adsfadf",
}
_ = step.Run(context.Background(), state)
if driver.GlobalID != "adsfadf" {
t.Fatalf("Should have called SSHConfig with GlobalID asdfasdf")
}
}
func TestPrepStepSSHConfig_NoGlobalID(t *testing.T) {
driver := &MockVagrantDriver{}
config := &Config{}
state := new(multistep.BasicStateBag)
state.Put("driver", driver)
state.Put("config", config)
step := StepSSHConfig{}
_ = step.Run(context.Background(), state)
if driver.GlobalID != "source" {
t.Fatalf("Should have called SSHConfig with GlobalID source")
}
}
func TestPrepStepSSHConfig_SpacesInPath(t *testing.T) {
driver := &MockVagrantDriver{}
driver.ReturnSSHConfig = &VagrantSSHConfig{
Hostname: "127.0.0.1",
User: "vagrant",
Port: "2222",
UserKnownHostsFile: "/dev/null",
StrictHostKeyChecking: false,
PasswordAuthentication: false,
IdentityFile: "\"/path with spaces/insecure_private_key\"",
IdentitiesOnly: true,
LogLevel: "FATAL"}
config := &Config{}
state := new(multistep.BasicStateBag)
state.Put("driver", driver)
state.Put("config", config)
step := StepSSHConfig{}
_ = step.Run(context.Background(), state)
expected := "/path with spaces/insecure_private_key"
if config.Comm.SSHPrivateKeyFile != expected {
t.Fatalf("Bad config private key. Recieved: %s; expected: %s.", config.Comm.SSHPrivateKeyFile, expected)
}
}
func TestPrepStepSSHConfig_NoSpacesInPath(t *testing.T) {
driver := &MockVagrantDriver{}
driver.ReturnSSHConfig = &VagrantSSHConfig{
Hostname: "127.0.0.1",
User: "vagrant",
Port: "2222",
UserKnownHostsFile: "/dev/null",
StrictHostKeyChecking: false,
PasswordAuthentication: false,
IdentityFile: "/path/without/spaces/insecure_private_key",
IdentitiesOnly: true,
LogLevel: "FATAL"}
config := &Config{}
state := new(multistep.BasicStateBag)
state.Put("driver", driver)
state.Put("config", config)
step := StepSSHConfig{}
_ = step.Run(context.Background(), state)
expected := "/path/without/spaces/insecure_private_key"
if config.Comm.SSHPrivateKeyFile != expected {
t.Fatalf("Bad config private key. Recieved: %s; expected: %s.", config.Comm.SSHPrivateKeyFile, expected)
}
}

View File

@ -1,77 +0,0 @@
package vagrant
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepUp struct {
TeardownMethod string
Provider string
GlobalID string
}
func (s *StepUp) generateArgs() []string {
box := "source"
if s.GlobalID != "" {
box = s.GlobalID
}
// start only the source box
args := []string{box}
if s.Provider != "" {
args = append(args, fmt.Sprintf("--provider=%s", s.Provider))
}
return args
}
func (s *StepUp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packersdk.Ui)
ui.Say("Calling Vagrant Up (this can take some time)...")
args := s.generateArgs()
// instance_id is the generic term used so that users can have access to the
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", args[0])
_, _, err := driver.Up(args)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepUp) Cleanup(state multistep.StateBag) {
driver := state.Get("driver").(VagrantDriver)
ui := state.Get("ui").(packersdk.Ui)
ui.Say(fmt.Sprintf("%sing Vagrant box...", s.TeardownMethod))
box := "source"
if s.GlobalID != "" {
box = s.GlobalID
}
var err error
if s.TeardownMethod == "halt" {
err = driver.Halt(box)
} else if s.TeardownMethod == "suspend" {
err = driver.Suspend(box)
} else if s.TeardownMethod == "destroy" {
err = driver.Destroy(box)
} else {
// Should never get here because of template validation
state.Put("error", fmt.Errorf("Invalid teardown method selected; must be either halt, suspend, or destroy."))
}
if err != nil {
state.Put("error", fmt.Errorf("Error halting Vagrant machine; please try to do this manually"))
}
}

View File

@ -1,40 +0,0 @@
package vagrant
import (
"strings"
"testing"
)
func TestPrepUpArgs(t *testing.T) {
type testArgs struct {
Step StepUp
Expected []string
}
tests := []testArgs{
{
Step: StepUp{
GlobalID: "foo",
Provider: "bar",
},
Expected: []string{"foo", "--provider=bar"},
},
{
Step: StepUp{},
Expected: []string{"source"},
},
{
Step: StepUp{
Provider: "pro",
},
Expected: []string{"source", "--provider=pro"},
},
}
for _, test := range tests {
args := test.Step.generateArgs()
for i, val := range test.Expected {
if strings.Compare(args[i], val) != 0 {
t.Fatalf("expected %#v but received %#v", test.Expected, args)
}
}
}
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var VagrantPluginVersion *version.PluginVersion
func init() {
VagrantPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -28,7 +28,6 @@ import (
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
tritonbuilder "github.com/hashicorp/packer/builder/triton"
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
yandexbuilder "github.com/hashicorp/packer/builder/yandex"
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
@ -36,8 +35,6 @@ import (
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest"
shelllocalpostprocessor "github.com/hashicorp/packer/post-processor/shell-local"
vagrantpostprocessor "github.com/hashicorp/packer/post-processor/vagrant"
vagrantcloudpostprocessor "github.com/hashicorp/packer/post-processor/vagrant-cloud"
yandexexportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-export"
yandeximportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-import"
azuredtlartifactprovisioner "github.com/hashicorp/packer/provisioner/azure-dtlartifact"
@ -74,7 +71,6 @@ var Builders = map[string]packersdk.Builder{
"profitbricks": new(profitbricksbuilder.Builder),
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
"triton": new(tritonbuilder.Builder),
"vagrant": new(vagrantbuilder.Builder),
"yandex": new(yandexbuilder.Builder),
}
@ -100,8 +96,6 @@ var PostProcessors = map[string]packersdk.PostProcessor{
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
"manifest": new(manifestpostprocessor.PostProcessor),
"shell-local": new(shelllocalpostprocessor.PostProcessor),
"vagrant": new(vagrantpostprocessor.PostProcessor),
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
"yandex-export": new(yandexexportpostprocessor.PostProcessor),
"yandex-import": new(yandeximportpostprocessor.PostProcessor),
}

View File

@ -51,6 +51,9 @@ import (
scalewaybuilder "github.com/hashicorp/packer-plugin-scaleway/builder/scaleway"
uclouduhostbuilder "github.com/hashicorp/packer-plugin-ucloud/builder/ucloud/uhost"
ucloudimportpostprocessor "github.com/hashicorp/packer-plugin-ucloud/post-processor/ucloud-import"
vagrantbuilder "github.com/hashicorp/packer-plugin-vagrant/builder/vagrant"
vagrantpostprocessor "github.com/hashicorp/packer-plugin-vagrant/post-processor/vagrant"
vagrantcloudpostprocessor "github.com/hashicorp/packer-plugin-vagrant/post-processor/vagrant-cloud"
virtualboxisobuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/iso"
virtualboxovfbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/ovf"
virtualboxvmbuilder "github.com/hashicorp/packer-plugin-virtualbox/builder/virtualbox/vm"
@ -96,6 +99,7 @@ var VendoredBuilders = map[string]packersdk.Builder{
"qemu": new(qemubuilder.Builder),
"scaleway": new(scalewaybuilder.Builder),
"ucloud-uhost": new(uclouduhostbuilder.Builder),
"vagrant": new(vagrantbuilder.Builder),
"vsphere-clone": new(vsphereclonebuilder.Builder),
"vsphere-iso": new(vsphereisobuilder.Builder),
"virtualbox-iso": new(virtualboxisobuilder.Builder),
@ -133,6 +137,8 @@ var VendoredPostProcessors = map[string]packersdk.PostProcessor{
"googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor),
"googlecompute-import": new(googlecomputeimportpostprocessor.PostProcessor),
"ucloud-import": new(ucloudimportpostprocessor.PostProcessor),
"vagrant": new(vagrantpostprocessor.PostProcessor),
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
"vsphere-template": new(vspheretemplatepostprocessor.PostProcessor),
"vsphere": new(vspherepostprocessor.PostProcessor),
}

1
go.mod
View File

@ -58,6 +58,7 @@ require (
github.com/hashicorp/packer-plugin-scaleway v0.0.1
github.com/hashicorp/packer-plugin-sdk v0.2.0
github.com/hashicorp/packer-plugin-ucloud v0.0.1
github.com/hashicorp/packer-plugin-vagrant v0.0.3
github.com/hashicorp/packer-plugin-virtualbox v0.0.1
github.com/hashicorp/packer-plugin-vmware v0.0.1
github.com/hashicorp/packer-plugin-vsphere v0.0.1

5
go.sum
View File

@ -545,6 +545,8 @@ github.com/hashicorp/packer-plugin-sdk v0.2.0 h1:A4Dq7p4y1vscY4gMzp7GQaXyDJYYhP4
github.com/hashicorp/packer-plugin-sdk v0.2.0/go.mod h1:0DiOMEBldmB0HEhp0npFSSygC8bIvW43pphEgWkp2WU=
github.com/hashicorp/packer-plugin-ucloud v0.0.1 h1:SC2F1BuXb6dKhY6fRdmAqTkuc17jlBIu/Ut0URJy8TU=
github.com/hashicorp/packer-plugin-ucloud v0.0.1/go.mod h1:xyMMmi/UPqFV3GT4eeX7wIqdoncNyrNuvdylnEQl1RU=
github.com/hashicorp/packer-plugin-vagrant v0.0.3 h1:BT8l8PM6TBawBRuhpvyuA4QSW9/FvE7rhGgVaiRpXz8=
github.com/hashicorp/packer-plugin-vagrant v0.0.3/go.mod h1:eEXMqtPg8V3aaDO3ZhZ+NB7Yii6eairrEvTFLJaEV4k=
github.com/hashicorp/packer-plugin-virtualbox v0.0.1 h1:vTfy7a10RUVMdNnDLo0EQrCVbAG4rGWkaDTMC7MVBi4=
github.com/hashicorp/packer-plugin-virtualbox v0.0.1/go.mod h1:OOGNMK8Y8zjsYngesZH5kCbH0Fj8PKvhqPp8w1ejM3Y=
github.com/hashicorp/packer-plugin-vmware v0.0.1 h1:jRQAdjHwg3zeCBb52KoZsuxugrHcQhjgQln72o9eGgM=
@ -603,8 +605,9 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=

View File

@ -1,43 +0,0 @@
package vagrantcloud
import (
"fmt"
)
const BuilderId = "pearkes.post-processor.vagrant-cloud"
type Artifact struct {
Tag string
Provider string
}
func NewArtifact(provider, tag string) *Artifact {
return &Artifact{
Tag: tag,
Provider: provider,
}
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return nil
}
func (a *Artifact) Id() string {
return ""
}
func (a *Artifact) String() string {
return fmt.Sprintf("'%s': %s", a.Provider, a.Tag)
}
func (*Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
return nil
}

View File

@ -1,15 +0,0 @@
package vagrantcloud
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_ImplementsArtifact(t *testing.T) {
var raw interface{}
raw = &Artifact{}
if _, ok := raw.(packersdk.Artifact); !ok {
t.Fatalf("Artifact should be a Artifact")
}
}

View File

@ -1,255 +0,0 @@
package vagrantcloud
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"github.com/hashicorp/packer-plugin-sdk/net"
)
type VagrantCloudClient struct {
// The http client for communicating
client *http.Client
// The base URL of the API
BaseURL string
// Access token
AccessToken string
}
type VagrantCloudErrors struct {
Errors []interface{} `json:"errors"`
}
func (v VagrantCloudErrors) FormatErrors() string {
errs := make([]string, 0)
for _, err := range v.Errors {
switch e := err.(type) {
case string:
errs = append(errs, e)
case map[string]interface{}:
for k, v := range e {
errs = append(errs, fmt.Sprintf("%s %s", k, v))
}
default:
errs = append(errs, fmt.Sprintf("%s", err))
}
}
return strings.Join(errs, ". ")
}
func (v VagrantCloudClient) New(baseUrl string, token string, InsecureSkipTLSVerify bool) (*VagrantCloudClient, error) {
c := &VagrantCloudClient{
client: net.HttpClientWithEnvironmentProxy(),
BaseURL: baseUrl,
AccessToken: token,
}
if InsecureSkipTLSVerify {
transport := c.client.Transport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
return c, c.ValidateAuthentication()
}
func decodeBody(resp *http.Response, out interface{}) error {
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
return dec.Decode(out)
}
// encodeBody is used to encode a request body
func encodeBody(obj interface{}) (io.Reader, error) {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
if err := enc.Encode(obj); err != nil {
return nil, err
}
return buf, nil
}
func (v *VagrantCloudClient) ValidateAuthentication() error {
resp, err := v.Get("authenticate")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf(resp.Status)
}
return nil
}
func (v *VagrantCloudClient) Get(path string) (*http.Response, error) {
reqUrl := fmt.Sprintf("%s/%s", v.BaseURL, path)
log.Printf("Post-Processor Vagrant Cloud API GET: %s", reqUrl)
req, err := v.newRequest("GET", reqUrl, nil)
if err != nil {
return nil, err
}
resp, err := v.client.Do(req)
log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) Delete(path string) (*http.Response, error) {
reqUrl := fmt.Sprintf("%s/%s", v.BaseURL, path)
// Scrub API key for logs
scrubbedUrl := strings.Replace(reqUrl, v.AccessToken, "ACCESS_TOKEN", -1)
log.Printf("Post-Processor Vagrant Cloud API DELETE: %s", scrubbedUrl)
req, err := v.newRequest("DELETE", reqUrl, nil)
if err != nil {
return nil, err
}
resp, err := v.client.Do(req)
log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) Upload(path string, url string) (*http.Response, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Error opening file for upload: %s", err)
}
fi, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("Error stating file for upload: %s", err)
}
defer file.Close()
request, err := v.newRequest("PUT", url, file)
if err != nil {
return nil, fmt.Errorf("Error preparing upload request: %s", err)
}
log.Printf("Post-Processor Vagrant Cloud API Upload: %s %s", path, url)
request.ContentLength = fi.Size()
resp, err := v.client.Do(request)
log.Printf("Post-Processor Vagrant Cloud Upload Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) DirectUpload(path string, url string) (*http.Response, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Error opening file for upload: %s", err)
}
defer file.Close()
fi, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("Error stating file for upload: %s", err)
}
request, err := http.NewRequest("PUT", url, file)
if err != nil {
return nil, fmt.Errorf("Error preparing upload request: %s", err)
}
log.Printf("Post-Processor Vagrant Cloud API Direct Upload: %s %s", path, url)
request.ContentLength = fi.Size()
resp, err := v.client.Do(request)
log.Printf("Post-Processor Vagrant Cloud Direct Upload Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) Callback(url string) (*http.Response, error) {
request, err := v.newRequest("PUT", url, nil)
if err != nil {
return nil, fmt.Errorf("Error preparing callback request: %s", err)
}
log.Printf("Post-Processor Vagrant Cloud API Direct Upload Callback: %s", url)
resp, err := v.client.Do(request)
log.Printf("Post-Processor Vagrant Cloud Direct Upload Callback Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) Post(path string, body interface{}) (*http.Response, error) {
reqUrl := fmt.Sprintf("%s/%s", v.BaseURL, path)
encBody, err := encodeBody(body)
if err != nil {
return nil, fmt.Errorf("Error encoding body for request: %s", err)
}
log.Printf("Post-Processor Vagrant Cloud API POST: %s. \n\n Body: %s", reqUrl, encBody)
req, err := v.newRequest("POST", reqUrl, encBody)
if err != nil {
return nil, err
}
resp, err := v.client.Do(req)
log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) Put(path string) (*http.Response, error) {
reqUrl := fmt.Sprintf("%s/%s", v.BaseURL, path)
log.Printf("Post-Processor Vagrant Cloud API PUT: %s", reqUrl)
req, err := v.newRequest("PUT", reqUrl, nil)
if err != nil {
return nil, err
}
resp, err := v.client.Do(req)
log.Printf("Post-Processor Vagrant Cloud API Response: \n\n%+v", resp)
return resp, err
}
func (v *VagrantCloudClient) newRequest(method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/json")
if len(v.AccessToken) > 0 {
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", v.AccessToken))
}
return req, err
}

View File

@ -1,30 +0,0 @@
package vagrantcloud
import (
"encoding/json"
"strings"
"testing"
)
func TestVagranCloudErrors(t *testing.T) {
testCases := []struct {
resp string
expected string
}{
{`{"Status":"422 Unprocessable Entity", "StatusCode":422, "errors":[]}`, ""},
{`{"Status":"404 Artifact not found", "StatusCode":404, "errors":["error1", "error2"]}`, "error1. error2"},
{`{"StatusCode":403, "errors":[{"message":"Bad credentials"}]}`, "message Bad credentials"},
{`{"StatusCode":500, "errors":[["error in unexpected format"]]}`, "[error in unexpected format]"},
}
for _, tc := range testCases {
var cloudErrors VagrantCloudErrors
err := json.NewDecoder(strings.NewReader(tc.resp)).Decode(&cloudErrors)
if err != nil {
t.Errorf("failed to decode error response: %s", err)
}
if got := cloudErrors.FormatErrors(); got != tc.expected {
t.Errorf("failed to get expected response; expected %q, got %q", tc.expected, got)
}
}
}

View File

@ -1,298 +0,0 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
// vagrant_cloud implements the packersdk.PostProcessor interface and adds a
// post-processor that uploads artifacts from the vagrant post-processor
// and vagrant builder to Vagrant Cloud (vagrantcloud.com) or manages
// self hosted boxes on the Vagrant Cloud
package vagrantcloud
import (
"archive/tar"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
var builtins = map[string]string{
"mitchellh.post-processor.vagrant": "vagrant",
"packer.post-processor.artifice": "artifice",
"vagrant": "vagrant",
}
const VAGRANT_CLOUD_URL = "https://vagrantcloud.com/api/v1"
type Config struct {
common.PackerConfig `mapstructure:",squash"`
Tag string `mapstructure:"box_tag"`
Version string `mapstructure:"version"`
VersionDescription string `mapstructure:"version_description"`
NoRelease bool `mapstructure:"no_release"`
AccessToken string `mapstructure:"access_token"`
VagrantCloudUrl string `mapstructure:"vagrant_cloud_url"`
InsecureSkipTLSVerify bool `mapstructure:"insecure_skip_tls_verify"`
BoxDownloadUrl string `mapstructure:"box_download_url"`
NoDirectUpload bool `mapstructure:"no_direct_upload"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
client *VagrantCloudClient
runner multistep.Runner
warnAtlasToken bool
insecureSkipTLSVerify bool
}
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
PluginType: BuilderId,
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"box_download_url",
},
},
}, raws...)
if err != nil {
return err
}
// Default configuration
if p.config.VagrantCloudUrl == "" {
p.config.VagrantCloudUrl = VAGRANT_CLOUD_URL
}
p.insecureSkipTLSVerify = p.config.InsecureSkipTLSVerify == true && p.config.VagrantCloudUrl != VAGRANT_CLOUD_URL
if p.config.AccessToken == "" {
envToken := os.Getenv("VAGRANT_CLOUD_TOKEN")
if envToken == "" {
envToken = os.Getenv("ATLAS_TOKEN")
if envToken != "" {
p.warnAtlasToken = true
}
}
p.config.AccessToken = envToken
}
// Accumulate any errors
errs := new(packersdk.MultiError)
// Required configuration
templates := map[string]*string{
"box_tag": &p.config.Tag,
"version": &p.config.Version,
}
for key, ptr := range templates {
if *ptr == "" {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
if p.config.VagrantCloudUrl == VAGRANT_CLOUD_URL && p.config.AccessToken == "" {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("access_token must be set if vagrant_cloud_url has not been overridden"))
}
// Create the HTTP client
p.client, err = VagrantCloudClient{}.New(p.config.VagrantCloudUrl, p.config.AccessToken, p.insecureSkipTLSVerify)
if err != nil {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("Failed to verify authentication token: %v", err))
}
if len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
if _, ok := builtins[artifact.BuilderId()]; !ok {
return nil, false, false, fmt.Errorf(
"Unknown artifact type: this post-processor requires an input artifact from the artifice post-processor, vagrant post-processor, or vagrant builder: %s", artifact.BuilderId())
}
// We assume that there is only one .box file to upload
if !strings.HasSuffix(artifact.Files()[0], ".box") {
return nil, false, false, fmt.Errorf(
"Unknown files in artifact, Vagrant box with .box suffix is required as first artifact file: %s", artifact.Files())
}
if p.warnAtlasToken {
ui.Message("Warning: Using Vagrant Cloud token found in ATLAS_TOKEN. Please make sure it is correct, or set VAGRANT_CLOUD_TOKEN")
}
// Determine the name of the provider for Vagrant Cloud, and Vagrant
providerName, err := getProvider(artifact.Id(), artifact.Files()[0], builtins[artifact.BuilderId()])
if err != nil {
return nil, false, false, fmt.Errorf("error getting provider name: %s", err)
}
var generatedData map[interface{}]interface{}
stateData := artifact.State("generated_data")
if stateData != nil {
// Make sure it's not a nil map so we can assign to it later.
generatedData = stateData.(map[interface{}]interface{})
}
// If stateData has a nil map generatedData will be nil
// and we need to make sure it's not
if generatedData == nil {
generatedData = make(map[interface{}]interface{})
}
generatedData["ArtifactId"] = artifact.Id()
generatedData["Provider"] = providerName
p.config.ctx.Data = generatedData
boxDownloadUrl, err := interpolate.Render(p.config.BoxDownloadUrl, &p.config.ctx)
if err != nil {
return nil, false, false, fmt.Errorf("Error processing box_download_url: %s", err)
}
// Set up the state
state := new(multistep.BasicStateBag)
state.Put("config", &p.config)
state.Put("client", p.client)
state.Put("artifact", artifact)
state.Put("artifactFilePath", artifact.Files()[0])
state.Put("ui", ui)
state.Put("providerName", providerName)
state.Put("boxDownloadUrl", boxDownloadUrl)
// Build the steps
steps := []multistep.Step{
new(stepVerifyBox),
new(stepCreateVersion),
new(stepCreateProvider),
}
if p.config.BoxDownloadUrl == "" {
steps = append(steps,
new(stepPrepareUpload),
new(stepUpload),
new(stepConfirmUpload))
}
steps = append(steps, new(stepReleaseVersion))
// Run the steps
p.runner = commonsteps.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, false, false, rawErr.(error)
}
return NewArtifact(providerName, p.config.Tag), true, false, nil
}
func getProvider(builderName, boxfile, builderId string) (providerName string, err error) {
if builderId == "artifice" {
// The artifice post processor cannot embed any data in the
// supplied artifact so the provider information must be extracted
// from the box file directly
providerName, err = providerFromVagrantBox(boxfile)
} else {
// For the Vagrant builder and Vagrant post processor the provider can
// be determined from information embedded in the artifact
providerName = providerFromBuilderName(builderName)
}
return providerName, err
}
// Converts a packer builder name to the corresponding vagrant provider
func providerFromBuilderName(name string) string {
switch name {
case "aws":
return "aws"
case "scaleway":
return "scaleway"
case "digitalocean":
return "digitalocean"
case "virtualbox":
return "virtualbox"
case "vmware":
return "vmware_desktop"
case "parallels":
return "parallels"
default:
return name
}
}
// Returns the Vagrant provider the box is intended for use with by
// reading the metadata file packaged inside the box
func providerFromVagrantBox(boxfile string) (providerName string, err error) {
log.Println("Attempting to determine provider from metadata in box file. This may take some time...")
f, err := os.Open(boxfile)
if err != nil {
return "", fmt.Errorf("Error attempting to open box file: %s", err)
}
defer f.Close()
// Vagrant boxes are gzipped tar archives
ar, err := gzip.NewReader(f)
if err != nil {
return "", fmt.Errorf("Error unzipping box archive: %s", err)
}
tr := tar.NewReader(ar)
// The metadata.json file in the tar archive contains a 'provider' key
type metadata struct {
ProviderName string `json:"provider"`
}
md := metadata{}
// Loop through the files in the archive and read the provider
// information from the boxes metadata.json file
for {
hdr, err := tr.Next()
if err == io.EOF {
if md.ProviderName == "" {
return "", fmt.Errorf("Error: Provider info was not found in box: %s", boxfile)
}
break
}
if err != nil {
return "", fmt.Errorf("Error reading header info from box tar archive: %s", err)
}
if hdr.Name == "metadata.json" {
contents, err := ioutil.ReadAll(tr)
if err != nil {
return "", fmt.Errorf("Error reading contents of metadata.json file from box file: %s", err)
}
err = json.Unmarshal(contents, &md)
if err != nil {
return "", fmt.Errorf("Error parsing metadata.json file: %s", err)
}
if md.ProviderName == "" {
return "", fmt.Errorf("Error: Could not determine Vagrant provider from box metadata.json file")
}
break
}
}
return md.ProviderName, nil
}

View File

@ -1,63 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package vagrantcloud
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
Tag *string `mapstructure:"box_tag" cty:"box_tag" hcl:"box_tag"`
Version *string `mapstructure:"version" cty:"version" hcl:"version"`
VersionDescription *string `mapstructure:"version_description" cty:"version_description" hcl:"version_description"`
NoRelease *bool `mapstructure:"no_release" cty:"no_release" hcl:"no_release"`
AccessToken *string `mapstructure:"access_token" cty:"access_token" hcl:"access_token"`
VagrantCloudUrl *string `mapstructure:"vagrant_cloud_url" cty:"vagrant_cloud_url" hcl:"vagrant_cloud_url"`
InsecureSkipTLSVerify *bool `mapstructure:"insecure_skip_tls_verify" cty:"insecure_skip_tls_verify" hcl:"insecure_skip_tls_verify"`
BoxDownloadUrl *string `mapstructure:"box_download_url" cty:"box_download_url" hcl:"box_download_url"`
NoDirectUpload *bool `mapstructure:"no_direct_upload" cty:"no_direct_upload" hcl:"no_direct_upload"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"box_tag": &hcldec.AttrSpec{Name: "box_tag", Type: cty.String, Required: false},
"version": &hcldec.AttrSpec{Name: "version", Type: cty.String, Required: false},
"version_description": &hcldec.AttrSpec{Name: "version_description", Type: cty.String, Required: false},
"no_release": &hcldec.AttrSpec{Name: "no_release", Type: cty.Bool, Required: false},
"access_token": &hcldec.AttrSpec{Name: "access_token", Type: cty.String, Required: false},
"vagrant_cloud_url": &hcldec.AttrSpec{Name: "vagrant_cloud_url", Type: cty.String, Required: false},
"insecure_skip_tls_verify": &hcldec.AttrSpec{Name: "insecure_skip_tls_verify", Type: cty.Bool, Required: false},
"box_download_url": &hcldec.AttrSpec{Name: "box_download_url", Type: cty.String, Required: false},
"no_direct_upload": &hcldec.AttrSpec{Name: "no_direct_upload", Type: cty.Bool, Required: false},
}
return s
}

View File

@ -1,850 +0,0 @@
package vagrantcloud
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"runtime"
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/stretchr/testify/assert"
)
type stubResponse struct {
Path string
Method string
Response string
StatusCode int
}
type tarFiles []struct {
Name, Body string
}
func testGoodConfig() map[string]interface{} {
return map[string]interface{}{
"access_token": "foo",
"version_description": "bar",
"box_tag": "hashicorp/precise64",
"version": "0.5",
}
}
func testBadConfig() map[string]interface{} {
return map[string]interface{}{
"access_token": "foo",
"box_tag": "baz",
"version_description": "bar",
}
}
func testNoAccessTokenProvidedConfig() map[string]interface{} {
return map[string]interface{}{
"box_tag": "baz",
"version_description": "bar",
"version": "0.5",
}
}
func newStackServer(stack []stubResponse) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if len(stack) < 1 {
rw.Header().Add("Error", fmt.Sprintf("Request stack is empty - Method: %s Path: %s", req.Method, req.URL.Path))
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
match := stack[0]
stack = stack[1:]
if match.Method != "" && req.Method != match.Method {
rw.Header().Add("Error", fmt.Sprintf("Request %s != %s", match.Method, req.Method))
http.Error(rw, fmt.Sprintf("Request %s != %s", match.Method, req.Method), http.StatusInternalServerError)
return
}
if match.Path != "" && match.Path != req.URL.Path {
rw.Header().Add("Error", fmt.Sprintf("Request %s != %s", match.Path, req.URL.Path))
http.Error(rw, fmt.Sprintf("Request %s != %s", match.Path, req.URL.Path), http.StatusInternalServerError)
return
}
rw.Header().Add("Complete", fmt.Sprintf("Method: %s Path: %s", match.Method, match.Path))
rw.WriteHeader(match.StatusCode)
if match.Response != "" {
_, err := rw.Write([]byte(match.Response))
if err != nil {
panic("failed to write response: " + err.Error())
}
}
}))
}
func newSecureServer(token string, handler http.HandlerFunc) *httptest.Server {
token = fmt.Sprintf("Bearer %s", token)
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("authorization") != token {
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
if handler != nil {
handler(rw, req)
}
}))
}
func newSelfSignedSslServer(token string, handler http.HandlerFunc) *httptest.Server {
token = fmt.Sprintf("Bearer %s", token)
return httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("authorization") != token {
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
if handler != nil {
handler(rw, req)
}
}))
}
func newNoAuthServer(handler http.HandlerFunc) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("authorization") != "" {
http.Error(rw, "Authorization header was provider", http.StatusBadRequest)
return
}
if handler != nil {
handler(rw, req)
}
}))
}
func TestPostProcessor_Insecure_Ssl(t *testing.T) {
var p PostProcessor
server := newSelfSignedSslServer("foo", nil)
defer server.Close()
config := testGoodConfig()
config["vagrant_cloud_url"] = server.URL
config["insecure_skip_tls_verify"] = true
if err := p.Configure(config); err != nil {
t.Fatalf("Expected TLS to skip certificate validation: %s", err)
}
}
func TestPostProcessor_Configure_fromVagrantEnv(t *testing.T) {
var p PostProcessor
config := testGoodConfig()
server := newSecureServer("bar", nil)
defer server.Close()
config["vagrant_cloud_url"] = server.URL
config["access_token"] = ""
os.Setenv("VAGRANT_CLOUD_TOKEN", "bar")
defer func() {
os.Setenv("VAGRANT_CLOUD_TOKEN", "")
}()
if err := p.Configure(config); err != nil {
t.Fatalf("err: %s", err)
}
if p.config.AccessToken != "bar" {
t.Fatalf("Expected to get token from VAGRANT_CLOUD_TOKEN env var. Got '%s' instead",
p.config.AccessToken)
}
}
func TestPostProcessor_Configure_fromAtlasEnv(t *testing.T) {
var p PostProcessor
config := testGoodConfig()
config["access_token"] = ""
server := newSecureServer("foo", nil)
defer server.Close()
config["vagrant_cloud_url"] = server.URL
os.Setenv("ATLAS_TOKEN", "foo")
defer func() {
os.Setenv("ATLAS_TOKEN", "")
}()
if err := p.Configure(config); err != nil {
t.Fatalf("err: %s", err)
}
if p.config.AccessToken != "foo" {
t.Fatalf("Expected to get token from ATLAS_TOKEN env var. Got '%s' instead",
p.config.AccessToken)
}
if !p.warnAtlasToken {
t.Fatal("Expected warn flag to be set when getting token from atlas env var.")
}
}
func TestPostProcessor_Configure_Good(t *testing.T) {
config := testGoodConfig()
server := newSecureServer("foo", nil)
defer server.Close()
config["vagrant_cloud_url"] = server.URL
var p PostProcessor
if err := p.Configure(config); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPostProcessor_Configure_Bad(t *testing.T) {
config := testBadConfig()
server := newSecureServer("foo", nil)
defer server.Close()
config["vagrant_cloud_url"] = server.URL
var p PostProcessor
if err := p.Configure(config); err == nil {
t.Fatalf("should have err")
}
}
func TestPostProcessor_Configure_checkAccessTokenIsRequiredByDefault(t *testing.T) {
var p PostProcessor
server := newSecureServer("foo", nil)
defer server.Close()
config := testNoAccessTokenProvidedConfig()
config["vagrant_cloud_url"] = server.URL
if err := p.Configure(config); err == nil {
t.Fatalf("Expected access token to be required.")
}
}
func TestPostProcessor_Configure_checkAccessTokenIsNotRequiredForOverridenVagrantCloud(t *testing.T) {
var p PostProcessor
server := newNoAuthServer(nil)
defer server.Close()
config := testNoAccessTokenProvidedConfig()
config["vagrant_cloud_url"] = server.URL
if err := p.Configure(config); err != nil {
t.Fatalf("Expected blank access token to be allowed and authenticate to pass: %s", err)
}
}
func TestPostProcessor_PostProcess_checkArtifactType(t *testing.T) {
artifact := &packersdk.MockArtifact{
BuilderIdValue: "invalid.builder",
}
config := testGoodConfig()
server := newSecureServer("foo", nil)
defer server.Close()
config["vagrant_cloud_url"] = server.URL
var p PostProcessor
p.Configure(config)
_, _, _, err := p.PostProcess(context.Background(), testUi(), artifact)
if !strings.Contains(err.Error(), "Unknown artifact type") {
t.Fatalf("Should error with message 'Unknown artifact type...' with BuilderId: %s", artifact.BuilderIdValue)
}
}
func TestPostProcessor_PostProcess_checkArtifactFileIsBox(t *testing.T) {
artifact := &packersdk.MockArtifact{
BuilderIdValue: "mitchellh.post-processor.vagrant", // good
FilesValue: []string{"invalid.boxfile"}, // should have .box extension
}
config := testGoodConfig()
server := newSecureServer("foo", nil)
defer server.Close()
config["vagrant_cloud_url"] = server.URL
var p PostProcessor
p.Configure(config)
_, _, _, err := p.PostProcess(context.Background(), testUi(), artifact)
if !strings.Contains(err.Error(), "Unknown files in artifact") {
t.Fatalf("Should error with message 'Unknown files in artifact...' with artifact file: %s",
artifact.FilesValue[0])
}
}
func TestPostProcessor_PostProcess_uploadsAndReleases(t *testing.T) {
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox"}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(boxfile.Name())
artifact := &packersdk.MockArtifact{
BuilderIdValue: "mitchellh.post-processor.vagrant",
FilesValue: []string{boxfile.Name()},
}
s := newStackServer([]stubResponse{stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"}})
defer s.Close()
stack := []stubResponse{
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`},
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box/hashicorp/precise64/version/0.5/release"},
}
server := newStackServer(stack)
defer server.Close()
config := testGoodConfig()
config["vagrant_cloud_url"] = server.URL
config["no_direct_upload"] = true
var p PostProcessor
err = p.Configure(config)
if err != nil {
t.Fatalf("err: %s", err)
}
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPostProcessor_PostProcess_uploadsAndNoRelease(t *testing.T) {
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox"}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(boxfile.Name())
artifact := &packersdk.MockArtifact{
BuilderIdValue: "mitchellh.post-processor.vagrant",
FilesValue: []string{boxfile.Name()},
}
s := newStackServer([]stubResponse{stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"}})
defer s.Close()
stack := []stubResponse{
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`},
}
server := newStackServer(stack)
defer server.Close()
config := testGoodConfig()
config["vagrant_cloud_url"] = server.URL
config["no_direct_upload"] = true
config["no_release"] = true
var p PostProcessor
err = p.Configure(config)
if err != nil {
t.Fatalf("err: %s", err)
}
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPostProcessor_PostProcess_directUpload5GFile(t *testing.T) {
// Disable test on Windows due to unreliable sparse file creation
if runtime.GOOS == "windows" {
return
}
// Boxes up to 5GB are supported for direct upload so
// set the box asset to be 5GB exactly
fSize := int64(5368709120)
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox"}`},
}
f, err := createBox(files)
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(f.Name())
if err := expandFile(f, fSize); err != nil {
t.Fatalf("failed to expand box file - %s", err)
}
artifact := &packersdk.MockArtifact{
BuilderIdValue: "mitchellh.post-processor.vagrant",
FilesValue: []string{f.Name()},
}
f.Close()
s := newStackServer(
[]stubResponse{
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"},
},
)
defer s.Close()
stack := []stubResponse{
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload/direct"},
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-complete"},
}
server := newStackServer(stack)
defer server.Close()
config := testGoodConfig()
config["vagrant_cloud_url"] = server.URL
config["no_release"] = true
// Set response here so we have API server URL available
stack[4].Response = `{"upload_path": "` + s.URL + `/box-upload-path", "callback": "` + server.URL + `/box-upload-complete"}`
var p PostProcessor
err = p.Configure(config)
if err != nil {
t.Fatalf("err: %s", err)
}
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPostProcessor_PostProcess_directUploadOver5GFile(t *testing.T) {
// Disable test on Windows due to unreliable sparse file creation
if runtime.GOOS == "windows" {
return
}
// Boxes over 5GB are not supported for direct upload so
// set the box asset to be one byte over 5GB
fSize := int64(5368709121)
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox"}`},
}
f, err := createBox(files)
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(f.Name())
if err := expandFile(f, fSize); err != nil {
t.Fatalf("failed to expand box file - %s", err)
}
f.Close()
artifact := &packersdk.MockArtifact{
BuilderIdValue: "mitchellh.post-processor.vagrant",
FilesValue: []string{f.Name()},
}
s := newStackServer(
[]stubResponse{
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"},
},
)
defer s.Close()
stack := []stubResponse{
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`},
}
server := newStackServer(stack)
defer server.Close()
config := testGoodConfig()
config["vagrant_cloud_url"] = server.URL
config["no_release"] = true
var p PostProcessor
err = p.Configure(config)
if err != nil {
t.Fatalf("err: %s", err)
}
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestPostProcessor_PostProcess_uploadsDirectAndReleases(t *testing.T) {
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider": "virtualbox"}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(boxfile.Name())
artifact := &packersdk.MockArtifact{
BuilderIdValue: "mitchellh.post-processor.vagrant",
FilesValue: []string{boxfile.Name()},
}
s := newStackServer(
[]stubResponse{
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"},
},
)
defer s.Close()
stack := []stubResponse{
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload/direct"},
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-complete"},
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box/hashicorp/precise64/version/0.5/release"},
}
server := newStackServer(stack)
defer server.Close()
config := testGoodConfig()
config["vagrant_cloud_url"] = server.URL
// Set response here so we have API server URL available
stack[4].Response = `{"upload_path": "` + s.URL + `/box-upload-path", "callback": "` + server.URL + `/box-upload-complete"}`
var p PostProcessor
err = p.Configure(config)
if err != nil {
t.Fatalf("err: %s", err)
}
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
if err != nil {
t.Fatalf("err: %s", err)
}
}
func testUi() *packersdk.BasicUi {
return &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
}
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packersdk.PostProcessor = new(PostProcessor)
}
func TestProviderFromBuilderName(t *testing.T) {
if providerFromBuilderName("foobar") != "foobar" {
t.Fatal("should copy unknown provider")
}
if providerFromBuilderName("vmware") != "vmware_desktop" {
t.Fatal("should convert provider")
}
}
func TestProviderFromVagrantBox_missing_box(t *testing.T) {
// Bad: Box does not exist
boxfile := "i_dont_exist.box"
_, err := providerFromVagrantBox(boxfile)
if err == nil {
t.Fatal("Should have error as box file does not exist")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_empty_box(t *testing.T) {
// Bad: Empty box file
boxfile, err := newBoxFile()
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatal("Should have error as box file is empty")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_gzip_only_box(t *testing.T) {
boxfile, err := newBoxFile()
if err != nil {
t.Fatalf("%s", err)
}
defer os.Remove(boxfile.Name())
// Bad: Box is just a plain gzip file
aw := gzip.NewWriter(boxfile)
_, err = aw.Write([]byte("foo content"))
if err != nil {
t.Fatal("Error zipping test box file")
}
aw.Close() // Flush the gzipped contents to file
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as box file is a plain gzip file: %s", err)
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_no_files_in_archive(t *testing.T) {
// Bad: Box contains no files
boxfile, err := createBox(tarFiles{})
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as box file has no contents")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_no_metadata(t *testing.T) {
// Bad: Box contains no metadata/metadata.json file
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as box file does not include metadata.json file")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_metadata_empty(t *testing.T) {
// Bad: Create a box with an empty metadata.json file
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", ""},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as box files metadata.json file is empty")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_metadata_bad_json(t *testing.T) {
// Bad: Create a box with bad JSON in the metadata.json file
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", "{provider: badjson}"},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as box files metadata.json file contains badly formatted JSON")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_metadata_no_provider_key(t *testing.T) {
// Bad: Create a box with no 'provider' key in the metadata.json file
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"cows":"moo"}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as provider key/value pair is missing from boxes metadata.json file")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_metadata_provider_value_empty(t *testing.T) {
// Bad: The boxes metadata.json file 'provider' key has an empty value
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider":""}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
_, err = providerFromVagrantBox(boxfile.Name())
if err == nil {
t.Fatalf("Should have error as value associated with 'provider' key in boxes metadata.json file is empty")
}
t.Logf("%s", err)
}
func TestProviderFromVagrantBox_metadata_ok(t *testing.T) {
// Good: The boxes metadata.json file has the 'provider' key/value pair
expectedProvider := "virtualbox"
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider":"` + expectedProvider + `"}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
provider, err := providerFromVagrantBox(boxfile.Name())
if err != nil {
t.Fatalf("error getting provider from vagrant box %s:%v", boxfile.Name(), err)
}
assert.Equal(t, expectedProvider, provider, "Error: Expected provider: '%s'. Got '%s'", expectedProvider, provider)
t.Logf("Expected provider '%s'. Got provider '%s'", expectedProvider, provider)
}
func TestGetProvider_artifice(t *testing.T) {
expectedProvider := "virtualbox"
files := tarFiles{
{"foo.txt", "This is a foo file"},
{"bar.txt", "This is a bar file"},
{"metadata.json", `{"provider":"` + expectedProvider + `"}`},
}
boxfile, err := createBox(files)
if err != nil {
t.Fatalf("Error creating test box: %s", err)
}
defer os.Remove(boxfile.Name())
provider, err := getProvider("", boxfile.Name(), "artifice")
if err != nil {
t.Fatalf("error getting provider %s:%v", boxfile.Name(), err)
}
assert.Equal(t, expectedProvider, provider, "Error: Expected provider: '%s'. Got '%s'", expectedProvider, provider)
t.Logf("Expected provider '%s'. Got provider '%s'", expectedProvider, provider)
}
func TestGetProvider_other(t *testing.T) {
expectedProvider := "virtualbox"
provider, _ := getProvider(expectedProvider, "foo.box", "other")
assert.Equal(t, expectedProvider, provider, "Error: Expected provider: '%s'. Got '%s'", expectedProvider, provider)
t.Logf("Expected provider '%s'. Got provider '%s'", expectedProvider, provider)
}
func newBoxFile() (boxfile *os.File, err error) {
boxfile, err = ioutil.TempFile(os.TempDir(), "test*.box")
if err != nil {
return boxfile, fmt.Errorf("Error creating test box file: %s", err)
}
return boxfile, nil
}
func createBox(files tarFiles) (boxfile *os.File, err error) {
boxfile, err = newBoxFile()
if err != nil {
return boxfile, err
}
// Box files are gzipped tar archives
aw := gzip.NewWriter(boxfile)
tw := tar.NewWriter(aw)
// Add each file to the box
for _, file := range files {
// Create and write the tar file header
hdr := &tar.Header{
Name: file.Name,
Mode: 0644,
Size: int64(len(file.Body)),
}
err = tw.WriteHeader(hdr)
if err != nil {
return boxfile, fmt.Errorf("Error writing box tar file header: %s", err)
}
// Write the file contents
_, err = tw.Write([]byte(file.Body))
if err != nil {
return boxfile, fmt.Errorf("Error writing box tar file contents: %s", err)
}
}
// Flush and close each writer
err = tw.Close()
if err != nil {
return boxfile, fmt.Errorf("Error flushing tar file contents: %s", err)
}
err = aw.Close()
if err != nil {
return boxfile, fmt.Errorf("Error flushing gzip file contents: %s", err)
}
return boxfile, nil
}
func expandFile(f *os.File, finalSize int64) (err error) {
s, err := f.Stat()
if err != nil {
return
}
size := finalSize - s.Size()
if size < 1 {
return
}
if _, err = f.Seek(size-1, 2); err != nil {
return
}
if _, err = f.Write([]byte{0}); err != nil {
return
}
return nil
}

View File

@ -1,48 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepConfirmUpload struct {
}
func (s *stepConfirmUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
ui := state.Get("ui").(packersdk.Ui)
upload := state.Get("upload").(*Upload)
url := upload.CallbackPath
config := state.Get("config").(*Config)
if config.NoDirectUpload {
return multistep.ActionContinue
}
ui.Say("Confirming direct box upload completion")
resp, err := client.Callback(url)
if err != nil || resp.StatusCode != 200 {
if resp == nil || resp.Body == nil {
state.Put("error", fmt.Errorf("No response from server."))
} else {
cloudErrors := &VagrantCloudErrors{}
err = decodeBody(resp, cloudErrors)
if err != nil {
ui.Error(fmt.Sprintf("error decoding error response: %s", err))
}
state.Put("error", fmt.Errorf("Error preparing upload: %s", cloudErrors.FormatErrors()))
}
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepConfirmUpload) Cleanup(state multistep.StateBag) {
// No cleanup
}

View File

@ -1,102 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type Provider struct {
Name string `json:"name"`
Url string `json:"url,omitempty"`
HostedToken string `json:"hosted_token,omitempty"`
UploadUrl string `json:"upload_url,omitempty"`
}
type stepCreateProvider struct {
name string // the name of the provider
}
func (s *stepCreateProvider) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
ui := state.Get("ui").(packersdk.Ui)
box := state.Get("box").(*Box)
version := state.Get("version").(*Version)
providerName := state.Get("providerName").(string)
downloadUrl := state.Get("boxDownloadUrl").(string)
path := fmt.Sprintf("box/%s/version/%v/providers", box.Tag, version.Version)
provider := &Provider{Name: providerName}
if downloadUrl != "" {
provider.Url = downloadUrl
}
// Wrap the provider in a provider object for the API
wrapper := make(map[string]interface{})
wrapper["provider"] = provider
ui.Say(fmt.Sprintf("Creating provider: %s", providerName))
resp, err := client.Post(path, wrapper)
if err != nil || (resp.StatusCode != 200) {
cloudErrors := &VagrantCloudErrors{}
err = decodeBody(resp, cloudErrors)
if err != nil {
ui.Error(fmt.Sprintf("error decoding error response: %s", err))
}
state.Put("error", fmt.Errorf("Error creating provider: %s", cloudErrors.FormatErrors()))
return multistep.ActionHalt
}
if err = decodeBody(resp, provider); err != nil {
state.Put("error", fmt.Errorf("Error parsing provider response: %s", err))
return multistep.ActionHalt
}
// Save the name for cleanup
s.name = provider.Name
state.Put("provider", provider)
return multistep.ActionContinue
}
func (s *stepCreateProvider) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*VagrantCloudClient)
ui := state.Get("ui").(packersdk.Ui)
box := state.Get("box").(*Box)
version := state.Get("version").(*Version)
// If we didn't save the provider name, it likely doesn't exist
if s.name == "" {
ui.Say("Cleaning up provider")
ui.Message("Provider was not created, not deleting")
return
}
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
// Return if we didn't cancel or halt, and thus need
// no cleanup
if !cancelled && !halted {
return
}
ui.Say("Cleaning up provider")
ui.Message(fmt.Sprintf("Deleting provider: %s", s.name))
path := fmt.Sprintf("box/%s/version/%v/provider/%s", box.Tag, version.Version, s.name)
// No need for resp from the cleanup DELETE
_, err := client.Delete(path)
if err != nil {
ui.Error(fmt.Sprintf("Error destroying provider: %s", err))
}
}

View File

@ -1,63 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type Version struct {
Version string `json:"version"`
Description string `json:"description,omitempty"`
}
type stepCreateVersion struct {
}
func (s *stepCreateVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
box := state.Get("box").(*Box)
ui.Say(fmt.Sprintf("Creating version: %s", config.Version))
if hasVersion, v := box.HasVersion(config.Version); hasVersion {
ui.Message(fmt.Sprintf("Version exists, skipping creation"))
state.Put("version", v)
return multistep.ActionContinue
}
path := fmt.Sprintf("box/%s/versions", box.Tag)
version := &Version{Version: config.Version, Description: config.VersionDescription}
// Wrap the version in a version object for the API
wrapper := make(map[string]interface{})
wrapper["version"] = version
resp, err := client.Post(path, wrapper)
if err != nil || (resp.StatusCode != 200) {
cloudErrors := &VagrantCloudErrors{}
err = decodeBody(resp, cloudErrors)
if err != nil {
ui.Error(fmt.Sprintf("error decoding error response: %s", err))
}
state.Put("error", fmt.Errorf("Error creating version: %s", cloudErrors.FormatErrors()))
return multistep.ActionHalt
}
if err = decodeBody(resp, version); err != nil {
state.Put("error", fmt.Errorf("Error parsing version response: %s", err))
return multistep.ActionHalt
}
state.Put("version", version)
return multistep.ActionContinue
}
func (s *stepCreateVersion) Cleanup(state multistep.StateBag) {}

View File

@ -1,80 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"os"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
const VAGRANT_CLOUD_DIRECT_UPLOAD_LIMIT = 5368709120 // Upload limit is 5G
type Upload struct {
UploadPath string `json:"upload_path"`
CallbackPath string `json:"callback"`
}
type stepPrepareUpload struct {
}
func (s *stepPrepareUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
box := state.Get("box").(*Box)
version := state.Get("version").(*Version)
provider := state.Get("provider").(*Provider)
artifactFilePath := state.Get("artifactFilePath").(string)
// If direct upload is enabled, the asset size must be <= 5 GB
if config.NoDirectUpload == false {
f, err := os.Stat(artifactFilePath)
if err != nil {
ui.Error(fmt.Sprintf("error determining size of upload artifact: %s", artifactFilePath))
}
if f.Size() > VAGRANT_CLOUD_DIRECT_UPLOAD_LIMIT {
ui.Say(fmt.Sprintf("Asset %s is larger than the direct upload limit. Setting `NoDirectUpload` to true", artifactFilePath))
config.NoDirectUpload = true
}
}
path := fmt.Sprintf("box/%s/version/%v/provider/%s/upload", box.Tag, version.Version, provider.Name)
if !config.NoDirectUpload {
path = path + "/direct"
}
upload := &Upload{}
ui.Say(fmt.Sprintf("Preparing upload of box: %s", artifactFilePath))
resp, err := client.Get(path)
if err != nil || (resp.StatusCode != 200) {
if resp == nil || resp.Body == nil {
state.Put("error", fmt.Errorf("No response from server."))
} else {
cloudErrors := &VagrantCloudErrors{}
err = decodeBody(resp, cloudErrors)
if err != nil {
ui.Error(fmt.Sprintf("error decoding error response: %s", err))
}
state.Put("error", fmt.Errorf("Error preparing upload: %s", cloudErrors.FormatErrors()))
}
return multistep.ActionHalt
}
if err = decodeBody(resp, upload); err != nil {
state.Put("error", fmt.Errorf("Error parsing upload response: %s", err))
return multistep.ActionHalt
}
// Save the upload details to the state
state.Put("upload", upload)
return multistep.ActionContinue
}
func (s *stepPrepareUpload) Cleanup(state multistep.StateBag) {
// No cleanup
}

View File

@ -1,54 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepReleaseVersion struct {
}
func (s *stepReleaseVersion) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
ui := state.Get("ui").(packersdk.Ui)
box := state.Get("box").(*Box)
version := state.Get("version").(*Version)
config := state.Get("config").(*Config)
ui.Say(fmt.Sprintf("Releasing version: %s", version.Version))
if config.NoRelease {
ui.Message("Not releasing version due to configuration")
return multistep.ActionContinue
}
path := fmt.Sprintf("box/%s/version/%v/release", box.Tag, version.Version)
resp, err := client.Put(path)
if err != nil || (resp.StatusCode != 200) {
cloudErrors := &VagrantCloudErrors{}
if err := decodeBody(resp, cloudErrors); err != nil {
state.Put("error", fmt.Errorf("Error parsing provider response: %s", err))
return multistep.ActionHalt
}
if strings.Contains(cloudErrors.FormatErrors(), "already been released") {
ui.Message("Not releasing version, already released")
return multistep.ActionContinue
}
state.Put("error", fmt.Errorf("Error releasing version: %s", cloudErrors.FormatErrors()))
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("Version successfully released and available"))
return multistep.ActionContinue
}
func (s *stepReleaseVersion) Cleanup(state multistep.StateBag) {
// No cleanup
}

View File

@ -1,73 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"log"
"net/http"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/retry"
)
type stepUpload struct {
}
func (s *stepUpload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
upload := state.Get("upload").(*Upload)
artifactFilePath := state.Get("artifactFilePath").(string)
url := upload.UploadPath
ui.Say(fmt.Sprintf("Uploading box: %s", artifactFilePath))
ui.Message(
"Depending on your internet connection and the size of the box,\n" +
"this may take some time")
err := retry.Config{
Tries: 3,
RetryDelay: (&retry.Backoff{InitialBackoff: 10 * time.Second, MaxBackoff: 10 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) error {
ui.Message(fmt.Sprintf("Uploading box"))
var err error
var resp *http.Response
if config.NoDirectUpload {
resp, err = client.Upload(artifactFilePath, url)
} else {
resp, err = client.DirectUpload(artifactFilePath, url)
}
if err != nil {
ui.Message(fmt.Sprintf(
"Error uploading box! Will retry in 10 seconds. Error: %s", err))
return err
}
if resp.StatusCode != 200 {
err := fmt.Errorf("bad HTTP status: %d", resp.StatusCode)
log.Print(err)
ui.Message(fmt.Sprintf(
"Error uploading box! Will retry in 10 seconds. Status: %d",
resp.StatusCode))
return err
}
return err
})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message("Box successfully uploaded")
return multistep.ActionContinue
}
func (s *stepUpload) Cleanup(state multistep.StateBag) {
// No cleanup
}

View File

@ -1,76 +0,0 @@
package vagrantcloud
import (
"context"
"fmt"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type Box struct {
Tag string `json:"tag"`
Versions []*Version `json:"versions"`
}
func (b *Box) HasVersion(version string) (bool, *Version) {
for _, v := range b.Versions {
if v.Version == version {
return true, v
}
}
return false, nil
}
type stepVerifyBox struct {
}
func (s *stepVerifyBox) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*VagrantCloudClient)
ui := state.Get("ui").(packersdk.Ui)
config := state.Get("config").(*Config)
ui.Say(fmt.Sprintf("Verifying box is accessible: %s", config.Tag))
path := fmt.Sprintf("box/%s", config.Tag)
resp, err := client.Get(path)
if err != nil {
state.Put("error", fmt.Errorf("Error retrieving box: %s", err))
return multistep.ActionHalt
}
if resp.StatusCode != 200 {
cloudErrors := &VagrantCloudErrors{}
err = decodeBody(resp, cloudErrors)
if err != nil {
ui.Error(fmt.Sprintf("error decoding error response: %s", err))
}
state.Put("error", fmt.Errorf("Error retrieving box: %s", cloudErrors.FormatErrors()))
return multistep.ActionHalt
}
box := &Box{}
if err = decodeBody(resp, box); err != nil {
state.Put("error", fmt.Errorf("Error parsing box response: %s", err))
return multistep.ActionHalt
}
if box.Tag != config.Tag {
state.Put("error", fmt.Errorf("Could not verify box: %s", config.Tag))
return multistep.ActionHalt
}
ui.Message("Box accessible and matches tag")
// Keep the box in state for later
state.Put("box", box)
// Box exists and is accessible
return multistep.ActionContinue
}
func (s *stepVerifyBox) Cleanup(state multistep.StateBag) {
// no cleanup needed
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var VagrantCloudPluginVersion *version.PluginVersion
func init() {
VagrantCloudPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -1,44 +0,0 @@
package vagrant
import (
"fmt"
"os"
)
const BuilderId = "mitchellh.post-processor.vagrant"
type Artifact struct {
Path string
Provider string
}
func NewArtifact(provider, path string) *Artifact {
return &Artifact{
Path: path,
Provider: provider,
}
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return []string{a.Path}
}
func (a *Artifact) Id() string {
return a.Provider
}
func (a *Artifact) String() string {
return fmt.Sprintf("'%s' provider box: %s", a.Provider, a.Path)
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
return os.Remove(a.Path)
}

View File

@ -1,22 +0,0 @@
package vagrant
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestArtifact_ImplementsArtifact(t *testing.T) {
var raw interface{}
raw = &Artifact{}
if _, ok := raw.(packersdk.Artifact); !ok {
t.Fatalf("Artifact should be a Artifact")
}
}
func TestArtifact_Id(t *testing.T) {
artifact := NewArtifact("vmware", "./")
if artifact.Id() != "vmware" {
t.Fatalf("should return name as Id")
}
}

View File

@ -1,57 +0,0 @@
package vagrant
import (
"bytes"
"fmt"
"strings"
"text/template"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type AWSProvider struct{}
func (p *AWSProvider) KeepInputArtifact() bool {
return true
}
func (p *AWSProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "aws"}
// Build up the template data to build our Vagrantfile
tplData := &awsVagrantfileTemplate{
Images: make(map[string]string),
}
for _, regions := range strings.Split(artifact.Id(), ",") {
parts := strings.Split(regions, ":")
if len(parts) != 2 {
err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
return
}
tplData.Images[parts[0]] = parts[1]
}
// Build up the contents
var contents bytes.Buffer
t := template.Must(template.New("vf").Parse(defaultAWSVagrantfile))
err = t.Execute(&contents, tplData)
vagrantfile = contents.String()
return
}
type awsVagrantfileTemplate struct {
Images map[string]string
}
var defaultAWSVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider "aws" do |aws|
{{ range $region, $ami := .Images }}
aws.region_config "{{ $region }}", ami: "{{ $ami }}"
{{ end }}
end
end
`

View File

@ -1,37 +0,0 @@
package vagrant
import (
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestAWSProvider_impl(t *testing.T) {
var _ Provider = new(AWSProvider)
}
func TestAWSProvider_KeepInputArtifact(t *testing.T) {
p := new(AWSProvider)
if !p.KeepInputArtifact() {
t.Fatal("should keep input artifact")
}
}
func TestAWSProvider_ArtifactId(t *testing.T) {
p := new(AWSProvider)
ui := testUi()
artifact := &packersdk.MockArtifact{
IdValue: "us-east-1:ami-1234",
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
result := `aws.region_config "us-east-1", ami: "ami-1234"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
}

View File

@ -1,67 +0,0 @@
package vagrant
import (
"fmt"
"strings"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type AzureProvider struct{}
func (p *AzureProvider) KeepInputArtifact() bool {
return true
}
func (p *AzureProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "azure"}
var AzureImageProps map[string]string
AzureImageProps = make(map[string]string)
// HACK(double16): It appears we can not access the Azure Artifact directly, so parse String()
artifactString := artifact.String()
ui.Message(fmt.Sprintf("artifact string: '%s'", artifactString))
lines := strings.Split(artifactString, "\n")
for l := 0; l < len(lines); l++ {
split := strings.Split(lines[l], ": ")
if len(split) > 1 {
AzureImageProps[strings.TrimSpace(split[0])] = strings.TrimSpace(split[1])
}
}
ui.Message(fmt.Sprintf("artifact string parsed: %+v", AzureImageProps))
if AzureImageProps["ManagedImageId"] != "" {
vagrantfile = fmt.Sprintf(managedImageVagrantfile, AzureImageProps["ManagedImageLocation"], AzureImageProps["ManagedImageId"])
} else if AzureImageProps["OSDiskUri"] != "" {
vagrantfile = fmt.Sprintf(vhdVagrantfile, AzureImageProps["StorageAccountLocation"], AzureImageProps["OSDiskUri"], AzureImageProps["OSType"])
} else {
err = fmt.Errorf("No managed image nor VHD URI found in artifact: %s", artifactString)
return
}
return
}
var managedImageVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :azure do |azure, override|
azure.location = "%s"
azure.vm_managed_image_id = "%s"
override.winrm.transport = :ssl
override.winrm.port = 5986
end
end
`
var vhdVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :azure do |azure, override|
azure.location = "%s"
azure.vm_vhd_uri = "%s"
azure.vm_operating_system = "%s"
override.winrm.transport = :ssl
override.winrm.port = 5986
end
end
`

View File

@ -1,94 +0,0 @@
package vagrant
import (
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestAzureProvider_impl(t *testing.T) {
var _ Provider = new(AzureProvider)
}
func TestAzureProvider_KeepInputArtifact(t *testing.T) {
p := new(AzureProvider)
if !p.KeepInputArtifact() {
t.Fatal("should keep input artifact")
}
}
func TestAzureProvider_ManagedImage(t *testing.T) {
p := new(AzureProvider)
ui := testUi()
artifact := &packersdk.MockArtifact{
StringValue: `Azure.ResourceManagement.VMImage:
OSType: Linux
ManagedImageResourceGroupName: packerruns
ManagedImageName: packer-1533651633
ManagedImageId: /subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589
ManagedImageLocation: westus`,
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
result := `azure.location = "westus"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_managed_image_id = "/subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
// DO NOT set resource group in Vagrantfile, it should be separate from the image
result = `azure.resource_group_name`
if strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_operating_system`
if strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
}
func TestAzureProvider_VHD(t *testing.T) {
p := new(AzureProvider)
ui := testUi()
artifact := &packersdk.MockArtifact{
IdValue: "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd",
StringValue: `Azure.ResourceManagement.VMImage:
OSType: Linux
StorageAccountLocation: westus
OSDiskUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd
OSDiskUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd?se=2018-09-07T18%3A36%3A34Z&sig=xUiFvwAviPYoP%2Bc91vErqvwYR1eK4x%2BAx7YLMe84zzU%3D&sp=r&sr=b&sv=2016-05-31
TemplateUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json
TemplateUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json?se=2018-09-07T18%3A36%3A34Z&sig=lDxePyAUCZbfkB5ddiofimXfwk5INn%2F9E2BsnqIKC9Q%3D&sp=r&sr=b&sv=2016-05-31`,
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
result := `azure.location = "westus"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_vhd_uri = "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
result = `azure.vm_operating_system = "Linux"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
// DO NOT set resource group in Vagrantfile, it should be separate from the image
result = `azure.resource_group_name`
if strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
}

View File

@ -1,53 +0,0 @@
package vagrant
import (
"bytes"
"fmt"
"strings"
"text/template"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type digitalOceanVagrantfileTemplate struct {
Image string ""
Region string ""
}
type DigitalOceanProvider struct{}
func (p *DigitalOceanProvider) KeepInputArtifact() bool {
return true
}
func (p *DigitalOceanProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "digital_ocean"}
// Determine the image and region...
tplData := &digitalOceanVagrantfileTemplate{}
parts := strings.Split(artifact.Id(), ":")
if len(parts) != 2 {
err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
return
}
tplData.Region = parts[0]
tplData.Image = parts[1]
// Build up the Vagrantfile
var contents bytes.Buffer
t := template.Must(template.New("vf").Parse(defaultDigitalOceanVagrantfile))
err = t.Execute(&contents, tplData)
vagrantfile = contents.String()
return
}
var defaultDigitalOceanVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :digital_ocean do |digital_ocean|
digital_ocean.image = "{{ .Image }}"
digital_ocean.region = "{{ .Region }}"
end
end
`

View File

@ -1,41 +0,0 @@
package vagrant
import (
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestDigitalOceanProvider_impl(t *testing.T) {
var _ Provider = new(DigitalOceanProvider)
}
func TestDigitalOceanProvider_KeepInputArtifact(t *testing.T) {
p := new(DigitalOceanProvider)
if !p.KeepInputArtifact() {
t.Fatal("should keep input artifact")
}
}
func TestDigitalOceanProvider_ArtifactId(t *testing.T) {
p := new(DigitalOceanProvider)
ui := testUi()
artifact := &packersdk.MockArtifact{
IdValue: "San Francisco:42",
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
image := `digital_ocean.image = "42"`
if !strings.Contains(vagrantfile, image) {
t.Fatalf("wrong image substitution: %s", vagrantfile)
}
region := `digital_ocean.region = "San Francisco"`
if !strings.Contains(vagrantfile, region) {
t.Fatalf("wrong region substitution: %s", vagrantfile)
}
}

View File

@ -1,29 +0,0 @@
package vagrant
import (
"fmt"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type DockerProvider struct{}
func (p *DockerProvider) KeepInputArtifact() bool {
return false
}
func (p *DockerProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "docker"}
vagrantfile = fmt.Sprintf(dockerVagrantfile, artifact.Id())
return
}
var dockerVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :docker do |docker, override|
docker.image = "%s"
end
end
`

View File

@ -1,9 +0,0 @@
package vagrant
import (
"testing"
)
func TestDockerProvider_impl(t *testing.T) {
var _ Provider = new(DockerProvider)
}

View File

@ -1,42 +0,0 @@
package vagrant
import (
"bytes"
"text/template"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type googleVagrantfileTemplate struct {
Image string ""
}
type GoogleProvider struct{}
func (p *GoogleProvider) KeepInputArtifact() bool {
return true
}
func (p *GoogleProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "google"}
// Build up the template data to build our Vagrantfile
tplData := &googleVagrantfileTemplate{}
tplData.Image = artifact.Id()
// Build up the Vagrantfile
var contents bytes.Buffer
t := template.Must(template.New("vf").Parse(defaultGoogleVagrantfile))
err = t.Execute(&contents, tplData)
vagrantfile = contents.String()
return
}
var defaultGoogleVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :google do |google|
google.image = "{{ .Image }}"
end
end
`

View File

@ -1,37 +0,0 @@
package vagrant
import (
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestGoogleProvider_impl(t *testing.T) {
var _ Provider = new(GoogleProvider)
}
func TestGoogleProvider_KeepInputArtifact(t *testing.T) {
p := new(GoogleProvider)
if !p.KeepInputArtifact() {
t.Fatal("should keep input artifact")
}
}
func TestGoogleProvider_ArtifactId(t *testing.T) {
p := new(GoogleProvider)
ui := testUi()
artifact := &packersdk.MockArtifact{
IdValue: "packer-1234",
}
vagrantfile, _, err := p.Process(ui, artifact, "foo")
if err != nil {
t.Fatalf("should not have error: %s", err)
}
result := `google.image = "packer-1234"`
if !strings.Contains(vagrantfile, result) {
t.Fatalf("wrong substitution: %s", vagrantfile)
}
}

View File

@ -1,75 +0,0 @@
package vagrant
import (
"fmt"
"os"
"path/filepath"
"strings"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type HypervProvider struct{}
func (p *HypervProvider) KeepInputArtifact() bool {
return false
}
func (p *HypervProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "hyperv"}
// ui.Message(fmt.Sprintf("artifacts all: %+v", artifact))
var outputDir string
// Vargant requires specific dir structure for hyperv
// hyperv builder creates the structure in the output dir
// we have to keep the structure in a temp dir
// hack little bit but string in artifact usually have output dir
artifactString := artifact.String()
d := strings.Split(artifactString, ": ")
outputDir = d[1]
// ui.Message(fmt.Sprintf("artifact dir from string: %s", outputDir))
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
ui.Message(fmt.Sprintf("Copying: %s", path))
var rel string
rel, err = filepath.Rel(outputDir, filepath.Dir(path))
// ui.Message(fmt.Sprintf("rel is: %s", rel))
if err != nil {
ui.Message(fmt.Sprintf("err in: %s", rel))
return
}
dstDir := filepath.Join(dir, rel)
// ui.Message(fmt.Sprintf("dstdir is: %s", dstDir))
if _, err = os.Stat(dstDir); err != nil {
if err = os.MkdirAll(dstDir, 0755); err != nil {
ui.Message(fmt.Sprintf("err in creating: %s", dstDir))
return
}
}
dstPath := filepath.Join(dstDir, filepath.Base(path))
// We prefer to link the files where possible because they are often very huge.
// Some filesystem configurations do not allow hardlinks. As the possibilities
// of mounting different devices in different paths are flexible, we just try to
// link the file and copy if the link fails, thereby automatically optimizing with a safe fallback.
if err = LinkFile(dstPath, path); err != nil {
// ui.Message(fmt.Sprintf("err in linking: %s to %s", path, dstPath))
if err = CopyContents(dstPath, path); err != nil {
ui.Message(fmt.Sprintf("err in copying: %s to %s", path, dstPath))
return
}
}
ui.Message(fmt.Sprintf("Copied %s to %s", path, dstPath))
}
return
}

View File

@ -1,117 +0,0 @@
package vagrant
import (
"fmt"
"path/filepath"
"strconv"
"strings"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// Lowercase a ascii letter.
func lower(c byte) byte {
return c | ('a' - 'A')
}
// Convert a string that represents a qemu disk image size to megabytes.
//
// Valid units (case-insensitive):
//
// B (byte) 1B
// K (kilobyte) 1024B
// M (megabyte) 1024K
// G (gigabyte) 1024M
// T (terabyte) 1024G
// P (petabyte) 1024T
// E (exabyte) 1024P
//
// The default is M.
func sizeInMegabytes(size string) uint64 {
unit := size[len(size)-1]
if unit >= '0' && unit <= '9' {
unit = 'm'
} else {
size = size[:len(size)-1]
}
value, _ := strconv.ParseUint(size, 10, 64)
switch lower(unit) {
case 'b':
return value / 1024 / 1024
case 'k':
return value / 1024
case 'm':
return value
case 'g':
return value * 1024
case 't':
return value * 1024 * 1024
case 'p':
return value * 1024 * 1024 * 1024
case 'e':
return value * 1024 * 1024 * 1024 * 1024
default:
panic(fmt.Sprintf("Unknown size unit %c", unit))
}
}
type LibVirtProvider struct{}
func (p *LibVirtProvider) KeepInputArtifact() bool {
return false
}
func (p *LibVirtProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
diskName := artifact.State("diskName").(string)
// Copy the disk image into the temporary directory (as box.img)
for _, path := range artifact.Files() {
if strings.HasSuffix(path, "/"+diskName) {
ui.Message(fmt.Sprintf("Copying from artifact: %s", path))
dstPath := filepath.Join(dir, "box.img")
if err = CopyContents(dstPath, path); err != nil {
return
}
}
}
format := artifact.State("diskType").(string)
origSize := sizeInMegabytes(artifact.State("diskSize").(string))
size := origSize / 1024 // In MB, want GB
if origSize%1024 > 0 {
// Make sure we don't make the size smaller
size++
}
domainType := artifact.State("domainType").(string)
// Convert domain type to libvirt driver
var driver string
switch domainType {
case "none", "tcg", "hvf":
driver = "qemu"
case "kvm":
driver = domainType
default:
return "", nil, fmt.Errorf("Unknown libvirt domain type: %s", domainType)
}
// Create the metadata
metadata = map[string]interface{}{
"provider": "libvirt",
"format": format,
"virtual_size": size,
}
vagrantfile = fmt.Sprintf(libvirtVagrantfile, driver)
return
}
var libvirtVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :libvirt do |libvirt|
libvirt.driver = "%s"
end
end
`

View File

@ -1,69 +0,0 @@
package vagrant
import (
"fmt"
"testing"
)
func assertSizeInMegabytes(t *testing.T, size string, expected uint64) {
actual := sizeInMegabytes(size)
if actual != expected {
t.Fatalf("the size `%s` was converted to `%d` but expected `%d`", size, actual, expected)
}
}
func Test_sizeInMegabytes_WithInvalidUnitMustPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatalf("expected a panic but got none")
}
}()
sizeInMegabytes("1234x")
}
func Test_sizeInMegabytes_WithoutUnitMustDefaultToMegabytes(t *testing.T) {
assertSizeInMegabytes(t, "1234", 1234)
}
func Test_sizeInMegabytes_WithBytesUnit(t *testing.T) {
assertSizeInMegabytes(t, fmt.Sprintf("%db", 1234*1024*1024), 1234)
assertSizeInMegabytes(t, fmt.Sprintf("%dB", 1234*1024*1024), 1234)
assertSizeInMegabytes(t, "1B", 0)
}
func Test_sizeInMegabytes_WithKiloBytesUnit(t *testing.T) {
assertSizeInMegabytes(t, fmt.Sprintf("%dk", 1234*1024), 1234)
assertSizeInMegabytes(t, fmt.Sprintf("%dK", 1234*1024), 1234)
assertSizeInMegabytes(t, "1K", 0)
}
func Test_sizeInMegabytes_WithMegabytesUnit(t *testing.T) {
assertSizeInMegabytes(t, "1234m", 1234)
assertSizeInMegabytes(t, "1234M", 1234)
assertSizeInMegabytes(t, "1M", 1)
}
func Test_sizeInMegabytes_WithGigabytesUnit(t *testing.T) {
assertSizeInMegabytes(t, "1234g", 1234*1024)
assertSizeInMegabytes(t, "1234G", 1234*1024)
assertSizeInMegabytes(t, "1G", 1*1024)
}
func Test_sizeInMegabytes_WithTerabytesUnit(t *testing.T) {
assertSizeInMegabytes(t, "1234t", 1234*1024*1024)
assertSizeInMegabytes(t, "1234T", 1234*1024*1024)
assertSizeInMegabytes(t, "1T", 1*1024*1024)
}
func Test_sizeInMegabytes_WithPetabytesUnit(t *testing.T) {
assertSizeInMegabytes(t, "1234p", 1234*1024*1024*1024)
assertSizeInMegabytes(t, "1234P", 1234*1024*1024*1024)
assertSizeInMegabytes(t, "1P", 1*1024*1024*1024)
}
func Test_sizeInMegabytes_WithExabytesUnit(t *testing.T) {
assertSizeInMegabytes(t, "1234e", 1234*1024*1024*1024*1024)
assertSizeInMegabytes(t, "1234E", 1234*1024*1024*1024*1024)
assertSizeInMegabytes(t, "1E", 1*1024*1024*1024*1024)
}

View File

@ -1,34 +0,0 @@
package vagrant
import (
"fmt"
"path/filepath"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type LXCProvider struct{}
func (p *LXCProvider) KeepInputArtifact() bool {
return false
}
func (p *LXCProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{
"provider": "lxc",
"version": "1.0.0",
}
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
ui.Message(fmt.Sprintf("Copying: %s", path))
dstPath := filepath.Join(dir, filepath.Base(path))
if err = CopyContents(dstPath, path); err != nil {
return
}
}
return
}

View File

@ -1,9 +0,0 @@
package vagrant
import (
"testing"
)
func TestLXCProvider_impl(t *testing.T) {
var _ Provider = new(LXCProvider)
}

View File

@ -1,58 +0,0 @@
package vagrant
import (
"fmt"
"path/filepath"
"regexp"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// These are the extensions of files and directories that are unnecessary for the function
// of a Parallels virtual machine.
var UnnecessaryFilesPatterns = []string{"\\.log$", "\\.backup$", "\\.Backup$", "\\.app/", "/Windows Disks/"}
type ParallelsProvider struct{}
func (p *ParallelsProvider) KeepInputArtifact() bool {
return false
}
func (p *ParallelsProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "parallels"}
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
// If the file isn't critical to the function of the
// virtual machine, we get rid of it.
unnecessary := false
for _, unnecessaryPat := range UnnecessaryFilesPatterns {
if matched, _ := regexp.MatchString(unnecessaryPat, path); matched {
unnecessary = true
break
}
}
if unnecessary {
continue
}
tmpPath := filepath.ToSlash(path)
pathRe := regexp.MustCompile(`^(.+?)([^/]+\.pvm/.+?)$`)
matches := pathRe.FindStringSubmatch(tmpPath)
var pvmPath string
if matches != nil {
pvmPath = filepath.FromSlash(matches[2])
} else {
continue // Just copy a pvm
}
dstPath := filepath.Join(dir, pvmPath)
ui.Message(fmt.Sprintf("Copying: %s", path))
if err = CopyContents(dstPath, path); err != nil {
return
}
}
return
}

View File

@ -1,9 +0,0 @@
package vagrant
import (
"testing"
)
func TestParallelsProvider_impl(t *testing.T) {
var _ Provider = new(ParallelsProvider)
}

View File

@ -1,344 +0,0 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
// vagrant implements the packersdk.PostProcessor interface and adds a
// post-processor that turns artifacts of known builders into Vagrant
// boxes.
package vagrant
import (
"compress/flate"
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-sdk/tmp"
"github.com/hashicorp/packer/post-processor/artifice"
"github.com/mitchellh/mapstructure"
)
var builtins = map[string]string{
"mitchellh.amazonebs": "aws",
"mitchellh.amazon.instance": "aws",
"mitchellh.virtualbox": "virtualbox",
"mitchellh.vmware": "vmware",
"mitchellh.vmware-esx": "vmware",
"pearkes.digitalocean": "digitalocean",
"packer.googlecompute": "google",
"hashicorp.scaleway": "scaleway",
"packer.parallels": "parallels",
"MSOpenTech.hyperv": "hyperv",
"transcend.qemu": "libvirt",
"ustream.lxc": "lxc",
"Azure.ResourceManagement.VMImage": "azure",
"packer.post-processor.docker-import": "docker",
"packer.post-processor.docker-tag": "docker",
"packer.post-processor.docker-push": "docker",
}
func availableProviders() []string {
dedupedProvidersMap := map[string]string{}
for _, v := range builtins {
dedupedProvidersMap[v] = v
}
dedupedProviders := []string{}
for k := range dedupedProvidersMap {
dedupedProviders = append(dedupedProviders, k)
}
return dedupedProviders
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
CompressionLevel int `mapstructure:"compression_level"`
Include []string `mapstructure:"include"`
OutputPath string `mapstructure:"output"`
Override map[string]interface{}
VagrantfileTemplate string `mapstructure:"vagrantfile_template"`
VagrantfileTemplateGenerated bool `mapstructure:"vagrantfile_template_generated"`
ProviderOverride string `mapstructure:"provider_override"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
}
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec {
return p.config.FlatMapstructure().HCL2Spec()
}
func (p *PostProcessor) Configure(raws ...interface{}) error {
if err := p.configureSingle(&p.config, raws...); err != nil {
return err
}
if p.config.ProviderOverride != "" {
validOverride := false
providers := availableProviders()
for _, prov := range providers {
if prov == p.config.ProviderOverride {
validOverride = true
break
}
}
if !validOverride {
return fmt.Errorf("The given provider_override %s is not valid. "+
"Please choose from one of %s", p.config.ProviderOverride,
strings.Join(providers, ", "))
}
}
return nil
}
func (p *PostProcessor) PostProcessProvider(name string, provider Provider, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, error) {
config, err := p.specificConfig(name)
if err != nil {
return nil, false, err
}
err = CreateDummyBox(ui, config.CompressionLevel)
if err != nil {
return nil, false, err
}
ui.Say(fmt.Sprintf("Creating Vagrant box for '%s' provider", name))
var generatedData map[interface{}]interface{}
stateData := artifact.State("generated_data")
if stateData != nil {
// Make sure it's not a nil map so we can assign to it later.
generatedData = stateData.(map[interface{}]interface{})
}
// If stateData has a nil map generatedData will be nil
// and we need to make sure it's not
if generatedData == nil {
generatedData = make(map[interface{}]interface{})
}
generatedData["ArtifactId"] = artifact.Id()
generatedData["BuildName"] = config.PackerBuildName
generatedData["Provider"] = name
config.ctx.Data = generatedData
outputPath, err := interpolate.Render(config.OutputPath, &config.ctx)
if err != nil {
return nil, false, err
}
// Create a temporary directory for us to build the contents of the box in
dir, err := tmp.Dir("packer")
if err != nil {
return nil, false, err
}
defer os.RemoveAll(dir)
// Copy all of the includes files into the temporary directory
for _, src := range config.Include {
ui.Message(fmt.Sprintf("Copying from include: %s", src))
dst := filepath.Join(dir, filepath.Base(src))
if err := CopyContents(dst, src); err != nil {
err = fmt.Errorf("Error copying include file: %s\n\n%s", src, err)
return nil, false, err
}
}
// Run the provider processing step
vagrantfile, metadata, err := provider.Process(ui, artifact, dir)
if err != nil {
return nil, false, err
}
// Write the metadata we got
if err := WriteMetadata(dir, metadata); err != nil {
return nil, false, err
}
// Write our Vagrantfile
var customVagrantfile string
if config.VagrantfileTemplate != "" {
ui.Message(fmt.Sprintf("Using custom Vagrantfile: %s", config.VagrantfileTemplate))
customBytes, err := ioutil.ReadFile(config.VagrantfileTemplate)
if err != nil {
return nil, false, err
}
customVagrantfile, err = interpolate.Render(string(customBytes), &config.ctx)
if err != nil {
return nil, false, err
}
}
f, err := os.Create(filepath.Join(dir, "Vagrantfile"))
if err != nil {
return nil, false, err
}
t := template.Must(template.New("root").Parse(boxVagrantfileContents))
err = t.Execute(f, &vagrantfileTemplate{
ProviderVagrantfile: vagrantfile,
CustomVagrantfile: customVagrantfile,
})
f.Close()
if err != nil {
return nil, false, err
}
// Create the box
if err := DirToBox(outputPath, dir, ui, config.CompressionLevel); err != nil {
return nil, false, err
}
return NewArtifact(name, outputPath), provider.KeepInputArtifact(), nil
}
func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifact packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
name := p.config.ProviderOverride
if name == "" {
n, ok := builtins[artifact.BuilderId()]
if !ok {
return nil, false, false, fmt.Errorf(
"Unknown artifact type, can't build box: %s", artifact.BuilderId())
}
name = n
}
provider := providerForName(name)
if provider == nil {
if artifact.BuilderId() == artifice.BuilderId {
return nil, false, false, fmt.Errorf(
"Unknown provider type: When using an artifact created by " +
"the artifice post-processor, you need to set the " +
"provider_override option.")
} else {
// This shouldn't happen since we hard code all of these ourselves
panic(fmt.Sprintf("bad provider name: %s", name))
}
}
artifact, keep, err := p.PostProcessProvider(name, provider, ui, artifact)
// In some cases, (e.g. AMI), deleting the input artifact would render the
// resulting vagrant box useless. Because of these cases, we want to
// forcibly set keep_input_artifact.
// TODO: rework all provisioners to only forcibly keep those where it matters
return artifact, keep, true, err
}
func (p *PostProcessor) configureSingle(c *Config, raws ...interface{}) error {
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
InterpolateContext: &c.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"output",
},
},
}, raws...)
if err != nil {
return err
}
// Defaults
if c.OutputPath == "" {
c.OutputPath = "packer_{{ .BuildName }}_{{.Provider}}.box"
}
found := false
for _, k := range md.Keys {
if k == "compression_level" {
found = true
break
}
}
if !found {
c.CompressionLevel = flate.DefaultCompression
}
var errs *packersdk.MultiError
if c.VagrantfileTemplate != "" && c.VagrantfileTemplateGenerated == false {
_, err := os.Stat(c.VagrantfileTemplate)
if err != nil {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf(
"vagrantfile_template '%s' does not exist", c.VagrantfileTemplate))
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}
func (p *PostProcessor) specificConfig(name string) (Config, error) {
config := p.config
if _, ok := config.Override[name]; ok {
if err := mapstructure.Decode(config.Override[name], &config); err != nil {
err = fmt.Errorf("Error overriding config for %s: %s", name, err)
return config, err
}
}
return config, nil
}
func providerForName(name string) Provider {
switch name {
case "aws":
return new(AWSProvider)
case "scaleway":
return new(ScalewayProvider)
case "digitalocean":
return new(DigitalOceanProvider)
case "virtualbox":
return new(VBoxProvider)
case "vmware":
return new(VMwareProvider)
case "parallels":
return new(ParallelsProvider)
case "hyperv":
return new(HypervProvider)
case "libvirt":
return new(LibVirtProvider)
case "google":
return new(GoogleProvider)
case "lxc":
return new(LXCProvider)
case "azure":
return new(AzureProvider)
case "docker":
return new(DockerProvider)
default:
return nil
}
}
type vagrantfileTemplate struct {
ProviderVagrantfile string
CustomVagrantfile string
}
const boxVagrantfileContents string = `
# The contents below were provided by the Packer Vagrant post-processor
{{ .ProviderVagrantfile }}
# The contents below (if any) are custom contents provided by the
# Packer template during image build.
{{ .CustomVagrantfile }}
`

View File

@ -1,59 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package vagrant
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
CompressionLevel *int `mapstructure:"compression_level" cty:"compression_level" hcl:"compression_level"`
Include []string `mapstructure:"include" cty:"include" hcl:"include"`
OutputPath *string `mapstructure:"output" cty:"output" hcl:"output"`
Override map[string]interface{} `cty:"override" hcl:"override"`
VagrantfileTemplate *string `mapstructure:"vagrantfile_template" cty:"vagrantfile_template" hcl:"vagrantfile_template"`
VagrantfileTemplateGenerated *bool `mapstructure:"vagrantfile_template_generated" cty:"vagrantfile_template_generated" hcl:"vagrantfile_template_generated"`
ProviderOverride *string `mapstructure:"provider_override" cty:"provider_override" hcl:"provider_override"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"compression_level": &hcldec.AttrSpec{Name: "compression_level", Type: cty.Number, Required: false},
"include": &hcldec.AttrSpec{Name: "include", Type: cty.List(cty.String), Required: false},
"output": &hcldec.AttrSpec{Name: "output", Type: cty.String, Required: false},
"override": &hcldec.AttrSpec{Name: "override", Type: cty.Map(cty.String), Required: false},
"vagrantfile_template": &hcldec.AttrSpec{Name: "vagrantfile_template", Type: cty.String, Required: false},
"vagrantfile_template_generated": &hcldec.AttrSpec{Name: "vagrantfile_template_generated", Type: cty.Bool, Required: false},
"provider_override": &hcldec.AttrSpec{Name: "provider_override", Type: cty.String, Required: false},
}
return s
}

View File

@ -1,239 +0,0 @@
package vagrant
import (
"bytes"
"compress/flate"
"context"
"io/ioutil"
"os"
"strings"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{}
}
func testPP(t *testing.T) *PostProcessor {
var p PostProcessor
if err := p.Configure(testConfig()); err != nil {
t.Fatalf("err: %s", err)
}
return &p
}
func testUi() *packersdk.BasicUi {
return &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
}
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packersdk.PostProcessor = new(PostProcessor)
}
func TestPostProcessorPrepare_compressionLevel(t *testing.T) {
var p PostProcessor
// Default
c := testConfig()
delete(c, "compression_level")
if err := p.Configure(c); err != nil {
t.Fatalf("err: %s", err)
}
config := p.config
if config.CompressionLevel != flate.DefaultCompression {
t.Fatalf("bad: %#v", config.CompressionLevel)
}
// Set
c = testConfig()
c["compression_level"] = 7
if err := p.Configure(c); err != nil {
t.Fatalf("err: %s", err)
}
config = p.config
if config.CompressionLevel != 7 {
t.Fatalf("bad: %#v", config.CompressionLevel)
}
}
func TestPostProcessorPrepare_outputPath(t *testing.T) {
var p PostProcessor
// Default
c := testConfig()
delete(c, "output")
err := p.Configure(c)
if err != nil {
t.Fatalf("err: %s", err)
}
// Bad template
c["output"] = "bad {{{{.Template}}}}"
err = p.Configure(c)
if err == nil {
t.Fatal("should have error")
}
}
func TestSpecificConfig(t *testing.T) {
var p PostProcessor
// Default
c := testConfig()
c["compression_level"] = 1
c["output"] = "folder"
c["override"] = map[string]interface{}{
"aws": map[string]interface{}{
"compression_level": 7,
},
}
if err := p.Configure(c); err != nil {
t.Fatalf("err: %s", err)
}
// overrides config
config, err := p.specificConfig("aws")
if err != nil {
t.Fatalf("err: %s", err)
}
if config.CompressionLevel != 7 {
t.Fatalf("bad: %#v", config.CompressionLevel)
}
if config.OutputPath != "folder" {
t.Fatalf("bad: %#v", config.OutputPath)
}
// does NOT overrides config
config, err = p.specificConfig("virtualbox")
if err != nil {
t.Fatalf("err: %s", err)
}
if config.CompressionLevel != 1 {
t.Fatalf("bad: %#v", config.CompressionLevel)
}
if config.OutputPath != "folder" {
t.Fatalf("bad: %#v", config.OutputPath)
}
}
func TestPostProcessorPrepare_vagrantfileTemplateExists(t *testing.T) {
f, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
name := f.Name()
c := testConfig()
c["vagrantfile_template"] = name
if err := f.Close(); err != nil {
t.Fatalf("err: %s", err)
}
var p PostProcessor
if err := p.Configure(c); err != nil {
t.Fatal("no error expected as vagrantfile_template exists")
}
if err := os.Remove(name); err != nil {
t.Fatalf("err: %s", err)
}
if err := p.Configure(c); err == nil {
t.Fatal("expected error since vagrantfile_template does not exist and vagrantfile_template_generated is unset")
}
// The vagrantfile_template will be generated during the build process
c["vagrantfile_template_generated"] = true
if err := p.Configure(c); err != nil {
t.Fatal("no error expected due to missing vagrantfile_template as vagrantfile_template_generated is set")
}
}
func TestPostProcessorPrepare_ProviderOverrideExists(t *testing.T) {
c := testConfig()
c["provider_override"] = "foo"
var p PostProcessor
if err := p.Configure(c); err == nil {
t.Fatal("Should have errored since foo is not a valid vagrant provider")
}
c = testConfig()
c["provider_override"] = "aws"
if err := p.Configure(c); err != nil {
t.Fatal("Should not have errored since aws is a valid vagrant provider")
}
}
func TestPostProcessorPostProcess_badId(t *testing.T) {
artifact := &packersdk.MockArtifact{
BuilderIdValue: "invalid.packer",
}
_, _, _, err := testPP(t).PostProcess(context.Background(), testUi(), artifact)
if !strings.Contains(err.Error(), "artifact type") {
t.Fatalf("err: %s", err)
}
}
func TestPostProcessorPostProcess_vagrantfileUserVariable(t *testing.T) {
var p PostProcessor
f, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(f.Name())
c := map[string]interface{}{
"packer_user_variables": map[string]string{
"foo": f.Name(),
},
"vagrantfile_template": "{{user `foo`}}",
}
err = p.Configure(c)
if err != nil {
t.Fatalf("err: %s", err)
}
a := &packersdk.MockArtifact{
BuilderIdValue: "packer.parallels",
}
a2, _, _, err := p.PostProcess(context.Background(), testUi(), a)
if a2 != nil {
for _, fn := range a2.Files() {
defer os.Remove(fn)
}
}
if err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProviderForName(t *testing.T) {
if v, ok := providerForName("virtualbox").(*VBoxProvider); !ok {
t.Fatalf("bad: %#v", v)
}
if providerForName("nope") != nil {
t.Fatal("should be nil if bad provider")
}
}

View File

@ -1,22 +0,0 @@
package vagrant
import (
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// Provider is the interface that each provider must implement in order
// to package the artifacts into a Vagrant-compatible box.
type Provider interface {
// KeepInputArtifact should return true/false whether this provider
// requires the input artifact to be kept by default.
KeepInputArtifact() bool
// Process is called to process an artifact into a Vagrant box. The
// artifact is given as well as the temporary directory path to
// put things.
//
// The Provider should return the contents for the Vagrantfile,
// any metadata (including the provider type in that), and an error
// if any.
Process(packersdk.Ui, packersdk.Artifact, string) (vagrantfile string, metadata map[string]interface{}, err error)
}

View File

@ -1,53 +0,0 @@
package vagrant
import (
"bytes"
"fmt"
"strings"
"text/template"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type scalewayVagrantfileTemplate struct {
Image string ""
Region string ""
}
type ScalewayProvider struct{}
func (p *ScalewayProvider) KeepInputArtifact() bool {
return true
}
func (p *ScalewayProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "scaleway"}
// Determine the image and region...
tplData := &scalewayVagrantfileTemplate{}
parts := strings.Split(artifact.Id(), ":")
if len(parts) != 2 {
err = fmt.Errorf("Poorly formatted artifact ID: %s", artifact.Id())
return
}
tplData.Region = parts[0]
tplData.Image = parts[1]
// Build up the Vagrantfile
var contents bytes.Buffer
t := template.Must(template.New("vf").Parse(defaultScalewayVagrantfile))
err = t.Execute(&contents, tplData)
vagrantfile = contents.String()
return
}
var defaultScalewayVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.provider :scaleway do |scaleway|
scaleway.image = "{{ .Image }}"
scaleway.region = "{{ .Region }}"
end
end
`

View File

@ -1,9 +0,0 @@
// +build !go1.10
package vagrant
import "archive/tar"
func setHeaderFormat(header *tar.Header) {
// no-op
}

View File

@ -1,12 +0,0 @@
// +build go1.10
package vagrant
import "archive/tar"
func setHeaderFormat(header *tar.Header) {
// We have to set the Format explicitly because of a bug in
// libarchive. This affects eg. the tar in macOS listing huge
// files with zero byte length.
header.Format = tar.FormatGNU
}

View File

@ -1,218 +0,0 @@
package vagrant
import (
"archive/tar"
"compress/flate"
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/tmp"
"github.com/klauspost/pgzip"
)
var (
// ErrInvalidCompressionLevel is returned when the compression level passed
// to gzip is not in the expected range. See compress/flate for details.
ErrInvalidCompressionLevel = fmt.Errorf(
"Invalid compression level. Expected an integer from -1 to 9.")
)
// Copies a file by copying the contents of the file to another place.
func CopyContents(dst, src string) error {
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
dstDir, _ := filepath.Split(dst)
if dstDir != "" {
err := os.MkdirAll(dstDir, 0755)
if err != nil {
return err
}
}
dstF, err := os.Create(dst)
if err != nil {
return err
}
defer dstF.Close()
if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
return nil
}
// Creates a (hard) link to a file, ensuring that all parent directories also exist.
func LinkFile(dst, src string) error {
dstDir, _ := filepath.Split(dst)
if dstDir != "" {
err := os.MkdirAll(dstDir, 0755)
if err != nil {
return err
}
}
if err := os.Link(src, dst); err != nil {
return err
}
return nil
}
// DirToBox takes the directory and compresses it into a Vagrant-compatible
// box. This function does not perform checks to verify that dir is
// actually a proper box. This is an expected precondition.
func DirToBox(dst, dir string, ui packersdk.Ui, level int) error {
log.Printf("Turning dir into box: %s => %s", dir, dst)
// Make the containing directory, if it does not already exist
err := os.MkdirAll(filepath.Dir(dst), 0755)
if err != nil {
return err
}
dstF, err := os.Create(dst)
if err != nil {
return err
}
defer dstF.Close()
var dstWriter io.WriteCloser = dstF
if level != flate.NoCompression {
log.Printf("Compressing with gzip compression level: %d", level)
gzipWriter, err := makePgzipWriter(dstWriter, level)
if err != nil {
return err
}
defer gzipWriter.Close()
dstWriter = gzipWriter
}
tarWriter := tar.NewWriter(dstWriter)
defer tarWriter.Close()
// This is the walk func that tars each of the files in the dir
tarWalk := func(path string, info os.FileInfo, prevErr error) error {
// If there was a prior error, return it
if prevErr != nil {
return prevErr
}
// Skip directories
if info.IsDir() {
log.Printf("Skipping directory '%s' for box '%s'", path, dst)
return nil
}
log.Printf("Box add: '%s' to '%s'", path, dst)
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
// go >=1.10 wants to use GNU tar format to workaround issues in
// libarchive < 3.3.2
setHeaderFormat(header)
// We have to set the Name explicitly because it is supposed to
// be a relative path to the root. Otherwise, the tar ends up
// being a bunch of files in the root, even if they're actually
// nested in a dir in the original "dir" param.
header.Name, err = filepath.Rel(dir, path)
if err != nil {
return err
}
if ui != nil {
ui.Message(fmt.Sprintf("Compressing: %s", header.Name))
}
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
if _, err := io.Copy(tarWriter, f); err != nil {
return err
}
return nil
}
// Tar.gz everything up
return filepath.Walk(dir, tarWalk)
}
// CreateDummyBox create a dummy Vagrant-compatible box under temporary dir
// This function is mainly used to check cases such as the host system having
// a GNU tar incompatible uname that will cause the actual Vagrant box creation
// to fail later
func CreateDummyBox(ui packersdk.Ui, level int) error {
ui.Say("Creating a dummy Vagrant box to ensure the host system can create one correctly")
// Create a temporary dir to create dummy Vagrant box from
tempDir, err := tmp.Dir("packer")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
// Write some dummy metadata for the box
if err := WriteMetadata(tempDir, make(map[string]string)); err != nil {
return err
}
// Create the dummy Vagrant box
tempBox, err := tmp.File("box-*.box")
if err != nil {
return err
}
defer tempBox.Close()
defer os.Remove(tempBox.Name())
if err := DirToBox(tempBox.Name(), tempDir, nil, level); err != nil {
return err
}
return nil
}
// WriteMetadata writes the "metadata.json" file for a Vagrant box.
func WriteMetadata(dir string, contents interface{}) error {
if _, err := os.Stat(filepath.Join(dir, "metadata.json")); os.IsNotExist(err) {
f, err := os.Create(filepath.Join(dir, "metadata.json"))
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
return enc.Encode(contents)
}
return nil
}
func makePgzipWriter(output io.WriteCloser, compressionLevel int) (io.WriteCloser, error) {
gzipWriter, err := pgzip.NewWriterLevel(output, compressionLevel)
if err != nil {
return nil, ErrInvalidCompressionLevel
}
gzipWriter.SetConcurrency(500000, runtime.GOMAXPROCS(-1))
return gzipWriter, nil
}

View File

@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var VagrantPostprocessorVersion *version.PluginVersion
func init() {
VagrantPostprocessorVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}

View File

@ -1,179 +0,0 @@
package vagrant
import (
"archive/tar"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type VBoxProvider struct{}
func (p *VBoxProvider) KeepInputArtifact() bool {
return false
}
func (p *VBoxProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "virtualbox"}
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
// We treat OVA files specially, we unpack those into the temporary
// directory so we can get the resulting disk and OVF.
if extension := filepath.Ext(path); extension == ".ova" {
ui.Message(fmt.Sprintf("Unpacking OVA: %s", path))
if err = DecompressOva(dir, path); err != nil {
return
}
} else {
ui.Message(fmt.Sprintf("Copying from artifact: %s", path))
dstPath := filepath.Join(dir, filepath.Base(path))
if err = CopyContents(dstPath, path); err != nil {
return
}
}
}
// Rename the OVF file to box.ovf, as required by Vagrant
ui.Message("Renaming the OVF to box.ovf...")
if err = p.renameOVF(dir); err != nil {
return
}
// Create the Vagrantfile from the template
var baseMacAddress string
baseMacAddress, err = p.findBaseMacAddress(dir)
if err != nil {
return
}
vagrantfile = fmt.Sprintf(vboxVagrantfile, baseMacAddress)
return
}
func (p *VBoxProvider) findOvf(dir string) (string, error) {
log.Println("Looking for OVF in artifact...")
file_matches, err := filepath.Glob(filepath.Join(dir, "*.ovf"))
if err != nil {
return "", err
}
if len(file_matches) > 1 {
return "", errors.New("More than one OVF file in VirtualBox artifact.")
}
if len(file_matches) < 1 {
return "", errors.New("ovf file couldn't be found")
}
return file_matches[0], err
}
func (p *VBoxProvider) renameOVF(dir string) error {
log.Println("Looking for OVF to rename...")
ovf, err := p.findOvf(dir)
if err != nil {
return err
}
log.Printf("Renaming: '%s' => box.ovf", ovf)
return os.Rename(ovf, filepath.Join(dir, "box.ovf"))
}
func (p *VBoxProvider) findBaseMacAddress(dir string) (string, error) {
log.Println("Looking for OVF for base mac address...")
ovf, err := p.findOvf(dir)
if err != nil {
return "", err
}
f, err := os.Open(ovf)
if err != nil {
return "", err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return "", err
}
re := regexp.MustCompile(`<Adapter slot="0".+?MACAddress="(.+?)"`)
matches := re.FindSubmatch(data)
if matches == nil {
return "", errors.New("can't find base mac address in OVF")
}
log.Printf("Base mac address: %s", string(matches[1]))
return string(matches[1]), nil
}
// DecompressOva takes an ova file and decompresses it into the target
// directory.
func DecompressOva(dir, src string) error {
log.Printf("Turning ova to dir: %s => %s", src, dir)
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
tarReader := tar.NewReader(srcF)
for {
hdr, err := tarReader.Next()
if hdr == nil || err == io.EOF {
break
}
if err != nil {
return err
}
// We use the fileinfo to get the file name because we are not
// expecting path information as from the tar header. It's important
// that we not use the path name from the tar header without checking
// for the presence of `..`. If we accidentally allow for that, we can
// open ourselves up to a path traversal vulnerability.
info := hdr.FileInfo()
// Shouldn't be any directories, skip them
if info.IsDir() {
continue
}
// We wrap this in an anonymous function so that the defers
// inside are handled more quickly so we can give up file handles.
err = func() error {
path := filepath.Join(dir, info.Name())
output, err := os.Create(path)
if err != nil {
return err
}
defer output.Close()
os.Chmod(path, info.Mode())
os.Chtimes(path, hdr.AccessTime, hdr.ModTime)
_, err = io.Copy(output, tarReader)
return err
}()
if err != nil {
return err
}
}
return nil
}
var vboxVagrantfile = `
Vagrant.configure("2") do |config|
config.vm.base_mac = "%s"
end
`

View File

@ -1,28 +0,0 @@
package vagrant
import (
"os"
"path/filepath"
"testing"
"github.com/hashicorp/packer-plugin-sdk/tmp"
"github.com/stretchr/testify/assert"
)
func TestVBoxProvider_impl(t *testing.T) {
var _ Provider = new(VBoxProvider)
}
func TestDecomressOVA(t *testing.T) {
td, err := tmp.Dir("pp-vagrant-virtualbox")
assert.NoError(t, err)
defer os.RemoveAll(td)
fixture := "./test-fixtures/decompress-tar/outside_parent.tar"
err = DecompressOva(td, fixture)
assert.NoError(t, err)
_, err = os.Stat(filepath.Join(filepath.Base(td), "demo.poc"))
assert.Error(t, err)
_, err = os.Stat(filepath.Join(td, "demo.poc"))
assert.NoError(t, err)
}

View File

@ -1,31 +0,0 @@
package vagrant
import (
"fmt"
"path/filepath"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type VMwareProvider struct{}
func (p *VMwareProvider) KeepInputArtifact() bool {
return false
}
func (p *VMwareProvider) Process(ui packersdk.Ui, artifact packersdk.Artifact, dir string) (vagrantfile string, metadata map[string]interface{}, err error) {
// Create the metadata
metadata = map[string]interface{}{"provider": "vmware_desktop"}
// Copy all of the original contents into the temporary directory
for _, path := range artifact.Files() {
ui.Message(fmt.Sprintf("Copying: %s", path))
dstPath := filepath.Join(dir, filepath.Base(path))
if err = CopyContents(dstPath, path); err != nil {
return
}
}
return
}

View File

@ -1,9 +0,0 @@
package vagrant
import (
"testing"
)
func TestVMwareProvider_impl(t *testing.T) {
var _ Provider = new(VMwareProvider)
}

View File

@ -753,10 +753,6 @@
"title": "Triton",
"path": "builders/triton"
},
{
"title": "Vagrant",
"path": "builders/vagrant"
},
{
"title": "Yandex.Cloud",
"path": "builders/yandex"
@ -868,14 +864,6 @@
"title": "Shell (Local)",
"path": "post-processors/shell-local"
},
{
"title": "Vagrant",
"path": "post-processors/vagrant"
},
{
"title": "Vagrant Cloud",
"path": "post-processors/vagrant-cloud"
},
{
"title": "Yandex.Cloud Compute Export",
"path": "post-processors/yandex-export"

View File

@ -134,6 +134,13 @@
"version": "latest",
"pluginTier": "community"
},
{
"title": "Vagrant",
"path": "vagrant",
"repo": "hashicorp/packer-plugin-vagrant",
"pluginTier": "official",
"version": "latest"
},
{
"title": "VirtualBox",
"path": "virtualbox",