root / pykota / trunk / bin / edpykota @ 2323

Revision 2323, 27.6 kB (checked in by jerome, 19 years ago)

Now logs a warning when user/group doesn't exist when running edpykota.
NB : the message is only printed once all users/groups have been processed.
Severity : if you don't need this, you don't need to upgrade.

  • Property svn:eol-style set to native
  • 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 Print Quota Editor
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005 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 sys
28import os
29import pwd
30import grp
31from pykota.tool import PyKotaTool, PyKotaToolError, crashed, N_
32from pykota.config import PyKotaConfigError
33from pykota.storage import PyKotaStorageError
34
35__doc__ = N_("""edpykota v%s (c) %s %s
36
37A Print Quota editor for PyKota.
38
39command line usage :
40
41  edpykota [options] user1 user2 ... userN
42 
43  edpykota [options] group1 group2 ... groupN
44
45options :
46
47  -v | --version       Prints edpykota's version number then exits.
48  -h | --help          Prints this message then exits.
49 
50  -a | --add           Adds users and/or printers if they don't
51                       exist on the Quota Storage Server.
52                       
53  -d | --delete        Deletes users/groups from the quota storage.
54                       Printers are never deleted.
55                       
56  -c | --charge p[,j]  Sets the price per page and per job to charge
57                       for a particular printer. Job price is optional.
58                       If both are to be set, separate them with a comma.
59                       Floating point values are allowed.
60                       
61  -o | --overcharge f  Sets the overcharging factor applied to the user
62                       when computing the cost of a print job. Positive or
63                       negative floating point values are allowed,
64                       this allows you to do some really creative
65                       things like giving money to an user whenever
66                       he prints. The number of pages in a print job
67                       is not modified by this coefficient, only the
68                       cost of the job for a particular user.
69                       Only users have a coefficient.
70 
71  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
72                              listed, separated by commas. The groups
73                              must already exist in the Quota Storage.
74 
75  -u | --users         Edit users print quotas, this is the default.
76 
77  -P | --printer p     Edit quotas on printer p only. Actually p can
78                       use wildcards characters to select only
79                       some printers. The default value is *, meaning
80                       all printers.
81                       You can specify several names or wildcards,
82                       by separating them with commas.
83 
84  -G | --pgroups pg1[,pg2...] Adds the printer(s) to the printer groups
85                       pg1, pg2, etc... which must already exist.
86                       A printer group is just like a normal printer,
87                       only that it is usually unknown from the printing
88                       system. Create printer groups exactly the same
89                       way that you create printers, then add other
90                       printers to them with this option.
91                       Accounting is done on a printer and on all
92                       the printer groups it belongs to, quota checking
93                       is done on a printer and on all the printer groups
94                       it belongs to.
95 
96  -g | --groups        Edit users groups print quotas instead of users.
97                         
98  -p | --prototype u|g Uses user u or group g as a prototype to set
99                       print quotas
100                       
101  -n | --noquota       Doesn't set a quota but only does accounting.
102 
103  -r | --reset         Resets the actual page counter for the user
104                       or group to zero on the specified printers.
105                       The life time page counter is kept unchanged.
106                       
107  -R | --hardreset     Resets the actual and life time page counters
108                       for the user or group to zero on the specified
109                       printers. This is a shortcut for '--used 0'.
110                       
111  -l | --limitby l     Choose if the user/group is limited in printing                     
112                       by its account balance or by its page quota.
113                       The default value is 'quota'. Allowed values
114                       are 'quota' 'balance' 'quota-then-balance' and
115                       'balance-then-quota'.
116                       WARNING : quota-then-balance and balance-then-quota
117                       are not yet implemented.
118                       
119  -b | --balance b     Sets the user's account balance to b.                     
120                       Account balance may be increase or decreased
121                       if b is prefixed with + or -.
122                       WARNING : when decreasing account balance,
123                       the total paid so far by the user is decreased
124                       too.
125                       Groups don't have a real balance, but the
126                       sum of their users' account balance.
127                       
128  -S | --softlimit sl  Sets the quota soft limit to sl pages.                       
129 
130  -H | --hardlimit hl  Sets the quota hard limit to hl pages.
131
132  -U | --used usage    Sets the pagecounters for the user to usage pages;
133                       useful for migrating users from a different system
134                       where they have already used some pages. Actual
135                       and Life Time page counters may be increased or decreased
136                       if usage is prefixed with + or -.
137                       WARNING : BOTH page counters are modified in all cases,
138                       so be careful.
139                       NB : if 'usage' equals '0', then the action taken is
140                       the same as if --hardreset was used.
141
142  user1 through userN and group1 through groupN can use wildcards
143  if the --add option is not set.
144 
145examples :                             
146
147  $ edpykota --add -p jerome john paul george ringo/ringo@example.com
148 
149  This will add users john, paul, george and ringo to the quota
150  database, and set their print quotas to the same values than user
151  jerome. User jerome must already exist.
152  User ringo's email address will also be set to 'ringo@example.com'
153 
154  $ edpykota --printer lp -S 50 -H 60 jerome
155 
156  This will set jerome's print quota on the lp printer to a soft limit
157  of 50 pages, and a hard limit of 60 pages. If either user jerome or
158  printer lp doesn't exist on the Quota Storage Server then nothing is done.
159
160  $ edpykota --add --printer lp --ingroups coders,it -S 50 -H 60 jerome
161 
162  Same as above, but if either user jerome or printer lp doesn't exist
163  on the Quota Storage Server they are automatically added. Also
164  user jerome is put into the groups "coders" and "it" which must
165  already exist in the Quota Storage.
166           
167  $ edpykota -g -S 500 -H 550 financial support           
168 
169  This will set print quota soft limit to 500 pages and hard limit
170  to 550 pages for groups financial and support on all printers.
171 
172  $ edpykota --reset jerome "jo*"
173 
174  This will reset jerome's page counter to zero on all printers, as
175  well as every user whose name begins with 'jo'.
176  Their life time page counter on each printer will be kept unchanged.
177  You can also reset the life time page counters by using the
178  --hardreset | -R command line option.
179 
180  $ edpykota --printer hpcolor --noquota jerome
181 
182  This will tell PyKota to not limit jerome when printing on the
183  hpcolor printer. All his jobs will be allowed on this printer, but
184  accounting of the pages he prints will still be kept.
185  Print Quotas for jerome on other printers are unchanged.
186 
187  $ edpykota --limitby balance jerome
188 
189  This will tell PyKota to limit jerome by his account's balance
190  when printing.
191 
192  $ edpykota --balance +10.0 jerome
193 
194  This will increase jerome's account balance by 10.0 (in your
195  own currency). You can decrease the account balance with a
196  dash prefix, and set it to a fixed amount with no prefix.
197 
198  $ edpykota --delete jerome rachel
199 
200  This will completely delete jerome and rachel from the Quota Storage
201  database. All their quotas and jobs will be deleted too.
202 
203  $ edpykota --printer lp --charge 0.1
204 
205  This will set the page price for printer lp to 0.1. Job price
206  will not be changed.
207 
208  $ edpykota --printer hplj1,hplj2 --pgroups Laser,HP
209 
210  This will put printers hplj1 and hplj2 in printers groups Laser and HP.
211  When printing either on hplj1 or hplj2, print quota will also be
212  checked and accounted for on virtual printers Laser and HP.
213 
214  $ edpykota --overcharge 2.5 poorstudent
215 
216  This will overcharge the poorstudent user by a factor of 2.5.
217 
218  $ edpykota --overcharge -1 jerome
219 
220  User jerome will actually earn money whenever he prints.
221 
222  $ edpykota --overcharge 0 boss
223 
224  User boss can print at will, it won't cost him anything because the
225  cost of each print job will be multiplied by zero before charging
226  his account.
227
228This program is free software; you can redistribute it and/or modify
229it under the terms of the GNU General Public License as published by
230the Free Software Foundation; either version 2 of the License, or
231(at your option) any later version.
232
233This program is distributed in the hope that it will be useful,
234but WITHOUT ANY WARRANTY; without even the implied warranty of
235MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
236GNU General Public License for more details.
237
238You should have received a copy of the GNU General Public License
239along with this program; if not, write to the Free Software
240Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
241
242Please e-mail bugs to: %s""") 
243       
244class EdPyKota(PyKotaTool) :       
245    """A class for edpykota."""
246    def main(self, names, options) :
247        """Edit user or group quotas."""
248        if not self.config.isAdmin :
249            raise PyKotaToolError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
250       
251        suffix = (options["groups"] and "Group") or "User"       
252       
253        softlimit = hardlimit = None
254
255        used = options["used"]
256        if used :
257            used = used.strip()
258            try :
259                int(used)
260            except ValueError :
261                raise PyKotaToolError, _("Invalid used value %s.") % used
262
263        if not options["noquota"] :
264            if options["softlimit"] :
265                try :
266                    softlimit = int(options["softlimit"].strip())
267                except ValueError :   
268                    raise PyKotaToolError, _("Invalid softlimit value %s.") % options["softlimit"]
269            if options["hardlimit"] :
270                try :
271                    hardlimit = int(options["hardlimit"].strip())
272                except ValueError :   
273                    raise PyKotaToolError, _("Invalid hardlimit value %s.") % options["hardlimit"]
274            if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) :       
275                # error, exchange them
276                self.printInfo(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit))
277                (softlimit, hardlimit) = (hardlimit, softlimit)
278           
279        overcharge = options["overcharge"]
280        if overcharge :
281            try :
282                overcharge = float(overcharge.strip())
283            except (ValueError, AttributeError) :   
284                raise PyKotaToolError, _("Invalid overcharge value %s") % options["overcharge"]
285               
286        balance = options["balance"]
287        if balance :
288            balance = balance.strip()
289            try :
290                balancevalue = float(balance)
291            except ValueError :   
292                raise PyKotaToolError, _("Invalid balance value %s") % options["balance"]
293           
294        if options["charge"] :
295            try :
296                charges = [float(part) for part in options["charge"].split(',', 1)]
297            except ValueError :   
298                raise PyKotaToolError, _("Invalid charge amount value %s") % options["charge"]
299            else :   
300                if len(charges) > 2 :
301                    charges = charges[:2]
302                if len(charges) != 2 :
303                    charges = [charges[0], None]
304                   
305        limitby = options["limitby"]
306        if limitby :
307            limitby = limitby.strip().lower()
308        if limitby and (limitby not in ('quota', 'balance', 'quota-then-balance', 'balance-then-quota')) :   
309            raise PyKotaToolError, _("Invalid limitby value %s") % options["limitby"]
310           
311        if options["ingroups"] :   
312            groupnames = [gname.strip() for gname in options["ingroups"].split(',')]
313        else :   
314            groupnames = []
315           
316        rejectunknown = self.config.getRejectUnknown()   
317        printeradded = 0
318        printers = self.storage.getMatchingPrinters(options["printer"])
319        if not printers :
320            pname = options["printer"]
321            if options["add"] and pname :
322                if self.isValidName(pname) :
323                    printers = [ self.storage.addPrinter(pname) ]
324                    if printers[0].Exists :
325                        printeradded = 1
326                    else :   
327                        raise PyKotaToolError, _("Impossible to add printer %s") % pname
328                else :   
329                    raise PyKotaToolError, _("Invalid printer name %s") % pname
330            else :
331                raise PyKotaToolError, _("There's no printer matching %s") % pname
332        if not names :   
333            if options["delete"] :   
334                raise PyKotaToolError, _("You have to pass user or group names on the command line")
335            else :
336                names = getattr(self.storage, "getAll%ssNames" % suffix)() # all users or groups
337               
338        printersgroups = []       
339        if options["pgroups"] :       
340            printersgroups = self.storage.getMatchingPrinters(options["pgroups"])
341           
342        if options["prototype"] :   
343            protoentry = getattr(self.storage, "get%s" % suffix)(options["prototype"])
344            if not protoentry.Exists :
345                raise PyKotaToolError, _("Prototype object %s not found in Quota Storage.") % protoentry.Name
346            else :   
347                limitby = protoentry.LimitBy
348                balancevalue = protoentry.AccountBalance
349                if balancevalue is not None :
350                    balance = str(abs(balancevalue))
351                else :   
352                    balance = None
353                overcharge = getattr(protoentry, "OverCharge", None)
354           
355        missingusers = {}
356        missinggroups = {}   
357        todelete = {}   
358        changed = {} # tracks changes made at the user/group level
359        for printer in printers :
360            for pgroup in printersgroups :
361                pgroup.addPrinterToGroup(printer)   
362               
363            if options["charge"] :
364                (perpage, perjob) = charges
365                printer.setPrices(perpage, perjob)   
366               
367            if options["prototype"] :
368                protoquota = getattr(self.storage, "get%sPQuota" % suffix)(protoentry, printer)
369                if not protoquota.Exists :
370                    self.printInfo(_("Prototype %s not found in Quota Storage for printer %s.") % (protoentry.Name, printer.Name))
371                else :   
372                    (softlimit, hardlimit) = (protoquota.SoftLimit, protoquota.HardLimit)
373                   
374            if not options["noquota"] :   
375                if hardlimit is None :   
376                    hardlimit = softlimit
377                    if hardlimit is not None :
378                        self.printInfo(_("Undefined hard limit set to soft limit (%s) on printer %s.") % (str(hardlimit), printer.Name))
379                if softlimit is None :   
380                    softlimit = hardlimit
381                    if softlimit is not None :
382                        self.printInfo(_("Undefined soft limit set to hard limit (%s) on printer %s.") % (str(softlimit), printer.Name))
383                       
384            if options["add"] :   
385                allentries = []   
386                for name in names :
387                    email = ""
388                    if not options["groups"] :
389                        splitname = name.split('/', 1)     # username/email
390                        if len(splitname) == 1 :
391                            splitname.append("")
392                        (name, email) = splitname
393                        if email and (email.count('@') != 1) :
394                            self.printInfo(_("Invalid email address %s") % email)
395                            email = ""
396                    entry = getattr(self.storage, "get%s" % suffix)(name)
397                    if email and not options["groups"] :
398                        entry.Email = email
399                    entrypquota = getattr(self.storage, "get%sPQuota" % suffix)(entry, printer)
400                    allentries.append((entry, entrypquota))
401            else :   
402                allentries = getattr(self.storage, "getPrinter%ssAndQuotas" % suffix)(printer, names)
403               
404            # TODO : do this only once !!!   
405            allnames = [entry.Name for (entry, dummy) in allentries]
406            for name in names :   
407                if not self.matchString(name, allnames) :
408                    if options["groups"] :
409                        missinggroups[name] = 1
410                    else :   
411                        missingusers[name] = 1
412               
413            for (entry, entrypquota) in allentries :
414                if not changed.has_key(entry.Name) :
415                    changed[entry.Name] = {}
416                    if not options["groups"] :
417                        changed[entry.Name]["ingroups"] = []
418                       
419                if not entry.Exists :       
420                    # not found
421                    if options["add"] :
422                        # In case we want to add something, it is crucial
423                        # that we DON'T check with the system accounts files
424                        # like /etc/passwd because users may be defined
425                        # only remotely
426                        if self.isValidName(entry.Name) :
427                            reject = 0
428                            if rejectunknown :
429                                if options["groups"] :
430                                    try :
431                                        grp.getgrnam(entry.Name)
432                                    except KeyError :   
433                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
434                                        reject = 1
435                                else :   
436                                    try :
437                                        pwd.getpwnam(entry.Name)
438                                    except KeyError :   
439                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
440                                        reject = 1
441                            if not reject :       
442                                entry = getattr(self.storage, "add%s" % suffix)(entry)
443                        else :   
444                            if options["groups"] :
445                                self.printInfo(_("Invalid group name %s") % entry.Name)
446                            else :   
447                                self.printInfo(_("Invalid user name %s") % entry.Name)
448                    else :
449                        if options["groups"] :
450                            missinggroups[entry.Name] = 1
451                        else :   
452                            missingusers[entry.Name] = 1
453                elif options["delete"] :               
454                    todelete[entry.Name] = entry
455                               
456                if entry.Exists and (not entrypquota.Exists) :
457                    # not found
458                    if options["add"] :
459                        entrypquota = getattr(self.storage, "add%sPQuota" % suffix)(entry, printer)
460                       
461                if not entrypquota.Exists :     
462                    self.printInfo(_("Quota not found for object %s on printer %s.") % (entry.Name, printer.Name))
463                else :   
464                    if options["noquota"] or options["prototype"] or ((softlimit is not None) and (hardlimit is not None)) :
465                        entrypquota.setLimits(softlimit, hardlimit)
466                    if limitby :
467                        if changed[entry.Name].get("limitby") is None :
468                            entry.setLimitBy(limitby)
469                            changed[entry.Name]["limitby"] = limitby
470                   
471                    if options["reset"] :
472                        entrypquota.reset()
473                       
474                    if options["hardreset"] :   
475                        entrypquota.hardreset()
476                       
477                    if not options["groups"] :
478                        if used :
479                            entrypquota.setUsage(used)
480                           
481                        if overcharge is not None :   
482                            if changed[entry.Name].get("overcharge") is None :
483                                entry.setOverChargeFactor(overcharge)
484                                changed[entry.Name]["overcharge"] = overcharge
485                               
486                        if balance :
487                            if changed[entry.Name].get("balance") is None :
488                                if balance.startswith("+") or balance.startswith("-") :
489                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
490                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
491                                    entry.setAccountBalance(newbalance, newlifetimepaid)
492                                else :
493                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
494                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
495                                    entry.setAccountBalance(balancevalue, newlifetimepaid)
496                                changed[entry.Name]["balance"] = balance
497                               
498                        for groupname in groupnames :       
499                            # not executed if option --ingroups is not used
500                            if groupname not in changed[entry.Name]["ingroups"] :
501                                group = self.storage.getGroup(groupname)
502                                if group.Exists :
503                                    self.storage.addUserToGroup(entry, group)
504                                    changed[entry.Name]["ingroups"].append(groupname)
505                                else :
506                                    self.printInfo(_("Group %s not found in the PyKota Storage.") % groupname)
507                       
508        # Now outputs the list of nonexistent users and groups               
509        for name in missingusers.keys() :
510            self.printInfo(_("Nonnexistent user %s") % name, level="warn")
511        for name in missinggroups.keys() :
512            self.printInfo(_("Nonnexistent group %s") % name, level="warn")
513       
514        # Now delete what has to be deleted               
515        for (name, entry) in todelete.items() :               
516            entry.delete()
517                     
518if __name__ == "__main__" : 
519    retcode = 0
520    try :
521        defaults = { \
522                     "printer" : "*", \
523                   }
524        short_options = "vhdo:c:l:b:i:naugrp:P:S:H:G:RU:"
525        long_options = ["help", "version", "overcharge=", "charge=", "delete", "limitby=", "balance=", "ingroups=", "noquota", "add", "users", "groups", "reset", "hardreset", "prototype=", "printer=", "softlimit=", "hardlimit=", "pgroups=", "used="]
526       
527        # Initializes the command line tool
528        editor = EdPyKota(doc=__doc__)
529        editor.deferredInit()
530       
531        # parse and checks the command line
532        (options, args) = editor.parseCommandline(sys.argv[1:], short_options, long_options)
533       
534        # sets long options
535        options["help"] = options["h"] or options["help"]
536        options["version"] = options["v"] or options["version"]
537        options["add"] = options["a"] or options["add"]
538        options["users"] = options["u"] or options["users"]
539        options["groups"] = options["g"] or options["groups"]
540        options["prototype"] = options["p"] or options["prototype"]
541        options["printer"] = options["P"] or options["printer"] or defaults["printer"]
542        options["softlimit"] = options["S"] or options["softlimit"]
543        options["hardlimit"] = options["H"] or options["hardlimit"] 
544        options["reset"] = options["r"] or options["reset"] 
545        options["noquota"] = options["n"] or options["noquota"]
546        options["limitby"] = options["l"] or options["limitby"]
547        options["balance"] = options["b"] or options["balance"] 
548        options["delete"] = options["d"] or options["delete"] 
549        options["ingroups"] = options["i"] or options["ingroups"]
550        options["charge"] = options["c"] or options["charge"]
551        options["pgroups"] = options["G"] or options["pgroups"]
552        options["hardreset"] = options["R"] or options["hardreset"] 
553        options["used"] = options["U"] or options["used"]
554        options["overcharge"] = options["o"] or options["overcharge"]
555       
556        if options["help"] :
557            editor.display_usage_and_quit()
558        elif options["version"] :
559            editor.display_version_and_quit()
560        elif options["users"] and options["groups"] :   
561            raise PyKotaToolError, _("incompatible options, see help.")
562        elif (options["add"] or options["prototype"]) and options["delete"] :   
563            raise PyKotaToolError, _("incompatible options, see help.")
564        elif (options["reset"] or options["hardreset"] or options["limitby"] or options["used"] or options["balance"] or options["overcharge"] or options["softlimit"] or options["hardlimit"]) and options["prototype"] :
565            raise PyKotaToolError, _("incompatible options, see help.")
566        elif options["noquota"] and (options["prototype"] or options["hardlimit"] or options["softlimit"]) :
567            raise PyKotaToolError, _("incompatible options, see help.")
568        elif options["groups"] and (options["balance"] or options["ingroups"] or options["used"] or options["overcharge"]) :
569            raise PyKotaToolError, _("incompatible options, see help.")
570        else :
571            retcode = editor.main(args, options)
572    except KeyboardInterrupt :       
573        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
574    except SystemExit :       
575        pass
576    except :
577        try :
578            editor.crashed("edpykota failed")
579        except :   
580            crashed("edpykota failed")
581        retcode = -1
582
583    try :
584        editor.storage.close()
585    except (TypeError, NameError, AttributeError) :   
586        pass
587       
588    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.