Add input operations (add / remove)

- Moved backtrack from resource to inputs cli command
- For now any input can be removed (even connected)

Change-Id: I2696db5eb19b65955d0fb86ee7cfede80e2daaa9
Implements: blueprint inputs-add-remove
This commit is contained in:
Jedrzej Nowak
2015-12-28 17:19:10 +01:00
parent ebbd845c31
commit a9a3290e33
7 changed files with 192 additions and 48 deletions

101
solar/cli/inputs.py Normal file
View File

@@ -0,0 +1,101 @@
# 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 click
import yaml
from solar.core import resource as sresource
from solar.dblayer.model import NONE
@click.group(help="Manages raw resource inputs")
def inputs():
pass
@inputs.command(help="Adds new input to resource")
@click.argument('resource')
@click.option("--name", '-n', help="Name of input")
@click.option("--value", '-v', help="Value input (yaml load will "
" be executed on it)", default=NONE)
@click.option("--schema", '-s', help="Schema for input, "
"will be guessed if not not given",
default=None)
def add(resource, name, value, schema):
r = sresource.load(resource)
value = yaml.safe_load(value)
r.input_add(name, value, schema)
return
@inputs.command(help="Removes input from resource")
@click.argument('resource')
@click.option("--name", '-n', help="Name of input")
def remove(resource, name):
r = sresource.load(resource)
r.input_delete(name)
pass
@inputs.command(help="Shows resource inputs metadata")
@click.argument('resource')
def show_meta(resource):
r = sresource.load(resource)
db_obj = r.db_obj
meta = db_obj.meta_inputs
click.echo(yaml.safe_dump(meta, default_flow_style=False))
@inputs.command(help="Shows real input values, with full path")
@click.option('-v', '--values', default=False, is_flag=True)
@click.option('-r', '--real_values', default=False, is_flag=True)
@click.option('-i', '--input', default=None)
@click.argument('resource')
def backtrack(resource, input, values, real_values):
r = sresource.load(resource)
db_obj = r.db_obj
def single(resource, name, get_val=False):
db_obj = sresource.load(resource).db_obj
se = db_obj.inputs._single_edge(name)
se = tuple(se)
if not se:
if get_val:
return dict(resource=resource,
name=name,
value=db_obj.inputs[name])
else:
return dict(resource=resource, name=name)
l = []
for (rname, rinput), _, meta in se:
l.append(dict(resource=resource, name=name))
val = single(rname, rinput, get_val)
if meta and isinstance(val, dict):
val['meta'] = meta
l.append(val)
return l
inps = {}
if input:
inps[input] = single(resource, input, values)
else:
for _inp in db_obj.inputs:
inps[_inp] = single(resource, _inp, values)
for name, values in inps.iteritems():
click.echo(yaml.safe_dump({name: values}, default_flow_style=False))
if real_values:
click.echo('! Real value: %r\n' % sresource.load(
resource).db_obj.inputs[name])

View File

@@ -29,6 +29,7 @@ from solar.core import signals
from solar.cli import base
from solar.cli.events import events
from solar.cli.inputs import inputs as cli_inputs
from solar.cli.orch import orchestration
from solar.cli.repository import repository as cli_repository
from solar.cli.resource import resource as cli_resource
@@ -163,6 +164,7 @@ def run():
main.add_command(changes)
main.add_command(events)
main.add_command(cli_repository)
main.add_command(cli_inputs)
main()

View File

@@ -64,50 +64,6 @@ def action(dry_run_mapping, dry_run, action, resource):
))
@resource.command()
@click.option('-v', '--values', default=False, is_flag=True)
@click.option('-r', '--real_values', default=False, is_flag=True)
@click.option('-i', '--input', default=None)
@click.argument('resource')
def backtrack_inputs(resource, input, values, real_values):
r = sresource.load(resource)
db_obj = r.db_obj
def single(resource, name, get_val=False):
db_obj = sresource.load(resource).db_obj
se = db_obj.inputs._single_edge(name)
se = tuple(se)
if not se:
if get_val:
return dict(resource=resource,
name=name,
value=db_obj.inputs[name])
else:
return dict(resource=resource, name=name)
l = []
for (rname, rinput), _, meta in se:
l.append(dict(resource=resource, name=name))
val = single(rname, rinput, get_val)
if meta and isinstance(val, dict):
val['meta'] = meta
l.append(val)
return l
inps = {}
if input:
inps[input] = single(resource, input, values)
else:
for _inp in db_obj.inputs:
inps[_inp] = single(resource, _inp, values)
for name, values in inps.iteritems():
click.echo(yaml.safe_dump({name: values}, default_flow_style=False))
if real_values:
click.echo('! Real value: %r' % sresource.load(
resource).db_obj.inputs[name], nl=True)
@resource.command()
def compile_all():
from solar.core.resource import compiler

View File

@@ -31,6 +31,7 @@ from solar.core.signals import get_mapping
from solar.core.tags_set_parser import Expression
from solar.core.tags_set_parser import get_string_tokens
from solar.core import validation
from solar.dblayer.model import NONE
from solar.dblayer.model import StrInt
from solar.dblayer.solar_models import CommitedResource
from solar.dblayer.solar_models import Resource as DBResource
@@ -153,10 +154,16 @@ class Resource(object):
@property
def args(self):
return self.db_obj.inputs.as_dict()
# ret = {}
# for i in self.resource_inputs().values():
# ret[i.name] = i.backtrack_value()
# return ret
def input_add(self, name, value=NONE, schema=None):
v = self.db_obj.inputs.add_new(name, value, schema)
self.db_obj.save_lazy()
return v
def input_delete(self, name):
self.db_obj.inputs.remove_existing(name)
self.db_obj.save_lazy()
return
def update(self, args):
# TODO: disconnect input when it is updated and end_node

View File

@@ -30,8 +30,10 @@ from solar.dblayer.model import IndexedField
from solar.dblayer.model import IndexField
from solar.dblayer.model import IndexFieldWrp
from solar.dblayer.model import Model
from solar.dblayer.model import NONE
from solar.dblayer.model import SingleIndexCache
from solar.dblayer.model import StrInt
from solar.utils import detect_input_schema_by_value
from solar.utils import solar_map
@@ -51,6 +53,10 @@ class UnknownInput(DBLayerSolarException, KeyError):
return "Unknown input %s" % self.name
class InputAlreadyExists(DBLayerSolarException):
pass
class InputsFieldWrp(IndexFieldWrp):
_simple_types = (NoneType, int, float, basestring, str, unicode)
@@ -592,6 +598,7 @@ class InputsFieldWrp(IndexFieldWrp):
raise UnknownInput(name)
def __delitem__(self, name):
# TODO: check if something is connected to it
self._has_own_input(name)
self._instance._field_changed(self)
try:
@@ -656,6 +663,20 @@ class InputsFieldWrp(IndexFieldWrp):
rst[key] = self[key]
return rst
def add_new(self, name, value=NONE, schema=None):
if value is not NONE and schema is None:
schema = detect_input_schema_by_value(value)
if name in self.keys():
raise InputAlreadyExists()
self._instance.meta_inputs[name] = {'schema': schema}
self[name] = value if value is not NONE else None
return True
def remove_existing(self, name):
del self[name]
del self._instance.meta_inputs[name]
return True
class InputsField(IndexField):
_wrp_class = InputsFieldWrp

View File

@@ -18,6 +18,7 @@ import pytest
from solar.dblayer.model import check_state_for
from solar.dblayer.model import StrInt
from solar.dblayer.solar_models import DBLayerSolarException
from solar.dblayer.solar_models import InputAlreadyExists
from solar.dblayer.solar_models import Resource
from solar.dblayer.solar_models import UnknownInput
@@ -694,3 +695,36 @@ def test_raise_error_unknown_input(rk):
with pytest.raises(UnknownInput):
r1.inputs['b'] = 11
@pytest.mark.parametrize('schema', (None, 'int!'))
def test_add_new_input(rk, schema):
k1 = next(rk)
r1 = create_resource(k1, {'name': 'first',
'inputs': {'a': 10}})
r1.save()
r1.inputs.add_new('b', 15, schema)
r1.save()
assert r1.inputs['b'] == 15
if schema:
assert r1.meta_inputs['b']['schema'] == schema
with pytest.raises(InputAlreadyExists):
r1.inputs.add_new('b', 25, schema)
def test_remove_input(rk):
k1 = next(rk)
r1 = create_resource(k1, {'name': 'first',
'inputs': {'a': 10,
'b': 15}})
r1.save()
r1.inputs.remove_existing('b')
assert 'b' not in r1.inputs.keys()
assert 'b' not in r1.meta_inputs.keys()
with pytest.raises(DBLayerSolarException):
r1.inputs.remove_existing('b')

View File

@@ -189,3 +189,26 @@ def parse_database_conn(name):
else:
raise Exception("Invalid database connection string: %r "
"It should be in RFC 1738 format. " % name)
def detect_input_schema_by_value(value):
_types = {
'int': 'int!',
'str': 'str!',
'list': '[]',
'hash': '{}',
'list_hash': '[{}]'
}
if value is None:
return ""
if isinstance(value, int):
return _types['int']
if isinstance(value, basestring):
return _types['str']
if isinstance(value, list):
if len(value) >= 1 and isinstance(value[0], dict):
return _types['list_hash']
return _types['list']
if isinstance(value, dict):
return _types['hash']