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
