The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/distrobuilder/pull/139

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) ===
Successfully tested:
- [ ] Leap 42.3
- [x] Leap 15
- [ ] Tumbleweed
- [ ] aarch64
From 5a7ee70ca2b0f7e35a713db54f72e3e56cdef5a9 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Tue, 26 Feb 2019 16:29:34 +0100
Subject: [PATCH 1/5] shared: Decrypt signed file

Signed-off-by: Thomas Hipp <[email protected]>
---
 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 <[email protected]>
Date: Tue, 26 Feb 2019 13:05:52 +0100
Subject: [PATCH 2/5] sources: Add openSUSE

Signed-off-by: Thomas Hipp <[email protected]>
---
 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 <[email protected]>
Date: Tue, 26 Feb 2019 13:22:43 +0100
Subject: [PATCH 3/5] shared: Add openSUSE

Signed-off-by: Thomas Hipp <[email protected]>
---
 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 <[email protected]>
Date: Tue, 26 Feb 2019 13:29:28 +0100
Subject: [PATCH 4/5] managers: Add zypper

Signed-off-by: Thomas Hipp <[email protected]>
---
 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 <[email protected]>
Date: Tue, 26 Feb 2019 13:30:38 +0100
Subject: [PATCH 5/5] shared: Add zypper

Signed-off-by: Thomas Hipp <[email protected]>
---
 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)
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to