[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