Merge pull request #139 from monstermunchkin/issues/135-opensuse
Add openSUSE
This commit is contained in:
commit
f1f8733941
67
doc/examples/opensuse
Normal file
67
doc/examples/opensuse
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
image:
|
||||||
|
distribution: openSUSE
|
||||||
|
release: 15.0
|
||||||
|
description: openSUSE Leap {{ image.release }}
|
||||||
|
expiry: 30d
|
||||||
|
architecture: x86_64
|
||||||
|
|
||||||
|
source:
|
||||||
|
downloader: opensuse-http
|
||||||
|
url: https://download.opensuse.org
|
||||||
|
keyserver: keyserver.ubuntu.com
|
||||||
|
keys:
|
||||||
|
- 0xA193FBB572174FC2
|
||||||
|
|
||||||
|
targets:
|
||||||
|
lxc:
|
||||||
|
create-message: |
|
||||||
|
You just created an openSUSE Leap container (release={{ image.release }}, arch={{ image.architecture }})
|
||||||
|
|
||||||
|
config:
|
||||||
|
- type: all
|
||||||
|
before: 5
|
||||||
|
content: |-
|
||||||
|
lxc.include = LXC_TEMPLATE_CONFIG/opensuse.common.conf
|
||||||
|
|
||||||
|
- type: user
|
||||||
|
before: 5
|
||||||
|
content: |-
|
||||||
|
lxc.include = LXC_TEMPLATE_CONFIG/opensuse.userns.conf
|
||||||
|
|
||||||
|
- type: all
|
||||||
|
after: 4
|
||||||
|
content: |-
|
||||||
|
lxc.include = LXC_TEMPLATE_CONFIG/common.conf
|
||||||
|
|
||||||
|
- type: user
|
||||||
|
after: 4
|
||||||
|
content: |-
|
||||||
|
lxc.include = LXC_TEMPLATE_CONFIG/userns.conf
|
||||||
|
|
||||||
|
- type: all
|
||||||
|
content: |-
|
||||||
|
lxc.arch = {{ image.architecture_kernel }}
|
||||||
|
|
||||||
|
files:
|
||||||
|
- path: /etc/hostname
|
||||||
|
generator: hostname
|
||||||
|
|
||||||
|
- path: /etc/hosts
|
||||||
|
generator: hosts
|
||||||
|
|
||||||
|
packages:
|
||||||
|
manager: zypper
|
||||||
|
|
||||||
|
update: true
|
||||||
|
cleanup: true
|
||||||
|
|
||||||
|
sets:
|
||||||
|
- packages:
|
||||||
|
- systemd
|
||||||
|
- neovim
|
||||||
|
action: install
|
||||||
|
|
||||||
|
environment:
|
||||||
|
variables:
|
||||||
|
- key: HOME
|
||||||
|
value: /root
|
@ -42,6 +42,8 @@ func Get(name string) *Manager {
|
|||||||
return NewYum()
|
return NewYum()
|
||||||
case "equo":
|
case "equo":
|
||||||
return NewEquo()
|
return NewEquo()
|
||||||
|
case "zypper":
|
||||||
|
return NewZypper()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
30
managers/zypper.go
Normal file
30
managers/zypper.go
Normal file
@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -240,6 +240,7 @@ func (d *Definition) Validate() error {
|
|||||||
"sabayon-http",
|
"sabayon-http",
|
||||||
"docker-http",
|
"docker-http",
|
||||||
"oraclelinux-http",
|
"oraclelinux-http",
|
||||||
|
"opensuse-http",
|
||||||
}
|
}
|
||||||
if !shared.StringInSlice(strings.TrimSpace(d.Source.Downloader), validDownloaders) {
|
if !shared.StringInSlice(strings.TrimSpace(d.Source.Downloader), validDownloaders) {
|
||||||
return fmt.Errorf("source.downloader must be one of %v", validDownloaders)
|
return fmt.Errorf("source.downloader must be one of %v", validDownloaders)
|
||||||
@ -253,6 +254,7 @@ func (d *Definition) Validate() error {
|
|||||||
"portage",
|
"portage",
|
||||||
"yum",
|
"yum",
|
||||||
"equo",
|
"equo",
|
||||||
|
"zypper",
|
||||||
}
|
}
|
||||||
if !shared.StringInSlice(strings.TrimSpace(d.Packages.Manager), validManagers) {
|
if !shared.StringInSlice(strings.TrimSpace(d.Packages.Manager), validManagers) {
|
||||||
return fmt.Errorf("packages.manager must be one of %v", validManagers)
|
return fmt.Errorf("packages.manager must be one of %v", validManagers)
|
||||||
|
@ -78,6 +78,25 @@ func RunScript(content string) error {
|
|||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSignedContent verifies the provided file, and returns its decrypted (plain) content.
|
||||||
|
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.
|
// VerifyFile verifies a file using gpg.
|
||||||
func VerifyFile(signedFile, signatureFile string, keys []string, keyserver string) (bool, error) {
|
func VerifyFile(signedFile, signatureFile string, keys []string, keyserver string) (bool, error) {
|
||||||
keyring, err := CreateGPGKeyring(keyserver, keys)
|
keyring, err := CreateGPGKeyring(keyserver, keys)
|
||||||
|
162
sources/opensuse-http.go
Normal file
162
sources/opensuse-http.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package sources
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lxc/distrobuilder/shared"
|
||||||
|
lxd "github.com/lxc/lxd/shared"
|
||||||
|
"gopkg.in/antchfx/htmlquery.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenSUSEHTTP represents the OpenSUSE HTTP downloader.
|
||||||
|
type OpenSUSEHTTP struct{}
|
||||||
|
|
||||||
|
// NewOpenSUSEHTTP creates a new OpenSUSEHTTP instance.
|
||||||
|
func NewOpenSUSEHTTP() *OpenSUSEHTTP {
|
||||||
|
return &OpenSUSEHTTP{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run downloads an OpenSUSE tarball.
|
||||||
|
func (s *OpenSUSEHTTP) Run(definition shared.Definition, rootfsDir string) error {
|
||||||
|
var baseURL string
|
||||||
|
var fname string
|
||||||
|
|
||||||
|
tarballPath := s.getPathToTarball(definition.Source.URL, definition.Image.Release,
|
||||||
|
definition.Image.ArchitectureMapped)
|
||||||
|
|
||||||
|
resp, err := http.Head(tarballPath)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
fpath, err := shared.DownloadHash(definition.Image, url.String(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error downloading openSUSE image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if definition.Source.SkipVerification {
|
||||||
|
// Unpack
|
||||||
|
return lxd.Unpack(filepath.Join(fpath, fname), rootfsDir, false, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
checksumPath := fmt.Sprintf("%s.sha256", tarballPath)
|
||||||
|
checksumFile := path.Base(checksumPath)
|
||||||
|
|
||||||
|
shared.DownloadHash(definition.Image, checksumPath, "", nil)
|
||||||
|
valid, err := shared.VerifyFile(filepath.Join(fpath, checksumFile), "",
|
||||||
|
definition.Source.Keys, definition.Source.Keyserver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return errors.New("Failed to verify tarball")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually verify the checksum
|
||||||
|
checksum, err := shared.GetSignedContent(filepath.Join(fpath, checksumFile),
|
||||||
|
definition.Source.Keys, definition.Source.Keyserver)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to read signed file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePath := filepath.Join(fpath, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack
|
||||||
|
return lxd.Unpack(filepath.Join(fpath, fname), rootfsDir, false, false, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OpenSUSEHTTP) getLatestBuild(URL string) string {
|
||||||
|
doc, err := htmlquery.LoadURL(URL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if doc == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := htmlquery.Find(doc, `//a[starts-with(text(),'opensuse')][ends-with(text(), 'tar.xz')][@href]/text()`)
|
||||||
|
|
||||||
|
if nodes == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes[len(nodes)-1].Data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OpenSUSEHTTP) getPathToTarball(baseURL string, release string, arch string) string {
|
||||||
|
u, err := url.Parse(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Join(u.Path, "repositories", "Virtualization:", "containers:", "images:")
|
||||||
|
|
||||||
|
if strings.ToLower(release) == "tumbleweed" {
|
||||||
|
u.Path = path.Join(u.Path, "openSUSE-Tumbleweed")
|
||||||
|
|
||||||
|
switch arch {
|
||||||
|
case "i686", "x86_64":
|
||||||
|
u.Path = path.Join(u.Path, "container")
|
||||||
|
case "aarch64":
|
||||||
|
u.Path = path.Join(u.Path, "container_ARM")
|
||||||
|
case "ppc64le":
|
||||||
|
u.Path = path.Join(u.Path, "container_PowerPC")
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Join(u.Path, fmt.Sprintf("opensuse-tumbleweed-image.%s-lxc.tar.xz",
|
||||||
|
arch))
|
||||||
|
} else {
|
||||||
|
u.Path = path.Join(u.Path, fmt.Sprintf("openSUSE-Leap-%s", release))
|
||||||
|
|
||||||
|
switch arch {
|
||||||
|
case "x86_64":
|
||||||
|
u.Path = path.Join(u.Path, "containers")
|
||||||
|
case "aarch64", "ppc64le":
|
||||||
|
u.Path = path.Join(u.Path, "containers_ports")
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Join(u.Path, s.getLatestBuild(u.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
@ -30,6 +30,8 @@ func Get(name string) Downloader {
|
|||||||
return NewDockerHTTP()
|
return NewDockerHTTP()
|
||||||
case "oraclelinux-http":
|
case "oraclelinux-http":
|
||||||
return NewOracleLinuxHTTP()
|
return NewOracleLinuxHTTP()
|
||||||
|
case "opensuse-http":
|
||||||
|
return NewOpenSUSEHTTP()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user