Author: gstein
Date: Tue Jun 11 21:17:44 2013
New Revision: 1491966

URL: http://svn.apache.org/r1491966
Log:
Begin conversion of the make_issue.pl script, over to Python.

This isn't 100% done, but most of the way. This commit enables the
team to review the current direction.

The steve.conf file doesn't belong here (maybe in trunk/conf/ and as
steve.conf.example). But it has to go somewhere for now/testing.

* cmdline/make_issue.py: new script, ported from make_issue.pl

* cmdline/steve.conf: temporary config file

Added:
    steve/trunk/cmdline/make_issue.py   (with props)
    steve/trunk/cmdline/steve.conf   (with props)

Added: steve/trunk/cmdline/make_issue.py
URL: 
http://svn.apache.org/viewvc/steve/trunk/cmdline/make_issue.py?rev=1491966&view=auto
==============================================================================
--- steve/trunk/cmdline/make_issue.py (added)
+++ steve/trunk/cmdline/make_issue.py Tue Jun 11 21:17:44 2013
@@ -0,0 +1,307 @@
+#!/usr/bin/python
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+# make_issue
+#
+# A program for creating issues to be voted upon by the named "group"
+#
+# o  must be run by voter user (see wrapsuid.c for setuid wrapper)
+#
+# o  creates an issue directory (/home/voter/issues/group/YYYYMMDD-name/),
+#    and fills it with files for the issue info, election monitors,
+#    and tally file;
+#
+# o  creates tables of hash-ids and hashed-hash-ids for use in validating
+#    votes, verifying that the values are unique;
+#
+# o  mails to the vote monitors an alert message to start tallying;
+#
+# o  mails to each voter their hash-id to be used, issue number, and
+#    some text explaining the issue.
+#
+
+import sys
+import os
+import argparse
+import re
+import shutil
+
+### how do we want to "properly" adjust path?
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 
'../lib')))
+import steve
+
+
+def main():
+  args = parse_argv()
+
+  ### where should we look for this config file?
+  config = steve.load_config('steve.conf')
+
+  # Expand the set of arguments (as a side-effect), if they were not provided
+  # on the cmdline.
+  augment_args(args, config)
+
+  issue_name = '%s-%s-%s' % (args.group, args.start, args.issue)
+
+  # Note: config.issue_dir updated as a side-effect
+  voters = get_voters(args, config)
+
+  # Note: config.issue_dir updated as a side-effect
+  create_issue_dir(issue_name, args, config)
+
+  info_fname = create_info_file(args, config)
+  monitors_hash, hash = build_hash(info_fname, voters, args)
+
+  monitors_fname = create_monitors_file(args, config)
+  type_fname = create_type_file(info_fname, args, config)
+  verify_email(info_fname, args, config)
+  verify_voters(voters, args, config)
+
+  email_monitors()
+  email_voters()
+
+  print "Issue %s with hashcode of %s\nhas been successfully created." \
+        % (issue_name, monitors_hash)
+
+
+def parse_argv():
+  parser = argparse.ArgumentParser(
+             prog=steve.PROG,
+             description='Make an issue for managing an on-line, '
+                         'anonymous voting process',
+             )
+  parser.add_argument('-b', '--batch', action='store_true',
+                      help='batch processing: assume "ok" unless errors')
+  parser.add_argument('-g', '--group',
+                      help='create an issue for an existing group of voters')
+  parser.add_argument('-s', '--start',
+                      help='YYYYMMDD format of date that voting is allowed to 
start')
+  parser.add_argument('-i', '--issue',
+                      help='append this alphanumeric string to start date '
+                           'as issue name')
+  parser.add_argument('-f', '--file',
+                      help='send the contents of this file to each voter '
+                           'as explanation')
+  parser.add_argument('-m', '--monitors',
+                      help='e-mail address(es) for sending mail to vote 
monitor(s)')
+  parser.add_argument('-v', '--votetype',
+                      help='type of vote: yna = Yes/No/Abstain, '
+                           'stvN = single transferable vote for [1-9] slots, '
+                           'selectN = vote for [1-9] of the candidates')
+
+  return parser.parse_args()
+
+
+def augment_args(args, config):
+  "Update ARGS in-place with missing values."
+
+  if args.group is None:
+    args.group = steve.get_input_line('group name for voters on this issue', 
True)
+  if not re.match(r'^\w+$', args.group):
+    die('group name must be an alphanumeric token')
+
+  if args.start is None:
+    args.start = steve.get_input_line('YYYYMMDD date that voting starts', True)
+  if not re.match(r'^[2-9]\d\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])$',
+                  args.start):
+    die('start date must be formatted as YYYYMMDD, like 20090930')
+
+  if args.issue is None:
+    args.issue = steve.get_input_line('short issue name to append to date', 
True)
+  if not re.match(r'^\w+$', args.issue):
+    die('issue name must be an alphanumeric token')
+
+  if args.file is None:
+    args.file = steve.get_input_line('file pathname of issue info on %s'
+                                     % (config.hostname,),
+                                     True)
+  args.file = os.path.realpath(args.file)
+  if not os.path.exists(args.file):
+    die('info file does not exist: %s', args.file)
+  if args.file.startswith('/etc/'):
+    die('forbidden to read info files from: /etc')
+  issue_dir = os.path.realpath(config.issue_dir)
+  if args.file.startswith(issue_dir + '/'):
+    die('forbidden to read info files from: %s', issue_dir)
+
+  if args.monitors is None:
+    args.monitors = steve.get_input_line('e-mail address(es) for vote 
monitors', True)
+  if '@' not in args.monitors:
+    die('vote monitor must be an Internet e-mail address')
+
+  if args.votetype is None:
+    args.votetype = steve.get_input_line('vote type; yna, stvN, or selectN 
(N=1-9)',
+                                         True)
+  args.votetype = args.votetype.lower()
+  if args.votetype == 'yna':
+    args.selector = 0
+    args.style = 'yes, no, or abstain'
+  elif args.votetype.startswith('stv') and args.votetype[3:].isdigit():
+    args.selector = int(args.votetype[3:])
+    args.style = 'single transferable vote for %d slots' % (args.selector,)
+  elif args.votetype.startswith('select') and args.votetype[6:].isdigit():
+    args.selector = int(args.votetype[6:])
+    args.style = 'select %d of the candidates labeled [a-z0-9]' % 
(args.selector,)
+  else:
+    die('vote type must be yna, stvN, or selectN (N=[1-9])')
+
+
+def get_voters(args, config):
+  if not os.path.isdir(config.issue_dir):
+    die('cannot find: %s', config.issue_dir)
+
+  config.issue_dir += '/' + args.group
+  if not os.path.isdir(config.issue_dir):
+    die('group "%s" has not been created yet, see votegroup', args.group)
+  if not os.access(config.issue_dir, os.R_OK | os.W_OK | os.X_OK):
+    die('you lack permissions on: %s', config.issue_dir)
+  uid = os.stat(config.issue_dir).st_uid
+  if uid != os.geteuid():
+    die('you are not the effective owner of: %s', config.issue_dir)
+
+  voters = steve.get_group(config.issue_dir + '/voters')
+  if not voters:
+    die('"%s" must be an existing voter group: see votegroup', args.group)
+
+  return voters
+
+
+def create_issue_dir(issue_name, args, config):
+  print 'Creating new issue:', issue_name
+
+  # Note that .issue_dir already has the group.
+  config.issue_dir += '/%s-%s' % (args.start, args.issue)
+  if os.path.exists(config.issue_dir):
+    die('already exists: %s', config.issue_dir)
+  os.mkdir(config.issue_dir, 0700)
+
+
+def create_info_file(args, config):
+  info_fname = config.issue_dir + '/issue'
+
+  ### generate the contents
+  contents = '### contents\n\n'
+  info = open(args.file).read()
+
+  open(info_fname, 'w').write(contents + info)
+
+  return info_fname
+
+
+def build_hash(info_fname, voters, args):
+  while True:
+    issue_id = steve.filestuff(info_fname)
+    monitors_hash = steve.get_hash_of('%s:%s' % (issue_id, args.monitors))
+
+    hash = { }
+    for voter in voters:
+      h1 = steve.get_hash_of('%s:%s' % (issue_id, voter))
+      h2 = steve.get_hash_of('%s:%s' % (issue_id, h1))
+
+      hash[voter] = (h1, h2)
+      hash[h1] = voter
+      hash[h2] = voter
+
+    if len(hash) == 3 * len(voters):
+      return monitors_hash, hash
+
+    _ = steve.get_input_line('anything to retry collision-free hash', True)
+    open(info_fname, 'a').write('')
+
+
+def create_monitors_file(args, config):
+  monitors_fname = config.issue_dir + '/monitors'
+
+  open(monitors_fname, 'w').write(args.monitors + '\n')
+
+  return monitors_fname
+
+
+def create_type_file(info_fname, args, config):
+  type_fname = config.issue_dir + '/vote_type'
+
+  f = open(type_fname, 'w')
+  f.write('%s\n' % (args.votetype,))
+
+  if args.selector == 0:
+    ballots = ('yes', 'no', 'abstain')
+  else:
+    ballots = steve.ballots(open(info_fname).readlines())
+
+  f.writelines(option + '\n' for option in ballots)
+
+  return type_fname
+
+
+def verify_email(info_fname, args, config):
+  print 'Here is the issue information to be sent to each voter:'
+
+  contents = open(info_fname).read() + _explain_vote('unique-hash-key')
+  _basic_verify(contents, args, config)
+
+
+def verify_voters(voters, args, config):
+  print 'Here is the list of voter e-mail addresses:'
+
+  contents = '\n'.join(voters) + '\n'
+  _basic_verify(contents, args, config)
+
+
+def _basic_verify(contents, args, config):
+  print '=============================================================='
+  print contents
+  print '=============================================================='
+
+  # No need to stop and verify.
+  if args.batch:
+    return
+
+  while True:
+    answer = steve.get_input_line('"ok" to accept, or "abort" to delete 
issue', False)
+    answer = answer.lower()
+    if answer == 'abort':
+      shutil.rmtree(config.issue_dir)
+      sys.exit(1)
+    if answer == 'ok':
+      return
+
+
+def email_monitors():
+  pass
+
+
+def email_voters():
+  pass
+
+
+def _explain_vote(key):
+  return '### explain vote for: %s\n' % (key,)
+
+
+### keep this? not sure that exceptions would be helpful since there is likely
+### no intent to trap them.
+def die(msg, *args):
+  "Print an error message and exit with failure."
+
+  print '%s: %s' % (steve.PROG, msg % args)
+  sys.exit(1)
+
+
+if __name__ == '__main__':
+  os.umask(0077)
+  main()

Propchange: steve/trunk/cmdline/make_issue.py
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: steve/trunk/cmdline/make_issue.py
------------------------------------------------------------------------------
    svn:executable = *

Added: steve/trunk/cmdline/steve.conf
URL: 
http://svn.apache.org/viewvc/steve/trunk/cmdline/steve.conf?rev=1491966&view=auto
==============================================================================
--- steve/trunk/cmdline/steve.conf (added)
+++ steve/trunk/cmdline/steve.conf Tue Jun 11 21:17:44 2013
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[general]
+UID_NAME = voter
+GID_NAME = voter
+HOME = /tmp
+ISSUE_DIR = %(HOME)s/issues
+HOSTNAME = people.apache.org
+EMAIL = [email protected]

Propchange: steve/trunk/cmdline/steve.conf
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to