Added fn-putsite command to insert freesites.
diff --git a/infocalypse/__init__.py b/infocalypse/__init__.py --- a/infocalypse/__init__.py +++ b/infocalypse/__init__.py @@ -197,12 +197,52 @@ fms can have pretty high latency. Be pat take hours (sometimes a day!) for your notification to appear. Don't send lots of redundant notifications. +FREESITE INSERTION: +hg fn-putsite --index <n> + +inserts a freesite based on the configuration in +the freesite.cfg file in the root of the repository. + +Use: +hg fn-putsite --createconfig + +to create a basic freesite.cfg file that you +can modify. Look at the comments in it for an +explanation of the supported parameters. + +The default freesite.cfg file inserts using the +same private key as the repo and a site name +of 'default'. Editing the name is highly +recommended. + +You can use --key CHK@ to insert a test version of +the site to a CHK key before writing to the USK. + +Limitations: +o You MUST have fn-pushed the repo at least once + in order to insert using the repo's private key. + If you haven't fn-push'd you'll see this error: + "You don't have the insert URI for this repo. + Supply a private key with --key or fn-push the repo." +o Inserts *all* files in the site_dir directory in + the freesite.cfg file. Run with --dryrun to make + sure that you aren't going to insert stuff you don't + want too. +o You must manually specify the USK edition you want + to insert on. You will get a collision error + if you specify an index that was already inserted. +o Don't use this for big sites. It should be fine + for notes on your project. If you have lots of images + or big binary files use a tool like jSite instead. +o Don't modify site files while the fn-putsite is + running. + HINTS: The -q, -v and --debug verbosity options are supported. Top level URIs ending in '.R1' are inserted redundantly. -Don't use this if you are worried about correlation +Don't use this if you're worried about correlation attacks. If you see 'abort: Connection refused' when you run @@ -253,6 +293,8 @@ from infcmds import get_config_info, exe from fmscmds import execute_fmsread, execute_fmsnotify +from sitecmds import read_freesite_cfg, execute_putsite, execute_genkey + def set_target_version(ui_, repo, opts, params, msg_fmt): """ INTERNAL: Update TARGET_VERSION in params. """ @@ -437,6 +479,56 @@ def infocalypse_fmsnotify(ui_, repo, **o params['INSERT_URI'] = insert_uri execute_fmsnotify(ui_, repo, params, stored_cfg) +MSG_BAD_INDEX = 'You must set --index to a value >= 0.' +def infocalypse_putsite(ui_, repo, **opts): + """ Insert an update to a freesite. + """ + + if opts['createconfig']: + params = {'SITE_CREATE_CONFIG':True} + execute_putsite(ui_, repo, params) + return + + params, stored_cfg = get_config_info(ui_, opts) + if opts['key'] != '': # order important + params['SITE_KEY'] = opts['key'] + if not (params['SITE_KEY'].startswith('SSK') or + params['SITE_KEY'] == 'CHK@'): + raise util.Abort("--key must be a valid SSK " + + "insert key or CHK@.") + read_freesite_cfg(ui_, repo, params, stored_cfg) + + try: + # --index not required for CHK@ + if not params['SITE_KEY'].startswith('CHK'): + params['SITE_INDEX'] = int(opts['index']) + if params['SITE_INDEX'] < 0: + raise ValueError() + else: + params['SITE_INDEX'] = -1 + except ValueError: + raise util.Abort(MSG_BAD_INDEX) + except TypeError: + raise util.Abort(MSG_BAD_INDEX) + + params['DRYRUN'] = opts['dryrun'] + + if not params.get('SITE_KEY', None): + insert_uri = stored_cfg.get_dir_insert_uri(repo.root) + if not insert_uri: + ui_.warn("You don't have the insert URI for this repo.\n" + + "Supply a private key with --key or fn-push " + + "the repo.\n") + return # REDFLAG: hmmm... abort? + params['SITE_KEY'] = 'SSK' + insert_uri.split('/')[0][3:] + + execute_putsite(ui_, repo, params) + +def infocalypse_genkey(ui_, **opts): + """ Print a new SSK key pair. """ + params, dummy = get_config_info(ui_, opts) + execute_genkey(ui_, params) + def infocalypse_setup(ui_, **opts): """ Setup the extension for use for the first time. """ @@ -511,6 +603,18 @@ cmdtable = { + FCP_OPTS, # Needs to invert the insert uri "[options]"), + "fn-putsite": (infocalypse_putsite, + [('', 'dryrun', None, "don't insert site"), + ('', 'index', -1, "edition to insert"), + ('', 'createconfig', None, "create default freesite.cfg"), + ('', 'key', '', "private SSK to insert under"),] + + FCP_OPTS, + "[options]"), + + "fn-genkey": (infocalypse_genkey, + FCP_OPTS, + "[options]"), + "fn-setup": (infocalypse_setup, [('', 'tmpdir', '~/infocalypse_tmp', 'temp directory'),] + FCP_OPTS, @@ -519,4 +623,5 @@ cmdtable = { commands.norepo += ' fn-setup' +commands.norepo += ' fn-genkey' diff --git a/infocalypse/sitecmds.py b/infocalypse/sitecmds.py new file mode 100644 --- /dev/null +++ b/infocalypse/sitecmds.py @@ -0,0 +1,209 @@ +""" Implementation of commands to insert freesites. + + Copyright (C) 2009 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 +""" + + +import os + +from ConfigParser import ConfigParser + +from mercurial import util + +from fcpconnection import FCPError +from fcpclient import FCPClient, get_file_infos, set_index_file + +def write_default_config(ui_, repo): + """ Write a default freesite.cfg file into the repository root dir. """ + file_name = os.path.join(repo.root, 'freesite.cfg') + + if os.path.exists(file_name): + raise util.Abort("Already exists: %s" % file_name) + + out_file = open(file_name, 'w') + try: + out_file.write("""[default] +# Human readable site name. +site_name = default +# Directory to insert from relative to the repository root. +site_dir = site_root +# Optional external file to load the site key from, relative +# to the directory your .infocalypse/infocalypse.ini file +# is stored in. This file should contain ONLY the SSK insert +# key up to the first slash. +# +# If this value is not set the insert SSK for the repo is +# used. +#site_key_file = example_freesite_key.txt +# +# Optional file to display by default. If this is not +# set index.html is used. +#default_file = index.html +""") + finally: + out_file.close() + + ui_.status('Created config file:\n%s\n' % file_name) + ui_.status('You probably want to edit at least the site_name.\n') + +def read_freesite_cfg(ui_, repo, params, stored_cfg): + """ Read param out of the freesite.cfg file. """ + cfg_file = os.path.join(repo.root, 'freesite.cfg') + + ui_.status('Using config file:\n%s\n' % cfg_file) + if not os.path.exists(cfg_file): + ui_.warn("Can't read: %s\n" % cfg_file) + raise util.Abort("Use --createconfig to create freesite.cfg") + + parser = ConfigParser() + parser.read(cfg_file) + if not parser.has_section('default'): + raise util.Abort("Can't read default section of config file?") + + params['SITE_NAME'] = parser.get('default', 'site_name') + params['SITE_DIR'] = parser.get('default', 'site_dir') + if parser.has_option('default','default_file'): + params['SITE_DEFAULT_FILE'] = parser.get('default', 'default_file') + else: + params['SITE_DEFAULT_FILE'] = 'index.html' + + if params.get('SITE_KEY'): + return # key set on command line + + if not parser.has_option('default','site_key_file'): + params['SITE_KEY'] = '' + return # Will use the insert SSK for the repo. + + key_file = parser.get('default', 'site_key_file', 'default') + if key_file == 'default': + ui_.status('Using repo insert key as site key.\n') + params['SITE_KEY'] = 'default' + return # Use the insert SSK for the repo. + try: + # Read private key from specified key file relative + # to the directory the .infocalypse config file is stored in. + key_file = os.path.join(os.path.dirname(stored_cfg.file_name), + key_file) + ui_.status('Reading site key from:\n%s\n' % key_file) + params['SITE_KEY'] = open(key_file, 'rb').read().strip() + except IOError: + raise util.Abort("Couldn't read site key from: %s" % key_file) + + if not params['SITE_KEY'].startswith('SSK@'): + raise util.Abort("Stored site key not an SSK?") + +def get_insert_uri(params): + """ Helper function builds the insert URI. """ + if params['SITE_KEY'] == 'CHK@': + return 'CHK@/' + return '%s/%s-%i/' % (params['SITE_KEY'], + params['SITE_NAME'], params['SITE_INDEX']) + +# Convert SSK to USK so n00b5 don't phr34k out. +def show_request_uri(ui_, params, uri): + """ Helper function to print the request URI.""" + if uri.startswith('SSK@'): + request_uri = 'U%s/%s/%i/' % (uri.split('/')[0][1:], + params['SITE_NAME'], + params['SITE_INDEX']) + else: + request_uri = uri + ui_.status('RequestURI:\n%s\n' % request_uri) + +def execute_putsite(ui_, repo, params): + """ Run the putsite command. """ + def progress(dummy, msg): + """ Message callback which writes to the hg ui instance.""" + + if msg[0] == 'SimpleProgress': + ui_.status("Progress: (%s/%s/%s)\n" % (msg[1]['Succeeded'], + msg[1]['Required'], + msg[1]['Total'])) + else: + ui_.status("Progress: %s\n" % msg[0]) + + + if params.get('SITE_CREATE_CONFIG', False): + write_default_config(ui_, repo) + return + + # Remove trailing / + params['SITE_KEY'] = params['SITE_KEY'].split('/')[0].strip() + insert_uri = get_insert_uri(params) + site_root = os.path.join(repo.root, params['SITE_DIR']) + + ui_.status('Default file: %s\n' % params['SITE_DEFAULT_FILE']) + ui_.status('Reading files from:\n%s\n' % site_root) + + infos = get_file_infos(site_root) + + try: + set_index_file(infos, params['SITE_DEFAULT_FILE']) + except ValueError: + raise util.Abort("Couldn't read %s" % params['SITE_DEFAULT_FILE']) + + ui_.status('--- files ---\n') + + for info in infos: + ui_.status('%s %s\n' % (info[0], info[1])) + ui_.status('---\n') + + if params['DRYRUN']: + ui_.status('Would have inserted to:\n%s\n' % insert_uri) + ui_.status('But --dryrun was set.') + return + + client = FCPClient.connect(params['FCP_HOST'], + params['FCP_PORT']) + client.in_params.default_fcp_params['DontCompress'] = False + client.message_callback = progress + try: + ui_.status('Inserting to:\n%s\n' % insert_uri) + try: + request_uri = client.put_complex_dir(insert_uri, infos)[1]['URI'] + show_request_uri(ui_, params, request_uri) + except FCPError, err: + if err.is_code(9): # magick number for collision + ui_.warn('An update was already inserted on that index.\n' + + 'Set a later index with --index and try again.\n') + raise util.Abort("Key collision.") + else: + ui_.warn(str(err) + '\n') + raise util.Abort("FCP Error") + finally: + client.close() + +MSG_FMT = """InsertURI: +%s +RequestURI: +%s + +This is what you need to put in a site_key_file file: +%s +""" + +def execute_genkey(ui_, params): + """ Run the genkey command. """ + client = FCPClient.connect(params['FCP_HOST'], + params['FCP_PORT']) + + client.message_callback = lambda x, y : None # silence. + resp = client.generate_ssk() + ui_.status(MSG_FMT % (resp[1]['InsertURI'], resp[1]['RequestURI'], + resp[1]['InsertURI'].split('/')[0] +'/'))