-
Notifications
You must be signed in to change notification settings - Fork 161
/
archlinux-http.go
144 lines (115 loc) · 3.71 KB
/
archlinux-http.go
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"
"gopkg.in/antchfx/htmlquery.v1"
"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
}