Merge in changes there were made since seeding 91/12491/2
authorMichael Hwang <mhwang@research.att.com>
Thu, 14 Sep 2017 17:06:21 +0000 (13:06 -0400)
committerMichael Hwang <mhwang@research.att.com>
Thu, 14 Sep 2017 18:50:51 +0000 (14:50 -0400)
* Fix DR config keys issue
* Add data format generate command
* Improve error messaging
* Add in support for inputs otherwise known as "sourced at deployment"

Change-Id: I9d97c30aeba587315d7fd1a18c38f71d8199d42b
Issue-Id: DCAEGEN2-91
Signed-off-by: Michael Hwang <mhwang@research.att.com>
21 files changed:
component-json-schemas/component-spec-schema.json
component-json-schemas/dmaap-schema.json [new file with mode: 0644]
component-json-schemas/tests/dmaap-mr-bad-extra.json [new file with mode: 0644]
component-json-schemas/tests/dmaap-mr-bad-missing.json [new file with mode: 0644]
component-json-schemas/tests/dmaap-mr-good.json [new file with mode: 0644]
dcae-cli/ChangeLog.md
dcae-cli/dcae_cli/_version.py
dcae-cli/dcae_cli/commands/component/commands.py
dcae-cli/dcae_cli/commands/data_format/commands.py
dcae-cli/dcae_cli/commands/tests/mocked_components/model/badjson [new file with mode: 0644]
dcae-cli/dcae_cli/commands/tests/mocked_components/model/generatedir/ex1.json [new file with mode: 0755]
dcae-cli/dcae_cli/commands/tests/mocked_components/model/generatedir/ex2.json [new file with mode: 0755]
dcae-cli/dcae_cli/commands/tests/test_data_format_cmd.py
dcae-cli/dcae_cli/util/discovery.py
dcae-cli/dcae_cli/util/dmaap.py
dcae-cli/dcae_cli/util/inputs.py [new file with mode: 0644]
dcae-cli/dcae_cli/util/run.py
dcae-cli/dcae_cli/util/tests/test_discovery.py
dcae-cli/dcae_cli/util/tests/test_inputs.py [new file with mode: 0644]
dcae-cli/requirements.txt
dcae-cli/setup.py

index 690cc96..27d0403 100644 (file)
           "type": "boolean",
           "default": false
         },
+        "sourced_at_deployment": {
+          "description": "An optional key that declares a parameter's value to be assigned at deployment time (true). Default is false.",
+          "type": "boolean",
+          "default": false
+        },
         "policy_schema" :{
           "type": "array",
           "uniqueItems": true,
diff --git a/component-json-schemas/dmaap-schema.json b/component-json-schemas/dmaap-schema.json
new file mode 100644 (file)
index 0000000..e6745f7
--- /dev/null
@@ -0,0 +1,51 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Schema for dmaap inputs",
+  "type": "object",
+  "oneOf": [
+    { "$ref": "#/definitions/message_router" }
+  ],
+  "definitions": {
+    "message_router": {
+      "type": "object",
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": ["message_router"]
+        },
+        "aaf_username": {
+          "type": "string"
+        },
+        "aaf_password": {
+          "type": "string"
+        },
+        "dmaap_info": {
+          "type": "object",
+          "properties": {
+            "client_role": {
+              "type": "string"
+            },
+            "client_id": {
+              "type": "string"
+            },
+            "location": {
+              "type": "string"
+            },
+            "topic_url": {
+              "type": "string"
+            }
+          },
+          "required": [
+            "topic_url"
+          ],
+          "additionalProperties": false
+        }
+      },
+      "required": [
+        "type",
+        "dmaap_info"
+      ],
+      "additionalProperties": false
+    }
+  }
+}
diff --git a/component-json-schemas/tests/dmaap-mr-bad-extra.json b/component-json-schemas/tests/dmaap-mr-bad-extra.json
new file mode 100644 (file)
index 0000000..e1821d5
--- /dev/null
@@ -0,0 +1,12 @@
+{
+        "type": "message_router",
+        "aaf_username": "foo3",
+        "aaf_password": "bar3",
+        "something_else": "boo",
+        "dmaap_info":{
+           "client_role":"some.dcae.member",
+           "client_id":"123456",
+           "location":"mtc5",
+           "topic_url":"https://message-router-url/some-topic"
+        }
+}
diff --git a/component-json-schemas/tests/dmaap-mr-bad-missing.json b/component-json-schemas/tests/dmaap-mr-bad-missing.json
new file mode 100644 (file)
index 0000000..9151032
--- /dev/null
@@ -0,0 +1,10 @@
+{
+        "type": "message_router",
+        "aaf_username": "foo3",
+        "aaf_password": "bar3",
+        "dmaap_info":{
+           "client_role":"some.dcae.member",
+           "client_id":"123456",
+           "location":"mtc5"
+        }
+}
diff --git a/component-json-schemas/tests/dmaap-mr-good.json b/component-json-schemas/tests/dmaap-mr-good.json
new file mode 100644 (file)
index 0000000..d3e8dda
--- /dev/null
@@ -0,0 +1,11 @@
+{
+        "type": "message_router",
+        "aaf_username": "foo3",
+        "aaf_password": "bar3",
+        "dmaap_info":{
+           "client_role":"some.dcae.member",
+           "client_id":"123456",
+           "location":"mtc5",
+           "topic_url":"https://message-router-url/some-topic"
+        }
+}
index 249065b..4e169fc 100644 (file)
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/) 
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [2.9.0]
+
+* Add data format generate command
+* Fix issue with data router config keys
+
+## [2.8.1]
+
+* Improve error message when inputs map is missing item. Show the specific parameters that are causing issues.
+
+## [2.8.0]
+
+* Enhance to support parameters that are sourced at deployment
+* Provide new command line arg --inputs-file
+* Use inputs file to bind values to generated configuration for parameters that have been specified to be `sourced_at_deployment` true.
+
 ## [2.7.0]
 
 * Rip out Docker related code and use common python-dockering library
index 66bee2c..3f09935 100644 (file)
@@ -19,4 +19,4 @@
 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
 
 # -*- coding: utf-8 -*-
-__version__ = "2.7.0"
+__version__ = "2.9.0"
index 9c7a56d..4326636 100644 (file)
@@ -29,7 +29,7 @@ import click
 
 from discovery_client import resolve_name
 
-from dcae_cli.util import profiles, load_json, dmaap
+from dcae_cli.util import profiles, load_json, dmaap, inputs
 from dcae_cli.util.run import run_component, dev_component
 from dcae_cli.util import discovery as dis
 from dcae_cli.util.discovery import DiscoveryNoDownstreamComponentError
@@ -165,6 +165,28 @@ def _parse_dmaap_file(dmaap_file):
         message = "Problems with parsing the dmaap file. Check to make sure that it is a valid json and is in the expected structure."
         raise DcaeException(message)
 
+_help_inputs_file = """
+Path to a file that contains a json that contains values to be used to bind to configuration parameters that have been marked as "sourced_at_deployment". The structure of the json is expected to be:
+
+ {
+   <parameter1 name>: value,
+   <parameter2 name>: value
+ }
+
+The "parameter name" is the value of the "name" property for the given configuration parameter.
+"""
+
+def _parse_inputs_file(inputs_file):
+    try:
+        with open(inputs_file, 'r+') as f:
+            inputs_map = json.load(f)
+            # TODO: Validation of schema in the future? Skipping this because
+            # dti_payload is not being intended to be used.
+            return inputs_map
+    except Exception as e:
+        message = "Problems with parsing the inputs file. Check to make sure that it is a valid json and is in the expected structure."
+        raise DcaeException(message)
+
 
 @component.command()
 @click.option('--external-ip', '-ip', default=None, help='The external IP address of the Docker host. Only used for Docker components.')
@@ -173,21 +195,29 @@ def _parse_dmaap_file(dmaap_file):
 @click.option('--force', is_flag=True, help='Force component to run without valid downstream dependencies')
 @click.option('--dmaap-file', type=click.Path(resolve_path=True, exists=True, dir_okay=False),
         help=_help_dmaap_file)
+@click.option('--inputs-file', type=click.Path(resolve_path=True, exists=True, dir_okay=False),
+        help=_help_inputs_file)
 @click.argument('component')
 @click.pass_obj
-def run(obj, external_ip, additional_user, attached, force, dmaap_file, component):
+def run(obj, external_ip, additional_user, attached, force, dmaap_file, component,
+        inputs_file):
     '''Runs the latest version of COMPONENT. You may optionally specify version via COMPONENT:VERSION'''
     cname, cver = parse_input(component)
     user, catalog = obj['config']['user'], obj['catalog']
 
     dmaap_map = _parse_dmaap_file(dmaap_file) if dmaap_file else {}
+    inputs_map = _parse_inputs_file(inputs_file) if inputs_file else {}
 
     try:
         run_component(user, cname, cver, catalog, additional_user, attached, force,
-                dmaap_map, external_ip)
+                dmaap_map, inputs_map, external_ip)
     except DiscoveryNoDownstreamComponentError as e:
         message = "Either run a compatible downstream component first or run with the --force flag to ignore this error"
         raise DcaeException(message)
+    except inputs.InputsValidationError as e:
+        click.echo("There is a problem. {0}".format(e))
+        message = "Component requires inputs. Please look at the use of --inputs-file and make sure the format is correct"
+        raise DcaeException(message)
 
 @component.command()
 @click.argument('component')
@@ -205,20 +235,28 @@ def undeploy(obj,  component):
 @click.option('--force', is_flag=True, help='Force component to run without valid downstream dependencies')
 @click.option('--dmaap-file', type=click.Path(resolve_path=True, exists=True, dir_okay=False),
         help=_help_dmaap_file)
+@click.option('--inputs-file', type=click.Path(resolve_path=True, exists=True, dir_okay=False),
+        help=_help_inputs_file)
 @click.pass_obj
-def dev(obj, specification, additional_user, force, dmaap_file):
+def dev(obj, specification, additional_user, force, dmaap_file, inputs_file):
     '''Set up component in development for discovery, use for local development'''
     user, catalog = obj['config']['user'], obj['catalog']
 
     dmaap_map = _parse_dmaap_file(dmaap_file) if dmaap_file else {}
+    inputs_map = _parse_inputs_file(inputs_file) if inputs_file else {}
 
     with open(specification, 'r+') as f:
         spec = json.loads(f.read())
         try:
-            dev_component(user, catalog, spec, additional_user, force, dmaap_map)
+            dev_component(user, catalog, spec, additional_user, force, dmaap_map,
+                    inputs_map)
         except DiscoveryNoDownstreamComponentError as e:
             message = "Either run a compatible downstream component first or run with the --force flag to ignore this error"
             raise DcaeException(message)
+        except inputs.InputsValidationError as e:
+            click.echo("There is a problem. {0}".format(e))
+            message = "Component requires inputs. Please look at the use of --inputs-file and make sure the format is correct"
+            raise DcaeException(message)
 
 
 @component.command()
index 9114688..b942442 100644 (file)
@@ -26,12 +26,22 @@ import json
 
 import click
 
+import genson
+
+import sys
+
+import os
+
+from jsonschema import Draft4Validator
+
 from dcae_cli.util import load_json
 from dcae_cli.util.logger import get_logger
+
 from dcae_cli.commands import util
 from dcae_cli.commands.util import create_table, parse_input
 
 from dcae_cli.catalog.exc import MissingEntry
+from dcae_cli.catalog.exc import DcaeException
 
 
 logger = get_logger('DataFormatCommand')
@@ -96,3 +106,59 @@ def publish(obj, data_format):
         click.echo("Data format has been published")
     else:
         click.echo("Data format could not be published")
+
+@data_format.command()
+@click.option('--keywords', is_flag=True, help='Adds a template of possible descriptive keywords', default=False)
+@click.argument('name_version', metavar="name:version",  required = True)
+@click.argument('file-or-dir-path', type=click.Path(resolve_path=True, exists=True, dir_okay=True, file_okay=True, readable=True),   metavar="file-or-dir-path")
+@click.pass_obj
+def generate(obj, name_version, file_or_dir_path, keywords):
+    '''Create schema from a file or directory examples'''
+    name, version = parse_input(name_version)
+    if version == None: 
+      version = ""
+    schema = genson.Schema()
+    if os.path.isfile(file_or_dir_path):
+      addfile(file_or_dir_path, schema)
+    else:
+      foundJSON = False
+      for root, dirs, files in os.walk(file_or_dir_path):
+          for filename in files:
+            fullfilename = os.path.join(file_or_dir_path, filename)
+            addfile(fullfilename,schema)
+            foundJSON = True
+      if foundJSON == False:
+        raise DcaeException('No JSON files found in ' + file_or_dir_path)
+
+    json_obj = json.loads(schema.to_json())
+    json_obj['$schema'] = "http://json-schema.org/draft-04/schema#"
+    jschema = json.dumps(json_obj)
+    jschema = jschema.replace('"required":', '"additionalproperties": true, "required":')
+    jschema = jschema.replace('"type":', ' "description": "", "type":')
+
+    if (keywords):
+      jschema = jschema.replace('"type": "string"', ' "maxLength": 0, "minLength": 0, "pattern": "", "type": "string"')
+      jschema = jschema.replace('"type": "integer"', ' "maximum": 0, "mininimum": 0, "multipleOf": 0, "type": "integer"')
+      jschema = jschema.replace('"type": "array"', ' "maxItems": 0, "minItems": 0, "uniqueItems": "false", "type": "array"')
+
+    jschema = '{ "self": { "name": "' + name + '", "version": "' + version + '", "description": ""} , "dataformatversion": "1.0.0", "jsonschema": ' + jschema + '}'
+    #Draft4Validator.check_schema(json.loads(jschema))
+    try:
+      print(json.dumps(json.loads(jschema), sort_keys=True, indent=4 ))
+    except ValueError:
+      raise DcaeException('Problem with JSON generation')
+
+def addfile(filename, schema):
+  try: 
+    fileadd = open(filename, "r")
+  except IOError:
+    raise DcaeException('Cannot open' + filename)
+  try: 
+    json_object = json.loads(fileadd.read())
+    schema.add_object(json_object)
+  except ValueError:
+    raise DcaeException('Bad JSON file: ' + filename)
+  finally:
+    fileadd.close()
+
+
diff --git a/dcae-cli/dcae_cli/commands/tests/mocked_components/model/badjson b/dcae-cli/dcae_cli/commands/tests/mocked_components/model/badjson
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/dcae-cli/dcae_cli/commands/tests/mocked_components/model/generatedir/ex1.json b/dcae-cli/dcae_cli/commands/tests/mocked_components/model/generatedir/ex1.json
new file mode 100755 (executable)
index 0000000..7db1e06
--- /dev/null
@@ -0,0 +1,3 @@
+{
+   "foobar": "test 1"
+}
diff --git a/dcae-cli/dcae_cli/commands/tests/mocked_components/model/generatedir/ex2.json b/dcae-cli/dcae_cli/commands/tests/mocked_components/model/generatedir/ex2.json
new file mode 100755 (executable)
index 0000000..75a5fa4
--- /dev/null
@@ -0,0 +1,4 @@
+{
+   "foobar2": "test 1"
+}
+
index 754e6a7..b8402f6 100644 (file)
@@ -77,6 +77,41 @@ def test_basic():
     spec_str = runner.invoke(cli, cmd, obj=obj).output
     assert df_spec == json.loads(spec_str)
 
+    # test of generate
+    bad_dir = os.path.join(TEST_DIR, 'mocked_components', 'model', 'baddir')
+    cmd = "data_format generate --keywords \"name:1.0.2\" {:}".format(bad_dir).split()
+    err_str = runner.invoke(cli, cmd, obj=obj).output
+    assert "does not exist" in err_str 
+
+    empty_dir = os.path.join(TEST_DIR, 'mocked_components', 'model', 'emptydir')
+    try:
+      os.stat(empty_dir)
+    except:
+      os.mkdir(empty_dir)
+    cmd = "data_format generate --keywords \"name:1.0.2\" {:}".format(empty_dir).split()
+    err_str = runner.invoke(cli, cmd, obj=obj).output
+    assert "No JSON files found" in err_str 
+
+    bad_json = os.path.join(TEST_DIR, 'mocked_components', 'model', 'badjson')
+    cmd = "data_format generate --keywords \"name:1.0.2\" {:}".format(bad_json).split()
+    err_str = runner.invoke(cli, cmd, obj=obj).output
+    assert "Bad JSON file" in err_str 
+
+    generate_dir = os.path.join(TEST_DIR, 'mocked_components', 'model', 'generatedir')
+    cmd = "data_format generate --keywords name:1.0.2 {:} ".format(generate_dir).split()
+    out_str = runner.invoke(cli, cmd, obj=obj).output
+    assert '{\n    "dataformatversion": "1.0.0", \n    "jsonschema": {\n        "$schema": "http://json-schema.org/draft-04/schema#", \n        "description": "", \n        "properties": {\n            "foobar": {\n                "description": "", \n                "maxLength": 0, \n                "minLength": 0, \n                "pattern": "", \n                "type": "string"\n            }, \n            "foobar2": {\n                "description": "", \n                "maxLength": 0, \n                "minLength": 0, \n                "pattern": "", \n                "type": "string"\n            }\n        }, \n        "type": "object"\n    }, \n    "self": {\n        "description": "", \n        "name": "name", \n        "version": "1.0.2"\n    }\n}\n' ==  out_str
+
+    generate_dir = os.path.join(TEST_DIR, 'mocked_components', 'model', 'generatedir')
+    cmd = "data_format generate name:1.0.2 {:} ".format(generate_dir).split()
+    out_str = runner.invoke(cli, cmd, obj=obj).output
+    assert '{\n    "dataformatversion": "1.0.0", \n    "jsonschema": {\n        "$schema": "http://json-schema.org/draft-04/schema#", \n        "description": "", \n        "properties": {\n            "foobar": {\n                "description": "", \n                "type": "string"\n            }, \n            "foobar2": {\n                "description": "", \n                "type": "string"\n            }\n        }, \n        "type": "object"\n    }, \n    "self": {\n        "description": "", \n        "name": "name", \n        "version": "1.0.2"\n    }\n}\n' == out_str
+
+    generate_dir = os.path.join(TEST_DIR, 'mocked_components', 'model', 'generatedir', 'ex1.json')
+    cmd = "data_format generate name:1.0.2 {:} ".format(generate_dir).split()
+    out_str = runner.invoke(cli, cmd, obj=obj).output
+    assert '{\n    "dataformatversion": "1.0.0", \n    "jsonschema": {\n        "$schema": "http://json-schema.org/draft-04/schema#", \n        "additionalproperties": true, \n        "description": "", \n        "properties": {\n            "foobar": {\n                "description": "", \n                "type": "string"\n            }\n        }, \n        "required": [\n            "foobar"\n        ], \n        "type": "object"\n    }, \n    "self": {\n        "description": "", \n        "name": "name", \n        "version": "1.0.2"\n    }\n}\n' == out_str
+
 
 if __name__ == '__main__':
     '''Test area'''
index f5a4b82..a75165e 100644 (file)
@@ -498,10 +498,20 @@ def _group_config(config, config_key_map):
     return grouped_conf
 
 
+def _apply_inputs(config, inputs_map):
+    """Update configuration with inputs
+
+    This method updates the values of the configuration parameters using values
+    from the inputs map.
+    """
+    config.update(inputs_map)
+    return config
+
+
 @contextlib.contextmanager
 def config_context(user, cname, cver, params, interface_map, instance_map,
-        config_key_map, dmaap_map={}, instance_prefix=None, host=consul_host,
-        always_cleanup=True, force_config=False):
+        config_key_map, dmaap_map={}, inputs_map={}, instance_prefix=None,
+        host=consul_host, always_cleanup=True, force_config=False):
     '''Convenience utility for creating configs and cleaning them up
 
     Args
@@ -518,6 +528,7 @@ def config_context(user, cname, cver, params, interface_map, instance_map,
                 user, cname, cver, params, interface_map, instance_map, dmaap_map,
                 instance_prefix, force=force_config)
 
+        conf = _apply_inputs(conf, inputs_map)
         conf = _group_config(conf, config_key_map)
 
         push_config(conf_key, conf, rels_key, rels, dmaap_key, dmaap_map, host)
index 0c89d6d..138e909 100644 (file)
@@ -287,9 +287,10 @@ def validate_dmaap_map_entries(dmaap_map, mr_config_keys, dr_config_keys):
         logger.error("Please use the \"--dmaap-file\" option")
         return False
 
+    config_keys = dr_config_keys + mr_config_keys
     # Look for missing keys
     is_missing = lambda config_key: config_key not in dmaap_map
-    missing_keys = list(filter(is_missing, mr_config_keys))
+    missing_keys = list(filter(is_missing, config_keys))
 
     if missing_keys:
         logger.error("Missing config keys in dmaap json: {0}".format(
@@ -298,7 +299,7 @@ def validate_dmaap_map_entries(dmaap_map, mr_config_keys, dr_config_keys):
         return False
 
     # Look for unexpected keys
-    is_unexpected = lambda config_key: config_key not in mr_config_keys
+    is_unexpected = lambda config_key: config_key not in config_keys
     unexpected_keys = list(filter(is_unexpected, dmaap_map.keys()))
 
     if unexpected_keys:
diff --git a/dcae-cli/dcae_cli/util/inputs.py b/dcae-cli/dcae_cli/util/inputs.py
new file mode 100644 (file)
index 0000000..4b212e2
--- /dev/null
@@ -0,0 +1,40 @@
+# ============LICENSE_START=======================================================
+# org.onap.dcae
+# ================================================================================
+# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+"""
+Functions for handling inputs
+"""
+
+class InputsValidationError(RuntimeError):
+    pass
+
+def filter_entries(inputs_map, spec):
+    """Filter inputs entries that are not in the spec"""
+    param_names = [ p["name"] for p in spec["parameters"] \
+            if "sourced_at_deployment" in p and p["sourced_at_deployment"] ]
+
+    # Identify any missing parameters from inputs_map
+    missing = list(filter(lambda pn: pn not in inputs_map, param_names))
+
+    if missing:
+        raise InputsValidationError(
+            "Inputs map is missing keys: {0}".format(missing))
+
+    return { pn: inputs_map[pn] for pn in param_names }
index 11bc429..67535fa 100644 (file)
@@ -27,7 +27,7 @@ import six
 from functools import partial
 import click
 from dcae_cli.util import docker_util as du
-from dcae_cli.util import dmaap
+from dcae_cli.util import dmaap, inputs
 from dcae_cli.util.cdap_util import run_component as run_cdap_component
 from dcae_cli.util.exc import DcaeException
 from dcae_cli.util import discovery as dis
@@ -124,7 +124,7 @@ def _verify_component(name, max_wait, consul_host):
 
 
 def run_component(user, cname, cver, catalog, additional_user, attached, force,
-        dmaap_map, external_ip=None):
+        dmaap_map, inputs_map, external_ip=None):
     '''Runs a component based on the component type
 
     Args
@@ -134,6 +134,8 @@ def run_component(user, cname, cver, catalog, additional_user, attached, force,
         this flag is set to True.
     dmaap_map: (dict) config_key to message router or data router connections.
         Used as a manual way to make available this information for the component.
+    inputs_map: (dict) config_key to value that is intended to be provided at
+        deployment time as an input
     '''
     cname, cver = catalog.verify_component(cname, cver)
     ctype = catalog.get_component_type(cname, cver)
@@ -154,12 +156,13 @@ def run_component(user, cname, cver, catalog, additional_user, attached, force,
 
         spec = catalog.get_component_spec(cname, cver)
         config_key_map = build_config_keys_map(spec)
+        inputs_map = inputs.filter_entries(inputs_map, spec)
 
         dmaap_map = _update_delivery_urls(spec, profile.docker_host.split(":")[0],
                 dmaap_map)
 
         with config_context(user, cname, cver, params, interface_map,
-                instance_map, config_key_map, dmaap_map=dmaap_map,
+                instance_map, config_key_map, dmaap_map=dmaap_map, inputs_map=inputs_map,
                 always_cleanup=should_wait, force_config=force) as (instance_name, _):
             image = catalog.get_docker_image(cname, cver)
             docker_config = catalog.get_docker_config(cname, cver)
@@ -201,17 +204,20 @@ def run_component(user, cname, cver, catalog, additional_user, attached, force,
     elif ctype =='cdap':
         (jar, config, spec) = catalog.get_cdap(cname, cver)
         config_key_map = build_config_keys_map(spec)
+        inputs_map = inputs.filter_entries(inputs_map, spec)
+
         params, interface_map = catalog.get_discovery_for_cdap(cname, cver, neighbors)
 
         with config_context(user, cname, cver, params, interface_map, instance_map,
-                config_key_map, dmaap_map=dmaap_map, always_cleanup=False,
+                config_key_map, dmaap_map=dmaap_map, inputs_map=inputs_map, always_cleanup=False,
                 force_config=force) as (instance_name, templated_conf):
             run_cdap_component(catalog, params, instance_name, profile, jar, config, spec, templated_conf)
     else:
         raise DcaeException("Unsupported component type for run")
 
 
-def dev_component(user, catalog, specification, additional_user, force, dmaap_map):
+def dev_component(user, catalog, specification, additional_user, force, dmaap_map,
+        inputs_map):
     '''Sets up the discovery layer for in development component
 
     The passed-in component specification is
@@ -234,6 +240,8 @@ def dev_component(user, catalog, specification, additional_user, force, dmaap_ma
         this flag is set to True.
     dmaap_map: (dict) config_key to message router connections. Used as a
         manual way to make available this information for the component.
+    inputs_map: (dict) config_key to value that is intended to be provided at
+        deployment time as an input
     '''
     instance_map = _get_instances(user, additional_user)
     neighbors = six.iterkeys(instance_map)
@@ -247,11 +255,13 @@ def dev_component(user, catalog, specification, additional_user, force, dmaap_ma
     cname = specification["self"]["name"]
     cver = specification["self"]["version"]
     config_key_map = build_config_keys_map(specification)
+    inputs_map = inputs.filter_entries(inputs_map, specification)
 
     dmaap_map = _update_delivery_urls(specification, "localhost", dmaap_map)
 
     with config_context(user, cname, cver, params, interface_map, instance_map,
-        config_key_map, dmaap_map, always_cleanup=True, force_config=force) \
+        config_key_map, dmaap_map, inputs_map=inputs_map, always_cleanup=True,
+        force_config=force) \
                 as (instance_name, templated_conf):
 
         click.echo("Ready for component development")
index bf9205a..aed5ca8 100644 (file)
@@ -443,6 +443,11 @@ def test_parse_instance_lookup():
     assert dis.parse_instance_lookup(results) == "192.168.1.100:8080"
 
 
+def test_apply_inputs():
+    updated_config = dis._apply_inputs({"foo": "bar"}, {"foo": "baz"})
+    assert updated_config == {"foo": "baz"}
+
+
 if __name__ == '__main__':
     '''Test area'''
     pytest.main([__file__, ])
diff --git a/dcae-cli/dcae_cli/util/tests/test_inputs.py b/dcae-cli/dcae_cli/util/tests/test_inputs.py
new file mode 100644 (file)
index 0000000..5271705
--- /dev/null
@@ -0,0 +1,37 @@
+# ============LICENSE_START=======================================================
+# org.onap.dcae
+# ================================================================================
+# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved.
+# ================================================================================
+# 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.
+# ============LICENSE_END=========================================================
+#
+# ECOMP is a trademark and service mark of AT&T Intellectual Property.
+
+"""
+Tests for inputs module
+"""
+import pytest
+from dcae_cli.util import inputs
+
+
+def test_filter_entries():
+    spec = { "parameters": [{"name": "foo"}, {"name": "bar",
+        "sourced_at_deployment": False}, {"name": "baz", "sourced_at_deployment": True}] }
+
+    with pytest.raises(inputs.InputsValidationError):
+        inputs.filter_entries({}, spec)
+
+    inputs_map = { "foo": "do not copy", "baz": "hello world", "extra": "do not copy" }
+
+    assert len(inputs.filter_entries(inputs_map, spec)) == 1
index 63dce5e..52d5aa1 100644 (file)
@@ -9,5 +9,6 @@ jsonschema==2.5.1
 docker-py==1.10.6
 terminaltables==3.1.0
 python-discovery-client==2.0.0
-python-dockering==1.2.0
+python-dockering==1.2.1
 psycopg2==2.7.1
+genson==0.2.2
index 25c4c4c..8c6405c 100644 (file)
@@ -51,6 +51,7 @@ setup(
                       'docker-py>=1.10,<2',
                       'terminaltables',
                       'psycopg2',
+                      'genson',
                       'python-discovery-client>=2.0.0',
                       'python-dockering>=1.0.0,<2.0.0'
                       ],