Author: jfthomps
Date: Thu Dec  3 19:42:31 2009
New Revision: 886895

URL: http://svn.apache.org/viewvc?rev=886895&view=rev
Log:
This script provides a command line interface to the XML RPC API for doing user 
group management.

Added:
    incubator/vcl/sandbox/useful_scripts/managegroups.py   (with props)

Added: incubator/vcl/sandbox/useful_scripts/managegroups.py
URL: 
http://svn.apache.org/viewvc/incubator/vcl/sandbox/useful_scripts/managegroups.py?rev=886895&view=auto
==============================================================================
--- incubator/vcl/sandbox/useful_scripts/managegroups.py (added)
+++ incubator/vcl/sandbox/useful_scripts/managegroups.py Thu Dec  3 19:42:31 
2009
@@ -0,0 +1,596 @@
+#!/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.
+
+
+
+
+# --------------------------------------------------------------------------
+# these three variables can be specified here an omitted at the command line
+# if specified in both places, the command line options have priority
+
+#vcluser = '' # log in to the VCL site with this user, must be in 
usern...@affiliation form
+#vclpass = '' # password for vcluser
+#vclurl = 'https://vcl.ncsu.edu/scheduling/index.php?mode=xmlrpccall' # URL of 
VCL site
+# --------------------------------------------------------------------------
+
+import xmlrpclib, sys, os, stat, re
+from optparse import OptionParser
+
+class VCLTransport(xmlrpclib.SafeTransport):
+       ##
+       # Send a complete request, and parse the response.
+       #
+       # @param host Target host.
+       # @param handler Target PRC handler.
+       # @param request_body XML-RPC request body.
+       # @param verbose Debugging flag.
+       # @return Parsed response.
+
+       def request(self, host, userid, passwd, handler, request_body, 
verbose=0):
+               # issue XML-RPC request
+
+               h = self.make_connection(host)
+               if verbose:
+                       h.set_debuglevel(1)
+
+               self.send_request(h, handler, request_body)
+               h.putheader('X-APIVERSION', '2')
+               h.putheader('X-User', userid)
+               h.putheader('X-Pass', passwd)
+               self.send_host(h, host)
+               self.send_user_agent(h)
+               self.send_content(h, request_body)
+
+               errcode, errmsg, headers = h.getreply()
+
+               if errcode != 200:
+                       raise ProtocolError(
+                               host + handler,
+                               errcode, errmsg,
+                               headers
+                               )
+
+               self.verbose = verbose
+
+               try:
+                       sock = h._conn.sock
+               except AttributeError:
+                       sock = None
+
+               return self._parse_response(h.getfile(), sock)
+
+class VCLServerProxy(xmlrpclib.ServerProxy):
+       __userid = ''
+       __passwd = ''
+       def __init__(self, uri, userid, passwd, transport=None, encoding=None,
+                                verbose=0, allow_none=0, use_datetime=0):
+               self.__userid = userid
+               self.__passwd = passwd
+               # establish a "logical" server connection
+
+               # get the url
+               import urllib
+               type, uri = urllib.splittype(uri)
+               if type not in ("http", "https"):
+                       raise IOError, "unsupported XML-RPC protocol"
+               self.__host, self.__handler = urllib.splithost(uri)
+               if not self.__handler:
+                       self.__handler = "/RPC2"
+
+               if transport is None:
+                       transport = VCLTransport()
+               self.__transport = transport
+
+               self.__encoding = encoding
+               self.__verbose = verbose
+               self.__allow_none = allow_none
+
+       def __request(self, methodname, params):
+               # call a method on the remote server
+
+               request = xmlrpclib.dumps(params, methodname, 
encoding=self.__encoding,
+                                 allow_none=self.__allow_none)
+
+               response = self.__transport.request(
+                       self.__host,
+                       self.__userid,
+                       self.__passwd,
+                       self.__handler,
+                       request,
+                       verbose=self.__verbose
+                       )
+
+               if len(response) == 1:
+                       response = response[0]
+
+               return response
+
+       def __getattr__(self, name):
+               # magic method dispatcher
+               return xmlrpclib._Method(self.__request, name)
+
+       # note: to call a remote object with an non-standard name, use
+       # result getattr(server, "strange-python-name")(args)
+
+def addUserGroup(options):
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCaddUserGroup(options.name, options.affiliation, 
options.owner, options.managingGroup, options.initialmax, options.totalmax, 
options.maxextend)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: User group sucessfully created"
+       else:
+               print "ERROR: unknown problem while creating new user group"
+
+def getUserGroupAttributes(options):
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCgetUserGroupAttributes(options.name, 
options.affiliation)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: Attributes retreived"
+               print 'owner: %s\nmanagingGroup: %s\ninitialMaxTime: 
%s\ntotalMaxTime: %s\nmaxExtendTime: %s' % (rc['owner'], rc['managingGroup'], 
rc['initialMaxTime'], rc['totalMaxTime'], rc['maxExtendTime'])
+       else:
+               print "ERROR: unknown problem while getting user group 
attributes"
+
+def deleteUserGroup(options):
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCdeleteUserGroup(options.name, options.affiliation)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: User group sucessfully deleted"
+       else:
+               print "ERROR: unknown problem while deleting user group"
+
+def editUserGroup(options):
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCeditUserGroup(options.name, options.affiliation, 
options.newname, options.newaffiliation, options.owner, options.managingGroup, 
options.initialmax, options.totalmax, options.maxextend)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: User group sucessfully updated"
+       else:
+               print "ERROR: unknown problem while updating user group"
+
+def getUserGroupMembers(options):
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCgetUserGroupMembers(options.name, options.affiliation)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: Membership retrieved"
+               for item in rc['members']:
+                       print item
+       else:
+               print "ERROR: unknown problem while getting user group members"
+
+def addUsersToGroup(options):
+       newusers = []
+       if options.filename:
+               fp = open(options.filename, 'r')
+               for line in fp:
+                       newusers += [line.rstrip()]
+               fp.close()
+       if options.userlist:
+               newusers += options.userlist.split(',')
+       if len(newusers) == 0:
+               print "WARNING: no users specified to add to group, not doing 
anything"
+               sys.exit(4)
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCaddUsersToGroup(options.name, options.affiliation, 
newusers)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: Users sucessfully added to group"
+       else:
+               print "ERROR: unknown problem while adding users to group"
+
+def removeUsersFromGroup(options):
+       delusers = []
+       if options.filename:
+               fp = open(options.filename, 'r')
+               for line in fp:
+                       delusers += [line.rstrip()]
+               fp.close()
+       if options.userlist:
+               delusers += options.userlist.split(',')
+       if len(delusers) == 0:
+               print "WARNING: no users specified to remove from group, not 
doing anything"
+               sys.exit(4)
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCremoveUsersFromGroup(options.name, 
options.affiliation, delusers)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       elif rc['status'] == 'success':
+               print "SUCCESS: Users sucessfully removed from group"
+       else:
+               print "ERROR: unknown problem while removing users from group"
+
+def emptyGroupMembership(options):
+       caller = VCLServerProxy(options.vclurl, options.vcluser, 
options.vclpass)
+       rc = caller.XMLRPCgetUserGroupMembers(options.name, options.affiliation)
+       if(rc['status'] == 'error'):
+               print  'ERROR: There was an error with the API call: ' + 
rc['errormsg']
+               sys.exit(5)
+       else:
+               options.userlist = ','.join(rc['members'])
+               options.filename = None
+               removeUsersFromGroup(options)
+
+def printHelp(command='all'):
+       if command == 'all':
+               print "Usage: \n"
+               print "  %s [-u vcluser] [-p 'vclpass'] [-r vclurl] <command> 
<command parameters>\n" % (sys.argv[0])
+               print "  These first three options can be omitted by defining 
them at the top of %s" % (sys.argv[0])
+               print "    -u vcluser - log in to VCL site with this user, must 
be in usern...@affiliation form"
+               print "    -p 'vclpass' - password used when logging in to VCL 
site, use quotes if it contains spaces"
+               print "    -r vclurl - URL of VCL site\n"
+               print "Commands:\n"
+       if command == 'all' or command == 'addUserGroup':
+               print "  addUserGroup - creates a new user group"
+               print "  parameters:"
+               print "    -n name - name of new user group"
+               print "    -a affiliation - affiliation of new user group"
+               print "    -o owner - user that will be the owner of the group 
in usern...@affiliation form"
+               print "    -m ManagingGroup - name of user group that can 
manage membership of the new group"
+               print "    -i InitialMaxTime - (minutes) max initial time users 
in this group can select for length of reservations"
+               print "    -t TotalMaxTime - (minutes) total length users in 
the group can have for a reservation (including all extensions)"
+               print "    -x MaxExtendTime - (minutes) max length of time 
users can request as an extension to a reservation at a time\n"
+       if command == 'all' or command == 'getUserGroupAttributes':
+               print "  getUserGroupAttributes - gets information about a user 
group"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group\n"
+       if command == 'all' or command == 'deleteUserGroup':
+               print "  deleteUserGroup - deletes a user group"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group\n"
+       if command == 'all' or command == 'editUserGroup':
+               print "  editUserGroup - modifies attributes of a user group"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group"
+               print "    -N NewName - (optional) new name for the user group"
+               print "    -A NewAffiliation - (optional) new affiliation for 
the user group"
+               print "    -O NewOwner - (optional) new owner for the user 
group in usern...@affiliation form"
+               print "    -M NewManagingGroup - (optional) new user group that 
can manage membership of the user group in gr...@affiliation form"
+               print "    -I NewInitialMaxTime - (optional) new max initial 
time users in the group can select for length of reservations"
+               print "    -T NewTotalMaxTime - (optional) new total length 
users in the group can have for a reservation (including all extensions)"
+               print "    -X NewMaxExtendTime - (optional) new max length of 
time users can request as an extension to a reservation at a time\n"
+       if command == 'all' or command == 'getUserGroupMembers':
+               print "  getUserGroupMembers - gets members of a user group 
(Note: it is possible for a group to have no members)"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group\n"
+       if command == 'all' or command == 'addUsersToGroup':
+               print "  addUsersToGroup - adds users to a group"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group"
+               print "    At least one of these must also be specified:"
+               print "      -l UserList - comma delimited list of users to add 
(no spaces) in usern...@affiliation form"
+               print "      -f filename - name of file containing users to add 
(one user per line) in usern...@affiliation form\n"
+       if command == 'all' or command == 'removeUsersFromGroup':
+               print "  removeUsersFromGroup - removes users from a group"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group"
+               print "    At least one of these must also be specified:"
+               print "      -l UserList - comma delimited list of users to 
remove (no spaces) in usern...@affiliation form"
+               print "      -f filename - name of file containing users to 
remove (one user per line) in usern...@affiliation form\n"
+       if command == 'all' or command == 'emptyGroupMembership':
+               print "  emptyGroupMembership - removes all users currently in 
a group"
+               print "  parameters:"
+               print "    -n name - name of an existing user group"
+               print "    -a affiliation - affiliation of user group\n"
+
+if len(sys.argv) == 1:
+       printHelp()
+       sys.exit(2)
+
+testargs = ' '.join(sys.argv[1:])
+m = 
re.search('(-u|-p|-r).*(addUserGroup|getUserGroupAttributes|deleteUserGroup|editUserGroup|getUserGroupMembers|addUsersToGroup|removeUsersFromGroup|emptyGroupMembership)',
 testargs)
+if m != None:
+       parser1 = OptionParser(add_help_option=False)
+       parser1.add_option('-u', dest='vcluser')
+       parser1.add_option('-p', type="string", dest='vclpass')
+       parser1.add_option('-r', dest='vclurl')
+       command = m.group(2)
+       last = sys.argv.index(command)
+       (options, remaining) = parser1.parse_args(sys.argv[1:last])
+       args = sys.argv[(last + 1):]
+       if options.vcluser:
+               vcluser = options.vcluser
+       if options.vclpass:
+               vclpass = options.vclpass
+       if options.vclurl:
+               vclurl = options.vclurl
+else:
+       args = sys.argv[2:]
+       command = sys.argv[1]
+
+try:
+       vcluser
+except NameError:
+       print "ERROR: vcluser must be definied internally or specified on the 
command line"
+       sys.exit(1)
+try:
+       vclpass
+except NameError:
+       print "ERROR: vclpass must be definied internally or specified on the 
command line"
+       sys.exit(1)
+try:
+       vclurl
+except NameError:
+       print "ERROR: vclurl must be definied internally or specified on the 
command line"
+       sys.exit(1)
+
+parser = OptionParser(add_help_option=False)
+
+# addUserGroup
+if command == 'addUserGroup':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       parser.add_option('-o', dest='owner')
+       parser.add_option('-m', dest='managingGroup')
+       parser.add_option('-i', dest='initialmax')
+       parser.add_option('-t', dest='totalmax')
+       parser.add_option('-x', dest='maxextend')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('addUserGroup')
+               sys.exit(2)
+       if options.name and options.affiliation and options.owner and 
options.managingGroup and options.initialmax and options.totalmax and 
options.maxextend:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('addUserGroup')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       addUserGroup(options)
+
+# getUserGroupAttributes
+elif command == 'getUserGroupAttributes':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('getUserGroupAttributes')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('getUserGroupAttributes')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       getUserGroupAttributes(options)
+
+# deleteUserGroup
+elif command == 'deleteUserGroup':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('deleteUserGroup')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('deleteUserGroup')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       deleteUserGroup(options)
+
+# editUserGroup
+elif command == 'editUserGroup':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       parser.add_option('-N', dest='newname')
+       parser.add_option('-A', dest='newaffiliation')
+       parser.add_option('-O', dest='owner')
+       parser.add_option('-M', dest='managingGroup')
+       parser.add_option('-I', dest='initialmax')
+       parser.add_option('-T', dest='totalmax')
+       parser.add_option('-X', dest='maxextend')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('editUserGroup')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('editUserGroup')
+               sys.exit(2)
+       params = "name: %s\naffiliation: %s" % (options.name, 
options.affiliation)
+       if options.newname:
+               params += "\nnewname: " + options.newname
+       else:
+               options.newname = ''
+       if options.newaffiliation:
+               params += "\nnewaffiliation: " + options.newaffiliation
+       else:
+               options.newaffiliation = ''
+       if options.owner:
+               params += "\nowner: " + options.owner
+       else:
+               options.owner = ''
+       if options.managingGroup:
+               params += "\nmanagingGroup: " + options.managingGroup
+       else:
+               options.managingGroup = ''
+       if options.initialmax:
+               params += "\ninitialmax: " + options.initialmax
+       else:
+               options.initialmax = ''
+       if options.totalmax:
+               params += "\ntotalmax: " + options.totalmax
+       else:
+               options.totalmax = ''
+       if options.maxextend:
+               params += "\nmaxextend: " + options.maxextend
+       else:
+               options.maxextend = ''
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       editUserGroup(options)
+
+# getUserGroupMembers
+elif command == 'getUserGroupMembers':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('getUserGroupMembers')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('getUserGroupMembers')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       getUserGroupMembers(options)
+
+# addUsersToGroup
+elif command == 'addUsersToGroup':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       parser.add_option('-l', dest='userlist')
+       parser.add_option('-f', dest='filename')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('addUsersToGroup')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('addUsersToGroup')
+               sys.exit(2)
+       if options.userlist:
+               pass
+       elif options.filename:
+               # check for file existance
+               if not os.path.exists(options.filename):
+                       print "ERROR: specified file (%s) does not exist" % 
(options.filename)
+                       sys.exit(3)
+               # check for file readability
+               perm = os.stat(options.filename)
+               mode = perm[stat.ST_MODE]
+               if not mode & stat.S_IREAD:
+                       print "ERROR: specified file (%s) is not readable" % 
(options.filename)
+                       sys.exit(3)
+       else:
+               print "ERROR: at least one of -l or -f must be specified\n"
+               printHelp('addUsersToGroup')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       addUsersToGroup(options)
+
+# removeUsersFromGroup
+elif command == 'removeUsersFromGroup':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       parser.add_option('-l', dest='userlist')
+       parser.add_option('-f', dest='filename')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('removeUsersFromGroup')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('removeUsersFromGroup')
+               sys.exit(2)
+       if options.userlist:
+               pass
+       elif options.filename:
+               # check for file existance
+               if not os.path.exists(options.filename):
+                       print "ERROR: specified file (%s) does not exist" % 
(options.filename)
+                       sys.exit(3)
+               # check for file readability
+               perm = os.stat(options.filename)
+               mode = perm[stat.ST_MODE]
+               if not mode & stat.S_IREAD:
+                       print "ERROR: specified file (%s) is not readable" % 
(options.filename)
+                       sys.exit(3)
+       else:
+               print "ERROR: at least one of -l or -f must be specified\n"
+               printHelp('removeUsersFromGroup')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       removeUsersFromGroup(options)
+
+# emptyGroupMembership
+elif command == 'emptyGroupMembership':
+       parser.add_option('-n', dest='name')
+       parser.add_option('-a', dest='affiliation')
+       (options, remaining) = parser.parse_args(args)
+       if len(remaining):
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('emptyGroupMembership')
+               sys.exit(2)
+       if options.name and options.affiliation:
+               pass
+       else:
+               print "ERROR: Incorrect number of parameters specified\n";
+               printHelp('emptyGroupMembership')
+               sys.exit(2)
+       options.vcluser = vcluser
+       options.vclpass = vclpass
+       options.vclurl = vclurl
+       emptyGroupMembership(options)
+else:
+       print "ERROR: unknown command: %s\n" % (command)
+       printHelp()
+       sys.exit(2)
+
+sys.exit(0)

Propchange: incubator/vcl/sandbox/useful_scripts/managegroups.py
------------------------------------------------------------------------------
    svn:executable = *


Reply via email to