diff options
author | Mike Crute <crutem@amazon.com> | 2019-06-17 14:27:32 -0700 |
---|---|---|
committer | Mike Crute <crutem@amazon.com> | 2019-06-17 14:27:32 -0700 |
commit | 59b4f339126e1f6832bfc8bda66e8d2d70f48f31 (patch) | |
tree | 2a38a66b0151f3c574af4414e91a88540e5c8b35 /bin | |
parent | 7a74a13ba57ea34d7d45919d2e1aece4d902fd77 (diff) | |
download | dotfiles-59b4f339126e1f6832bfc8bda66e8d2d70f48f31.tar.bz2 dotfiles-59b4f339126e1f6832bfc8bda66e8d2d70f48f31.tar.xz dotfiles-59b4f339126e1f6832bfc8bda66e8d2d70f48f31.zip |
Add Ubuntu kernel downloader
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/get-ubuntu-mainline-kernel.py | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/bin/get-ubuntu-mainline-kernel.py b/bin/get-ubuntu-mainline-kernel.py new file mode 100755 index 0000000..2005dd3 --- /dev/null +++ b/bin/get-ubuntu-mainline-kernel.py | |||
@@ -0,0 +1,231 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | """ | ||
3 | Ubuntu Mainline Kernel Downloader | ||
4 | Author: Mike Crute <mike[at]crute[dot]us> | ||
5 | |||
6 | This script parses the Ubuntu mainline kernel build page, determines the most | ||
7 | recent kernel version (skipping rc and suffixed kernels) and downloads the | ||
8 | appropriate (non-realtime) Debian packages for installation. | ||
9 | |||
10 | The architecture defaults to amd64 and can be customized at the bottom of the | ||
11 | script. | ||
12 | |||
13 | After running this, run `sudo dpkg -i *.deb` to install the latest kernel | ||
14 | packages. | ||
15 | """ | ||
16 | |||
17 | import re | ||
18 | import os.path | ||
19 | import logging | ||
20 | import html.parser | ||
21 | import urllib.parse | ||
22 | import urllib.request | ||
23 | |||
24 | |||
25 | class KernelVersion: | ||
26 | """Kernel Version Parser and Wrapper | ||
27 | |||
28 | Parses a kernel version from a URL and wraps it in a class that makes | ||
29 | comparison and sorting easy. Considers major, minor, patch, and rc version | ||
30 | components but nothing else. Will reject suffixed kernels (-wiley, etc...) | ||
31 | because it's not easy to know where they fall in the series. This should be | ||
32 | valid for all kernels 4.5 and newer but breaks for older ones. | ||
33 | """ | ||
34 | |||
35 | VERSION_RE = re.compile( | ||
36 | "^v(?P<major>[0-9]+)\.(?P<minor>[0-9]+)" | ||
37 | "(\.(?P<patch>[0-9]+))?(-rc(?P<rc>[0-9]+))?/$") | ||
38 | |||
39 | def __init__(self, raw_version, major, minor, patch, rc=None): | ||
40 | self._raw_version = raw_version | ||
41 | self.major = int(major or 0) | ||
42 | self.minor = int(minor or 0) | ||
43 | self.patch = int(patch or 0) | ||
44 | self.rc = int(rc or 0) | ||
45 | |||
46 | def __repr__(self): | ||
47 | return (f"{self.__class__.__name__}({self._raw_version!r}, " | ||
48 | f"{self.major!r}, {self.minor!r}, {self.patch!r}, " | ||
49 | f"{self.rc!r})") | ||
50 | |||
51 | def _as_tuple(self): | ||
52 | return (self.major, self.minor, self.patch, self.rc) | ||
53 | |||
54 | def __lt__(self, other): | ||
55 | return self._as_tuple() < other._as_tuple() | ||
56 | |||
57 | def __gt__(self, other): | ||
58 | return self._as_tuple() > other._as_tuple() | ||
59 | |||
60 | def __eq__(self, other): | ||
61 | return self._as_tuple() == other._as_tuple() | ||
62 | |||
63 | def __le__(self, other): | ||
64 | return self < other or self == other | ||
65 | |||
66 | def __ge__(self, other): | ||
67 | return self > other or self == other | ||
68 | |||
69 | def __hash__(self): | ||
70 | return hash((self._raw_version,) + self._as_tuple()) | ||
71 | |||
72 | @classmethod | ||
73 | def parse_version(cls, version): | ||
74 | match = cls.VERSION_RE.match(version) | ||
75 | if not match: | ||
76 | raise ValueError(f"Unable to parse version {version!r}") | ||
77 | |||
78 | return cls( | ||
79 | version, | ||
80 | int(match["major"]), | ||
81 | int(match["minor"]), | ||
82 | int(match["patch"] or 0), | ||
83 | int(match["rc"] or 0) | ||
84 | ) | ||
85 | |||
86 | @property | ||
87 | def is_rc(self): | ||
88 | return bool(self.rc) | ||
89 | |||
90 | def detail_url(self, base_url): | ||
91 | return urllib.parse.urljoin(base_url, self._raw_version) | ||
92 | |||
93 | |||
94 | class KernelPackage: | ||
95 | """Kernel Package URL | ||
96 | |||
97 | Encapsulates a kernel package URL and allow access to the filename and URL. | ||
98 | Must be hashable because there are duplicate package names on the pages and | ||
99 | we use a set to deduplicate them. | ||
100 | """ | ||
101 | |||
102 | def __init__(self, base_url, filename): | ||
103 | self.base_url = base_url | ||
104 | self.filename = filename | ||
105 | |||
106 | def __hash__(self): | ||
107 | return hash((self.base_url, self.filename)) | ||
108 | |||
109 | def __eq__(self, other): | ||
110 | return ((self.base_url, self.filename) == | ||
111 | (other.base_url, other.filename)) | ||
112 | |||
113 | @property | ||
114 | def url(self): | ||
115 | return urllib.parse.urljoin(self.base_url, self.filename) | ||
116 | |||
117 | def fetch(self): | ||
118 | urllib.request.urlretrieve(self.url, self.filename) | ||
119 | |||
120 | |||
121 | class HTMLParserFetcher(html.parser.HTMLParser): | ||
122 | """HTML Fetcher and Parser Base Class | ||
123 | |||
124 | Aggregates some set of results and returns them after fetching and parsing | ||
125 | the page. | ||
126 | """ | ||
127 | |||
128 | def __init__(self): | ||
129 | self.results = set() | ||
130 | super().__init__() | ||
131 | |||
132 | def fetch(self, url): | ||
133 | self.feed(urllib.request.urlopen(url).read().decode("utf-8")) | ||
134 | return self.results | ||
135 | |||
136 | |||
137 | class KernelListParser(HTMLParserFetcher): | ||
138 | """Main Kernel Version List Parser | ||
139 | |||
140 | Parses the main list of kernel versions (an Apache index page) and contains | ||
141 | a list of versions extracted from that page. | ||
142 | """ | ||
143 | |||
144 | def __init__(self, skip_rcs): | ||
145 | self.skip_rcs = skip_rcs | ||
146 | super().__init__() | ||
147 | |||
148 | def handle_starttag(self, tag, attrs): | ||
149 | if tag != "a": | ||
150 | return | ||
151 | |||
152 | url = dict(attrs).get("href") | ||
153 | if not url.startswith("v"): | ||
154 | return | ||
155 | |||
156 | try: | ||
157 | version = KernelVersion.parse_version(url) | ||
158 | except ValueError: | ||
159 | logging.warn(f"Skipping invalid version {url!r}") | ||
160 | return | ||
161 | |||
162 | if self.skip_rcs and version.is_rc: | ||
163 | return | ||
164 | else: | ||
165 | self.results.add(version) | ||
166 | |||
167 | |||
168 | class KernelDetailParser(HTMLParserFetcher): | ||
169 | """Kernel Detail Page Parser | ||
170 | |||
171 | Parses the detail page for a specific kernel version and returns candidate | ||
172 | packages of the correct architecture that can be downloaded. | ||
173 | |||
174 | There's a lot of duplication on this page so the results will be | ||
175 | de-duplicated. | ||
176 | """ | ||
177 | |||
178 | def __init__(self, base_url, arch): | ||
179 | self.base_url = base_url | ||
180 | self.pkg_re = re.compile(f".*_(all|{arch}).deb$") | ||
181 | super().__init__() | ||
182 | |||
183 | def _is_candidate(self, url): | ||
184 | return url and self.pkg_re.match(url) and "lowlatency" not in url | ||
185 | |||
186 | def handle_starttag(self, tag, attrs): | ||
187 | if tag != "a": | ||
188 | return | ||
189 | |||
190 | url = dict(attrs).get("href") | ||
191 | if self._is_candidate(url): | ||
192 | self.results.add(KernelPackage(self.base_url, url)) | ||
193 | |||
194 | |||
195 | class KernelVersionFetcher: | ||
196 | """Fetch Kernel Packages | ||
197 | |||
198 | Determines the latest version of the kernel avaiable (per the rules in | ||
199 | KernelVersion) and downloads the packages required to install that version | ||
200 | into the current directory. | ||
201 | """ | ||
202 | |||
203 | URL = "https://kernel.ubuntu.com/~kernel-ppa/mainline/" | ||
204 | |||
205 | def __init__(self, arch): | ||
206 | self.arch = arch | ||
207 | |||
208 | def find_latest_version(self, versions): | ||
209 | for v in sorted(versions, reverse=True): | ||
210 | return v | ||
211 | |||
212 | # If we made it here we found no candidate version | ||
213 | raise Exception("No latest version found") | ||
214 | |||
215 | def find_packages(self): | ||
216 | all_versions = KernelListParser(True).fetch(self.URL) | ||
217 | latest = self.find_latest_version(all_versions) | ||
218 | |||
219 | page_url = latest.detail_url(self.URL) | ||
220 | return KernelDetailParser(page_url, self.arch).fetch(page_url) | ||
221 | |||
222 | def fetch_packages(self): | ||
223 | for package in self.find_packages(): | ||
224 | print(f"Fetching: {package.filename}") | ||
225 | package.fetch() | ||
226 | |||
227 | # TODO: Check the checksums | ||
228 | |||
229 | |||
230 | if __name__ == "__main__": | ||
231 | KernelVersionFetcher("amd64").fetch_packages() | ||