[24440] | 1 | """ |
---|
| 2 | Python bindings for the Moira library |
---|
| 3 | |
---|
| 4 | Moira is the Athena Service Management system. It serves as the |
---|
| 5 | central repository for information about users, groups hosts, print |
---|
| 6 | queues, and several other aspects of the Athena environment. |
---|
| 7 | """ |
---|
| 8 | |
---|
| 9 | import os |
---|
| 10 | import re |
---|
| 11 | |
---|
| 12 | import _moira |
---|
[24605] | 13 | from _moira import (auth, host, motd, noop, proxy, MoiraException) |
---|
[24440] | 14 | |
---|
| 15 | |
---|
| 16 | help_re = re.compile('([a-z0-9_, ]*) \(([a-z0-9_, ]*)\)(?: => ([a-z0-9_, ]*))?', |
---|
| 17 | re.I) |
---|
| 18 | et_re = re.compile(r'^\s*#\s*define\s+([A-Za-z0-9_]+)\s+.*?([0-9]+)') |
---|
| 19 | |
---|
| 20 | |
---|
| 21 | _arg_cache = {} |
---|
| 22 | _return_cache = {} |
---|
| 23 | _et_cache = {} |
---|
| 24 | |
---|
| 25 | |
---|
| 26 | def _clear_caches(): |
---|
| 27 | """Clear query caches. |
---|
| 28 | |
---|
| 29 | Clears all caches that may only be accurate for a particular Moira |
---|
| 30 | server or query version. |
---|
| 31 | """ |
---|
| 32 | _arg_cache.clear() |
---|
| 33 | _return_cache.clear() |
---|
| 34 | |
---|
| 35 | |
---|
[24605] | 36 | def connect(server=''): |
---|
| 37 | _moira.connect(server) |
---|
| 38 | version(-1) |
---|
| 39 | connect.__doc__ = _moira.connect.__doc__ |
---|
| 40 | |
---|
| 41 | |
---|
[24440] | 42 | def disconnect(): |
---|
| 43 | """Disconnect from the active Moira server""" |
---|
| 44 | _moira.disconnect() |
---|
| 45 | _clear_caches() |
---|
| 46 | |
---|
| 47 | |
---|
| 48 | def _load_help(handle): |
---|
| 49 | """Fetch info about the arguments and return values for a query. |
---|
| 50 | |
---|
| 51 | This uses the "_help" Moira query to retrieve names for the |
---|
| 52 | arguments and return values to and from a particular Moira |
---|
| 53 | query. These values are cached and used for translating arguments |
---|
| 54 | and return values into and out of dictionaries and into and out of |
---|
| 55 | tuples. |
---|
| 56 | """ |
---|
| 57 | help_string = ', '.join(query('_help', handle)[0]).strip() |
---|
| 58 | |
---|
| 59 | handle_str, arg_str, return_str = help_re.match(help_string).groups('') |
---|
| 60 | |
---|
| 61 | handles = handle_str.split(', ') |
---|
| 62 | args = arg_str.split(', ') |
---|
| 63 | returns = return_str.split(', ') |
---|
| 64 | |
---|
| 65 | for h in handles: |
---|
| 66 | _arg_cache[h] = args |
---|
| 67 | _return_cache[h] = returns |
---|
| 68 | |
---|
| 69 | |
---|
| 70 | def _list_query(handle, *args): |
---|
| 71 | """ |
---|
| 72 | Execute a Moira query and return the result as a list of tuples. |
---|
| 73 | |
---|
| 74 | This bypasses the tuple -> dict conversion done in moira.query() |
---|
| 75 | """ |
---|
| 76 | results = [] |
---|
| 77 | _moira._query(handle, results.append, *args) |
---|
| 78 | return results |
---|
| 79 | |
---|
| 80 | |
---|
| 81 | def _parse_args(handle, args, kwargs): |
---|
| 82 | """ |
---|
| 83 | Convert a set of arguments into the canonical Moira list form. |
---|
| 84 | |
---|
| 85 | Both query and access accept either positional arguments or |
---|
| 86 | keyword arguments, cross-referenced against the argument names |
---|
| 87 | given by the "_help" query. |
---|
| 88 | |
---|
| 89 | This function takes the args and kwargs as they're provided to |
---|
| 90 | either of those functions and returns a list of purely positional |
---|
| 91 | arguments that can be passed to the low-level Moira query |
---|
| 92 | function. |
---|
| 93 | """ |
---|
| 94 | if (handle not in _return_cache or |
---|
| 95 | not _return_cache[handle]): |
---|
| 96 | _load_help(handle) |
---|
| 97 | |
---|
| 98 | if kwargs: |
---|
| 99 | return tuple(kwargs.get(i, '*') |
---|
| 100 | for i in _arg_cache[handle]) |
---|
| 101 | else: |
---|
| 102 | return args |
---|
| 103 | |
---|
| 104 | |
---|
| 105 | def query(handle, *args, **kwargs): |
---|
| 106 | """ |
---|
| 107 | Execute a Moira query and return the result as a list of dicts. |
---|
| 108 | |
---|
| 109 | Arguments can be specified either as positional or keyword |
---|
| 110 | arguments. If specified by keyword, they are cross-referenced with |
---|
| 111 | the argument name given by the query "_help handle". |
---|
| 112 | |
---|
| 113 | All of the real work of Moira is done in queries. There are over |
---|
| 114 | 100 queries, each of which requires different arguments. The |
---|
| 115 | arguments to the queries should be passed as separate arguments to |
---|
| 116 | the function. |
---|
| 117 | """ |
---|
| 118 | if handle.startswith('_'): |
---|
| 119 | return _list_query(handle, *args) |
---|
| 120 | else: |
---|
| 121 | fmt = kwargs.pop('fmt', dict) |
---|
| 122 | |
---|
| 123 | args = _parse_args(handle, args, kwargs) |
---|
| 124 | |
---|
| 125 | plain_results = _list_query(handle, *args) |
---|
| 126 | results = [] |
---|
| 127 | |
---|
| 128 | for r in plain_results: |
---|
| 129 | results.append(fmt(zip(_return_cache[handle], r))) |
---|
| 130 | |
---|
| 131 | return results |
---|
| 132 | |
---|
| 133 | |
---|
| 134 | def access(handle, *args, **kwargs): |
---|
| 135 | """ |
---|
| 136 | Determine if the user has the necessary access to perform a query. |
---|
| 137 | |
---|
| 138 | As with moira.query, arguments can be specified either as |
---|
| 139 | positional or keyword arguments. If specified as keywords, they |
---|
| 140 | are cross-referenced with the argument names given by the "_help" |
---|
| 141 | query. |
---|
| 142 | |
---|
| 143 | This function returns True if the user, as currently |
---|
| 144 | authenticated, would be allowed to perform the query with the |
---|
| 145 | given arguments, and False otherwise. |
---|
| 146 | """ |
---|
| 147 | args = _parse_args(handle, args, kwargs) |
---|
| 148 | |
---|
| 149 | try: |
---|
| 150 | _moira._access(handle, *args) |
---|
| 151 | return True |
---|
| 152 | except MoiraException, e: |
---|
| 153 | if e.code != errors()['MR_PERM']: |
---|
| 154 | raise |
---|
| 155 | return False |
---|
| 156 | |
---|
| 157 | |
---|
[24605] | 158 | def version(ver): |
---|
| 159 | # Changing the Moira version can change a query's arguments and |
---|
| 160 | # return values |
---|
| 161 | _clear_caches() |
---|
| 162 | return _moira.version(ver) |
---|
| 163 | version.__doc__ = _moira.version.__doc__ |
---|
| 164 | |
---|
| 165 | |
---|
[24440] | 166 | def errors(): |
---|
| 167 | """ |
---|
| 168 | Return a dict of Moira error codes. |
---|
| 169 | |
---|
| 170 | This function parses error codes out of the Moira header files and |
---|
| 171 | returns a dictionary of those error codes. |
---|
| 172 | |
---|
| 173 | The value that's returned should be treated as immutable. It's a |
---|
| 174 | bug that it isn't. |
---|
| 175 | """ |
---|
| 176 | if not _et_cache: |
---|
| 177 | for prefix in ('/usr/include', |
---|
| 178 | '/sw/include'): |
---|
| 179 | header = os.path.join(prefix, 'moira/mr_et.h') |
---|
| 180 | if os.path.exists(header): |
---|
| 181 | for line in open(header): |
---|
| 182 | m = et_re.search(line) |
---|
| 183 | if m: |
---|
| 184 | errname, errcode = m.groups() |
---|
| 185 | _et_cache[errname] = int(errcode) |
---|
| 186 | |
---|
| 187 | return _et_cache |
---|
| 188 | |
---|
| 189 | |
---|
| 190 | __all__ = ['connect', 'disconnect', 'auth', 'host', 'motd', 'noop', 'query', |
---|
[24605] | 191 | 'proxy', 'version', 'access', 'errors', '_list_query', |
---|
| 192 | 'MoiraException'] |
---|