Remove cli.py, move all to solar/solar/cli.py, migrate all to click

This commit is contained in:
Przemyslaw Kaminski 2015-06-10 10:32:05 +02:00
parent 8dd75ed449
commit d2a4e50ff1
5 changed files with 340 additions and 333 deletions

236
cli.py
View File

@ -1,236 +0,0 @@
#!/usr/bin/env python
import click
import json
#import matplotlib
#matplotlib.use('Agg') # don't show windows
#import matplotlib.pyplot as plt
import networkx as nx
import os
import subprocess
from solar.core import actions as xa
from solar.core import resource as xr
from solar.core import signals as xs
from solar import operations
from solar import state
from solar.interfaces.db import get_db
db = get_db()
@click.group()
def cli():
pass
def init_cli_resource():
@click.group()
def resource():
pass
cli.add_command(resource)
@click.command()
@click.argument('resource_path')
@click.argument('action_name')
def action(action_name, resource_path):
print 'action', resource_path, action_name
r = xr.load(resource_path)
xa.resource_action(r, action_name)
resource.add_command(action)
@click.command()
@click.argument('name')
@click.argument('base_path')
@click.argument('dest_path')
@click.argument('args')
def create(args, dest_path, base_path, name):
print 'create', name, base_path, dest_path, args
args = json.loads(args)
xr.create(name, base_path, dest_path, args)
resource.add_command(create)
@click.command()
@click.argument('resource_path')
@click.argument('tag_name')
@click.option('--add/--delete', default=True)
def tag(add, tag_name, resource_path):
print 'Tag', resource_path, tag_name, add
r = xr.load(resource_path)
if add:
r.add_tag(tag_name)
else:
r.remove_tag(tag_name)
r.save()
resource.add_command(tag)
@click.command()
@click.argument('path')
@click.option('--all/--one', default=False)
@click.option('--tag', default=None)
@click.option('--use-json/--no-use-json', default=False)
def show(use_json, tag, all, path):
import json
import six
printer = lambda r: six.print_(r)
if use_json:
printer = lambda r: six.print_(json.dumps(r.to_dict()))
if all or tag:
for name, resource in xr.load_all(path).items():
show = True
if tag:
if tag not in resource.tags:
show = False
if show:
printer(resource)
else:
printer(xr.load(path))
resource.add_command(show)
@click.command()
@click.argument('name')
@click.argument('args')
def update(name, args):
args = json.loads(args)
all = xr.load_all()
r = all[name]
r.update(args)
resource.add_command(update)
def init_cli_connect():
@click.command()
@click.argument('emitter')
@click.argument('receiver')
@click.option('--mapping', default=None)
def connect(mapping, receiver, emitter):
print 'Connect', emitter, receiver
emitter = xr.load(emitter)
receiver = xr.load(receiver)
print emitter
print receiver
if mapping is not None:
mapping = json.loads(mapping)
xs.connect(emitter, receiver, mapping=mapping)
cli.add_command(connect)
@click.command()
@click.argument('emitter')
@click.argument('receiver')
def disconnect(receiver, emitter):
print 'Disconnect', emitter, receiver
emitter = xr.load(emitter)
receiver = xr.load(receiver)
print emitter
print receiver
xs.disconnect(emitter, receiver)
cli.add_command(disconnect)
def init_changes():
@click.group()
def changes():
pass
cli.add_command(changes)
@click.command()
def stage():
log = operations.stage_changes()
print log.show()
changes.add_command(stage)
@click.command()
@click.option('--one', is_flag=True, default=False)
def commit(one):
if one:
operations.commit_one()
else:
operations.commit_changes()
changes.add_command(commit)
@click.command()
@click.option('--limit', default=5)
def history(limit):
print state.CL().show()
changes.add_command(history)
@click.command()
@click.option('--last', is_flag=True, default=False)
@click.option('--all', is_flag=True, default=False)
@click.option('--uid', default=None)
def rollback(last, all, uid):
if last:
print operations.rollback_last()
elif all:
print operations.rollback_all()
elif uid:
print operations.rollback_uid(uid)
changes.add_command(rollback)
def init_cli_connections():
@click.group()
def connections():
pass
cli.add_command(connections)
@click.command()
def show():
print json.dumps(xs.CLIENTS, indent=2)
connections.add_command(show)
# TODO: this requires graphing libraries
@click.command()
def graph():
#g = xs.connection_graph()
g = xs.detailed_connection_graph()
nx.write_dot(g, 'graph.dot')
subprocess.call(['dot', '-Tpng', 'graph.dot', '-o', 'graph.png'])
# Matplotlib
#pos = nx.spring_layout(g)
#nx.draw_networkx_nodes(g, pos)
#nx.draw_networkx_edges(g, pos, arrows=True)
#nx.draw_networkx_labels(g, pos)
#plt.axis('off')
#plt.savefig('graph.png')
connections.add_command(graph)
def init_cli_deployment_config():
@click.command()
@click.argument('filepath')
def deploy(filepath):
print 'Deploying from file {}'.format(filepath)
xd.deploy(filepath)
cli.add_command(deploy)
if __name__ == '__main__':
init_cli_resource()
init_cli_connect()
init_cli_connections()
init_cli_deployment_config()
init_changes()
cli()

View File

@ -46,4 +46,4 @@ setup(
include_package_data=True,
entry_points={
'console_scripts': [
'solar = solar.cli:main']})
'solar = solar.cli:run']})

View File

@ -17,17 +17,22 @@
On create "golden" resource should be moved to special place
"""
import argparse
import click
import json
import networkx as nx
import os
import sys
import pprint
import textwrap
import subprocess
import yaml
from solar import utils
from solar import operations
from solar import state
from solar.core import actions
from solar.core import resource
from solar.core.resource import assign_resources_to_nodes
from solar.core.resource import connect_resources
from solar.core import signals
from solar.core.tags_set_parser import Expression
from solar.interfaces.db import get_db
@ -36,98 +41,19 @@ from solar.interfaces.db import get_db
from solar.extensions.modules.discovery import Discovery
class Cmd(object):
db = get_db()
def __init__(self):
self.parser = argparse.ArgumentParser(
description=textwrap.dedent(__doc__),
formatter_class=argparse.RawDescriptionHelpFormatter)
self.subparser = self.parser.add_subparsers(
title='actions',
description='Supported actions',
help='Provide of one valid actions')
self.register_actions()
self.db = get_db()
def parse(self, args):
parsed = self.parser.parse_args(args)
return parsed.func(parsed)
@click.group()
def main():
pass
def register_actions(self):
parser = self.subparser.add_parser('discover')
parser.set_defaults(func=getattr(self, 'discover'))
# Profile actions
parser = self.subparser.add_parser('profile')
parser.set_defaults(func=getattr(self, 'profile'))
parser.add_argument('-l', '--list', dest='list', action='store_true')
group = parser.add_argument_group('create')
group.add_argument('-c', '--create', dest='create', action='store_true')
group.add_argument('-t', '--tags', nargs='+', default=['env/test_env'])
group.add_argument('-i', '--id', default=utils.generate_uuid())
# Assign
parser = self.subparser.add_parser('assign')
parser.set_defaults(func=getattr(self, 'assign'))
parser.add_argument('-n', '--nodes')
parser.add_argument('-r', '--resources')
# Run action on tags
parser = self.subparser.add_parser('run')
parser.set_defaults(func=getattr(self, 'run'))
parser.add_argument('-t', '--tags')
parser.add_argument('-a', '--action')
# Perform resources connection
parser = self.subparser.add_parser('connect')
parser.set_defaults(func=getattr(self, 'connect'))
parser.add_argument(
'-p',
'--profile')
def run(self, args):
from solar.core import actions
from solar.core import resource
resources = filter(
lambda r: Expression(args.tags, r.get('tags', [])).evaluate(),
self.db.get_list('resource'))
for resource in resources:
resource_obj = resource.load(resource['id'])
actions.resource_action(resource_obj, args.action)
def profile(self, args):
if args.create:
params = {'tags': args.tags, 'id': args.id}
profile_template_path = os.path.join(
utils.read_config()['template-dir'], 'profile.yml')
data = yaml.load(utils.render_template(profile_template_path, params))
self.db.store('profiles', data)
else:
pprint.pprint(self.db.get_list('profiles'))
def discover(self, args):
Discovery({'id': 'discovery'}).discover()
def connect(self, args):
profile = self.db.get_record('profiles', args.profile)
connect_resources(profile)
def assign(self, args):
nodes = filter(
lambda n: Expression(args.nodes, n.get('tags', [])).evaluate(),
self.db.get_list('nodes'))
resources = filter(
lambda r: Expression(args.resources, r.get('tags', [])).evaluate(),
self._get_resources_list())
print("For {0} nodes assign {1} resources".format(len(nodes), len(resources)))
assign_resources_to_nodes(resources, nodes)
def _get_resources_list(self):
@main.command()
@click.option('-n', '--nodes')
@click.option('-r', '--resources')
def assign(resources, nodes):
def _get_resources_list():
result = []
for path in utils.find_by_mask(utils.read_config()['resources-files-mask']):
resource = utils.yaml_load(path)
@ -137,11 +63,241 @@ class Cmd(object):
return result
nodes = filter(
lambda n: Expression(nodes, n.get('tags', [])).evaluate(),
db.get_list('nodes'))
def main():
api = Cmd()
api.parse(sys.argv[1:])
resources = filter(
lambda r: Expression(resources, r.get('tags', [])).evaluate(),
_get_resources_list())
print("For {0} nodes assign {1} resources".format(len(nodes), len(resources)))
assign_resources_to_nodes(resources, nodes)
@main.command()
@click.option('-p', '--profile')
def connect(profile):
profile_ = db.get_record('profiles', profile)
connect_resources(profile_)
@main.command()
def discover():
Discovery({'id': 'discovery'}).discover()
@main.command()
@click.option('-c', '--create', default=False, is_flag=True)
@click.option('-t', '--tags', multiple=True)
@click.option('-i', '--id')
def profile(id, tags, create):
if not id:
id = utils.generate_uuid()
if create:
params = {'tags': tags, 'id': id}
profile_template_path = os.path.join(
utils.read_config()['template-dir'], 'profile.yml')
data = yaml.load(utils.render_template(profile_template_path, params))
db.store('profiles', data)
else:
pprint.pprint(db.get_list('profiles'))
@main.command()
@click.option('-t', '--tags')
@click.option('-a', '--action')
def run(action, tags):
from solar.core import actions
from solar.core import resource
resources = filter(
lambda r: Expression(tags, r.get('tags', [])).evaluate(),
db.get_list('resource'))
for resource in resources:
resource_obj = resource.load(resource['id'])
actions.resource_action(resource_obj, action)
def init_cli_resource():
@main.group()
def resource():
pass
@resource.command()
@click.argument('resource_path')
@click.argument('action_name')
def action(action_name, resource_path):
print 'action', resource_path, action_name
r = resource.load(resource_path)
actions.resource_action(r, action_name)
@resource.command()
@click.argument('name')
@click.argument('base_path')
@click.argument('dest_path')
@click.argument('args')
def create(args, dest_path, base_path, name):
print 'create', name, base_path, dest_path, args
args = json.loads(args)
resource.create(name, base_path, dest_path, args)
@resource.command()
@click.argument('resource_path')
@click.argument('tag_name')
@click.option('--add/--delete', default=True)
def tag(add, tag_name, resource_path):
print 'Tag', resource_path, tag_name, add
r = resource.load(resource_path)
if add:
r.add_tag(tag_name)
else:
r.remove_tag(tag_name)
r.save()
@resource.command()
@click.argument('path')
@click.option('--all/--one', default=False)
@click.option('--tag', default=None)
@click.option('--use-json/--no-use-json', default=False)
def show(use_json, tag, all, path):
import json
import six
printer = lambda r: six.print_(r)
if use_json:
printer = lambda r: six.print_(json.dumps(r.to_dict()))
if all or tag:
for name, res in resource.load_all(path).items():
show = True
if tag:
if tag not in res.tags:
show = False
if show:
printer(res)
else:
printer(resource.load(path))
@resource.command()
@click.argument('name')
@click.argument('args')
def update(name, args):
args = json.loads(args)
all = resource.load_all()
r = all[name]
r.update(args)
def init_cli_connect():
@main.command()
@click.argument('emitter')
@click.argument('receiver')
@click.option('--mapping', default=None)
def connect(mapping, receiver, emitter):
print 'Connect', emitter, receiver
emitter = resource.load(emitter)
receiver = resource.load(receiver)
print emitter
print receiver
if mapping is not None:
mapping = json.loads(mapping)
signals.connect(emitter, receiver, mapping=mapping)
@main.command()
@click.argument('emitter')
@click.argument('receiver')
def disconnect(receiver, emitter):
print 'Disconnect', emitter, receiver
emitter = resource.load(emitter)
receiver = resource.load(receiver)
print emitter
print receiver
signals.disconnect(emitter, receiver)
def init_changes():
@main.group()
def changes():
pass
@changes.command()
def stage():
log = operations.stage_changes()
print log.show()
@changes.command()
@click.option('--one', is_flag=True, default=False)
def commit(one):
if one:
operations.commit_one()
else:
operations.commit_changes()
@changes.command()
@click.option('--limit', default=5)
def history(limit):
print state.CL().show()
@changes.command()
@click.option('--last', is_flag=True, default=False)
@click.option('--all', is_flag=True, default=False)
@click.option('--uid', default=None)
def rollback(last, all, uid):
if last:
print operations.rollback_last()
elif all:
print operations.rollback_all()
elif uid:
print operations.rollback_uid(uid)
def init_cli_connections():
@main.group()
def connections():
pass
@connections.command()
def show():
print json.dumps(signals.CLIENTS, indent=2)
# TODO: this requires graphing libraries
@connections.command()
def graph():
#g = xs.connection_graph()
g = signals.detailed_connection_graph()
nx.write_dot(g, 'graph.dot')
subprocess.call(['dot', '-Tpng', 'graph.dot', '-o', 'graph.png'])
# Matplotlib
#pos = nx.spring_layout(g)
#nx.draw_networkx_nodes(g, pos)
#nx.draw_networkx_edges(g, pos, arrows=True)
#nx.draw_networkx_labels(g, pos)
#plt.axis('off')
#plt.savefig('graph.png')
def init_cli_deployment_config():
@main.command()
@click.argument('filepath')
def deploy(filepath):
print 'Deploying from file {}'.format(filepath)
xd.deploy(filepath)
def run():
init_cli_resource()
init_cli_connect()
init_cli_connections()
init_cli_deployment_config()
init_changes()
main()
if __name__ == '__main__':
main()
run()

View File

@ -187,6 +187,8 @@ class ListObserver(BaseObserver):
self.log('Unsubscribed emitter {}'.format(emitter))
idx = self._emitter_idx(emitter)
self.value.pop(idx)
for receiver in self.receivers:
receiver.notify(self)
def _emitter_idx(self, emitter):
try:

View File

@ -329,6 +329,91 @@ input:
(sample2.args['port'].attached_to.name, 'port')]
)
# Test disconnect
xs.disconnect(sample2, list_input_multi)
self.assertEqual(
[ip['value'] for ip in list_input_multi.args['ips'].value],
[sample1.args['ip']]
)
self.assertEqual(
[p['value'] for p in list_input_multi.args['ports'].value],
[sample1.args['port']]
)
def test_nested_list_input(self):
sample_meta_dir = self.make_resource_meta("""
id: sample
handler: ansible
version: 1.0.0
input:
ip:
schema: str
value:
port:
schema: int
value:
""")
list_input_meta_dir = self.make_resource_meta("""
id: list-input
handler: ansible
version: 1.0.0
input:
ips:
schema: [str]
value: []
ports:
schema: [int]
value: []
""")
list_input_nested_meta_dir = self.make_resource_meta("""
id: list-input-nested
handler: ansible
version: 1.0.0
input:
ipss:
schema: [[str]]
value: []
portss:
schema: [[int]]
value: []
""")
sample1 = self.create_resource(
'sample1', sample_meta_dir, {'ip': '10.0.0.1', 'port': '1000'}
)
sample2 = self.create_resource(
'sample2', sample_meta_dir, {'ip': '10.0.0.2', 'port': '1001'}
)
list_input = self.create_resource(
'list-input', list_input_meta_dir, {'ips': [], 'ports': []}
)
list_input_nested = self.create_resource(
'list-input-nested', list_input_nested_meta_dir, {'ipss': [], 'portss': []}
)
xs.connect(sample1, list_input, mapping={'ip': 'ips', 'port': 'ports'})
xs.connect(sample2, list_input, mapping={'ip': 'ips', 'port': 'ports'})
xs.connect(list_input, list_input_nested, mapping={'ips': 'ipss', 'ports': 'portss'})
self.assertListEqual(
[ips['value'] for ips in list_input_nested.args['ipss'].value],
[list_input.args['ips'].value]
)
self.assertListEqual(
[ps['value'] for ps in list_input_nested.args['portss'].value],
[list_input.args['ports'].value]
)
# Test disconnect
xs.disconnect(sample1, list_input)
self.assertListEqual(
[[ip['value'] for ip in ips['value']] for ips in list_input_nested.args['ipss'].value],
[[sample2.args['ip'].value]]
)
self.assertListEqual(
[[p['value'] for p in ps['value']] for ps in list_input_nested.args['portss'].value],
[[sample2.args['port'].value]]
)
'''
class TestMultiInput(base.BaseResourceTest):