blob: 9306ff32d867f093b97014c677d0be558e117055 [file] [log] [blame]
#!/usr/bin/env ${PY_STRING}
#
# Licensed to the Apache Software Foundation (ASF) undeugr 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.
#
from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
import sys
import json
import re
import qpid_dispatch_site
import argparse
try:
from collections.abc import Mapping, Sequence
except ImportError:
from collections import Mapping, Sequence
from qpid_dispatch.management.client import Node, Url
from qpid_dispatch_internal.tools.command import (UsageError, _qdmanage_parser, check_args,
main, opts_ssl_domain, opts_url, opts_sasl)
from qpid_dispatch_internal.management.qdrouter import QdSchema
from qpid_dispatch_internal.compat import PY_TEXT_TYPE
from qpid_dispatch_internal.compat import PY_STRING_TYPE
INTEGER_TYPE = "integer"
def attr_split(attrstr, qd_schema, type):
"""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
elif nv[1] == "false":
nv[1] = False
elif type and qd_schema.entity_type(type) and qd_schema.entity_type(type).attribute(nv[0]).type == INTEGER_TYPE:
nv[1] = int(nv[1])
return nv
class QdManage():
def __init__(self):
self.qd_schema = QdSchema()
self.prefix = 'org.apache.qpid.dispatch.'
self.operations = ['QUERY', 'CREATE', 'READ', 'UPDATE', 'DELETE',
'GET-TYPES', 'GET-OPERATIONS', 'GET-ATTRIBUTES', 'GET-ANNOTATIONS',
'GET-MGMT-NODES', 'GET-SCHEMA', 'GET-LOG']
self.op = _qdmanage_parser(self.operations)
def clean_opts(self):
attr_type = self.opts.type
if attr_type:
self.opts.type = self.long_type(attr_type)
def run(self, argv):
# Make all args unicode to avoid encoding arg values as AMQP bytes.
al = [x.decode() if not isinstance(x, PY_TEXT_TYPE) else x
for x in argv[1:]]
self.opts, self.args = self.op.parse_known_args(al)
self.clean_opts()
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),
opts_sasl(self.opts),
edge_router=self.opts.edge_router)
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("%s" % 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, (PY_STRING_TYPE, PY_TEXT_TYPE)):
print("%s" % result)
else:
try:
self.print_json(result)
except ValueError:
print("%s" % result)
def long_type(self, type):
if not type or "." in type:
return type
if type in ('link', 'node'):
return self.prefix + "router." + type
if type in ('linkRoute', 'address', 'autoLink'):
return self.prefix + "router.config." + type
return self.prefix + type
def call_node(self, method, *argnames, **kwargs):
"""Call method on node, use opts named in argnames"""
names = set(argnames)
attributes = kwargs.get('attributes')
if attributes and attributes.get('type'):
attributes['type'] = self.long_type(attributes['type'])
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, self.qd_schema, self.opts.type) 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, self.qd_schema, self.opts.type) 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."""
if not self.opts.type:
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."""
if not self.opts.type:
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."""
if not self.opts.type:
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."""
if not self.opts.type:
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 as 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, self.qd_schema, self.opts.type) 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))