From 60467738edba9ea50578da38092ef7d6063495a4 Mon Sep 17 00:00:00 2001
From: Rene Moser <mail@renemoser.net>
Date: Thu, 26 Mar 2015 19:52:36 +0100
Subject: [PATCH 1/6] cloudstack: add new module cloudstack_sshkey

This module depends on ansible.module_utils.cloudstack.
---
 cloud/cloudstack/__init__.py          |   0
 cloud/cloudstack/cloudstack_sshkey.py | 210 ++++++++++++++++++++++++++
 2 files changed, 210 insertions(+)
 create mode 100644 cloud/cloudstack/__init__.py
 create mode 100644 cloud/cloudstack/cloudstack_sshkey.py

diff --git a/cloud/cloudstack/__init__.py b/cloud/cloudstack/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py
new file mode 100644
index 00000000000..414ded6c971
--- /dev/null
+++ b/cloud/cloudstack/cloudstack_sshkey.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2015, René Moser <mail@renemoser.net>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: cloudstack_sshkey
+short_description: Manages SSH keys on Apache CloudStack based clouds.
+description:
+  - If no public key is provided, a new ssh private/public key pair will be
+    created and the private key will be returned.
+version_added: '2.0'
+author: René Moser
+options:
+  name:
+    description:
+      - Name of public key.
+    required: true
+    default: null
+    aliases: []
+  project:
+    description:
+      - Name of the project the public key to be registered in.
+    required: false
+    default: null
+    aliases: []
+  state:
+    description:
+      - State of the public key.
+    required: false
+    default: 'present'
+    choices: [ 'present', 'absent' ]
+    aliases: []
+  public_key:
+    description:
+      - String of the public key.
+    required: false
+    default: null
+    aliases: []
+'''
+
+EXAMPLES = '''
+---
+# create a new private / public key pair:
+- local_action: cloudstack_sshkey name=linus@example.com
+  register: key
+- debug: msg='private key is {{ key.private_key }}'
+
+# remove a public key by its name:
+- local_action: cloudstack_sshkey name=linus@example.com state=absent
+
+# register your existing local public key:
+- local_action: cloudstack_sshkey name=linus@example.com public_key='{{ lookup('file', '~/.ssh/id_rsa.pub') }}'
+'''
+
+RETURN = '''
+---
+name:
+  description: Name of the SSH public key.
+  returned: success
+  type: string
+  sample: linus@example.com
+fingerprint:
+  description: Fingerprint of the SSH public key.
+  returned: success
+  type: string
+  sample: "86:5e:a3:e8:bd:95:7b:07:7c:c2:5c:f7:ad:8b:09:28"
+private_key:
+  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"
+'''
+
+
+try:
+    from cs import CloudStack, CloudStackException, read_config
+    has_lib_cs = True
+except ImportError:
+    has_lib_cs = False
+
+from ansible.module_utils.cloudstack import *
+
+class AnsibleCloudStackSshKey(AnsibleCloudStack):
+
+    def __init__(self, module):
+        AnsibleCloudStack.__init__(self, module)
+        self.result = {
+            'changed': False,
+        }
+        self.ssh_key = None
+
+
+    def register_ssh_key(self):
+        ssh_key = self.get_ssh_key()
+        if not ssh_key:
+            self.result['changed'] = True
+            args = {}
+            args['projectid'] = self.get_project_id()
+            args['name'] = self.module.params.get('name')
+            args['publickey'] = self.module.params.get('public_key')
+            if not self.module.check_mode:
+                ssh_key = self.cs.registerSSHKeyPair(**args)
+        return ssh_key
+
+
+    def create_ssh_key(self):
+        ssh_key = self.get_ssh_key()
+        if not ssh_key:
+            self.result['changed'] = True
+            args = {}
+            args['projectid'] = self.get_project_id()
+            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):
+        ssh_key = self.get_ssh_key()
+        if ssh_key:
+            self.result['changed'] = True
+            args = {}
+            args['name'] = self.module.params.get('name')
+            if not self.module.check_mode:
+                res = self.cs.deleteSSHKeyPair(**args)
+        return ssh_key
+
+
+    def get_ssh_key(self):
+        if not self.ssh_key:
+            args = {}
+            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]
+        return self.ssh_key
+
+
+    def get_result(self, ssh_key):
+        if ssh_key:
+            if 'fingerprint' in ssh_key:
+                self.result['fingerprint'] = ssh_key['fingerprint']
+
+            if 'name' in ssh_key:
+                self.result['name'] = ssh_key['name']
+
+            if 'privatekey' in ssh_key:
+                self.result['private_key'] = ssh_key['privatekey']
+        return self.result
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec = dict(
+            name = dict(required=True, default=None),
+            public_key = dict(default=None),
+            project = dict(default=None),
+            state = dict(choices=['present', 'absent'], default='present'),
+            api_key = dict(default=None),
+            api_secret = dict(default=None),
+            api_url = dict(default=None),
+            api_http_method = dict(default='get'),
+        ),
+        supports_check_mode=True
+    )
+
+    if not has_lib_cs:
+        module.fail_json(msg="python library cs required: pip install cs")
+
+    try:
+        acs_sshkey = AnsibleCloudStackSshKey(module)
+        state = module.params.get('state')
+        if state in ['absent']:
+            ssh_key = acs_sshkey.remove_ssh_key()
+        else:
+            if module.params.get('public_key'):
+                ssh_key = acs_sshkey.register_ssh_key()
+            else:
+                ssh_key = acs_sshkey.create_ssh_key()
+
+        result = acs_sshkey.get_result(ssh_key)
+
+    except CloudStackException, e:
+        module.fail_json(msg='CloudStackException: %s' % str(e))
+
+    module.exit_json(**result)
+
+# import module snippets
+from ansible.module_utils.basic import *
+main()

From 82e25447adeab1c7d464e64119b594c5386506f8 Mon Sep 17 00:00:00 2001
From: Rene Moser <mail@renemoser.net>
Date: Sat, 28 Mar 2015 10:58:02 +0100
Subject: [PATCH 2/6] cloudstack_ssh: fix missing projectid if state=absent

---
 cloud/cloudstack/cloudstack_sshkey.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py
index 414ded6c971..97d6a222f09 100644
--- a/cloud/cloudstack/cloudstack_sshkey.py
+++ b/cloud/cloudstack/cloudstack_sshkey.py
@@ -139,6 +139,7 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack):
             self.result['changed'] = True
             args = {}
             args['name'] = self.module.params.get('name')
+            args['projectid'] = self.get_project_id()
             if not self.module.check_mode:
                 res = self.cs.deleteSSHKeyPair(**args)
         return ssh_key

From bf32de8d8f705692662108759c5d2077baf6ddba Mon Sep 17 00:00:00 2001
From: Rene Moser <mail@renemoser.net>
Date: Sat, 28 Mar 2015 22:07:39 +0100
Subject: [PATCH 3/6] cloudstack_ssh: register_ssh_key() set public_key as
 param

---
 cloud/cloudstack/cloudstack_sshkey.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py
index 97d6a222f09..589e2783913 100644
--- a/cloud/cloudstack/cloudstack_sshkey.py
+++ b/cloud/cloudstack/cloudstack_sshkey.py
@@ -107,14 +107,14 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack):
         self.ssh_key = None
 
 
-    def register_ssh_key(self):
+    def register_ssh_key(self, public_key):
         ssh_key = self.get_ssh_key()
         if not ssh_key:
             self.result['changed'] = True
             args = {}
             args['projectid'] = self.get_project_id()
             args['name'] = self.module.params.get('name')
-            args['publickey'] = self.module.params.get('public_key')
+            args['publickey'] = public_key
             if not self.module.check_mode:
                 ssh_key = self.cs.registerSSHKeyPair(**args)
         return ssh_key
@@ -194,8 +194,9 @@ def main():
         if state in ['absent']:
             ssh_key = acs_sshkey.remove_ssh_key()
         else:
-            if module.params.get('public_key'):
-                ssh_key = acs_sshkey.register_ssh_key()
+            public_key = module.params.get('public_key')
+            if public_key:
+                ssh_key = acs_sshkey.register_ssh_key(public_key)
             else:
                 ssh_key = acs_sshkey.create_ssh_key()
 

From a24d691419c41f0e64b95d3560237f3829340917 Mon Sep 17 00:00:00 2001
From: Rene Moser <mail@renemoser.net>
Date: Sat, 28 Mar 2015 22:09:21 +0100
Subject: [PATCH 4/6] cloudstack_ssh: update description

---
 cloud/cloudstack/cloudstack_sshkey.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py
index 589e2783913..7e803be02e5 100644
--- a/cloud/cloudstack/cloudstack_sshkey.py
+++ b/cloud/cloudstack/cloudstack_sshkey.py
@@ -23,8 +23,8 @@ DOCUMENTATION = '''
 module: cloudstack_sshkey
 short_description: Manages SSH keys on Apache CloudStack based clouds.
 description:
-  - If no public key is provided, a new ssh private/public key pair will be
-    created and the private key will be returned.
+  - If no key was found and no public key was provided and a new SSH
+    private/public key pair will be created and the private key will be returned.
 version_added: '2.0'
 author: René Moser
 options:

From c03baa7ec64a6ae6acbd4176c24d1e757b88c42b Mon Sep 17 00:00:00 2001
From: Rene Moser <mail@renemoser.net>
Date: Sat, 28 Mar 2015 22:12:19 +0100
Subject: [PATCH 5/6] cloudstack_ssh: replace ssh public key if fingerprints do
 not match

---
 cloud/cloudstack/cloudstack_sshkey.py | 38 ++++++++++++++++++++++++---
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py
index 7e803be02e5..4f63a9d566b 100644
--- a/cloud/cloudstack/cloudstack_sshkey.py
+++ b/cloud/cloudstack/cloudstack_sshkey.py
@@ -95,6 +95,12 @@ try:
 except ImportError:
     has_lib_cs = False
 
+try:
+    import sshpubkeys
+    has_lib_sshpubkeys = True
+except ImportError:
+    has_lib_sshpubkeys = False
+
 from ansible.module_utils.cloudstack import *
 
 class AnsibleCloudStackSshKey(AnsibleCloudStack):
@@ -109,14 +115,30 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack):
 
     def register_ssh_key(self, public_key):
         ssh_key = self.get_ssh_key()
+  
+        args = {}
+        args['projectid'] = self.get_project_id()
+        args['name'] = self.module.params.get('name')
+
+        res = None
         if not ssh_key:
             self.result['changed'] = True
-            args = {}
-            args['projectid'] = self.get_project_id()
-            args['name'] = self.module.params.get('name')
             args['publickey'] = public_key
             if not self.module.check_mode:
-                ssh_key = self.cs.registerSSHKeyPair(**args)
+                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:
+                    self.cs.deleteSSHKeyPair(**args)
+                    args['publickey'] = public_key
+                    res = self.cs.registerSSHKeyPair(**args)
+
+        if res and 'keypair' in res:
+            ssh_key = res['keypair']
+
         return ssh_key
 
 
@@ -170,6 +192,11 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack):
         return self.result
 
 
+    def _get_ssh_fingerprint(self, public_key):
+        key = sshpubkeys.SSHKey(public_key)
+        return key.hash()
+
+
 def main():
     module = AnsibleModule(
         argument_spec = dict(
@@ -188,6 +215,9 @@ def main():
     if not has_lib_cs:
         module.fail_json(msg="python library cs required: pip install cs")
 
+    if not has_lib_sshpubkeys:
+        module.fail_json(msg="python library sshpubkeys required: pip install sshpubkeys")
+
     try:
         acs_sshkey = AnsibleCloudStackSshKey(module)
         state = module.params.get('state')

From 392feaea63f845f53c02f70ea4a7dd3e723f3ed9 Mon Sep 17 00:00:00 2001
From: Rene Moser <mail@renemoser.net>
Date: Tue, 31 Mar 2015 11:55:39 +0200
Subject: [PATCH 6/6] cloudstack_sshkey: cleanup docs

---
 cloud/cloudstack/cloudstack_sshkey.py | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/cloud/cloudstack/cloudstack_sshkey.py b/cloud/cloudstack/cloudstack_sshkey.py
index 4f63a9d566b..657e367fefe 100644
--- a/cloud/cloudstack/cloudstack_sshkey.py
+++ b/cloud/cloudstack/cloudstack_sshkey.py
@@ -32,27 +32,22 @@ options:
     description:
       - Name of public key.
     required: true
-    default: null
-    aliases: []
   project:
     description:
       - Name of the project the public key to be registered in.
     required: false
     default: null
-    aliases: []
   state:
     description:
       - State of the public key.
     required: false
     default: 'present'
     choices: [ 'present', 'absent' ]
-    aliases: []
   public_key:
     description:
       - String of the public key.
     required: false
     default: null
-    aliases: []
 '''
 
 EXAMPLES = '''