Legoktm has uploaded a new change for review.
https://gerrit.wikimedia.org/r/189303
Change subject: [WIP] Initial commit
......................................................................
[WIP] Initial commit
Change-Id: I11e3ac7c3414009a6a4c9a4c97977597efd0e764
---
A NOTES
A app.py
A rebuild.py
A requirements.txt
A tardist.json
A tardist.py
A tox.ini
A worker.py
8 files changed, 315 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/tardist
refs/changes/03/189303/1
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..90dcadc
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,10 @@
+/ - tardist version info / link to docs?
+/update/extensions/MassMessage/master - queue to rebuild a tarball
+/list - returns a list of extensions that are supported
+/info/extensions/MassMessage - returns list of branches+links to tarballs, and
other metadata (license, description (localized?), etc...)
+/info/extensions - batch return of all extension info maybe?
+
+
+
+Need submodule domain whitelisting?
+
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..1583410
--- /dev/null
+++ b/app.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+from flask import Flask, Response, request
+import json
+
+from tardist import tardist
+
+__version__ = '0.0.1'
+app = Flask(__name__)
+
+
+def respond(status='ok', **kwargs) -> Response:
+ """
+
+ :param status: 'ok' or False
+ :type status: str|bool
+ :return:
+ """
+ if request.args.get('pretty'):
+ json_kwargs = {'indent': 4, 'separators': (',', ': '), 'sort_keys':
True}
+ else:
+ json_kwargs = {}
+ return Response(
+ json.dumps(
+ dict(_status=status or 'error', **kwargs),
+ **json_kwargs
+ ),
+ content_type='application/json'
+ )
+
+
[email protected]('/')
[email protected]('/version')
+def version():
+ return respond(version=__version__)
+
+
[email protected]('/update/<type_>/<extension>')
[email protected]('/update/<type_>/<extension>/<branch>')
+def update(type_, extension, branch='*'):
+ repo = '%s/%s' % (type_, extension)
+ if repo not in tardist.get_all_repos():
+ return respond(status=False, error='Invalid repo provided')
+
+ tardist.push_update(repo, branch)
+ return respond(update='queued', repo=repo, branch=branch)
+
+
[email protected]('/list')
[email protected]('/list/<type_>')
+def list_(type_=''):
+ if type_ not in ['extensions', 'skins', '']:
+ return respond(status=False, error='Invalid type provided')
+
+ return respond(repos=[repo for repo in tardist.get_all_repos() if
repo.startswith(type_)])
+
+if __name__ == '__main__':
+ app.run(debug=True)
diff --git a/rebuild.py b/rebuild.py
new file mode 100644
index 0000000..9977de8
--- /dev/null
+++ b/rebuild.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+import sys
+
+from tardist import tardist
+
+if len(sys.argv) > 1:
+ repos = sys.argv[1:]
+else:
+ repos = tardist.get_all_repos(cached=False)
+
+for repo in repos:
+ if len(repo.split('/')) > 2:
+ repo, branch = repo.rsplit('/', 1)
+ else:
+ branch = '*'
+ tardist.push_update(repo, branch)
+
+print('Queued %s updates.' % len(repos))
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..b8a0ee2
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+flask
+redis
+requests
diff --git a/tardist.json b/tardist.json
new file mode 100644
index 0000000..451e1dd
--- /dev/null
+++ b/tardist.json
@@ -0,0 +1,5 @@
+{
+ "GIT_URL": "https://gerrit.wikimedia.org/r/mediawiki/%s",
+ "SRC_PATH": "/home/km/projects/tardist/src",
+ "DIST_PATH": "/home/km/projects/tardist/dist"
+}
diff --git a/tardist.py b/tardist.py
new file mode 100644
index 0000000..b8b7a4d
--- /dev/null
+++ b/tardist.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python3
+
+import glob
+import json
+import logging
+import os
+import redis
+import requests
+import subprocess
+
+
+class cwd:
+ """
+ Context manager to easily change current directory
+ and then go back
+ """
+ def __init__(self, path):
+ self.path = path
+ self.cwd = ''
+
+ def __enter__(self):
+ self.cwd = os.getcwd()
+ os.chdir(self.path)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ os.chdir(self.cwd)
+
+
+class TarDist:
+ REPO_LIST_KEY = 'tardist-repo-list'
+ UPDATES_KEY = 'tardist-updates'
+
+ def __init__(self, conf):
+ self.conf = conf
+ self._redis = None
+ self._session = requests.Session()
+ self.logger = logging.getLogger(__file__)
+
+ @property
+ def redis(self) -> redis.Redis:
+ if not self._redis:
+ self._redis = redis.Redis()
+
+ return self._redis
+
+ def _make_gerrit_request(self, endpoint) -> dict:
+ """
+ Make a Gerrit API request
+
+ :param endpoint: API endpoint to hit: /projects/?...=...
+ :return:
+ """
+ url = 'https://gerrit.wikimedia.org/r' + endpoint
+ r = self._session.get(url)
+ if not r.ok:
+ self.logger.error('Gerrit request failed: %s: %s' % (endpoint,
r.status_code))
+ return {}
+ clean = r.text[4:]
+ return json.loads(clean)
+
+ def get_all_repos(self, cached=True) -> list:
+ data = False
+ if cached:
+ data = json.loads((self.redis.get(self.REPO_LIST_KEY) or
b'false').decode())
+ if not data:
+ data = sorted(list(self._get_all_repos()))
+ self.redis.set(self.REPO_LIST_KEY, json.dumps(data)) # Cache for an
hour
+ return data
+
+ def _get_all_repos(self) -> iter:
+ resp = self._make_gerrit_request('/projects/?p=mediawiki/')
+ for repo in resp:
+ if repo.startswith(('mediawiki/skins/', 'mediawiki/extensions/'))
and len(repo.split('/')) == 3:
+ yield repo[10:] # Trim "mediawiki/"
+
+ def shell_exec(self, args, **kwargs) -> str:
+ """
+ Shortcut wrapper to execute a shell command
+
+ >>> self.shell_exec(['ls', '-l'])
+ """
+ return subprocess.check_output(args, **kwargs).decode()
+
+ def push_update(self, repo, branch):
+ """
+ Queue an update for the repo on branch
+ :param repo: Full name of repo, "extensions/MassMessage"
+ :param branch: Branch name, "master". '*' means all branches
+ """
+ self.redis.sadd(self.UPDATES_KEY, repo + '/' + branch)
+
+ def get_update(self) -> dict:
+ """
+ Get a queued update to process
+
+ redis sets are not ordered, so a random entry
+ will be removed from the queue.
+ """
+ info = self.redis.spop(self.UPDATES_KEY)
+ if info:
+ split = info.decode().rsplit('/', 1)
+ return {'repo': split[0], 'branch': split[1]}
+ else:
+ return {}
+
+ def update_extension(self, repo, branch):
+ """
+ Fetch an extension's updates, and
+ create new tarballs if needed
+ """
+ print(repo, branch)
+ name = repo.split('/')[1] # Name without type prefix, "Vector"
+ full_path = os.path.join(self.conf['SRC_PATH'], repo)
+ self.logger.info('Starting update for %s' % repo)
+ if not os.path.exists(full_path):
+ with cwd(self.conf['SRC_PATH']):
+ self.logger.debug('Cloning %s' % repo)
+ self.shell_exec(['git', 'clone', self.conf['GIT_URL'] % repo,
repo])
+ os.chdir(full_path)
+ with cwd(full_path):
+ self.logger.info('Creating %s for %s' % (branch, repo))
+ # Update remotes
+ self.shell_exec(['git', 'fetch'])
+ try:
+ # Could fail if repo is empty
+ self.shell_exec(['git', 'reset', '--hard', 'origin/master'])
+ # Reset everything!
+ self.shell_exec(['git', 'clean', '-ffd'])
+ # Checkout the branch
+ self.shell_exec(['git', 'checkout', 'origin/%s' % branch])
+ except subprocess.CalledProcessError:
+ # Just a warning because this is expected for some extensions
+ self.logger.warning('could not checkout origin/%s' % branch)
+ return
+ # Reset everything, again.
+ self.shell_exec(['git', 'clean', '-ffd'])
+ # Sync submodules in case their urls have changed
+ self.shell_exec(['git', 'submodule', 'sync'])
+ # Update them, initializing new ones if needed
+ self.shell_exec(['git', 'submodule', 'update', '--init'])
+ # Gets short hash of HEAD
+ rev = self.shell_exec(['git', 'rev-parse', '--short',
'HEAD']).strip()
+ tarball_fname = '%s-%s-%s.tar.gz' % (name, branch, rev)
+ # Create a 'version' file with basic info about the tarball
+ with open('version', 'w') as f:
+ f.write('%s: %s\n' % (name, branch))
+ f.write(self.shell_exec(['date', '+%Y-%m-%dT%H:%M:%S']) +
'\n') # TODO: Do this in python
+ f.write(rev + '\n')
+ old_tarballs = glob.glob(os.path.join(self.conf['DIST_PATH'],
'%s-%s-*.tar.gz' % (name, branch)))
+ self.logger.debug('Deleting old tarballs...')
+ for old in old_tarballs:
+ # FIXME: Race condition, we should probably do this later on...
+ os.unlink(old)
+ with cwd(self.conf['SRC_PATH']):
+ # Finally, create the new tarball
+ with cwd(os.path.join(self.conf['SRC_PATH'], repo.rsplit('/',
1)[0])):
+ self.shell_exec(
+ ['tar', '--exclude', '.git', '-czPf',
os.path.join(self.conf['DIST_PATH'], tarball_fname), name]
+ )
+ self.logger.info('Finished update for %s' % repo)
+
+
+def _get_conf():
+ conf = False
+ for path in ['/etc/tardist.conf', os.path.join(os.path.dirname(__file__),
'tardist.json')]:
+ if os.path.exists(path):
+ with open(path) as f:
+ conf = json.load(f)
+
+ if conf is False:
+ print('tardist is not configured properly.')
+ quit()
+
+ return conf
+
+tardist = TarDist(_get_conf())
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..9a4162f
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,23 @@
+[tox]
+
+# Ensure 1.6+ is used to support 'skipsdist'
+minversion = 1.6
+
+# Do not run install command
+skipsdist = True
+
+# Environements to execute when invoking 'tox'
+envlist = flake8
+
+[testenv:flake8]
+commands = flake8
+deps = flake8
+base_python=python3.4
+
+[flake8]
+exclude = .tox
+max_line_length = 120
+
+[testenv:tests]
+commands = python nightly_test.py
+base_python=python3.4
diff --git a/worker.py b/worker.py
new file mode 100644
index 0000000..41f9962
--- /dev/null
+++ b/worker.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+import sys
+import time
+
+from tardist import tardist
+
+
+def main(*args):
+ while True:
+ update = tardist.get_update()
+ if update:
+ tardist.update_extension(**update)
+ elif '--continuous' in args:
+ time.sleep(5)
+ else:
+ break
+
+
+if __name__ == '__main__':
+ main(*sys.argv)
--
To view, visit https://gerrit.wikimedia.org/r/189303
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I11e3ac7c3414009a6a4c9a4c97977597efd0e764
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/services/tardist
Gerrit-Branch: master
Gerrit-Owner: Legoktm <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits