[lxc-devel] [distrobuilder/master] Issues/135 opensuse

monstermunchkin on Github lxc-bot at linuxcontainers.org
Tue Feb 26 16:19:25 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 395 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190226/26d50198/attachment.bin>
-------------- next part --------------
From 5a7ee70ca2b0f7e35a713db54f72e3e56cdef5a9 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 26 Feb 2019 16:29:34 +0100
Subject: [PATCH 1/5] shared: Decrypt signed file

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

diff --git a/shared/util.go b/shared/util.go
index a31abc2..1e23891 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -78,6 +78,24 @@ func RunScript(content string) error {
 	return cmd.Run()
 }
 
+func GetSignedContent(signedFile string, keys []string, keyserver string) ([]byte, error) {
+	keyring, err := CreateGPGKeyring(keyserver, keys)
+	if err != nil {
+		return nil, err
+	}
+
+	gpgDir := path.Dir(keyring)
+	defer os.RemoveAll(gpgDir)
+
+	out, err := exec.Command("gpg", "--homedir", gpgDir, "--keyring", keyring,
+		"--decrypt", signedFile).Output()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get file content: %v", err)
+	}
+
+	return out, nil
+}
+
 // VerifyFile verifies a file using gpg.
 func VerifyFile(signedFile, signatureFile string, keys []string, keyserver string) (bool, error) {
 	keyring, err := CreateGPGKeyring(keyserver, keys)

From 5fc17564fa350eab6b5b47c078b8b64a5960e72c Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 26 Feb 2019 13:05:52 +0100
Subject: [PATCH 2/5] sources: Add openSUSE

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 sources/opensuse-http.go | 212 +++++++++++++++++++++++++++++++++++++++
 sources/source.go        |   2 +
 2 files changed, 214 insertions(+)
 create mode 100644 sources/opensuse-http.go

diff --git a/sources/opensuse-http.go b/sources/opensuse-http.go
new file mode 100644
index 0000000..ec4e5e7
--- /dev/null
+++ b/sources/opensuse-http.go
@@ -0,0 +1,212 @@
+package sources
+
+import (
+	"crypto/sha256"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+	"syscall"
+
+	"github.com/lxc/distrobuilder/shared"
+	lxd "github.com/lxc/lxd/shared"
+)
+
+// OpenSUSEHTTP represents the OpenSUSE HTTP downloader.
+type OpenSUSEHTTP struct{}
+
+// NewOpenSUSEHTTP creates a new OpenSUSEHTTP instance.
+func NewOpenSUSEHTTP() *OpenSUSEHTTP {
+	return &OpenSUSEHTTP{}
+}
+
+func (s *OpenSUSEHTTP) Run(definition shared.Definition, rootfsDir string) error {
+	var baseURL string
+	var fname string
+
+	switch definition.Image.ArchitectureKernel {
+	case "x86_64":
+		if strings.ToLower(definition.Image.Release) == "tumbleweed" {
+			fname = "openSUSE-Tumbleweed-GNOME-Live-x86_64-Current.iso"
+			baseURL = fmt.Sprintf("%s/tumbleweed/iso/", definition.Source.URL)
+		} else {
+			// Leap
+			fname = fmt.Sprintf("openSUSE-Leap-%s-GNOME-Live-x86_64-Current.iso", definition.Image.Release)
+			baseURL = fmt.Sprintf("%s/distribution/leap/%s/live/", definition.Source.URL, definition.Image.Release)
+		}
+	case "aarch64":
+		// TODO: support
+	case "i686":
+		if strings.ToLower(definition.Image.Release) == "tumbleweed" {
+			fname = "openSUSE-Tumbleweed-GNOME-Live-i686-Current.iso"
+			baseURL = fmt.Sprintf("%s/tumbleweed/iso/", definition.Source.URL)
+		} else {
+			// Leap
+			return fmt.Errorf("Leap is not supported on %s", definition.Image.ArchitectureKernel)
+		}
+	default:
+		return fmt.Errorf("Unsupported architecture: %s", definition.Image.ArchitectureKernel)
+	}
+
+	// Resolve target since "-Current" is only a symlink
+	resp, err := http.Head(fmt.Sprintf("%s/%s", baseURL, fname))
+	if err != nil {
+		return fmt.Errorf("Couldn't resolve URL: %v", err)
+	}
+
+	baseURL, fname = path.Split(resp.Request.URL.String())
+
+	url, err := url.Parse(fmt.Sprintf("%s/%s", baseURL, fname))
+	if err != nil {
+		return err
+	}
+
+	checksumFile := ""
+	if !definition.Source.SkipVerification {
+		// Force gpg checks when using http
+		if url.Scheme != "https" {
+			if len(definition.Source.Keys) == 0 {
+				return errors.New("GPG keys are required if downloading from HTTP")
+			}
+
+			checksumFile = fmt.Sprintf("%s.sha256", fname)
+
+			shared.DownloadHash(baseURL+checksumFile, "", nil)
+			valid, err := shared.VerifyFile(filepath.Join(os.TempDir(), checksumFile), "",
+				definition.Source.Keys, definition.Source.Keyserver)
+			if err != nil {
+				return err
+			}
+			if !valid {
+				return errors.New("Failed to verify tarball")
+			}
+		}
+	}
+
+	err = shared.DownloadHash(url.String(), "", nil)
+	if err != nil {
+		return fmt.Errorf("Error downloading openSUSE image: %s", err)
+	}
+
+	// Manually verify the checksum
+	checksum, err := shared.GetSignedContent(filepath.Join(os.TempDir(), checksumFile),
+		definition.Source.Keys, definition.Source.Keyserver)
+	if err != nil {
+		return fmt.Errorf("Failed to read signed file: %v", err)
+	}
+
+	imagePath := filepath.Join(os.TempDir(), fname)
+
+	image, err := os.Open(imagePath)
+	if err != nil {
+		return fmt.Errorf("Failed to verify image: %v", err)
+	}
+
+	hash := sha256.New()
+	_, err = io.Copy(hash, image)
+	if err != nil {
+		image.Close()
+		return fmt.Errorf("Failed to verify image: %v", err)
+	}
+
+	image.Close()
+
+	result := fmt.Sprintf("%x", hash.Sum(nil))
+	checksumStr := strings.TrimSpace(string(checksum))
+
+	if result != checksumStr {
+		return fmt.Errorf("Hash mismatch for %s: %s != %s", imagePath, result, checksumStr)
+	}
+
+	return s.unpackISO(imagePath, rootfsDir)
+}
+
+func (s *OpenSUSEHTTP) unpackISO(filePath, rootfsDir string) error {
+	isoDir := filepath.Join(os.TempDir(), "distrobuilder", "iso")
+	squashfsDir := filepath.Join(os.TempDir(), "distrobuilder", "squashfs")
+	roRootDir := filepath.Join(os.TempDir(), "distrobuilder", "rootfs.ro")
+	tempRootDir := filepath.Join(os.TempDir(), "distrobuilder", "rootfs")
+
+	os.MkdirAll(isoDir, 0755)
+	os.MkdirAll(squashfsDir, 0755)
+	os.MkdirAll(roRootDir, 0755)
+	defer os.RemoveAll(filepath.Join(os.TempDir(), "distrobuilder"))
+
+	// this is easier than doing the whole loop thing ourselves
+	err := shared.RunCommand("mount", "-o", "ro", filePath, isoDir)
+	if err != nil {
+		return err
+	}
+	defer syscall.Unmount(isoDir, 0)
+
+	var rootfsImage string
+	squashfsImage := filepath.Join(isoDir, "LiveOS", "squashfs.img")
+	if lxd.PathExists(squashfsImage) {
+		// The squashfs.img contains an image containing the rootfs, so first
+		// mount squashfs.img
+		err = shared.RunCommand("mount", "-o", "ro", squashfsImage, squashfsDir)
+		if err != nil {
+			return err
+		}
+		defer syscall.Unmount(squashfsDir, 0)
+
+		rootfsImage = filepath.Join(squashfsDir, "LiveOS", "rootfs.img")
+	} else {
+		rootfsImage = filepath.Join(isoDir, "images", "install.img")
+	}
+
+	err = shared.RunCommand("mount", "-o", "ro", rootfsImage, roRootDir)
+	if err != nil {
+		return err
+	}
+	defer syscall.Unmount(roRootDir, 0)
+
+	// Remove rootfsDir otherwise rsync will copy the content into the directory
+	// itself
+	err = os.RemoveAll(rootfsDir)
+	if err != nil {
+		return err
+	}
+
+	// Since roRootDir is read-only, we need to copy it to a temporary rootfs
+	// directory in order to create the minimal rootfs.
+	err = shared.RunCommand("rsync", "-qa", roRootDir+"/", tempRootDir)
+	if err != nil {
+		return err
+	}
+
+	// Setup the mounts and chroot into the rootfs
+	exitChroot, err := shared.SetupChroot(tempRootDir, shared.DefinitionEnv{})
+	if err != nil {
+		return fmt.Errorf("Failed to setup chroot: %s", err)
+	}
+
+	err = shared.RunScript(fmt.Sprintf(`
+#!/bin/sh
+set -eux
+
+# This will fix gpg key issues
+export HOME=/root
+
+mkdir /rootfs
+
+zypper --installroot=/rootfs --non-interactive --gpg-auto-import-keys clean -a
+zypper --installroot=/rootfs --non-interactive --gpg-auto-import-keys ref
+zypper --installroot=/rootfs --non-interactive --gpg-auto-import-keys install -t pattern base
+
+cp -r /etc/zypp/repos.d/ /rootfs/etc/zypp/
+`))
+	if err != nil {
+		exitChroot()
+		return err
+	}
+
+	exitChroot()
+
+	return shared.RunCommand("rsync", "-qa", tempRootDir+"/rootfs/", rootfsDir)
+}
diff --git a/sources/source.go b/sources/source.go
index 7ec368b..a284e68 100644
--- a/sources/source.go
+++ b/sources/source.go
@@ -30,6 +30,8 @@ func Get(name string) Downloader {
 		return NewDockerHTTP()
 	case "oraclelinux-http":
 		return NewOracleLinuxHTTP()
+	case "opensuse-http":
+		return NewOpenSUSEHTTP()
 	}
 
 	return nil

From 4909921855a0841923635e82171243e6cca4a161 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 26 Feb 2019 13:22:43 +0100
Subject: [PATCH 3/5] shared: Add openSUSE

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

diff --git a/shared/definition.go b/shared/definition.go
index 0ea231b..b7e7363 100644
--- a/shared/definition.go
+++ b/shared/definition.go
@@ -240,6 +240,7 @@ func (d *Definition) Validate() error {
 		"sabayon-http",
 		"docker-http",
 		"oraclelinux-http",
+		"opensuse-http",
 	}
 	if !shared.StringInSlice(strings.TrimSpace(d.Source.Downloader), validDownloaders) {
 		return fmt.Errorf("source.downloader must be one of %v", validDownloaders)

From b3de96a35be1deefd990a6b312ef236fe88fd566 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 26 Feb 2019 13:29:28 +0100
Subject: [PATCH 4/5] managers: Add zypper

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 managers/manager.go |  2 ++
 managers/zypper.go  | 30 ++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)
 create mode 100644 managers/zypper.go

diff --git a/managers/manager.go b/managers/manager.go
index 44dcfdd..21d7f0a 100644
--- a/managers/manager.go
+++ b/managers/manager.go
@@ -42,6 +42,8 @@ func Get(name string) *Manager {
 		return NewYum()
 	case "equo":
 		return NewEquo()
+	case "zypper":
+		return NewZypper()
 	}
 
 	return nil
diff --git a/managers/zypper.go b/managers/zypper.go
new file mode 100644
index 0000000..6f54213
--- /dev/null
+++ b/managers/zypper.go
@@ -0,0 +1,30 @@
+package managers
+
+// NewZypper create a new Manager instance.
+func NewZypper() *Manager {
+	return &Manager{
+		command: "zypper",
+		flags: ManagerFlags{
+			global: []string{
+				"--non-interactive",
+				"--gpg-auto-import-keys",
+			},
+			clean: []string{
+				"clean",
+				"-a",
+			},
+			install: []string{
+				"install",
+			},
+			remove: []string{
+				"remove",
+			},
+			refresh: []string{
+				"refresh",
+			},
+			update: []string{
+				"update",
+			},
+		},
+	}
+}

From a1da4d1253d4ea02361d0bcc465460a5d7984ff3 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 26 Feb 2019 13:30:38 +0100
Subject: [PATCH 5/5] shared: Add zypper

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

diff --git a/shared/definition.go b/shared/definition.go
index b7e7363..b8d4ae7 100644
--- a/shared/definition.go
+++ b/shared/definition.go
@@ -254,6 +254,7 @@ func (d *Definition) Validate() error {
 		"portage",
 		"yum",
 		"equo",
+		"zypper",
 	}
 	if !shared.StringInSlice(strings.TrimSpace(d.Packages.Manager), validManagers) {
 		return fmt.Errorf("packages.manager must be one of %v", validManagers)


More information about the lxc-devel mailing list