From 3ed93cfdba1f2e273d43af8b15cc33825c479f59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Toma=C5=BE=20Muraus?= <kami@k5-storitve.net>
Date: Tue, 26 Oct 2010 17:03:54 +0200
Subject: [PATCH 1/4] Remove trailing whitespace.

---
 libcloud/drivers/elastichosts.py |   16 +++++++---------
 1 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/libcloud/drivers/elastichosts.py b/libcloud/drivers/elastichosts.py
index 259469e..3a0287a 100644
--- a/libcloud/drivers/elastichosts.py
+++ b/libcloud/drivers/elastichosts.py
@@ -178,22 +178,22 @@ class ElasticHostsResponse(Response):
             raise InvalidCredsError()
 
         return self.status >= 200 and self.status <= 299
-    
+
     def parse_body(self):
         if not self.body:
             return self.body
-        
+
         try:
             data = json.loads(self.body)
         except:
             raise MalformedResponseError("Failed to parse JSON", body=self.body, driver=ElasticHostsBaseNodeDriver)
 
         return data
-    
+
     def parse_error(self):
         error_header = self.headers.get('x-elastic-error', '')
         return 'X-Elastic-Error: %s (%s)' % (error_header, self.body.strip())
-    
+
 class ElasticHostsNodeSize(NodeSize):
     def __init__(self, id, name, cpu, ram, disk, bandwidth, price, driver):
         self.id = id
@@ -215,7 +215,7 @@ class ElasticHostsBaseConnection(ConnectionUserAndKey):
     """
     Base connection class for the ElasticHosts driver
     """
-    
+
     host = API_ENDPOINTS[DEFAULT_ENDPOINT]['host']
     responseCls = ElasticHostsResponse
 
@@ -286,10 +286,10 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
 
         @keyword    name: String with a name for this new node (required)
         @type       name: C{string}
-        
+
         @keyword    smp: Number of virtual processors or None to calculate based on the cpu speed
         @type       smp: C{int}
-        
+
         @keyword    nic_model: e1000, rtl8139 or virtio (is not specified, e1000 is used)
         @type       nic_model: C{string}
 
@@ -451,5 +451,3 @@ class ElasticHostsUS1NodeDriver(ElasticHostsBaseNodeDriver):
     ElasticHosts node driver for the San Antonio Peer 1 end-point
     """
     connectionCls = ElasticHostsUS1Connection
-
-
-- 
1.7.1


From e3eb197d7cdc0cb371f84c4a79b7297410e09836 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Toma=C5=BE=20Muraus?= <kami@k5-storitve.net>
Date: Tue, 26 Oct 2010 17:07:13 +0200
Subject: [PATCH 2/4] Update standard drives list.

---
 libcloud/drivers/elastichosts.py |   39 ++++++++++++++++++++-----------------
 1 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/libcloud/drivers/elastichosts.py b/libcloud/drivers/elastichosts.py
index 3a0287a..37f05e1 100644
--- a/libcloud/drivers/elastichosts.py
+++ b/libcloud/drivers/elastichosts.py
@@ -105,50 +105,53 @@ INSTANCE_TYPES = {
 
 # Retrieved from http://www.elastichosts.com/cloud-hosting/api
 STANDARD_DRIVES = {
-    'cf82519b-01a0-4247-aff5-a2dadf4401ad': {
+    '980cf63c-f21e-4382-997b-6541d5809629': {
         'uuid': 'cf82519b-01a0-4247-aff5-a2dadf4401ad',
-        'description': 'Debian Linux 4.0: Base system without X',
+        'description': 'Debian Linux 5.0',
         'size_gunzipped': '1GB',
+        'supports_deployment': True,
     },
-    'e6111e4c-67af-4438-b1bc-189747d5a8e5': {
-        'uuid': 'e6111e4c-67af-4438-b1bc-189747d5a8e5',
-        'description': 'Debian Linux 5.0: Base system without X',
+    'aee5589a-88c3-43ef-bb0a-9cab6e64192d': {
+        'uuid': 'aee5589a-88c3-43ef-bb0a-9cab6e64192d',
+        'description': 'Ubuntu Linux 10.04',
         'size_gunzipped': '1GB',
+        'supports_deployment': True,
     },
-    'bf1d943e-2a55-46bb-a8c7-6099e44a3dde': {
+    '38df0986-4d85-4b76-b502-3878ffc80161': {
         'uuid': 'bf1d943e-2a55-46bb-a8c7-6099e44a3dde',
-        'description': 'Ubuntu Linux 8.10: Base system with X',
-        'size_gunzipped': '3GB',
-    },
-    '757586d5-f1e9-4d9c-b215-5a391c9a24bf': {
-        'uuid': '757586d5-f1e9-4d9c-b215-5a391c9a24bf',
-        'description': 'Ubuntu Linux 9.04: Base system with X',
+        'description': 'CentOS Linux 5.5',
         'size_gunzipped': '3GB',
+        'supports_deployment': True,
     },
     'b9d0eb72-d273-43f1-98e3-0d4b87d372c0': {
         'uuid': 'b9d0eb72-d273-43f1-98e3-0d4b87d372c0',
         'description': 'Windows Web Server 2008',
         'size_gunzipped': '13GB',
+        'supports_deployment': False,
     },
-    '30824e97-05a4-410c-946e-2ba5a92b07cb': {
-        'uuid': '30824e97-05a4-410c-946e-2ba5a92b07cb',
+    'b405b598-4ae4-4ba8-8a2b-a9487d693f34': {
+        'uuid': 'b405b598-4ae4-4ba8-8a2b-a9487d693f34',
         'description': 'Windows Web Server 2008 R2',
         'size_gunzipped': '13GB',
+        'supports_deployment': False,
     },
-    '9ecf810e-6ad1-40ef-b360-d606f0444671': {
-        'uuid': '9ecf810e-6ad1-40ef-b360-d606f0444671',
+    '9397d327-3bf6-46a2-abf6-69553dbb6927': {
+        'uuid': '9397d327-3bf6-46a2-abf6-69553dbb6927',
         'description': 'Windows Web Server 2008 R2 + SQL Server',
         'size_gunzipped': '13GB',
+        'supports_deployment': False,
     },
     '10a88d1c-6575-46e3-8d2c-7744065ea530': {
         'uuid': '10a88d1c-6575-46e3-8d2c-7744065ea530',
         'description': 'Windows Server 2008 Standard R2',
         'size_gunzipped': '13GB',
+        'supports_deployment': False,
     },
-    '2567f25c-8fb8-45c7-95fc-bfe3c3d84c47': {
-        'uuid': '2567f25c-8fb8-45c7-95fc-bfe3c3d84c47',
+    '662c5b3f-9828-4aa2-a866-7cfa53798cdf': {
+        'uuid': '662c5b3f-9828-4aa2-a866-7cfa53798cdf',
         'description': 'Windows Server 2008 Standard R2 + SQL Server',
         'size_gunzipped': '13GB',
+        'supports_deployment': False,
     },
 }
 
-- 
1.7.1


From c99f9e59f71c8faa9674a5aa5db97e88ad9543a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Toma=C5=BE=20Muraus?= <kami@k5-storitve.net>
Date: Tue, 26 Oct 2010 20:20:58 +0200
Subject: [PATCH 3/4] Allow user to specify ssh port and username when deploying a node.

---
 libcloud/base.py |   16 +++++++++++++---
 1 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/libcloud/base.py b/libcloud/base.py
index f3235c4..57fb4c7 100644
--- a/libcloud/base.py
+++ b/libcloud/base.py
@@ -631,13 +631,20 @@ class NodeDriver(object):
         or returning a generated password.
 
         This function may raise a L{DeplyomentException}, if a create_node
-        call was successful, but there is a later error (like SSH failing or 
+        call was successful, but there is a later error (like SSH failing or
         timing out).  This exception includes a Node object which you may want
         to destroy if incomplete deployments are not desirable.
 
         @keyword    deploy: Deployment to run once machine is online and availble to SSH.
         @type       deploy: L{Deployment}
 
+        @keyword    ssh_username: Optional name of the account which is used when connecting to
+                                  SSH server (default is root)
+        @type       ssh_username: C{str}
+
+        @keyword    ssh_port: Optional SSH server port (default is 22)
+        @type       ssh_port: C{int}
+
         See L{NodeDriver.create_node} for more keyword args.
         """
         # TODO: support ssh keys
@@ -675,9 +682,12 @@ class NodeDriver(object):
               if node.public_ip is not None and node.public_ip != "" and node.state == NodeState.RUNNING:
                   break
 
+          ssh_username = kwargs.get('ssh_username', 'root')
+          ssh_port = kwargs.get('ssh_port', 22)
+
           client = SSHClient(hostname=node.public_ip[0],
-                              port=22, username='root',
-                              password=password)
+                             port=ssh_port, username=ssh_username,
+                             password=password)
           laste = None
           while time.time() < end:
               laste = None
-- 
1.7.1


From 39b7b0cb23fbd582abda1b0c8945ae265ccd4134 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Toma=C5=BE=20Muraus?= <kami@k5-storitve.net>
Date: Tue, 26 Oct 2010 23:58:11 +0200
Subject: [PATCH 4/4] Add deployment support to the elastichosts driver.

---
 libcloud/drivers/elastichosts.py |   64 +++++++++++++++++++++++++++++++++-----
 1 files changed, 56 insertions(+), 8 deletions(-)

diff --git a/libcloud/drivers/elastichosts.py b/libcloud/drivers/elastichosts.py
index 37f05e1..0844594 100644
--- a/libcloud/drivers/elastichosts.py
+++ b/libcloud/drivers/elastichosts.py
@@ -12,7 +12,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-# Copyright 2009 RedRata Ltd
 """
 ElasticHosts Driver
 """
@@ -22,8 +21,9 @@ import base64
 
 from libcloud.types import Provider, NodeState, InvalidCredsError, MalformedResponseError
 from libcloud.base import ConnectionUserAndKey, Response
-from libcloud.base import NodeDriver, NodeSize, Node
+from libcloud.base import NodeDriver, NodeSize, Node, NodeAuthPassword
 from libcloud.base import NodeImage
+from libcloud.deployment import ScriptDeployment, SSHKeyDeployment, MultiStepDeployment
 
 # JSON is included in the standard library starting with Python 2.6.  For 2.5
 # and 2.4, there's a simplejson egg at: http://pypi.python.org/pypi/simplejson
@@ -238,6 +238,7 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
     type = Provider.ELASTICHOSTS
     name = 'ElasticHosts'
     connectionCls = ElasticHostsBaseConnection
+    features = {"create_node": ["generates_password"]}
 
     def reboot_node(self, node):
         # Reboots the node
@@ -296,14 +297,15 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
         @keyword    nic_model: e1000, rtl8139 or virtio (is not specified, e1000 is used)
         @type       nic_model: C{string}
 
-        @keyword    vnc_password: If not set, VNC access is disabled.
-        @type       vnc_password: C{bool}
+        @keyword    vnc_password: If set, the same password is also used for SSH access with user toor,
+                                  otherwise VNC access is disabled and no SSH login is possible.
+        @type       vnc_password: C{string}
         """
         size = kwargs['size']
         image = kwargs['image']
         smp = kwargs.get('smp', 'auto')
         nic_model = kwargs.get('nic_model', 'e1000')
-        vnc_password = kwargs.get('vnc_password', None)
+        vnc_password, ssh_password = [kwargs.get('vnc_password', None)] * 2
 
         if nic_model not in ['e1000', 'rtl8139', 'virtio']:
             raise ElasticHostsException('Invalid NIC model specified')
@@ -352,9 +354,9 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
                                            method = 'POST').object
 
         if isinstance(response, list):
-            nodes = [self._to_node(node) for node in response]
+            nodes = [self._to_node(node, ssh_password) for node in response]
         else:
-            nodes = self._to_node(response)
+            nodes = self._to_node(response, ssh_password)
 
         return nodes
 
@@ -383,6 +385,49 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
 
         return (response.status == 200 and response.body != '')
 
+    def deploy_node(self, **kwargs):
+      """
+      Create a new node, and start deployment.
+
+      @keyword    enable_root: If true, root password will be set to vnc_password (this will enable SSH access)
+                               and default 'toor' account will be deleted.
+      @type       enable_root: C{bool}
+
+      For detailed description and keywords args, see L{NodeDriver.deploy_node}.
+      """
+      image = kwargs['image']
+      vnc_password = kwargs.get('vnc_password', None)
+      enable_root = kwargs.get('enable_root', False)
+
+      if not vnc_password:
+        raise ValueError('You need to provide vnc_password argument if you want to use deployment')
+
+      if image in STANDARD_DRIVES and STANDARD_DRIVES[image]['supports_deployment']:
+        raise valueError('Image %s does not support deployment' % (image.id))
+
+      if enable_root:
+        root_enable_script = ScriptDeployment(script = "unset HISTFILE;" \
+                                                       "echo root:%s | chpasswd;" \
+                                                       "sed -i '/^toor.*$/d' /etc/passwd /etc/shadow;" \
+                                                       "history -c" % \
+                                                       (vnc_password), delete = True)
+        deploy = kwargs.get('deploy', None)
+        if deploy:
+          if isinstance(deploy, ScriptDeployment) or isinstance(deploy, SSHKeyDeployment):
+            deployment = MultiStepDeployment([deploy, root_enable_script])
+          elif isinstance(deploy, MultiStepDeployment):
+            deployment = deploy
+            deployment.add(root_enable_script)
+        else:
+          deployment = root_enable_script
+
+        kwargs['deploy'] = deployment
+
+      if not kwargs.get('ssh_username', None):
+        kwargs['ssh_username'] = 'toor'
+
+      return super(ElasticHostsBaseNodeDriver, self).deploy_node(**kwargs)
+
     def ex_shutdown_node(self, node):
         # Sends the ACPI power-down event
         response = self.connection.request(action = '/servers/%s/shutdown' % (node.id),
@@ -396,7 +441,7 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
         return response.status == 204
 
     # Helper methods
-    def _to_node(self, data):
+    def _to_node(self, data, ssh_password = None):
         try:
             state = NODE_STATE_MAP[data['status']]
         except KeyError:
@@ -412,6 +457,9 @@ class ElasticHostsBaseNodeDriver(NodeDriver):
         if data.has_key('vnc:ip') and data.has_key('vnc:password'):
             extra.update({'vnc_ip': data['vnc:ip'], 'vnc_password': data['vnc:password']})
 
+        if ssh_password:
+          extra.update({'password': ssh_password})
+
         node = Node(id = data['server'], name = data['name'], state =  state,
                     public_ip = public_ip, private_ip = None, driver = self.connection.driver,
                     extra = extra)
-- 
1.7.1

