extract and vendor lxc and lxd (#10965)

This commit is contained in:
Sylvia Moss 2021-04-22 14:21:23 +02:00 committed by GitHub
parent 2cd296874e
commit 972497589e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 24 additions and 2008 deletions

View File

@ -1,38 +0,0 @@
package lxc
import (
"fmt"
"os"
)
type Artifact struct {
dir string
f []string
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
}
func (*Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Files() []string {
return a.f
}
func (*Artifact) Id() string {
return "VM"
}
func (a *Artifact) String() string {
return fmt.Sprintf("VM files in directory: %s", a.dir)
}
func (a *Artifact) State(name string) interface{} {
return a.StateData[name]
}
func (a *Artifact) Destroy() error {
return os.RemoveAll(a.dir)
}

View File

@ -1,91 +0,0 @@
package lxc
import (
"context"
"os"
"path/filepath"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// The unique ID for this builder
const BuilderId = "ustream.lxc"
type wrappedCommandTemplate struct {
Command string
}
type Builder struct {
config Config
runner multistep.Runner
}
func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
errs := b.config.Prepare(raws...)
if errs != nil {
return nil, nil, errs
}
return nil, nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.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{
new(stepPrepareOutputDir),
new(stepLxcCreate),
&StepWaitInit{
WaitTimeout: b.config.InitTimeout,
},
new(StepProvision),
new(stepExport),
}
// Setup the state bag
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
// Run
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(ctx, state)
// If there was an error, return that
if rawErr, ok := state.GetOk("error"); ok {
return nil, rawErr.(error)
}
// Compile the artifact list
files := make([]string, 0, 5)
visit := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
}
return err
}
if err := filepath.Walk(b.config.OutputDir, visit); err != nil {
return nil, err
}
artifact := &Artifact{
dir: b.config.OutputDir,
f: files,
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
return artifact, nil
}

View File

@ -1,56 +0,0 @@
package lxc
import (
"os"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"config_file": "builder_test.go",
"template_name": "debian",
"template_environment_vars": "SUITE=jessie",
}
}
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)
}
// Bad, missing config file
config = testConfig()
delete(config, "config_file")
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.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}

View File

@ -1,41 +0,0 @@
package lxc
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)
}
func RunCommand(args ...string) error {
var stdout, stderr bytes.Buffer
log.Printf("Executing args: %#v", args)
cmd := exec.Command(args[0], args[1:]...)
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("Command error: %s", stderrString)
}
log.Printf("stdout: %s", stdoutString)
log.Printf("stderr: %s", stderrString)
return err
}

View File

@ -1,181 +0,0 @@
package lxc
import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/tmp"
)
type LxcAttachCommunicator struct {
RootFs string
ContainerName string
AttachOptions []string
CmdWrapper CommandWrapper
}
func (c *LxcAttachCommunicator) Start(ctx context.Context, cmd *packersdk.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-attach execution exited with '%d': '%s'",
exitStatus, cmd.Command)
cmd.SetExited(exitStatus)
}()
return nil
}
func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
log.Printf("Uploading to rootfs: %s", dst)
tf, err := tmp.File("packer-lxc-attach")
if err != nil {
return fmt.Errorf("Error uploading file to rootfs: %s", err)
}
defer os.Remove(tf.Name())
io.Copy(tf, r)
attachCommand := []string{"cat", "%s", " | ", "lxc-attach"}
attachCommand = append(attachCommand, c.AttachOptions...)
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"/bin/cat > %s\""}...)
cpCmd, err := c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), tf.Name(), c.ContainerName, dst))
if err != nil {
return err
}
if fi != nil {
tfDir := filepath.Dir(tf.Name())
// rename tempfile to match original file name. This makes sure that if file is being
// moved into a directory, the filename is preserved instead of a temp name.
adjustedTempName := filepath.Join(tfDir, (*fi).Name())
mvCmd, err := c.CmdWrapper(fmt.Sprintf("mv %s %s", tf.Name(), adjustedTempName))
if err != nil {
return err
}
defer os.Remove(adjustedTempName)
ShellCommand(mvCmd).Run()
// change cpCmd to use new file name as source
cpCmd, err = c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), adjustedTempName, c.ContainerName, dst))
if err != nil {
return err
}
}
log.Printf("Running copy command: %s", dst)
return ShellCommand(cpCmd).Run()
}
func (c *LxcAttachCommunicator) UploadDir(dst string, src string, exclude []string) error {
// TODO: remove any file copied if it appears in `exclude`
dest := filepath.Join(c.RootFs, dst)
log.Printf("Uploading directory '%s' to rootfs '%s'", src, dest)
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s/. %s", src, dest))
if err != nil {
return err
}
return ShellCommand(cpCmd).Run()
}
func (c *LxcAttachCommunicator) Download(src string, w io.Writer) error {
src = filepath.Join(c.RootFs, src)
log.Printf("Downloading from rootfs dir: %s", src)
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(w, f); err != nil {
return err
}
return nil
}
func (c *LxcAttachCommunicator) DownloadDir(src string, dst string, exclude []string) error {
return fmt.Errorf("DownloadDir is not implemented for lxc")
}
func (c *LxcAttachCommunicator) Execute(commandString string) (*exec.Cmd, error) {
log.Printf("Executing with lxc-attach in container: %s %s %s", c.ContainerName, c.RootFs, commandString)
attachCommand := []string{"lxc-attach"}
attachCommand = append(attachCommand, c.AttachOptions...)
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"%s\""}...)
command, err := c.CmdWrapper(
fmt.Sprintf(strings.Join(attachCommand, " "), c.ContainerName, commandString))
if err != nil {
return nil, err
}
localCmd := ShellCommand(command)
log.Printf("Executing lxc-attach: %s %#v", localCmd.Path, localCmd.Args)
return localCmd, nil
}
func (c *LxcAttachCommunicator) CheckInit() (string, error) {
log.Printf("Debug runlevel exec")
localCmd, err := c.Execute("/sbin/runlevel")
if err != nil {
return "", err
}
pr, _ := localCmd.StdoutPipe()
if err = localCmd.Start(); err != nil {
return "", err
}
output, err := ioutil.ReadAll(pr)
if err != nil {
return "", err
}
err = localCmd.Wait()
if err != nil {
return "", err
}
return strings.TrimSpace(string(output)), nil
}

View File

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

View File

@ -1,114 +0,0 @@
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
package lxc
import (
"fmt"
"os"
"time"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/mitchellh/mapstructure"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// The path to the lxc configuration file.
ConfigFile string `mapstructure:"config_file" required:"true"`
// The directory in which to save the exported
// tar.gz. Defaults to `output-<BuildName>` in the current directory.
OutputDir string `mapstructure:"output_directory" required:"false"`
// The name of the LXC container. Usually stored
// in `/var/lib/lxc/containers/<container_name>`. Defaults to
// `packer-<BuildName>`.
ContainerName string `mapstructure:"container_name" required:"false"`
// Allows you to specify a wrapper command, such
// as ssh so you can execute packer builds on a remote host. Defaults to
// `{{.Command}}`; i.e. no wrapper.
CommandWrapper string `mapstructure:"command_wrapper" required:"false"`
// The timeout in seconds to wait for the the
// container to start. Defaults to 20 seconds.
InitTimeout time.Duration `mapstructure:"init_timeout" required:"false"`
// Options to pass to lxc-create. For
// instance, you can specify a custom LXC container configuration file with
// ["-f", "/path/to/lxc.conf"]. Defaults to []. See man 1 lxc-create for
// available options.
CreateOptions []string `mapstructure:"create_options" required:"false"`
// Options to pass to lxc-start. For
// instance, you can override parameters from the LXC container configuration
// file via ["--define", "KEY=VALUE"]. Defaults to []. See
// man 1 lxc-start for available options.
StartOptions []string `mapstructure:"start_options" required:"false"`
// Options to pass to lxc-attach. For
// instance, you can prevent the container from inheriting the host machine's
// environment by specifying ["--clear-env"]. Defaults to []. See
// man 1 lxc-attach for available options.
AttachOptions []string `mapstructure:"attach_options" required:"false"`
// The LXC template name to use.
Name string `mapstructure:"template_name" required:"true"`
// Options to pass to the given
// lxc-template command, usually located in
// `/usr/share/lxc/templates/lxc-<template_name>`. Note: This gets passed as
// ARGV to the template command. Ensure you have an array of strings, as a
// single string with spaces probably won't work. Defaults to [].
Parameters []string `mapstructure:"template_parameters" required:"false"`
// Environmental variables to
// use to build the template with.
EnvVars []string `mapstructure:"template_environment_vars" required:"true"`
// The minimum run level to wait for the
// container to reach. Note some distributions (Ubuntu) simulate run levels
// and may report 5 rather than 3.
TargetRunlevel int `mapstructure:"target_runlevel" required:"false"`
ctx interpolate.Context
}
func (c *Config) Prepare(raws ...interface{}) error {
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
}, raws...)
if err != nil {
return err
}
// Accumulate any errors
var errs *packersdk.MultiError
if c.OutputDir == "" {
c.OutputDir = fmt.Sprintf("output-%s", c.PackerBuildName)
}
if c.ContainerName == "" {
c.ContainerName = fmt.Sprintf("packer-%s", c.PackerBuildName)
}
if c.TargetRunlevel == 0 {
c.TargetRunlevel = 3
}
if c.CommandWrapper == "" {
c.CommandWrapper = "{{.Command}}"
}
if c.InitTimeout == 0 {
c.InitTimeout = 20 * time.Second
}
if _, err := os.Stat(c.ConfigFile); os.IsNotExist(err) {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("LXC Config file appears to be missing: %s", c.ConfigFile))
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}

View File

@ -1,69 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package lxc
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
ConfigFile *string `mapstructure:"config_file" required:"true" cty:"config_file" hcl:"config_file"`
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory" hcl:"output_directory"`
ContainerName *string `mapstructure:"container_name" required:"false" cty:"container_name" hcl:"container_name"`
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
InitTimeout *string `mapstructure:"init_timeout" required:"false" cty:"init_timeout" hcl:"init_timeout"`
CreateOptions []string `mapstructure:"create_options" required:"false" cty:"create_options" hcl:"create_options"`
StartOptions []string `mapstructure:"start_options" required:"false" cty:"start_options" hcl:"start_options"`
AttachOptions []string `mapstructure:"attach_options" required:"false" cty:"attach_options" hcl:"attach_options"`
Name *string `mapstructure:"template_name" required:"true" cty:"template_name" hcl:"template_name"`
Parameters []string `mapstructure:"template_parameters" required:"false" cty:"template_parameters" hcl:"template_parameters"`
EnvVars []string `mapstructure:"template_environment_vars" required:"true" cty:"template_environment_vars" hcl:"template_environment_vars"`
TargetRunlevel *int `mapstructure:"target_runlevel" required:"false" cty:"target_runlevel" hcl:"target_runlevel"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"config_file": &hcldec.AttrSpec{Name: "config_file", Type: cty.String, Required: false},
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
"container_name": &hcldec.AttrSpec{Name: "container_name", Type: cty.String, Required: false},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
"init_timeout": &hcldec.AttrSpec{Name: "init_timeout", Type: cty.String, Required: false},
"create_options": &hcldec.AttrSpec{Name: "create_options", Type: cty.List(cty.String), Required: false},
"start_options": &hcldec.AttrSpec{Name: "start_options", Type: cty.List(cty.String), Required: false},
"attach_options": &hcldec.AttrSpec{Name: "attach_options", Type: cty.List(cty.String), Required: false},
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
"template_parameters": &hcldec.AttrSpec{Name: "template_parameters", Type: cty.List(cty.String), Required: false},
"template_environment_vars": &hcldec.AttrSpec{Name: "template_environment_vars", Type: cty.List(cty.String), Required: false},
"target_runlevel": &hcldec.AttrSpec{Name: "target_runlevel", Type: cty.Number, Required: false},
}
return s
}

View File

@ -1,87 +0,0 @@
package lxc
import (
"context"
"fmt"
"io"
"log"
"os"
"os/user"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepExport struct{}
func (s *stepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
name := config.ContainerName
lxc_dir := "/var/lib/lxc"
user, err := user.Current()
if err != nil {
log.Print("Cannot find current user. Falling back to /var/lib/lxc...")
}
if user.Uid != "0" && user.HomeDir != "" {
lxc_dir = filepath.Join(user.HomeDir, ".local", "share", "lxc")
}
containerDir := filepath.Join(lxc_dir, name)
outputPath := filepath.Join(config.OutputDir, "rootfs.tar.gz")
configFilePath := filepath.Join(config.OutputDir, "lxc-config")
configFile, err := os.Create(configFilePath)
if err != nil {
err := fmt.Errorf("Error creating config file: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
originalConfigFile, err := os.Open(config.ConfigFile)
if err != nil {
err := fmt.Errorf("Error opening config file: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
_, err = io.Copy(configFile, originalConfigFile)
if err != nil {
err := fmt.Errorf("error copying file %s: %v", config.ConfigFile, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
commands := make([][]string, 3)
commands[0] = []string{
"lxc-stop", "--name", name,
}
commands[1] = []string{
"tar", "-C", containerDir, "--numeric-owner", "--anchored", "--exclude=./rootfs/dev/log", "-czf", outputPath, "./rootfs",
}
commands[2] = []string{
"chmod", "+x", configFilePath,
}
ui.Say("Exporting container...")
for _, command := range commands {
err := RunCommand(command...)
if err != nil {
err := fmt.Errorf("Error exporting container: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepExport) Cleanup(state multistep.StateBag) {}

View File

@ -1,78 +0,0 @@
package lxc
import (
"context"
"fmt"
"log"
"os/user"
"path/filepath"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepLxcCreate struct{}
func (s *stepLxcCreate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
name := config.ContainerName
// TODO: read from env
lxc_dir := "/var/lib/lxc"
user, err := user.Current()
if err != nil {
log.Print("Cannot find current user. Falling back to /var/lib/lxc...")
}
if user.Uid != "0" && user.HomeDir != "" {
lxc_dir = filepath.Join(user.HomeDir, ".local", "share", "lxc")
}
rootfs := filepath.Join(lxc_dir, name, "rootfs")
if config.PackerForce {
s.Cleanup(state)
}
commands := make([][]string, 3)
commands[0] = append(commands[0], "env")
commands[0] = append(commands[0], config.EnvVars...)
commands[0] = append(commands[0], "lxc-create")
commands[0] = append(commands[0], config.CreateOptions...)
commands[0] = append(commands[0], []string{"-n", name, "-t", config.Name, "--"}...)
commands[0] = append(commands[0], config.Parameters...)
// prevent tmp from being cleaned on boot, we put provisioning scripts there
// todo: wait for init to finish before moving on to provisioning instead of this
commands[1] = []string{"touch", filepath.Join(rootfs, "tmp", ".tmpfs")}
commands[2] = append([]string{"lxc-start"}, config.StartOptions...)
commands[2] = append(commands[2], []string{"-d", "--name", name}...)
ui.Say("Creating container...")
for _, command := range commands {
err := RunCommand(command...)
if err != nil {
err := fmt.Errorf("Error creating container: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
state.Put("mount_path", rootfs)
return multistep.ActionContinue
}
func (s *stepLxcCreate) Cleanup(state multistep.StateBag) {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
command := []string{
"lxc-destroy", "-f", "-n", config.ContainerName,
}
ui.Say("Unregistering and deleting virtual machine...")
if err := RunCommand(command...); err != nil {
ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err))
}
}

View File

@ -1,51 +0,0 @@
package lxc
import (
"context"
"log"
"os"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepPrepareOutputDir struct{}
func (stepPrepareOutputDir) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
if _, err := os.Stat(config.OutputDir); err == nil && config.PackerForce {
ui.Say("Deleting previous output directory...")
os.RemoveAll(config.OutputDir)
}
if err := os.MkdirAll(config.OutputDir, 0755); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (stepPrepareOutputDir) Cleanup(state multistep.StateBag) {
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
if cancelled || halted {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
ui.Say("Deleting output directory...")
for i := 0; i < 5; i++ {
err := os.RemoveAll(config.OutputDir)
if err == nil {
break
}
log.Printf("Error removing output dir: %s", err)
time.Sleep(2 * time.Second)
}
}
}

View File

@ -1,47 +0,0 @@
package lxc
import (
"context"
"log"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// StepProvision provisions the instance within a chroot.
type StepProvision struct{}
func (s *StepProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
hook := state.Get("hook").(packersdk.Hook)
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
ui := state.Get("ui").(packersdk.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
// Create our communicator
comm := &LxcAttachCommunicator{
ContainerName: config.ContainerName,
AttachOptions: config.AttachOptions,
RootFs: mountPath,
CmdWrapper: wrappedCommand,
}
// Loads hook data from builder's state, if it has been set.
hookData := commonsteps.PopulateProvisionHookData(state)
// Update state generated_data with complete hookData
// to make them accessible by post-processors
state.Put("generated_data", hookData)
// Provision
log.Println("Running the provision hook")
if err := hook.Run(ctx, packersdk.HookProvision, ui, comm, hookData); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepProvision) Cleanup(state multistep.StateBag) {}

View File

@ -1,105 +0,0 @@
package lxc
import (
"context"
"errors"
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type StepWaitInit struct {
WaitTimeout time.Duration
}
func (s *StepWaitInit) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
var err error
cancel := make(chan struct{})
waitDone := make(chan bool, 1)
go func() {
ui.Say("Waiting for container to finish init...")
err = s.waitForInit(state, cancel)
waitDone <- true
}()
log.Printf("Waiting for container to finish init, up to timeout: %s", s.WaitTimeout)
timeout := time.After(s.WaitTimeout)
WaitLoop:
for {
select {
case <-waitDone:
if err != nil {
ui.Error(fmt.Sprintf("Error waiting for container to finish init: %s", err))
return multistep.ActionHalt
}
ui.Say("Container finished init!")
break WaitLoop
case <-timeout:
err := fmt.Errorf("Timeout waiting for container to finish init.")
state.Put("error", err)
ui.Error(err.Error())
close(cancel)
return multistep.ActionHalt
case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
close(cancel)
log.Println("Interrupt detected, quitting waiting for container to finish init.")
return multistep.ActionHalt
}
}
}
return multistep.ActionContinue
}
func (s *StepWaitInit) Cleanup(multistep.StateBag) {
}
func (s *StepWaitInit) waitForInit(state multistep.StateBag, cancel <-chan struct{}) error {
config := state.Get("config").(*Config)
mountPath := state.Get("mount_path").(string)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
for {
select {
case <-cancel:
log.Println("Cancelled. Exiting loop.")
return errors.New("Wait cancelled")
case <-time.After(1 * time.Second):
}
comm := &LxcAttachCommunicator{
ContainerName: config.ContainerName,
AttachOptions: config.AttachOptions,
RootFs: mountPath,
CmdWrapper: wrappedCommand,
}
runlevel, _ := comm.CheckInit()
currentRunlevel := "unknown"
if arr := strings.Split(runlevel, " "); len(arr) >= 2 {
currentRunlevel = arr[1]
}
log.Printf("Current runlevel in container: '%s'", runlevel)
targetRunlevel := fmt.Sprintf("%d", config.TargetRunlevel)
if currentRunlevel == targetRunlevel {
log.Printf("Container finished init.")
break
} else if currentRunlevel > targetRunlevel {
log.Printf("Expected Runlevel %s, Got Runlevel %s, continuing", targetRunlevel, currentRunlevel)
break
}
}
return nil
}

View File

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

View File

@ -1,38 +0,0 @@
package lxd
import (
"fmt"
)
type Artifact struct {
id string
// StateData should store data such as GeneratedData
// to be shared with post-processors
StateData map[string]interface{}
}
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 a.StateData[name]
}
func (a *Artifact) Destroy() error {
_, err := LXDCommand("image", "delete", a.id)
return err
}

View File

@ -1,70 +0,0 @@
package lxd
import (
"context"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
)
// 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) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }
func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
errs := b.config.Prepare(raws...)
if errs != nil {
return nil, nil, errs
}
return nil, nil, nil
}
func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.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("hook", hook)
state.Put("ui", ui)
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
// Run
b.runner = commonsteps.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
b.runner.Run(ctx, 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),
StateData: map[string]interface{}{"generated_data": state.Get("generated_data")},
}
return artifact, nil
}

View File

@ -1,77 +0,0 @@
package lxd
import (
"os"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/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.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
}
}

View File

@ -1,43 +0,0 @@
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
}

View File

@ -1,142 +0,0 @@
package lxd
import (
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"syscall"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type Communicator struct {
ContainerName string
CmdWrapper CommandWrapper
}
func (c *Communicator) Start(ctx context.Context, cmd *packersdk.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 {
ctx := context.TODO()
fileDestination := filepath.Join(c.ContainerName, dst)
// find out if the place we are pushing to is a directory
testDirectoryCommand := fmt.Sprintf(`test -d "%s"`, dst)
cmd := &packersdk.RemoteCmd{Command: testDirectoryCommand}
err := c.Start(ctx, cmd)
if err != nil {
log.Printf("Unable to check whether remote path is a dir: %s", err)
return err
}
cmd.Wait()
if cmd.ExitStatus() == 0 {
log.Printf("path is a directory; copying file into directory.")
fileDestination = filepath.Join(c.ContainerName, dst, (*fi).Name())
}
cpCmd, err := c.CmdWrapper(fmt.Sprintf("lxc file push - %s", fileDestination))
if err != nil {
return err
}
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 {
fileDestination := fmt.Sprintf("%s/%s", c.ContainerName, dst)
pushCommand := fmt.Sprintf("lxc file push --debug -pr %s %s", src, fileDestination)
log.Printf(pushCommand)
cp, err := c.CmdWrapper(pushCommand)
if err != nil {
log.Printf("Error running cp command: %s", err)
return err
}
cpCmd := ShellCommand(cp)
log.Printf("Running cp command: %s", cp)
err = cpCmd.Run()
if err != nil {
log.Printf("Error running cp 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
}

View File

@ -1,21 +0,0 @@
package lxd
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestCommunicator_ImplementsCommunicator(t *testing.T) {
var raw interface{}
raw = &Communicator{}
if _, ok := raw.(packersdk.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

View File

@ -1,92 +0,0 @@
//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config
package lxd
import (
"fmt"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/mitchellh/mapstructure"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// The name of the output artifact. Defaults to
// name.
OutputImage string `mapstructure:"output_image" required:"false"`
ContainerName string `mapstructure:"container_name"`
// Lets you prefix all builder commands, such as
// with ssh for a remote build host. Defaults to `{{.Command}}`; i.e. no
// wrapper.
CommandWrapper string `mapstructure:"command_wrapper" required:"false"`
// 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, ...
Image string `mapstructure:"image" required:"true"`
Profile string `mapstructure:"profile"`
// The number of seconds to sleep between launching
// the LXD instance and provisioning it; defaults to 3 seconds.
InitSleep string `mapstructure:"init_sleep" required:"false"`
// 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.
PublishProperties map[string]string `mapstructure:"publish_properties" required:"false"`
// List of key/value pairs you wish to
// pass to lxc launch via --config. Defaults to empty.
LaunchConfig map[string]string `mapstructure:"launch_config" required:"false"`
ctx interpolate.Context
}
func (c *Config) Prepare(raws ...interface{}) error {
var md mapstructure.Metadata
err := config.Decode(c, &config.DecodeOpts{
Metadata: &md,
Interpolate: true,
}, raws...)
if err != nil {
return err
}
// Accumulate any errors
var errs *packersdk.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 = packersdk.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 c.Profile == "" {
c.Profile = "default"
}
// Sadly we have to wait a few seconds for /tmp to be intialized and networking
// to finish starting. There isn't a great cross platform to check when things are ready.
if c.InitSleep == "" {
c.InitSleep = "3"
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}

View File

@ -1,61 +0,0 @@
// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT.
package lxd
import (
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/zclconf/go-cty/cty"
)
// FlatConfig is an auto-generated flat version of Config.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatConfig struct {
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"`
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"`
PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"`
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"`
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"`
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
OutputImage *string `mapstructure:"output_image" required:"false" cty:"output_image" hcl:"output_image"`
ContainerName *string `mapstructure:"container_name" cty:"container_name" hcl:"container_name"`
CommandWrapper *string `mapstructure:"command_wrapper" required:"false" cty:"command_wrapper" hcl:"command_wrapper"`
Image *string `mapstructure:"image" required:"true" cty:"image" hcl:"image"`
Profile *string `mapstructure:"profile" cty:"profile" hcl:"profile"`
InitSleep *string `mapstructure:"init_sleep" required:"false" cty:"init_sleep" hcl:"init_sleep"`
PublishProperties map[string]string `mapstructure:"publish_properties" required:"false" cty:"publish_properties" hcl:"publish_properties"`
LaunchConfig map[string]string `mapstructure:"launch_config" required:"false" cty:"launch_config" hcl:"launch_config"`
}
// FlatMapstructure returns a new FlatConfig.
// FlatConfig is an auto-generated flat version of Config.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatConfig)
}
// HCL2Spec returns the hcl spec of a Config.
// This spec is used by HCL to read the fields of Config.
// The decoded values from this spec will then be applied to a FlatConfig.
func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false},
"packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false},
"packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false},
"packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false},
"packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false},
"packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false},
"packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false},
"packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false},
"output_image": &hcldec.AttrSpec{Name: "output_image", Type: cty.String, Required: false},
"container_name": &hcldec.AttrSpec{Name: "container_name", Type: cty.String, Required: false},
"command_wrapper": &hcldec.AttrSpec{Name: "command_wrapper", Type: cty.String, Required: false},
"image": &hcldec.AttrSpec{Name: "image", Type: cty.String, Required: false},
"profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false},
"init_sleep": &hcldec.AttrSpec{Name: "init_sleep", Type: cty.String, Required: false},
"publish_properties": &hcldec.AttrSpec{Name: "publish_properties", Type: cty.Map(cty.String), Required: false},
"launch_config": &hcldec.AttrSpec{Name: "launch_config", Type: cty.Map(cty.String), Required: false},
}
return s
}

View File

@ -1,68 +0,0 @@
package lxd
import (
"context"
"fmt"
"log"
"strconv"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepLxdLaunch struct{}
func (s *stepLxdLaunch) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
name := config.ContainerName
image := config.Image
profile := fmt.Sprintf("--profile=%s", config.Profile)
launch_args := []string{
"launch", "--ephemeral=false", profile, image, name,
}
for k, v := range config.LaunchConfig {
launch_args = append(launch_args, "--config", fmt.Sprintf("%s=%s", k, v))
}
ui.Say("Creating container...")
_, err := LXDCommand(launch_args...)
if err != nil {
err := fmt.Errorf("Error creating container: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
sleep_seconds, err := strconv.Atoi(config.InitSleep)
if err != nil {
err := fmt.Errorf("Error parsing InitSleep into int: %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 doesn't get cleared and lose our provisioner scripts.
time.Sleep(time.Duration(sleep_seconds) * time.Second)
log.Printf("Sleeping for %d seconds...", sleep_seconds)
return multistep.ActionContinue
}
func (s *stepLxdLaunch) Cleanup(state multistep.StateBag) {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
cleanup_args := []string{
"delete", "--force", config.ContainerName,
}
ui.Say("Unregistering and deleting deleting container...")
if _, err := LXDCommand(cleanup_args...); err != nil {
ui.Error(fmt.Sprintf("Error deleting container: %s", err))
}
}

View File

@ -1,44 +0,0 @@
package lxd
import (
"context"
"log"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// StepProvision provisions the container
type StepProvision struct{}
func (s *StepProvision) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
hook := state.Get("hook").(packersdk.Hook)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
wrappedCommand := state.Get("wrappedCommand").(CommandWrapper)
// Create our communicator
comm := &Communicator{
ContainerName: config.ContainerName,
CmdWrapper: wrappedCommand,
}
// Loads hook data from builder's state, if it has been set.
hookData := commonsteps.PopulateProvisionHookData(state)
// Update state generated_data with complete hookData
// to make them accessible by post-processors
state.Put("generated_data", hookData)
// Provision
log.Println("Running the provision hook")
if err := hook.Run(ctx, packersdk.HookProvision, ui, comm, hookData); err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepProvision) Cleanup(state multistep.StateBag) {}

View File

@ -1,60 +0,0 @@
package lxd
import (
"context"
"fmt"
"regexp"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
type stepPublish struct{}
func (s *stepPublish) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.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,
}
for k, v := range config.PublishProperties {
publish_args = append(publish_args, fmt.Sprintf("%s=%s", k, v))
}
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) {}

View File

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

View File

@ -18,8 +18,6 @@ import (
azuredtlbuilder "github.com/hashicorp/packer/builder/azure/dtl" azuredtlbuilder "github.com/hashicorp/packer/builder/azure/dtl"
filebuilder "github.com/hashicorp/packer/builder/file" filebuilder "github.com/hashicorp/packer/builder/file"
hcloudbuilder "github.com/hashicorp/packer/builder/hcloud" hcloudbuilder "github.com/hashicorp/packer/builder/hcloud"
lxcbuilder "github.com/hashicorp/packer/builder/lxc"
lxdbuilder "github.com/hashicorp/packer/builder/lxd"
nullbuilder "github.com/hashicorp/packer/builder/null" nullbuilder "github.com/hashicorp/packer/builder/null"
oneandonebuilder "github.com/hashicorp/packer/builder/oneandone" oneandonebuilder "github.com/hashicorp/packer/builder/oneandone"
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks" profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
@ -55,8 +53,6 @@ var Builders = map[string]packersdk.Builder{
"azure-dtl": new(azuredtlbuilder.Builder), "azure-dtl": new(azuredtlbuilder.Builder),
"file": new(filebuilder.Builder), "file": new(filebuilder.Builder),
"hcloud": new(hcloudbuilder.Builder), "hcloud": new(hcloudbuilder.Builder),
"lxc": new(lxcbuilder.Builder),
"lxd": new(lxdbuilder.Builder),
"null": new(nullbuilder.Builder), "null": new(nullbuilder.Builder),
"oneandone": new(oneandonebuilder.Builder), "oneandone": new(oneandonebuilder.Builder),
"profitbricks": new(profitbricksbuilder.Builder), "profitbricks": new(profitbricksbuilder.Builder),

View File

@ -38,6 +38,8 @@ import (
hypervvmcxbuilder "github.com/hashicorp/packer-plugin-hyperv/builder/hyperv/vmcx" hypervvmcxbuilder "github.com/hashicorp/packer-plugin-hyperv/builder/hyperv/vmcx"
jdcloudbuilder "github.com/hashicorp/packer-plugin-jdcloud/builder/jdcloud" jdcloudbuilder "github.com/hashicorp/packer-plugin-jdcloud/builder/jdcloud"
linodebuilder "github.com/hashicorp/packer-plugin-linode/builder/linode" linodebuilder "github.com/hashicorp/packer-plugin-linode/builder/linode"
lxcbuilder "github.com/hashicorp/packer-plugin-lxc/builder/lxc"
lxdbuilder "github.com/hashicorp/packer-plugin-lxd/builder/lxd"
ncloudbuilder "github.com/hashicorp/packer-plugin-ncloud/builder/ncloud" ncloudbuilder "github.com/hashicorp/packer-plugin-ncloud/builder/ncloud"
openstackbuilder "github.com/hashicorp/packer-plugin-openstack/builder/openstack" openstackbuilder "github.com/hashicorp/packer-plugin-openstack/builder/openstack"
oracleclassicbuilder "github.com/hashicorp/packer-plugin-oracle/builder/classic" oracleclassicbuilder "github.com/hashicorp/packer-plugin-oracle/builder/classic"
@ -96,6 +98,8 @@ var VendoredBuilders = map[string]packersdk.Builder{
"hyperone": new(hyperonebuilder.Builder), "hyperone": new(hyperonebuilder.Builder),
"jdcloud": new(jdcloudbuilder.Builder), "jdcloud": new(jdcloudbuilder.Builder),
"linode": new(linodebuilder.Builder), "linode": new(linodebuilder.Builder),
"lxc": new(lxcbuilder.Builder),
"lxd": new(lxdbuilder.Builder),
"ncloud": new(ncloudbuilder.Builder), "ncloud": new(ncloudbuilder.Builder),
"openstack": new(openstackbuilder.Builder), "openstack": new(openstackbuilder.Builder),
"oracle-classic": new(oracleclassicbuilder.Builder), "oracle-classic": new(oracleclassicbuilder.Builder),

2
go.mod
View File

@ -45,6 +45,8 @@ require (
github.com/hashicorp/packer-plugin-hyperv v0.0.1 github.com/hashicorp/packer-plugin-hyperv v0.0.1
github.com/hashicorp/packer-plugin-jdcloud v0.0.1 github.com/hashicorp/packer-plugin-jdcloud v0.0.1
github.com/hashicorp/packer-plugin-linode v0.0.2 github.com/hashicorp/packer-plugin-linode v0.0.2
github.com/hashicorp/packer-plugin-lxc v0.0.1
github.com/hashicorp/packer-plugin-lxd v0.0.1
github.com/hashicorp/packer-plugin-ncloud v0.0.2 github.com/hashicorp/packer-plugin-ncloud v0.0.2
github.com/hashicorp/packer-plugin-openstack v0.0.2 github.com/hashicorp/packer-plugin-openstack v0.0.2
github.com/hashicorp/packer-plugin-oracle v0.0.3 github.com/hashicorp/packer-plugin-oracle v0.0.3

4
go.sum
View File

@ -544,6 +544,10 @@ github.com/hashicorp/packer-plugin-jdcloud v0.0.1 h1:MLvAroDOHWimBf6cBa0trlHJpB8
github.com/hashicorp/packer-plugin-jdcloud v0.0.1/go.mod h1:bVGtjp3v98rpguEYxJAXQbg8CjllInh5WFqO9a0T4lc= github.com/hashicorp/packer-plugin-jdcloud v0.0.1/go.mod h1:bVGtjp3v98rpguEYxJAXQbg8CjllInh5WFqO9a0T4lc=
github.com/hashicorp/packer-plugin-linode v0.0.2 h1:obN0kQKlfCQuFmRwVx6ksQApQZ85gPAcSCtjDd0F30c= github.com/hashicorp/packer-plugin-linode v0.0.2 h1:obN0kQKlfCQuFmRwVx6ksQApQZ85gPAcSCtjDd0F30c=
github.com/hashicorp/packer-plugin-linode v0.0.2/go.mod h1:uF8FAE3+PG/lVI1TwjXSaS3AHv4+Wb3/3gsrg0h6IQ0= github.com/hashicorp/packer-plugin-linode v0.0.2/go.mod h1:uF8FAE3+PG/lVI1TwjXSaS3AHv4+Wb3/3gsrg0h6IQ0=
github.com/hashicorp/packer-plugin-lxc v0.0.1 h1:UPcoEYb/MFArZZLKyMdEiNqbvDthw+3PULKhgN98tKM=
github.com/hashicorp/packer-plugin-lxc v0.0.1/go.mod h1:W5fbCd3CnNUa+ApVGJ6QwAt646QT3eMhNeBWIcM51As=
github.com/hashicorp/packer-plugin-lxd v0.0.1 h1:CrFbQmQmdgI3n1RHMPmTUDinRPnPa/bIS9IsMtzERp8=
github.com/hashicorp/packer-plugin-lxd v0.0.1/go.mod h1:h3wqgxQiWy8pIJytTjeqXlAd1PXfNTvhTdODnvzDG3w=
github.com/hashicorp/packer-plugin-ncloud v0.0.2 h1:MGvGkOVfzeosqOSs5dteghLwv9VRcRxTuLoLX1ssUag= github.com/hashicorp/packer-plugin-ncloud v0.0.2 h1:MGvGkOVfzeosqOSs5dteghLwv9VRcRxTuLoLX1ssUag=
github.com/hashicorp/packer-plugin-ncloud v0.0.2/go.mod h1:Hud2R1pkky96TQy3TPTTrr9Kej4b/4dqC/v+uEE0VDY= github.com/hashicorp/packer-plugin-ncloud v0.0.2/go.mod h1:Hud2R1pkky96TQy3TPTTrr9Kej4b/4dqC/v+uEE0VDY=
github.com/hashicorp/packer-plugin-openstack v0.0.2 h1:wGNE8es3Bn9auuIoX+gqT9chXzYY9GlM55eSpM4uwtU= github.com/hashicorp/packer-plugin-openstack v0.0.2 h1:wGNE8es3Bn9auuIoX+gqT9chXzYY9GlM55eSpM4uwtU=

View File

@ -1,116 +0,0 @@
---
description: |
The `lxc` Packer builder builds containers for lxc1. The builder starts an LXC
container, runs provisioners within this container, then exports the container
as a tar.gz of the root file system.
page_title: LXC - Builders
---
# LXC Builder
Type: `lxc`
Artifact BuilderId: `ustream.lxc`
The `lxc` Packer builder builds containers for lxc1. The builder starts an LXC
container, runs provisioners within this container, then exports the container
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
`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
container if the parent OS is Debian based.
## Basic Example
Below is a fully functioning example.
```json
{
"builders": [
{
"type": "lxc",
"name": "lxc-trusty",
"config_file": "/tmp/lxc/config",
"template_name": "ubuntu",
"template_environment_vars": ["SUITE=trusty"]
},
{
"type": "lxc",
"name": "lxc-xenial",
"config_file": "/tmp/lxc/config",
"template_name": "ubuntu",
"template_environment_vars": ["SUITE=xenial"]
},
{
"type": "lxc",
"name": "lxc-jessie",
"config_file": "/tmp/lxc/config",
"template_name": "debian",
"template_environment_vars": ["SUITE=jessie"]
},
{
"type": "lxc",
"name": "lxc-centos-7-x64",
"config_file": "/tmp/lxc/config",
"template_name": "centos",
"template_parameters": ["-R", "7", "-a", "x86_64"]
}
]
}
```
## Configuration Reference
### Required:
- `config_file` (string) - The path to the lxc configuration file.
- `template_name` (string) - The LXC template name to use.
- `template_environment_vars` (array of strings) - Environmental variables to
use to build the template with.
### Optional:
- `target_runlevel` (number) - The minimum run level to wait for the
container to reach. Note some distributions (Ubuntu) simulate run levels
and may report 5 rather than 3.
- `output_directory` (string) - The directory in which to save the exported
tar.gz. Defaults to `output-<BuildName>` in the current directory.
- `container_name` (string) - The name of the LXC container. Usually stored
in `/var/lib/lxc/containers/<container_name>`. Defaults to
`packer-<BuildName>`.
- `command_wrapper` (string) - Allows you to specify a wrapper command, such
as `ssh` so you can execute Packer builds on a remote host. Defaults to
Empty.
- `init_timeout` (string) - The timeout in seconds to wait for the the
container to start. Defaults to 20 seconds.
- `template_parameters` (array of strings) - Options to pass to the given
`lxc-template` command, usually located in
`/usr/share/lxc/templates/lxc-<template_name>`. Note: This gets passed as
ARGV to the template command. Ensure you have an array of strings, as a
single string with spaces probably won't work. Defaults to `[]`.
- `create_options` (array of strings) - Options to pass to `lxc-create`. For
instance, you can specify a custom LXC container configuration file with
`["-f", "/path/to/lxc.conf"]`. Defaults to `[]`. See `man 1 lxc-create` for
available options.
- `start_options` (array of strings) - Options to pass to `lxc-start`. For
instance, you can override parameters from the LXC container configuration
file via `["--define", "KEY=VALUE"]`. Defaults to `[]`. See
`man 1 lxc-start` for available options.
- `attach_options` (array of strings) - Options to pass to `lxc-attach`. For
instance, you can prevent the container from inheriting the host machine's
environment by specifying `["--clear-env"]`. Defaults to `[]`. See
`man 1 lxc-attach` for available options.

View File

@ -1,80 +0,0 @@
---
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.
page_title: LXD - Builders
---
# LXD Builder
Type: `lxd`
Artifact BuilderId: `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.
```json
{
"builders": [
{
"type": "lxd",
"name": "lxd-xenial",
"image": "ubuntu-daily:xenial",
"output_image": "ubuntu-xenial",
"publish_properties": {
"description": "Trivial repackage with Packer"
}
}
]
}
```
## 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:
- `init_sleep` (string) - The number of seconds to sleep between launching
the LXD instance and provisioning it; defaults to 3 seconds.
- `name` (string) - Name of the builder. Defaults to `lxd`.
- `container_name` (string) - Name of the build container.
Defaults to `packer-$name`.
- `profile` - Name of the LXD profile used for the build container.
Defaults to `default`.
- `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 `""`.
- `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 [here](https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/) 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.

View File

@ -1,42 +0,0 @@
<!-- Code generated from the comments of the Config struct in builder/lxc/config.go; DO NOT EDIT MANUALLY -->
- `output_directory` (string) - The directory in which to save the exported
tar.gz. Defaults to `output-<BuildName>` in the current directory.
- `container_name` (string) - The name of the LXC container. Usually stored
in `/var/lib/lxc/containers/<container_name>`. Defaults to
`packer-<BuildName>`.
- `command_wrapper` (string) - Allows you to specify a wrapper command, such
as ssh so you can execute packer builds on a remote host. Defaults to
`{{.Command}}`; i.e. no wrapper.
- `init_timeout` (duration string | ex: "1h5m2s") - The timeout in seconds to wait for the the
container to start. Defaults to 20 seconds.
- `create_options` ([]string) - Options to pass to lxc-create. For
instance, you can specify a custom LXC container configuration file with
["-f", "/path/to/lxc.conf"]. Defaults to []. See man 1 lxc-create for
available options.
- `start_options` ([]string) - Options to pass to lxc-start. For
instance, you can override parameters from the LXC container configuration
file via ["--define", "KEY=VALUE"]. Defaults to []. See
man 1 lxc-start for available options.
- `attach_options` ([]string) - Options to pass to lxc-attach. For
instance, you can prevent the container from inheriting the host machine's
environment by specifying ["--clear-env"]. Defaults to []. See
man 1 lxc-attach for available options.
- `template_parameters` ([]string) - Options to pass to the given
lxc-template command, usually located in
`/usr/share/lxc/templates/lxc-<template_name>`. Note: This gets passed as
ARGV to the template command. Ensure you have an array of strings, as a
single string with spaces probably won't work. Defaults to [].
- `target_runlevel` (int) - The minimum run level to wait for the
container to reach. Note some distributions (Ubuntu) simulate run levels
and may report 5 rather than 3.
<!-- End of code generated from the comments of the Config struct in builder/lxc/config.go; -->

View File

@ -1,10 +0,0 @@
<!-- Code generated from the comments of the Config struct in builder/lxc/config.go; DO NOT EDIT MANUALLY -->
- `config_file` (string) - The path to the lxc configuration file.
- `template_name` (string) - The LXC template name to use.
- `template_environment_vars` ([]string) - Environmental variables to
use to build the template with.
<!-- End of code generated from the comments of the Config struct in builder/lxc/config.go; -->

View File

@ -1,26 +0,0 @@
<!-- Code generated from the comments of the Config struct in builder/lxd/config.go; DO NOT EDIT MANUALLY -->
- `output_image` (string) - The name of the output artifact. Defaults to
name.
- `container_name` (string) - Container Name
- `command_wrapper` (string) - Lets you prefix all builder commands, such as
with ssh for a remote build host. Defaults to `{{.Command}}`; i.e. no
wrapper.
- `profile` (string) - Profile
- `init_sleep` (string) - The number of seconds to sleep between launching
the LXD instance and provisioning it; defaults to 3 seconds.
- `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.
- `launch_config` (map[string]string) - List of key/value pairs you wish to
pass to lxc launch via --config. Defaults to empty.
<!-- End of code generated from the comments of the Config struct in builder/lxd/config.go; -->

View File

@ -1,7 +0,0 @@
<!-- Code generated from the comments of the Config struct in builder/lxd/config.go; DO NOT EDIT MANUALLY -->
- `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, ...
<!-- End of code generated from the comments of the Config struct in builder/lxd/config.go; -->

View File

@ -704,14 +704,6 @@
"title": "Hetzner Cloud", "title": "Hetzner Cloud",
"path": "builders/hetzner-cloud" "path": "builders/hetzner-cloud"
}, },
{
"title": "LXC",
"path": "builders/lxc"
},
{
"title": "LXD",
"path": "builders/lxd"
},
{ {
"title": "Null", "title": "Null",
"path": "builders/null" "path": "builders/null"

View File

@ -86,6 +86,20 @@
"pluginTier": "community", "pluginTier": "community",
"version": "latest" "version": "latest"
}, },
{
"title": "LXC",
"path": "lxc",
"repo": "hashicorp/packer-plugin-lxc",
"pluginTier": "community",
"version": "latest"
},
{
"title": "LXD",
"path": "lxd",
"repo": "hashicorp/packer-plugin-lxd",
"pluginTier": "community",
"version": "latest"
},
{ {
"title": "Naver Cloud", "title": "Naver Cloud",
"path": "ncloud", "path": "ncloud",