sources: Add Fedora support

Resolves #23

Signed-off-by: Thomas Hipp <thomas.hipp@canonical.com>
This commit is contained in:
Thomas Hipp 2018-03-06 10:20:45 +01:00
parent 580c95e63a
commit dbebec3c55
No known key found for this signature in database
GPG Key ID: 993408D1137B7D51
3 changed files with 165 additions and 6 deletions

View File

@ -186,6 +186,7 @@ func (d *Definition) Validate() error {
"archlinux-http",
"centos-http",
"debootstrap",
"fedora-http",
"ubuntu-http",
}
if !shared.StringInSlice(strings.TrimSpace(d.Source.Downloader), validDownloaders) {

156
sources/fedora-http.go Normal file
View File

@ -0,0 +1,156 @@
package sources
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
lxd "github.com/lxc/lxd/shared"
"github.com/lxc/distrobuilder/shared"
)
// FedoraHTTP represents the Fedora HTTP downloader.
type FedoraHTTP struct {
fname string
}
// NewFedoraHTTP creates a new FedoraHTTP instance.
func NewFedoraHTTP() *FedoraHTTP {
return &FedoraHTTP{}
}
// Run downloads a container base image and unpacks it and its layers.
func (s *FedoraHTTP) Run(definition shared.Definition, rootfsDir string) error {
baseURL := fmt.Sprintf("%s/packages/Fedora-Container-Base",
definition.Source.URL)
// Get latest build
build, err := s.getLatestBuild(baseURL, definition.Image.Release)
if err != nil {
return err
}
fname := fmt.Sprintf("Fedora-Container-Base-%s-%s.%s.tar.xz",
definition.Image.Release, build, definition.Image.ArchitectureMapped)
// Download image
err = shared.Download(fmt.Sprintf("%s/%s/%s/images/%s",
baseURL, definition.Image.Release, build, fname), "")
if err != nil {
return err
}
// Unpack the base image
err = lxd.Unpack(filepath.Join(os.TempDir(), fname), rootfsDir, false, false)
if err != nil {
return err
}
// Unpack the rest of the image (/bin, /sbin, /usr, etc.)
return s.unpackLayers(rootfsDir)
}
func (s *FedoraHTTP) unpackLayers(rootfsDir string) error {
// Read manifest file which contains the path to the layers
file, err := os.Open(filepath.Join(rootfsDir, "manifest.json"))
if err != nil {
return err
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return err
}
// Structure of the manifest excluding RepoTags
var manifests []struct {
Layers []string
Config string
}
err = json.Unmarshal(data, &manifests)
if err != nil {
return err
}
pathsToRemove := []string{
filepath.Join(rootfsDir, "manifest.json"),
filepath.Join(rootfsDir, "repositories"),
}
// Unpack tarballs (or layers) which contain the rest of the rootfs, and
// remove files not relevant to the image.
for _, manifest := range manifests {
for _, layer := range manifest.Layers {
err := lxd.Unpack(filepath.Join(rootfsDir, layer), rootfsDir,
false, false)
if err != nil {
return err
}
pathsToRemove = append(pathsToRemove,
filepath.Join(rootfsDir, filepath.Dir(layer)))
}
pathsToRemove = append(pathsToRemove, filepath.Join(rootfsDir, manifest.Config))
}
// Clean up /tmp since there are unnecessary files there
files, err := filepath.Glob(filepath.Join(rootfsDir, "tmp", "*"))
if err != nil {
return err
}
pathsToRemove = append(pathsToRemove, files...)
// Clean up /root since there are unnecessary files there
files, err = filepath.Glob(filepath.Join(rootfsDir, "root", "*"))
if err != nil {
return err
}
pathsToRemove = append(pathsToRemove, files...)
for _, f := range pathsToRemove {
os.RemoveAll(f)
}
return nil
}
func (s *FedoraHTTP) getLatestBuild(URL, release string) (string, error) {
resp, err := http.Get(fmt.Sprintf("%s/%s", URL, release))
if err != nil {
return "", err
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
// Builds are formatted in one of two ways:
// - <yyyy><mm><dd>.<build_number>
// - <yyyy><mm><dd>.n.<build_number>
re := regexp.MustCompile(`\d{8}\.(n\.)?\d`)
// Find all builds
matches := re.FindAllString(string(content), -1)
if len(matches) == 0 {
return "", errors.New("Unable to find latest build")
}
// Sort builds
sort.Strings(matches)
// Return latest build
return matches[len(matches)-1], nil
}

View File

@ -10,16 +10,18 @@ type Downloader interface {
// Get returns a Downloader.
func Get(name string) Downloader {
switch name {
case "ubuntu-http":
return NewUbuntuHTTP()
case "debootstrap":
return NewDebootstrap()
case "alpinelinux-http":
return NewAlpineLinuxHTTP()
case "archlinux-http":
return NewArchLinuxHTTP()
case "centos-http":
return NewCentOSHTTP()
case "alpinelinux-http":
return NewAlpineLinuxHTTP()
case "debootstrap":
return NewDebootstrap()
case "fedora-http":
return NewFedoraHTTP()
case "ubuntu-http":
return NewUbuntuHTTP()
}
return nil