root / pykota / trunk / bin / pkusers @ 2704

Revision 2704, 19.1 kB (checked in by jerome, 18 years ago)

Fixed creation and deletion code.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota Users Manager
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import os
28import sys
29import pwd
30import grp
31
32from pykota.tool import PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
33
34__doc__ = N_("""pkusers v%(__version__)s (c) %(__years__)s %(__author__)s
35
36An Users and Groups Manager for PyKota.
37
38command line usage :
39
40  pkusers [options] user1 user2 user3 ... userN
41 
42or : 
43
44  pkusers --groups [options] group1 group2 group3 ... groupN
45
46options :
47
48  -v | --version       Prints pkusers's version number then exits.
49  -h | --help          Prints this message then exits.
50 
51  -a | --add           Adds users if they don't exist on the database.
52                       If they exist, they are modified unless
53                       -s|--skipexisting is also used.
54                       
55  -d | --delete        Deletes users from the quota storage.
56 
57  -D | --description d Adds a textual description to users or groups.
58                       
59  -g | --groups        Edit users groups instead of users.
60                         
61  -p | --prototype u|g Uses user u or group g as a prototype to create
62                       or modify datas.
63                       
64  -o | --overcharge f  Sets the overcharging factor applied to the user
65                       when computing the cost of a print job. Positive or
66                       negative floating point values are allowed,
67                       this allows you to do some really creative
68                       things like giving money to an user whenever
69                       he prints. The number of pages in a print job
70                       is not modified by this coefficient, only the
71                       cost of the job for a particular user.
72                       Only users have such a coefficient.
73 
74  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
75                              listed, separated by commas. The groups
76                              must already exist in the Quota Storage.
77                       
78  -l | --limitby l     Choose if the user/group is limited in printing                     
79                       by its account balance or by its page quota.
80                       The default value is 'quota'. Allowed values
81                       are 'quota' 'balance' 'noquota' 'noprint' 
82                       and 'nochange' :
83                       
84                         - quota : limit by number of pages per printer.
85                         - balance : limit by number of credits in account.
86                         - noquota : no limit, accounting still done.
87                         - nochange : no limit, accounting not done.
88                         - noprint : printing is denied.
89                       NB : nochange and noprint are not supported for groups.
90                       
91  -b | --balance b     Sets the user's account balance to b.                     
92                       Account balance may be increase or decreased
93                       if b is prefixed with + or -.
94                       WARNING : when decreasing account balance,
95                       the total paid so far by the user is decreased
96                       too.
97                       Groups don't have a real balance, but the
98                       sum of their users' account balance.
99                       
100  -C | --comment txt   Defines some informational text to be associated
101                       with a change to an user's account balance.
102                       Only meaningful if -b | --balance is also used.
103                       
104                       
105  -r | --remove        In combination with the --groups option above,                       
106                       remove users from the specified users groups.
107                       
108  -s | --skipexisting  In combination with the --add option above, tells
109                       pkusers to not modify existing users.
110                       
111  user1 through userN and group1 through groupN can use wildcards
112  if the --add option is not set.
113 
114examples :                             
115
116  $ pkusers --add -p jerome john paul george ringo/ringo@example.com
117 
118  This will add users john, paul, george and ringo to the quota
119  database, and set their account information based on user
120  jerome's own account. User jerome must already exist.
121  User ringo's email address will also be set to 'ringo@example.com'
122 
123  $ pkusers --ingroups coders,it jerome
124 
125  User jerome is put into the groups "coders" and "it" which must
126  already exist in the quota database.
127           
128  $ pkusers --limitby balance jerome
129 
130  This will tell PyKota to limit jerome by his account's balance
131  when printing.
132 
133  $ pkusers --balance +10.0 jerome
134 
135  This will increase jerome's account balance by 10.0 (in your
136  own currency). You can decrease the account balance with a
137  dash prefix, and set it to a fixed amount with no prefix.
138 
139  $ pkusers --delete jerome rachel
140 
141  This will completely delete jerome and rachel from the quota
142  database. All their quotas and jobs will be deleted too.
143 
144  $ pkusers --overcharge 2.5 poorstudent
145 
146  This will overcharge the poorstudent user by a factor of 2.5.
147 
148  $ pkusers --overcharge -1 jerome
149 
150  User jerome will actually earn money whenever he prints.
151 
152  $ pkusers --overcharge 0 boss
153 
154  User boss can print at will, it won't cost him anything because the
155  cost of each print job will be multiplied by zero before charging
156  his account.
157""")
158       
159class PKUsers(PyKotaTool) :       
160    """A class for a users and users groups manager."""
161    def main(self, names, options) :
162        """Manage users or groups."""
163        if not self.config.isAdmin :
164            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
165           
166        suffix = (options["groups"] and "Group") or "User"       
167       
168        if options["delete"] :   
169            self.display("%s...\n" % _("Deletion"))
170            todelete = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
171            nbtotal = len(todelete)
172            for i in range(nbtotal) :
173                todelete[i].delete()
174                percent = 100.0 * float(i) / float(nbtotal)
175                self.display("\r%.02f%%" % percent)
176        else :
177            if options["add"] :   
178                self.display("%s...\n" % _("Creation"))
179                rejectunknown = self.config.getRejectUnknown()   
180                entries = []
181                nbtotal = len(names)
182                for i in range(nbtotal) :
183                    ename = names[i]
184                    email = ""
185                    if not options["groups"] :
186                        splitname = ename.split('/', 1)     # username/email
187                        if len(splitname) == 1 :
188                            splitname.append("")
189                        (ename, email) = splitname
190                        if email and (email.count('@') != 1) :
191                            self.printInfo(_("Invalid email address %s") % email)
192                            email = ""
193                    entry = getattr(self.storage, "get%s" % suffix)(ename)
194                    if entry.Exists :
195                        if options["skipexisting"] :
196                            self.printInfo(_("%s %s already exists, skipping.") % (suffix, entry.Name))
197                        else :   
198                            self.printInfo(_("%s %s already exists, will be modified.") % (suffix, entry.Name))
199                            entries.append(entry)
200                    else :
201                        if self.isValidName(entry.Name) :
202                            reject = 0
203                            if rejectunknown :
204                                if options["groups"] :
205                                    try :
206                                        grp.getgrnam(entry.Name)
207                                    except KeyError :   
208                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
209                                        reject = 1
210                                else :   
211                                    try :
212                                        pwd.getpwnam(entry.Name)
213                                    except KeyError :   
214                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
215                                        reject = 1
216                            if not reject :       
217                                if email :
218                                    entry.Email = email
219                                entry = getattr(self.storage, "add%s" % suffix)(entry)
220                                entries.append(entry)
221                        else :   
222                            if options["groups"] :
223                                self.printInfo(_("Invalid group name %s") % entry.Name)
224                            else :   
225                                self.printInfo(_("Invalid user name %s") % entry.Name)
226                               
227                    percent = 100.0 * float(i) / float(nbtotal)
228                    self.display("\r%.02f%%" % percent)
229                self.display("\r100.00%%\r        \r%s\n" % _("Done."))
230            else :       
231                if not names :
232                    names = ["*"]
233                entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
234                if not entries :
235                    raise PyKotaCommandLineError, _("There's no %s matching %s") % (suffix.lower(), " ".join(names))
236                       
237            if options["list"] :
238                if suffix == "User" :
239                    maildomain = self.config.getMailDomain()
240                    smtpserver = self.config.getSMTPServer()
241                    for entry in entries :
242                        email = entry.Email
243                        if not email :
244                            if maildomain :     
245                                email = "%s@%s" % (entry.Name, maildomain)
246                            elif smtpserver :   
247                                email = "%s@%s" % (entry.Name, smtpserver)
248                            else :   
249                                email = "%s@%s" % (entry.Name, "localhost")
250                        msg = "%s - <%s>" % (entry.Name, email)
251                        if entry.Description :
252                            msg += " - %s" % entry.Description
253                        print msg   
254                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
255                        print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
256                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
257                        print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
258                else :   
259                    for entry in entries :
260                        msg = "%s" % entry.Name
261                        if entry.Description :
262                            msg += " - %s" % entry.Description
263                        print msg   
264                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
265                        print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
266                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
267            else :
268                self.display("%s...\n" % _("Modification"))
269               
270                limitby = options["limitby"]
271                if limitby :
272                    limitby = limitby.strip().lower()
273                if limitby :
274                    if limitby not in ('quota', 'balance', 'noquota', \
275                                                'noprint', 'nochange') :
276                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
277                    if limitby in ('noquota', 'nochange') :   
278                        options["noquota"] = 1
279                    if (limitby in ('nochange', 'noprint')) and options["groups"] :   
280                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
281                   
282                overcharge = options["overcharge"]
283                if overcharge :
284                    try :
285                        overcharge = float(overcharge.strip())
286                    except (ValueError, AttributeError) :   
287                        raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
288                       
289                balance = options["balance"]
290                if balance :
291                    balance = balance.strip()
292                    try :
293                        balancevalue = float(balance)
294                    except ValueError :   
295                        raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
296                   
297                if options["ingroups"] :
298                    usersgroups = self.storage.getMatchingGroups(options["ingroups"])
299                    if not usersgroups :
300                        raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
301                else :         
302                    usersgroups = []
303                       
304                if options["description"] :
305                    description = options["description"].strip()
306                   
307                if options["prototype"] :   
308                    protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
309                    if not protoentry.Exists :
310                        raise PyKotaCommandLineError, _("Prototype object %s not found in Quota Storage.") % protoentry.Name
311                    else :   
312                        limitby = protoentry.LimitBy
313                        balancevalue = protoentry.AccountBalance
314                        if balancevalue is not None :
315                            balance = str(abs(balancevalue))
316                        else :   
317                            balance = None
318                        overcharge = getattr(protoentry, "OverCharge", None)
319                   
320                nbtotal = len(entries)
321                for i in range(nbtotal) :       
322                    entry = entries[i]
323                    if description is not None :        # NB : "" is allowed !
324                        entry.setDescription(description)
325                    entry.save()   
326                    if not options["groups"] :
327                        for ugroup in usersgroups :
328                            if options["remove"] :
329                                ugroup.delUserFromGroup(entry)
330                            else :
331                                ugroup.addUserToGroup(entry)
332                    percent = 100.0 * float(i) / float(nbtotal)
333                    self.display("\r%.02f%%" % percent)
334                               
335        if not options["list"] :               
336            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
337                     
338if __name__ == "__main__" : 
339    retcode = 0
340    try :
341        defaults = { \
342                     "comment" : "", \
343                   }
344        short_options = "hvaD:dgl:rsp:o:i:b:C:L"
345        long_options = ["help", "version", "add", "description=", \
346                        "delete", "groups", "list", "remove", \
347                        "skipexisting", "overcharge=", "prototype=", \
348                        "ingroups=", "limitby=", "balance=", "comment=", \
349                       ]
350                       
351       
352        # Initializes the command line tool
353        manager = PKUsers(doc=__doc__)
354        manager.deferredInit()
355       
356        # parse and checks the command line
357        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
358       
359        # sets long options
360        options["help"] = options["h"] or options["help"]
361        options["version"] = options["v"] or options["version"]
362        options["add"] = options["a"] or options["add"]
363        options["description"] = options["D"] or options["description"]
364        options["delete"] = options["d"] or options["delete"] 
365        options["groups"] = options["g"] or options["groups"]
366        options["list"] = options["L"] or options["list"]
367        options["remove"] = options["r"] or options["remove"]
368        options["skipexisting"] = options["s"] or options["skipexisting"]
369        options["prototype"] = options["p"] or options["prototype"]
370        options["limitby"] = options["l"] or options["limitby"]
371        options["balance"] = options["b"] or options["balance"] 
372        options["ingroups"] = options["i"] or options["ingroups"]
373        options["overcharge"] = options["o"] or options["overcharge"]
374        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
375       
376        if options["help"] :
377            manager.display_usage_and_quit()
378        elif options["version"] :
379            manager.display_version_and_quit()
380        elif (options["delete"] and (options["add"] or options["remove"] or options["description"])) \
381           or (options["skipexisting"] and not options["add"]) \
382           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"])) \
383           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
384            raise PyKotaCommandLineError, _("incompatible options, see help.")
385        elif options["remove"] and not options["ingroups"] :   
386            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
387        elif (not args) and options["add"] :
388            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
389        else :
390            retcode = manager.main(args, options)
391    except KeyboardInterrupt :       
392        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
393        retcode = -3
394    except PyKotaCommandLineError, msg :   
395        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
396        retcode = -2
397    except SystemExit :       
398        pass
399    except :
400        try :
401            manager.crashed("pkusers failed")
402        except :   
403            crashed("pkusers failed")
404        retcode = -1
405
406    try :
407        manager.storage.close()
408    except (TypeError, NameError, AttributeError) :   
409        pass
410       
411    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.