From a4810779eeaed77a3c47ce1fb07e31cda8c6d124 Mon Sep 17 00:00:00 2001
From: Rob Parrott <robparrott@gmail.com>
Date: Fri, 15 Mar 2013 22:55:01 -0400
Subject: [PATCH] modified ec2 module to use EC2 idempotency via client-token
 attribute.

---
 ec2 | 108 ++++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 69 insertions(+), 39 deletions(-)

diff --git a/ec2 b/ec2
index 475b7f93223..4abcbb7c19a 100644
--- a/ec2
+++ b/ec2
@@ -1,4 +1,4 @@
-#!/usr/bin/python -tt
+#!/usr/local/bin/python -tt
 # This file is part of Ansible
 #
 # Ansible is free software: you can redistribute it and/or modify
@@ -28,6 +28,12 @@ options:
     required: true
     default: null
     aliases: ['keypair']
+  id:
+    description:
+      - identifier for this instance or set of instances, so that the module will be idempotent with respect to EC2 instances.
+    required: false
+    default: null
+    aliases: []
   group:
     description:
       - security group to use with the instance
@@ -149,6 +155,7 @@ def main():
     module = AnsibleModule(
         argument_spec = dict(
             key_name = dict(required=True, aliases = ['keypair']),
+            id = dict(),
             group = dict(),
             group_id = dict(),
             instance_type = dict(aliases=['type']),
@@ -169,6 +176,7 @@ def main():
     )
 
     key_name = module.params.get('key_name')
+    id = module.params.get('id')
     group_name = module.params.get('group')
     group_id = module.params.get('group_id')
     instance_type = module.params.get('instance_type')
@@ -212,12 +220,30 @@ def main():
     except boto.exception.NoAuthHandlerFound, e:
             module.fail_json(msg = str(e))
 
+    # Lookup any instances that much our run id.
+    
+    running_instances = []
+    count_remaining = int(count)
+        
+    if id != None:
+        filter_dict = {'client-token':id, 'instance-state-name' : 'running'}
+        previous_reservations = ec2.get_all_instances(None, filter_dict )
+        for res in previous_reservations:
+            for prev_instance in res.instances:
+                running_instances.append(prev_instance)
+        count_remaining = count_remaining - len(running_instances) 
+#        module.fail_json(msg = "known running instances: %s" % (running_instances)) 
+
+    
     # Both min_count and max_count equal count parameter. This means the launch request is explicit (we want count, or fail) in how many instances we want.
 
-    try:
-        res = ec2.run_instances(image, key_name = key_name,
-                                min_count = count, 
-                                max_count = count,
+    
+    if count_remaining > 0:
+        try:
+            res = ec2.run_instances(image, key_name = key_name,
+                                client_token=id,
+                                min_count = count_remaining, 
+                                max_count = count_remaining,
                                 monitoring_enabled = monitoring,
                                 security_groups = [group_name],
                                 instance_type = instance_type,
@@ -225,50 +251,54 @@ def main():
                                 ramdisk_id = ramdisk,
                                 subnet_id = vpc_subnet_id,
                                 user_data = user_data)
-    except boto.exception.BotoServerError, e:
-        module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
-
-    instids = [ i.id for i in res.instances ]
-    while True:
-        try:
-            res.connection.get_all_instances(instids)
-            break
-        except boto.exception.EC2ResponseError as e:
-            if "<Code>InvalidInstanceID.NotFound</Code>" in str(e):
-                # there's a race between start and get an instance
-                continue
-            else:
-                module.fail_json(msg = str(e))
-
-    if instance_tags:
-        try:
-            ec2.create_tags(instids, module.from_json(instance_tags))
-        except boto.exception.EC2ResponseError as e:
+        except boto.exception.BotoServerError, e:
             module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
 
-    # wait here until the instances are up
-    res_list = res.connection.get_all_instances(instids)
-    this_res = res_list[0]
-    num_running = 0
-    wait_timeout = time.time() + wait_timeout
-    while wait and wait_timeout > time.time() and num_running < len(instids):
+        instids = [ i.id for i in res.instances ]
+        while True:
+            try:
+                res.connection.get_all_instances(instids)
+                break
+            except boto.exception.EC2ResponseError as e:
+                if "<Code>InvalidInstanceID.NotFound</Code>" in str(e):
+                    # there's a race between start and get an instance
+                    continue
+                else:
+                    module.fail_json(msg = str(e))
+
+        if instance_tags:
+            try:
+                ec2.create_tags(instids, module.from_json(instance_tags))
+            except boto.exception.EC2ResponseError as e:
+                module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
+
+        # wait here until the instances are up
         res_list = res.connection.get_all_instances(instids)
         this_res = res_list[0]
-        num_running = len([ i for i in this_res.instances if i.state=='running' ])
-        time.sleep(5)
-    if wait and wait_timeout <= time.time():
-        # waiting took too long
-        module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
-    instances = []
-    for inst in this_res.instances:
+        num_running = 0
+        wait_timeout = time.time() + wait_timeout
+        while wait and wait_timeout > time.time() and num_running < len(instids):
+            res_list = res.connection.get_all_instances(instids)
+            this_res = res_list[0]
+            num_running = len([ i for i in this_res.instances if i.state=='running' ])
+            time.sleep(5)
+        if wait and wait_timeout <= time.time():
+            # waiting took too long
+            module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
+    
+        for inst in this_res.instances:
+            running_instances.append(inst)
+        
+    instance_dict_array = []
+    for inst in running_instances:
         d = {
            'id': inst.id,
            'public_ip': inst.ip_address,
            'public_dns_name': inst.public_dns_name
             }
-        instances.append(d)
+        instance_dict_array.append(d)
 
-    module.exit_json(changed=True, instances=instances)
+    module.exit_json(changed=True, instances=instance_dict_array)
 
 # this is magic, see lib/ansible/module_common.py
 #<<INCLUDE_ANSIBLE_MODULE_COMMON>>