Repository: ansible/mazer (language: python)

Issue:
mazer version fails from pypi?
<!---
Verify first that your issue/request is not already reported on GitHub.
 -->
I just downloaded the latest version of mazer using pip and I’m getting this error

```(mazer) dnewswan-mac:demo dnewswan$ mazer version
'NoneType' object has no attribute 'copy'
Traceback (most recent call last):
  File "/Users/dnewswan/code/venv/mazer/bin/mazer", line 10, in <module>
    sys.exit(main())
  File "/Users/dnewswan/code/venv/mazer/lib/python3.6/site-packages/ansible_galaxy_cli/main.py", line 36, in main
    exit_code = cli.run()
  File "/Users/dnewswan/code/venv/mazer/lib/python3.6/site-packages/ansible_galaxy_cli/cli/galaxy.py", line 221, in run
    galaxy_context = self._get_galaxy_context(self.options, self.config)
  File "/Users/dnewswan/code/venv/mazer/lib/python3.6/site-packages/ansible_galaxy_cli/cli/galaxy.py", line 188, in _get_galaxy_context
    server = config.server.copy()
AttributeError: 'NoneType' object has no attribute 'copy'```
```
can’t even check the version

## Bug Report

##### MAZER VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "mazer version" between quotes below -->
```

```

##### CONFIGURATION
<!--- If using an ansible.cfg file, list the contents of that file here"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->


mazer version fails from pypi?
<!---
Verify first that your issue/request is not already reported on GitHub.
 -->
I just downloaded the latest version of mazer using pip and I’m getting this error

```(mazer) dnewswan-mac:demo dnewswan$ mazer version
'NoneType' object has no attribute 'copy'
Traceback (most recent call last):
  File "/Users/dnewswan/code/venv/mazer/bin/mazer", line 10, in <module>
    sys.exit(main())
  File "/Users/dnewswan/code/venv/mazer/lib/python3.6/site-packages/ansible_galaxy_cli/main.py", line 36, in main
    exit_code = cli.run()
  File "/Users/dnewswan/code/venv/mazer/lib/python3.6/site-packages/ansible_galaxy_cli/cli/galaxy.py", line 221, in run
    galaxy_context = self._get_galaxy_context(self.options, self.config)
  File "/Users/dnewswan/code/venv/mazer/lib/python3.6/site-packages/ansible_galaxy_cli/cli/galaxy.py", line 188, in _get_galaxy_context
    server = config.server.copy()
AttributeError: 'NoneType' object has no attribute 'copy'```
```
can’t even check the version

## Bug Report

##### MAZER VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "mazer version" between quotes below -->
```

```

##### CONFIGURATION
<!--- If using an ansible.cfg file, list the contents of that file here"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->




Discussion / hints:
@alikins I think David got this problem when trying to install Mazer while an invalid config was present. I'll try to reproduce the problem myself, but it looks the same from his previous report.
@ironfroggy previous report? I didn't see anything in issues.
It was reported in chat, it doesn't look like he filed an issue. My
apologies for not following up on the issue sooner.

On Tue, Jun 11, 2019 at 9:30 AM Adrian Likins <notifications@github.com>
wrote:

> @ironfroggy <https://github.com/ironfroggy> previous report? I didn't see
> anything in issues.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/ansible/mazer/issues/283?email_source=notifications&email_token=AAAIRJX6MYWDYQ3STEFC2KTPZ6SFPA5CNFSM4HWTYVRKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXNDZAA#issuecomment-500841600>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAAIRJTCOEXE6PA4KWVAOQTPZ6SFPANCNFSM4HWTYVRA>
> .
>


-- 

CALVIN SPEALMAN

SENIOR QUALITY ENGINEER

cspealma@redhat.com  M: +1.336.210.5107
[image: https://red.ht/sig] <https://red.ht/sig>
TRIED. TESTED. TRUSTED. <https://redhat.com/trusted>

I haven't been able to reproduce this myself, no matter how I formulate a config file. 
I can reproduce with a config file that has no value for the rhs of 'server', like this:

``` yaml
server:
  # ignore_certs: false
  # url: https://galaxy-qa.ansible.com
  # api_key: 0c6973571301ce1f3b08c27978e3f50a057157b8

collections_path: ~/.ansible/collections/
global_collections_path: /usr/share/ansible/collections/
```

```
(mazer_0.4.0_py36) [newswoop:F29:mazer (mazer_config_broke %)]$ mazer --config server_none_mazer_config.yml version
ERROR| 'NoneType' object has no attribute 'copy'
Traceback (most recent call last):
  File "/home/adrian/venvs/mazer_0.4.0_py36/bin/mazer", line 11, in <module>
    load_entry_point('mazer', 'console_scripts', 'mazer')()
  File "/home/adrian/src/mazer/ansible_galaxy_cli/main.py", line 36, in main
    exit_code = cli.run()
  File "/home/adrian/src/mazer/ansible_galaxy_cli/cli/galaxy.py", line 221, in run
    galaxy_context = self._get_galaxy_context(self.options, self.config)
  File "/home/adrian/src/mazer/ansible_galaxy_cli/cli/galaxy.py", line 188, in _get_galaxy_context
    server = config.server.copy()
AttributeError: 'NoneType' object has no attribute 'copy'
```

Currently in the app itself, the config object is never persisted/saved to a file, so the copy() isn't really.

At the moment there is not much validation of the config object or the data from yaml loading (pretty much just ensures that any required attributes exist, but not their value). 

The config class could be replaced with an `attrs` based class, with some validators. Or some more adhoc validation in ansible_galaxy.config.load().

Since the config as is probably won't survive the transplant to ansible-galaxy, I'll just make sure it raises a GalaxyConfigFileError in that case.
@alikins I think David got this problem when trying to install Mazer while an invalid config was present. I'll try to reproduce the problem myself, but it looks the same from his previous report.
@ironfroggy previous report? I didn't see anything in issues.
It was reported in chat, it doesn't look like he filed an issue. My
apologies for not following up on the issue sooner.

On Tue, Jun 11, 2019 at 9:30 AM Adrian Likins <notifications@github.com>
wrote:

> @ironfroggy <https://github.com/ironfroggy> previous report? I didn't see
> anything in issues.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/ansible/mazer/issues/283?email_source=notifications&email_token=AAAIRJX6MYWDYQ3STEFC2KTPZ6SFPA5CNFSM4HWTYVRKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXNDZAA#issuecomment-500841600>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAAIRJTCOEXE6PA4KWVAOQTPZ6SFPANCNFSM4HWTYVRA>
> .
>


-- 

CALVIN SPEALMAN

SENIOR QUALITY ENGINEER

cspealma@redhat.com  M: +1.336.210.5107
[image: https://red.ht/sig] <https://red.ht/sig>
TRIED. TESTED. TRUSTED. <https://redhat.com/trusted>

I haven't been able to reproduce this myself, no matter how I formulate a config file. 
I can reproduce with a config file that has no value for the rhs of 'server', like this:

``` yaml
server:
  # ignore_certs: false
  # url: https://galaxy-qa.ansible.com
  # api_key: 0c6973571301ce1f3b08c27978e3f50a057157b8

collections_path: ~/.ansible/collections/
global_collections_path: /usr/share/ansible/collections/
```

```
(mazer_0.4.0_py36) [newswoop:F29:mazer (mazer_config_broke %)]$ mazer --config server_none_mazer_config.yml version
ERROR| 'NoneType' object has no attribute 'copy'
Traceback (most recent call last):
  File "/home/adrian/venvs/mazer_0.4.0_py36/bin/mazer", line 11, in <module>
    load_entry_point('mazer', 'console_scripts', 'mazer')()
  File "/home/adrian/src/mazer/ansible_galaxy_cli/main.py", line 36, in main
    exit_code = cli.run()
  File "/home/adrian/src/mazer/ansible_galaxy_cli/cli/galaxy.py", line 221, in run
    galaxy_context = self._get_galaxy_context(self.options, self.config)
  File "/home/adrian/src/mazer/ansible_galaxy_cli/cli/galaxy.py", line 188, in _get_galaxy_context
    server = config.server.copy()
AttributeError: 'NoneType' object has no attribute 'copy'
```

Currently in the app itself, the config object is never persisted/saved to a file, so the copy() isn't really.

At the moment there is not much validation of the config object or the data from yaml loading (pretty much just ensures that any required attributes exist, but not their value). 

The config class could be replaced with an `attrs` based class, with some validators. Or some more adhoc validation in ansible_galaxy.config.load().

Since the config as is probably won't survive the transplant to ansible-galaxy, I'll just make sure it raises a GalaxyConfigFileError in that case.

Fix patch:
diff --git a/ansible_galaxy/config/config.py b/ansible_galaxy/config/config.py
index 309f6c3b..a0a04381 100644
--- a/ansible_galaxy/config/config.py
+++ b/ansible_galaxy/config/config.py
@@ -25,10 +25,10 @@ def as_dict(self):
     @classmethod
     def from_dict(cls, data):
         inst = cls()
-        inst.server = data.get('server', inst.server)
-        inst.collections_path = data.get('collections_path', inst.collections_path)
-        inst.global_collections_path = data.get('global_collections_path', inst.global_collections_path)
-        inst.options = data.get('options', inst.options)
+        inst.server = data.get('server') or inst.server
+        inst.collections_path = data.get('collections_path') or inst.collections_path
+        inst.global_collections_path = data.get('global_collections_path') or inst.global_collections_path
+        inst.options = data.get('options') or inst.options
         return inst
 
 


Test patch:
diff --git a/tests/ansible_galaxy/config/test_config.py b/tests/ansible_galaxy/config/test_config.py
index f36cb379..6a525e91 100644
--- a/tests/ansible_galaxy/config/test_config.py
+++ b/tests/ansible_galaxy/config/test_config.py
@@ -56,6 +56,27 @@ def test_config_from_dict():
     assert config_.options['some_option'] == 'some_option_value'
 
 
+def test_config_from_dict_with_none_values():
+    config_data = OrderedDict([
+        ('server', None),
+        ('collections_path', None),
+        ('globals_collections_path', None),
+        ('options', None),
+    ])
+
+    config_ = config.Config.from_dict(config_data)
+    assert_object(config_)
+
+    # If the yaml/config_data has None values for server/options, should
+    # get empty dicts instead
+    # See https://github.com/ansible/mazer/issues/283
+    assert config_.server == {}
+    assert config_.options == {}
+    # collections_path attrs can be 'unset' to None
+    assert config_.collections_path is None
+    assert config_.global_collections_path is None
+
+
 def test_config_as_dict_empty():
     config_ = config.Config()
     config_data = config_.as_dict()
diff --git a/tests/ansible_galaxy_cli/cli/test_galaxy.py b/tests/ansible_galaxy_cli/cli/test_galaxy.py
index 5fc42cbc..33591111 100644
--- a/tests/ansible_galaxy_cli/cli/test_galaxy.py
+++ b/tests/ansible_galaxy_cli/cli/test_galaxy.py
@@ -94,6 +94,18 @@ def test_run_list(mazer_args_for_test):
     log.debug('res: %s', res)
 
 
+def test_version_invalid_config(mazer_args_for_test, mocker):
+
+    mocker.patch('ansible_galaxy_cli.cli.galaxy.config.config_file.load', return_value=None)
+    cli = galaxy.GalaxyCLI(args=mazer_args_for_test + ['version'])
+    cli.parse()
+    res = cli.run()
+
+    log.debug('mat: %s', mazer_args_for_test)
+    log.debug('res: %s', res)
+    assert res == 0
+
+
 def test_publish_no_args(mazer_args_for_test):
     cli = galaxy.GalaxyCLI(args=mazer_args_for_test + ['publish'])
     cli.parse()