Repository: ansible-collections/community.aws

Issue:
aws_ssm_parameter_store update fails if no description element was defined before
### Summary

`aws_ssm_parameter_store` update fails if no description element was defined before.

If an SSM parameter has an empty description already in SSM, then the AWS API (I'm guessing `aws ssm describe-parameters`) doesn't include the `description` element in the response JSON at all for such item, causing issues in `community.aws. aws_ssm_parameter_store` handling of such reply (Description element no defined).

### Issue Type

Bug Report

### Component Name

ssm_parameter

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.12.6]
  config file = None
  configured module search path = ['/home/circleci/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/circleci/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/circleci/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/circleci/.local/bin/ansible
  python version = 3.10.4 (main, Apr  2 2022, 09:04:19) [GCC 11.2.0]
  jinja version = 3.1.2
  libyaml = True
```


### Collection Versions

```console (paste below)
$ ansible-galaxy collection list
# /home/circleci/.local/lib/python3.10/site-packages/ansible_collections
Collection                    Version
----------------------------- -------
amazon.aws                    2.3.0  
ansible.netcommon             2.6.1  
ansible.posix                 1.4.0  
ansible.utils                 2.6.1  
ansible.windows               1.10.0 
arista.eos                    3.1.0  
awx.awx                       19.4.0 
azure.azcollection            1.13.0 
check_point.mgmt              2.3.0  
chocolatey.chocolatey         1.2.0  
cisco.aci                     2.2.0  
cisco.asa                     2.1.0  
cisco.dnac                    6.4.0  
cisco.intersight              1.0.19 
cisco.ios                     2.8.1  
cisco.iosxr                   2.9.0  
cisco.ise                     1.2.1  
cisco.meraki                  2.6.2  
cisco.mso                     1.4.0  
cisco.nso                     1.0.3  
cisco.nxos                    2.9.1  
cisco.ucs                     1.8.0  
cloud.common                  2.1.1  
cloudscale_ch.cloud           2.2.2  
community.aws                 2.5.0  
community.azure               1.1.0  
community.ciscosmb            1.0.5  
community.crypto              2.3.2  
community.digitalocean        1.19.0 
community.dns                 2.2.0  
community.docker              2.6.0  
community.fortios             1.0.0  
community.general             4.8.2  
community.google              1.0.0  
community.grafana             1.4.0  
community.hashi_vault         2.5.0  
community.hrobot              1.4.0  
community.kubernetes          2.0.1  
community.kubevirt            1.0.0  
community.libvirt             1.1.0  
community.mongodb             1.4.0  
community.mysql               2.3.8  
community.network             3.3.0  
community.okd                 2.2.0  
community.postgresql          1.7.4  
community.proxysql            1.4.0  
community.rabbitmq            1.2.1  
community.routeros            2.1.0  
community.sap                 1.0.0  
community.sap_libs            1.1.0  
community.skydive             1.0.0  
community.sops                1.2.2  
community.vmware              1.18.0 
community.windows             1.10.0 
community.zabbix              1.7.0  
containers.podman             1.9.3  
cyberark.conjur               1.1.0  
cyberark.pas                  1.0.14 
dellemc.enterprise_sonic      1.1.1  
dellemc.openmanage            4.4.0  
dellemc.os10                  1.1.1  
dellemc.os6                   1.0.7  
dellemc.os9                   1.0.4  
f5networks.f5_modules         1.17.0 
fortinet.fortimanager         2.1.5  
fortinet.fortios              2.1.6  
frr.frr                       1.0.4  
gluster.gluster               1.0.2  
google.cloud                  1.0.2  
hetzner.hcloud                1.6.0  
hpe.nimble                    1.1.4  
ibm.qradar                    1.0.3  
infinidat.infinibox           1.3.3  
infoblox.nios_modules         1.2.2  
inspur.sm                     1.3.0  
junipernetworks.junos         2.10.0 
kubernetes.core               2.3.1  
mellanox.onyx                 1.0.0  
netapp.aws                    21.7.0 
netapp.azure                  21.10.0
netapp.cloudmanager           21.17.0
netapp.elementsw              21.7.0 
netapp.ontap                  21.19.1
netapp.storagegrid            21.10.0
netapp.um_info                21.8.0 
netapp_eseries.santricity     1.3.0  
netbox.netbox                 3.7.1  
ngine_io.cloudstack           2.2.4  
ngine_io.exoscale             1.0.0  
ngine_io.vultr                1.1.1  
openstack.cloud               1.8.0  
openvswitch.openvswitch       2.1.0  
ovirt.ovirt                   1.6.6  
purestorage.flasharray        1.13.0 
purestorage.flashblade        1.9.0  
sensu.sensu_go                1.13.1 
servicenow.servicenow         1.0.6  
splunk.es                     1.0.2  
t_systems_mms.icinga_director 1.29.0 
theforeman.foreman            2.2.0  
vmware.vmware_rest            2.1.5  
vyos.vyos                     2.8.0  
wti.remote                    1.0.3
```


### AWS SDK versions

```console (paste below)
$ pip show boto boto3 botocore
WARNING: Package(s) not found: boto
Name: boto3
Version: 1.24.73
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email: 
License: Apache License 2.0
Location: /home/circleci/.local/lib/python3.10/site-packages
Requires: botocore, jmespath, s3transfer
Required-by: 
---
Name: botocore
Version: 1.27.73
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email: 
License: Apache License 2.0
Location: /home/circleci/.local/lib/python3.10/site-packages
Requires: jmespath, python-dateutil, urllib3
Required-by: boto3, s3transfer
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```


### OS / Environment

Ubuntu 22.04

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
    - name: "SSM"
      ssm_parameter:
        name: "mytest"
        string_type: "String"
        value: "myvalue"
        state: "present"
        overwrite_value: "changed"
    - name: "SSM"
      ssm_parameter:
        name: "mytest"
        string_type: "String"
        value: "myvalue"
        state: "present"
        description: "this_should_fail"
        overwrite_value: "changed"
```


### Expected Results

Second command successfully adding the description element.

### Actual Results

Second command fails due to description being empty in SSM.

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Files identified in the description:
* [`plugins/modules/ssm_parameter.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/ssm_parameter.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @116davinder @jillr @markuman @mikedlr @nathanwebsterdotme @ozbillwang @s-hertel @tremble
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Potential solution is probably to consider `Description` an empty string if the Description element doesn't exist in the response: https://github.com/ansible-collections/community.aws/blob/3043bca5d3544a40b78f22e0ece084dde8324530/plugins/modules/ssm_parameter.py#L325

```
if 'Description' not in existing_parameter['Parameters'][0]:
    existing_parameter['Parameters'][0]['Description'] = ''
```
@cergfix, It would be really nice if you can run above reproduce steps with `-vvvv` and paste the output to pin-point error location.
```paste below
The full traceback is:
Traceback (most recent call last):
  ...
  File "/tmp/ansible_community.aws.ssm_parameter_payload_fmtkbrh6/ansible_community.aws.ssm_parameter_payload.zip/ansible_collections/community/aws/plugins/modules/ssm_parameter.py", line 483, in <module>                                                                                        
  File "/tmp/ansible_community.aws.ssm_parameter_payload_fmtkbrh6/ansible_community.aws.ssm_parameter_payload.zip/ansible_collections/community/aws/plugins/modules/ssm_parameter.py", line 464, in main                                                                                            
  File "/tmp/ansible_community.aws.ssm_parameter_payload_fmtkbrh6/ansible_community.aws.ssm_parameter_payload.zip/ansible_collections/community/aws/plugins/modules/ssm_parameter.py", line 386, in create_update_parameter                                                                         
KeyError: 'Description'

```

I'm opening a PR now.

Resolving patch:
diff --git a/plugins/modules/ssm_parameter.py b/plugins/modules/ssm_parameter.py
--- a/plugins/modules/ssm_parameter.py
+++ b/plugins/modules/ssm_parameter.py
@@ -383,7 +383,7 @@ def create_update_parameter(client, module):
                 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                     module.fail_json_aws(e, msg="getting description value")
 
-                if describe_existing_parameter['Description'] != args['Description']:
+                if describe_existing_parameter.get('Description') != args['Description']:
                     (changed, response) = update_parameter(client, module, **args)
         if changed:
             _wait_updated(client, module, module.params.get('name'), original_version)


Test patch:
diff --git a/tests/integration/targets/ssm_parameter/tasks/main.yml b/tests/integration/targets/ssm_parameter/tasks/main.yml
--- a/tests/integration/targets/ssm_parameter/tasks/main.yml
+++ b/tests/integration/targets/ssm_parameter/tasks/main.yml
@@ -415,6 +415,30 @@
       that:
       - result is not changed
 
+  - name: Create key/value pair in aws parameter store with no description
+    aws_ssm_parameter_store:
+      name: '{{ simple_name }}'
+      value: '{{ simple_value }}'
+    register: result
+
+  - assert:
+      that:
+      - result is changed
+      - '"description" not in result.parameter_metadata'
+
+  - name: Add a description
+    aws_ssm_parameter_store:
+      name: '{{ simple_name }}'
+      value: '{{ simple_value }}'
+      description: '{{ simple_description }}'
+    register: result
+
+  - assert:
+      that:
+        - result is changed
+        - '"description" in result.parameter_metadata'
+        - result.parameter_metadata.description == simple_description
+
   always:
   # ============================================================
   - name: Delete remaining key/value pairs in aws parameter store