source: trunk/sara_cmt/bin/cmt @ 14154

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

See #11

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