1""" 2Manage Elasticache 3================== 4 5.. versionadded:: 2014.7.0 6 7Create, destroy and update Elasticache clusters. Be aware that this interacts 8with Amazon's services, and so may incur charges. 9 10Note: This module currently only supports creation and deletion of 11elasticache resources and will not modify clusters when their configuration 12changes in your state files. 13 14This module uses ``boto``, which can be installed via package, or pip. 15 16This module accepts explicit elasticache credentials but can also utilize 17IAM roles assigned to the instance through Instance Profiles. Dynamic 18credentials are then automatically obtained from AWS API and no further 19configuration is necessary. More information available `here 20<http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html>`_. 21 22If IAM roles are not used you need to specify them either in a pillar file or 23in the minion's config file: 24 25.. code-block:: yaml 26 27 elasticache.keyid: GKTADJGHEIQSXMKKRBJ08H 28 elasticache.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs 29 30It's also possible to specify ``key``, ``keyid`` and ``region`` via a profile, either 31passed in as a dict, or as a string to pull from pillars or minion config: 32 33.. code-block:: yaml 34 35 myprofile: 36 keyid: GKTADJGHEIQSXMKKRBJ08H 37 key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs 38 region: us-east-1 39 40.. code-block:: yaml 41 42 Ensure myelasticache exists: 43 boto_elasticache.present: 44 - name: myelasticache 45 - engine: redis 46 - cache_node_type: cache.t1.micro 47 - num_cache_nodes: 1 48 - notification_topic_arn: arn:aws:sns:us-east-1:879879:my-sns-topic 49 - region: us-east-1 50 - keyid: GKTADJGHEIQSXMKKRBJ08H 51 - key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs 52 53 # Using a profile from pillars 54 Ensure myelasticache exists: 55 boto_elasticache.present: 56 - name: myelasticache 57 - engine: redis 58 - cache_node_type: cache.t1.micro 59 - num_cache_nodes: 1 60 - notification_topic_arn: arn:aws:sns:us-east-1:879879:my-sns-topic 61 - region: us-east-1 62 - profile: myprofile 63 64 # Passing in a profile 65 Ensure myelasticache exists: 66 boto_elasticache.present: 67 - name: myelasticache 68 - engine: redis 69 - cache_node_type: cache.t1.micro 70 - num_cache_nodes: 1 71 - notification_topic_arn: arn:aws:sns:us-east-1:879879:my-sns-topic 72 - region: us-east-1 73 - profile: 74 keyid: GKTADJGHEIQSXMKKRBJ08H 75 key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs 76""" 77 78 79import logging 80 81log = logging.getLogger(__name__) 82 83 84def __virtual__(): 85 """ 86 Only load if boto is available. 87 """ 88 if "boto_elasticache.exists" in __salt__: 89 return "boto_elasticache" 90 return (False, "boto_elasticache module could not be loaded") 91 92 93def cache_cluster_present(*args, **kwargs): 94 return present(*args, **kwargs) 95 96 97def present( 98 name, 99 engine=None, 100 cache_node_type=None, 101 num_cache_nodes=None, 102 preferred_availability_zone=None, 103 port=None, 104 cache_parameter_group_name=None, 105 cache_security_group_names=None, 106 replication_group_id=None, 107 auto_minor_version_upgrade=True, 108 security_group_ids=None, 109 cache_subnet_group_name=None, 110 engine_version=None, 111 notification_topic_arn=None, 112 preferred_maintenance_window=None, 113 wait=None, 114 region=None, 115 key=None, 116 keyid=None, 117 profile=None, 118): 119 """ 120 Ensure the cache cluster exists. 121 122 name 123 Name of the cache cluster (cache cluster id). 124 125 engine 126 The name of the cache engine to be used for this cache cluster. Valid 127 values are memcached or redis. 128 129 cache_node_type 130 The compute and memory capacity of the nodes in the cache cluster. 131 cache.t1.micro, cache.m1.small, etc. See: https://boto.readthedocs.io/en/latest/ref/elasticache.html#boto.elasticache.layer1.ElastiCacheConnection.create_cache_cluster 132 133 num_cache_nodes 134 The number of cache nodes that the cache cluster will have. 135 136 preferred_availability_zone 137 The EC2 Availability Zone in which the cache cluster will be created. 138 All cache nodes belonging to a cache cluster are placed in the 139 preferred availability zone. 140 141 port 142 The port number on which each of the cache nodes will accept 143 connections. 144 145 cache_parameter_group_name 146 The name of the cache parameter group to associate with this cache 147 cluster. If this argument is omitted, the default cache parameter group 148 for the specified engine will be used. 149 150 cache_security_group_names 151 A list of cache security group names to associate with this cache 152 cluster. Use this parameter only when you are creating a cluster 153 outside of a VPC. 154 155 replication_group_id 156 The replication group to which this cache cluster should belong. If 157 this parameter is specified, the cache cluster will be added to the 158 specified replication group as a read replica; otherwise, the cache 159 cluster will be a standalone primary that is not part of any 160 replication group. 161 162 auto_minor_version_upgrade 163 Determines whether minor engine upgrades will be applied automatically 164 to the cache cluster during the maintenance window. A value of True 165 allows these upgrades to occur; False disables automatic upgrades. 166 167 security_group_ids 168 One or more VPC security groups associated with the cache cluster. Use 169 this parameter only when you are creating a cluster in a VPC. 170 171 cache_subnet_group_name 172 The name of the cache subnet group to be used for the cache cluster. 173 Use this parameter only when you are creating a cluster in a VPC. 174 175 engine_version 176 The version number of the cache engine to be used for this cluster. 177 178 notification_topic_arn 179 The Amazon Resource Name (ARN) of the Amazon Simple Notification 180 Service (SNS) topic to which notifications will be sent. The Amazon SNS 181 topic owner must be the same as the cache cluster owner. 182 183 preferred_maintenance_window 184 The weekly time range (in UTC) during which system maintenance can 185 occur. Example: sun:05:00-sun:09:00 186 187 wait 188 Boolean. Wait for confirmation from boto that the cluster is in the 189 available state. 190 191 region 192 Region to connect to. 193 194 key 195 Secret key to be used. 196 197 keyid 198 Access key to be used. 199 200 profile 201 A dict with region, key and keyid, or a pillar key (string) 202 that contains a dict with region, key and keyid. 203 """ 204 ret = {"name": name, "result": True, "comment": "", "changes": {}} 205 if cache_security_group_names and cache_subnet_group_name: 206 _subnet_group = __salt__["boto_elasticache.get_cache_subnet_group"]( 207 cache_subnet_group_name, region, key, keyid, profile 208 ) 209 vpc_id = _subnet_group["vpc_id"] 210 if not security_group_ids: 211 security_group_ids = [] 212 _security_group_ids = __salt__["boto_secgroup.convert_to_group_ids"]( 213 groups=cache_security_group_names, 214 vpc_id=vpc_id, 215 region=region, 216 key=key, 217 keyid=keyid, 218 profile=profile, 219 ) 220 security_group_ids.extend(_security_group_ids) 221 cache_security_group_names = None 222 config = __salt__["boto_elasticache.get_config"](name, region, key, keyid, profile) 223 if config is None: 224 msg = "Failed to retrieve cache cluster info from AWS." 225 ret["comment"] = msg 226 ret["result"] = None 227 return ret 228 elif not config: 229 if __opts__["test"]: 230 msg = "Cache cluster {} is set to be created.".format(name) 231 ret["comment"] = msg 232 ret["result"] = None 233 return ret 234 created = __salt__["boto_elasticache.create"]( 235 name=name, 236 num_cache_nodes=num_cache_nodes, 237 cache_node_type=cache_node_type, 238 engine=engine, 239 replication_group_id=replication_group_id, 240 engine_version=engine_version, 241 cache_parameter_group_name=cache_parameter_group_name, 242 cache_subnet_group_name=cache_subnet_group_name, 243 cache_security_group_names=cache_security_group_names, 244 security_group_ids=security_group_ids, 245 preferred_availability_zone=preferred_availability_zone, 246 preferred_maintenance_window=preferred_maintenance_window, 247 port=port, 248 notification_topic_arn=notification_topic_arn, 249 auto_minor_version_upgrade=auto_minor_version_upgrade, 250 wait=wait, 251 region=region, 252 key=key, 253 keyid=keyid, 254 profile=profile, 255 ) 256 if created: 257 ret["changes"]["old"] = None 258 config = __salt__["boto_elasticache.get_config"]( 259 name, region, key, keyid, profile 260 ) 261 ret["changes"]["new"] = config 262 else: 263 ret["result"] = False 264 ret["comment"] = "Failed to create {} cache cluster.".format(name) 265 return ret 266 # TODO: support modification of existing elasticache clusters 267 else: 268 ret["comment"] = "Cache cluster {} is present.".format(name) 269 return ret 270 271 272def subnet_group_present( 273 name, 274 subnet_ids=None, 275 subnet_names=None, 276 description=None, 277 tags=None, 278 region=None, 279 key=None, 280 keyid=None, 281 profile=None, 282): 283 """ 284 Ensure ElastiCache subnet group exists. 285 286 .. versionadded:: 2015.8.0 287 288 name 289 The name for the ElastiCache subnet group. This value is stored as a lowercase string. 290 291 subnet_ids 292 A list of VPC subnet IDs for the cache subnet group. Exclusive with subnet_names. 293 294 subnet_names 295 A list of VPC subnet names for the cache subnet group. Exclusive with subnet_ids. 296 297 description 298 Subnet group description. 299 300 tags 301 A list of tags. 302 303 region 304 Region to connect to. 305 306 key 307 Secret key to be used. 308 309 keyid 310 Access key to be used. 311 312 profile 313 A dict with region, key and keyid, or a pillar key (string) that 314 contains a dict with region, key and keyid. 315 """ 316 ret = {"name": name, "result": True, "comment": "", "changes": {}} 317 318 exists = __salt__["boto_elasticache.subnet_group_exists"]( 319 name=name, tags=tags, region=region, key=key, keyid=keyid, profile=profile 320 ) 321 if not exists: 322 if __opts__["test"]: 323 ret["comment"] = "Subnet group {} is set to be created.".format(name) 324 ret["result"] = None 325 return ret 326 created = __salt__["boto_elasticache.create_subnet_group"]( 327 name=name, 328 subnet_ids=subnet_ids, 329 subnet_names=subnet_names, 330 description=description, 331 tags=tags, 332 region=region, 333 key=key, 334 keyid=keyid, 335 profile=profile, 336 ) 337 if not created: 338 ret["result"] = False 339 ret["comment"] = "Failed to create {} subnet group.".format(name) 340 return ret 341 ret["changes"]["old"] = None 342 ret["changes"]["new"] = name 343 ret["comment"] = "Subnet group {} created.".format(name) 344 return ret 345 ret["comment"] = "Subnet group present." 346 return ret 347 348 349def cache_cluster_absent(*args, **kwargs): 350 return absent(*args, **kwargs) 351 352 353def absent(name, wait=True, region=None, key=None, keyid=None, profile=None): 354 """ 355 Ensure the named elasticache cluster is deleted. 356 357 name 358 Name of the cache cluster. 359 360 wait 361 Boolean. Wait for confirmation from boto that the cluster is in the 362 deleting state. 363 364 region 365 Region to connect to. 366 367 key 368 Secret key to be used. 369 370 keyid 371 Access key to be used. 372 373 profile 374 A dict with region, key and keyid, or a pillar key (string) 375 that contains a dict with region, key and keyid. 376 """ 377 ret = {"name": name, "result": True, "comment": "", "changes": {}} 378 379 is_present = __salt__["boto_elasticache.exists"](name, region, key, keyid, profile) 380 381 if is_present: 382 if __opts__["test"]: 383 ret["comment"] = "Cache cluster {} is set to be removed.".format(name) 384 ret["result"] = None 385 return ret 386 deleted = __salt__["boto_elasticache.delete"]( 387 name, wait, region, key, keyid, profile 388 ) 389 if deleted: 390 ret["changes"]["old"] = name 391 ret["changes"]["new"] = None 392 else: 393 ret["result"] = False 394 ret["comment"] = "Failed to delete {} cache cluster.".format(name) 395 else: 396 ret["comment"] = "{} does not exist in {}.".format(name, region) 397 return ret 398 399 400def replication_group_present(*args, **kwargs): 401 return creategroup(*args, **kwargs) 402 403 404def creategroup( 405 name, 406 primary_cluster_id, 407 replication_group_description, 408 wait=None, 409 region=None, 410 key=None, 411 keyid=None, 412 profile=None, 413): 414 """ 415 Ensure the a replication group is create. 416 417 name 418 Name of replication group 419 420 wait 421 Waits for the group to be available 422 423 primary_cluster_id 424 Name of the master cache node 425 426 replication_group_description 427 Description for the group 428 429 region 430 Region to connect to. 431 432 key 433 Secret key to be used. 434 435 keyid 436 Access key to be used. 437 438 profile 439 A dict with region, key and keyid, or a pillar key (string) 440 that contains a dict with region, key and keyid. 441 """ 442 ret = {"name": name, "result": None, "comment": "", "changes": {}} 443 is_present = __salt__["boto_elasticache.group_exists"]( 444 name, region, key, keyid, profile 445 ) 446 if not is_present: 447 if __opts__["test"]: 448 ret["comment"] = "Replication {} is set to be created.".format(name) 449 ret["result"] = None 450 created = __salt__["boto_elasticache.create_replication_group"]( 451 name, 452 primary_cluster_id, 453 replication_group_description, 454 wait, 455 region, 456 key, 457 keyid, 458 profile, 459 ) 460 if created: 461 config = __salt__["boto_elasticache.describe_replication_group"]( 462 name, region, key, keyid, profile 463 ) 464 ret["changes"]["old"] = None 465 ret["changes"]["new"] = config 466 ret["result"] = True 467 else: 468 ret["result"] = False 469 ret["comment"] = "Failed to create {} replication group.".format(name) 470 else: 471 ret["comment"] = "{} replication group exists .".format(name) 472 ret["result"] = True 473 return ret 474 475 476def subnet_group_absent( 477 name, tags=None, region=None, key=None, keyid=None, profile=None 478): 479 ret = {"name": name, "result": True, "comment": "", "changes": {}} 480 481 exists = __salt__["boto_elasticache.subnet_group_exists"]( 482 name=name, tags=tags, region=region, key=key, keyid=keyid, profile=profile 483 ) 484 if not exists: 485 ret["result"] = True 486 ret["comment"] = "{} ElastiCache subnet group does not exist.".format(name) 487 return ret 488 489 if __opts__["test"]: 490 ret["comment"] = "ElastiCache subnet group {} is set to be removed.".format( 491 name 492 ) 493 ret["result"] = None 494 return ret 495 deleted = __salt__["boto_elasticache.delete_subnet_group"]( 496 name, region, key, keyid, profile 497 ) 498 if not deleted: 499 ret["result"] = False 500 ret["comment"] = "Failed to delete {} ElastiCache subnet group.".format(name) 501 return ret 502 ret["changes"]["old"] = name 503 ret["changes"]["new"] = None 504 ret["comment"] = "ElastiCache subnet group {} deleted.".format(name) 505 return ret 506 507 508def replication_group_absent( 509 name, tags=None, region=None, key=None, keyid=None, profile=None 510): 511 ret = {"name": name, "result": True, "comment": "", "changes": {}} 512 513 exists = __salt__["boto_elasticache.group_exists"]( 514 name=name, region=region, key=key, keyid=keyid, profile=profile 515 ) 516 if not exists: 517 ret["result"] = True 518 ret["comment"] = "{} ElastiCache replication group does not exist.".format(name) 519 log.info(ret["comment"]) 520 return ret 521 522 if __opts__["test"]: 523 ret[ 524 "comment" 525 ] = "ElastiCache replication group {} is set to be removed.".format(name) 526 ret["result"] = True 527 return ret 528 deleted = __salt__["boto_elasticache.delete_replication_group"]( 529 name, region, key, keyid, profile 530 ) 531 if not deleted: 532 ret["result"] = False 533 log.error(ret["comment"]) 534 ret["comment"] = "Failed to delete {} ElastiCache replication group.".format( 535 name 536 ) 537 return ret 538 ret["changes"]["old"] = name 539 ret["changes"]["new"] = None 540 ret["comment"] = "ElastiCache replication group {} deleted.".format(name) 541 log.info(ret["comment"]) 542 return ret 543