From dbebec3c555eb83f1630b651ce78d8433e038b35 Mon Sep 17 00:00:00 2001 From: Thomas Hipp Date: Tue, 6 Mar 2018 10:20:45 +0100 Subject: [PATCH] sources: Add Fedora support Resolves #23 Signed-off-by: Thomas Hipp --- shared/definition.go | 1 + sources/fedora-http.go | 156 +++++++++++++++++++++++++++++++++++++++++ sources/source.go | 14 ++-- 3 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 sources/fedora-http.go diff --git a/shared/definition.go b/shared/definition.go index 170ce63..fab6a93 100644 --- a/shared/definition.go +++ b/shared/definition.go @@ -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) { diff --git a/sources/fedora-http.go b/sources/fedora-http.go new file mode 100644 index 0000000..7e87803 --- /dev/null +++ b/sources/fedora-http.go @@ -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: + // -
. + // -
.n. + 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 +} diff --git a/sources/source.go b/sources/source.go index ccdc686..e7f6c8b 100644 --- a/sources/source.go +++ b/sources/source.go @@ -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