blob: 695487296bdf0b234aa077f8be27ca62e501efd4 [file] [log] [blame]
#!/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))