source: trunk/sara_cmt/bin/cmt @ 14171

Last change on this file since 14171 was 14171, checked in by ramonb, 12 years ago

sara_cmt/bin/cmt,
sara_cmt/sara_cmt/logger.py,
sara_cmt/sara_cmt/settings.py:

  • apply same ETC_PREPEND location as in setup.py to source code
  • see #14
  • Property svn:executable set to *
File size: 13.9 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 given ENTITY.
413
414            arguments:   set <ASSIGNMENTS>
415
416            The object will get values according to the given assignments.
417            Assignments could be formed like [<FK>__]<attr>=<value>""")
418    parser.add_option('-c', '--change',
419        action='callback',
420        callback=change,
421        type='string',
422        metavar='ENTITY',
423        nargs=1,
424        help="""Change value(s) of object(s) of given ENTITY.
425
426            arguments:   get <QUERY> set <ASSIGNMENTS>
427
428            The query, which consists out of one or more terms, is used to make
429            a selection of objects to change. These objects will be changed
430            according to the given assignments.""")
431    parser.add_option('-g', '--generate',
432        action='callback',
433        callback=generate,
434        type='string',
435        metavar='TEMPLATE',
436        nargs=1,
437        help='Render the given template')
438    parser.add_option('-l', '--list',
439        action='callback',
440        callback=show,
441        type='string',
442        metavar='ENTITY [ATTRIBUTE=VALUE]',
443        nargs=1,
444        help="""List object(s) of the given ENTITY.
445
446            arguments:   get <QUERY>
447
448            The query, which consists out of one or more terms, is used to make
449            a selection of objects to list.""")
450    parser.add_option('-r', '--remove',
451        action='callback',
452        callback=remove,
453        type='string',
454        metavar='ENTITY [ATTRIBUTE=VALUE]',
455        nargs=1,
456        help='Remove objects which reflect the given query')
457
458    # TODO: implement the following option(s)
459    parser.add_option('-v', '--verbose',
460        action='store_true',
461        dest='VERBOSE',
462        default=config_parser.getboolean('defaults', 'VERBOSE'))
463
464    (options, args) = parser.parse_args()
465    logger.debug('(options, args) = (%s, %s)' % (options, args))
466
467    if len(sys.argv) == 1:
468        # No arguments are given
469        parser.print_help()
470        return 1
471
472    return 0
473#
474# </Methods for processing of arguments>
475#
476#####
477
478
479if __name__ == '__main__':
480    status = main()
481    sys.exit(status)
Note: See TracBrowser for help on using the repository browser.