The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/distrobuilder/pull/16
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === TBD: - [ ] Tests - [ ] Template support for LXC metadata (#14)
From b48e4ba2dcc16e441bf52df048ba6693aadca295 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <[email protected]> Date: Mon, 12 Feb 2018 16:19:29 +0100 Subject: [PATCH 1/6] shared: Add Pack function Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> Date: Fri, 9 Feb 2018 10:17:06 +0100 Subject: [PATCH 2/6] image: Create LXD images Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> Date: Mon, 12 Feb 2018 21:14:49 +0100 Subject: [PATCH 3/6] image: Create LXC images Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> Date: Wed, 21 Feb 2018 11:29:18 +0100 Subject: [PATCH 4/6] generators: Add generators Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> Date: Wed, 21 Feb 2018 15:31:02 +0100 Subject: [PATCH 5/6] chroot: Fix unmounting Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> Date: Wed, 21 Feb 2018 18:29:58 +0100 Subject: [PATCH 6/6] main: Build images Signed-off-by: Thomas Hipp <[email protected]> --- 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 }
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
