Extract vagrant (#10960)
* remove vagrant, rework website * regenerate command/plugin, and go mod tidy
This commit is contained in:
parent
ccbd0a29cc
commit
af37f53439
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
5
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -1,9 +0,0 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDockerProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(DockerProvider)
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLXCProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(LXCProvider)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParallelsProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(ParallelsProvider)
|
||||
}
|
|
@ -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 }}
|
||||
`
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -1,9 +0,0 @@
|
|||
// +build !go1.10
|
||||
|
||||
package vagrant
|
||||
|
||||
import "archive/tar"
|
||||
|
||||
func setHeaderFormat(header *tar.Header) {
|
||||
// no-op
|
||||
}
|
|
@ -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
|
||||
}
|
Binary file not shown.
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVMwareProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(VMwareProvider)
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue