#!/usr/bin/env python """ Script to insert jfniki releases into Freenet. Copyright (C) 2011 Darrell Karbott This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks """ # This script isn't really for public consumption. # It is brittle and not fully debugged. # It assumes you have hg infocalypse installed and configured. # # POSSIBLE BUG: ANONYMITY ISSUE: This script may leak the *nix user id into the .jar file. # Need to audit. # DO NOT RUN IT if this concerns you. # import os import shutil import subprocess from binascii import hexlify from mercurial import ui, hg, commands from insert_files import insert_files from minimalfms import get_connection, send_msgs ############################################################ # For testing this script. #JUST_STAGE = True JUST_STAGE = False # CAUTION: This directory is recursively deleted! STAGING_DIR = '/tmp/staging' FCP_HOST = '127.0.0.1' FCP_PORT = 19481 FMS_HOST = '127.0.0.1' FMS_PORT = 11119 FMS_ID = 'djk' FMS_GROUP = 'sites' # REQUIRES: must match name in freesite.cfg. LATER: fix. SITE_NAME = 'jfniki_releases' PUBLIC_SITE = "USK@kRM~jJVREwnN2qnA8R0Vt8HmpfRzBZ0j4rHC2cQ-0hw," + \ "2xcoQVdQLyqfTpF2DpkdUIbHFCeL4W~2X1phUYymnhM,AQACAAE/%s/%%d/" % \ SITE_NAME ############################################################ # Indexes of referenced USK sites FREENET_DOC_WIKI_IDX = 56 FNIKI_IDX = 85 REPO_IDX = 19 ############################################################ THIS_FILES_DIR = os.path.abspath(os.path.dirname(__file__)) REPO_DIR = os.path.abspath(os.path.join(THIS_FILES_DIR, '..')) FREENET_JAR = os.path.abspath(os.path.join(THIS_FILES_DIR, '../alien/libs/freenet.jar')) RELEASE_NOTES = os.path.abspath(os.path.join(THIS_FILES_DIR, '../doc/latest_release.txt')) INDEX_HTML = os.path.abspath(os.path.join(THIS_FILES_DIR, 'generated_freesite/index.html')) INDEX_HTML_TEMPLATE = os.path.abspath(os.path.join(THIS_FILES_DIR, 'index_template.html')) FMS_MESSAGE_TEMPLATE = os.path.abspath(os.path.join(THIS_FILES_DIR, 'fms_message_template.txt')) ############################################################ def zip_source(staging_dir, source_dir, zip_file_name): subprocess.check_call(['/usr/bin/zip', '-r', '-9', # best compression zip_file_name, source_dir], # LATER: Better way to supress full path in zip? cwd=staging_dir) def stage_release(): # LATER: check for uncommitted changes ui_ = ui.ui() repo = hg.repository(ui_, REPO_DIR) # Get current head. heads = [hexlify(repo[head].node())[:12] for head in repo.heads()] assert len(heads) == 1 # Don't try to handle multiple heads head = heads[0] jar_name = "jfniki.%s.jar" % head zip_name = "jfniki.%s.zip" % head export_dir_name = "jfniki.%s" % head zip_file_name = "%s/%s" % (STAGING_DIR, zip_name) jar_file_name = "%s/%s" % (STAGING_DIR, jar_name) # scrub staging directory try: shutil.rmtree(STAGING_DIR) except: pass os.makedirs(STAGING_DIR) # dump clean source to staging dest = "%s/%s" % (STAGING_DIR, export_dir_name) # TRICKY: Had to put a print in the implementation of command.archive to figure # out required default opts. # {'rev': '', 'no_decode': None, 'prefix': '', 'exclude': [], 'include': [], 'type': ''} commands.archive(ui_, repo, dest, rev='', no_decode=None, prefix='', exclude=[], include=[], type='') # remove origin tarballs to save space shutil.rmtree("%s/alien/origins/" % dest) # zip up the source. zip_source(STAGING_DIR, export_dir_name, zip_file_name) # cp freenet.jar required for build os.makedirs("%s/%s/%s" % (STAGING_DIR, export_dir_name, "alien/libs")) shutil.copyfile(FREENET_JAR, "%s/%s/%s" % (STAGING_DIR, export_dir_name, "alien/libs/freenet.jar")) # build jar result = subprocess.check_call(["/usr/bin/ant", "-buildfile", "%s/%s/build.xml" % (STAGING_DIR, export_dir_name)]) print "ant result code: %d" % result # copy jar with name including the hg rev. shutil.copyfile("%s/%s/%s" % (STAGING_DIR, export_dir_name, "build/jar/jfniki.jar"), jar_file_name) print print "SUCCESSFULLY STAGED:" print jar_file_name print zip_file_name print return (head, jar_file_name, zip_file_name) def simple_templating(text, substitutions): for variable in substitutions: text = text.replace(variable, str(substitutions[variable])) assert text.find("__") == -1 # Catch unresolved variables return text def latest_site_index(repo): # C&P: wikibot.py """ Read the latest known freesite index out of the hg changelog. """ for tag, dummy in reversed(repo.tagslist()): if tag.startswith('I_'): return int(tag.split('_')[1]) return -1 def tag_site_index(ui_, repo, index=None): # C&P: wikibot.py """ Tag the local repository with a freesite index. """ if index is None: index = latest_site_index(repo) + 1 commands.tag(ui_, repo, 'I_%i' % index) ############################################################ # ATTRIBUTION: # http://wiki.python.org/moin/EscapingHtml HTML_ESCAPE_TABLE = { "&": "&", '"': """, "'": "'", ">": ">", "<": "<"} def html_escape(text): """Produce entities within text.""" return "".join(HTML_ESCAPE_TABLE.get(c,c) for c in text) ############################################################ def update_html(head, jar_chk, zip_chk): ui_ = ui.ui() repo = hg.repository(ui_, REPO_DIR) site_usk = PUBLIC_SITE % (latest_site_index(repo) + 1) html = simple_templating(open(INDEX_HTML_TEMPLATE).read(), {'__HEAD__':head, '__JAR_CHK__': jar_chk, '__SRC_CHK__': zip_chk, '__RELEASE_NOTES__' : html_escape(open(RELEASE_NOTES).read()), '__SITE_USK__': site_usk, '__INDEX_FDW__': FREENET_DOC_WIKI_IDX, '__INDEX_FNIKI__': FNIKI_IDX, '__INDEX_REPO__': REPO_IDX, }) updated = open(INDEX_HTML, 'w') updated.write(html) updated.close() commit_msg = "index.html:%s" % head commands.commit(ui_, repo, pat = (INDEX_HTML, ), include = [], addremove = None, close_branch = None, user = '', date = '', exclude = [], logfile = '', message = commit_msg) tag_site_index(ui_, repo) # Insert the latest tagged freesite version into freenet. # NOTE: depends on state set in freesite.cfg # REQUIRES: 'I_<num>' tag exists in the repo. # REQUIRES: hg infocalypse is installed and configured. def insert_freesite(): print REPO_DIR ui_ = ui.ui() repo = hg.repository(ui_, REPO_DIR) target_index = latest_site_index(repo) assert target_index >= 0 # BUG: There are case when this can fail with error code == 0 # e.g. when private key can't be read. # DCI: Test. does fn-putsite set error code on failure? subprocess.check_call(["/usr/bin/hg", "-R", REPO_DIR, "fn-putsite", "--index", str(target_index)]) # LATER: Do better. Parse request URI from output. return PUBLIC_SITE % target_index, target_index def send_fms_notification(site_uri, target_index, head, jar_chk, zip_chk): connection = get_connection(FMS_HOST, FMS_PORT, FMS_ID) msg = simple_templating(open(FMS_MESSAGE_TEMPLATE).read(), {'__HEAD__':head, '__JAR_CHK__': jar_chk, '__SRC_CHK__': zip_chk, '__SITE_USK__' : site_uri, '__RELEASE_NOTES__' : open(RELEASE_NOTES).read(), }) send_msgs(connection, ((FMS_ID, FMS_GROUP, "jfniki releases #%d" % target_index, msg),)) print "Sent FMS notification to: %s" % FMS_GROUP def release(): print print "If you haven't read the warning about ANONYMITY ISSUES" print "with this script, now might be a good time to hit Ctrl-C." print print print "RELEASE NOTES:" print open(RELEASE_NOTES).read() print print "------------------------------------------------------------" head, jar_file, zip_file = stage_release() if JUST_STAGE: return jar_chk, zip_chk = insert_files(FCP_HOST, FCP_PORT, [jar_file, zip_file]) update_html(head, jar_chk, zip_chk) site_uri, target_index = insert_freesite() send_fms_notification(site_uri, target_index, head, jar_chk, zip_chk) print print "Success!" if __name__ == "__main__": release()