Merge pull request #7221 from hashicorp/vagrant_builder
Vagrant builder
This commit is contained in:
commit
75f574bd4b
|
@ -0,0 +1,50 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/packer/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
|
||||
}
|
||||
|
||||
// NewArtifact returns a vagrant artifact containing the .box file
|
||||
func NewArtifact(dir string) (packer.Artifact, error) {
|
||||
return &artifact{
|
||||
OutputDir: dir,
|
||||
BoxName: "package.box",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *artifact) Files() []string {
|
||||
return []string{a.BoxName}
|
||||
}
|
||||
|
||||
func (a *artifact) Id() string {
|
||||
return filepath.Join(a.OutputDir, a.BoxName)
|
||||
}
|
||||
|
||||
func (a *artifact) String() string {
|
||||
return fmt.Sprintf("Vagrant box is %s", a.Id())
|
||||
}
|
||||
|
||||
func (a *artifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *artifact) Destroy() error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var raw interface{} = &artifact{}
|
||||
|
||||
if _, ok := raw.(packer.Artifact); !ok {
|
||||
t.Fatalf("Artifact does not implement packer.Artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
a := &artifact{
|
||||
OutputDir: "/my/dir",
|
||||
BoxName: "package.box",
|
||||
}
|
||||
|
||||
expected := "/my/dir/package.box"
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = strings.Replace(expected, "/", "\\", -1)
|
||||
}
|
||||
if strings.Compare(a.Id(), expected) != 0 {
|
||||
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",
|
||||
}
|
||||
expected := "Vagrant box is /my/dir/package.box"
|
||||
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())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// Builder implements packer.Builder and builds the actual VirtualBox
|
||||
// images.
|
||||
type Builder struct {
|
||||
config *Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
type SSHConfig struct {
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
common.HTTPConfig `mapstructure:",squash"`
|
||||
common.ISOConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
bootcommand.BootConfig `mapstructure:",squash"`
|
||||
SSHConfig `mapstructure:",squash"`
|
||||
|
||||
// This is the name of the new virtual machine.
|
||||
// By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build.
|
||||
OutputDir string `mapstructure:"output_dir"`
|
||||
SourceBox string `mapstructure:"source_path"`
|
||||
GlobalID string `mapstructure:"global_id"`
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
ChecksumType string `mapstructure:"checksum_type"`
|
||||
BoxName string `mapstructure:"box_name"`
|
||||
|
||||
Provider string `mapstructure:"provider"`
|
||||
|
||||
Communicator string `mapstructure:"communicator"`
|
||||
|
||||
// What vagrantfile to use
|
||||
VagrantfileTpl string `mapstructure:"vagrantfile_template"`
|
||||
|
||||
// Whether to Halt, Suspend, or Destroy the box
|
||||
TeardownMethod string `mapstructure:"teardown_method"`
|
||||
|
||||
// Options for the "vagrant init" command
|
||||
BoxVersion string `mapstructure:"box_version"`
|
||||
Template string `mapstructure:"template"`
|
||||
SyncedFolder string `mapstructure:"synced_folder"`
|
||||
|
||||
// Options for the "vagrant box add" command
|
||||
SkipAdd bool `mapstructure:"skip_add"`
|
||||
AddCACert string `mapstructure:"add_cacert"`
|
||||
AddCAPath string `mapstructure:"add_capath"`
|
||||
AddCert string `mapstructure:"add_cert"`
|
||||
AddClean bool `mapstructure:"add_clean"`
|
||||
AddForce bool `mapstructure:"add_force"`
|
||||
AddInsecure bool `mapstructure:"add_insecure"`
|
||||
|
||||
// Don't package the Vagrant box after build.
|
||||
SkipPackage bool `mapstructure:"skip_package"`
|
||||
OutputVagrantfile string `mapstructure:"output_vagrantfile"`
|
||||
PackageInclude []string `mapstructure:"package_include"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
// Prepare processes the build configuration parameters.
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
b.config = new(Config)
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"boot_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors and warnings
|
||||
var errs *packer.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 = packer.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 = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required unless you have set global_id"))
|
||||
}
|
||||
} else {
|
||||
if b.config.GlobalID != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You may either set global_id or source_path but not both"))
|
||||
}
|
||||
if strings.HasSuffix(b.config.SourceBox, ".box") {
|
||||
b.config.SourceBox, err = common.ValidatedURL(b.config.SourceBox)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
|
||||
}
|
||||
fileOK := common.FileExistsLocally(b.config.SourceBox)
|
||||
if !fileOK {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Source file '%s' needs to exist at time of config validation!", b.config.SourceBox))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf(`TeardownMethod must be "halt", "suspend", or "destroy"`))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a VirtualBox appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.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("cache", cache)
|
||||
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, &common.StepDownload{
|
||||
Checksum: b.config.Checksum,
|
||||
ChecksumType: b.config.ChecksumType,
|
||||
Description: "Box",
|
||||
Extension: "box",
|
||||
ResultKey: "box_path",
|
||||
Url: []string{b.config.SourceBox},
|
||||
})
|
||||
}
|
||||
steps = append(steps,
|
||||
&common.StepOutputDir{
|
||||
Force: b.config.PackerForce,
|
||||
Path: b.config.OutputDir,
|
||||
},
|
||||
&StepCreateVagrantfile{
|
||||
Template: b.config.Template,
|
||||
SyncedFolder: b.config.SyncedFolder,
|
||||
SourceBox: b.config.SourceBox,
|
||||
OutputDir: b.config.OutputDir,
|
||||
GlobalID: b.config.GlobalID,
|
||||
},
|
||||
&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.SSHConfig.Comm,
|
||||
Host: CommHost(),
|
||||
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
new(common.StepProvision),
|
||||
&StepPackage{
|
||||
SkipPackage: b.config.SkipPackage,
|
||||
Include: b.config.PackageInclude,
|
||||
Vagrantfile: b.config.OutputVagrantfile,
|
||||
GlobalID: b.config.GlobalID,
|
||||
})
|
||||
|
||||
// Run the steps.
|
||||
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||
b.runner.Run(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.")
|
||||
}
|
||||
|
||||
return NewArtifact(b.config.OutputDir)
|
||||
}
|
||||
|
||||
// Cancel.
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_ValidateSource(t *testing.T) {
|
||||
b := &Builder{}
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := b.Prepare(tc.config)
|
||||
if (err != nil) != tc.errExpected {
|
||||
t.Fatalf("Unexpected behavior from test case %#v; %s.", tc.config, tc.reason)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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 {
|
||||
args = append(args, "--output", filepath.Join(d.VagrantCWD, "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)
|
||||
vers, err := d.Version()
|
||||
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 out
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
stdout, _, err := d.vagrantCmd(args...)
|
||||
sshConf := &VagrantSSHConfig{}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
sshConf.Hostname = parseSSHConfig(lines, "HostName ")
|
||||
sshConf.User = parseSSHConfig(lines, "User ")
|
||||
sshConf.Port = parseSSHConfig(lines, "Port ")
|
||||
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
|
||||
}
|
||||
|
||||
func (d *Vagrant_2_2_Driver) vagrantCmd(args ...string) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
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))
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("Vagrant error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("[vagrant driver] stdout: %s", stdoutString)
|
||||
log.Printf("[vagrant driver] stderr: %s", stderrString)
|
||||
|
||||
return stdoutString, stderrString, err
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/helper/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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/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(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.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..")
|
||||
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) {
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateVagrantfile struct {
|
||||
Template string
|
||||
SourceBox string
|
||||
OutputDir string
|
||||
SyncedFolder string
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
var DEFAULT_TEMPLATE = `Vagrant.configure("2") do |config|
|
||||
config.vm.box = "{{.BoxName}}"
|
||||
{{ if ne .SyncedFolder "" -}}
|
||||
config.vm.synced_folder "{{.SyncedFolder}}", "/vagrant"
|
||||
{{- else -}}
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
{{- end}}
|
||||
end`
|
||||
|
||||
type VagrantfileOptions struct {
|
||||
SyncedFolder string
|
||||
BoxName string
|
||||
}
|
||||
|
||||
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.SourceBox,
|
||||
}
|
||||
|
||||
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(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.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) {
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/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: "bananas",
|
||||
}
|
||||
templatePath, err := testy.createVagrantfile()
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.Remove(templatePath)
|
||||
contents, err := ioutil.ReadFile(templatePath)
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.box = "bananas"
|
||||
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)
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.box = ""
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepPackage struct {
|
||||
SkipPackage bool
|
||||
Include []string
|
||||
Vagrantfile string
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
func (s *StepPackage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.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{}
|
||||
if s.GlobalID != "" {
|
||||
packageArgs = append(packageArgs, s.GlobalID)
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/packer/helper/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(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
sshConfig, err := driver.SSHConfig(s.GlobalID)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
config.Comm.SSHPrivateKeyFile = sshConfig.IdentityFile
|
||||
config.Comm.SSHUsername = sshConfig.User
|
||||
config.Comm.SSHHost = sshConfig.Hostname
|
||||
port, err := strconv.Atoi(sshConfig.Port)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
config.Comm.SSHPort = port
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSSHConfig) Cleanup(state multistep.StateBag) {
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepUp struct {
|
||||
TeardownMethod string
|
||||
Provider string
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
func (s *StepUp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Calling Vagrant Up...")
|
||||
|
||||
var args []string
|
||||
if s.GlobalID != "" {
|
||||
args = append(args, s.GlobalID)
|
||||
}
|
||||
|
||||
if s.Provider != "" {
|
||||
args = append(args, fmt.Sprintf("--provider=%s", s.Provider))
|
||||
}
|
||||
|
||||
_, _, 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").(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("%sing Vagrant box...", s.TeardownMethod))
|
||||
|
||||
var err error
|
||||
if s.TeardownMethod == "halt" {
|
||||
err = driver.Halt(s.GlobalID)
|
||||
} else if s.TeardownMethod == "suspend" {
|
||||
err = driver.Suspend(s.GlobalID)
|
||||
} else if s.TeardownMethod == "destroy" {
|
||||
err = driver.Destroy(s.GlobalID)
|
||||
} else {
|
||||
// Should never get here because of template validation
|
||||
state.Put("error", fmt.Errorf("Invalid teardown method selected; must be either halt, suspend, or destory."))
|
||||
}
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error halting Vagrant machine; please try to do this manually"))
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ import (
|
|||
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
|
||||
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
||||
tritonbuilder "github.com/hashicorp/packer/builder/triton"
|
||||
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
|
||||
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
|
||||
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
|
||||
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
|
||||
|
@ -118,6 +119,7 @@ var Builders = map[string]packer.Builder{
|
|||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
"vmware-iso": new(vmwareisobuilder.Builder),
|
||||
|
|
|
@ -147,28 +147,30 @@ builder.
|
|||
|
||||
Snapshots from on or before July 15, 2013 cannot be used to create a
|
||||
disk.
|
||||
|
||||
- `image_ignore_data_disks`(boolean) - If this value is true, the image created
|
||||
will not include any snapshot of data disks. This option would be useful for
|
||||
any circumstance that default data disks with instance types are not concerned.
|
||||
The default value is false.
|
||||
|
||||
- `wait_snapshot_ready_timeout`(number) - Timeout of creating snapshot(s). The
|
||||
default timeout is 3600 seconds if this option is not set or is set to 0. For
|
||||
those disks containing lots of data, it may require a higher timeout value.
|
||||
- `image_ignore_data_disks`(boolean) - If this value is true, the image
|
||||
created will not include any snapshot of data disks. This option would be
|
||||
useful for any circumstance that default data disks with instance types are
|
||||
not concerned. The default value is false.
|
||||
|
||||
- `wait_snapshot_ready_timeout`(number) - Timeout of creating snapshot(s).
|
||||
The default timeout is 3600 seconds if this option is not set or is set
|
||||
to 0. For those disks containing lots of data, it may require a higher
|
||||
timeout value.
|
||||
|
||||
- `image_force_delete` (boolean) - If this value is true, when the target
|
||||
image names including those copied are duplicated with existing images,
|
||||
it will delete the existing images and then create the target images,
|
||||
otherwise, the creation will fail. The default value is false. Check
|
||||
`image_name` and `image_copy_names` options for names of target images.
|
||||
If [-force](https://packer.io/docs/commands/build.html#force) option is provided
|
||||
in `build` command, this option can be omitted and taken as true.
|
||||
image names including those copied are duplicated with existing images, it
|
||||
will delete the existing images and then create the target images,
|
||||
otherwise, the creation will fail. The default value is false. Check
|
||||
`image_name` and `image_copy_names` options for names of target images. If
|
||||
[-force](https://packer.io/docs/commands/build.html#force) option is
|
||||
provided in `build` command, this option can be omitted and taken as true.
|
||||
|
||||
- `image_force_delete_snapshots` (boolean) - If this value is true, when
|
||||
delete the duplicated existing images, the source snapshots of those images
|
||||
will be delete either. If [-force](https://packer.io/docs/commands/build.html#force)
|
||||
option is provided in `build` command, this option can be omitted and taken as true.
|
||||
will be delete either. If
|
||||
[-force](https://packer.io/docs/commands/build.html#force) option is
|
||||
provided in `build` command, this option can be omitted and taken as true.
|
||||
|
||||
- `image_share_account` (array of string) - The IDs of to-be-added Aliyun
|
||||
accounts to which the image is shared. The number of accounts is 1 to 10.
|
||||
|
@ -299,10 +301,13 @@ Here is a basic example for Alicloud.
|
|||
\~> Note: Images can become deprecated after a while; run
|
||||
`aliyun ecs DescribeImages` to find one that exists.
|
||||
|
||||
\~> Note: Since WinRM is closed by default in the system image. If you are planning
|
||||
to use Windows as the base image, you need enable it by userdata in order to connect to
|
||||
the instance, check [alicloud_windows.json](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/alicloud_windows.json)
|
||||
and [winrm_enable_userdata.ps1](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/winrm_enable_userdata.ps1) for details.
|
||||
\~> Note: Since WinRM is closed by default in the system image. If you are
|
||||
planning to use Windows as the base image, you need enable it by userdata in
|
||||
order to connect to the instance, check
|
||||
[alicloud\_windows.json](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/alicloud_windows.json)
|
||||
and
|
||||
[winrm\_enable\_userdata.ps1](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/winrm_enable_userdata.ps1)
|
||||
for details.
|
||||
|
||||
See the
|
||||
[examples/alicloud](https://github.com/hashicorp/packer/tree/master/examples/alicloud)
|
||||
|
|
|
@ -218,9 +218,9 @@ fail. If that's the case, you might see an error like this:
|
|||
==> amazon-ebs: Error querying AMI: AuthFailure: AWS was not able to validate the provided access credentials
|
||||
|
||||
If you suspect your system's date is wrong, you can compare it against
|
||||
<http://www.time.gov/>. On Linux/OS X, you can run the `date` command to get
|
||||
the current time. If you're on Linux, you can try setting the time with ntp by
|
||||
running `sudo ntpd -q`.
|
||||
<http://www.time.gov/>. On
|
||||
Linux/OS X, you can run the `date` command to get the current time. If you're
|
||||
on Linux, you can try setting the time with ntp by running `sudo ntpd -q`.
|
||||
|
||||
### `exceeded wait attempts` while waiting for tasks to complete
|
||||
|
||||
|
|
|
@ -206,8 +206,8 @@ Providing `temp_resource_group_name` or `location` in combination with
|
|||
- `os_disk_size_gb` (number) Specify the size of the OS disk in GB
|
||||
(gigabytes). Values of zero or less than zero are ignored.
|
||||
|
||||
- `disk_caching_type` (string) Specify the disk caching type. Valid values are None, ReadOnly,
|
||||
and ReadWrite. The default value is ReadWrite.
|
||||
- `disk_caching_type` (string) Specify the disk caching type. Valid values
|
||||
are None, ReadOnly, and ReadWrite. The default value is ReadWrite.
|
||||
|
||||
- `disk_additional_size` (array of integers) - The size(s) of any additional
|
||||
hard disks for the VM in gigabytes. If this is not specified then the VM
|
||||
|
@ -325,13 +325,14 @@ Providing `temp_resource_group_name` or `location` in combination with
|
|||
value and defaults to false. **Important** Setting this true means that
|
||||
your builds are faster, however any failed deletes are not reported.
|
||||
|
||||
- `managed_image_os_disk_snapshot_name` (string) If managed\_image\_os\_disk\_snapshot\_name
|
||||
is set, a snapshot of the OS disk is created with the same name as this value before the
|
||||
VM is captured.
|
||||
- `managed_image_os_disk_snapshot_name` (string) If
|
||||
managed\_image\_os\_disk\_snapshot\_name is set, a snapshot of the OS disk
|
||||
is created with the same name as this value before the VM is captured.
|
||||
|
||||
- `managed_image_data_disk_snapshot_prefix` (string) If managed\_image\_data\_disk\_snapshot\_prefix
|
||||
is set, snapshot of the data disk(s) is created with the same prefix as this value before the VM
|
||||
is captured.
|
||||
- `managed_image_data_disk_snapshot_prefix` (string) If
|
||||
managed\_image\_data\_disk\_snapshot\_prefix is set, snapshot of the data
|
||||
disk(s) is created with the same prefix as this value before the VM is
|
||||
captured.
|
||||
|
||||
## Basic Example
|
||||
|
||||
|
@ -403,14 +404,14 @@ here](https://technet.microsoft.com/en-us/library/hh824815.aspx)
|
|||
}
|
||||
```
|
||||
|
||||
The Windows Guest Agent participates in the Sysprep process. The
|
||||
agent must be fully installed before the VM can be sysprep'ed. To
|
||||
ensure this is true all agent services must be running before
|
||||
executing sysprep.exe. The above JSON snippet shows one way to do this
|
||||
in the PowerShell provisioner. This snippet is **only** required if
|
||||
the VM is configured to install the agent, which is the default. To
|
||||
learn more about disabling the Windows Guest Agent please see [Install the VM Agent](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/agent-windows#install-the-vm-agent).
|
||||
|
||||
The Windows Guest Agent participates in the Sysprep process. The agent must be
|
||||
fully installed before the VM can be sysprep'ed. To ensure this is true all
|
||||
agent services must be running before executing sysprep.exe. The above JSON
|
||||
snippet shows one way to do this in the PowerShell provisioner. This snippet is
|
||||
**only** required if the VM is configured to install the agent, which is the
|
||||
default. To learn more about disabling the Windows Guest Agent please see
|
||||
[Install the VM
|
||||
Agent](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/agent-windows#install-the-vm-agent).
|
||||
|
||||
### Linux
|
||||
|
||||
|
|
|
@ -184,8 +184,8 @@ You must specify (only) one of `commit`, `discard`, or `export_path`.
|
|||
`login_username`, and `login_password` will be ignored. For more
|
||||
information see the [section on ECR](#amazon-ec2-container-registry).
|
||||
|
||||
- `exec_user` (string) - Username or UID (format:
|
||||
<name\\\|uid>\[:<group\\\|gid>\]) to run remote commands with.
|
||||
- `exec_user` (string) - Username (UID) to run remote commands with. You can
|
||||
also set the group name/ID if you want: (UID or UID:GID).
|
||||
You may need this if you get permission errors trying to run the `shell` or
|
||||
other provisioners.
|
||||
|
||||
|
@ -380,8 +380,8 @@ portable provisioning scripts.
|
|||
|
||||
## Overriding the host directory
|
||||
|
||||
By default, Packer creates a temporary folder under your home directory, and
|
||||
uses that to stage files for uploading into the container. If you would like to
|
||||
By default, Packer creates a temporary folder under your home directory, and
|
||||
uses that to stage files for uploading into the container. If you would like to
|
||||
change the path to this temporary folder, you can set the `PACKER_TMP_DIR`.
|
||||
This can be useful, for example, if you have your home directory permissions
|
||||
set up to disallow access from the docker daemon.
|
||||
|
|
|
@ -18,9 +18,8 @@ based on existing images.
|
|||
|
||||
It is possible to build images from scratch, but not with the `googlecompute`
|
||||
Packer builder. The process is recommended only for advanced users, please see
|
||||
\[Building GCE Images from Scratch\]
|
||||
(<https://cloud.google.com/compute/docs/tutorials/building-images>) and the
|
||||
[Google Compute Import
|
||||
[Building GCE Images from Scratch](https://cloud.google.com/compute/docs/tutorials/building-images)
|
||||
and the [Google Compute Import
|
||||
Post-Processor](/docs/post-processors/googlecompute-import.html) for more
|
||||
information.
|
||||
|
||||
|
@ -139,7 +138,7 @@ using the gcloud command.
|
|||
gcloud compute firewall-rules create allow-winrm --allow tcp:5986
|
||||
|
||||
Or alternatively by navigating to
|
||||
<https://console.cloud.google.com/networking/firewalls/list>.
|
||||
<a href="https://console.cloud.google.com/networking/firewalls/list" class="uri">https://console.cloud.google.com/networking/firewalls/list</a>.
|
||||
|
||||
Once this is set up, the following is a complete working packer config after
|
||||
setting a valid `account_file` and `project_id`:
|
||||
|
|
|
@ -75,7 +75,9 @@ builder.
|
|||
- `ssh_keys` (array of strings) - List of SSH keys by name or id to be added
|
||||
to image on launch.
|
||||
|
||||
- `rescue` (string) - Enable and boot in to the specified rescue system. This enables simple installation of custom operating systems. `linux64` `linux32` or `freebsd64`
|
||||
- `rescue` (string) - Enable and boot in to the specified rescue system. This
|
||||
enables simple installation of custom operating systems. `linux64`
|
||||
`linux32` or `freebsd64`
|
||||
|
||||
## Basic Example
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ as a tar.gz of the root file system.
|
|||
The LXC builder requires a modern linux kernel and the `lxc` or `lxc1` package.
|
||||
This builder does not work with LXD.
|
||||
|
||||
~> Note: to build Centos images on a Debian family host, you will need the
|
||||
\~> Note: to build Centos images on a Debian family host, you will need the
|
||||
`yum` package installed. <br>Some provisioners such as `ansible-local` get
|
||||
confused when running in a container of a different family. E.G. it will
|
||||
attempt to use `apt-get` to install packages, when running in a Centos
|
||||
|
|
|
@ -47,7 +47,7 @@ Below is a fully functioning example.
|
|||
container. This can be a (local or remote) image (name or fingerprint).
|
||||
E.G. `my-base-image`, `ubuntu-daily:x`, `08fababf6f27`, ...
|
||||
|
||||
~> Note: The builder may appear to pause if required to download a
|
||||
\~> Note: The builder may appear to pause if required to download a
|
||||
remote image, as they are usually 100-200MB. `/var/log/lxd/lxd.log` will
|
||||
mention starting such downloads.
|
||||
|
||||
|
@ -68,8 +68,8 @@ Below is a fully functioning example.
|
|||
- `publish_properties` (map\[string\]string) - Pass key values to the publish
|
||||
step to be set as properties on the output image. This is most helpful to
|
||||
set the description, but can be used to set anything needed. See
|
||||
<https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/> for more
|
||||
properties.
|
||||
<a href="https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/" class="uri">https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/</a>
|
||||
for more properties.
|
||||
|
||||
- `launch_config` (map\[string\]string) - List of key/value pairs you wish to
|
||||
pass to `lxc launch` via `--config`. Defaults to empty.
|
||||
|
|
|
@ -90,7 +90,6 @@ Here is a basic example for windows server.
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
Here is a basic example for linux server.
|
||||
|
||||
{
|
||||
|
|
|
@ -278,8 +278,8 @@ builder.
|
|||
isn't specified, the default enforced by your OpenStack cluster will be
|
||||
used.
|
||||
|
||||
- `volume_size` (int) - Size of the Block Storage service volume in GB. If this
|
||||
isn't specified, it is set to source image min disk value (if set) or
|
||||
- `volume_size` (int) - Size of the Block Storage service volume in GB. If
|
||||
this isn't specified, it is set to source image min disk value (if set) or
|
||||
calculated from the source image bytes size. Note that in some cases this
|
||||
needs to be specified, if `use_blockstorage_volume` is true.
|
||||
|
||||
|
|
|
@ -141,9 +141,10 @@ If this is set, a few more options become available.
|
|||
- `builder_communicator` (communicator) - This represents an
|
||||
[`ssh communicator`](/docs/templates/communicator.html#ssh-communicator),
|
||||
and can be configured as such. If you use a different builder image, you
|
||||
may need to change the `ssh_username`, for example. That might look like this:
|
||||
may need to change the `ssh_username`, for example. That might look like
|
||||
this:
|
||||
|
||||
```json
|
||||
``` json
|
||||
{
|
||||
"builders": [
|
||||
{
|
||||
|
@ -170,7 +171,7 @@ If this is set, a few more options become available.
|
|||
amount of memory, and other resources allocated to the builder instance.
|
||||
Default: `oc3`.
|
||||
|
||||
* `builder_upload_image_command` (string) - The command to run to upload the
|
||||
- `builder_upload_image_command` (string) - The command to run to upload the
|
||||
image to Object Storage Classic. This is for advanced users only, and you
|
||||
should consult the default in code to decide on the changes to make. For
|
||||
most users the default should suffice.
|
||||
|
@ -272,7 +273,7 @@ attributes file:
|
|||
Here is an example using a persistent volume. Note the `persistent_volume_size`
|
||||
setting.
|
||||
|
||||
```json
|
||||
``` json
|
||||
{
|
||||
"variables": {
|
||||
"opc_username": "{{ env `OPC_USERNAME`}}",
|
||||
|
|
|
@ -48,8 +48,8 @@ builder.
|
|||
|
||||
- `image` (string) - The UUID of the base image to use. This is the image
|
||||
that will be used to launch a new server and provision it. See
|
||||
<https://api-marketplace.scaleway.com/images> get the complete list of the
|
||||
accepted image UUID.
|
||||
[the images list](https://api-marketplace.scaleway.com/images)
|
||||
get the complete list of the accepted image UUID.
|
||||
|
||||
- `region` (string) - The name of the region to launch the server in (`par1`
|
||||
or `ams1`). Consequently, this is the region where the snapshot will be
|
||||
|
|
|
@ -30,7 +30,7 @@ This reusable image can then be used to launch new machines.
|
|||
The builder does *not* manage images. Once it creates an image, it is up to you
|
||||
to use it or delete it.
|
||||
|
||||
~> **Private installations of Triton must have custom images enabled!** To
|
||||
\~> **Private installations of Triton must have custom images enabled!** To
|
||||
use the Triton builder with a private/on-prem installation of Joyent's Triton
|
||||
software, you'll need an operator to manually [enable custom
|
||||
images](https://docs.joyent.com/private-cloud/install/image-management) after
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
The Vagrant builder is intended for building new boxes from already-existing
|
||||
boxes. Your source should be a URL or path to a .box file or a Vagrant Cloud
|
||||
box name such as `hashicorp/precise64`.
|
||||
|
||||
Packer will not install vagrant, nor will it install the underlying
|
||||
virtualization platforms or extra providers; We expect when you run this
|
||||
builder that you have already installed what you need.
|
||||
|
||||
By default, this builder will initialize a new Vagrant workspace, launch your
|
||||
box from that workspace, provision it, call `vagrant package` to package it
|
||||
into a new box, and then destroy the original box. Please note that vagrant
|
||||
will _not_ remove the box file from your system (we don't call
|
||||
`vagrant box remove`).
|
||||
|
||||
You can change the behavior so that the builder doesn't destroy the box by
|
||||
setting the `teardown_method` option. You can change the behavior so the builder
|
||||
doesn't package it (not all provisioners support the `vagrant package` command)
|
||||
by setting the `skip package` option. You can also change the behavior so that
|
||||
rather than inititalizing a new Vagrant workspace, you use an already defined
|
||||
one, by using `global_id` instead of `source_box`.
|
||||
|
||||
Required:
|
||||
|
||||
- `source_path` (string) - 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.
|
||||
|
||||
or
|
||||
|
||||
- `global_id` (string) - 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.
|
||||
|
||||
Optional:
|
||||
|
||||
- `output_dir` (string) - 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.
|
||||
|
||||
- `box_name` (string) - 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.
|
||||
|
||||
- `checksum` (string) - The checksum for the .box file. The type of the
|
||||
checksum is specified with `checksum_type`, documented below.
|
||||
|
||||
- `checksum_type` (string) - The type of the checksum specified in `checksum`.
|
||||
Valid values are `none`, `md5`, `sha1`, `sha256`, or `sha512`. Although the
|
||||
checksum will not be verified when `checksum_type` is set to "none", this is
|
||||
not recommended since OVA files can be very large and corruption does happen
|
||||
from time to time.
|
||||
|
||||
- `vagrantfile_template` (string) - a path to a golang template for a
|
||||
vagrantfile. Our default template can be found
|
||||
[here](https://github.com/hashicorp/packer/tree/master/builder/vagrant/step_initialize_vagrant.go#L23-L30). So far the only template variables available to you are {{ .BoxName }} and
|
||||
{{ .SyncedFolder }}, which correspond to the Packer options `box_name` and
|
||||
`synced_folder`
|
||||
|
||||
- `skip_add` (string) - Don't call "vagrant add" to add the box to your local
|
||||
environment; this is necesasry if you want to launch a box that is already
|
||||
added to your vagrant environment.
|
||||
|
||||
- `teardown_method` (string) - Whether to halt, suspend, or destroy the box when
|
||||
the build has completed. Defaults to "halt"
|
||||
|
||||
- `box_version` (string) - What box version to use when initializing Vagrant.
|
||||
|
||||
- `add_cacert` (string) - Equivalent to setting the
|
||||
[`--cacert`](https://www.vagrantup.com/docs/cli/box.html#cacert-certfile)
|
||||
option in `vagrant add`; defaults to unset.
|
||||
|
||||
- `add_capath` (string) - Equivalent to setting the
|
||||
[`--capath`](https://www.vagrantup.com/docs/cli/box.html#capath-certdir) option
|
||||
in `vagrant add`; defaults to unset.
|
||||
|
||||
- `add_cert` (string) - Equivalent to setting the
|
||||
[`--cert`](https://www.vagrantup.com/docs/cli/box.html#cert-certfile) option in
|
||||
`vagrant add`; defaults to unset.
|
||||
|
||||
- `add_clean` (bool) - Equivalent to setting the
|
||||
[`--clean`](https://www.vagrantup.com/docs/cli/box.html#clean) flag in
|
||||
`vagrant add`; defaults to unset.
|
||||
|
||||
- `add_force` (bool) - Equivalent to setting the
|
||||
[`--force`](https://www.vagrantup.com/docs/cli/box.html#force) flag in
|
||||
`vagrant add`; defaults to unset.
|
||||
|
||||
- `add_insecure` (bool) - Equivalent to setting the
|
||||
[`--force`](https://www.vagrantup.com/docs/cli/box.html#insecure) flag in
|
||||
`vagrant add`; defaults to unset.
|
||||
|
||||
- `skip_package` (bool) - if true, Packer will not call `vagrant package` to
|
||||
package your base box into its own standalone .box file.
|
|
@ -58,7 +58,7 @@ The format will be covered in more detail later. But as you can see, the output
|
|||
immediately becomes machine-friendly. Try some other commands with the
|
||||
`-machine-readable` flag to see!
|
||||
|
||||
~> The `-machine-readable` flag is designed for automated environments and
|
||||
\~> The `-machine-readable` flag is designed for automated environments and
|
||||
is mutually-exclusive with the `-debug` flag, which is designed for interactive
|
||||
environments.
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ plugin interface, and this page documents how to do that.
|
|||
Prior to reading this page, it is assumed you have read the page on [plugin
|
||||
development basics](/docs/extending/plugins.html).
|
||||
|
||||
~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
\~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
recommend getting a bit more comfortable before you dive into writing plugins.
|
||||
|
||||
## The Interface
|
||||
|
|
|
@ -24,7 +24,7 @@ development basics](/docs/extending/plugins.html).
|
|||
Post-processor plugins implement the `packer.PostProcessor` interface and are
|
||||
served using the `plugin.ServePostProcessor` function.
|
||||
|
||||
~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
\~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
recommend getting a bit more comfortable before you dive into writing plugins.
|
||||
|
||||
## The Interface
|
||||
|
|
|
@ -23,7 +23,7 @@ development basics](/docs/extending/plugins.html).
|
|||
Provisioner plugins implement the `packer.Provisioner` interface and are served
|
||||
using the `plugin.ServeProvisioner` function.
|
||||
|
||||
~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
\~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
recommend getting a bit more comfortable before you dive into writing plugins.
|
||||
|
||||
## The Interface
|
||||
|
|
|
@ -56,7 +56,8 @@ later, it will take precedence over one found earlier.
|
|||
|
||||
3. The `%APPDATA%/packer.d/plugins` if `%APPDATA%` is defined (windows)
|
||||
|
||||
4. The `%USERPROFILE%/packer.d/plugins` if `%USERPROFILE%` is defined (windows)
|
||||
4. The `%USERPROFILE%/packer.d/plugins` if `%USERPROFILE%` is defined
|
||||
(windows)
|
||||
|
||||
5. The current working directory.
|
||||
|
||||
|
@ -83,7 +84,7 @@ assumed that you're familiar with the language. This page will not be a Go
|
|||
language tutorial. Thankfully, if you are familiar with Go, the Go toolchain
|
||||
provides many conveniences to help to develop Packer plugins.
|
||||
|
||||
~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
\~> **Warning!** This is an advanced topic. If you're new to Packer, we
|
||||
recommend getting a bit more comfortable before you dive into writing plugins.
|
||||
|
||||
### Plugin System Architecture
|
||||
|
@ -159,7 +160,7 @@ using standard installation procedures.
|
|||
The specifics of how to implement each type of interface are covered in the
|
||||
relevant subsections available in the navigation to the left.
|
||||
|
||||
~> **Lock your dependencies!** Using `govendor` is highly recommended since
|
||||
\~> **Lock your dependencies!** Using `govendor` is highly recommended since
|
||||
the Packer codebase will continue to improve, potentially breaking APIs along
|
||||
the way until there is a stable release. By locking your dependencies, your
|
||||
plugins will continue to work with the version of Packer you lock to.
|
||||
|
|
|
@ -42,7 +42,7 @@ are two categories: required and optional parameters.
|
|||
- `image_name` (string) - The name of the user-defined image, \[2, 128\]
|
||||
English or Chinese characters. It must begin with an uppercase/lowercase
|
||||
letter or a Chinese character, and may contain numbers, `_` or `-`. It
|
||||
cannot begin with <http://> or <https://>.
|
||||
cannot begin with `http://` or `https://`
|
||||
|
||||
- `oss_bucket_name` (string) - The name of the OSS bucket where the RAW or
|
||||
VHD file will be copied to for import. If the Bucket isn't exist,
|
||||
|
@ -52,8 +52,8 @@ are two categories: required and optional parameters.
|
|||
|
||||
- `image_platform` (string) - platform such `CentOS`
|
||||
|
||||
- `image_architecture` (string) - Platform type of the image system:i386 \|
|
||||
x86\_64
|
||||
- `image_architecture` (string) - Platform type of the image system: `i386` or
|
||||
`x86_64`
|
||||
|
||||
- `format` (string) - The format of the image for import, now alicloud only
|
||||
support RAW and VHD.
|
||||
|
@ -70,7 +70,7 @@ are two categories: required and optional parameters.
|
|||
|
||||
- `image_description` (string) - The description of the image, with a length
|
||||
limit of 0 to 256 characters. Leaving it blank means null, which is the
|
||||
default value. It cannot begin with <http://> or <https://>.
|
||||
default value. It cannot begin with `http://` or `https://`.
|
||||
|
||||
- `image_force_delete` (boolean) - If this value is true, when the target
|
||||
image name is duplicated with an existing image, it will delete the
|
||||
|
@ -79,9 +79,9 @@ are two categories: required and optional parameters.
|
|||
|
||||
- `image_system_size` (number) - Size of the system disk, in GB, values
|
||||
range:
|
||||
- cloud - 5 ~ 2000
|
||||
- cloud\_efficiency - 20 ~ 2048
|
||||
- cloud\_ssd - 20 ~ 2048
|
||||
- cloud - 5 \~ 2000
|
||||
- cloud\_efficiency - 20 \~ 2048
|
||||
- cloud\_ssd - 20 \~ 2048
|
||||
|
||||
## Basic Example
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Type: `amazon-import`
|
|||
The Packer Amazon Import post-processor takes an OVA artifact from various
|
||||
builders and imports it to an AMI available to Amazon Web Services EC2.
|
||||
|
||||
~> This post-processor is for advanced users. It depends on specific IAM
|
||||
\~> This post-processor is for advanced users. It depends on specific IAM
|
||||
roles inside AWS and is best used with images that operate with the EC2
|
||||
configuration model (eg, cloud-init for Linux systems). Please ensure you read
|
||||
the [prerequisites for
|
||||
|
@ -85,12 +85,13 @@ Optional:
|
|||
provider whose API is compatible with aws EC2. Specify another endpoint
|
||||
like this `https://ec2.custom.endpoint.com`.
|
||||
|
||||
- `format` (string) - One of: `ova`, `raw`, `vhd`, `vhdx`, or `vmdk`. This specifies
|
||||
the format of the source virtual machine image. The resulting artifact from the builder
|
||||
is assumed to have a file extension matching the format. This defaults to `ova`.
|
||||
- `format` (string) - One of: `ova`, `raw`, `vhd`, `vhdx`, or `vmdk`. This
|
||||
specifies the format of the source virtual machine image. The resulting
|
||||
artifact from the builder is assumed to have a file extension matching the
|
||||
format. This defaults to `ova`.
|
||||
|
||||
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS verification of
|
||||
the AWS EC2 endpoint. The default is `false`.
|
||||
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS
|
||||
verification of the AWS EC2 endpoint. The default is `false`.
|
||||
|
||||
- `license_type` (string) - The license type to be used for the Amazon
|
||||
Machine Image (AMI) after importing. Valid values: `AWS` or `BYOL`
|
||||
|
|
|
@ -38,7 +38,6 @@ is optional.
|
|||
commit. Example of instructions are `CMD`, `ENTRYPOINT`, `ENV`, and
|
||||
`EXPOSE`. Example: `[ "USER ubuntu", "WORKDIR /app", "EXPOSE 8080" ]`
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
An example is shown below, showing only the post-processor configuration:
|
||||
|
@ -61,9 +60,9 @@ to a registry, if you want.
|
|||
## Changing Metadata
|
||||
|
||||
Below is an example using the changes argument of the post-processor. This
|
||||
feature allows the tarball metadata to be changed when imported into the
|
||||
Docker environment. It is derived from the `docker import --change` command
|
||||
line [option to
|
||||
feature allows the tarball metadata to be changed when imported into the Docker
|
||||
environment. It is derived from the `docker import --change` command line
|
||||
[option to
|
||||
Docker](https://docs.docker.com/engine/reference/commandline/import/).
|
||||
|
||||
Example uses of all of the options, assuming one is building an NGINX image
|
||||
|
|
|
@ -14,7 +14,7 @@ Type: `googlecompute-import`
|
|||
The Google Compute Image Import post-processor takes a compressed raw disk
|
||||
image and imports it to a GCE image available to Google Compute Engine.
|
||||
|
||||
~> This post-processor is for advanced users. Please ensure you read the
|
||||
\~> This post-processor is for advanced users. Please ensure you read the
|
||||
[GCE import
|
||||
documentation](https://cloud.google.com/compute/docs/images/import-existing-image)
|
||||
before using this post-processor.
|
||||
|
|
|
@ -60,14 +60,13 @@ Optional parameters:
|
|||
Packer injects some environmental variables by default into the
|
||||
environment, as well, which are covered in the section below.
|
||||
|
||||
- `env_var_format` (string) - When we parse the environment_vars that you
|
||||
- `env_var_format` (string) - When we parse the environment\_vars that you
|
||||
provide, this gives us a string template to use in order to make sure that
|
||||
we are setting the environment vars correctly. By default on Windows hosts
|
||||
this format is `set %s=%s && ` and on Unix, it is `%s='%s' `. You probably
|
||||
this format is `set %s=%s && ` and on Unix, it is `%s='%s' `. You probably
|
||||
won't need to change this format, but you can see usage examples for where
|
||||
it is necessary below.
|
||||
|
||||
|
||||
- `execute_command` (array of strings) - The command used to execute the
|
||||
script. By default this is `["/bin/sh", "-c", "{{.Vars}}", "{{.Script}}"]`
|
||||
on unix and `["cmd", "/c", "{{.Vars}}", "{{.Script}}"]` on windows. This is
|
||||
|
@ -250,10 +249,10 @@ are cleaned up.
|
|||
For a shell script, that means the script **must** exit with a zero code. You
|
||||
*must* be extra careful to `exit 0` when necessary.
|
||||
|
||||
|
||||
## Usage Examples:
|
||||
|
||||
### Windows Host
|
||||
|
||||
Example of running a .cmd file on windows:
|
||||
|
||||
{
|
||||
|
@ -295,7 +294,6 @@ Contents of "example\_bash.sh":
|
|||
Example of running a powershell script on windows: Required customizations:
|
||||
env\_var\_format and execute\_command
|
||||
|
||||
|
||||
{
|
||||
"type": "shell-local",
|
||||
"environment_vars": ["SHELLLOCALTEST=ShellTest4"],
|
||||
|
@ -317,6 +315,7 @@ customizations: env\_var\_format, tempfile\_extension, and execute\_command
|
|||
}
|
||||
|
||||
### Unix Host
|
||||
|
||||
Example of running a bash script on unix:
|
||||
|
||||
{
|
||||
|
@ -336,19 +335,15 @@ Example of running a bash "inline" on unix:
|
|||
|
||||
Example of running a python script on unix:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "shell-local",
|
||||
"script": "hello.py",
|
||||
"environment_vars": ["HELLO_USER=packeruser"],
|
||||
"execute_command": ["/bin/sh", "-c", "{{.Vars}} /usr/local/bin/python {{.Script}}"]
|
||||
}
|
||||
```
|
||||
{
|
||||
"type": "shell-local",
|
||||
"script": "hello.py",
|
||||
"environment_vars": ["HELLO_USER=packeruser"],
|
||||
"execute_command": ["/bin/sh", "-c", "{{.Vars}} /usr/local/bin/python {{.Script}}"]
|
||||
}
|
||||
|
||||
Where "hello.py" contains:
|
||||
|
||||
```
|
||||
import os
|
||||
import os
|
||||
|
||||
print('Hello, %s!' % os.getenv("HELLO_USER"))
|
||||
```
|
||||
print('Hello, %s!' % os.getenv("HELLO_USER"))
|
||||
|
|
|
@ -144,5 +144,5 @@ The following Docker input artifacts are supported:
|
|||
|
||||
### QEMU/libvirt
|
||||
|
||||
The `libvirt` provider supports QEMU artifacts built using any these accelerators: none,
|
||||
kvm, tcg, or hvf.
|
||||
The `libvirt` provider supports QEMU artifacts built using any these
|
||||
accelerators: none, kvm, tcg, or hvf.
|
||||
|
|
|
@ -60,16 +60,16 @@ Optional:
|
|||
|
||||
- `insecure` (boolean) - If it's true skip verification of server
|
||||
certificate. Default is false
|
||||
|
||||
- `snapshot_enable` (boolean) - Create a snapshot before marking as a
|
||||
|
||||
- `snapshot_enable` (boolean) - Create a snapshot before marking as a
|
||||
template. Default is false
|
||||
|
||||
- `snapshot_name` (string) - Name for the snapshot.
|
||||
Required when `snapshot_enable` is `true`
|
||||
|
||||
- `snapshot_description` (string) - Description for the snapshot.
|
||||
Required when `snapshot_enable` is `true`
|
||||
|
||||
|
||||
- `snapshot_name` (string) - Name for the snapshot. Required when
|
||||
`snapshot_enable` is `true`
|
||||
|
||||
- `snapshot_description` (string) - Description for the snapshot. Required
|
||||
when `snapshot_enable` is `true`
|
||||
|
||||
## Using the vSphere Template with local builders
|
||||
|
||||
Once the [vSphere](/docs/post-processors/vsphere.html) takes an artifact from
|
||||
|
|
|
@ -68,6 +68,7 @@ Optional:
|
|||
example:
|
||||
|
||||
<!-- -->
|
||||
|
||||
"extra_arguments": [ "--extra-vars \"Region={{user `Region`}} Stage={{user `Stage`}}\"" ]
|
||||
|
||||
- `inventory_groups` (string) - A comma-separated list of groups to which
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
description: |
|
||||
The breakpoint provisioner will pause until the user presses "enter" to
|
||||
resume the build. This is intended for debugging purposes, and allows you
|
||||
to halt at a particular part of the provisioning process.
|
||||
The breakpoint provisioner will pause until the user presses "enter" to resume
|
||||
the build. This is intended for debugging purposes, and allows you to halt at a
|
||||
particular part of the provisioning process.
|
||||
layout: docs
|
||||
page_title: 'breakpoint - Provisioners'
|
||||
sidebar_current: 'docs-provisioners-breakpoint'
|
||||
|
@ -12,9 +12,9 @@ sidebar_current: 'docs-provisioners-breakpoint'
|
|||
|
||||
Type: `breakpoint`
|
||||
|
||||
The breakpoint provisioner will pause until the user presses "enter" to
|
||||
resume the build. This is intended for debugging purposes, and allows you
|
||||
to halt at a particular part of the provisioning process.
|
||||
The breakpoint provisioner will pause until the user presses "enter" to resume
|
||||
the build. This is intended for debugging purposes, and allows you to halt at a
|
||||
particular part of the provisioning process.
|
||||
|
||||
This is independent of the `-debug` flag, which will instead halt at every step
|
||||
and between every provisioner.
|
||||
|
@ -33,8 +33,8 @@ and between every provisioner.
|
|||
### Optional
|
||||
|
||||
- `disable` (boolean) - If `true`, skip the breakpoint. Useful for when you
|
||||
have set multiple breakpoints and want to toggle them off or on.
|
||||
Default: `false`
|
||||
have set multiple breakpoints and want to toggle them off or on. Default:
|
||||
`false`
|
||||
|
||||
- `note` (string) - a string to include explaining the purpose or location of
|
||||
the breakpoint. For example, you may find it useful to number your
|
||||
|
@ -48,10 +48,8 @@ output prompting you to press "enter" to continue the build when you are ready.
|
|||
|
||||
For example:
|
||||
|
||||
```
|
||||
==> docker: Pausing at breakpoint provisioner with note "foo bar baz".
|
||||
==> docker: Press enter to continue.
|
||||
```
|
||||
==> docker: Pausing at breakpoint provisioner with note "foo bar baz".
|
||||
==> docker: Press enter to continue.
|
||||
|
||||
Once you press enter, the build will resume and run normally until it either
|
||||
completes or errors.
|
||||
completes or errors.
|
||||
|
|
|
@ -73,7 +73,8 @@ Optional parameters:
|
|||
]
|
||||
|
||||
- `facter` (object of key:value strings) - Additional
|
||||
[facts](https://docs.puppet.com/facter/) to make available to the Puppet run.
|
||||
[facts](https://docs.puppet.com/facter/) to make available to the Puppet
|
||||
run.
|
||||
|
||||
- `guest_os_type` (string) - The remote host's OS type ('windows' or 'unix')
|
||||
to tailor command-line and path separators. (default: unix).
|
||||
|
@ -88,9 +89,9 @@ Optional parameters:
|
|||
This is useful if your main manifest uses imports, but the directory might
|
||||
not contain the `manifest_file` itself.
|
||||
|
||||
~> `manifest_dir` is passed to Puppet as `--manifestdir` option. This option
|
||||
was deprecated in puppet 3.6, and removed in puppet 4.0. If you have multiple
|
||||
manifests you should use `manifest_file` instead.
|
||||
\~> `manifest_dir` is passed to Puppet as `--manifestdir` option. This
|
||||
option was deprecated in puppet 3.6, and removed in puppet 4.0. If you have
|
||||
multiple manifests you should use `manifest_file` instead.
|
||||
|
||||
- `module_paths` (array of strings) - Array of local module directories to be
|
||||
uploaded.
|
||||
|
|
|
@ -74,10 +74,10 @@ Optional parameters:
|
|||
this as an environment variable. For example:
|
||||
`"environment_vars": "WINRMPASS={{.WinRMPassword}}"`
|
||||
|
||||
- `env_var_format` (string) - When we parse the environment_vars that you
|
||||
- `env_var_format` (string) - When we parse the environment\_vars that you
|
||||
provide, this gives us a string template to use in order to make sure that
|
||||
we are setting the environment vars correctly. By default on Windows hosts
|
||||
this format is `set %s=%s && ` and on Unix, it is `%s='%s' `. You probably
|
||||
this format is `set %s=%s && ` and on Unix, it is `%s='%s' `. You probably
|
||||
won't need to change this format, but you can see usage examples for where
|
||||
it is necessary below.
|
||||
|
||||
|
@ -230,6 +230,7 @@ For a shell script, that means the script **must** exit with a zero code. You
|
|||
## Usage Examples:
|
||||
|
||||
### Windows Host
|
||||
|
||||
Example of running a .cmd file on windows:
|
||||
|
||||
{
|
||||
|
@ -271,7 +272,6 @@ Contents of "example\_bash.sh":
|
|||
Example of running a powershell script on windows: Required customizations:
|
||||
env\_var\_format and execute\_command
|
||||
|
||||
|
||||
{
|
||||
"type": "shell-local",
|
||||
"environment_vars": ["SHELLLOCALTEST=ShellTest4"],
|
||||
|
@ -293,6 +293,7 @@ customizations: env\_var\_format, tempfile\_extension, and execute\_command
|
|||
}
|
||||
|
||||
### Unix Host
|
||||
|
||||
Example of running a bash script on unix:
|
||||
|
||||
{
|
||||
|
@ -312,19 +313,15 @@ Example of running a bash "inline" on unix:
|
|||
|
||||
Example of running a python script on unix:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "shell-local",
|
||||
"script": "hello.py",
|
||||
"environment_vars": ["HELLO_USER=packeruser"],
|
||||
"execute_command": ["/bin/sh", "-c", "{{.Vars}} /usr/local/bin/python {{.Script}}"]
|
||||
}
|
||||
```
|
||||
{
|
||||
"type": "shell-local",
|
||||
"script": "hello.py",
|
||||
"environment_vars": ["HELLO_USER=packeruser"],
|
||||
"execute_command": ["/bin/sh", "-c", "{{.Vars}} /usr/local/bin/python {{.Script}}"]
|
||||
}
|
||||
|
||||
Where "hello.py" contains:
|
||||
|
||||
```
|
||||
import os
|
||||
import os
|
||||
|
||||
print('Hello, %s!' % os.getenv("HELLO_USER"))
|
||||
```
|
||||
print('Hello, %s!' % os.getenv("HELLO_USER"))
|
||||
|
|
|
@ -41,10 +41,10 @@ Optional parameters:
|
|||
- `check_registry` (bool) - if `true`, checks for several registry keys that
|
||||
indicate that the system is going to reboot. This is useful if an
|
||||
installation kicks off a reboot and you want the provisioner to wait for
|
||||
that reboot to complete before reconnecting. Please note that this option is
|
||||
a beta feature, and we generally recommend that you finish installs that
|
||||
auto-reboot (like windows updates) during your autounattend phase before our
|
||||
winrm provisioner connects.
|
||||
that reboot to complete before reconnecting. Please note that this option
|
||||
is a beta feature, and we generally recommend that you finish installs that
|
||||
auto-reboot (like windows updates) during your autounattend phase before
|
||||
our winrm provisioner connects.
|
||||
|
||||
- `registry_keys` (array of strings) - if `check-registry` is `true`,
|
||||
windows-restart will not reconnect until after all of the listed keys are
|
||||
|
@ -52,13 +52,11 @@ Optional parameters:
|
|||
|
||||
default:
|
||||
|
||||
```
|
||||
var DefaultRegistryKeys = []string{
|
||||
"HKLM:SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending",
|
||||
"HKLM:SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\PackagesPending",
|
||||
"HKLM:Software\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootInProgress",
|
||||
}
|
||||
```
|
||||
var DefaultRegistryKeys = []string{
|
||||
"HKLM:SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending",
|
||||
"HKLM:SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\PackagesPending",
|
||||
"HKLM:Software\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootInProgress",
|
||||
}
|
||||
|
||||
- `restart_command` (string) - The command to execute to initiate the
|
||||
restart. By default this is `shutdown /r /f /t 0 /c "packer restart"`.
|
||||
|
|
|
@ -75,9 +75,9 @@ The SSH communicator has the following options:
|
|||
- `ssh_bastion_port` (number) - The port of the bastion host. Defaults to
|
||||
`22`.
|
||||
|
||||
- `ssh_bastion_private_key_file` (string) - Path to a PEM encoded private
|
||||
key file to use to authenticate with the bastion host. The `~` can be used
|
||||
in path and will be expanded to the home directory of current user.
|
||||
- `ssh_bastion_private_key_file` (string) - Path to a PEM encoded private key
|
||||
file to use to authenticate with the bastion host. The `~` can be used in
|
||||
path and will be expanded to the home directory of current user.
|
||||
|
||||
- `ssh_bastion_username` (string) - The username to connect to the bastion
|
||||
host.
|
||||
|
@ -155,23 +155,25 @@ Packer supports the following ciphers:
|
|||
- arcfour128
|
||||
- arcfour256
|
||||
- arcfour
|
||||
- <es128-gcm@openssh.com>
|
||||
- <acha20-poly1305@openssh.com>
|
||||
- `es128-gcm@openssh.com`
|
||||
- `acha20-poly1305@openssh.com`
|
||||
|
||||
And the following MACs:
|
||||
|
||||
- hmac-sha1
|
||||
- hmac-sha1-96
|
||||
- hmac-sha2-256
|
||||
- <hmac-sha2-256-etm@openssh.com>
|
||||
- `hmac-sha2-256-etm@openssh.com`
|
||||
|
||||
## WinRM Communicator
|
||||
|
||||
The WinRM communicator has the following options.
|
||||
|
||||
- `winrm_host` (string) - The address for WinRM to connect to.
|
||||
|
||||
NOTE: If using an Amazon EBS builder, you can specify the interface WinRM connects to via [`ssh_interface`](https://www.packer.io/docs/builders/amazon-ebs.html#ssh_interface)
|
||||
|
||||
NOTE: If using an Amazon EBS builder, you can specify the interface WinRM
|
||||
connects to via
|
||||
[`ssh_interface`](https://www.packer.io/docs/builders/amazon-ebs.html#ssh_interface)
|
||||
|
||||
- `winrm_insecure` (boolean) - If `true`, do not check server certificate
|
||||
chain and host name.
|
||||
|
|
|
@ -44,7 +44,8 @@ Here is a full list of the available functions for reference.
|
|||
reference](/docs/templates/engine.html#isotime-function-format-reference).
|
||||
- `lower` - Lowercases the string.
|
||||
- `pwd` - The working directory while executing Packer.
|
||||
- `sed` - Use [a golang implementation of sed](https://github.com/rwtodd/Go.Sed) to parse an input string.
|
||||
- `sed` - Use [a golang implementation of
|
||||
sed](https://github.com/rwtodd/Go.Sed) to parse an input string.
|
||||
- `split` - Split an input string using separator and return the requested
|
||||
substring.
|
||||
- `template_dir` - The directory to the template for the build.
|
||||
|
@ -227,6 +228,7 @@ Formatting for the function `isotime` uses the magic reference date **Mon Jan 2
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
*The values in parentheses are the abbreviated, or 24-hour clock values*
|
||||
|
||||
Note that "-0700" is always formatted into "+0000" because `isotime` is always
|
||||
|
@ -272,10 +274,10 @@ builder in this example.
|
|||
The function `split` takes an input string, a seperator string, and a numeric
|
||||
component value and returns the requested substring.
|
||||
|
||||
Please note that you cannot use the `split` function on user variables,
|
||||
because we can't nest the functions currently. This function is indended to
|
||||
be used on builder variables like build_name. If you need a split user
|
||||
variable, the best way to do it is to create a separate variable.
|
||||
Please note that you cannot use the `split` function on user variables, because
|
||||
we can't nest the functions currently. This function is indended to be used on
|
||||
builder variables like build\_name. If you need a split user variable, the best
|
||||
way to do it is to create a separate variable.
|
||||
|
||||
Here are some examples using the above options:
|
||||
|
||||
|
@ -310,16 +312,15 @@ this case, on the `fixed-string` value):
|
|||
|
||||
# sed Function Format Reference
|
||||
|
||||
See the library documentation https://github.com/rwtodd/Go.Sed for notes about
|
||||
the difference between this golang implementation of sed and the regexes you may
|
||||
be used to. A very simple example of this functionality:
|
||||
See the library documentation
|
||||
<a href="https://github.com/rwtodd/Go.Sed" class="uri">https://github.com/rwtodd/Go.Sed</a>
|
||||
for notes about the difference between this golang implementation of sed and
|
||||
the regexes you may be used to. A very simple example of this functionality:
|
||||
|
||||
```
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "shell-local",
|
||||
"environment_vars": ["EXAMPLE={{ sed \"s/null/awesome/\" build_type}}"],
|
||||
"inline": ["echo build_type is $EXAMPLE\n"]
|
||||
}
|
||||
]
|
||||
```
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "shell-local",
|
||||
"environment_vars": ["EXAMPLE={{ sed \"s/null/awesome/\" build_type}}"],
|
||||
"inline": ["echo build_type is $EXAMPLE\n"]
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue