2021-11-23 06:09:18 +00:00
|
|
|
# vim: ts=4 et:
|
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
import json
|
2021-11-23 06:09:18 +00:00
|
|
|
import re
|
2021-11-30 16:11:32 +00:00
|
|
|
from datetime import datetime, timedelta
|
2021-11-23 06:09:18 +00:00
|
|
|
from urllib.request import urlopen
|
|
|
|
|
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
class Alpine():
|
2021-11-23 06:09:18 +00:00
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
DEFAULT_RELEASES_URL = 'https://alpinelinux.org/releases.json'
|
|
|
|
DEFAULT_CDN_URL = 'https://dl-cdn.alpinelinux.org/alpine'
|
2022-01-30 19:18:09 +00:00
|
|
|
DEFAULT_WEB_TIMEOUT = 5
|
2021-11-23 06:09:18 +00:00
|
|
|
|
2022-01-30 19:18:09 +00:00
|
|
|
def __init__(self, releases_url=None, cdn_url=None, web_timeout=None):
|
2021-11-30 16:11:32 +00:00
|
|
|
self.now = datetime.utcnow()
|
|
|
|
self.release_today = self.now.strftime('%Y%m%d')
|
|
|
|
self.eol_tomorrow = (self.now + timedelta(days=1)).strftime('%F')
|
|
|
|
self.latest = None
|
|
|
|
self.versions = {}
|
|
|
|
self.releases_url = releases_url or self.DEFAULT_RELEASES_URL
|
2022-01-30 19:18:09 +00:00
|
|
|
self.web_timeout = web_timeout or self.DEFAULT_WEB_TIMEOUT
|
2021-11-30 16:11:32 +00:00
|
|
|
self.cdn_url = cdn_url or self.DEFAULT_CDN_URL
|
2021-11-28 23:04:28 +00:00
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
# get all Alpine versions, and their EOL and latest release
|
2022-01-30 19:18:09 +00:00
|
|
|
res = urlopen(self.releases_url, timeout=self.web_timeout)
|
2021-11-30 16:11:32 +00:00
|
|
|
r = json.load(res)
|
|
|
|
branches = sorted(
|
|
|
|
r['release_branches'], reverse=True,
|
|
|
|
key=lambda x: x.get('branch_date', '0000-00-00')
|
|
|
|
)
|
|
|
|
for b in branches:
|
|
|
|
ver = b['rel_branch'].lstrip('v')
|
|
|
|
if not self.latest:
|
|
|
|
self.latest = ver
|
2021-11-23 06:09:18 +00:00
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
rel = None
|
|
|
|
if releases := b.get('releases', None):
|
|
|
|
rel = sorted(
|
|
|
|
releases, reverse=True, key=lambda x: x['date']
|
|
|
|
)[0]['version']
|
|
|
|
elif ver == 'edge':
|
|
|
|
# edge "releases" is today's YYYYMMDD
|
|
|
|
rel = self.release_today
|
2021-11-23 06:09:18 +00:00
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
self.versions[ver] = {
|
|
|
|
'version': ver,
|
|
|
|
'release': rel,
|
|
|
|
'end_of_life': b.get('eol_date', self.eol_tomorrow),
|
|
|
|
'arches': b.get('arches'),
|
|
|
|
}
|
2021-11-23 06:09:18 +00:00
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
def _ver(self, ver=None):
|
|
|
|
if not ver or ver == 'latest' or ver == 'latest-stable':
|
|
|
|
ver = self.latest
|
2021-11-23 06:09:18 +00:00
|
|
|
|
2021-11-30 16:11:32 +00:00
|
|
|
return ver
|
|
|
|
|
|
|
|
def repo_url(self, repo, arch, ver=None):
|
|
|
|
ver = self._ver(ver)
|
|
|
|
if ver != 'edge':
|
|
|
|
ver = 'v' + ver
|
|
|
|
|
|
|
|
return f"{self.cdn_url}/{ver}/{repo}/{arch}"
|
|
|
|
|
|
|
|
def virt_iso_url(self, arch, ver=None):
|
|
|
|
ver = self._ver(ver)
|
|
|
|
rel = self.versions[ver]['release']
|
|
|
|
return f"{self.cdn_url}/v{ver}/releases/{arch}/alpine-virt-{rel}-{arch}.iso"
|
|
|
|
|
|
|
|
def version_info(self, ver=None):
|
|
|
|
ver = self._ver(ver)
|
|
|
|
if ver not in self.versions:
|
|
|
|
# perhaps a release candidate?
|
|
|
|
apk_ver = self.apk_version('main', 'x86_64', 'alpine-base', ver=ver)
|
|
|
|
rel = apk_ver.split('-')[0]
|
|
|
|
ver = '.'.join(rel.split('.')[:2])
|
|
|
|
self.versions[ver] = {
|
|
|
|
'version': ver,
|
|
|
|
'release': rel,
|
|
|
|
'end_of_life': self.eol_tomorrow,
|
|
|
|
'arches': self.versions['edge']['arches'], # reasonable assumption
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.versions[ver]
|
|
|
|
|
|
|
|
# TODO? maybe implement apk_info() to read from APKINDEX, but for now
|
|
|
|
# this apk_version() seems faster and gets what we need
|
|
|
|
|
|
|
|
def apk_version(self, repo, arch, apk, ver=None):
|
|
|
|
ver = self._ver(ver)
|
|
|
|
repo_url = self.repo_url(repo, arch, ver=ver)
|
|
|
|
apks_re = re.compile(f'"{apk}-(\\d.*)\\.apk"')
|
2022-01-30 19:18:09 +00:00
|
|
|
res = urlopen(repo_url, timeout=self.web_timeout)
|
2021-11-30 16:11:32 +00:00
|
|
|
for line in map(lambda x: x.decode('utf8'), res):
|
|
|
|
if not line.startswith('<a href="'):
|
|
|
|
continue
|
|
|
|
|
|
|
|
match = apks_re.search(line)
|
|
|
|
if match:
|
|
|
|
return match.group(1)
|
|
|
|
|
|
|
|
# didn't find it?
|
|
|
|
raise RuntimeError(f"Unable to find {apk} APK via {repo_url}")
|