cfn-bridge 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +50 -0
- data/lib/cloud_formation/bridge/cli.rb +4 -0
- data/lib/cloud_formation/bridge/executor.rb +10 -1
- data/lib/cloud_formation/bridge/names.rb +10 -0
- data/lib/cloud_formation/bridge/request.rb +7 -1
- data/lib/cloud_formation/bridge/resources/base.rb +14 -3
- data/lib/cloud_formation/bridge/resources/base_elasti_cache_resource.rb +58 -0
- data/lib/cloud_formation/bridge/resources/elasti_cache_node_urls.rb +35 -0
- data/lib/cloud_formation/bridge/resources/elasti_cache_replica_cluster.rb +66 -0
- data/lib/cloud_formation/bridge/resources/elasti_cache_replication_group.rb +67 -0
- data/lib/cloud_formation/bridge/util.rb +6 -0
- data/lib/cloud_formation/bridge/version.rb +1 -1
- data/spec/files/create-cache-node-urls-message.json +12 -0
- data/spec/files/create-redis-cache-node-urls-message.json +12 -0
- data/spec/files/create-replica-cluster-message.json +13 -0
- data/spec/files/create-replication-group-message.json +14 -0
- data/spec/files/delete-replica-cluster-message.json +14 -0
- data/spec/files/delete-replication-group-message.json +15 -0
- data/spec/files/describe-cache-cluster-primary.json +57 -0
- data/spec/files/describe-cache-cluster-replica-done.json +68 -0
- data/spec/files/describe-cache-cluster-replica.json +46 -0
- data/spec/files/describe-memcached-cluster.json +73 -0
- data/spec/files/describe-replication-group-primary-and-replica.json +49 -0
- data/spec/files/describe-replication-group-primary-only.json +38 -0
- data/spec/files/outputs-formation.json +0 -4
- data/spec/files/sample-replica-cluster.json +51 -0
- data/spec/files/sample-replication-group.json +34 -0
- data/spec/lib/cloud_formation/bridge/executor_spec.rb +1 -1
- data/spec/lib/cloud_formation/bridge/request_spec.rb +17 -5
- data/spec/lib/cloud_formation/bridge/resources/elasti_cache_node_urls_spec.rb +61 -0
- data/spec/lib/cloud_formation/bridge/resources/elasti_cache_replica_cluster_spec.rb +67 -0
- data/spec/lib/cloud_formation/bridge/resources/elasti_cache_replication_group_spec.rb +65 -0
- data/spec/support/file_support.rb +6 -0
- metadata +40 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 937f3058f2f853584b9d5340292cc7a9d188dea8
         | 
| 4 | 
            +
              data.tar.gz: d5f84fe245dce34c6f46e095cb03a338c1d4c97c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 4177d8b32ce517c52807ec0cff6d762a9ed4eb63ad327bd65779ece2d99af33d3fa04ac1741228b8fd8322bf371c915182c6359553cd0a9dae3313ad899adcd2
         | 
| 7 | 
            +
              data.tar.gz: 5d0caaf158b0175de5159055a25b8bde177eec8605d82da9dbd0d90077e2d60800b3dfcbe83f776efb76a1c132e821e684700713e321aac82932bf0116f6b6dc
         | 
    
        data/README.md
    CHANGED
    
    | @@ -13,6 +13,9 @@ | |
| 13 13 | 
             
            	- [Current custom resources](#current-custom-resources)
         | 
| 14 14 | 
             
            		- [Custom::SubscribeSQSQueueToSNSTopic](#customsubscribesqsqueuetosnstopic)
         | 
| 15 15 | 
             
            		- [Custom::CloudFormationOutputs](#customcloudformationoutputs)
         | 
| 16 | 
            +
            		- [Custom::Custom::ElastiCacheReplicationGroup](#customcustomelasticachereplicationgroup)
         | 
| 17 | 
            +
            		- [Custom::ElastiCacheReplicaCluster](#customelasticachereplicacluster)
         | 
| 18 | 
            +
            		- [Custom::ElastiCacheNodeURLs](#customelasticachenodeurls)
         | 
| 16 19 | 
             
            	- [Contributing](#contributing)
         | 
| 17 20 |  | 
| 18 21 | 
             
            <!-- END doctoc generated TOC please keep comment here to allow auto update -->
         | 
| @@ -194,6 +197,12 @@ It is declared just like any other custom resource with the name you have regist | |
| 194 197 |  | 
| 195 198 | 
             
            And with this you should be able to start creating your own custom cloud formation resources.
         | 
| 196 199 |  | 
| 200 | 
            +
            Gotchas you should be aware:
         | 
| 201 | 
            +
             | 
| 202 | 
            +
            * Do not return `nil` on `Data` fields, your resource will not be created and you will not get any error message about this;
         | 
| 203 | 
            +
            * Make sure all messages are logged somewhere (I'd recommend an email) so even if the service fails to create resources for some reason you can still work with the cloud formation manually;
         | 
| 204 | 
            +
            * Direct your logs somewhere where you can easily look at, the command line interface will print everything to `STDOUT`, make sure you send this data to a file so you can look at what's going on;
         | 
| 205 | 
            +
             | 
| 197 206 | 
             
            ## Current custom resources
         | 
| 198 207 |  | 
| 199 208 | 
             
            ### Custom::SubscribeSQSQueueToSNSTopic
         | 
| @@ -214,6 +223,47 @@ Parameters: | |
| 214 223 |  | 
| 215 224 | 
             
            * `Name` - the name of the cloud formation you want to get the outputs from - *required*;
         | 
| 216 225 |  | 
| 226 | 
            +
             | 
| 227 | 
            +
            ### Custom::Custom::ElastiCacheReplicationGroup
         | 
| 228 | 
            +
             | 
| 229 | 
            +
            Creates an `ElastiCache` replication group from an already available cache cluster (that will be configured as the primary).
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            Parameters:
         | 
| 232 | 
            +
             | 
| 233 | 
            +
            * `ClusterId` - the name of the primary ElastiCache cluster for this replication group;
         | 
| 234 | 
            +
            * `ReplicationGroupId` - the name of this replication group - this field follows the same `ElastiCache` naming requirements, 20 alphanumeric characters or `-`;
         | 
| 235 | 
            +
            * `Description` - the group description;
         | 
| 236 | 
            +
             | 
| 237 | 
            +
            Produced `Fn::GetAtt` values:
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            * `ReplicationGroupId` - the replication group id;
         | 
| 240 | 
            +
             | 
| 241 | 
            +
            ### Custom::ElastiCacheReplicaCluster
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            Creates an `ElastiCache` replica cluster for an already existing replication group.
         | 
| 244 | 
            +
             | 
| 245 | 
            +
            Parameters:
         | 
| 246 | 
            +
             | 
| 247 | 
            +
            * `ReplicationGroupId` - the id of the replication group where this replica cluster will register itself;
         | 
| 248 | 
            +
            * `ReplicaClusterId` - the id for this replication group;
         | 
| 249 | 
            +
             | 
| 250 | 
            +
            Produced `Fn::GetAtt` values:
         | 
| 251 | 
            +
             | 
| 252 | 
            +
            * `ReplicaClusterId` - the id of this replica cluster;
         | 
| 253 | 
            +
            * `NodeURLs` - list of `host:port` values (separated by `,`) for the nodes in the cluster if it is a redis cluster or the configuration URL for a memcached cluster;
         | 
| 254 | 
            +
             | 
| 255 | 
            +
            ### Custom::ElastiCacheNodeURLs
         | 
| 256 | 
            +
             | 
| 257 | 
            +
            Produces pairs of `host:port` separated by `,` for all nodes in the cluster if it's a Redis cluster or the configuration `host:port` if it is a Memcached cluster.
         | 
| 258 | 
            +
             | 
| 259 | 
            +
            Parameters:
         | 
| 260 | 
            +
             | 
| 261 | 
            +
            * `ClusterId` - the name of the primary ElastiCache cluster for this replication group;
         | 
| 262 | 
            +
             | 
| 263 | 
            +
            Produced `Fn::GetAtt` values:
         | 
| 264 | 
            +
             | 
| 265 | 
            +
            * `NodeURLs` - list of `host:port` values (separated by `,`) for the nodes in the cluster if it is a Redis cluster or the configuration `host:port` for a Memcached cluster;
         | 
| 266 | 
            +
             | 
| 217 267 | 
             
            ## Contributing
         | 
| 218 268 |  | 
| 219 269 | 
             
            1. [Fork it](https://github.com/TheNeatCompany/cfn-bridge/fork)
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            require 'thor'
         | 
| 2 2 | 
             
            require 'cloud_formation/bridge/poller'
         | 
| 3 3 | 
             
            require 'cloud_formation/bridge/util'
         | 
| 4 | 
            +
            require 'cloud_formation/bridge/version'
         | 
| 4 5 |  | 
| 5 6 | 
             
            module CloudFormation
         | 
| 6 7 | 
             
              module Bridge
         | 
| @@ -9,6 +10,9 @@ module CloudFormation | |
| 9 10 | 
             
                  desc "start QUEUE_NAME", "Starts watching this specific SQS queue"
         | 
| 10 11 | 
             
                  def start(queue_name)
         | 
| 11 12 | 
             
                    STDOUT.sync = true
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    Util::LOGGER.info("Starting cfn-bridge with queue #{queue_name} - version #{CloudFormation::Bridge::VERSION}")
         | 
| 15 | 
            +
             | 
| 12 16 | 
             
                    poller = CloudFormation::Bridge::Poller.new(queue_name)
         | 
| 13 17 | 
             
                    poller.start
         | 
| 14 18 | 
             
                  end
         | 
| @@ -2,6 +2,9 @@ require 'cloud_formation/bridge/exception_notifier' | |
| 2 2 | 
             
            require 'cloud_formation/bridge/names'
         | 
| 3 3 | 
             
            require 'cloud_formation/bridge/resources/subscribe_queue_to_topic'
         | 
| 4 4 | 
             
            require 'cloud_formation/bridge/resources/cloud_formation_outputs'
         | 
| 5 | 
            +
            require 'cloud_formation/bridge/resources/elasti_cache_replication_group'
         | 
| 6 | 
            +
            require 'cloud_formation/bridge/resources/elasti_cache_replica_cluster'
         | 
| 7 | 
            +
            require 'cloud_formation/bridge/resources/elasti_cache_node_urls'
         | 
| 5 8 |  | 
| 6 9 | 
             
            module CloudFormation
         | 
| 7 10 | 
             
              module Bridge
         | 
| @@ -14,6 +17,12 @@ module CloudFormation | |
| 14 17 | 
             
                      CloudFormation::Bridge::Resources::SubscribeQueueToTopic.new,
         | 
| 15 18 | 
             
                    "Custom::CloudFormationOutputs" =>
         | 
| 16 19 | 
             
                      CloudFormation::Bridge::Resources::CloudFormationOutputs.new,
         | 
| 20 | 
            +
                    "Custom::ElastiCacheReplicationGroup" =>
         | 
| 21 | 
            +
                      CloudFormation::Bridge::Resources::ElastiCacheReplicationGroup.new,
         | 
| 22 | 
            +
                    "Custom::ElastiCacheReplicaCluster" =>
         | 
| 23 | 
            +
                      CloudFormation::Bridge::Resources::ElastiCacheReplicaCluster.new,
         | 
| 24 | 
            +
                    "Custom::ElastiCacheNodeURLs" =>
         | 
| 25 | 
            +
                      CloudFormation::Bridge::Resources::ElastiCacheNodeUrls.new,
         | 
| 17 26 | 
             
                  }
         | 
| 18 27 |  | 
| 19 28 | 
             
                  attr_reader :registry
         | 
| @@ -40,7 +49,7 @@ module CloudFormation | |
| 40 49 | 
             
                      end
         | 
| 41 50 | 
             
                    rescue Exception => ex
         | 
| 42 51 | 
             
                      ExceptionNotifier.report_exception(ex, request.request)
         | 
| 43 | 
            -
                      request.fail!(ex.message)
         | 
| 52 | 
            +
                      request.fail!("#{ex.class.name} - #{ex.message}")
         | 
| 44 53 | 
             
                    end
         | 
| 45 54 |  | 
| 46 55 | 
             
                  end
         | 
| @@ -29,6 +29,16 @@ module CloudFormation | |
| 29 29 | 
             
                    DATA = 'Data'
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
| 32 | 
            +
                  module ELASTI_CACHE
         | 
| 33 | 
            +
                    AVAILABLE = 'available'
         | 
| 34 | 
            +
                    CLUSTER_ID = 'ClusterId'
         | 
| 35 | 
            +
                    REPLICA_CLUSTER_ID = 'ReplicaClusterId'
         | 
| 36 | 
            +
                    DESCRIPTION = 'Description'
         | 
| 37 | 
            +
                    NODE_URLS = 'NodeURLs'
         | 
| 38 | 
            +
                    REPLICA = 'replica'
         | 
| 39 | 
            +
                    REPLICATION_GROUP_ID = 'ReplicationGroupId'
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 32 42 | 
             
                end
         | 
| 33 43 | 
             
              end
         | 
| 34 44 | 
             
            end
         | 
| @@ -74,7 +74,13 @@ module CloudFormation | |
| 74 74 | 
             
                  end
         | 
| 75 75 |  | 
| 76 76 | 
             
                  def succeed!(response)
         | 
| 77 | 
            -
                    actual_response =  | 
| 77 | 
            +
                    actual_response = case response
         | 
| 78 | 
            +
                      when Hash
         | 
| 79 | 
            +
                        build_response(response)
         | 
| 80 | 
            +
                      else
         | 
| 81 | 
            +
                        build_response
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 78 84 | 
             
                    HttpBridge.put(request_url, actual_response)
         | 
| 79 85 | 
             
                  end
         | 
| 80 86 |  | 
| @@ -1,4 +1,6 @@ | |
| 1 | 
            +
            require 'timeout'
         | 
| 1 2 | 
             
            require 'cloud_formation/bridge/names'
         | 
| 3 | 
            +
            require 'cloud_formation/bridge/util'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module CloudFormation
         | 
| 4 6 | 
             
              module Bridge
         | 
| @@ -7,8 +9,8 @@ module CloudFormation | |
| 7 9 | 
             
                  class Base
         | 
| 8 10 | 
             
                    include CloudFormation::Bridge::Names
         | 
| 9 11 |  | 
| 10 | 
            -
                    def require_fields(request, fields)
         | 
| 11 | 
            -
                      empty_fields = fields.select do |field|
         | 
| 12 | 
            +
                    def require_fields(request, *fields)
         | 
| 13 | 
            +
                      empty_fields = fields.flatten.select do |field|
         | 
| 12 14 | 
             
                        request.resource_properties[field].nil? ||
         | 
| 13 15 | 
             
                          request.resource_properties[field].strip.empty?
         | 
| 14 16 | 
             
                      end
         | 
| @@ -21,7 +23,16 @@ module CloudFormation | |
| 21 23 |  | 
| 22 24 | 
             
                    def update(request)
         | 
| 23 25 | 
             
                      raise CloudFormation::Bridge::OperationNotImplementedError.new(
         | 
| 24 | 
            -
             | 
| 26 | 
            +
                              "The resource #{self.class.name} does not implement the update operation - #{request.inspect}")
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def wait_until(description, seconds = 5, max_wait = 600, &block)
         | 
| 30 | 
            +
                      Timeout.timeout(max_wait) do
         | 
| 31 | 
            +
                        while !block.call
         | 
| 32 | 
            +
                          Util.logger.info("Waiting for #{description}")
         | 
| 33 | 
            +
                          sleep(seconds)
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
                      end
         | 
| 25 36 | 
             
                    end
         | 
| 26 37 |  | 
| 27 38 | 
             
                  end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            require 'aws/elasticache'
         | 
| 2 | 
            +
            require 'cloud_formation/bridge/names'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module CloudFormation
         | 
| 5 | 
            +
              module Bridge
         | 
| 6 | 
            +
                module Resources
         | 
| 7 | 
            +
                  module BaseElastiCacheResource
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    UnknownCacheEngineError = Class.new(StandardError)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    include CloudFormation::Bridge::Names
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def replication_group_available?(replication_group_id)
         | 
| 14 | 
            +
                      replication_group = client.describe_replication_groups(replication_group_id: replication_group_id)[:replication_groups][0]
         | 
| 15 | 
            +
                      replication_group[:status] == ELASTI_CACHE::AVAILABLE
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def find_cluster(cluster_id)
         | 
| 19 | 
            +
                      client.describe_cache_clusters(
         | 
| 20 | 
            +
                        cache_cluster_id: cluster_id,
         | 
| 21 | 
            +
                        show_cache_node_info: true
         | 
| 22 | 
            +
                      )[:cache_clusters][0]
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def node_urls(cluster_id)
         | 
| 26 | 
            +
                      cluster = client.describe_cache_clusters(
         | 
| 27 | 
            +
                        cache_cluster_id: cluster_id,
         | 
| 28 | 
            +
                        show_cache_node_info: true
         | 
| 29 | 
            +
                      )[:cache_clusters][0]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      case cluster[:engine]
         | 
| 32 | 
            +
                        when 'redis'
         | 
| 33 | 
            +
                          cluster[:cache_nodes].map do |node|
         | 
| 34 | 
            +
                            "#{node[:endpoint][:address]}:#{node[:endpoint][:port]}"
         | 
| 35 | 
            +
                          end.join(",")
         | 
| 36 | 
            +
                        when 'memcached'
         | 
| 37 | 
            +
                          "#{cluster[:configuration_endpoint][:address]}:#{cluster[:configuration_endpoint][:port]}"
         | 
| 38 | 
            +
                        else
         | 
| 39 | 
            +
                          UnknownCacheEngineError.new("Don't know what to do with cache engine #{cluster[:engine]} - #{cluster.inspect}")
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def wait_until_cluster_is_available(cluster_id)
         | 
| 44 | 
            +
                      wait_until("replica #{cluster_id} to be available") do
         | 
| 45 | 
            +
                        cluster = find_cluster(cluster_id)
         | 
| 46 | 
            +
                        Util.logger.info("Cluster info is #{cluster.inspect}")
         | 
| 47 | 
            +
                        cluster[:cache_cluster_status] == ELASTI_CACHE::AVAILABLE
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def client
         | 
| 52 | 
            +
                      @client ||= AWS::ElastiCache.new.client
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require 'cloud_formation/bridge/resources/base'
         | 
| 2 | 
            +
            require 'cloud_formation/bridge/resources/base_elasti_cache_resource'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module CloudFormation
         | 
| 5 | 
            +
              module Bridge
         | 
| 6 | 
            +
                module Resources
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  class ElastiCacheNodeUrls < Base
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    include BaseElastiCacheResource
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    def create(request)
         | 
| 13 | 
            +
                      require_fields(request, ELASTI_CACHE::CLUSTER_ID)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                      cluster_id = request.resource_properties[ELASTI_CACHE::CLUSTER_ID]
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      wait_until_cluster_is_available(cluster_id)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      {
         | 
| 20 | 
            +
                        FIELDS::DATA => {
         | 
| 21 | 
            +
                          ELASTI_CACHE::REPLICA_CLUSTER_ID => cluster_id,
         | 
| 22 | 
            +
                          ELASTI_CACHE::NODE_URLS => node_urls(cluster_id),
         | 
| 23 | 
            +
                        },
         | 
| 24 | 
            +
                        FIELDS::PHYSICAL_RESOURCE_ID => cluster_id,
         | 
| 25 | 
            +
                      }
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def delete(request)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            require 'aws/elasticache'
         | 
| 2 | 
            +
            require 'cloud_formation/bridge/util'
         | 
| 3 | 
            +
            require 'cloud_formation/bridge/resources/base'
         | 
| 4 | 
            +
            require 'cloud_formation/bridge/resources/base_elasti_cache_resource'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module CloudFormation
         | 
| 7 | 
            +
              module Bridge
         | 
| 8 | 
            +
                module Resources
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  class ElastiCacheReplicaCluster < Base
         | 
| 11 | 
            +
                    include BaseElastiCacheResource
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    REQUIRED_FIELDS = [
         | 
| 14 | 
            +
                      ELASTI_CACHE::REPLICATION_GROUP_ID,
         | 
| 15 | 
            +
                      ELASTI_CACHE::REPLICA_CLUSTER_ID,
         | 
| 16 | 
            +
                    ]
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def create(request)
         | 
| 19 | 
            +
                      require_fields(request, REQUIRED_FIELDS)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                      cluster_id = request.resource_properties[ELASTI_CACHE::REPLICA_CLUSTER_ID]
         | 
| 22 | 
            +
                      replication_id = request.resource_properties[ELASTI_CACHE::REPLICATION_GROUP_ID]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                      client.create_cache_cluster(cache_cluster_id: cluster_id, replication_group_id: replication_id)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      wait_until_cluster_is_available(cluster_id)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      {
         | 
| 29 | 
            +
                        FIELDS::DATA => {
         | 
| 30 | 
            +
                          ELASTI_CACHE::REPLICA_CLUSTER_ID => cluster_id,
         | 
| 31 | 
            +
                          ELASTI_CACHE::NODE_URLS => node_urls(cluster_id),
         | 
| 32 | 
            +
                        },
         | 
| 33 | 
            +
                        FIELDS::PHYSICAL_RESOURCE_ID => cluster_id,
         | 
| 34 | 
            +
                      }
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def delete(request)
         | 
| 38 | 
            +
                      require_fields(request, ELASTI_CACHE::REPLICA_CLUSTER_ID)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      cluster_id = request.resource_properties[ELASTI_CACHE::REPLICA_CLUSTER_ID]
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                      begin
         | 
| 43 | 
            +
                        wait_until_cluster_is_available(cluster_id)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                        client.delete_cache_cluster(cache_cluster_id: cluster_id)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                        wait_until("cluster #{cluster_id} to be gone") do
         | 
| 48 | 
            +
                          begin
         | 
| 49 | 
            +
                            find_cluster(cluster_id)
         | 
| 50 | 
            +
                            false
         | 
| 51 | 
            +
                          rescue AWS::ElastiCache::Errors::CacheClusterNotFound
         | 
| 52 | 
            +
                            true
         | 
| 53 | 
            +
                          end
         | 
| 54 | 
            +
                        end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      rescue AWS::ElastiCache::Errors::CacheClusterNotFound
         | 
| 57 | 
            +
                        # no cache cluster? ignore
         | 
| 58 | 
            +
                        Util.logger.info("Could not find cache cluster for #{cluster_id}, ignoring")
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            require 'aws/elasticache'
         | 
| 2 | 
            +
            require 'cloud_formation/bridge/util'
         | 
| 3 | 
            +
            require 'cloud_formation/bridge/resources/base'
         | 
| 4 | 
            +
            require 'cloud_formation/bridge/resources/base_elasti_cache_resource'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module CloudFormation
         | 
| 7 | 
            +
              module Bridge
         | 
| 8 | 
            +
                module Resources
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  class ElastiCacheReplicationGroup < Base
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    include BaseElastiCacheResource
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    REQUIRED_FIELDS = [
         | 
| 15 | 
            +
                      ELASTI_CACHE::CLUSTER_ID,
         | 
| 16 | 
            +
                      ELASTI_CACHE::REPLICATION_GROUP_ID,
         | 
| 17 | 
            +
                      ELASTI_CACHE::DESCRIPTION,
         | 
| 18 | 
            +
                    ]
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def create(request)
         | 
| 21 | 
            +
                      require_fields(request, REQUIRED_FIELDS)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      replication_id = request.resource_properties[ELASTI_CACHE::REPLICATION_GROUP_ID]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      client.create_replication_group(
         | 
| 26 | 
            +
                        replication_group_id: replication_id,
         | 
| 27 | 
            +
                        primary_cluster_id: request.resource_properties[ELASTI_CACHE::CLUSTER_ID],
         | 
| 28 | 
            +
                        replication_group_description: request.resource_properties[ELASTI_CACHE::DESCRIPTION],
         | 
| 29 | 
            +
                      )
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      wait_until("replication group #{replication_id} to be available") do
         | 
| 32 | 
            +
                        replication_group_available?(replication_id)
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      {
         | 
| 36 | 
            +
                        FIELDS::DATA => {
         | 
| 37 | 
            +
                          ELASTI_CACHE::REPLICATION_GROUP_ID => replication_id
         | 
| 38 | 
            +
                        },
         | 
| 39 | 
            +
                        FIELDS::PHYSICAL_RESOURCE_ID => replication_id,
         | 
| 40 | 
            +
                      }
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def delete(request)
         | 
| 44 | 
            +
                      require_fields(request, ELASTI_CACHE::REPLICATION_GROUP_ID)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                      begin
         | 
| 47 | 
            +
                        replication_id = request.resource_properties[ELASTI_CACHE::REPLICATION_GROUP_ID]
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                        wait_until("replication group #{replication_id} to be available") do
         | 
| 50 | 
            +
                          replication_group_available?(replication_id)
         | 
| 51 | 
            +
                        end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                        client.delete_replication_group(
         | 
| 54 | 
            +
                          replication_group_id: replication_id,
         | 
| 55 | 
            +
                          retain_primary_cluster: true,
         | 
| 56 | 
            +
                        )
         | 
| 57 | 
            +
                      rescue AWS::ElastiCache::Errors::ReplicationGroupNotFoundFault
         | 
| 58 | 
            +
                        # main cluster does not exist, ignore
         | 
| 59 | 
            +
                        Util.logger.info("Replication group #{replication_id} does not exist, ignoring")
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            { "LogicalResourceId": "ReplicaClusterNodeUrls",
         | 
| 2 | 
            +
              "RequestId": "f9103cf1-5e1a-4597-a233-5838a7528126",
         | 
| 3 | 
            +
              "RequestType": "Create",
         | 
| 4 | 
            +
              "ResourceProperties": {
         | 
| 5 | 
            +
                "ClusterId": "samplecache",
         | 
| 6 | 
            +
                "ServiceToken": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 7 | 
            +
              },
         | 
| 8 | 
            +
              "ResourceType": "Custom::ElastiCacheNodeURLs",
         | 
| 9 | 
            +
              "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456%3Astack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64%7CRedisReplicaCluster%7Cf9103cf1-5e1a-4597-a233-5838a7528126",
         | 
| 10 | 
            +
              "StackId": "arn:aws:cloudformation:us-east-1:123456:stack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64",
         | 
| 11 | 
            +
              "TopicArn": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 12 | 
            +
            }
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            { "LogicalResourceId": "ReplicaClusterNodeUrls",
         | 
| 2 | 
            +
              "RequestId": "f9103cf1-5e1a-4597-a233-5838a7528126",
         | 
| 3 | 
            +
              "RequestType": "Create",
         | 
| 4 | 
            +
              "ResourceProperties": {
         | 
| 5 | 
            +
                "ClusterId": "dev-redis-replica",
         | 
| 6 | 
            +
                "ServiceToken": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 7 | 
            +
              },
         | 
| 8 | 
            +
              "ResourceType": "Custom::ElastiCacheNodeURLs",
         | 
| 9 | 
            +
              "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456%3Astack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64%7CRedisReplicaCluster%7Cf9103cf1-5e1a-4597-a233-5838a7528126",
         | 
| 10 | 
            +
              "StackId": "arn:aws:cloudformation:us-east-1:123456:stack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64",
         | 
| 11 | 
            +
              "TopicArn": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 12 | 
            +
            }
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            { "LogicalResourceId": "RedisReplicaCluster",
         | 
| 2 | 
            +
              "RequestId": "f9103cf1-5e1a-4597-a233-5838a7528126",
         | 
| 3 | 
            +
              "RequestType": "Create",
         | 
| 4 | 
            +
              "ResourceProperties": {
         | 
| 5 | 
            +
                "ReplicaClusterId": "dev-redis-replica",
         | 
| 6 | 
            +
                "ReplicationGroupId": "dev-redis-rep-group",
         | 
| 7 | 
            +
                "ServiceToken": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 8 | 
            +
              },
         | 
| 9 | 
            +
              "ResourceType": "Custom::ElastiCacheReplicaCluster",
         | 
| 10 | 
            +
              "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A123456%3Astack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64%7CRedisReplicaCluster%7Cf9103cf1-5e1a-4597-a233-5838a7528126",
         | 
| 11 | 
            +
              "StackId": "arn:aws:cloudformation:us-east-1:123456:stack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64",
         | 
| 12 | 
            +
              "TopicArn": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 13 | 
            +
            }
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            { "LogicalResourceId": "RedisClusterReplicationGroup",
         | 
| 2 | 
            +
              "RequestId": "b2fcc8a6-3f25-4e91-8aad-8d0b2f62e352",
         | 
| 3 | 
            +
              "RequestType": "Create",
         | 
| 4 | 
            +
              "ResourceProperties": {
         | 
| 5 | 
            +
                "ClusterId": "cluster-id-here",
         | 
| 6 | 
            +
                "Description": "Sample replication group for the redis instances",
         | 
| 7 | 
            +
                "ReplicationGroupId": "dev-redis-rep",
         | 
| 8 | 
            +
                "ServiceToken": "arn:aws:sns:us-east-1:12345567:test-stack-sns"
         | 
| 9 | 
            +
              },
         | 
| 10 | 
            +
              "ResourceType": "Custom::ElastiCacheReplicationGroup",
         | 
| 11 | 
            +
              "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A04%3Astack/replica-cluster/1a98e2b0-390e-11e4-a5f9-50e2416294e0%7CRedisClusterReplicationGroup%7Cb2fcc8a6-3f25-4e91-8aad-8d0b2f62e352",
         | 
| 12 | 
            +
              "StackId": "arn:aws:cloudformation:us-east-1:12345567:stack/mauricio-replica-cluster/1a98e2b0-390e-11e4-a5f9-50e2416294e0",
         | 
| 13 | 
            +
              "TopicArn": "arn:aws:sns:us-east-1:12345567:test-stack-sns"
         | 
| 14 | 
            +
            }
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            { "LogicalResourceId": "RedisReplicaCluster",
         | 
| 2 | 
            +
              "PhysicalResourceId": "dev-redis-rep-group",
         | 
| 3 | 
            +
              "RequestId": "ebaf8396-72f3-43b1-b58f-a808d952e688",
         | 
| 4 | 
            +
              "RequestType": "Delete",
         | 
| 5 | 
            +
              "ResourceProperties": {
         | 
| 6 | 
            +
                "ReplicaClusterId": "dev-redis-replica",
         | 
| 7 | 
            +
                "ReplicationGroupId": "dev-redis-rep-group",
         | 
| 8 | 
            +
                "ServiceToken": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 9 | 
            +
              },
         | 
| 10 | 
            +
              "ResourceType": "Custom::ElastiCacheReplicaCluster",
         | 
| 11 | 
            +
              "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A047170177871%3Astack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64%7CRedisReplicaCluster%7Cebaf8396-72f3-43b1-b58f-a808d952e688",
         | 
| 12 | 
            +
              "StackId": "arn:aws:cloudformation:us-east-1:123456:stack/sample-replica-cluster/ce7bd340-393c-11e4-962c-50fa1dbb2c64",
         | 
| 13 | 
            +
              "TopicArn": "arn:aws:sns:us-east-1:123456:sns-topic"
         | 
| 14 | 
            +
            }
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            { "LogicalResourceId": "RedisClusterReplicationGroup",
         | 
| 2 | 
            +
              "PhysicalResourceId": "sample-replica-cluster",
         | 
| 3 | 
            +
              "RequestId": "11214fd4-f6d6-4be0-87de-a1fa3b215990",
         | 
| 4 | 
            +
              "RequestType": "Delete",
         | 
| 5 | 
            +
              "ResourceProperties": {
         | 
| 6 | 
            +
                "ClusterId": "cluster-id-here",
         | 
| 7 | 
            +
                "Description": "Sample replication group for the redis instances",
         | 
| 8 | 
            +
                "ReplicationGroupId": "dev-redis-rep",
         | 
| 9 | 
            +
                "ServiceToken": "arn:aws:sns:us-east-1:12345567:test-stack-sns"
         | 
| 10 | 
            +
              },
         | 
| 11 | 
            +
              "ResourceType": "Custom::ElastiCacheReplicationGroup",
         | 
| 12 | 
            +
              "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A01234578%3Astack/sample-replica-cluster/1a98e2b0-390e-11e4-a5f9-50e2416294e0%7CRedisClusterReplicationGroup%7C11214fd4-f6d6-4be0-87de-a1fa3b215990",
         | 
| 13 | 
            +
              "StackId": "arn:aws:cloudformation:us-east-1:1234578:stack/mauricio-replica-cluster/1a98e2b0-390e-11e4-a5f9-50e2416294e0",
         | 
| 14 | 
            +
              "TopicArn": "arn:aws:sns:us-east-1:1234578:test-stack-sns"
         | 
| 15 | 
            +
            }
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            {
         | 
| 2 | 
            +
              "cache_clusters": [
         | 
| 3 | 
            +
                {
         | 
| 4 | 
            +
                  "cache_security_groups": [
         | 
| 5 | 
            +
                    {
         | 
| 6 | 
            +
                      "cache_security_group_name": "redis-redis-srkbp24vmwfr",
         | 
| 7 | 
            +
                      "status": "active"
         | 
| 8 | 
            +
                    }
         | 
| 9 | 
            +
                  ],
         | 
| 10 | 
            +
                  "cache_nodes": [
         | 
| 11 | 
            +
                    {
         | 
| 12 | 
            +
                      "parameter_group_status": "in-sync",
         | 
| 13 | 
            +
                      "cache_node_status": "available",
         | 
| 14 | 
            +
                      "cache_node_create_time": "2014-08-04 12:56:26 UTC",
         | 
| 15 | 
            +
                      "cache_node_id": "0001",
         | 
| 16 | 
            +
                      "customer_availability_zone": "us-east-1c",
         | 
| 17 | 
            +
                      "endpoint": {
         | 
| 18 | 
            +
                        "port": 6379,
         | 
| 19 | 
            +
                        "address": "red-re-1uptio9f2ytdi.mzufvw.0001.use1.cache.amazonaws.com"
         | 
| 20 | 
            +
                      }
         | 
| 21 | 
            +
                    }
         | 
| 22 | 
            +
                  ],
         | 
| 23 | 
            +
                  "security_groups": [
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  ],
         | 
| 26 | 
            +
                  "cache_cluster_id": "red-re-1uptio9f2ytdi",
         | 
| 27 | 
            +
                  "cache_parameter_group": {
         | 
| 28 | 
            +
                    "cache_node_ids_to_reboot": [
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    ],
         | 
| 31 | 
            +
                    "parameter_apply_status": "in-sync",
         | 
| 32 | 
            +
                    "cache_parameter_group_name": "default.redis2.8"
         | 
| 33 | 
            +
                  },
         | 
| 34 | 
            +
                  "cache_cluster_status": "available",
         | 
| 35 | 
            +
                  "replication_group_id": "rg-redisclu-2ffb4776",
         | 
| 36 | 
            +
                  "cache_node_type": "cache.m1.small",
         | 
| 37 | 
            +
                  "engine": "redis",
         | 
| 38 | 
            +
                  "pending_modified_values": {
         | 
| 39 | 
            +
                    "cache_node_ids_to_remove": [
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    ]
         | 
| 42 | 
            +
                  },
         | 
| 43 | 
            +
                  "preferred_availability_zone": "us-east-1c",
         | 
| 44 | 
            +
                  "cache_cluster_create_time": "2014-08-04 12:56:26 UTC",
         | 
| 45 | 
            +
                  "engine_version": "2.8.6",
         | 
| 46 | 
            +
                  "auto_minor_version_upgrade": true,
         | 
| 47 | 
            +
                  "preferred_maintenance_window": "thu:10:00-thu:11:00",
         | 
| 48 | 
            +
                  "client_download_landing_page": "https://console.aws.amazon.com/elasticache/home#client-download:",
         | 
| 49 | 
            +
                  "snapshot_retention_limit": 0,
         | 
| 50 | 
            +
                  "num_cache_nodes": 1,
         | 
| 51 | 
            +
                  "snapshot_window": "09:00-10:00"
         | 
| 52 | 
            +
                }
         | 
| 53 | 
            +
              ],
         | 
| 54 | 
            +
              "response_metadata": {
         | 
| 55 | 
            +
                "request_id": "1ab3c5f5-3891-11e4-9cb8-252ab83f5b14"
         | 
| 56 | 
            +
              }
         | 
| 57 | 
            +
            }
         |