[lxc-devel] [distrobuilder/master] [WIP] build images

monstermunchkin on Github lxc-bot at linuxcontainers.org
Wed Feb 21 17:35:47 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 373 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180221/a4355ef0/attachment.bin>
-------------- next part --------------
From b48e4ba2dcc16e441bf52df048ba6693aadca295 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Mon, 12 Feb 2018 16:19:29 +0100
Subject: [PATCH 1/6] shared: Add Pack function

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 shared/util.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/shared/util.go b/shared/util.go
index ddac11c..976e82a 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -79,3 +79,8 @@ func VerifyFile(signedFile, signatureFile string, keys []string, keyserver strin
 
 	return true, nil
 }
+
+// Pack creates an xz-compressed tarball.
+func Pack(filename, path string, args ...string) error {
+	return RunCommand("tar", append([]string{"-cJf", filename, "-C", path}, args...)...)
+}

From 2be9e89612eb0ac7efc0deeb9f941f01b31e107b Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 9 Feb 2018 10:17:06 +0100
Subject: [PATCH 2/6] image: Create LXD images

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 image/lxd.go | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 177 insertions(+)
 create mode 100644 image/lxd.go

diff --git a/image/lxd.go b/image/lxd.go
new file mode 100644
index 0000000..88737e7
--- /dev/null
+++ b/image/lxd.go
@@ -0,0 +1,177 @@
+package image
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/lxc/distrobuilder/shared"
+	"github.com/lxc/lxd/shared/osarch"
+	pongo2 "gopkg.in/flosch/pongo2.v3"
+	yaml "gopkg.in/yaml.v2"
+)
+
+// A LXDMetadataTemplate represents template information.
+type LXDMetadataTemplate struct {
+	Template   string                       `yaml:"template"`
+	When       []string                     `yaml:"when"`
+	Trigger    string                       `yaml:"trigger,omitempty"`
+	Path       string                       `yaml:"path,omitempty"`
+	Container  map[string]string            `yaml:"container,omitempty"`
+	Config     map[string]string            `yaml:"config,omitempty"`
+	Devices    map[string]map[string]string `yaml:"devices,omitempty"`
+	Properties map[string]string            `yaml:"properties,omitempty"`
+	CreateOnly bool                         `yaml:"create_only,omitempty"`
+}
+
+// A LXDMetadataProperties represents properties of the LXD image.
+type LXDMetadataProperties struct {
+	Architecture string `yaml:"architecture"`
+	Description  string `yaml:"description"`
+	OS           string `yaml:"os"`
+	Release      string `yaml:"release"`
+	Variant      string `yaml:"variant,omitempty"`
+	Name         string `yaml:"name,omitempty"`
+}
+
+// A LXDMetadata represents meta information about the LXD image.
+type LXDMetadata struct {
+	Architecture string                         `yaml:"architecture"`
+	CreationDate int64                          `yaml:"creation_date"`
+	Properties   LXDMetadataProperties          `yaml:"properties,omitempty"`
+	Templates    map[string]LXDMetadataTemplate `yaml:"templates,omitempty"`
+}
+
+// A LXDImage represents a LXD image.
+type LXDImage struct {
+	cacheDir     string
+	creationDate time.Time
+	Metadata     LXDMetadata
+	definition   shared.DefinitionImage
+}
+
+// NewLXDImage returns a LXDImage.
+func NewLXDImage(cacheDir string, imageDef shared.DefinitionImage) *LXDImage {
+	return &LXDImage{
+		cacheDir,
+		time.Now(),
+		LXDMetadata{
+			Templates: make(map[string]LXDMetadataTemplate),
+		},
+		imageDef,
+	}
+}
+
+// Build creates a LXD image.
+func (l *LXDImage) Build(unified bool) error {
+	var err error
+
+	err = l.createMetadata()
+	if err != nil {
+		return nil
+	}
+
+	file, err := os.Create(filepath.Join(l.cacheDir, "metadata.yaml"))
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	data, err := yaml.Marshal(l.Metadata)
+	if err != nil {
+		return err
+	}
+
+	file.Write(data)
+
+	if unified {
+		var fname string
+		paths := []string{"rootfs", "templates", "metadata.yaml"}
+
+		if l.definition.Name != "" {
+			fname, _ = l.renderTemplate(l.definition.Name)
+		} else {
+			fname = "lxd"
+		}
+
+		err = shared.Pack(fmt.Sprintf("%s.tar.xz", fname), l.cacheDir, paths...)
+		if err != nil {
+			return err
+		}
+	} else {
+		err = shared.RunCommand("mksquashfs", filepath.Join(l.cacheDir, "rootfs"),
+			"rootfs.squashfs", "-noappend")
+		if err != nil {
+			return err
+		}
+
+		err = shared.Pack("lxd.tar.xz", l.cacheDir, "templates", "metadata.yaml")
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (l *LXDImage) createMetadata() error {
+	var err error
+
+	ID, err := osarch.ArchitectureId(l.definition.Arch)
+	if err != nil {
+		return err
+	}
+
+	arch, err := osarch.ArchitectureName(ID)
+	if err != nil {
+		return err
+	}
+
+	l.Metadata.Architecture = arch
+	l.Metadata.CreationDate = l.creationDate.Unix()
+	l.Metadata.Properties = LXDMetadataProperties{
+		Architecture: arch,
+		OS:           l.definition.Distribution,
+		Release:      l.definition.Release,
+	}
+
+	l.Metadata.Properties.Description, err = l.renderTemplate(l.definition.Description)
+	if err != err {
+		return nil
+	}
+
+	l.Metadata.Properties.Name, err = l.renderTemplate(l.definition.Name)
+	if err != nil {
+		return err
+	}
+
+	return err
+}
+
+func (l *LXDImage) renderTemplate(template string) (string, error) {
+	var (
+		err error
+		ret string
+	)
+
+	ctx := pongo2.Context{
+		"arch":          l.definition.Arch,
+		"os":            l.definition.Distribution,
+		"release":       l.definition.Release,
+		"variant":       l.definition.Variant,
+		"creation_date": l.creationDate.Format("20060201_1504"),
+	}
+
+	tpl, err := pongo2.FromString(template)
+	if err != nil {
+		return ret, err
+	}
+
+	ret, err = tpl.Execute(ctx)
+	if err != nil {
+		return ret, err
+	}
+
+	return ret, err
+}

From f11d041833556132818b202875dda0d9fcbd6459 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Mon, 12 Feb 2018 21:14:49 +0100
Subject: [PATCH 3/6] image: Create LXC images

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 image/lxc.go | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)
 create mode 100644 image/lxc.go

diff --git a/image/lxc.go b/image/lxc.go
new file mode 100644
index 0000000..9a109c4
--- /dev/null
+++ b/image/lxc.go
@@ -0,0 +1,138 @@
+package image
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/lxc/distrobuilder/shared"
+)
+
+// LXCImage represents a LXC image.
+type LXCImage struct {
+	cacheDir   string
+	definition shared.DefinitionImage
+	target     shared.DefinitionTargetLXC
+}
+
+// NewLXCImage returns a LXCImage.
+func NewLXCImage(cacheDir string, definition shared.DefinitionImage,
+	target shared.DefinitionTargetLXC) *LXCImage {
+	img := LXCImage{
+		cacheDir,
+		definition,
+		target,
+	}
+
+	// create metadata directory
+	err := os.MkdirAll(filepath.Join(cacheDir, "metadata"), 0755)
+	if err != nil {
+		return nil
+	}
+
+	return &img
+}
+
+// AddTemplate adds an entry to the templates file.
+func (l *LXCImage) AddTemplate(path string) error {
+	metaDir := filepath.Join(l.cacheDir, "metadata")
+
+	file, err := os.OpenFile(filepath.Join(metaDir, "templates"),
+		os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	file.WriteString(fmt.Sprintf("%v\n", path))
+
+	return nil
+}
+
+// Build creates a LXC image.
+func (l *LXCImage) Build() error {
+	err := l.createMetadata()
+	if err != nil {
+		return err
+	}
+
+	err = l.packMetadata()
+	if err != nil {
+		return err
+	}
+
+	err = shared.Pack("rootfs.tar.xz", l.cacheDir, "rootfs")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (l *LXCImage) createMetadata() error {
+	metaDir := filepath.Join(l.cacheDir, "metadata")
+
+	err := l.writeMetadata(filepath.Join(metaDir, "config"), l.target.Config)
+	if err != nil {
+		return fmt.Errorf("Error writing 'config': %s", err)
+	}
+
+	err = l.writeMetadata(filepath.Join(metaDir, "config-user"), l.target.ConfigUser)
+	if err != nil {
+		return fmt.Errorf("Error writing 'config-user': %s", err)
+	}
+
+	err = l.writeMetadata(filepath.Join(metaDir, "create-message"), l.target.CreateMessage)
+	if err != nil {
+		return fmt.Errorf("Error writing 'create-message': %s", err)
+	}
+
+	err = l.writeMetadata(filepath.Join(metaDir, "expiry"), string(time.Now().Unix()))
+	if err != nil {
+		return fmt.Errorf("Error writing 'expiry': %s", err)
+	}
+	var excludesUser string
+
+	filepath.Walk(filepath.Join(l.cacheDir, "rootfs", "dev"),
+		func(path string, info os.FileInfo, err error) error {
+			if info.Mode()&os.ModeDevice != 0 {
+				excludesUser += fmt.Sprintf("%s\n",
+					strings.TrimPrefix(path, filepath.Join(l.cacheDir, "rootfs")))
+			}
+
+			return nil
+		})
+
+	err = l.writeMetadata(filepath.Join(metaDir, "excludes-user"), excludesUser)
+	if err != nil {
+		return fmt.Errorf("Error writing 'excludes-user': %s", err)
+	}
+
+	return nil
+}
+
+func (l *LXCImage) packMetadata() error {
+	err := shared.Pack("meta.tar.xz", filepath.Join(l.cacheDir, "metadata"), "config",
+		"config-user", "create-message", "expiry", "templates", "excludes-user")
+	if err != nil {
+		return fmt.Errorf("Failed to create metadata: %s", err)
+	}
+
+	return nil
+}
+func (l *LXCImage) writeMetadata(filename, content string) error {
+	file, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	_, err = file.WriteString(content)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

From 283ba50ae57f7ab091fd588ed787cf2c9b428ba6 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 21 Feb 2018 11:29:18 +0100
Subject: [PATCH 4/6] generators: Add generators

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 generators/generators.go | 57 +++++++++++++++++++++++++++++++++++++++
 generators/hostname.go   | 60 +++++++++++++++++++++++++++++++++++++++++
 generators/hosts.go      | 69 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 186 insertions(+)
 create mode 100644 generators/generators.go
 create mode 100644 generators/hostname.go
 create mode 100644 generators/hosts.go

diff --git a/generators/generators.go b/generators/generators.go
new file mode 100644
index 0000000..cbbcfe7
--- /dev/null
+++ b/generators/generators.go
@@ -0,0 +1,57 @@
+package generators
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+
+	p "path"
+
+	"github.com/lxc/distrobuilder/image"
+)
+
+// Generator interface.
+type Generator interface {
+	CreateLXCData(string, string, *image.LXCImage) error
+	CreateLXDData(string, string, *image.LXDImage) error
+}
+
+// Get returns a Generator.
+func Get(generator string) Generator {
+	switch generator {
+	case "hostname":
+		return HostnameGenerator{}
+	case "hosts":
+		return HostsGenerator{}
+	}
+
+	return nil
+}
+
+// StoreFile caches a file which can be restored with the RestoreFiles function.
+func StoreFile(cacheDir, path string) error {
+	// create temporary directory containing old files
+	err := os.MkdirAll(filepath.Join(cacheDir, "tmp", p.Dir(path)), 0755)
+	if err != nil {
+		return err
+	}
+
+	return os.Rename(filepath.Join(cacheDir, "rootfs", path),
+		filepath.Join(cacheDir, "tmp", path))
+}
+
+// RestoreFiles restores original files which were cached by StoreFile.
+func RestoreFiles(cacheDir string) error {
+	f := func(path string, info os.FileInfo, err error) error {
+		if info.IsDir() {
+			// We don't care about directories. They should be present so there's
+			// no need to create them.
+			return nil
+		}
+
+		return os.Rename(path,
+			filepath.Join(cacheDir, "rootfs", strings.TrimPrefix(path, cacheDir)))
+	}
+
+	return filepath.Walk(filepath.Join(cacheDir, "tmp"), f)
+}
diff --git a/generators/hostname.go b/generators/hostname.go
new file mode 100644
index 0000000..91f9118
--- /dev/null
+++ b/generators/hostname.go
@@ -0,0 +1,60 @@
+package generators
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/lxc/distrobuilder/image"
+)
+
+// HostnameGenerator represents the Hostname generator.
+type HostnameGenerator struct{}
+
+// CreateLXCData creates a hostname template.
+func (g HostnameGenerator) CreateLXCData(cacheDir, path string, img *image.LXCImage) error {
+	rootfs := filepath.Join(cacheDir, "rootfs")
+
+	// store original file
+	err := StoreFile(cacheDir, path)
+	if err != nil {
+		return err
+	}
+
+	file, err := os.Create(filepath.Join(rootfs, path))
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	file.WriteString("LXC_NAME\n")
+
+	return img.AddTemplate(path)
+}
+
+// CreateLXDData creates a hostname template.
+func (g HostnameGenerator) CreateLXDData(cacheDir, path string, img *image.LXDImage) error {
+	templateDir := filepath.Join(cacheDir, "templates")
+
+	err := os.MkdirAll(templateDir, 0755)
+	if err != nil {
+		return err
+	}
+
+	file, err := os.Create(filepath.Join(templateDir, "hostname.tpl"))
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	file.WriteString("{{ container.name }}\n")
+
+	img.Metadata.Templates[path] = image.LXDMetadataTemplate{
+		Template: "hostname.tpl",
+		When: []string{
+			"create",
+			"copy",
+		},
+	}
+
+	return err
+}
diff --git a/generators/hosts.go b/generators/hosts.go
new file mode 100644
index 0000000..72f0c23
--- /dev/null
+++ b/generators/hosts.go
@@ -0,0 +1,69 @@
+package generators
+
+import (
+	"io"
+	"os"
+	"path/filepath"
+
+	"github.com/lxc/distrobuilder/image"
+)
+
+// HostsGenerator represents the hosts generator.
+type HostsGenerator struct{}
+
+// CreateLXCData creates a LXC specific entry in the hosts file.
+func (g HostsGenerator) CreateLXCData(cacheDir, path string, img *image.LXCImage) error {
+	rootfs := filepath.Join(cacheDir, "rootfs")
+
+	// store original file
+	err := StoreFile(cacheDir, path)
+	if err != nil {
+		return err
+	}
+
+	file, err := os.OpenFile(filepath.Join(rootfs, path),
+		os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	file.WriteString("127.0.0.1\tLXC_NAME\n")
+
+	return img.AddTemplate(path)
+}
+
+// CreateLXDData creates a hosts template.
+func (g HostsGenerator) CreateLXDData(cacheDir, path string, img *image.LXDImage) error {
+	templateDir := filepath.Join(cacheDir, "templates")
+
+	err := os.MkdirAll(templateDir, 0755)
+	if err != nil {
+		return err
+	}
+
+	file, err := os.Create(filepath.Join(templateDir, "hosts.tpl"))
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	hostsFile, err := os.Open(filepath.Join(cacheDir, "rootfs", path))
+	if err != nil {
+		return err
+	}
+	defer hostsFile.Close()
+
+	io.Copy(file, hostsFile)
+	file.WriteString("127.0.0.1\t{{ container.name }}\n")
+
+	img.Metadata.Templates[path] = image.LXDMetadataTemplate{
+		Template: "hostname.tpl",
+		When: []string{
+			"create",
+			"copy",
+		},
+	}
+
+	return err
+}

From 1d01544098d06528cc58b97fd5d2be02a84a9142 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 21 Feb 2018 15:31:02 +0100
Subject: [PATCH 5/6] chroot: Fix unmounting

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 distrobuilder/chroot.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/distrobuilder/chroot.go b/distrobuilder/chroot.go
index f055d73..780d42e 100644
--- a/distrobuilder/chroot.go
+++ b/distrobuilder/chroot.go
@@ -125,6 +125,9 @@ func setupChroot(rootfs string) (func() error, error) {
 
 	err = setupMounts(rootfs, mounts)
 	if err != nil {
+		for _, mount := range mounts {
+			syscall.Unmount(filepath.Join(rootfs, mount.target), syscall.MNT_DETACH)
+		}
 		return nil, fmt.Errorf("Failed to mount filesystems: %v", err)
 	}
 
@@ -170,7 +173,9 @@ func setupChroot(rootfs string) (func() error, error) {
 		// This will kill all processes in the chroot and allow to cleanly
 		// unmount everything.
 		killChrootProcesses(rootfs)
-		syscall.Unmount(rootfs, syscall.MNT_DETACH)
+		for _, mount := range mounts {
+			syscall.Unmount(filepath.Join(rootfs, mount.target), syscall.MNT_DETACH)
+		}
 
 		return nil
 	}, nil

From 684e34e82052f8dd34830ba5166e6a999ca88a20 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 21 Feb 2018 18:29:58 +0100
Subject: [PATCH 6/6] main: Build images

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 distrobuilder/main.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/distrobuilder/main.go b/distrobuilder/main.go
index 48e90ec..a38f232 100644
--- a/distrobuilder/main.go
+++ b/distrobuilder/main.go
@@ -53,9 +53,12 @@ import (
 	"os"
 	"path/filepath"
 
+	"github.com/lxc/distrobuilder/generators"
+	"github.com/lxc/distrobuilder/image"
 	"github.com/lxc/distrobuilder/shared"
 	"github.com/lxc/distrobuilder/sources"
 
+	lxd "github.com/lxc/lxd/shared"
 	cli "gopkg.in/urfave/cli.v1"
 	yaml "gopkg.in/yaml.v2"
 )
@@ -190,6 +193,54 @@ 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 {
+				continue
+			}
+
+			if len(file.Releases) > 0 && !lxd.StringInSlice(def.Image.Release, file.Releases) {
+				continue
+			}
+
+			err := generator.CreateLXCData(c.GlobalString("cache-dir"), file.Path, img)
+			if err != nil {
+				continue
+			}
+		}
+
+		img.Build()
+
+		// Clean up the chroot by restoring the orginal files.
+		generators.RestoreFiles(c.GlobalString("cache-dir"))
+	}
+
+	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 {
+				continue
+			}
+
+			generator.CreateLXDData(c.GlobalString("cache-dir"), file.Path, img)
+		}
+
+		img.Build(c.GlobalBool("unified"))
+	}
+
+	if c.GlobalBool("plain") {
+		shared.Pack("plain.tar.xz", filepath.Join(c.GlobalString("cache-dir"), "rootfs"), ".")
+	}
+
 	return nil
 }
 


More information about the lxc-devel mailing list