[lxc-devel] [distrobuilder/master] Support Ubuntu Core

monstermunchkin on Github lxc-bot at linuxcontainers.org
Mon Jun 24 14:25:54 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 385 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190624/d85bd5c4/attachment.bin>
-------------- next part --------------
From 5444d51af33c4488d8c21ac2d5303df928a93a0e Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 21 Jun 2019 20:16:16 +0200
Subject: [PATCH 1/2] sources: Add Ubuntu Core support

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 sources/ubuntu-http.go | 353 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 311 insertions(+), 42 deletions(-)

diff --git a/sources/ubuntu-http.go b/sources/ubuntu-http.go
index 9d81316..58bc1ef 100644
--- a/sources/ubuntu-http.go
+++ b/sources/ubuntu-http.go
@@ -4,16 +4,17 @@ import (
 	"crypto/sha256"
 	"errors"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"net/http"
 	"net/url"
 	"os"
-	"path"
 	"path/filepath"
 	"regexp"
 	"strings"
 
 	lxd "github.com/lxc/lxd/shared"
+	"golang.org/x/sys/unix"
 
 	"github.com/lxc/distrobuilder/shared"
 )
@@ -21,6 +22,7 @@ import (
 // UbuntuHTTP represents the Ubuntu HTTP downloader.
 type UbuntuHTTP struct {
 	fname string
+	fpath string
 }
 
 // NewUbuntuHTTP creates a new UbuntuHTTP instance.
@@ -30,19 +32,263 @@ func NewUbuntuHTTP() *UbuntuHTTP {
 
 // Run downloads the tarball and unpacks it.
 func (s *UbuntuHTTP) Run(definition shared.Definition, rootfsDir string) error {
-	baseURL := fmt.Sprintf("%s/releases/%s/release/", definition.Source.URL,
-		definition.Image.Release)
+	err := s.downloadImage(definition)
+	if err != nil {
+		return err
+	}
+
+	switch strings.ToLower(definition.Image.Variant) {
+	case "default":
+		return s.runDefaultVariant(definition, rootfsDir)
+	case "core":
+		return s.runCoreVariant(definition, rootfsDir)
+
+	}
+
+	return fmt.Errorf("Unknown Ubuntu variant: %s", definition.Image.Variant)
+}
+
+func (s *UbuntuHTTP) runDefaultVariant(definition shared.Definition, rootfsDir string) error {
+	err := s.unpack(filepath.Join(s.fpath, s.fname), rootfsDir)
+	if err != nil {
+		return err
+	}
+
+	if definition.Source.AptSources != "" {
+		// Run the template
+		out, err := shared.RenderTemplate(definition.Source.AptSources, definition)
+		if err != nil {
+			return err
+		}
+
+		// Append final new line if missing
+		if !strings.HasSuffix(out, "\n") {
+			out += "\n"
+		}
+
+		// Replace content of sources.list with the templated content.
+		file, err := os.Create(filepath.Join(rootfsDir, "etc", "apt", "sources.list"))
+		if err != nil {
+			return err
+		}
+		defer file.Close()
+
+		file.WriteString(out)
+	}
+
+	return nil
+}
+
+func (s *UbuntuHTTP) runCoreVariant(definition shared.Definition, rootfsDir string) error {
+	f := filepath.Join(s.fpath, s.fname)
+
+	if !lxd.PathExists(filepath.Join(s.fpath, strings.TrimSuffix(s.fname, ".xz"))) {
+		err := shared.RunCommand("unxz", "-k", filepath.Join(s.fpath, s.fname))
+		if err != nil {
+			return err
+		}
+	}
+
+	s.fname = strings.TrimSuffix(s.fname, ".xz")
+	f = filepath.Join(s.fpath, s.fname)
+
+	output, err := lxd.RunCommand("kpartx", "-a", "-v", f)
+	if err != nil {
+		return err
+	}
+	defer lxd.RunCommand("kpartx", "-d", f)
+
+	lines := strings.Split(output, "\n")
+
+	if len(lines) < 3 {
+		return fmt.Errorf("Failed to mount core image")
+	}
+
+	rootPartition := filepath.Join("/dev", "mapper", strings.Split(lines[2], " ")[2])
+
+	imageDir := filepath.Join(os.TempDir(), "distrobuilder", "image")
+	snapsDir := filepath.Join(os.TempDir(), "distrobuilder", "snaps")
+
+	os.MkdirAll(imageDir, 0755)
+	os.MkdirAll(snapsDir, 0755)
+	defer os.RemoveAll(filepath.Join(os.TempDir(), "distrobuilder"))
+
+	err = shared.RunCommand("mount", rootPartition, imageDir)
+	if err != nil {
+		return err
+	}
+	defer unix.Unmount(imageDir, 0)
+
+	err = shared.RunCommand("rsync", "-qa", filepath.Join(imageDir, "system-data"), rootfsDir)
+	if err != nil {
+		return err
+	}
+
+	// Create all the needed paths and links
+
+	dirs := []string{"bin", "dev", "initrd", "lib", "mnt", "proc", "root", "sbin", "sys"}
+
+	for _, d := range dirs {
+		err := os.Mkdir(filepath.Join(rootfsDir, d), 0755)
+		if err != nil {
+			return err
+		}
+	}
+
+	links := []struct {
+		target string
+		link   string
+	}{
+		{
+			"lib",
+			filepath.Join(rootfsDir, "lib64"),
+		},
+		{
+			"/bin/busybox",
+			filepath.Join(rootfsDir, "bin", "sh"),
+		},
+		{
+			"/bin/init",
+			filepath.Join(rootfsDir, "sbin", "init"),
+		},
+	}
+
+	for _, l := range links {
+		err = os.Symlink(l.target, l.link)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Copy system binaries
+
+	binaries := []struct {
+		source string
+		target string
+	}{
+		{
+			getFullPath("busybox"),
+			filepath.Join(rootfsDir, "bin", "busybox"),
+		},
+		{
+			getFullPath("cpio"),
+			filepath.Join(rootfsDir, "bin", "cpio"),
+		},
+		{
+			getFullPath("mount.fuse"),
+			filepath.Join(rootfsDir, "bin", "mount.fuse"),
+		},
+		{
+			getFullPath("pivot_root"),
+			filepath.Join(rootfsDir, "bin", "pivot_root"),
+		},
+		{
+			getFullPath("squashfuse"),
+			filepath.Join(rootfsDir, "bin", "squashfuse"),
+		},
+	}
+
+	for _, b := range binaries {
+		err := lxd.FileCopy(b.source, b.target)
+		if err != nil {
+			return err
+		}
+
+		err = os.Chmod(b.target, 0755)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Copy needed libraries
 
-	if strings.ContainsAny(definition.Image.Release, "0123456789") {
-		s.fname = fmt.Sprintf("ubuntu-base-%s-base-%s.tar.gz",
-			definition.Image.Release, definition.Image.ArchitectureMapped)
-	} else {
-		// if release is non-numerical, find the latest release
-		s.fname = getLatestRelease(definition.Source.URL,
-			definition.Image.Release, definition.Image.ArchitectureMapped)
-		if s.fname == "" {
-			return fmt.Errorf("Couldn't find latest release")
+	patterns := []string{
+		"/lib/*-linux-gnu/ld-linux*.so.2",
+		"/lib/*-linux-gnu/libc.so.6",
+		"/lib/*-linux-gnu/libdl.so.2",
+		"/lib/*-linux-gnu/libfuse.so.2",
+		"/usr/lib/*-linux-gnu/liblz4.so.1",
+		"/lib/*-linux-gnu/liblzma.so.5",
+		"/lib/*-linux-gnu/liblzo2.so.2",
+		"/lib/*-linux-gnu/libpthread.so.0",
+		"/lib/*-linux-gnu/libz.so.1",
+		"/lib/*-linux-gnu/libsquashfuse.so.0",
+		"/lib/*-linux-gnu/libfuseprivate.so.0",
+	}
+
+	for _, p := range patterns {
+		matches, err := filepath.Glob(p)
+		if err != nil {
+			return err
+		}
+
+		if len(matches) != 1 {
+			continue
 		}
+
+		target := filepath.Join(rootfsDir, "lib", filepath.Base(matches[0]))
+
+		err = lxd.FileCopy(matches[0], target)
+		if err != nil {
+			return err
+		}
+
+		err = os.Chmod(target, 0755)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Download init script
+	res, err := http.Get("https://raw.githubusercontent.com/lxc/lxc-ci/master/images/ubuntu-core/init")
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+
+	initFile, err := os.Create(filepath.Join(rootfsDir, "bin", "init"))
+	if err != nil {
+		return err
+	}
+	defer initFile.Close()
+
+	_, err = io.Copy(initFile, res.Body)
+	if err != nil {
+		return err
+	}
+
+	err = initFile.Chmod(0755)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *UbuntuHTTP) downloadImage(definition shared.Definition) error {
+	var baseURL string
+
+	switch strings.ToLower(definition.Image.Variant) {
+	case "default":
+		baseURL = fmt.Sprintf("%s/releases/%s/release/", definition.Source.URL,
+			definition.Image.Release)
+
+		if strings.ContainsAny(definition.Image.Release, "0123456789") {
+			s.fname = fmt.Sprintf("ubuntu-base-%s-base-%s.tar.gz",
+				definition.Image.Release, definition.Image.ArchitectureMapped)
+		} else {
+			// if release is non-numerical, find the latest release
+			s.fname = getLatestRelease(baseURL,
+				definition.Image.Release, definition.Image.ArchitectureMapped)
+			if s.fname == "" {
+				return fmt.Errorf("Couldn't find latest release")
+			}
+		}
+	case "core":
+		baseURL = fmt.Sprintf("%s/%s/stable/current/", definition.Source.URL, definition.Image.Release)
+		s.fname = fmt.Sprintf("ubuntu-core-%s-%s.img.xz", definition.Image.Release, definition.Image.ArchitectureMapped)
+	default:
+		return fmt.Errorf("Unknown Ubuntu variant: %s", definition.Image.Variant)
 	}
 
 	url, err := url.Parse(baseURL)
@@ -80,38 +326,11 @@ func (s *UbuntuHTTP) Run(definition shared.Definition, rootfsDir string) error {
 		}
 	}
 
-	fpath, err = shared.DownloadHash(definition.Image, baseURL+s.fname, checksumFile, sha256.New())
+	s.fpath, err = shared.DownloadHash(definition.Image, baseURL+s.fname, checksumFile, sha256.New())
 	if err != nil {
 		return fmt.Errorf("Error downloading Ubuntu image: %s", err)
 	}
 
-	err = s.unpack(filepath.Join(fpath, s.fname), rootfsDir)
-	if err != nil {
-		return err
-	}
-
-	if definition.Source.AptSources != "" {
-		// Run the template
-		out, err := shared.RenderTemplate(definition.Source.AptSources, definition)
-		if err != nil {
-			return err
-		}
-
-		// Append final new line if missing
-		if !strings.HasSuffix(out, "\n") {
-			out += "\n"
-		}
-
-		// Replace content of sources.list with the templated content.
-		file, err := os.Create(filepath.Join(rootfsDir, "etc", "apt", "sources.list"))
-		if err != nil {
-			return err
-		}
-		defer file.Close()
-
-		file.WriteString(out)
-	}
-
 	return nil
 }
 
@@ -127,8 +346,8 @@ func (s UbuntuHTTP) unpack(filePath, rootDir string) error {
 	return nil
 }
 
-func getLatestRelease(URL, release, arch string) string {
-	resp, err := http.Get(URL + path.Join("/", "releases", release, "release"))
+func getLatestRelease(baseURL, release, arch string) string {
+	resp, err := http.Get(baseURL)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 		return ""
@@ -145,3 +364,53 @@ func getLatestRelease(URL, release, arch string) string {
 
 	return ""
 }
+
+func getFullPath(name string) string {
+	var out string
+
+	paths := strings.Split(os.Getenv("PATH"), ":")
+	f := func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		if filepath.Base(path) != name {
+			// This is not the file we're looking for.
+			return nil
+		}
+
+		target := path
+
+		if info.Mode() == os.ModeSymlink {
+			// Check the symlink target
+			target, err = os.Readlink(path)
+			if err != nil {
+				return err
+			}
+
+			if filepath.Base(target) != name {
+				return nil
+			}
+		}
+
+		if out == "" {
+			out = target
+		}
+
+		return nil
+	}
+
+	for _, p := range paths {
+		filepath.Walk(p, f)
+
+		if out != "" {
+			return out
+		}
+	}
+
+	return ""
+}

From 212f1391de98f64f37f29a8c8bd9ef5c6de5d0b3 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 21 Jun 2019 20:16:44 +0200
Subject: [PATCH 2/2] doc: Add Ubuntu Core example

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 doc/examples/ubuntu-core | 100 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)
 create mode 100644 doc/examples/ubuntu-core

diff --git a/doc/examples/ubuntu-core b/doc/examples/ubuntu-core
new file mode 100644
index 0000000..e2803e5
--- /dev/null
+++ b/doc/examples/ubuntu-core
@@ -0,0 +1,100 @@
+image:
+  distribution: ubuntu
+  release: 16
+  variant: core
+  description: Ubuntu Core {{ image.release }}
+  expiry: 30d
+  architecture: amd64
+
+source:
+  downloader: ubuntu-http
+  url: http://cdimage.ubuntu.com/ubuntu-core
+  keys:
+    - 0x46181433FBB75451
+    - 0xD94AA3F0EFE21092
+
+targets:
+  lxc:
+    create-message: |-
+      You just created an {{ image.description }} container.
+
+      To enable SSH, run: apt install openssh-server
+      No default root or user password are set by LXC.
+
+    config:
+      - type: all
+        before: 5
+        content: |-
+          lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.common.conf
+
+      - type: user
+        before: 5
+        content: |-
+          lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.userns.conf
+
+      - type: all
+        after: 4
+        content: |-
+          lxc.include = LXC_TEMPLATE_CONFIG/common.conf
+
+          # For Ubuntu 14.04
+          lxc.mount.entry = /sys/kernel/debug sys/kernel/debug none bind,optional 0 0
+          lxc.mount.entry = /sys/kernel/security sys/kernel/security none bind,optional 0 0
+          lxc.mount.entry = /sys/fs/pstore sys/fs/pstore none bind,optional 0 0
+          lxc.mount.entry = mqueue dev/mqueue mqueue rw,relatime,create=dir,optional 0 0
+
+      - type: user
+        after: 4
+        content: |-
+          lxc.include = LXC_TEMPLATE_CONFIG/userns.conf
+
+          # For Ubuntu 14.04
+          lxc.mount.entry = /sys/firmware/efi/efivars sys/firmware/efi/efivars none bind,optional 0 0
+          lxc.mount.entry = /proc/sys/fs/binfmt_misc proc/sys/fs/binfmt_misc none bind,optional 0 0
+
+      - type: all
+        content: |-
+          lxc.arch = {{ image.architecture_personality }}
+
+packages:
+  custom-manager:
+    install:
+      cmd: true
+    remove:
+      cmd: true
+    update:
+      cmd: true
+    clean:
+      cmd: true 
+    refresh:
+      cmd: true
+
+files:
+  - name: cloud-init-meta
+    path: /lxd/meta-data
+    generator: template
+  - name: cloud-init-network
+    path: /lxd/network-config
+    generator: template
+  - name: cloud-init-user
+    path: /lxd/user-data
+    generator: template
+    template:
+      properties:
+        default: |
+          #cloud-config
+          {}
+  - name: cloud-init-vendor
+    path: /lxd/vendor-data
+    generator: template
+    template:
+      properties:
+        default: |
+          #cloud-config
+          {}	
+  - name: hostname
+    path: /lxd/hostname
+    generator: template
+
+mappings:
+  architecture_map: debian
\ No newline at end of file


More information about the lxc-devel mailing list