Repository: ansible/ansible

Issue:
cs_sshkeypair: unhandled case of already registered ssh keys
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
cloudstack/cs_sshkeypair

##### ANSIBLE VERSION
```
ansible-playbook 2.2.1.0
  config file =
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
Nothing specific

##### OS / ENVIRONMENT
Using ansible in `alpine:3.5.0` docker image, also did `pip install sshpubkeys cs uritools`

##### SUMMARY
In a specific (east-2) region of IDCF Japan, API is returning 431 response (full detailed below).

##### STEPS TO REPRODUCE
Failing step config is below, and quite simple.

```yaml
- name: "SSH Key Registration"
  cs_sshkeypair:
    name: "{{ inventory_hostname }}"
    public_key: "{{ lookup('file', ssh_public_key_file) }}"
```

##### EXPECTED RESULTS
if I'm not wrong, if the API result says "the key is already registered", that means the step should not fail, and should marked as `not changed` or `ok` (some something else that does not fail?).

##### ACTUAL RESULTS
```
fatal: [***some-hostname-here****]: FAILED! => {"changed": false, "failed": true, "msg": "CloudStackException: ('HTTP 431 response from CloudStack', <Response [431]>, {u'errorcode': 431, u'uuidList': [], u'cserrorcode': 4350, u'errortext': u\"A key pair with key 'ssh-rsa AAAA...some ssh public key here ....c' already exists for this account.\"})"}
```



Discussion:
cc @resmo
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@furkanmustafa Hi, could you tell the version of cloudstack involved? You could either find this in the standard UI "about" or by the API http://cloudstack.apache.org/api/apidocs-4.9/apis/listCapabilities.html

needs_info



Resolving patch:
diff --git a/lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py b/lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py
--- a/lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py
+++ b/lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py
@@ -109,102 +109,126 @@
   description: Private key of generated SSH keypair.
   returned: changed
   type: string
-  sample: "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQCkeFYjI+4k8bWfIRMzp4pCzhlopNydbbwRu824P5ilD4ATWMUG\nvEtuCQ2Mp5k5Bma30CdYHgh2/SbxC5RxXSUKTUJtTKpoJUy8PAhb1nn9dnfkC2oU\naRVi9NRUgypTIZxMpgooHOxvAzWxbZCyh1W+91Ld3FNaGxTLqTgeevY84wIDAQAB\nAoGAcwQwgLyUwsNB1vmjWwE0QEmvHS4FlhZyahhi4hGfZvbzAxSWHIK7YUT1c8KU\n9XsThEIN8aJ3GvcoL3OAqNKRnoNb14neejVHkYRadhxqc0GVN6AUIyCqoEMpvhFI\nQrinM572ORzv5ffRjCTbvZcYlW+sqFKNo5e8pYIB8TigpFECQQDu7bg9vkvg8xPs\nkP1K+EH0vsR6vUfy+m3euXjnbJtiP7RoTkZk0JQMOmexgy1qQhISWT0e451wd62v\nJ7M0trl5AkEAsDivJnMIlCCCypwPN4tdNUYpe9dtidR1zLmb3SA7wXk5xMUgLZI9\ncWPjBCMt0KKShdDhQ+hjXAyKQLF7iAPuOwJABjdHCMwvmy2XwhrPjCjDRoPEBtFv\n0sFzJE08+QBZVogDwIbwy+SlRWArnHGmN9J6N+H8dhZD3U4vxZPJ1MBAOQJBAJxO\nCv1dt1Q76gbwmYa49LnWO+F+2cgRTVODpr5iYt5fOmBQQRRqzFkRMkFvOqn+KVzM\nQ6LKM6dn8BEl295vLhUCQQCVDWzoSk3GjL3sOjfAUTyAj8VAXM69llaptxWWySPM\nE9pA+8rYmHfohYFx7FD5/KWCO+sfmxTNB48X0uwyE8tO\n-----END RSA PRIVATE KEY-----\n"
+  sample: "-----BEGIN RSA PRIVATE KEY-----\nMII...8tO\n-----END RSA PRIVATE KEY-----\n"
 '''
 
 try:
     import sshpubkeys
-    has_lib_sshpubkeys = True
+    HAS_LIB_SSHPUBKEYS = True
 except ImportError:
-    has_lib_sshpubkeys = False
+    HAS_LIB_SSHPUBKEYS = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.cloudstack import (
+    AnsibleCloudStack,
+    CloudStackException,
+    cs_required_together,
+    cs_argument_spec
+)
 
-from ansible.module_utils.cloudstack import *
 
 class AnsibleCloudStackSshKey(AnsibleCloudStack):
 
     def __init__(self, module):
         super(AnsibleCloudStackSshKey, self).__init__(module)
         self.returns = {
-            'privatekey':   'private_key',
-            'fingerprint':  'fingerprint',
+            'privatekey': 'private_key',
+            'fingerprint': 'fingerprint',
         }
         self.ssh_key = None
 
-
     def register_ssh_key(self, public_key):
         ssh_key = self.get_ssh_key()
-        args                = {}
-        args['domainid']    = self.get_domain('id')
-        args['account']     = self.get_account('name')
-        args['projectid']   = self.get_project('id')
-        args['name']        = self.module.params.get('name')
+        args = self._get_common_args()
+        name = self.module.params.get('name')
 
         res = None
         if not ssh_key:
             self.result['changed'] = True
             args['publickey'] = public_key
             if not self.module.check_mode:
+                args['name'] = name
                 res = self.cs.registerSSHKeyPair(**args)
-
         else:
             fingerprint = self._get_ssh_fingerprint(public_key)
             if ssh_key['fingerprint'] != fingerprint:
                 self.result['changed'] = True
                 if not self.module.check_mode:
+                    # delete the ssh key with matching name but wrong fingerprint
+                    args['name'] = name
+                    self.cs.deleteSSHKeyPair(**args)
+
+            elif ssh_key['name'].lower() != name.lower():
+                self.result['changed'] = True
+                if not self.module.check_mode:
+                    # delete the ssh key with matching fingerprint but wrong name
+                    args['name'] = ssh_key['name']
                     self.cs.deleteSSHKeyPair(**args)
-                    args['publickey'] = public_key
-                    res = self.cs.registerSSHKeyPair(**args)
+                    # First match for key retrievment will be the fingerprint.
+                    # We need to make another lookup if there is a key with identical name.
+                    self.ssh_key = None
+                    ssh_key = self.get_ssh_key()
+                    if ssh_key['fingerprint'] != fingerprint:
+                        args['name'] = name
+                        self.cs.deleteSSHKeyPair(**args)
+
+            if not self.module.check_mode and self.result['changed']:
+                args['publickey'] = public_key
+                args['name'] = name
+                res = self.cs.registerSSHKeyPair(**args)
 
         if res and 'keypair' in res:
             ssh_key = res['keypair']
 
         return ssh_key
 
-
     def create_ssh_key(self):
         ssh_key = self.get_ssh_key()
         if not ssh_key:
             self.result['changed'] = True
-            args                = {}
-            args['domainid']    = self.get_domain('id')
-            args['account']     = self.get_account('name')
-            args['projectid']   = self.get_project('id')
-            args['name']        = self.module.params.get('name')
+            args = self._get_common_args()
+            args['name'] = self.module.params.get('name')
             if not self.module.check_mode:
                 res = self.cs.createSSHKeyPair(**args)
                 ssh_key = res['keypair']
         return ssh_key
 
-
-    def remove_ssh_key(self):
+    def remove_ssh_key(self, name=None):
         ssh_key = self.get_ssh_key()
         if ssh_key:
             self.result['changed'] = True
-            args                = {}
-            args['domainid']    = self.get_domain('id')
-            args['account']     = self.get_account('name')
-            args['projectid']   = self.get_project('id')
-            args['name']        = self.module.params.get('name')
+            args = self._get_common_args()
+            args['name'] = name or self.module.params.get('name')
             if not self.module.check_mode:
-                res = self.cs.deleteSSHKeyPair(**args)
+                self.cs.deleteSSHKeyPair(**args)
         return ssh_key
 
+    def _get_common_args(self):
+        return {
+            'domainid': self.get_domain('id'),
+            'account': self.get_account('name'),
+            'projectid': self.get_project('id')
+        }
 
     def get_ssh_key(self):
         if not self.ssh_key:
-            args                = {}
-            args['domainid']    = self.get_domain('id')
-            args['account']     = self.get_account('name')
-            args['projectid']   = self.get_project('id')
-            args['name']        = self.module.params.get('name')
-
-            ssh_keys = self.cs.listSSHKeyPairs(**args)
-            if ssh_keys and 'sshkeypair' in ssh_keys:
-                self.ssh_key = ssh_keys['sshkeypair'][0]
+            public_key = self.module.params.get('public_key')
+            if public_key:
+                # Query by fingerprint of the public key
+                args_fingerprint = self._get_common_args()
+                args_fingerprint['fingerprint'] = self._get_ssh_fingerprint(public_key)
+                ssh_keys = self.cs.listSSHKeyPairs(**args_fingerprint)
+                if ssh_keys and 'sshkeypair' in ssh_keys:
+                    self.ssh_key = ssh_keys['sshkeypair'][0]
+            # When key has not been found by fingerprint, use the name
+            if not self.ssh_key:
+                args_name = self._get_common_args()
+                args_name['name'] = self.module.params.get('name')
+                ssh_keys = self.cs.listSSHKeyPairs(**args_name)
+                if ssh_keys and 'sshkeypair' in ssh_keys:
+                    self.ssh_key = ssh_keys['sshkeypair'][0]
         return self.ssh_key
 
-
-
     def _get_ssh_fingerprint(self, public_key):
         key = sshpubkeys.SSHKey(public_key)
         return key.hash()
@@ -213,12 +237,12 @@ def _get_ssh_fingerprint(self, public_key):
 def main():
     argument_spec = cs_argument_spec()
     argument_spec.update(dict(
-        name = dict(required=True),
-        public_key = dict(default=None),
-        domain = dict(default=None),
-        account = dict(default=None),
-        project = dict(default=None),
-        state = dict(choices=['present', 'absent'], default='present'),
+        name=dict(required=True),
+        public_key=dict(),
+        domain=dict(),
+        account=dict(),
+        project=dict(),
+        state=dict(choices=['present', 'absent'], default='present'),
     ))
 
     module = AnsibleModule(
@@ -227,7 +251,7 @@ def main():
         supports_check_mode=True
     )
 
-    if not has_lib_sshpubkeys:
+    if not HAS_LIB_SSHPUBKEYS:
         module.fail_json(msg="python library sshpubkeys required: pip install sshpubkeys")
 
     try:
@@ -249,7 +273,6 @@ def main():
 
     module.exit_json(**result)
 
-# import module snippets
-from ansible.module_utils.basic import *
+
 if __name__ == '__main__':
     main()


Test patch:
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -78,7 +78,6 @@ lib/ansible/modules/cloud/cloudstack/cs_host.py
 lib/ansible/modules/cloud/cloudstack/cs_instance.py
 lib/ansible/modules/cloud/cloudstack/cs_iso.py
 lib/ansible/modules/cloud/cloudstack/cs_portforward.py
-lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py
 lib/ansible/modules/cloud/digital_ocean/digital_ocean.py
 lib/ansible/modules/cloud/google/gc_storage.py
 lib/ansible/modules/cloud/google/gce_tag.py