[lxc-devel] [distrobuilder/master] *: Switch to cobra CLI

monstermunchkin on Github lxc-bot at linuxcontainers.org
Mon Mar 5 16:50:41 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 379 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180305/53ae0bd6/attachment.bin>
-------------- next part --------------
From 5f4b4b25c524c1c6bf57412d4a5f3815e9b6ff0a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 2 Mar 2018 22:08:58 +0100
Subject: [PATCH] *: Switch to cobra CLI

Resolves #13

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 distrobuilder/main.go           | 281 ++++++++++++++++++++--------------------
 distrobuilder/main_build-dir.go |  20 +++
 distrobuilder/main_lxc.go       |  79 +++++++++++
 distrobuilder/main_lxd.go       |  91 +++++++++++++
 generators/hostname_test.go     |   4 +-
 generators/hosts_test.go        |   4 +-
 image/lxc.go                    |  17 ++-
 image/lxc_test.go               |   4 +-
 image/lxd.go                    |  20 ++-
 image/lxd_test.go               |   2 +-
 10 files changed, 359 insertions(+), 163 deletions(-)
 create mode 100644 distrobuilder/main_build-dir.go
 create mode 100644 distrobuilder/main_lxc.go
 create mode 100644 distrobuilder/main_lxd.go

diff --git a/distrobuilder/main.go b/distrobuilder/main.go
index dfb9e92..c893092 100644
--- a/distrobuilder/main.go
+++ b/distrobuilder/main.go
@@ -53,132 +53,129 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/lxc/distrobuilder/generators"
-	"github.com/lxc/distrobuilder/image"
 	"github.com/lxc/distrobuilder/shared"
 	"github.com/lxc/distrobuilder/sources"
+	"github.com/spf13/cobra"
 
-	lxd "github.com/lxc/lxd/shared"
-	cli "gopkg.in/urfave/cli.v1"
 	yaml "gopkg.in/yaml.v2"
 )
 
+type cmdGlobal struct {
+	flagCleanup  bool
+	flagCacheDir string
+
+	definition *shared.Definition
+	sourceDir  string
+	targetDir  string
+}
+
 func main() {
-	app := cli.NewApp()
-	app.Usage = "image generator"
-	// INPUT can either be a file or '-' which reads from stdin
-	app.ArgsUsage = "[file|-]"
-	app.HideHelp = true
-	app.Action = run
-	app.Flags = []cli.Flag{
-		cli.BoolFlag{
-			Name:  "lxc",
-			Usage: "generate LXC image files",
-		},
-		cli.BoolFlag{
-			Name:  "lxd",
-			Usage: "generate LXD image files",
-		},
-		cli.BoolFlag{
-			Name:  "plain",
-			Usage: "generate plain chroot",
-		},
-		cli.BoolTFlag{
-			Name:  "unified",
-			Usage: "output unified tarball for LXD images",
-		},
-		cli.BoolTFlag{
-			Name:  "cleanup",
-			Usage: "clean up build directory",
-		},
-		cli.StringFlag{
-			Name:  "template-dir",
-			Usage: "template directory",
-		},
-		cli.StringFlag{
-			Name:  "cache-dir",
-			Usage: "cache directory",
-			Value: "/var/cache/distrobuilder",
-		},
-		cli.StringFlag{
-			Name:  "compression",
-			Usage: "compression algorithm",
-		},
-		cli.BoolFlag{
-			Name:  "help, h",
-			Usage: "show help",
-		},
+	// Sanity checks
+	if os.Geteuid() != 0 {
+		fmt.Fprintln(os.Stderr, "You must be root to run this tool")
+		os.Exit(1)
 	}
 
-	err := app.Run(os.Args)
+	// Global flags
+	globalCmd := cmdGlobal{}
+
+	app := &cobra.Command{
+		Use:                "distrobuilder",
+		Short:              "System container image builder for LXC and LXD",
+		PersistentPostRunE: globalCmd.postRun,
+	}
+
+	app.PersistentFlags().BoolVar(&globalCmd.flagCleanup, "cleanup", true,
+		"Clean up cache directory")
+	app.PersistentFlags().StringVar(&globalCmd.flagCacheDir, "cache-dir",
+		"/var/cache/distrobuilder", "Cache directory")
+
+	// LXC sub-commands
+	LXCCmd := cmdLXC{global: &globalCmd}
+	app.AddCommand(LXCCmd.commandBuild())
+	app.AddCommand(LXCCmd.commandPack())
+
+	// LXD sub-commands
+	LXDCmd := cmdLXD{global: &globalCmd}
+	app.AddCommand(LXDCmd.commandBuild())
+	app.AddCommand(LXDCmd.commandPack())
+
+	// build-dir sub-command
+	buildDirCmd := cmdBuildDir{global: &globalCmd}
+	app.AddCommand(buildDirCmd.command())
+
+	// Run the main command and handle errors
+	err := app.Execute()
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error: %s\n", err)
 		os.Exit(1)
 	}
 }
 
-func run(c *cli.Context) error {
-	// Sanity checks
-	if os.Geteuid() != 0 {
-		return fmt.Errorf("You must be root to run this tool")
+func (c *cmdGlobal) preRunBuild(cmd *cobra.Command, args []string) error {
+	c.sourceDir = c.flagCacheDir
+
+	if len(args) > 1 {
+		// Create and set target directory if provided
+		err := os.MkdirAll(args[1], 0755)
+		if err != nil {
+			return err
+		}
+		c.targetDir = args[1]
+	} else {
+		// Use current working directory as target
+		var err error
+		c.targetDir, err = os.Getwd()
+		if err != nil {
+			return err
+		}
 	}
 
-	// Create our working directory
-	os.RemoveAll(c.GlobalString("cache-dir"))
-	os.MkdirAll(c.GlobalString("cache-dir"), 0755)
+	// Special case: Make cache directory the target directory. This will
+	// prevent us from having to move ${cacheDir}/rootfs to ${target}/rootfs.
+	if cmd.CalledAs() == "build-dir" {
+		c.flagCacheDir = c.targetDir
+	}
+
+	var err error
 
 	// Get the image definition
-	def, err := getDefinition(c.Args().Get(0))
+	c.definition, err = getDefinition(args[0])
 	if err != nil {
-		return fmt.Errorf("Error getting definition: %s", err)
+		return err
 	}
 
-	// Get the downloader to use for this image
-	downloader := sources.Get(def.Source.Downloader)
-	if downloader == nil {
-		return fmt.Errorf("Unsupported source downloader: %s", def.Source.Downloader)
+	// Get the mapped architecture
+	arch, err := getMappedArchitecture(c.definition)
+	if err != nil {
+		return err
 	}
 
-	// Translate the requested architecture name
-	var arch string
-	if def.Mappings.ArchitectureMap != "" {
-		// Translate the architecture using the requested map
-		arch, err = shared.GetArch(def.Mappings.ArchitectureMap, def.Image.Arch)
-		if err != nil {
-			return fmt.Errorf("Failed to translate the architecture name: %s", err)
-		}
-	} else if len(def.Mappings.Architectures) > 0 {
-		// Translate the architecture using a user specified mapping
-		var ok bool
-		arch, ok = def.Mappings.Architectures[def.Image.Arch]
-		if !ok {
-			// If no mapping exists, it means it doesn't need translating
-			arch = def.Image.Arch
-		}
-	} else {
-		// No map or mappings provided, just go with it as it is
-		arch = def.Image.Arch
+	// Create cache directory
+	err = os.MkdirAll(c.flagCacheDir, 0755)
+	if err != nil {
+		return err
+	}
+
+	// Get the downloader to use for this image
+	downloader := sources.Get(c.definition.Source.Downloader)
+	if downloader == nil {
+		return fmt.Errorf("Unsupported source downloader: %s", c.definition.Source.Downloader)
 	}
 
 	// Download the root filesystem
-	err = downloader.Run(def.Source, def.Image.Release, arch,
-		c.GlobalString("cache-dir"))
+	err = downloader.Run(c.definition.Source, c.definition.Image.Release, arch, c.flagCacheDir)
 	if err != nil {
 		return fmt.Errorf("Error while downloading source: %s", err)
 	}
 
-	if c.GlobalBoolT("cleanup") {
-		defer os.RemoveAll(c.GlobalString("cache-dir"))
-	}
-
 	// Setup the mounts and chroot into the rootfs
-	exitChroot, err := setupChroot(filepath.Join(c.GlobalString("cache-dir"), "rootfs"))
+	exitChroot, err := setupChroot(filepath.Join(c.flagCacheDir, "rootfs"))
 	if err != nil {
 		return fmt.Errorf("Failed to setup chroot: %s", err)
 	}
 
 	// Install/remove/update packages
-	err = managePackages(def.Packages)
+	err = managePackages(c.definition.Packages)
 	if err != nil {
 		exitChroot()
 		return fmt.Errorf("Failed to manage packages: %s", err)
@@ -187,67 +184,39 @@ func run(c *cli.Context) error {
 	// Unmount everything and exit the chroot
 	exitChroot()
 
-	if c.GlobalBool("lxc") {
-		img := image.NewLXCImage(c.GlobalString("cache-dir"), def.Image, def.Targets.LXC)
-
-		for _, file := range def.Files {
-			generator := generators.Get(file.Generator)
-			if generator == nil {
-				return fmt.Errorf("Unknown generator '%s'", file.Generator)
-			}
-
-			if len(file.Releases) > 0 && !lxd.StringInSlice(def.Image.Release, file.Releases) {
-				continue
-			}
+	return nil
+}
 
-			err := generator.CreateLXCData(c.GlobalString("cache-dir"), file.Path, img)
-			if err != nil {
-				continue
-			}
-		}
+func (c *cmdGlobal) preRunPack(cmd *cobra.Command, args []string) error {
+	var err error
 
-		err := img.Build()
-		if err != nil {
-			return fmt.Errorf("Failed to create LXC image: %s", err)
-		}
+	c.sourceDir = args[1]
 
-		// Clean up the chroot by restoring the orginal files.
-		err = generators.RestoreFiles(c.GlobalString("cache-dir"))
-		if err != nil {
-			return fmt.Errorf("Failed to restore cached files: %s", err)
-		}
+	c.targetDir = "."
+	if len(args) == 3 {
+		c.targetDir = args[2]
 	}
 
-	if c.GlobalBool("lxd") {
-		img := image.NewLXDImage(c.GlobalString("cache-dir"), def.Image)
-
-		for _, file := range def.Files {
-			if len(file.Releases) > 0 && !lxd.StringInSlice(def.Image.Release, file.Releases) {
-				continue
-			}
-
-			generator := generators.Get(file.Generator)
-			if generator == nil {
-				return fmt.Errorf("Unknown generator '%s'", file.Generator)
-			}
-
-			err := generator.CreateLXDData(c.GlobalString("cache-dir"), file.Path, img)
-			if err != nil {
-				return fmt.Errorf("Failed to create LXD data: %s", err)
-			}
-		}
+	// Get the image definition
+	c.definition, err = getDefinition(args[0])
+	if err != nil {
+		return err
+	}
 
-		err := img.Build(c.GlobalBool("unified"))
-		if err != nil {
-			return fmt.Errorf("Failed to create LXD image: %s", err)
-		}
+	// Create cache directory
+	err = os.MkdirAll(c.flagCacheDir, 0755)
+	if err != nil {
+		return err
 	}
 
-	if c.GlobalBool("plain") {
-		err := shared.Pack("plain.tar.xz", filepath.Join(c.GlobalString("cache-dir"), "rootfs"), ".")
-		if err != nil {
-			return fmt.Errorf("Failed to create plain rootfs: %s", err)
-		}
+	return nil
+}
+
+func (c *cmdGlobal) postRun(cmd *cobra.Command, args []string) error {
+	// Clean up cache directory if needed. Do not clean up if the build-dir
+	// sub-command is run since the directory is needed for further actions.
+	if c.flagCleanup && cmd.CalledAs() != "build-dir" {
+		return os.RemoveAll(c.flagCacheDir)
 	}
 
 	return nil
@@ -292,3 +261,29 @@ func getDefinition(fname string) (*shared.Definition, error) {
 
 	return &def, nil
 }
+
+func getMappedArchitecture(def *shared.Definition) (string, error) {
+	var arch string
+
+	if def.Mappings.ArchitectureMap != "" {
+		// Translate the architecture using the requested map
+		var err error
+		arch, err = shared.GetArch(def.Mappings.ArchitectureMap, def.Image.Arch)
+		if err != nil {
+			return "", fmt.Errorf("Failed to translate the architecture name: %s", err)
+		}
+	} else if len(def.Mappings.Architectures) > 0 {
+		// Translate the architecture using a user specified mapping
+		var ok bool
+		arch, ok = def.Mappings.Architectures[def.Image.Arch]
+		if !ok {
+			// If no mapping exists, it means it doesn't need translating
+			arch = def.Image.Arch
+		}
+	} else {
+		// No map or mappings provided, just go with it as it is
+		arch = def.Image.Arch
+	}
+
+	return arch, nil
+}
diff --git a/distrobuilder/main_build-dir.go b/distrobuilder/main_build-dir.go
new file mode 100644
index 0000000..416914d
--- /dev/null
+++ b/distrobuilder/main_build-dir.go
@@ -0,0 +1,20 @@
+package main
+
+import "github.com/spf13/cobra"
+
+type cmdBuildDir struct {
+	cmd    *cobra.Command
+	global *cmdGlobal
+}
+
+func (c *cmdBuildDir) command() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "build-dir <filename|-> <target dir>",
+		Short: "Build plain rootfs",
+		Args:  cobra.ExactArgs(2),
+		RunE:  c.global.preRunBuild,
+	}
+
+	c.cmd = cmd
+	return cmd
+}
diff --git a/distrobuilder/main_lxc.go b/distrobuilder/main_lxc.go
new file mode 100644
index 0000000..40a1cd9
--- /dev/null
+++ b/distrobuilder/main_lxc.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/lxc/distrobuilder/generators"
+	"github.com/lxc/distrobuilder/image"
+	lxd "github.com/lxc/lxd/shared"
+	"github.com/spf13/cobra"
+)
+
+type cmdLXC struct {
+	cmdBuild *cobra.Command
+	cmdPack  *cobra.Command
+	global   *cmdGlobal
+}
+
+type cmdBuildLXC struct {
+	cmd    *cobra.Command
+	global *cmdGlobal
+}
+
+func (c *cmdLXC) commandBuild() *cobra.Command {
+	c.cmdBuild = &cobra.Command{
+		Use:     "build-lxc <filename|-> [target dir]",
+		Short:   "Build LXC image from scratch",
+		Args:    cobra.RangeArgs(1, 2),
+		PreRunE: c.global.preRunBuild,
+		RunE:    c.run,
+	}
+	return c.cmdBuild
+}
+
+func (c *cmdLXC) commandPack() *cobra.Command {
+	c.cmdPack = &cobra.Command{
+		Use:     "pack-lxc <filename|-> <source dir> [target dir]",
+		Short:   "Create LXC image from existing rootfs",
+		Args:    cobra.RangeArgs(2, 3),
+		PreRunE: c.global.preRunPack,
+		RunE:    c.run,
+	}
+	return c.cmdPack
+}
+
+func (c *cmdLXC) run(cmd *cobra.Command, args []string) error {
+	img := image.NewLXCImage(c.global.sourceDir, c.global.targetDir,
+		c.global.flagCacheDir, c.global.definition.Image,
+		c.global.definition.Targets.LXC)
+
+	for _, file := range c.global.definition.Files {
+		generator := generators.Get(file.Generator)
+		if generator == nil {
+			return fmt.Errorf("Unknown generator '%s'", file.Generator)
+		}
+
+		if len(file.Releases) > 0 && !lxd.StringInSlice(
+			c.global.definition.Image.Release, file.Releases) {
+			continue
+		}
+
+		err := generator.CreateLXCData(c.global.flagCacheDir, file.Path, img)
+		if err != nil {
+			continue
+		}
+	}
+
+	err := img.Build()
+	if err != nil {
+		return fmt.Errorf("Failed to create LXC image: %s", err)
+	}
+
+	// Clean up the chroot by restoring the orginal files.
+	err = generators.RestoreFiles(c.global.flagCacheDir)
+	if err != nil {
+		return fmt.Errorf("Failed to restore cached files: %s", err)
+	}
+
+	return nil
+}
diff --git a/distrobuilder/main_lxd.go b/distrobuilder/main_lxd.go
new file mode 100644
index 0000000..e357877
--- /dev/null
+++ b/distrobuilder/main_lxd.go
@@ -0,0 +1,91 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/lxc/distrobuilder/generators"
+	"github.com/lxc/distrobuilder/image"
+	lxd "github.com/lxc/lxd/shared"
+	"github.com/spf13/cobra"
+)
+
+type cmdLXD struct {
+	cmdBuild *cobra.Command
+	cmdPack  *cobra.Command
+	global   *cmdGlobal
+
+	flagType        string
+	flagCompression string
+}
+
+func (c *cmdLXD) commandBuild() *cobra.Command {
+	c.cmdBuild = &cobra.Command{
+		Use:   "build-lxd <filename|-> [target dir] [--type=TYPE] [--compression=COMPRESSION]",
+		Short: "Build LXD image from scratch",
+		Args:  cobra.RangeArgs(1, 2),
+		PreRunE: func(cmd *cobra.Command, args []string) error {
+			if !lxd.StringInSlice(c.flagType, []string{"split", "unified"}) {
+				return errors.New("--type needs to be one of ['split', 'unified']")
+			}
+
+			return c.global.preRunBuild(cmd, args)
+		},
+		RunE: c.run,
+	}
+
+	c.cmdBuild.Flags().StringVar(&c.flagType, "type", "split", "Type of tarball to create")
+	c.cmdBuild.Flags().StringVar(&c.flagCompression, "compression", "xz", "Type of compression to use")
+
+	return c.cmdBuild
+}
+
+func (c *cmdLXD) commandPack() *cobra.Command {
+	c.cmdPack = &cobra.Command{
+		Use:   "pack-lxd <filename|-> <source dir> [target dir] [--type=TYPE] [--compression=COMPRESSION]",
+		Short: "Create LXD image from existing rootfs",
+		Args:  cobra.RangeArgs(2, 3),
+		PreRunE: func(cmd *cobra.Command, args []string) error {
+			if !lxd.StringInSlice(c.flagType, []string{"split", "unified"}) {
+				return errors.New("--type needs to be one of ['split', 'unified']")
+			}
+
+			return c.global.preRunPack(cmd, args)
+		},
+		RunE: c.run,
+	}
+
+	c.cmdPack.Flags().StringVar(&c.flagType, "type", "split", "Type of tarball to create")
+	c.cmdPack.Flags().StringVar(&c.flagCompression, "compression", "xz", "Type of compression to use")
+
+	return c.cmdPack
+}
+
+func (c *cmdLXD) run(cmd *cobra.Command, args []string) error {
+	img := image.NewLXDImage(c.global.sourceDir, c.global.targetDir,
+		c.global.flagCacheDir, c.global.definition.Image)
+
+	for _, file := range c.global.definition.Files {
+		if len(file.Releases) > 0 && !lxd.StringInSlice(c.global.definition.Image.Release,
+			file.Releases) {
+			continue
+		}
+
+		generator := generators.Get(file.Generator)
+		if generator == nil {
+			return fmt.Errorf("Unknown generator '%s'", file.Generator)
+		}
+
+		err := generator.CreateLXDData(c.global.sourceDir, file.Path, img)
+		if err != nil {
+			return fmt.Errorf("Failed to create LXD data: %s", err)
+		}
+	}
+
+	err := img.Build(c.flagType == "unified")
+	if err != nil {
+		return fmt.Errorf("Failed to create LXD image: %s", err)
+	}
+
+	return nil
+}
diff --git a/generators/hostname_test.go b/generators/hostname_test.go
index be7f9fe..b2c00ed 100644
--- a/generators/hostname_test.go
+++ b/generators/hostname_test.go
@@ -25,7 +25,7 @@ func TestHostnameGeneratorCreateLXCData(t *testing.T) {
 		Release:      "artful",
 	}
 
-	image := image.NewLXCImage(cacheDir, definition, shared.DefinitionTargetLXC{})
+	image := image.NewLXCImage(cacheDir, "", cacheDir, definition, shared.DefinitionTargetLXC{})
 
 	err := os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755)
 	if err != nil {
@@ -66,7 +66,7 @@ func TestHostnameGeneratorCreateLXDData(t *testing.T) {
 		Release:      "artful",
 	}
 
-	image := image.NewLXDImage(cacheDir, definition)
+	image := image.NewLXDImage(cacheDir, "", cacheDir, definition)
 
 	err := os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755)
 	if err != nil {
diff --git a/generators/hosts_test.go b/generators/hosts_test.go
index 4571276..f29da90 100644
--- a/generators/hosts_test.go
+++ b/generators/hosts_test.go
@@ -25,7 +25,7 @@ func TestHostsGeneratorCreateLXCData(t *testing.T) {
 		Release:      "artful",
 	}
 
-	image := image.NewLXCImage(cacheDir, definition, shared.DefinitionTargetLXC{})
+	image := image.NewLXCImage(cacheDir, "", cacheDir, definition, shared.DefinitionTargetLXC{})
 
 	err := os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755)
 	if err != nil {
@@ -70,7 +70,7 @@ func TestHostsGeneratorCreateLXDData(t *testing.T) {
 		Release:      "artful",
 	}
 
-	image := image.NewLXDImage(cacheDir, definition)
+	image := image.NewLXDImage(cacheDir, "", cacheDir, definition)
 
 	err := os.MkdirAll(filepath.Join(cacheDir, "rootfs", "etc"), 0755)
 	if err != nil {
diff --git a/image/lxc.go b/image/lxc.go
index 64d0ceb..e3e1513 100644
--- a/image/lxc.go
+++ b/image/lxc.go
@@ -15,15 +15,19 @@ import (
 
 // LXCImage represents a LXC image.
 type LXCImage struct {
+	sourceDir  string
+	targetDir  string
 	cacheDir   string
 	definition shared.DefinitionImage
 	target     shared.DefinitionTargetLXC
 }
 
 // NewLXCImage returns a LXCImage.
-func NewLXCImage(cacheDir string, definition shared.DefinitionImage,
+func NewLXCImage(sourceDir, targetDir, cacheDir string, definition shared.DefinitionImage,
 	target shared.DefinitionTargetLXC) *LXCImage {
 	img := LXCImage{
+		sourceDir,
+		targetDir,
 		cacheDir,
 		definition,
 		target,
@@ -69,7 +73,7 @@ func (l *LXCImage) Build() error {
 		return err
 	}
 
-	err = shared.Pack("rootfs.tar.xz", l.cacheDir, "rootfs")
+	err = shared.Pack(filepath.Join(l.targetDir, "rootfs.tar.xz"), l.sourceDir, "rootfs")
 	if err != nil {
 		return err
 	}
@@ -103,8 +107,8 @@ func (l *LXCImage) createMetadata() error {
 
 	var excludesUser string
 
-	if lxd.PathExists(filepath.Join(l.cacheDir, "rootfs", "dev")) {
-		err := filepath.Walk(filepath.Join(l.cacheDir, "rootfs", "dev"),
+	if lxd.PathExists(filepath.Join(l.sourceDir, "rootfs", "dev")) {
+		err := filepath.Walk(filepath.Join(l.sourceDir, "rootfs", "dev"),
 			func(path string, info os.FileInfo, err error) error {
 				if err != nil {
 					return err
@@ -112,7 +116,7 @@ func (l *LXCImage) createMetadata() error {
 
 				if info.Mode()&os.ModeDevice != 0 {
 					excludesUser += fmt.Sprintf("%s\n",
-						strings.TrimPrefix(path, filepath.Join(l.cacheDir, "rootfs")))
+						strings.TrimPrefix(path, filepath.Join(l.sourceDir, "rootfs")))
 				}
 
 				return nil
@@ -138,7 +142,8 @@ func (l *LXCImage) packMetadata() error {
 		files = append(files, "templates")
 	}
 
-	err := shared.Pack("meta.tar.xz", filepath.Join(l.cacheDir, "metadata"), files...)
+	err := shared.Pack(filepath.Join(l.targetDir, "meta.tar.xz"),
+		filepath.Join(l.cacheDir, "metadata"), files...)
 	if err != nil {
 		return fmt.Errorf("Failed to create metadata: %s", err)
 	}
diff --git a/image/lxc_test.go b/image/lxc_test.go
index 803d73b..9271176 100644
--- a/image/lxc_test.go
+++ b/image/lxc_test.go
@@ -38,7 +38,7 @@ func lxcCacheDir() string {
 }
 
 func setupLXC() *LXCImage {
-	return NewLXCImage(lxcCacheDir(), lxcImageDef, lxcTarget)
+	return NewLXCImage(lxcCacheDir(), "", lxcCacheDir(), lxcImageDef, lxcTarget)
 }
 
 func teardownLXC() {
@@ -46,7 +46,7 @@ func teardownLXC() {
 }
 
 func TestNewLXCImage(t *testing.T) {
-	image := NewLXCImage(lxcCacheDir(), lxcImageDef, lxcTarget)
+	image := NewLXCImage(lxcCacheDir(), "", lxcCacheDir(), lxcImageDef, lxcTarget)
 	defer teardownLXC()
 
 	if image.cacheDir != lxcCacheDir() {
diff --git a/image/lxd.go b/image/lxd.go
index f04b6c6..8449e7e 100644
--- a/image/lxd.go
+++ b/image/lxd.go
@@ -15,6 +15,8 @@ import (
 
 // A LXDImage represents a LXD image.
 type LXDImage struct {
+	sourceDir    string
+	targetDir    string
 	cacheDir     string
 	creationDate time.Time
 	Metadata     api.ImageMetadata
@@ -22,8 +24,11 @@ type LXDImage struct {
 }
 
 // NewLXDImage returns a LXDImage.
-func NewLXDImage(cacheDir string, imageDef shared.DefinitionImage) *LXDImage {
+func NewLXDImage(sourceDir, targetDir, cacheDir string,
+	imageDef shared.DefinitionImage) *LXDImage {
 	return &LXDImage{
+		sourceDir,
+		targetDir,
 		cacheDir,
 		time.Now(),
 		api.ImageMetadata{
@@ -41,7 +46,7 @@ func (l *LXDImage) Build(unified bool) error {
 		return nil
 	}
 
-	file, err := os.Create(filepath.Join(l.cacheDir, "metadata.yaml"))
+	file, err := os.Create(filepath.Join(l.sourceDir, "metadata.yaml"))
 	if err != nil {
 		return err
 	}
@@ -60,7 +65,7 @@ func (l *LXDImage) Build(unified bool) error {
 	paths := []string{"metadata.yaml"}
 
 	// Only include templates directory in the tarball if it's present.
-	info, err := os.Stat(filepath.Join(l.cacheDir, "templates"))
+	info, err := os.Stat(filepath.Join(l.sourceDir, "templates"))
 	if err == nil && info.IsDir() {
 		paths = append(paths, "templates")
 	}
@@ -81,20 +86,21 @@ func (l *LXDImage) Build(unified bool) error {
 		}
 
 		paths = append(paths, "rootfs")
-		err = shared.Pack(fmt.Sprintf("%s.tar.xz", fname), l.cacheDir, paths...)
+		err = shared.Pack(filepath.Join(l.targetDir, fmt.Sprintf("%s.tar.xz", fname)),
+			l.sourceDir, paths...)
 		if err != nil {
 			return err
 		}
 	} else {
 		// Create rootfs as squashfs.
-		err = shared.RunCommand("mksquashfs", filepath.Join(l.cacheDir, "rootfs"),
-			"rootfs.squashfs", "-noappend")
+		err = shared.RunCommand("mksquashfs", filepath.Join(l.sourceDir, "rootfs"),
+			filepath.Join(l.targetDir, "rootfs.squashfs"), "-noappend")
 		if err != nil {
 			return err
 		}
 
 		// Create metadata tarball.
-		err = shared.Pack("lxd.tar.xz", l.cacheDir, paths...)
+		err = shared.Pack(filepath.Join(l.targetDir, "lxd.tar.xz"), l.sourceDir, paths...)
 		if err != nil {
 			return err
 		}
diff --git a/image/lxd_test.go b/image/lxd_test.go
index a855de1..a10237b 100644
--- a/image/lxd_test.go
+++ b/image/lxd_test.go
@@ -36,7 +36,7 @@ func setupLXD(t *testing.T) *LXDImage {
 		t.Fatalf("Failed to create templates directory: %s", err)
 	}
 
-	image := NewLXDImage(cacheDir, lxdImageDef)
+	image := NewLXDImage(cacheDir, "", cacheDir, lxdImageDef)
 
 	// Override creation date
 	image.creationDate = time.Date(2006, 1, 2, 3, 4, 5, 0, time.UTC)


More information about the lxc-devel mailing list