#!/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: 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-JSON-SCHEMA'] usage = "%prog [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): self.opts, self.args = self.op.parse_args(argv[1:]) if len(self.args) == 0: raise UsageError("No operation specified") operation = self.args.pop(0) self.node = Node.connect( opts_url(self.opts), self.opts.router, self.opts.timeout, opts_ssl_domain(self.opts)) if operation.upper() in self.operations: method = getattr(self, operation.lower().replace('-','_')) method() else: # Custom operation self.operation(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 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.""" check_args(self.args, 1) if self.args: self.opts.type = self.args[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.""" check_args(self.args, 1) if self.args: self.opts.type = self.args[0] self.print_json(self.call_node('get_annotations', 'type')) def get_attributes(self): """get-attributes [TYPE] List entity types with their attributes.""" check_args(self.args, 1) if self.args: self.opts.type = self.args[0] self.print_json(self.call_node('get_attributes', 'type')) def get_operations(self): """get-operations [TYPE] List entity types with their operations.""" check_args(self.args, 1) if self.args: self.opts.type = self.args[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 get_json_schema(self): """get-schema-json [INDENT] Get the router schema in JSON format. Optional integer indent for formatted output.""" check_args(self.args, 1, 0) indent = None if self.args: indent = self.args[0] print self.node.call(self.node.request( operation='GET-JSON-SCHEMA', identity='self', indent=indent)).body 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) print self.node.call(request).body if __name__ == "__main__": sys.exit(QdManage().main(sys.argv))