[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