Add support for removal of resource
This commit is contained in:
parent
7987334385
commit
4ae397c5a1
@ -41,6 +41,7 @@ from solar.cli import executors
|
||||
from solar.cli.orch import orchestration
|
||||
from solar.cli.system_log import changes
|
||||
from solar.cli.events import events
|
||||
from solar.cli.resource import resource as cli_resource
|
||||
|
||||
|
||||
# HELPERS
|
||||
@ -157,203 +158,12 @@ def init_cli_connections():
|
||||
fabric_api.local('dot -Tsvg graph.dot -o graph.svg')
|
||||
|
||||
|
||||
def init_cli_resource():
|
||||
@main.group()
|
||||
def resource():
|
||||
pass
|
||||
|
||||
@resource.command()
|
||||
@click.argument('action')
|
||||
@click.argument('resource')
|
||||
@click.option('-d', '--dry-run', default=False, is_flag=True)
|
||||
@click.option('-m', '--dry-run-mapping', default='{}')
|
||||
def action(dry_run_mapping, dry_run, action, resource):
|
||||
if dry_run:
|
||||
dry_run_executor = executors.DryRunExecutor(mapping=json.loads(dry_run_mapping))
|
||||
|
||||
click.echo(
|
||||
'action {} for resource {}'.format(action, resource)
|
||||
)
|
||||
|
||||
r = sresource.load(resource)
|
||||
try:
|
||||
actions.resource_action(r, action)
|
||||
except errors.SolarError as e:
|
||||
log.debug(e)
|
||||
sys.exit(1)
|
||||
|
||||
if dry_run:
|
||||
click.echo('EXECUTED:')
|
||||
for key in dry_run_executor.executed:
|
||||
click.echo('{}: {}'.format(
|
||||
click.style(dry_run_executor.compute_hash(key), fg='green'),
|
||||
str(key)
|
||||
))
|
||||
|
||||
@resource.command()
|
||||
@click.argument('resource')
|
||||
def backtrack_inputs(resource):
|
||||
r = sresource.load(resource)
|
||||
|
||||
inputs = []
|
||||
|
||||
def backtrack(i):
|
||||
def format_input(i):
|
||||
return '{}::{}'.format(i.resource.name, i.name)
|
||||
|
||||
if isinstance(i, list):
|
||||
return [backtrack(bi) for bi in i]
|
||||
|
||||
if isinstance(i, dict):
|
||||
return {
|
||||
k: backtrack(bi) for k, bi in i.items()
|
||||
}
|
||||
|
||||
bi = i.backtrack_value_emitter(level=1)
|
||||
if isinstance(i, orm.DBResourceInput) and isinstance(bi, orm.DBResourceInput) and i == bi:
|
||||
return (format_input(i), )
|
||||
|
||||
return (format_input(i), backtrack(bi))
|
||||
|
||||
for i in r.resource_inputs().values():
|
||||
click.echo(yaml.safe_dump({i.name: backtrack(i)}, default_flow_style=False))
|
||||
|
||||
@resource.command()
|
||||
def compile_all():
|
||||
from solar.core.resource import compiler
|
||||
|
||||
destination_path = utils.read_config()['resources-compiled-file']
|
||||
|
||||
if os.path.exists(destination_path):
|
||||
os.remove(destination_path)
|
||||
|
||||
for path in utils.find_by_mask(utils.read_config()['resources-files-mask']):
|
||||
meta = utils.yaml_load(path)
|
||||
meta['base_path'] = os.path.dirname(path)
|
||||
|
||||
compiler.compile(meta)
|
||||
|
||||
@resource.command()
|
||||
def clear_all():
|
||||
click.echo('Clearing all resources and connections')
|
||||
orm.db.clear()
|
||||
|
||||
@resource.command()
|
||||
@click.argument('name')
|
||||
@click.argument(
|
||||
'base_path', type=click.Path(exists=True, resolve_path=True))
|
||||
@click.argument('args', nargs=-1)
|
||||
def create(args, base_path, name):
|
||||
args_parsed = {}
|
||||
|
||||
click.echo('create {} {} {}'.format(name, base_path, args))
|
||||
for arg in args:
|
||||
try:
|
||||
args_parsed.update(json.loads(arg))
|
||||
except ValueError:
|
||||
k, v = arg.split('=')
|
||||
args_parsed.update({k: v})
|
||||
resources = vr.create(name, base_path, args=args_parsed)
|
||||
for res in resources:
|
||||
click.echo(res.color_repr())
|
||||
|
||||
@resource.command()
|
||||
@click.option('--name', default=None)
|
||||
@click.option('--tag', default=None)
|
||||
@click.option('--json', default=False, is_flag=True)
|
||||
@click.option('--color', default=True, is_flag=True)
|
||||
def show(**kwargs):
|
||||
resources = []
|
||||
|
||||
for res in sresource.load_all():
|
||||
show = True
|
||||
if kwargs['tag']:
|
||||
if kwargs['tag'] not in res.tags:
|
||||
show = False
|
||||
if kwargs['name']:
|
||||
if res.name != kwargs['name']:
|
||||
show = False
|
||||
|
||||
if show:
|
||||
resources.append(res)
|
||||
|
||||
echo = click.echo_via_pager
|
||||
if kwargs['json']:
|
||||
output = json.dumps([r.to_dict() for r in resources], indent=2)
|
||||
echo = click.echo
|
||||
else:
|
||||
if kwargs['color']:
|
||||
formatter = lambda r: r.color_repr()
|
||||
else:
|
||||
formatter = lambda r: unicode(r)
|
||||
output = '\n'.join(formatter(r) for r in resources)
|
||||
|
||||
if output:
|
||||
echo(output)
|
||||
|
||||
@resource.command()
|
||||
@click.argument('resource_name')
|
||||
@click.argument('tag_name')
|
||||
@click.option('--add/--delete', default=True)
|
||||
def tag(add, tag_name, resource_name):
|
||||
click.echo('Tag {} with {} {}'.format(resource_name, tag_name, add))
|
||||
r = sresource.load(resource_name)
|
||||
if add:
|
||||
r.add_tag(tag_name)
|
||||
else:
|
||||
r.remove_tag(tag_name)
|
||||
# TODO: the above functions should save resource automatically to the DB
|
||||
|
||||
@resource.command()
|
||||
@click.argument('name')
|
||||
@click.argument('args', nargs=-1)
|
||||
def update(name, args):
|
||||
args_parsed = {}
|
||||
for arg in args:
|
||||
try:
|
||||
args_parsed.update(json.loads(arg))
|
||||
except ValueError:
|
||||
k, v = arg.split('=')
|
||||
args_parsed.update({k: v})
|
||||
click.echo('Updating resource {} with args {}'.format(name, args_parsed))
|
||||
res = sresource.load(name)
|
||||
res.update(args_parsed)
|
||||
|
||||
@resource.command()
|
||||
@click.option('--check-missing-connections', default=False, is_flag=True)
|
||||
def validate(check_missing_connections):
|
||||
errors = vr.validate_resources()
|
||||
for r, error in errors:
|
||||
click.echo('ERROR: %s: %s' % (r.name, error))
|
||||
|
||||
if check_missing_connections:
|
||||
missing_connections = vr.find_missing_connections()
|
||||
if missing_connections:
|
||||
click.echo(
|
||||
'The following resources have inputs of the same value '
|
||||
'but are not connected:'
|
||||
)
|
||||
click.echo(
|
||||
tabulate.tabulate([
|
||||
['%s::%s' % (r1, i1), '%s::%s' % (r2, i2)]
|
||||
for r1, i1, r2, i2 in missing_connections
|
||||
])
|
||||
)
|
||||
|
||||
@resource.command()
|
||||
@click.argument('path', type=click.Path(exists=True, dir_okay=False))
|
||||
def get_inputs(path):
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
click.echo(vr.get_inputs(content))
|
||||
|
||||
|
||||
def run():
|
||||
init_actions()
|
||||
init_cli_connect()
|
||||
init_cli_connections()
|
||||
init_cli_resource()
|
||||
|
||||
main.add_command(cli_resource)
|
||||
main.add_command(orchestration)
|
||||
main.add_command(changes)
|
||||
main.add_command(events)
|
||||
|
227
solar/solar/cli/resource.py
Normal file
227
solar/solar/cli/resource.py
Normal file
@ -0,0 +1,227 @@
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
#
|
||||
# Licensed 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 os
|
||||
import yaml
|
||||
import tabulate
|
||||
|
||||
import click
|
||||
|
||||
from solar.core import actions
|
||||
from solar.core import resource as sresource
|
||||
from solar.core import signals
|
||||
from solar.core.tags_set_parser import Expression
|
||||
from solar.core.resource import virtual_resource as vr
|
||||
from solar.core.log import log
|
||||
from solar import errors
|
||||
from solar.interfaces import orm
|
||||
from solar import utils
|
||||
|
||||
from solar.cli import executors
|
||||
|
||||
|
||||
@click.group()
|
||||
def resource():
|
||||
pass
|
||||
|
||||
@resource.command()
|
||||
@click.argument('action')
|
||||
@click.argument('resource')
|
||||
@click.option('-d', '--dry-run', default=False, is_flag=True)
|
||||
@click.option('-m', '--dry-run-mapping', default='{}')
|
||||
def action(dry_run_mapping, dry_run, action, resource):
|
||||
if dry_run:
|
||||
dry_run_executor = executors.DryRunExecutor(mapping=json.loads(dry_run_mapping))
|
||||
|
||||
click.echo(
|
||||
'action {} for resource {}'.format(action, resource)
|
||||
)
|
||||
|
||||
r = sresource.load(resource)
|
||||
try:
|
||||
actions.resource_action(r, action)
|
||||
except errors.SolarError as e:
|
||||
log.debug(e)
|
||||
sys.exit(1)
|
||||
|
||||
if dry_run:
|
||||
click.echo('EXECUTED:')
|
||||
for key in dry_run_executor.executed:
|
||||
click.echo('{}: {}'.format(
|
||||
click.style(dry_run_executor.compute_hash(key), fg='green'),
|
||||
str(key)
|
||||
))
|
||||
|
||||
@resource.command()
|
||||
@click.argument('resource')
|
||||
def backtrack_inputs(resource):
|
||||
r = sresource.load(resource)
|
||||
|
||||
inputs = []
|
||||
|
||||
def backtrack(i):
|
||||
def format_input(i):
|
||||
return '{}::{}'.format(i.resource.name, i.name)
|
||||
|
||||
if isinstance(i, list):
|
||||
return [backtrack(bi) for bi in i]
|
||||
|
||||
if isinstance(i, dict):
|
||||
return {
|
||||
k: backtrack(bi) for k, bi in i.items()
|
||||
}
|
||||
|
||||
bi = i.backtrack_value_emitter(level=1)
|
||||
if isinstance(i, orm.DBResourceInput) and isinstance(bi, orm.DBResourceInput) and i == bi:
|
||||
return (format_input(i), )
|
||||
|
||||
return (format_input(i), backtrack(bi))
|
||||
|
||||
for i in r.resource_inputs().values():
|
||||
click.echo(yaml.safe_dump({i.name: backtrack(i)}, default_flow_style=False))
|
||||
|
||||
@resource.command()
|
||||
def compile_all():
|
||||
from solar.core.resource import compiler
|
||||
|
||||
destination_path = utils.read_config()['resources-compiled-file']
|
||||
|
||||
if os.path.exists(destination_path):
|
||||
os.remove(destination_path)
|
||||
|
||||
for path in utils.find_by_mask(utils.read_config()['resources-files-mask']):
|
||||
meta = utils.yaml_load(path)
|
||||
meta['base_path'] = os.path.dirname(path)
|
||||
|
||||
compiler.compile(meta)
|
||||
|
||||
@resource.command()
|
||||
def clear_all():
|
||||
click.echo('Clearing all resources and connections')
|
||||
orm.db.clear()
|
||||
|
||||
@resource.command()
|
||||
@click.argument('name')
|
||||
@click.argument(
|
||||
'base_path', type=click.Path(exists=True, resolve_path=True))
|
||||
@click.argument('args', nargs=-1)
|
||||
def create(args, base_path, name):
|
||||
args_parsed = {}
|
||||
|
||||
click.echo('create {} {} {}'.format(name, base_path, args))
|
||||
for arg in args:
|
||||
try:
|
||||
args_parsed.update(json.loads(arg))
|
||||
except ValueError:
|
||||
k, v = arg.split('=')
|
||||
args_parsed.update({k: v})
|
||||
resources = vr.create(name, base_path, args=args_parsed)
|
||||
for res in resources:
|
||||
click.echo(res.color_repr())
|
||||
|
||||
@resource.command()
|
||||
@click.option('--name', default=None)
|
||||
@click.option('--tag', default=None)
|
||||
@click.option('--json', default=False, is_flag=True)
|
||||
@click.option('--color', default=True, is_flag=True)
|
||||
def show(**kwargs):
|
||||
resources = []
|
||||
|
||||
for res in sresource.load_all():
|
||||
show = True
|
||||
if kwargs['tag']:
|
||||
if kwargs['tag'] not in res.tags:
|
||||
show = False
|
||||
if kwargs['name']:
|
||||
if res.name != kwargs['name']:
|
||||
show = False
|
||||
|
||||
if show:
|
||||
resources.append(res)
|
||||
|
||||
echo = click.echo_via_pager
|
||||
if kwargs['json']:
|
||||
output = json.dumps([r.to_dict() for r in resources], indent=2)
|
||||
echo = click.echo
|
||||
else:
|
||||
if kwargs['color']:
|
||||
formatter = lambda r: r.color_repr()
|
||||
else:
|
||||
formatter = lambda r: unicode(r)
|
||||
output = '\n'.join(formatter(r) for r in resources)
|
||||
|
||||
if output:
|
||||
echo(output)
|
||||
|
||||
@resource.command()
|
||||
@click.argument('resource_name')
|
||||
@click.argument('tag_name')
|
||||
@click.option('--add/--delete', default=True)
|
||||
def tag(add, tag_name, resource_name):
|
||||
click.echo('Tag {} with {} {}'.format(resource_name, tag_name, add))
|
||||
r = sresource.load(resource_name)
|
||||
if add:
|
||||
r.add_tag(tag_name)
|
||||
else:
|
||||
r.remove_tag(tag_name)
|
||||
# TODO: the above functions should save resource automatically to the DB
|
||||
|
||||
@resource.command()
|
||||
@click.argument('name')
|
||||
@click.argument('args', nargs=-1)
|
||||
def update(name, args):
|
||||
args_parsed = {}
|
||||
for arg in args:
|
||||
try:
|
||||
args_parsed.update(json.loads(arg))
|
||||
except ValueError:
|
||||
k, v = arg.split('=')
|
||||
args_parsed.update({k: v})
|
||||
click.echo('Updating resource {} with args {}'.format(name, args_parsed))
|
||||
res = sresource.load(name)
|
||||
res.update(args_parsed)
|
||||
|
||||
@resource.command()
|
||||
@click.option('--check-missing-connections', default=False, is_flag=True)
|
||||
def validate(check_missing_connections):
|
||||
errors = vr.validate_resources()
|
||||
for r, error in errors:
|
||||
click.echo('ERROR: %s: %s' % (r.name, error))
|
||||
|
||||
if check_missing_connections:
|
||||
missing_connections = vr.find_missing_connections()
|
||||
if missing_connections:
|
||||
click.echo(
|
||||
'The following resources have inputs of the same value '
|
||||
'but are not connected:'
|
||||
)
|
||||
click.echo(
|
||||
tabulate.tabulate([
|
||||
['%s::%s' % (r1, i1), '%s::%s' % (r2, i2)]
|
||||
for r1, i1, r2, i2 in missing_connections
|
||||
])
|
||||
)
|
||||
|
||||
@resource.command()
|
||||
@click.argument('path', type=click.Path(exists=True, dir_okay=False))
|
||||
def get_inputs(path):
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
click.echo(vr.get_inputs(content))
|
||||
|
||||
@resource.command()
|
||||
@click.argument('name')
|
||||
def remove(name):
|
||||
res = sresource.load(name)
|
||||
res.delete()
|
@ -112,6 +112,9 @@ class Resource(object):
|
||||
i.value = v
|
||||
i.save()
|
||||
|
||||
def delete(self):
|
||||
return self.db_obj.delete()
|
||||
|
||||
def resource_inputs(self):
|
||||
return {
|
||||
i.name: i for i in self.db_obj.inputs.as_set()
|
||||
|
@ -413,6 +413,17 @@ class DBResourceInput(DBObject):
|
||||
)[0].start_node.properties
|
||||
)
|
||||
|
||||
def delete(self):
|
||||
db.delete_relations(
|
||||
source=self._db_node,
|
||||
type_=base.BaseGraphDB.RELATION_TYPES.input_to_input
|
||||
)
|
||||
db.delete_relations(
|
||||
dest=self._db_node,
|
||||
type_=base.BaseGraphDB.RELATION_TYPES.input_to_input
|
||||
)
|
||||
super(DBResourceInput, self).delete()
|
||||
|
||||
def backtrack_value_emitter(self, level=None):
|
||||
# TODO: this is actually just fetching head element in linked list
|
||||
# so this whole algorithm can be moved to the db backend probably
|
||||
@ -559,6 +570,12 @@ class DBResource(DBObject):
|
||||
event.save()
|
||||
self.events.add(event)
|
||||
|
||||
def delete(self):
|
||||
for input in self.inputs.as_set():
|
||||
self.inputs.remove(input)
|
||||
input.delete()
|
||||
super(DBResource, self).delete()
|
||||
|
||||
|
||||
# TODO: remove this
|
||||
if __name__ == '__main__':
|
||||
|
@ -44,7 +44,8 @@ def create_diff(staged, commited):
|
||||
|
||||
def _stage_changes(staged_resources, commited_resources, staged_log):
|
||||
|
||||
for res_uid in staged_resources.keys():
|
||||
union = set(staged_resources.keys()) | set(commited_resources.keys())
|
||||
for res_uid in union:
|
||||
commited_data = commited_resources.get(res_uid, {})
|
||||
staged_data = staged_resources.get(res_uid, {})
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user