Repository: StackStorm/st2

Issue:
Default values in pack config schema not being honored
If a pack has a schema that declares a default value for a specific property and the property is absent in the config instance (`/opt/stackstorm/configs/<pack>.yaml`), then a python runner attempts to read this value from the config a `KeyError` is thrown instead of the default value being returned.

# Steps to reproduce
I've created a test pack that demonstrates this: https://github.com/nmaludy/stackstorm-testconfigdefault
```
st2 pack install https://github.com/nmaludy/stackstorm-testconfigdefault
st2 run testconfigdefault.get_config key='output'
```

# Output:
```
[root@stackstorm ~]# st2 run testconfigdefault.get_config key='output'
.
id: 593fdecfa814c06997fc8fd4
status: failed
parameters: 
  key: output
result: 
  exit_code: 1
  result: None
  stderr: "Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py", line 259, in <module>
    obj.run()
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py", line 155, in run
    output = action.run(**self._parameters)
  File "/opt/stackstorm/packs/testconfigdefault/actions/get_config.py", line 11, in run
    return self.config[key]
KeyError: u'output'
"
  stdout: ''
```

# Expected output:
I expect to have the default value of 'json' from the schema be returned. Maybe i'm incorrect in my assumption?



Discussion:
Which version of StackStorm are you running?

I believe it's related to an issue which has been fixed in v2.2.1 - https://github.com/StackStorm/st2/pull/3260
```
[root@stackstorm ~]# st2 --version
st2 2.2.0
```

I'll try upgrading
Same error with 2.2.1

```
[root@stackstorm ~]# st2 --version
st2 2.2.1
[root@stackstorm ~]# st2 run testconfigdefault.get_config key='output'
.
id: 593feb0da814c006059ee511
status: failed
parameters: 
  key: output
result: 
  exit_code: 1
  result: None
  stderr: "Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py", line 259, in <module>
    obj.run()
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/runners/python_action_wrapper.py", line 155, in run
    output = action.run(**self._parameters)
  File "/opt/stackstorm/packs/testconfigdefault/actions/get_config.py", line 11, in run
    return self.config[key]
KeyError: u'output'
"
  stdout: ''
```
Thanks for testing with v2.2.1.

I was able to reproduce the issue. It's related to the config item not being required (`required: false`). We only set default values for items which are required.

I believe we had a special reason to do go with this behavior in the past (something with Mistral workflow and default parameter values, IIRC).

Simple solution is to change config item required attribute from false to true (it has no negative affect because item being required and having a default value is the same as not being required and not having a default value).

Having said that, I will dig in and think about it some more - maybe now it makes sense to also assign default values for config items which have `required: false`.
@Kami that did work, thanks for the workaround and thanks for looking into this!

Resolving patch:
diff --git a/st2common/st2common/util/config_loader.py b/st2common/st2common/util/config_loader.py
--- a/st2common/st2common/util/config_loader.py
+++ b/st2common/st2common/util/config_loader.py
@@ -154,11 +154,10 @@ def _assign_default_values(self, schema, config):
         """
         for schema_item_key, schema_item in six.iteritems(schema):
             default_value = schema_item.get('default', None)
-            is_required = schema_item.get('required', False)
             is_object = schema_item.get('type', None) == 'object'
             has_properties = schema_item.get('properties', None)
 
-            if is_required and default_value and not config.get(schema_item_key, None):
+            if default_value and not config.get(schema_item_key, None):
                 config[schema_item_key] = default_value
 
             # Inspect nested object properties


Test patch:
diff --git a/st2common/tests/unit/test_config_loader.py b/st2common/tests/unit/test_config_loader.py
--- a/st2common/tests/unit/test_config_loader.py
+++ b/st2common/tests/unit/test_config_loader.py
@@ -75,7 +75,8 @@ def test_get_config_some_values_overriden_in_datastore(self):
             'api_secret': 'some_api_secret',
             'regions': ['us-west-1'],
             'region': 'default-region-value',
-            'private_key_path': 'some_private_key'
+            'private_key_path': 'some_private_key',
+            'non_required_with_default_value': 'config value'
         }
 
         self.assertEqual(config, expected_config)
@@ -93,6 +94,27 @@ def test_get_config_default_value_from_config_schema_is_used(self):
         config = loader.get_config()
         self.assertEqual(config['region'], 'us-west-1')
 
+        # Config item attribute has required: false
+        # Value is provided in the config - it should be used as provided
+        pack_name = 'dummy_pack_5'
+
+        loader = ContentPackConfigLoader(pack_name=pack_name)
+        config = loader.get_config()
+        self.assertEqual(config['non_required_with_default_value'], 'config value')
+
+        config_db = Config.get_by_pack(pack_name)
+        del config_db['values']['non_required_with_default_value']
+        Config.add_or_update(config_db)
+
+        # No value in the config - default value should be used
+        config_db = Config.get_by_pack(pack_name)
+        config_db.delete()
+
+        # No config exists for that pack - default value should be used
+        loader = ContentPackConfigLoader(pack_name=pack_name)
+        config = loader.get_config()
+        self.assertEqual(config['non_required_with_default_value'], 'some default value')
+
     def test_default_values_from_schema_are_used_when_no_config_exists(self):
         pack_name = 'dummy_pack_5'
         config_db = Config.get_by_pack(pack_name)