source: trunk/sara_cmt/bin/cmt.py @ 14151

Last change on this file since 14151 was 14151, checked in by sil, 12 years ago
  • Made a 'bin'- and 'etc'-directory and moved the CMT commandline-script and its config-files to these directories.
  • Modified setup.py to copy some files to /usr/local/bin and /etc/cmt (for now)
  • Hardcoded some references to config-files (for the time being)

See #11

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