File: batch.py

package info (click to toggle)
freeipa 4.12.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 100,668 kB
  • sloc: python: 298,952; javascript: 71,606; ansic: 49,369; sh: 6,547; makefile: 2,553; xml: 343; sed: 16
file content (211 lines) | stat: -rw-r--r-- 7,487 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# Authors:
#   Adam Young <ayoung@redhat.com>
#   Rob Crittenden <rcritten@redhat.com>
#
# Copyright (c) 2010  Red Hat
# See file 'copying' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import logging

import six

from ipalib import api, errors
from ipalib import Command
from ipalib.frontend import Local
from ipalib.parameters import Str, Dict
from ipalib.output import Output
from ipalib.text import _
from ipalib.request import context
from ipalib.plugable import Registry

__doc__ = _("""
Plugin to make multiple ipa calls via one remote procedure call

To run this code in the lite-server

curl   -H "Content-Type:application/json"          -H "Accept:application/json" -H "Accept-Language:en"        --negotiate -u :          --cacert /etc/ipa/ca.crt           -d  @batch_request.json -X POST       http://localhost:8888/ipa/json

where the contents of the file batch_request.json follow the below example

{"method":"batch","params":[[
        {"method":"group_find","params":[[],{}]},
        {"method":"user_find","params":[[],{"whoami":"true","all":"true"}]},
        {"method":"user_show","params":[["admin"],{"all":true}]}
        ],{}],"id":1}

The format of the response is nested the same way.  At the top you will see
  "error": null,
    "id": 1,
    "result": {
        "count": 3,
            "results": [


And then a nested response for each IPA command method sent in the request

""")

if six.PY3:
    unicode = str

logger = logging.getLogger(__name__)

register = Registry()

@register()
class batch(Command):
    __doc__ = _('Make multiple ipa calls via one remote procedure call')
    NO_CLI = True

    takes_args = (
        Dict('methods*',
            doc=_('Nested Methods to execute'),
        ),
    )

    takes_options = (
        Str('keeponly*',
            doc=_('Keep specified attributes in the output, '
                  'remove everything else.'),
        ),
    )

    has_output = (
        Output('count', int, doc=''),
        Output('results', (list, tuple), doc='')
    )

    def _validate_request(self, request):
        """
        Check that an individual request in a batch is parseable and the
        commands exists.
        """
        if 'method' not in request:
            raise errors.RequirementError(name='method')
        if 'params' not in request:
            raise errors.RequirementError(name='params')
        name = request['method']
        if (name not in self.api.Command or
                isinstance(self.api.Command[name], Local)):
            raise errors.CommandError(name=name)

        # If params are not formated as a tuple(list, dict)
        # the following lines will raise an exception
        # that triggers an internal server error
        # Raise a ConversionError instead to report the issue
        # to the client
        try:
            a, kw = request['params']
            newkw = dict((str(k), v) for k, v in kw.items())
            api.Command[name].args_options_2_params(*a, **newkw)
        except (AttributeError, ValueError, TypeError):
            raise errors.ConversionError(
                name='params',
                error=_(u'must contain a tuple (list, dict)'))
        except Exception as e:
            raise errors.ConversionError(
                name='params',
                error=str(e))

    def _repr_iter(self, **params):
        """
        Iterate through the request and use the Command _repr_intr so
        that sensitive information (passwords) is not exposed.

        In case of a malformatted request redact the entire thing.
        """
        exceptions = False
        for arg in (params.get('methods', [])):
            try:
                self._validate_request(arg)
            except Exception:
                # redact the whole request since we don't know what's in it
                exceptions = True
                yield u'********'
                continue

            name = arg['method']
            a, kw = arg['params']
            newkw = dict((str(k), v) for k, v in kw.items())
            param = api.Command[name].args_options_2_params(
                *a, **newkw)

            yield '{}({})'.format(
                api.Command[name].name,
                ', '.join(api.Command[name]._repr_iter(**param))
            )

        if exceptions:
            logger.debug('batch: %s',
                         ', '.join(super(batch, self)._repr_iter(**params)))

    def execute(self, methods=None, **options):
        results = []
        op_account = getattr(context, 'principal', '[autobind]')
        keeponly = options.get("keeponly", None)
        for arg in (methods or []):
            params = dict()
            name = None
            try:
                self._validate_request(arg)
                name = arg['method']
                a, kw = arg['params']
                newkw = dict((str(k), v) for k, v in kw.items())
                params = api.Command[name].args_options_2_params(
                    *a, **newkw)
                newkw.setdefault('version', options['version'])

                result = api.Command[name](*a, **newkw)
                logger.info(
                    '%s: batch: %s(%s): SUCCESS',
                    op_account,
                    name,
                    ', '.join(api.Command[name]._repr_iter(**params))
                )
                result['error'] = None
                res = result.get('result', None)
                if keeponly is not None and isinstance(res, dict):
                    result["result"] = dict(
                        filter(lambda x: x[0] in keeponly, res.items())
                    )
            except Exception as e:
                if (isinstance(e, errors.RequirementError) or
                        isinstance(e, errors.CommandError) or
                        isinstance(e, errors.ConversionError)):
                    logger.info(
                        '%s: batch: %s',
                        op_account,
                        e.__class__.__name__
                    )
                else:
                    logger.info(
                        '%s: batch: %s(%s): %s',
                        op_account, name,
                        ', '.join(api.Command[name]._repr_iter(**params)),
                        e.__class__.__name__
                    )
                if isinstance(e, errors.PublicError):
                    reported_error = e
                else:
                    reported_error = errors.InternalError()
                result = dict(
                    error=reported_error.strerror,
                    error_code=reported_error.errno,
                    error_name=unicode(type(reported_error).__name__),
                    error_kw=reported_error.kw,
                )
            results.append(result)
        return dict(count=len(results) , results=results)