extract and vendor lxc and lxd (#10965)
This commit is contained in:
parent
2cd296874e
commit
972497589e
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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) {}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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) {}
|
|
@ -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)
|
||||
}
|
|
@ -18,8 +18,6 @@ import (
|
|||
azuredtlbuilder "github.com/hashicorp/packer/builder/azure/dtl"
|
||||
filebuilder "github.com/hashicorp/packer/builder/file"
|
||||
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"
|
||||
oneandonebuilder "github.com/hashicorp/packer/builder/oneandone"
|
||||
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
|
||||
|
@ -55,8 +53,6 @@ var Builders = map[string]packersdk.Builder{
|
|||
"azure-dtl": new(azuredtlbuilder.Builder),
|
||||
"file": new(filebuilder.Builder),
|
||||
"hcloud": new(hcloudbuilder.Builder),
|
||||
"lxc": new(lxcbuilder.Builder),
|
||||
"lxd": new(lxdbuilder.Builder),
|
||||
"null": new(nullbuilder.Builder),
|
||||
"oneandone": new(oneandonebuilder.Builder),
|
||||
"profitbricks": new(profitbricksbuilder.Builder),
|
||||
|
|
|
@ -38,6 +38,8 @@ import (
|
|||
hypervvmcxbuilder "github.com/hashicorp/packer-plugin-hyperv/builder/hyperv/vmcx"
|
||||
jdcloudbuilder "github.com/hashicorp/packer-plugin-jdcloud/builder/jdcloud"
|
||||
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"
|
||||
openstackbuilder "github.com/hashicorp/packer-plugin-openstack/builder/openstack"
|
||||
oracleclassicbuilder "github.com/hashicorp/packer-plugin-oracle/builder/classic"
|
||||
|
@ -96,6 +98,8 @@ var VendoredBuilders = map[string]packersdk.Builder{
|
|||
"hyperone": new(hyperonebuilder.Builder),
|
||||
"jdcloud": new(jdcloudbuilder.Builder),
|
||||
"linode": new(linodebuilder.Builder),
|
||||
"lxc": new(lxcbuilder.Builder),
|
||||
"lxd": new(lxdbuilder.Builder),
|
||||
"ncloud": new(ncloudbuilder.Builder),
|
||||
"openstack": new(openstackbuilder.Builder),
|
||||
"oracle-classic": new(oracleclassicbuilder.Builder),
|
||||
|
|
2
go.mod
2
go.mod
|
@ -45,6 +45,8 @@ require (
|
|||
github.com/hashicorp/packer-plugin-hyperv 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-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-openstack v0.0.2
|
||||
github.com/hashicorp/packer-plugin-oracle v0.0.3
|
||||
|
|
4
go.sum
4
go.sum
|
@ -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-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-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/go.mod h1:Hud2R1pkky96TQy3TPTTrr9Kej4b/4dqC/v+uEE0VDY=
|
||||
github.com/hashicorp/packer-plugin-openstack v0.0.2 h1:wGNE8es3Bn9auuIoX+gqT9chXzYY9GlM55eSpM4uwtU=
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -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; -->
|
|
@ -704,14 +704,6 @@
|
|||
"title": "Hetzner Cloud",
|
||||
"path": "builders/hetzner-cloud"
|
||||
},
|
||||
{
|
||||
"title": "LXC",
|
||||
"path": "builders/lxc"
|
||||
},
|
||||
{
|
||||
"title": "LXD",
|
||||
"path": "builders/lxd"
|
||||
},
|
||||
{
|
||||
"title": "Null",
|
||||
"path": "builders/null"
|
||||
|
|
|
@ -86,6 +86,20 @@
|
|||
"pluginTier": "community",
|
||||
"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",
|
||||
"path": "ncloud",
|
||||
|
|
Loading…
Reference in New Issue