source: trunk/sara_cmt/bin/cmt @ 14174

Last change on this file since 14174 was 14174, checked in by sil, 12 years ago

made some corrections in the help-texts of the option parser

  • Property svn:executable set to *
File size: 14.1 KB
Line 
1#!/usr/bin/env python
2
3#####
4#
5# <Setup environment for Django's DB API>
6#
7from sara_cmt import settings
8from django.core.management import setup_environ
9setup_environ(settings)
10import os
11os.environ['DJANGO_SETTINGS_MODULE'] = settings.__name__
12#
13# </Setup environment for Django's DB API>
14#
15#####
16
17#####
18#
19# <Some imports for our Django-template related action>
20#
21from django.template.loader import render_to_string
22from django.template import TemplateDoesNotExist
23import re
24#
25# </Some imports for our Django-template related action>
26#
27#####
28
29from types import ListType, StringTypes
30
31import datetime
32
33import ConfigParser
34import sys
35
36from sara_cmt.django_cli import ModelExtension, ObjectManager, QueryManager, \
37    logger, parser
38
39import sara_cmt.cluster.models
40
41
42from django.db.models import get_model
43
44
45def search_model(value):
46    return get_model('cluster', value)
47
48
49#####
50#
51# <Decorators/etc/cmt/>
52#
53
54
55def crud_validate(func):
56    """
57        Validate the entity given as an argument to CRUD-functions
58    """
59
60    def crudFunc(option, opt_str, value, parser, *args, **kwargs):
61        model = search_model(value)
62        if model:
63            return func(option, opt_str, value, parser, *args, **kwargs)
64        else:
65            logger.error('Entity %s not known.' % (value.__repr__()))
66            sys.exit(1)
67    return crudFunc
68#
69# </Decorators>
70#
71#####
72
73
74
75#####
76#
77# <CMT-specific settings from file>
78#
79
80# Instantiate ConfigParser
81#TODO: think about a (better) way to make this dynamic:
82import site
83
84if site.sys.prefix in [ '/usr', '/' ]:
85    ETC_PREPEND = ''
86else:
87    ETC_PREPEND = site.sys.prefix
88
89configfile = '%s/etc/cmt/cmt.conf' % ETC_PREPEND
90config_parser = ConfigParser.ConfigParser()
91config_parser.optionxform = lambda x: x
92# ^^ Hack for case-insensitivity, reference:
93#    http://www.finalcog.com/python-config-parser-lower-case-names
94config_parser.read(configfile)
95
96# Setup the logger for logging
97loglevel = config_parser.get('defaults', 'LOGLEVEL')
98if loglevel == 'NOTSET' and settings.DEBUG == True:
99    logger.setLevel(config_parser.getint('loglevels', 'DEBUG'))
100else:
101    logger.setLevel(config_parser.getint('loglevels', loglevel))
102
103
104# Collect package information
105CMTSARA_VERSION = config_parser.get('info', 'version').strip("'")
106CMTSARA_DESCRIPTION = config_parser.get('info', 'description').strip("'")
107
108#
109# </CMT-specific settings from file>
110#
111#####
112
113
114
115#####
116#
117# <Managers for database related interactions>
118#
119object_mgr = ObjectManager()
120query_mgr = QueryManager()
121#
122# </Managers for database related interactions>
123#
124#####
125
126
127
128#####
129#
130# <CRUD methods>
131#
132
133
134@crud_validate
135def add(option, opt_str, value, parser, *args, **kwargs):
136    """
137        Add an object according to the given values.
138
139        When the INTERACTIVE-flag has been set and an object is not complete
140        yet, the user should be given the opportunity to add data to the
141        missing fields on an interactive manner.
142    """
143    my_args = collect_args(option, parser)    # get method-specific args-list
144    query_mgr.push_args(my_args, search_model(value), ['set'])
145    query = query_mgr.get_query()
146
147    new_obj = search_model(value)()
148    logger.debug('Initiated a new %s' % new_obj.__class__.__name__)
149
150    new_obj.setattrs_from_dict(query['set'])
151
152    # Complete if needed (only in interactive mode)
153    if parser.values.INTERACTIVE:
154        new_obj.interactive_completion()
155
156    # Save object
157    save_msg = 'Added a new %s'%value
158    if not parser.values.DRYRUN:
159        try:
160            new_obj.save()
161        except Exception, e:
162            logger.error('Could not add new %s: '%(value,e))
163    else:
164        logger.info('[DRYRUN] %s'%save_msg)
165
166
167@crud_validate
168def show(option, opt_str, value, parser, *args, **kwargs):
169    """
170        Show the objects which match the given values (queries).
171    """
172    # Split all given args to dict
173    my_args = collect_args(option, parser)
174    query_mgr.push_args(my_args, search_model(value), ['get'])
175    query = query_mgr.get_query()
176
177    objects = object_mgr.get_objects(query)
178
179    # !!! TODO: either print short, or print long lists !!!
180    # !!! TODO: use config files for fields (not) to print (maybe
181    for _object in objects:
182        ModelExtension.display(_object)
183
184
185@crud_validate
186def change(option, opt_str, value, parser, *args, **kwargs):
187    """
188        Search for an object which matches the given values, and change
189        it/them.
190    """
191    my_args = collect_args(option, parser)
192    query_mgr.push_args(my_args, search_model(value), ['get', 'set'])
193    query = query_mgr.get_query()
194
195    objects = object_mgr.get_objects(query)
196
197    if objects:
198        # Temporary try-except. See subtrac.sara.nl/osd/interne-services/ticket/164
199        try:
200            logger.info('Found %s entities matching query: %s'
201                % (len(objects), ', '.join([_object.label for _object in objects])))
202        except AttributeError:
203            logger.info('Found %s entities matching query: %s'
204                % (len(objects), ', '.join([_object.__unicode__() for _object in objects])))
205        confirmed = not parser.values.INTERACTIVE or \
206            raw_input('Are you sure? [Yn] ')
207        if confirmed in ['', 'y', 'Y', True]:
208            for _object in objects:
209                attr_set_msg = 'Attributes has been set: %s' % _object
210                if not parser.values.DRYRUN:
211                    _object.setattrs_from_dict(query['set'])
212                    logger.debug(attr_set_msg)
213                else:
214                    logger.debug('[DRYRUN] %s' % attr_set_msg)
215        else:
216            logger.info('Change has been cancelled')
217
218
219@crud_validate
220def remove(option, opt_str, value, parser, *args, **kwargs):
221    """
222        Remove the objects which match the given values (queries).
223    """
224    my_args = collect_args(option, parser)
225    query_mgr.push_args(my_args, search_model(value), ['get'])
226    query = query_mgr.get_query()
227
228    objects = object_mgr.get_objects(query)
229
230    if objects:
231        logger.info('Found %s objects matching query: %s'\
232            % (len(objects), ', '.join([_object.__str__()
233                for _object in objects])))
234        confirmation = not parser.values.INTERACTIVE or \
235            raw_input('Are you sure? [Yn] ')
236        print 'confirmation', confirmation
237        # Delete and log
238        if confirmation in ['', 'y', 'Y', True]:
239            logger.info('deleting...')
240            for _object in objects:
241                del_msg = 'Deleted %s' % _object
242                if not parser.values.DRYRUN:
243                    _object.delete()
244                    logger.info(del_msg)
245                else:
246                    logger.info('[DRYRUN] %s' % del_msg)
247
248    else:
249        logger.info('No existing objects found matching query')
250#
251# </CRUD methods>
252#
253#####
254
255
256def generate(option, opt_str, value, parser, *args, **kwargs):
257    from django.template import Context
258
259    # Save full path of templatefile to generate
260    template_filename = value
261
262    # Make a dict with filenames of the available templates
263    files = [f for f in os.listdir(settings.CMT_TEMPLATES_DIR) if f[-4:]=='.cmt']
264    files.sort()
265
266    fdict = {}
267    i = 1
268    for f in files:
269        fdict[i] = f
270        i+=1
271
272    if template_filename[-4:] != '.cmt':
273        template_filename += '.cmt'
274
275    # Loop until a valid template has been chosen by the user
276    while template_filename not in fdict.values():
277        logger.warning("File '%s' not known"%template_filename)
278
279        # Give a numbered overview of the available templates
280        for key,val in fdict.items():
281            print '%s : %s'%(str(key).rjust(2),val)
282        logger.debug('fdict: %s'%fdict.values())
283        template_filename = raw_input('\nChoose: ')
284
285        # If number given, lookup the filename in the dictionary
286        if template_filename.isdigit():
287            num = int(template_filename)
288            if num <= len(fdict):
289                template_filename = fdict[num]
290                logger.debug('filename: %s'%template_filename)
291            else:
292                continue
293        # Else check for the extension
294        elif template_filename[-4:] != '.cmt':
295            template_filename += '.cmt'
296
297        logger.debug('%s (%s)'%(template_filename,type(template_filename)))
298
299    template_fullpath = os.path.join(settings.CMT_TEMPLATES_DIR, template_filename)
300
301    try:
302        # Initialize a Context to render the template with
303        template_data = {}
304        template_data['version'] = CMTSARA_VERSION
305        template_data['svn_id'] = '$Id:$'
306        template_data['svn_url'] = '$URL:$'
307        template_data['input'] = template_fullpath
308        template_data['__template_outputfiles__'] = {} # reserved for data to write to files
309        context = Context(template_data)
310
311        template_data['stores'] = template_data['__template_outputfiles__'] # to stay bw compatible with ramon's code (temporary)
312       
313        rendered_string = render_to_string(template_filename, context_instance=context)
314        # While rendering the template there are variables added to the
315        # Context, so these can be used for post-processing.
316        logger.debug('<RESULT>\n%s\n</RESULT>'%rendered_string)
317
318    except IOError, e:
319        logger.error('Template does not exist: %s' % e)
320
321    for outputfile, content in context['__template_outputfiles__'].items():
322        write_msg = 'Writing outputfile: %s' % outputfile
323        created_msg = 'Outputfile(s) created: %s' % outputfile
324
325        if not parser.values.DRYRUN: # Write output file(s)
326            try:
327                logger.info(write_msg)
328                f = open(outputfile, 'w')
329                f.writelines(content)
330                f.close()
331                logger.info(created_msg)
332            except IOError, e:
333                logger.error('Failed creating outputfile: %s' % e)
334            except KeyError, e:
335                logger.error('No outputfiles defined in template')
336        else:
337            write_msg = '[DRYRUN] %s' % write_msg
338            created_msg = '[DRYRUN] %s' % created_msg
339            logger.info(write_msg)
340            logger.info(created_msg)
341
342    if not parser.values.DRYRUN:
343        try:
344            for script in context['epilogue']:
345                logger.info('Executing epilogue script')
346                os.system(script)
347                logger.info('Finished epilogue script')
348        except KeyError, e:
349            logger.debug('No epilogue script found')
350    return
351
352
353#####
354#
355# <Methods for processing of arguments>
356#
357
358
359def collect_args(option, parser):
360    """
361        Collects the arguments belonging to the given option, and removes them
362        from the arguments-datastructue. Returns the collected arguments as a
363        list.
364    """
365    collected = []
366
367    if parser.rargs:
368
369        def floatable(str):
370            try:
371                float(str)
372                return True
373            except ValueError:
374                return False
375
376        for arg in parser.rargs:
377            # stop on --foo like options
378            if arg[:2] == '--' and len(arg) > 2:
379                break
380            # stop on -a, but not on -3 or -3.0
381            if arg[:1] == '-' and len(arg) > 1 and not floatable(arg):
382                break
383            collected.append(arg)
384
385        parser.largs.extend(parser.rargs[:len(collected)])
386        del parser.rargs[:len(collected)]
387        setattr(parser.values, option.dest, collected)
388
389    logger.debug('Collected arguments for %s: %s' % (option, collected))
390    return collected
391
392
393def main():
394    parser.version = CMTSARA_VERSION
395    parser.description = CMTSARA_DESCRIPTION
396
397    parser.add_option('-n', '--dry-run',
398        action='store_true',
399        dest='DRYRUN',
400        default=config_parser.getboolean('defaults', 'DRYRUN'),
401        help='This flag has to be given before -[aclmr]')
402    parser.add_option('--script',
403        action='store_false',
404        dest='INTERACTIVE',
405        default=config_parser.getboolean('defaults', 'INTERACTIVE'))
406    parser.add_option('-a', '--add',
407        action='callback',
408        callback=add,
409        type='string',
410        metavar='ENTITY',
411        nargs=1,
412        help='Add an object of the given ENTITY. '\
413            'Extra arguments should be given like `set ASSIGNMENTS`, '\
414            'where ASSIGNMENTS are formatted like `ATTR=VAL`')
415    parser.add_option('-c', '--change',
416        action='callback',
417        callback=change,
418        type='string',
419        metavar='ENTITY',
420        nargs=1,
421        help='Change value(s) of object(s) of the given ENTITY. '\
422            'Arguments should be given like `get QUERY set ASSIGNMENTS`, '\
423            'where both QUERY and ASSIGNMENTS consist out of one or more '\
424            'terms formatted like `ATTR=VAL`. Only objects matching QUERY '\
425            'will be changed according to the given ASSIGNMENTS')
426    parser.add_option('-g', '--generate',
427        action='callback',
428        callback=generate,
429        type='string',
430        metavar='TEMPLATE',
431        nargs=1,
432        help='Render the given TEMPLATE, and store the result if specified')
433    parser.add_option('-l', '--list',
434        action='callback',
435        callback=show,
436        type='string',
437        metavar='ENTITY',
438        nargs=1,
439        help='List object(s) of the given ENTITY. Arguments should be given '\
440            'like `get QUERY`, where QUERY consists out of one or more terms '\
441            'formatted like `ATTR=VAL`')
442    parser.add_option('-r', '--remove',
443        action='callback',
444        callback=remove,
445        type='string',
446        metavar='ENTITY',
447        nargs=1,
448        help='Remove object(s) of the given ENTITY. '\
449            'Arguments should be given like `get QUERY`, where QUERY '\
450            'consists out of one or more terms formatted like `ATTR=VAL`. '\
451            'Only objects matching QUERY will be listed')
452
453    # TODO: implement the following option(s)
454    parser.add_option('-v', '--verbose',
455        action='store_true',
456        dest='VERBOSE',
457        default=config_parser.getboolean('defaults', 'VERBOSE'))
458
459    (options, args) = parser.parse_args()
460    logger.debug('(options, args) = (%s, %s)' % (options, args))
461
462    if len(sys.argv) == 1:
463        # No arguments are given
464        parser.print_help()
465        return 1
466
467    return 0
468#
469# </Methods for processing of arguments>
470#
471#####
472
473
474if __name__ == '__main__':
475    status = main()
476    sys.exit(status)
Note: See TracBrowser for help on using the repository browser.