|
#!/usr/bin/env python3 |
|
|
|
import os |
|
import sys |
|
import subprocess |
|
import requests |
|
import json |
|
import re |
|
import datetime |
|
|
|
ua = 'Update Checker for Mattermost' |
|
url_base = 'https://raw.githubusercontent.com/mattermost/docs/refs/heads/master/source/product-overview/' |
|
url_support = url_base + 'mattermost-server-releases.md' |
|
url_index = url_base + 'version-archive.rst' |
|
|
|
notify_channel = os.getenv('MM_CHANNEL', '') |
|
notify_url = os.getenv('MM_HOOK', '') |
|
esr = bool(re.match(r'(?:1|t|y)', os.getenv('MM_ESR', ''))) |
|
user = os.getenv('MM_USER', 'mattermost') |
|
mm = os.getenv('MM_BIN', '/opt/mattermost/bin/mattermost') |
|
|
|
sys.stderr.write('Checking for new version…') |
|
|
|
def vuple(version: str): |
|
return tuple(map(int, version.split('.'))) |
|
|
|
def get_current(): |
|
r = subprocess.run( |
|
['sudo', '-u', user, mm, 'version'], |
|
capture_output=True, |
|
text=True, |
|
check=True, |
|
) |
|
values = {} |
|
for line in r.stdout.splitlines(): |
|
kv = re.split(r':\s+', line, maxsplit=2) |
|
if len(kv) == 2: |
|
values[kv[0]] = kv[1] |
|
if not values: |
|
raise Exception('No version values found.') |
|
return values |
|
|
|
def rload(url: str): |
|
r = requests.get( |
|
url, |
|
cookies=rload.cookies, |
|
headers={ |
|
'User-Agent': ua, |
|
}, |
|
timeout=7, |
|
) |
|
if r.status_code != 200: |
|
raise Exception('Remote file fetch failed.') |
|
rload.cookies = r.cookies |
|
return r.text |
|
rload.cookies = {} |
|
|
|
def get_support(): |
|
text = re.search(r'^#+\s*Latest\b.*', rload(url_support), re.M | re.S) |
|
if not text: |
|
raise Exception('Remote support file changed format.') |
|
values = {} |
|
for line in text[0].splitlines(): |
|
version = re.match(r'\s*\|\s*v(?P<v>\d+\.\d+)\b.+?\|\s*(?P<date>\d+(?:-\d+){2})\b[^|]*\|\s*$', line) |
|
if version: |
|
values[version['v']] = version['date'] |
|
if not values: |
|
raise Exception('No support dates found.') |
|
return values |
|
|
|
def get_index(ent: bool, current_minor: str, current_minor_tup: tuple): |
|
if ent: |
|
pat_ed = 'Enterprise' |
|
else: |
|
pat_ed = 'Team' |
|
pat_ed = r'^[ \t]+Mattermost ' + pat_ed + r' Edition v' |
|
text = re.search(pat_ed + '.*', rload(url_index), re.M | re.S) |
|
except_format = 'Remote archive index file changed format.' |
|
if not text: |
|
raise Exception(except_format) |
|
values = {} |
|
value = None |
|
for line in text[0].splitlines(): |
|
version = re.match(pat_ed + r'(?P<v>\d+(?:\.\d+){2})\s+(?:.+?(?P<esr>\bESR\b))?.+?`Download\s+<(?P<archive_download>.+?)>`', line) |
|
if version: |
|
if ( |
|
value and |
|
( |
|
not esr or |
|
value['esr'] or |
|
current_minor == value['minor'] |
|
) |
|
): |
|
values[value['minor']] = value |
|
value = version.groupdict() |
|
value['minor'] = re.match(r'\d+\.\d+', value['v'])[0] |
|
if current_minor_tup > vuple(value['minor']): |
|
value = None |
|
break |
|
value['esr'] = bool(value['esr']) |
|
elif not value: |
|
raise Exception(except_format) |
|
else: |
|
detail = re.match(r'\s+-\s*(?:(?P<label>\S+(?:\s+\S+)*):\s+)?((?:``)?)(?P<value>.+)\2$', line) |
|
if not detail: |
|
raise Exception(except_format) |
|
detail = detail.groupdict('archive') |
|
value[detail['label']] = detail['value'] |
|
if ( |
|
value and |
|
( |
|
not esr or |
|
value['esr'] or |
|
current_minor == value['minor'] |
|
) |
|
): |
|
values[value['minor']] = value |
|
if not values: |
|
raise Exception('No versions found.') |
|
return values |
|
|
|
current = get_current() |
|
if current.get('Version') is None: |
|
raise Exception('Unexpected output from local version check.') |
|
current_minor = re.match(r'\d+\.\d+', current['Version']) |
|
if not current_minor: |
|
raise Exception('Unexpected format from local version check.') |
|
current_minor = current_minor[0] |
|
current_minor_tup = vuple(current_minor) |
|
messages = [] |
|
support = get_support() |
|
message_eol = ':warning: Your Mattermost version has reached end of life. Upgrade to a supported version.' |
|
if support.get(current_minor) is None: |
|
if current_minor_tup < vuple(list(support.keys())[-1]): |
|
messages.append(message_eol) |
|
else: |
|
messages.append(':interrobang: Support for your Mattermost version is indeterminate.') |
|
elif support.get(current_minor) < datetime.datetime.now().strftime('%F'): |
|
messages.append(message_eol) |
|
index = get_index( |
|
current.get('Build Enterprise Ready', 'true') != 'false', |
|
current_minor, |
|
current_minor_tup, |
|
) |
|
details_minor = index.get(current_minor) |
|
if not details_minor: |
|
messages.append(':interrobang: Your Mattermost version was not found in the archive index.') |
|
elif (current_tup := vuple(current['Version'])) >= (patch_tup := vuple(details_minor['v'])): |
|
if current_tup != patch_tup: |
|
messages.append(':interrobang: Your Mattermost version is newer than expected.') |
|
details_minor = None |
|
del index[current_minor] |
|
if index: |
|
if not notify_url: |
|
sys.stderr.write(' done.\nUpgrades available: v%s\n' % ( |
|
', v'.join([index[minor]['v'] for minor in index]), |
|
)) |
|
sys.exit(1) |
|
messages.extend([ |
|
':information_source: Mattermost upgrade%s available:' % ( |
|
's' if len(index) > 1 else '', |
|
), |
|
'', |
|
'|Version|ESR|Checksum|', |
|
'|---:|---|---|', |
|
]) |
|
for minor in index: |
|
details = index[minor] |
|
messages.append('|[v%s](%s)|%s|`%s`|' % ( |
|
details['v'], |
|
details['archive_download'], |
|
':white_check_mark:' if details['esr'] else '', |
|
details.get('SHA-256 Checksum', '???'), |
|
)) |
|
body = { |
|
'text': '\n'.join(messages), |
|
} |
|
if notify_channel: |
|
body['channel'] = notify_channel |
|
r = requests.post(notify_url, json=body) |
|
r.raise_for_status() |
|
sys.stderr.write(' done.\n') |
|
else: |
|
sys.stderr.write(' none.\n') |