From 041a4872fef82016c21ffc9148a20a7086d95bda Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Mon, 10 Jun 2013 12:17:31 -0400 Subject: [PATCH 1/9] Amazon RDS module --- library/cloud/rds | 479 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 library/cloud/rds diff --git a/library/cloud/rds b/library/cloud/rds new file mode 100644 index 0000000000..396928d917 --- /dev/null +++ b/library/cloud/rds @@ -0,0 +1,479 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: rds +short_description: create or delete an Amazon rds instance +description: + - Creates or deletes rds instances. When creating an instance it can be either a new instance or a read-only replica of an existing instance. This module has a dependency on python-boto >= 2.5. +options: + action: + description: + - Specifies the action to take. Must be one of: create | replicate | delete + required: true + default: null + aliases: [] + instance_name: + description: + - Database instance identifier. + required: true + default: null + aliases: [] + source_instance: + description: + - Name of the database to replicate. Used only when action=replicate. + required: false + default: null + aliases: [] + db_engine: + description: + - The type of database. Used only when action=create. Must be one of: MySQL | oracle-se1 | oracle-se | oracle-ee | sqlserver-ee | sqlserver-se | sqlserver-ex | sqlserver-web + required: false + default: null + aliases: [] + size: + description: + - Size in gigabytes of the initial storage for the DB instance. Used only when action=create. + required: false + default: null + aliases: [] + instance_type: + description: + - The instance type of the database. Must be specified when action=create. Optional when action=replicate. If not specified then the replica inherits the same instance type as the source instance. Must be one of: db.t1.micro | db.m1.small | db.m1.medium | db.m1.large | db.m1.xlarge | db.m2.xlarge | db.m2.2xlarge | db.m2.4xlarge + required: false + default: null + aliases: [] + username: + description: + - Master database username. Used only when action=create. + required: false + default: null + aliases: [] + password: + description: + - Password for the master database username. Used only when action=create. + required: false + default: null + aliases: [] + ec2_region: + description: + - the EC2 region to use. If not specified then the EC2_REGION environment variable is used. If neither exist then the AWS is queried for the region that the host invoking the module is located in. + required: true + default: null + aliases: [] + db_name: + description: + - Name of a database to create within the instance. If not specified then no database is created. Used only when action=create. + required: false + default: null + aliases: [] + engine_version: + description: + - Version number of the database engine to use. Used only when action=create. If not specified then the current Amazon RDS default engine version is used. + required: false + default: null + aliases: [] + parameter_group: + description: + - Name of the DB parameter group to associate with this instance. If omitted then the RDS default DBParameterGroup will be used. Used only when action=create. + required: false + default: null + aliases: [] + license_model: + description: + - The license model for this DB instance. Used only when action=create. Must be one of: license-included | bring-your-own-license | general-public-license + required: false + default: null + aliases: [] + multi_zone: + description: + - Specifies if this is a Multi-availability-zone deployment. Can not be used in conjunction with zone parameter. Used only when action=create. Valid values: true | false + required: false + default: null + aliases: [] + iops: + description: + - Specifies the number of IOPS for the instance. Used only when action=create. Must be an integer greater than 1000. + required: false + default: null + aliases: [] + security_groups: + description: + - Comma separated list of one or more security groups. Used only when action=create. If a subnet is specified then this is treated as a list of VPC security groups. + required: false + default: null + aliases: [] + port: + description: + - Port number that the DB instance uses for connections. Defaults to 3306 for mysql, 1521 for Oracle, 1443 for SQL Server. Used only when action=create or action=replicate. + required: false + default: null + aliases: [] + upgrade: + description: + - Indicates that minor version upgrades should be applied automatically. Used only when action=create or action=replicate. + required: false + default: no + choices: [ "yes", "no" ] + aliases: [] + option_group: + description: + - The name of the option group to use. If not specified then the default option group is used. Used only when action=create. + required: false + default: null + aliases: [] + maint_window: + description: + - Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi. (Example: Mon:22:00-Mon:23:15) If not specified then a random maintenance window is assigned. Used only when action=create. + required: false + default: null + aliases: [] + backup_window: + description: + - Backup window in format of hh24:mi-hh24:mi. If not specified then a random backup window is assigned. Used only when action=create. + required: false + default: null + aliases: [] + backup_retention: + description: + - Number of days backups are retained. Set to 0 to disable backups. Default is 1 day. Valid range: 0-35. Used only when action=create. + required: false + default: null + aliases: [] + zone: + description: + - availability zone in which to launch the instance. Used only when action=create or action=replicate. + required: false + default: null + aliases: [] + subnet: + description: + - VPC subnet group. If specified then a VPC instance is created. Used only when action=create. + required: false + default: null + aliases: [] + snapshot: + description: + - Name of final snapshot to take when deleting an instance. If no snapshot name is provided then no snapshot is taken. Used only when action=delete. + required: false + default: null + aliases: [] + ec2_secret_key: + description: + - EC2 secret key. If not specified then the EC2_SECRET_KEY environment variable is used. + required: false + default: null + aliases: [] + ec2_access_key: + description: + - EC2 access key. If not specified then the EC2_ACCESS_KEY environment variable is used. + required: false + default: null + aliases: [] + wait: + description: + - When action=create or action=replicate, wait for the database to enter the 'available' state. When action=delete wait for the database to be terminated. + required: false + default: "no" + choices: [ "yes", "no" ] + aliases: [] + wait_timeout: + description: + - how long before wait gives up, in seconds + default: 300 + aliases: [] +requirements: [ "boto" ] +author: Bruce Pennypacker +''' + +EXAMPLES = ''' +# Basic mysql provisioning example + action: rds + action=create + instance_name=new_database + db_engine=MySQL + size=10 + instance_type=db.m1.small + username=mysql_admin + password=1nsecure + +# Create a read-only replica and wait for it to become available + action: rds + action=replicate + instance_name=new_database_replica + source_instance=new_database + wait=yes + wait_timeout=600 + +# Delete an instance, but create a snapshot before doing so + action: rds + action=delete + instance_name=new_database + snapshot=new_database_snapshot +''' + +import sys +import time + +AWS_REGIONS = ['ap-northeast-1', + 'ap-southeast-1', + 'ap-southeast-2', + 'eu-west-1', + 'sa-east-1', + 'us-east-1', + 'us-west-1', + 'us-west-2'] + +try: + import boto.rds +except ImportError: + print "failed=True msg='boto required for this module'" + sys.exit(1) + +try: + import urllib2 +except ImportError: + print "failed=True msg='urllib2 required for this module'" + sys.exit(1) + +def main(): + module = AnsibleModule( + argument_spec = dict( + action = dict(choices=['create', 'replicate', 'delete'], required=True), + instance_name = dict(required=True), + source_instance = dict(required=False), + db_engine = dict(choices=['MySQL', 'oracle-se1', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex', 'sqlserver-web'], required=False), + size = dict(required=False), + instance_type = dict(aliases=['type'], choices=['db.t1.micro', 'db.m1.small', 'db.m1.medium', 'db.m1.large', 'db.m1.xlarge', 'db.m2.xlarge', 'db.m2.2xlarge', 'db.m2.4xlarge'], required=False), + username = dict(required=False), + password = dict(no_log=True, required=False), + db_name = dict(required=False), + engine_version = dict(required=False), + parameter_group = dict(required=False), + license_model = dict(choices=['license-included', 'bring-your-own-license', 'general-public-license'], required=False), + multi_zone = dict(choices=BOOLEANS, default=False), + iops = dict(required=False), + security_groups = dict(required=False), + port = dict(required=False), + upgrade = dict(choices=BOOLEANS, default=False), + option_group = dict(required=False), + maint_window = dict(required=False), + backup_window = dict(required=False), + backup_retention = dict(required=False), + ec2_region = dict(aliases=['EC2_REGION'], choices=AWS_REGIONS, required=False), + zone = dict(required=False), + subnet = dict(required=False), + ec2_secret_key = dict(aliases=['EC2_SECRET_KEY'], no_log=True, required=False), + ec2_access_key = dict(aliases=['EC2_ACCESS_KEY'], required=False), + wait = dict(choices=BOOLEANS, default=False), + wait_timeout = dict(default=300), + snapshot = dict(required=False), + ) + ) + + action = module.params.get('action') + instance_name = module.params.get('instance_name'); + source_instance = module.params.get('source_instance'); + db_engine = module.params.get('db_engine'); + size = module.params.get('size'); + instance_type = module.params.get('instance_type'); + username = module.params.get('username'); + password = module.params.get('password'); + db_name = module.params.get('db_name'); + engine_version = module.params.get('engine_version'); + parameter_group = module.params.get('parameter_group'); + license_model = module.params.get('license_model'); + multi_zone = module.params.get('multi_zone'); + iops = module.params.get('iops'); + security_groups = module.params.get('security_groups'); + port = module.params.get('port'); + upgrade = module.params.get('upgrade'); + option_group = module.params.get('option_group'); + maint_window = module.params.get('maint_window'); + subnet = module.params.get('subnet'); + backup_window = module.params.get('backup_window'); + backup_retention = module.params.get('module_retention'); + ec2_region = module.params.get('ec2_region') + zone = module.params.get('zone') + ec2_secret_key = module.params.get('ec2_secret_key') + ec2_access_key = module.params.get('ec2_access_key') + wait = module.params.get('wait') + wait_timeout = int(module.params.get('wait_timeout')) + snapshot = module.params.get('snapshot') + + # allow environment variables to be used if ansible vars aren't set + if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ: + ec2_secret_key = os.environ['EC2_SECRET_KEY'] + if not ec2_access_key and 'EC2_ACCESS_KEY' in os.environ: + ec2_access_key = os.environ['EC2_ACCESS_KEY'] + if not ec2_region and 'EC2_REGION' in os.environ: + ec2_region = os.environ['EC2_REGION'] + + # If region isn't set either as a param or environ variable then + # look up the current region via the AWS URL + if not ec2_region: + response = urllib2.urlopen('http://169.254.169.254/latest/meta-data/placement/availability-zone') + az = response.read() + for r in AWS_REGIONS: + if az.startswith(r): + ec2_region = r + break + + if not ec2_region: + module.fail_json(msg = str("ec2_region not specified and unable to determine region from AWS.")) + + # connect to the rds endpoint + try: + conn = boto.rds.connect_to_region(ec2_region, aws_access_key_id=ec2_access_key, aws_secret_access_key=ec2_secret_key) + except boto.exception.BotoServerError, e: + module.fail_json(msg = e.error_message) + + # Validate parameters for each action + if action == 'create': + required_vars = [ 'instance_name', 'db_engine', 'size', 'instance_type', 'username', 'password' ] + invalid_vars = [ 'source_instance', 'snapshot' ] + elif action == 'replicate': + required_vars = [ 'instance_name', 'source_instance' ] + invalid_vars = [ 'db_engine', 'size', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'subnet', 'snapshot'] + elif action == 'delete': + required_vars = [ 'instance_name' ] + invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone' ] + + for v in required_vars: + if not module.params.get(v): + module.fail_json(msg = str("Variable %s required for %s action" % (v, action))) + + for v in invalid_vars: + if module.params.get(v): + module.fail_json(msg = str("Variable %s invalid for %s action" % (v, action))) + + # Package up the optional parameters + params = {} + + if db_engine: + params["engine"] = db_engine + + if port: + params["port"] = port + + if db_name: + params["db_name"] = db_name + + if parameter_group: + params["param_group"] = parameter_group + + if zone: + params["availability_zone"] = zone + + if maint_window: + params["preferred_maintenance_window"] = maint_window + + if backup_window: + params["preferred_backup_window"] = backup_window + + if backup_retention: + params["backup_retention_period"] = backup_retention + + if multi_zone: + params["multi_az"] = multi_zone + + if engine_version: + params["engine_version"] = engine_version + + if upgrade: + params["auto_minor_version_upgrade"] = upgrade + + if subnet: + params["db_subnet_group_name"] = subnet + + if license_model: + params["license_model"] = license_model + + if option_group: + params["option_group_name"] = option_group + + if iops: + params["iops"] = iops + + if security_groups: + params["security_groups"] = security_groups.split(',') + + try: + if action == 'create': + db = conn.create_dbinstance(instance_name, size, instance_type, username, password, **params) + elif action == 'replicate': + if instance_type: + params["instance_class"] = instance_type + db = conn.create_dbinstance_read_replica(instance_name, source_instance, **params) + elif action == 'delete': + if snapshot: + params["skip_final_snapshot"] = False + params["final_snapshot_id"] = snapshot + db = conn.delete_dbinstance(instance_name, **params) + + except boto.exception.BotoServerError, e: + module.fail_json(msg = e.error_message) + + # If we're not waiting for a delete to complete then we're all done + # so just return + if action == 'delete' and not wait: + module.exit_json(changed=True) + + instances = conn.get_all_dbinstances(instance_name) + my_inst = instances[0] + + # Wait for the instance to be available if requested + if wait: + try: + wait_timeout = time.time() + wait_timeout + time.sleep(5) + + while wait_timeout > time.time() and my_inst.status != 'available': + time.sleep(5) + if wait_timeout <= time.time(): + module.fail_json(msg = "Timeout waiting for database instance %s" % instance_name) + instances = conn.get_all_dbinstances(instance_name) + my_inst = instances[0] + except boto.exception.BotoServerError, e: + # If we're waiting for an instance to be deleted then + # get_all_dbinstances will eventually throw a + # DBInstanceNotFound error. + if action == 'delete' and e.error_code == 'DBInstanceNotFound': + module.exit_json(changed=True) + else: + module.fail_json(msg = e.error_message) + + # If we got here then pack up all the instance details to send + # back to ansible + d = { + 'id': my_inst.id, + 'create_time': my_inst.create_time, + 'status': my_inst.status, + 'endpoint': my_inst.endpoint[0], + 'port': my_inst.endpoint[1], + 'availability_zone': my_inst.availability_zone, + 'backup_retention': my_inst.backup_retention_period, + 'backup_window': my_inst.preferred_backup_window, + 'maintenance_window': my_inst.preferred_maintenance_window, + } + + module.exit_json(changed=True, instance=d) + +# this is magic, see lib/ansible/module_common.py +#<> + +main() From d62beb2df523fa42fc3aae9991873bbd8f9d982f Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Mon, 10 Jun 2013 15:49:38 -0400 Subject: [PATCH 2/9] fixed bug in delete action - skip_final_snapshot must always be set --- library/cloud/rds | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/cloud/rds b/library/cloud/rds index 396928d917..6c66ac5a7a 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -423,6 +423,9 @@ def main(): if snapshot: params["skip_final_snapshot"] = False params["final_snapshot_id"] = snapshot + else: + params["skip_final_snapshot"] = True + db = conn.delete_dbinstance(instance_name, **params) except boto.exception.BotoServerError, e: From 26c37d7c071437d1a469de72728dc665c7763c0f Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Tue, 11 Jun 2013 10:45:50 -0400 Subject: [PATCH 3/9] added 'facts' action --- library/cloud/rds | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/library/cloud/rds b/library/cloud/rds index 6c66ac5a7a..87d12c898b 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -23,7 +23,7 @@ description: options: action: description: - - Specifies the action to take. Must be one of: create | replicate | delete + - Specifies the action to take. Must be one of: create | replicate | delete | facts required: true default: null aliases: [] @@ -224,6 +224,13 @@ EXAMPLES = ''' action=delete instance_name=new_database snapshot=new_database_snapshot + +# Get facts about an instance + action: rds + action=facts + instance_name=new_database + register: new_database_facts + ''' import sys @@ -253,7 +260,7 @@ except ImportError: def main(): module = AnsibleModule( argument_spec = dict( - action = dict(choices=['create', 'replicate', 'delete'], required=True), + action = dict(choices=['create', 'replicate', 'delete', 'facts'], required=True), instance_name = dict(required=True), source_instance = dict(required=False), db_engine = dict(choices=['MySQL', 'oracle-se1', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex', 'sqlserver-web'], required=False), @@ -351,7 +358,10 @@ def main(): invalid_vars = [ 'db_engine', 'size', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'subnet', 'snapshot'] elif action == 'delete': required_vars = [ 'instance_name' ] - invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone' ] + invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone' , 'source_instance'] + elif action == 'facts': + required_vars = [ 'instance_name' ] + invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone', 'wait', 'source_instance' ] for v in required_vars: if not module.params.get(v): @@ -428,6 +438,9 @@ def main(): db = conn.delete_dbinstance(instance_name, **params) + # Don't do anything for the 'facts' action since we'll just drop down + # to get_all_dbinstances below to collect the facts + except boto.exception.BotoServerError, e: module.fail_json(msg = e.error_message) @@ -442,7 +455,7 @@ def main(): # Wait for the instance to be available if requested if wait: try: - wait_timeout = time.time() + wait_timeout + wait_timeout = time.time() + wait_timeout time.sleep(5) while wait_timeout > time.time() and my_inst.status != 'available': From c9e373dd7d22ee99dae715cba50d8b497db98ac6 Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Tue, 11 Jun 2013 13:11:11 -0400 Subject: [PATCH 4/9] Added modify action --- library/cloud/rds | 48 ++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/library/cloud/rds b/library/cloud/rds index 87d12c898b..551d52e925 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -23,7 +23,7 @@ description: options: action: description: - - Specifies the action to take. Must be one of: create | replicate | delete | facts + - Specifies the action to take. Must be one of: create | replicate | delete | facts | modify required: true default: null aliases: [] @@ -47,13 +47,13 @@ options: aliases: [] size: description: - - Size in gigabytes of the initial storage for the DB instance. Used only when action=create. + - Size in gigabytes of the initial storage for the DB instance. Used only when action=create or action=modify. required: false default: null aliases: [] instance_type: description: - - The instance type of the database. Must be specified when action=create. Optional when action=replicate. If not specified then the replica inherits the same instance type as the source instance. Must be one of: db.t1.micro | db.m1.small | db.m1.medium | db.m1.large | db.m1.xlarge | db.m2.xlarge | db.m2.2xlarge | db.m2.4xlarge + - The instance type of the database. Must be specified when action=create. Optional when action=replicate or action=modify. If not specified then the replica inherits the same instance type as the source instance. Must be one of: db.t1.micro | db.m1.small | db.m1.medium | db.m1.large | db.m1.xlarge | db.m2.xlarge | db.m2.2xlarge | db.m2.4xlarge required: false default: null aliases: [] @@ -65,7 +65,7 @@ options: aliases: [] password: description: - - Password for the master database username. Used only when action=create. + - Password for the master database username. Used only when action=create or action=modify. required: false default: null aliases: [] @@ -89,7 +89,7 @@ options: aliases: [] parameter_group: description: - - Name of the DB parameter group to associate with this instance. If omitted then the RDS default DBParameterGroup will be used. Used only when action=create. + - Name of the DB parameter group to associate with this instance. If omitted then the RDS default DBParameterGroup will be used. Used only when action=create or action=modify. required: false default: null aliases: [] @@ -101,19 +101,19 @@ options: aliases: [] multi_zone: description: - - Specifies if this is a Multi-availability-zone deployment. Can not be used in conjunction with zone parameter. Used only when action=create. Valid values: true | false + - Specifies if this is a Multi-availability-zone deployment. Can not be used in conjunction with zone parameter. Used only when action=create or action=modify. Valid values: true | false required: false default: null aliases: [] iops: description: - - Specifies the number of IOPS for the instance. Used only when action=create. Must be an integer greater than 1000. + - Specifies the number of IOPS for the instance. Used only when action=create or action=modify. Must be an integer greater than 1000. required: false default: null aliases: [] security_groups: description: - - Comma separated list of one or more security groups. Used only when action=create. If a subnet is specified then this is treated as a list of VPC security groups. + - Comma separated list of one or more security groups. Used only when action=create or action=modify. If a subnet is specified then this is treated as a list of VPC security groups. required: false default: null aliases: [] @@ -138,19 +138,19 @@ options: aliases: [] maint_window: description: - - Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi. (Example: Mon:22:00-Mon:23:15) If not specified then a random maintenance window is assigned. Used only when action=create. + - Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi. (Example: Mon:22:00-Mon:23:15) If not specified then a random maintenance window is assigned. Used only when action=create or action=modify. required: false default: null aliases: [] backup_window: description: - - Backup window in format of hh24:mi-hh24:mi. If not specified then a random backup window is assigned. Used only when action=create. + - Backup window in format of hh24:mi-hh24:mi. If not specified then a random backup window is assigned. Used only when action=create or action=modify. required: false default: null aliases: [] backup_retention: description: - - Number of days backups are retained. Set to 0 to disable backups. Default is 1 day. Valid range: 0-35. Used only when action=create. + - Number of days backups are retained. Set to 0 to disable backups. Default is 1 day. Valid range: 0-35. Used only when action=create or action=modify. required: false default: null aliases: [] @@ -186,7 +186,7 @@ options: aliases: [] wait: description: - - When action=create or action=replicate, wait for the database to enter the 'available' state. When action=delete wait for the database to be terminated. + - When action=create, replicate, or modify then wait for the database to enter the 'available' state. When action=delete wait for the database to be terminated. required: false default: "no" choices: [ "yes", "no" ] @@ -196,6 +196,12 @@ options: - how long before wait gives up, in seconds default: 300 aliases: [] + apply_immediately: + description: + - Used only when action=modify. If enabled, the modifications will be applied as soon as possible rather than waiting for the next preferred maintenance window. + default: no + choices: [ "yes", "no" ] + aliases: [] requirements: [ "boto" ] author: Bruce Pennypacker ''' @@ -260,7 +266,7 @@ except ImportError: def main(): module = AnsibleModule( argument_spec = dict( - action = dict(choices=['create', 'replicate', 'delete', 'facts'], required=True), + action = dict(choices=['create', 'replicate', 'delete', 'facts', 'modify'], required=True), instance_name = dict(required=True), source_instance = dict(required=False), db_engine = dict(choices=['MySQL', 'oracle-se1', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex', 'sqlserver-web'], required=False), @@ -289,6 +295,7 @@ def main(): wait = dict(choices=BOOLEANS, default=False), wait_timeout = dict(default=300), snapshot = dict(required=False), + apply_immediately = dict(choices=BOOLEANS, default=False), ) ) @@ -321,6 +328,7 @@ def main(): wait = module.params.get('wait') wait_timeout = int(module.params.get('wait_timeout')) snapshot = module.params.get('snapshot') + apply_immediately = module.params.get('apply_immediately') # allow environment variables to be used if ansible vars aren't set if not ec2_secret_key and 'EC2_SECRET_KEY' in os.environ: @@ -352,16 +360,19 @@ def main(): # Validate parameters for each action if action == 'create': required_vars = [ 'instance_name', 'db_engine', 'size', 'instance_type', 'username', 'password' ] - invalid_vars = [ 'source_instance', 'snapshot' ] + invalid_vars = [ 'source_instance', 'snapshot', 'apply_immediately' ] elif action == 'replicate': required_vars = [ 'instance_name', 'source_instance' ] - invalid_vars = [ 'db_engine', 'size', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'subnet', 'snapshot'] + invalid_vars = [ 'db_engine', 'size', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'subnet', 'snapshot', 'apply_immediately' ] elif action == 'delete': required_vars = [ 'instance_name' ] - invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone' , 'source_instance'] + invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone' , 'source_instance', 'apply_immediately' ] elif action == 'facts': required_vars = [ 'instance_name' ] - invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone', 'wait', 'source_instance' ] + invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone', 'wait', 'source_instance' 'apply_immediately' ] + elif action == 'modify': + required_vars = [ 'instance_name' ] + invalid_vars = [ 'db_engine', 'username', 'db_name', 'engine_version', 'license_model', 'option_group', 'port', 'upgrade', 'subnet', 'zone', 'source_instance' ] for v in required_vars: if not module.params.get(v): @@ -437,6 +448,9 @@ def main(): params["skip_final_snapshot"] = True db = conn.delete_dbinstance(instance_name, **params) + elif action == 'modify': + params["apply_immediately"] = apply_immediately + db = conn.modify_dbinstance(instance_name, **params) # Don't do anything for the 'facts' action since we'll just drop down # to get_all_dbinstances below to collect the facts From 1ef29a36542e4fc8026c43eaba406eea7d9f1750 Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Wed, 12 Jun 2013 11:44:33 -0400 Subject: [PATCH 5/9] endpoint is available only when instance is available --- library/cloud/rds | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/library/cloud/rds b/library/cloud/rds index 551d52e925..3e3ca70516 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -493,13 +493,24 @@ def main(): 'id': my_inst.id, 'create_time': my_inst.create_time, 'status': my_inst.status, - 'endpoint': my_inst.endpoint[0], - 'port': my_inst.endpoint[1], 'availability_zone': my_inst.availability_zone, 'backup_retention': my_inst.backup_retention_period, 'backup_window': my_inst.preferred_backup_window, 'maintenance_window': my_inst.preferred_maintenance_window, + 'multi_zone': my_inst.multi_az, + 'instance_type': my_inst.instance_class, + 'username': my_inst.master_username, + 'iops': my_inst.iops } + + # Endpoint exists only if the instance is available + if my_inst.status == 'available': + d["endpoint"] = my_inst.endpoint[0] + d["port"] = my_inst.endpoint[1] + else: + d["endpoint"] = None + d["port"] = None + module.exit_json(changed=True, instance=d) From 14eda0d125796a332c8ca9b5400af2afd162b1c3 Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Wed, 12 Jun 2013 16:22:35 -0400 Subject: [PATCH 6/9] Added replication_source to the results --- library/cloud/rds | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/cloud/rds b/library/cloud/rds index 3e3ca70516..214011e318 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -511,7 +511,12 @@ def main(): d["endpoint"] = None d["port"] = None - + # ReadReplicaSourceDBInstanceIdentifier may or may not exist + try: + d["replication_source"] = my_inst.ReadReplicaSourceDBInstanceIdentifier + except Exception, e: + d["replication_source"] = None + module.exit_json(changed=True, instance=d) # this is magic, see lib/ansible/module_common.py From d25622f17ead044d7fcf9ec99e3c171ca7d267d6 Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Mon, 17 Jun 2013 09:39:45 -0400 Subject: [PATCH 7/9] changed 'action' to 'command'. Fixed a tab issue. --- library/cloud/rds | 94 +++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/library/cloud/rds b/library/cloud/rds index 214011e318..7d6e0e6ce7 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -21,7 +21,7 @@ short_description: create or delete an Amazon rds instance description: - Creates or deletes rds instances. When creating an instance it can be either a new instance or a read-only replica of an existing instance. This module has a dependency on python-boto >= 2.5. options: - action: + command: description: - Specifies the action to take. Must be one of: create | replicate | delete | facts | modify required: true @@ -35,37 +35,37 @@ options: aliases: [] source_instance: description: - - Name of the database to replicate. Used only when action=replicate. + - Name of the database to replicate. Used only when command=replicate. required: false default: null aliases: [] db_engine: description: - - The type of database. Used only when action=create. Must be one of: MySQL | oracle-se1 | oracle-se | oracle-ee | sqlserver-ee | sqlserver-se | sqlserver-ex | sqlserver-web + - The type of database. Used only when command=create. Must be one of: MySQL | oracle-se1 | oracle-se | oracle-ee | sqlserver-ee | sqlserver-se | sqlserver-ex | sqlserver-web required: false default: null aliases: [] size: description: - - Size in gigabytes of the initial storage for the DB instance. Used only when action=create or action=modify. + - Size in gigabytes of the initial storage for the DB instance. Used only when command=create or command=modify. required: false default: null aliases: [] instance_type: description: - - The instance type of the database. Must be specified when action=create. Optional when action=replicate or action=modify. If not specified then the replica inherits the same instance type as the source instance. Must be one of: db.t1.micro | db.m1.small | db.m1.medium | db.m1.large | db.m1.xlarge | db.m2.xlarge | db.m2.2xlarge | db.m2.4xlarge + - The instance type of the database. Must be specified when command=create. Optional when command=replicate or command=modify. If not specified then the replica inherits the same instance type as the source instance. Must be one of: db.t1.micro | db.m1.small | db.m1.medium | db.m1.large | db.m1.xlarge | db.m2.xlarge | db.m2.2xlarge | db.m2.4xlarge required: false default: null aliases: [] username: description: - - Master database username. Used only when action=create. + - Master database username. Used only when command=create. required: false default: null aliases: [] password: description: - - Password for the master database username. Used only when action=create or action=modify. + - Password for the master database username. Used only when command=create or command=modify. required: false default: null aliases: [] @@ -77,98 +77,98 @@ options: aliases: [] db_name: description: - - Name of a database to create within the instance. If not specified then no database is created. Used only when action=create. + - Name of a database to create within the instance. If not specified then no database is created. Used only when command=create. required: false default: null aliases: [] engine_version: description: - - Version number of the database engine to use. Used only when action=create. If not specified then the current Amazon RDS default engine version is used. + - Version number of the database engine to use. Used only when command=create. If not specified then the current Amazon RDS default engine version is used. required: false default: null aliases: [] parameter_group: description: - - Name of the DB parameter group to associate with this instance. If omitted then the RDS default DBParameterGroup will be used. Used only when action=create or action=modify. + - Name of the DB parameter group to associate with this instance. If omitted then the RDS default DBParameterGroup will be used. Used only when command=create or command=modify. required: false default: null aliases: [] license_model: description: - - The license model for this DB instance. Used only when action=create. Must be one of: license-included | bring-your-own-license | general-public-license + - The license model for this DB instance. Used only when command=create. Must be one of: license-included | bring-your-own-license | general-public-license required: false default: null aliases: [] multi_zone: description: - - Specifies if this is a Multi-availability-zone deployment. Can not be used in conjunction with zone parameter. Used only when action=create or action=modify. Valid values: true | false + - Specifies if this is a Multi-availability-zone deployment. Can not be used in conjunction with zone parameter. Used only when command=create or command=modify. Valid values: true | false required: false default: null aliases: [] iops: description: - - Specifies the number of IOPS for the instance. Used only when action=create or action=modify. Must be an integer greater than 1000. + - Specifies the number of IOPS for the instance. Used only when command=create or command=modify. Must be an integer greater than 1000. required: false default: null aliases: [] security_groups: description: - - Comma separated list of one or more security groups. Used only when action=create or action=modify. If a subnet is specified then this is treated as a list of VPC security groups. + - Comma separated list of one or more security groups. Used only when command=create or command=modify. If a subnet is specified then this is treated as a list of VPC security groups. required: false default: null aliases: [] port: description: - - Port number that the DB instance uses for connections. Defaults to 3306 for mysql, 1521 for Oracle, 1443 for SQL Server. Used only when action=create or action=replicate. + - Port number that the DB instance uses for connections. Defaults to 3306 for mysql, 1521 for Oracle, 1443 for SQL Server. Used only when command=create or command=replicate. required: false default: null aliases: [] upgrade: description: - - Indicates that minor version upgrades should be applied automatically. Used only when action=create or action=replicate. + - Indicates that minor version upgrades should be applied automatically. Used only when command=create or command=replicate. required: false default: no choices: [ "yes", "no" ] aliases: [] option_group: description: - - The name of the option group to use. If not specified then the default option group is used. Used only when action=create. + - The name of the option group to use. If not specified then the default option group is used. Used only when command=create. required: false default: null aliases: [] maint_window: description: - - Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi. (Example: Mon:22:00-Mon:23:15) If not specified then a random maintenance window is assigned. Used only when action=create or action=modify. + - Maintenance window in format of ddd:hh24:mi-ddd:hh24:mi. (Example: Mon:22:00-Mon:23:15) If not specified then a random maintenance window is assigned. Used only when command=create or command=modify. required: false default: null aliases: [] backup_window: description: - - Backup window in format of hh24:mi-hh24:mi. If not specified then a random backup window is assigned. Used only when action=create or action=modify. + - Backup window in format of hh24:mi-hh24:mi. If not specified then a random backup window is assigned. Used only when command=create or command=modify. required: false default: null aliases: [] backup_retention: description: - - Number of days backups are retained. Set to 0 to disable backups. Default is 1 day. Valid range: 0-35. Used only when action=create or action=modify. + - Number of days backups are retained. Set to 0 to disable backups. Default is 1 day. Valid range: 0-35. Used only when command=create or command=modify. required: false default: null aliases: [] zone: description: - - availability zone in which to launch the instance. Used only when action=create or action=replicate. + - availability zone in which to launch the instance. Used only when command=create or command=replicate. required: false default: null aliases: [] subnet: description: - - VPC subnet group. If specified then a VPC instance is created. Used only when action=create. + - VPC subnet group. If specified then a VPC instance is created. Used only when command=create. required: false default: null aliases: [] snapshot: description: - - Name of final snapshot to take when deleting an instance. If no snapshot name is provided then no snapshot is taken. Used only when action=delete. + - Name of final snapshot to take when deleting an instance. If no snapshot name is provided then no snapshot is taken. Used only when command=delete. required: false default: null aliases: [] @@ -186,7 +186,7 @@ options: aliases: [] wait: description: - - When action=create, replicate, or modify then wait for the database to enter the 'available' state. When action=delete wait for the database to be terminated. + - When command=create, replicate, or modify then wait for the database to enter the 'available' state. When command=delete wait for the database to be terminated. required: false default: "no" choices: [ "yes", "no" ] @@ -198,7 +198,7 @@ options: aliases: [] apply_immediately: description: - - Used only when action=modify. If enabled, the modifications will be applied as soon as possible rather than waiting for the next preferred maintenance window. + - Used only when command=modify. If enabled, the modifications will be applied as soon as possible rather than waiting for the next preferred maintenance window. default: no choices: [ "yes", "no" ] aliases: [] @@ -209,7 +209,7 @@ author: Bruce Pennypacker EXAMPLES = ''' # Basic mysql provisioning example action: rds - action=create + command=create instance_name=new_database db_engine=MySQL size=10 @@ -219,7 +219,7 @@ EXAMPLES = ''' # Create a read-only replica and wait for it to become available action: rds - action=replicate + command=replicate instance_name=new_database_replica source_instance=new_database wait=yes @@ -227,13 +227,13 @@ EXAMPLES = ''' # Delete an instance, but create a snapshot before doing so action: rds - action=delete + command=delete instance_name=new_database snapshot=new_database_snapshot # Get facts about an instance action: rds - action=facts + command=facts instance_name=new_database register: new_database_facts @@ -266,7 +266,7 @@ except ImportError: def main(): module = AnsibleModule( argument_spec = dict( - action = dict(choices=['create', 'replicate', 'delete', 'facts', 'modify'], required=True), + command = dict(choices=['create', 'replicate', 'delete', 'facts', 'modify'], required=True), instance_name = dict(required=True), source_instance = dict(required=False), db_engine = dict(choices=['MySQL', 'oracle-se1', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex', 'sqlserver-web'], required=False), @@ -281,7 +281,7 @@ def main(): multi_zone = dict(choices=BOOLEANS, default=False), iops = dict(required=False), security_groups = dict(required=False), - port = dict(required=False), + port = dict(required=False), upgrade = dict(choices=BOOLEANS, default=False), option_group = dict(required=False), maint_window = dict(required=False), @@ -299,7 +299,7 @@ def main(): ) ) - action = module.params.get('action') + command = module.params.get('command') instance_name = module.params.get('instance_name'); source_instance = module.params.get('source_instance'); db_engine = module.params.get('db_engine'); @@ -357,30 +357,30 @@ def main(): except boto.exception.BotoServerError, e: module.fail_json(msg = e.error_message) - # Validate parameters for each action - if action == 'create': + # Validate parameters for each command + if command == 'create': required_vars = [ 'instance_name', 'db_engine', 'size', 'instance_type', 'username', 'password' ] invalid_vars = [ 'source_instance', 'snapshot', 'apply_immediately' ] - elif action == 'replicate': + elif command == 'replicate': required_vars = [ 'instance_name', 'source_instance' ] invalid_vars = [ 'db_engine', 'size', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'subnet', 'snapshot', 'apply_immediately' ] - elif action == 'delete': + elif command == 'delete': required_vars = [ 'instance_name' ] invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone' , 'source_instance', 'apply_immediately' ] - elif action == 'facts': + elif command == 'facts': required_vars = [ 'instance_name' ] invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone', 'wait', 'source_instance' 'apply_immediately' ] - elif action == 'modify': + elif command == 'modify': required_vars = [ 'instance_name' ] invalid_vars = [ 'db_engine', 'username', 'db_name', 'engine_version', 'license_model', 'option_group', 'port', 'upgrade', 'subnet', 'zone', 'source_instance' ] for v in required_vars: if not module.params.get(v): - module.fail_json(msg = str("Variable %s required for %s action" % (v, action))) + module.fail_json(msg = str("Variable %s required for %s command" % (v, command))) for v in invalid_vars: if module.params.get(v): - module.fail_json(msg = str("Variable %s invalid for %s action" % (v, action))) + module.fail_json(msg = str("Variable %s invalid for %s command" % (v, command))) # Package up the optional parameters params = {} @@ -434,13 +434,13 @@ def main(): params["security_groups"] = security_groups.split(',') try: - if action == 'create': + if command == 'create': db = conn.create_dbinstance(instance_name, size, instance_type, username, password, **params) - elif action == 'replicate': + elif command == 'replicate': if instance_type: params["instance_class"] = instance_type db = conn.create_dbinstance_read_replica(instance_name, source_instance, **params) - elif action == 'delete': + elif command == 'delete': if snapshot: params["skip_final_snapshot"] = False params["final_snapshot_id"] = snapshot @@ -448,11 +448,11 @@ def main(): params["skip_final_snapshot"] = True db = conn.delete_dbinstance(instance_name, **params) - elif action == 'modify': + elif command == 'modify': params["apply_immediately"] = apply_immediately db = conn.modify_dbinstance(instance_name, **params) - # Don't do anything for the 'facts' action since we'll just drop down + # Don't do anything for the 'facts' command since we'll just drop down # to get_all_dbinstances below to collect the facts except boto.exception.BotoServerError, e: @@ -460,7 +460,7 @@ def main(): # If we're not waiting for a delete to complete then we're all done # so just return - if action == 'delete' and not wait: + if command == 'delete' and not wait: module.exit_json(changed=True) instances = conn.get_all_dbinstances(instance_name) @@ -482,7 +482,7 @@ def main(): # If we're waiting for an instance to be deleted then # get_all_dbinstances will eventually throw a # DBInstanceNotFound error. - if action == 'delete' and e.error_code == 'DBInstanceNotFound': + if command == 'delete' and e.error_code == 'DBInstanceNotFound': module.exit_json(changed=True) else: module.fail_json(msg = e.error_message) From 898889d20e721348387dc04f781514327ea988b4 Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Mon, 17 Jun 2013 09:42:42 -0400 Subject: [PATCH 8/9] added try/except around conn.get_all_dbinstances --- library/cloud/rds | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/cloud/rds b/library/cloud/rds index 7d6e0e6ce7..bd2490a0fb 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -463,8 +463,12 @@ def main(): if command == 'delete' and not wait: module.exit_json(changed=True) - instances = conn.get_all_dbinstances(instance_name) - my_inst = instances[0] + try: + instances = conn.get_all_dbinstances(instance_name) + my_inst = instances[0] + except boto.exception.BotoServerError, e: + module.fail_json(msg = e.error_message) + # Wait for the instance to be available if requested if wait: From 9098908cc8cef2c8074185f5fbd662e51d53dc92 Mon Sep 17 00:00:00 2001 From: Bruce Pennypacker Date: Wed, 19 Jun 2013 09:35:50 -0400 Subject: [PATCH 9/9] Set master_password for modify command --- library/cloud/rds | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/cloud/rds b/library/cloud/rds index bd2490a0fb..b4e92efd82 100644 --- a/library/cloud/rds +++ b/library/cloud/rds @@ -372,6 +372,8 @@ def main(): invalid_vars = [ 'db_engine', 'size', 'instance_type', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'backup_window', 'backup_retention', 'port', 'upgrade', 'subnet', 'zone', 'wait', 'source_instance' 'apply_immediately' ] elif command == 'modify': required_vars = [ 'instance_name' ] + if password: + params["master_password"] = password invalid_vars = [ 'db_engine', 'username', 'db_name', 'engine_version', 'license_model', 'option_group', 'port', 'upgrade', 'subnet', 'zone', 'source_instance' ] for v in required_vars: