Repository: cloudtools/troposphere

Issue:
Add support to create Elasticsearch Domains
Cloudformation recently has implemented the creation of Elasticsearch domains, would be nice to have this feature in troposphere too.

http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html



Discussion:
@edubxb - are you planning on submitting a PR for this?  I can take a stab at it if not, just don't want to double up work :)

I will try... I will take a look at the code and make a PR.

Cool - if you decide you don't have the  time, let us know here and I'll get it knocked out soon.  It looks like you'll need to create an AWSObject for the Domain itself, and then AWSProperties for: 

http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticsearch-domain-ebsoptions.html
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticsearch-domain-elasticsearchclusterconfig.html
http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticsearch-domain-snapshotoptions.html

Shouldn't be too hard - if you need a hand, go ahead and ping here.  Good luck!

Thanks for your help!

Just an update  - #414 was submitted, so hopefully this will go in soon.


Resolving patch:
diff --git a/examples/ElasticsearchDomain.py b/examples/ElasticsearchDomain.py
new file mode 100644
--- /dev/null
+++ b/examples/ElasticsearchDomain.py
@@ -0,0 +1,42 @@
+# Converted from Elasticsearch Domain example located at:
+# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticsearch-domain.html#d0e51519
+
+from troposphere import Template
+from troposphere.elasticsearch import ElasticsearchDomain, EBSOptions
+from troposphere.elasticsearch import ElasticsearchClusterConfig
+from troposphere.elasticsearch import SnapshotOptions
+
+
+templ = Template()
+
+templ.add_description('Elasticsearch Domain example')
+
+es_domain = templ.add_resource(ElasticsearchDomain(
+    'ElasticsearchDomain',
+    DomainName="ExampleElasticsearchDomain",
+    ElasticsearchClusterConfig=ElasticsearchClusterConfig(
+        DedicatedMasterEnabled=True,
+        InstanceCount=2,
+        ZoneAwarenessEnabled=True,
+        InstanceType="m3.medium.elasticsearch",
+        DedicatedMasterType="m3.medium.elasticsearch",
+        DedicatedMasterCount=3
+    ),
+    EBSOptions=EBSOptions(EBSEnabled=True,
+                          Iops=0,
+                          VolumeSize=20,
+                          VolumeType="gp2"),
+    SnapshotOptions=SnapshotOptions(AutomatedSnapshotStartHour=0),
+    AccessPolicies={'Version': '2012-10-17',
+                    'Statement': [{
+                        'Effect': 'Allow',
+                        'Principal': {
+                            'AWS': '*'
+                        },
+                        'Action': 'es:*',
+                        'Resource': '*'
+                    }]},
+    AdvancedOptions={"rest.action.multi.allow_explicit_index": "true"}
+))
+
+print(templ.to_json())
diff --git a/troposphere/elasticsearch.py b/troposphere/elasticsearch.py
new file mode 100644
--- /dev/null
+++ b/troposphere/elasticsearch.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2012-2015, Mark Peek <mark@peek.org>
+# All rights reserved.
+#
+# See LICENSE file for full license.
+
+from . import AWSProperty, AWSObject
+from .validators import boolean, integer, integer_range, positive_integer
+
+VALID_VOLUME_TYPES = ('standard', 'gp2', 'io1')
+
+try:
+    from awacs.aws import Policy
+    policytypes = (dict, Policy)
+except ImportError:
+    policytypes = dict,
+
+
+def validate_volume_type(volume_type):
+    """Validate VolumeType for ElasticsearchDomain"""
+    if volume_type not in VALID_VOLUME_TYPES:
+        raise ValueError("Elasticsearch Domain VolumeType must be one of: %s" %
+                         ", ".join(VALID_VOLUME_TYPES))
+    return volume_type
+
+
+class EBSOptions(AWSProperty):
+    props = {
+        'EBSEnabled': (boolean, False),
+        'Iops': (positive_integer, False),
+        'VolumeSize': (integer, False),
+        'VolumeType': (validate_volume_type, False)
+    }
+
+    def validate(self):
+        volume_type = self.properties.get('VolumeType')
+        iops = self.properties.get('Iops')
+        if volume_type == 'io1' and not iops:
+            raise ValueError("Must specify Iops if VolumeType is 'io1'.")
+
+
+class ElasticsearchClusterConfig(AWSProperty):
+    props = {
+        'DedicatedMasterCount': (integer, False),
+        'DedicatedMasterEnabled': (boolean, False),
+        'DedicatedMasterType': (basestring, False),
+        'InstanceCount': (integer, False),
+        'InstanceType': (basestring, False),
+        'ZoneAwarenessEnabled': (boolean, False)
+    }
+
+
+class SnapshotOptions(AWSProperty):
+    props = {
+        'AutomatedSnapshotStartHour': (integer_range(0, 23), False)
+    }
+
+
+class ElasticsearchDomain(AWSObject):
+    resource_type = "AWS::Elasticsearch::Domain"
+
+    props = {
+        'AccessPolicies': (policytypes, False),
+        'AdvancedOptions': (dict, False),
+        'DomainName': (basestring, True),
+        'EBSOptions': (EBSOptions, False),
+        'ElasticsearchClusterConfig': (ElasticsearchClusterConfig, False),
+        'SnapshotOptions': (SnapshotOptions, False),
+        'Tags': (list, False)
+    }


Test patch:
diff --git a/tests/examples_output/ElasticsearchDomain.template b/tests/examples_output/ElasticsearchDomain.template
new file mode 100644
--- /dev/null
+++ b/tests/examples_output/ElasticsearchDomain.template
@@ -0,0 +1,44 @@
+{
+    "Description": "Elasticsearch Domain example",
+    "Resources": {
+        "ElasticsearchDomain": {
+            "Properties": {
+                "AccessPolicies": {
+                    "Statement": [
+                        {
+                            "Action": "es:*",
+                            "Effect": "Allow",
+                            "Principal": {
+                                "AWS": "*"
+                            },
+                            "Resource": "*"
+                        }
+                    ],
+                    "Version": "2012-10-17"
+                },
+                "AdvancedOptions": {
+                    "rest.action.multi.allow_explicit_index": "true"
+                },
+                "DomainName": "ExampleElasticsearchDomain",
+                "EBSOptions": {
+                    "EBSEnabled": "true",
+                    "Iops": 0,
+                    "VolumeSize": 20,
+                    "VolumeType": "gp2"
+                },
+                "ElasticsearchClusterConfig": {
+                    "DedicatedMasterCount": 3,
+                    "DedicatedMasterEnabled": "true",
+                    "DedicatedMasterType": "m3.medium.elasticsearch",
+                    "InstanceCount": 2,
+                    "InstanceType": "m3.medium.elasticsearch",
+                    "ZoneAwarenessEnabled": "true"
+                },
+                "SnapshotOptions": {
+                    "AutomatedSnapshotStartHour": 0
+                }
+            },
+            "Type": "AWS::Elasticsearch::Domain"
+        }
+    }
+}