| #!/usr/bin/env python |
| |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| |
| import sys, json, re |
| import qpid_dispatch_site |
| from qpid_dispatch.management.client import Node, Url |
| from collections import Mapping, Sequence |
| from optparse import OptionGroup |
| from qpid_dispatch_internal.tools.command import OptionParser, Option, UsageError, connection_options, check_args, main, opts_ssl_domain, opts_url |
| |
| def attr_split(attrstr): |
| """Split an attribute string of the form name=value or name to indicate None""" |
| nv = attrstr.split("=", 1) |
| if len(nv) == 1: return [nv[0], None] |
| else: |
| if nv[1] == "true": nv[1] = True |
| if nv[1] == "false": nv[1] = False |
| return nv |
| |
| class QdManage(): |
| |
| def __init__(self): |
| |
| self.operations = ['QUERY', 'CREATE', 'READ', 'UPDATE', 'DELETE', |
| 'GET-TYPES', 'GET-OPERATIONS', 'GET-ATTRIBUTES', 'GET-ANNOTATIONS', |
| 'GET-MGMT-NODES', 'GET-SCHEMA', 'GET-LOG'] |
| |
| usage = "%prog <operation> [options...] [arguments...]" |
| description = "Standard operations: %s. Use GET-OPERATIONS to find additional operations." \ |
| % (", ".join(self.operations)) |
| |
| op = OptionParser(usage=usage, option_class=Option, description=description) |
| op.add_option('--type', help='Type of entity to operate on.') |
| op.add_option('--name', help='Name of entity to operate on.') |
| op.add_option('--identity', help='Identity of entity to operate on.', metavar="ID") |
| op.add_option("--indent", type="int", default=2, |
| help="Pretty-printing indent. -1 means don't pretty-print (default %default)") |
| op.add_option('--stdin', action='store_true', |
| help='Read attributes as JSON map or list of maps from stdin.') |
| op.add_option('--body', help='JSON value to use as body of a non-standard operation call.') |
| op.add_option('--properties', help='JSON map to use as properties for a non-standard operation call.') |
| op.add_option_group(connection_options(op)) |
| self.op = op |
| |
| def run(self, argv): |
| # Make all args unicode to avoid encoding arg values as AMQP bytes. |
| self.opts, self.args = self.op.parse_args([unicode(x) for x in argv[1:]]) |
| if self.opts.indent == -1: self.opts.indent = None |
| if len(self.args) == 0: raise UsageError("No operation specified") |
| self.node = Node.connect( |
| opts_url(self.opts), self.opts.router, self.opts.timeout, opts_ssl_domain(self.opts)) |
| operation = self.args.pop(0) |
| method = operation.lower().replace('-','_') |
| if operation.upper() in self.operations and hasattr(self, method): |
| getattr(self, method)() # Built-in operation |
| else: |
| self.operation(operation) # Custom operation |
| |
| def main(self, argv): |
| return main(self.run, argv, self.op) |
| |
| def print_json(self, data): |
| """Print data as JSON""" |
| print json.dumps(data, indent=self.opts.indent) |
| |
| def print_result(self, result): |
| """Print a string result as-is, else try json dump, else print as-is""" |
| if not result: return |
| if isinstance(result, basestring): |
| print result |
| else: |
| try: |
| self.print_json(result) |
| except ValueError: |
| print result |
| |
| def call_node(self, method, *argnames, **kwargs): |
| """Call method on node, use opts named in argnames""" |
| names = set(argnames) |
| for k in self.opts.__dict__: |
| if k in names and hasattr(self.opts, k): |
| kwargs[k] = getattr(self.opts, k) |
| return getattr(self.node, method)(**kwargs) |
| |
| def call_bulk(self, func): |
| """Call function for attributes from stdin or --attributes option""" |
| if self.opts.stdin: |
| data = json.load(sys.stdin) |
| if isinstance(data, Mapping): |
| self.print_json(func(data).attributes) |
| elif isinstance(data, Sequence): |
| self.print_json([func(attrs).attributes for attrs in data]) |
| else: raise ValueError("stdin is not a JSON map or list") |
| else: |
| self.print_json(func(self.opts.attributes).attributes) |
| |
| def query(self): |
| """query [ATTR...] Print attributes of entities.""" |
| if self.args: self.opts.attribute_names = self.args |
| result = self.call_node('query', 'type', 'attribute_names') |
| self.print_json(result.get_dicts(clean=True)) |
| |
| def create(self): |
| """create [ATTR=VALUE...] Create a new entity.""" |
| if self.args: |
| self.opts.attributes = dict(attr_split(arg) for arg in self.args) |
| self.call_bulk(lambda attrs: self.call_node('create', 'type', 'name', attributes=attrs)) |
| |
| def read(self): |
| """read Print attributes of selected entity.""" |
| check_args(self.args, 0) |
| self.print_json(self.call_node('read', 'type', 'name', 'identity').attributes) |
| |
| def update(self): |
| """update [ATTR=VALUE...] Update an entity.""" |
| if self.args: |
| self.opts.attributes = dict(attr_split(arg) for arg in self.args) |
| self.call_bulk( |
| lambda attrs: self.call_node('update', 'type', 'name', 'identity', attributes=attrs)) |
| |
| def delete(self): |
| """delete Delete an entity""" |
| check_args(self.args, 0) |
| self.call_node('delete', 'type', 'name', 'identity') |
| |
| def get_types(self): |
| """get-types [TYPE] List entity types with their base types.""" |
| self.opts.type = check_args(self.args, 1)[0] |
| self.print_json(self.call_node('get_types', 'type')) |
| |
| def get_annotations(self): |
| """get-annotations [TYPE] List entity types with the annotations they implement.""" |
| self.opts.type = check_args(self.args, 1)[0] |
| self.print_json(self.call_node('get_annotations', 'type')) |
| |
| def get_attributes(self): |
| """get-attributes [TYPE] List entity types with their attributes.""" |
| self.opts.type = check_args(self.args, 1)[0] |
| self.print_json(self.call_node('get_attributes', 'type')) |
| |
| def get_operations(self): |
| """get-operations [TYPE] List entity types with their operations.""" |
| self.opts.type = check_args(self.args, 1)[0] |
| self.print_json(self.call_node('get_operations', 'type')) |
| |
| def get_mgmt_nodes(self): |
| """get-mgmt-nodes List of other known management nodes""" |
| check_args(self.args, 0) |
| self.print_json(self.call_node('get_mgmt_nodes')) |
| |
| def json_arg(self, value): |
| try: |
| return json.loads(value) |
| except ValueError, e: |
| if not re.search(r'["{}\[\]]', value): # Doesn't look like attempted JSON. |
| return value # Just treat as plain string |
| raise ValueError("Invalid JSON '%s': %s" % (value, e)) |
| |
| def operation(self, operation): |
| """operation [ATTR=VALUE...] Call custom operation with ATTR=VALUE as request properties. Use --body and --properties if specified.""" |
| properties = dict(attr_split(arg) for arg in self.args or []) |
| if self.opts.properties: |
| properties.update(self.json_arg(self.opts.properties)) |
| body = None |
| if self.opts.body: |
| body = self.json_arg(self.opts.body) |
| request = self.call_node('request', 'type', 'name', 'identity', operation=operation, body=body, **properties) |
| self.print_result(self.node.call(request).body) |
| |
| if __name__ == "__main__": |
| sys.exit(QdManage().main(sys.argv)) |