summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorMike Crute <crutem@amazon.com>2019-06-17 14:27:32 -0700
committerMike Crute <crutem@amazon.com>2019-06-17 14:27:32 -0700
commit59b4f339126e1f6832bfc8bda66e8d2d70f48f31 (patch)
tree2a38a66b0151f3c574af4414e91a88540e5c8b35 /bin
parent7a74a13ba57ea34d7d45919d2e1aece4d902fd77 (diff)
downloaddotfiles-59b4f339126e1f6832bfc8bda66e8d2d70f48f31.tar.bz2
dotfiles-59b4f339126e1f6832bfc8bda66e8d2d70f48f31.tar.xz
dotfiles-59b4f339126e1f6832bfc8bda66e8d2d70f48f31.zip
Add Ubuntu kernel downloader
Diffstat (limited to 'bin')
-rwxr-xr-xbin/get-ubuntu-mainline-kernel.py231
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"""
3Ubuntu Mainline Kernel Downloader
4Author: Mike Crute <mike[at]crute[dot]us>
5
6This script parses the Ubuntu mainline kernel build page, determines the most
7recent kernel version (skipping rc and suffixed kernels) and downloads the
8appropriate (non-realtime) Debian packages for installation.
9
10The architecture defaults to amd64 and can be customized at the bottom of the
11script.
12
13After running this, run `sudo dpkg -i *.deb` to install the latest kernel
14packages.
15"""
16
17import re
18import os.path
19import logging
20import html.parser
21import urllib.parse
22import urllib.request
23
24
25class 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
94class 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
121class 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
137class 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
168class 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
195class 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
230if __name__ == "__main__":
231 KernelVersionFetcher("amd64").fetch_packages()