You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@steve.apache.org by gs...@apache.org on 2013/06/11 23:17:44 UTC

svn commit: r1491966 - in /steve/trunk/cmdline: make_issue.py steve.conf

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 = voter@apache.org

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



Re: svn commit: r1491966 - in /steve/trunk/cmdline: make_issue.py steve.conf

Posted by Alan Cabrera <li...@toolazydogs.com>.
On Jun 11, 2013, at 3:35 PM, Greg Stein <gs...@gmail.com> wrote:

> On Tue, Jun 11, 2013 at 5:17 PM,  <gs...@apache.org> wrote:
>> 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
> 
> The goal here is to directly port each of the scripts, in
> approximately their current form and function. Future rounds can clean
> and reorganize. Alan said much the same on IRC, and I (obviously)
> concur with his thought.
> 
> Please take a look at the direction and provide any feedback.
> 
> (I know there are better ways to do some of this; I'm just muscling
> through right now; some will be also be refactored due to commonality
> with other scripts)

Thanks for checking this in.  It really clears things up for me as to how you envisioned organizing stuff.  I'll change votegroup.py to follow suit.


Regards,
Alan


Re: svn commit: r1491966 - in /steve/trunk/cmdline: make_issue.py steve.conf

Posted by Greg Stein <gs...@gmail.com>.
On Tue, Jun 11, 2013 at 5:17 PM,  <gs...@apache.org> wrote:
> 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

The goal here is to directly port each of the scripts, in
approximately their current form and function. Future rounds can clean
and reorganize. Alan said much the same on IRC, and I (obviously)
concur with his thought.

Please take a look at the direction and provide any feedback.

(I know there are better ways to do some of this; I'm just muscling
through right now; some will be also be refactored due to commonality
with other scripts)

Cheers,
-g