commit
f409dc54f5
|
@ -0,0 +1,34 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Artifact struct {
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Artifact) BuilderId() string {
|
||||||
|
return BuilderId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Files() []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Id() string {
|
||||||
|
return a.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) String() string {
|
||||||
|
return fmt.Sprintf("image: %s", a.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) State(name string) interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Artifact) Destroy() error {
|
||||||
|
_, err := LXDCommand("image", "delete", a.id)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The unique ID for this builder
|
||||||
|
const BuilderId = "lxd"
|
||||||
|
|
||||||
|
type wrappedCommandTemplate struct {
|
||||||
|
Command string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config *Config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
c, errs := NewConfig(raws...)
|
||||||
|
if errs != nil {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
b.config = c
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
wrappedCommand := func(command string) (string, error) {
|
||||||
|
b.config.ctx.Data = &wrappedCommandTemplate{Command: command}
|
||||||
|
return interpolate.Render(b.config.CommandWrapper, &b.config.ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
steps := []multistep.Step{
|
||||||
|
&stepLxdLaunch{},
|
||||||
|
&StepProvision{},
|
||||||
|
&stepPublish{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the state bag
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("config", b.config)
|
||||||
|
state.Put("cache", cache)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
||||||
|
|
||||||
|
// Run
|
||||||
|
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact := &Artifact{
|
||||||
|
id: state.Get("imageFingerprint").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifact, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"output_image": "foo",
|
||||||
|
"image": "bar",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_Foo(t *testing.T) {
|
||||||
|
if os.Getenv("PACKER_ACC") == "" {
|
||||||
|
t.Skip("This test is only run with PACKER_ACC=1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilderPrepare_ConfigFile(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
// Good
|
||||||
|
config := testConfig()
|
||||||
|
warnings, err := b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good, remote image
|
||||||
|
config = testConfig()
|
||||||
|
config["image"] = "remote:bar"
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good, remote output image
|
||||||
|
config = testConfig()
|
||||||
|
config["output_image"] = "remote:foo"
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not have error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad, missing image name
|
||||||
|
config = testConfig()
|
||||||
|
delete(config, "image")
|
||||||
|
b = Builder{}
|
||||||
|
warnings, err = b.Prepare(config)
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warnings)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should have error")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Builder{}
|
||||||
|
if _, ok := raw.(packer.Builder); !ok {
|
||||||
|
t.Fatalf("Builder should be a builder")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandWrapper is a type that given a command, will possibly modify that
|
||||||
|
// command in-flight. This might return an error.
|
||||||
|
type CommandWrapper func(string) (string, error)
|
||||||
|
|
||||||
|
// ShellCommand takes a command string and returns an *exec.Cmd to execute
|
||||||
|
// it within the context of a shell (/bin/sh).
|
||||||
|
func ShellCommand(command string) *exec.Cmd {
|
||||||
|
return exec.Command("/bin/sh", "-c", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yeah...LXD calls `lxc` because the command line is different between the
|
||||||
|
// packages. This should also avoid a naming collision between the LXC builder.
|
||||||
|
func LXDCommand(args ...string) (string, error) {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
log.Printf("Executing lxc command: %#v", args)
|
||||||
|
cmd := exec.Command("lxc", args...)
|
||||||
|
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("LXD command error: %s", stderrString)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("stdout: %s", stdoutString)
|
||||||
|
log.Printf("stderr: %s", stderrString)
|
||||||
|
|
||||||
|
return stdoutString, err
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Communicator struct {
|
||||||
|
ContainerName string
|
||||||
|
CmdWrapper CommandWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Communicator) Start(cmd *packer.RemoteCmd) error {
|
||||||
|
localCmd, err := c.Execute(cmd.Command)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localCmd.Stdin = cmd.Stdin
|
||||||
|
localCmd.Stdout = cmd.Stdout
|
||||||
|
localCmd.Stderr = cmd.Stderr
|
||||||
|
if err := localCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
exitStatus := 0
|
||||||
|
if err := localCmd.Wait(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
exitStatus = 1
|
||||||
|
|
||||||
|
// There is no process-independent way to get the REAL
|
||||||
|
// exit status so we just try to go deeper.
|
||||||
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
exitStatus = status.ExitStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"lxc exec execution exited with '%d': '%s'",
|
||||||
|
exitStatus, cmd.Command)
|
||||||
|
cmd.SetExited(exitStatus)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Communicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
||||||
|
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file push - %s", filepath.Join(c.ContainerName, dst)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Running copy command: %s", cpCmd)
|
||||||
|
command := ShellCommand(cpCmd)
|
||||||
|
command.Stdin = r
|
||||||
|
|
||||||
|
return command.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Communicator) UploadDir(dst string, src string, exclude []string) error {
|
||||||
|
// NOTE:lxc file push doesn't yet support directory uploads.
|
||||||
|
// As a work around, we tar up the folder, upload it as a file, then extract it
|
||||||
|
|
||||||
|
// Don't use 'z' flag as compressing may take longer and the transfer is likely local.
|
||||||
|
// If this isn't the case, it is possible for the user to compress in another step then transfer.
|
||||||
|
// It wouldn't be possibe to disable compression, without exposing this option.
|
||||||
|
tar, err := c.CmdWrapper(fmt.Sprintf("tar -cf - -C %s .", src))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cp, err := c.CmdWrapper(fmt.Sprintf("lxc exec %s -- tar -xf - -C %s", c.ContainerName, dst))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tarCmd := ShellCommand(tar)
|
||||||
|
cpCmd := ShellCommand(cp)
|
||||||
|
|
||||||
|
cpCmd.Stdin, _ = tarCmd.StdoutPipe()
|
||||||
|
log.Printf("Starting tar command: %s", tar)
|
||||||
|
err = tarCmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Running cp command: %s", cp)
|
||||||
|
err = cpCmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error running cp command: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tarCmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error running tar command: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Communicator) Download(src string, w io.Writer) error {
|
||||||
|
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file pull %s -", filepath.Join(c.ContainerName, src)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Running copy command: %s", cpCmd)
|
||||||
|
command := ShellCommand(cpCmd)
|
||||||
|
command.Stdout = w
|
||||||
|
|
||||||
|
return command.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Communicator) DownloadDir(src string, dst string, exclude []string) error {
|
||||||
|
// TODO This could probably be "lxc exec <container> -- cd <src> && tar -czf - | tar -xzf - -C <dst>"
|
||||||
|
return fmt.Errorf("DownloadDir is not implemented for lxc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Communicator) Execute(commandString string) (*exec.Cmd, error) {
|
||||||
|
log.Printf("Executing with lxc exec in container: %s %s", c.ContainerName, commandString)
|
||||||
|
command, err := c.CmdWrapper(
|
||||||
|
fmt.Sprintf("lxc exec %s -- /bin/sh -c \"%s\"", c.ContainerName, commandString))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localCmd := ShellCommand(command)
|
||||||
|
log.Printf("Executing lxc exec: %s %#v", localCmd.Path, localCmd.Args)
|
||||||
|
|
||||||
|
return localCmd, nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
|
||||||
|
var raw interface{}
|
||||||
|
raw = &Communicator{}
|
||||||
|
if _, ok := raw.(packer.Communicator); !ok {
|
||||||
|
t.Fatalf("Communicator should be a communicator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acceptance tests
|
||||||
|
// TODO Execute a command
|
||||||
|
// TODO Upload a file
|
||||||
|
// TODO Download a file
|
||||||
|
// TODO Upload a Directory
|
|
@ -0,0 +1,60 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/packer/common"
|
||||||
|
"github.com/hashicorp/packer/helper/config"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
OutputImage string `mapstructure:"output_image"`
|
||||||
|
ContainerName string `mapstructure:"container_name"`
|
||||||
|
CommandWrapper string `mapstructure:"command_wrapper"`
|
||||||
|
Image string `mapstructure:"image"`
|
||||||
|
InitTimeout time.Duration
|
||||||
|
|
||||||
|
ctx interpolate.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(raws ...interface{}) (*Config, error) {
|
||||||
|
var c Config
|
||||||
|
|
||||||
|
var md mapstructure.Metadata
|
||||||
|
err := config.Decode(&c, &config.DecodeOpts{
|
||||||
|
Metadata: &md,
|
||||||
|
Interpolate: true,
|
||||||
|
}, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate any errors
|
||||||
|
var errs *packer.MultiError
|
||||||
|
|
||||||
|
if c.ContainerName == "" {
|
||||||
|
c.ContainerName = fmt.Sprintf("packer-%s", c.PackerBuildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OutputImage == "" {
|
||||||
|
c.OutputImage = c.ContainerName
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CommandWrapper == "" {
|
||||||
|
c.CommandWrapper = "{{.Command}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Image == "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs, fmt.Errorf("`image` is a required parameter for LXD. Please specify an image by alias or fingerprint. e.g. `ubuntu-daily:x`"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepLxdLaunch struct{}
|
||||||
|
|
||||||
|
func (s *stepLxdLaunch) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
name := config.ContainerName
|
||||||
|
image := config.Image
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"launch", "--ephemeral=false", image, name,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Creating container...")
|
||||||
|
_, err := LXDCommand(args...)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error creating container: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
// TODO: Should we check `lxc info <container>` for "Running"?
|
||||||
|
// We have to do this so /tmp doens't get cleared and lose our provisioner scripts.
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepLxdLaunch) Cleanup(state multistep.StateBag) {
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"delete", "--force", config.ContainerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Unregistering and deleting deleting container...")
|
||||||
|
if _, err := LXDCommand(args...); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error deleting container: %s", err))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StepProvision provisions the container
|
||||||
|
type StepProvision struct{}
|
||||||
|
|
||||||
|
func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
hook := state.Get("hook").(packer.Hook)
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
|
||||||
|
|
||||||
|
// Create our communicator
|
||||||
|
comm := &Communicator{
|
||||||
|
ContainerName: config.ContainerName,
|
||||||
|
CmdWrapper: wrappedCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision
|
||||||
|
log.Println("Running the provision hook")
|
||||||
|
if err := hook.Run(packer.HookProvision, ui, comm, nil); err != nil {
|
||||||
|
state.Put("error", err)
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepProvision) Cleanup(state multistep.StateBag) {}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package lxd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepPublish struct{}
|
||||||
|
|
||||||
|
func (s *stepPublish) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*Config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
name := config.ContainerName
|
||||||
|
stop_args := []string{
|
||||||
|
// We created the container with "--ephemeral=false" so we know it is safe to stop.
|
||||||
|
"stop", name,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Stopping container...")
|
||||||
|
_, err := LXDCommand(stop_args...)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error stopping container: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
publish_args := []string{
|
||||||
|
"publish", name, "--alias", config.OutputImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Say("Publishing container...")
|
||||||
|
stdoutString, err := LXDCommand(publish_args...)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error publishing container: %s", err)
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
r := regexp.MustCompile("([0-9a-fA-F]+)$")
|
||||||
|
fingerprint := r.FindAllStringSubmatch(stdoutString, -1)[0][0]
|
||||||
|
|
||||||
|
ui.Say(fmt.Sprintf("Created image: %s", fingerprint))
|
||||||
|
|
||||||
|
state.Put("imageFingerprint", fingerprint)
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepPublish) Cleanup(state multistep.StateBag) {}
|
|
@ -69,6 +69,8 @@ import (
|
||||||
shelllocalprovisioner "github.com/hashicorp/packer/provisioner/shell-local"
|
shelllocalprovisioner "github.com/hashicorp/packer/provisioner/shell-local"
|
||||||
windowsrestartprovisioner "github.com/hashicorp/packer/provisioner/windows-restart"
|
windowsrestartprovisioner "github.com/hashicorp/packer/provisioner/windows-restart"
|
||||||
windowsshellprovisioner "github.com/hashicorp/packer/provisioner/windows-shell"
|
windowsshellprovisioner "github.com/hashicorp/packer/provisioner/windows-shell"
|
||||||
|
|
||||||
|
lxdbuilder "github.com/hashicorp/packer/builder/lxd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PluginCommand struct {
|
type PluginCommand struct {
|
||||||
|
@ -89,6 +91,7 @@ var Builders = map[string]packer.Builder{
|
||||||
"file": new(filebuilder.Builder),
|
"file": new(filebuilder.Builder),
|
||||||
"googlecompute": new(googlecomputebuilder.Builder),
|
"googlecompute": new(googlecomputebuilder.Builder),
|
||||||
"hyperv-iso": new(hypervisobuilder.Builder),
|
"hyperv-iso": new(hypervisobuilder.Builder),
|
||||||
|
"lxd": new(lxdbuilder.Builder),
|
||||||
"null": new(nullbuilder.Builder),
|
"null": new(nullbuilder.Builder),
|
||||||
"oneandone": new(oneandonebuilder.Builder),
|
"oneandone": new(oneandonebuilder.Builder),
|
||||||
"openstack": new(openstackbuilder.Builder),
|
"openstack": new(openstackbuilder.Builder),
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
description: |
|
||||||
|
The `lxd` Packer builder builds containers for LXD. The builder starts an LXD
|
||||||
|
container, runs provisioners within this container, then saves the container
|
||||||
|
as an LXD image.
|
||||||
|
layout: docs
|
||||||
|
page_title: LXD Builder
|
||||||
|
...
|
||||||
|
|
||||||
|
# LXD Builder
|
||||||
|
|
||||||
|
Type: `lxd`
|
||||||
|
|
||||||
|
The `lxd` Packer builder builds containers for LXD. The builder starts an LXD
|
||||||
|
container, runs provisioners within this container, then saves the container
|
||||||
|
as an LXD image.
|
||||||
|
|
||||||
|
The LXD builder requires a modern linux kernel and the `lxd` package.
|
||||||
|
This builder does not work with LXC.
|
||||||
|
|
||||||
|
## Basic Example
|
||||||
|
|
||||||
|
Below is a fully functioning example.
|
||||||
|
|
||||||
|
``` {.javascript}
|
||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{
|
||||||
|
"type": "lxd",
|
||||||
|
"name": "lxd-xenial",
|
||||||
|
"image": "ubuntu-daily:xenial",
|
||||||
|
"output_image": "ubuntu-xenial"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
### Required:
|
||||||
|
|
||||||
|
- `image` (string) - The source image to use when creating the build 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 remote image, as they are usually 100-200MB. `/var/log/lxd/lxd.log` will mention starting such downloads.
|
||||||
|
|
||||||
|
### Optional:
|
||||||
|
|
||||||
|
- `name` (string) - The name of the started container. Defaults to `packer-$PACKER_BUILD_NAME`.
|
||||||
|
|
||||||
|
- `output_image` (string) - The name of the output artifact. Defaults to `name`.
|
||||||
|
|
||||||
|
- `command_wrapper` (string) - lets you prefix all builder commands, such as with `ssh` for a remote build host. Defaults to `""`.
|
||||||
|
|
|
@ -190,8 +190,11 @@
|
||||||
<li<%= sidebar_current("docs-provisioners-file")%>>
|
<li<%= sidebar_current("docs-provisioners-file")%>>
|
||||||
<a href="/docs/provisioners/file.html">File</a>
|
<a href="/docs/provisioners/file.html">File</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-provisioners-lxd")%>>
|
||||||
|
<a href="/docs/builders/lxd.html">LXD</a>
|
||||||
|
</li>
|
||||||
<li<%= sidebar_current("docs-provisioners-powershell")%>>
|
<li<%= sidebar_current("docs-provisioners-powershell")%>>
|
||||||
<a href="/docs/provisioners/powershell.html">PowerShell</a>
|
<a href="/docs/provisioners/powershell.html">webPowerShell</a>
|
||||||
</li>
|
</li>
|
||||||
<li<%= sidebar_current("docs-provisioners-puppet-masterless")%>>
|
<li<%= sidebar_current("docs-provisioners-puppet-masterless")%>>
|
||||||
<a href="/docs/provisioners/puppet-masterless.html">Puppet Masterless</a>
|
<a href="/docs/provisioners/puppet-masterless.html">Puppet Masterless</a>
|
||||||
|
|
Loading…
Reference in New Issue