1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
package sources
import (
"errors"
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/antchfx/htmlquery"
"github.com/lxc/distrobuilder/shared"
)
type archlinux struct {
common
}
// Run downloads an Arch Linux tarball.
func (s *archlinux) Run() error {
release := s.definition.Image.Release
// Releases are only available for the x86_64 architecture. ARM only has
// a "latest" tarball.
if s.definition.Image.ArchitectureMapped == "x86_64" && release == "" {
var err error
// Get latest release
release, err = s.getLatestRelease(s.definition.Source.URL, s.definition.Image.ArchitectureMapped)
if err != nil {
return fmt.Errorf("Failed to get latest release: %w", err)
}
}
var fname string
var tarball string
if s.definition.Image.ArchitectureMapped == "x86_64" {
fname = fmt.Sprintf("archlinux-bootstrap-%s-%s.tar.zst",
release, s.definition.Image.ArchitectureMapped)
tarball = fmt.Sprintf("%s/%s/%s", s.definition.Source.URL,
release, fname)
} else {
fname = fmt.Sprintf("ArchLinuxARM-%s-latest.tar.gz",
s.definition.Image.ArchitectureMapped)
tarball = fmt.Sprintf("%s/os/%s", s.definition.Source.URL, fname)
}
url, err := url.Parse(tarball)
if err != nil {
return fmt.Errorf("Failed to parse URL %q: %w", tarball, err)
}
if !s.definition.Source.SkipVerification && url.Scheme != "https" &&
len(s.definition.Source.Keys) == 0 {
return errors.New("GPG keys are required if downloading from HTTP")
}
fpath, err := s.DownloadHash(s.definition.Image, tarball, "", nil)
if err != nil {
return fmt.Errorf("Failed to download %q: %w", tarball, err)
}
// Force gpg checks when using http
if !s.definition.Source.SkipVerification && url.Scheme != "https" {
_, err = s.DownloadHash(s.definition.Image, tarball+".sig", "", nil)
if err != nil {
return fmt.Errorf("Failed downloading %q: %w", tarball+".sig", err)
}
valid, err := s.VerifyFile(
filepath.Join(fpath, fname),
filepath.Join(fpath, fname+".sig"))
if err != nil {
return fmt.Errorf("Failed to verify %q: %w", fname, err)
}
if !valid {
return fmt.Errorf("Invalid signature for %q", fname)
}
}
s.logger.WithField("file", filepath.Join(fpath, fname)).Info("Unpacking image")
// Unpack
err = shared.Unpack(filepath.Join(fpath, fname), s.rootfsDir)
if err != nil {
return fmt.Errorf("Failed to unpack file %q: %w", filepath.Join(fpath, fname), err)
}
// Move everything inside 'root.<architecture>' (which was is the tarball) to its
// parent directory
files, err := filepath.Glob(fmt.Sprintf("%s/*", filepath.Join(s.rootfsDir,
"root."+s.definition.Image.ArchitectureMapped)))
if err != nil {
return fmt.Errorf("Failed to get files: %w", err)
}
for _, file := range files {
err = os.Rename(file, filepath.Join(s.rootfsDir, path.Base(file)))
if err != nil {
return fmt.Errorf("Failed to rename file %q: %w", file, err)
}
}
path := filepath.Join(s.rootfsDir, "root."+s.definition.Image.ArchitectureMapped)
err = os.RemoveAll(path)
if err != nil {
return fmt.Errorf("Failed to remove %q: %w", path, err)
}
return nil
}
func (s *archlinux) getLatestRelease(URL string, arch string) (string, error) {
doc, err := htmlquery.LoadURL(URL)
if err != nil {
return "", fmt.Errorf("Failed to load URL %q: %w", URL, err)
}
re := regexp.MustCompile(`^\d{4}\.\d{2}\.\d{2}/?$`)
var releases []string
for _, node := range htmlquery.Find(doc, `//a[@href]/text()`) {
if re.MatchString(node.Data) {
releases = append(releases, strings.TrimSuffix(node.Data, "/"))
}
}
if len(releases) == 0 {
return "", errors.New("Failed to determine latest release")
}
// Sort releases in case they're out-of-order
sort.Strings(releases)
return releases[len(releases)-1], nil
}
|