Repository: ansible/ansible

Issue:
Ansible should not preserve quotes on /etc/lsb-release DISTRIB_ID value
##### ISSUE TYPE

 - Bug Report

##### COMPONENT NAME

Ansible facts, specifically `ansible_distribution` et al, and how they are evaluated.

##### ANSIBLE VERSION

```
ansible 2.3.1.0
  config file = /home/naftuli/Documents/Development/naftulikay/ansible-role-time/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609]
```

##### CONFIGURATION

None.

##### OS / ENVIRONMENT

elementary OS Loki, which is Ubuntu 16.04. 

##### SUMMARY

My `ansible_distribution` here is being reported as the literal string `"elementary"` including the quotes, because these are present in `/etc/lsb-release`:

```
DISTRIB_ID="elementary"
DISTRIB_RELEASE=0.4.1
DISTRIB_CODENAME=loki
DISTRIB_DESCRIPTION="elementary OS 0.4.1 Loki"
```

##### STEPS TO REPRODUCE

Run the following Ansible command on a Loki VM or something:

```
$ ansible -i 127.0.0.1, -c local all -m setup -a filter=ansible_distribution
127.0.0.1 | SUCCESS => {
    "ansible_facts": {
        "ansible_distribution": "\"elementary\""
    },
    "changed": false
}
```

##### EXPECTED RESULTS

I expected Ansible to unescape quotes in `/etc/lsb-release`, as this file is straight from the upstream distribution, unmodified.

##### ACTUAL RESULTS

I instead get the literal string `"elementary"` as opposed to `elementary`:

```
127.0.0.1 | SUCCESS => {
    "ansible_facts": {
        "ansible_distribution": "\"elementary\""
    },
    "changed": false
}
```



Discussion:
Looking around, the format of lsb-release isn't really specified. The shell version of the 'lsb_release' command doesn't seem to read /etc/lsb-release, while the newer python based lsb_release.py does read it and will remove quotes.

Assuming no one has named their distro  "THE_QUOTES_ARE_INCLUDED_IN_THE_NAME" and expect the quotes to be included, removing them in the facts code should be ok.

What is the output of the lsb_release command?

-  `lsb_release`
- `lsb_release -a`
- `lsb_release -i`
Stripping the quotes breaks an existing unit test that has the DISTRIB_DESCRIPTION quoted.

The Ubuntu 16.04 /etc/lsb-release looks like:
```
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04 LTS"
```

with description quoted but not ID. To me, I would say quoting the the DISTRIB_ID is unconventional,
but cant say it's wrong.

Is there is a good doc describing the /etc/lsb-release format that explains the expected behavior for quoted values? 

I think we should just leave it as is for now until we see a better spec for the format.
@alikins Here are the commands you requested:

```
naftuli@reprisal:~$ lsb_release
No LSB modules are available.
naftuli@reprisal:~$ lsb_release -a
No LSB modules are available.
Distributor ID: elementary
Description:    elementary OS 0.4.1 Loki
Release:        0.4.1
Codename:       loki
naftuli@reprisal:~$ lsb_release -i
Distributor ID: elementary
naftuli@reprisal:~$ cat /etc/lsb-release
DISTRIB_ID="elementary"
DISTRIB_RELEASE=0.4.1
DISTRIB_CODENAME=loki
DISTRIB_DESCRIPTION="elementary OS 0.4.1 Loki"
```

This is a _very_ stock installation of elementary OS, being a downstream from Ubuntu 16.04 Xenial.

```
$ ansible -i 127.0.0.1, -c local all -m setup | grep distribution
        "ansible_distribution": "\"elementary\"",
        "ansible_distribution_major_version": "0",
        "ansible_distribution_release": "loki",
        "ansible_distribution_version": "0.4.1",
```

And again, for giggles:

```
$ ansible --version
ansible 2.3.1.0
  config file =
  configured module search path = Default w/o overrides
  python version = 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609]
```

Resolving patch:
diff --git a/lib/ansible/module_utils/facts/system/distribution.py b/lib/ansible/module_utils/facts/system/distribution.py
--- a/lib/ansible/module_utils/facts/system/distribution.py
+++ b/lib/ansible/module_utils/facts/system/distribution.py
@@ -92,6 +92,8 @@ class DistributionFiles:
         'Archlinux': 'Arch Linux'
     }
 
+    STRIP_QUOTES = r'\'\"\\'
+
     def __init__(self, module):
         self.module = module
 
@@ -108,6 +110,7 @@ def _get_dist_file_content(self, path, allow_empty=False):
 
     def _parse_dist_file(self, name, dist_file_content, path, collected_facts):
         dist_file_dict = {}
+        dist_file_content = dist_file_content.strip(DistributionFiles.STRIP_QUOTES)
         if name in self.SEARCH_STRING:
             # look for the distribution string in the data and replace according to RELEASE_NAME_MAP
             # only the distribution name is set, the version is assumed to be correct from platform.dist()
diff --git a/lib/ansible/module_utils/facts/system/lsb.py b/lib/ansible/module_utils/facts/system/lsb.py
--- a/lib/ansible/module_utils/facts/system/lsb.py
+++ b/lib/ansible/module_utils/facts/system/lsb.py
@@ -27,6 +27,7 @@
 class LSBFactCollector(BaseFactCollector):
     name = 'lsb'
     _fact_ids = set()
+    STRIP_QUOTES = r'\'\"\\'
 
     def _lsb_release_bin(self, lsb_path, module):
         lsb_facts = {}
@@ -97,5 +98,9 @@ def collect(self, module=None, collected_facts=None):
         if lsb_facts and 'release' in lsb_facts:
             lsb_facts['major_release'] = lsb_facts['release'].split('.')[0]
 
+        for k, v in lsb_facts.items():
+            if v:
+                lsb_facts[k] = v.strip(LSBFactCollector.STRIP_QUOTES)
+
         facts_dict['lsb'] = lsb_facts
         return facts_dict


Test patch:
diff --git a/test/units/module_utils/facts/system/test_lsb.py b/test/units/module_utils/facts/system/test_lsb.py
--- a/test/units/module_utils/facts/system/test_lsb.py
+++ b/test/units/module_utils/facts/system/test_lsb.py
@@ -88,7 +88,7 @@ def test_etc_lsb_release(self):
         self.assertIsInstance(facts_dict, dict)
         self.assertEqual(facts_dict['lsb']['release'], '14.04')
         self.assertEqual(facts_dict['lsb']['id'], 'Ubuntu')
-        self.assertEqual(facts_dict['lsb']['description'], '"Ubuntu 14.04.3 LTS"')
+        self.assertEqual(facts_dict['lsb']['description'], 'Ubuntu 14.04.3 LTS')
         self.assertEqual(facts_dict['lsb']['codename'], 'trusty')
 
     def test_etc_lsb_release_no_decimal_release(self):
@@ -104,5 +104,5 @@ def test_etc_lsb_release_no_decimal_release(self):
         self.assertIsInstance(facts_dict, dict)
         self.assertEqual(facts_dict['lsb']['release'], '11')
         self.assertEqual(facts_dict['lsb']['id'], 'AwesomeOS')
-        self.assertEqual(facts_dict['lsb']['description'], '"AwesomeÖS 11"')
+        self.assertEqual(facts_dict['lsb']['description'], 'AwesomeÖS 11')
         self.assertEqual(facts_dict['lsb']['codename'], 'stonehenge')