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:
101
solar/cli/inputs.py
Normal file
101
solar/cli/inputs.py
Normal 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])
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user