rds module: add snapshot capabilities
Add the ability to create snapshots and restore from them Make instance creation, deletion, restore, and snapshotting idempotent (really helps testing a playbook if you can run it multiple times)
This commit is contained in:
parent
643690ffec
commit
b65486734a
1 changed files with 134 additions and 67 deletions
|
@ -28,7 +28,7 @@ options:
|
|||
required: true
|
||||
default: null
|
||||
aliases: []
|
||||
choices: [ 'create', 'replicate', 'delete', 'facts', 'modify' , 'promote' ]
|
||||
choices: [ 'create', 'replicate', 'delete', 'facts', 'modify' , 'promote', 'snapshot', 'restore' ]
|
||||
instance_name:
|
||||
description:
|
||||
- Database instance identifier.
|
||||
|
@ -56,7 +56,7 @@ options:
|
|||
aliases: []
|
||||
instance_type:
|
||||
description:
|
||||
- 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.
|
||||
- The instance type of the database. Must be specified when command=create. Optional when command=replicate, command=modify or command=restore. If not specified then the replica inherits the same instance type as the source instance.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
|
@ -99,7 +99,7 @@ options:
|
|||
aliases: []
|
||||
license_model:
|
||||
description:
|
||||
- The license model for this DB instance. Used only when command=create.
|
||||
- The license model for this DB instance. Used only when command=create or command=restore.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
|
@ -162,7 +162,7 @@ options:
|
|||
aliases: []
|
||||
zone:
|
||||
description:
|
||||
- availability zone in which to launch the instance. Used only when command=create or command=replicate.
|
||||
- availability zone in which to launch the instance. Used only when command=create, command=replicate or command=restore.
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['aws_zone', 'ec2_zone']
|
||||
|
@ -174,7 +174,7 @@ options:
|
|||
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 command=delete.
|
||||
- Name of snapshot to take. When command=delete, if no snapshot name is provided then no snapshot is taken. Used only when command=delete or command=snapshot.
|
||||
required: false
|
||||
default: null
|
||||
aliases: []
|
||||
|
@ -192,7 +192,7 @@ options:
|
|||
aliases: [ 'ec2_access_key', 'access_key' ]
|
||||
wait:
|
||||
description:
|
||||
- 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.
|
||||
- When command=create, replicate, modify or restore 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" ]
|
||||
|
@ -277,10 +277,18 @@ except ImportError:
|
|||
print "failed=True msg='boto required for this module'"
|
||||
sys.exit(1)
|
||||
|
||||
def get_current_resource(conn, resource, command):
|
||||
# There will be exceptions but we want the calling code to handle them
|
||||
if command == 'snapshot':
|
||||
return conn.get_all_dbsnapshots(snapshot_id=resource)[0]
|
||||
else:
|
||||
return conn.get_all_dbinstances(resource)[0]
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
command = dict(choices=['create', 'replicate', 'delete', 'facts', 'modify', 'promote'], required=True),
|
||||
command = dict(choices=['create', 'replicate', 'delete', 'facts', 'modify', 'promote', 'snapshot', 'restore'], 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', 'postgres'], required=False),
|
||||
|
@ -400,6 +408,14 @@ def main():
|
|||
elif command == 'promote':
|
||||
required_vars = [ 'instance_name' ]
|
||||
invalid_vars = [ 'db_engine', 'size', 'username', 'password', 'db_name', 'engine_version', 'parameter_group', 'license_model', 'multi_zone', 'iops', 'security_groups', 'option_group', 'maint_window', 'subnet', 'source_instance', 'snapshot', 'apply_immediately', 'new_instance_name' ]
|
||||
|
||||
elif command == 'snapshot':
|
||||
required_vars = [ 'instance_name', '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', 'subnet', 'source_instance', 'apply_immediately', 'new_instance_name' ]
|
||||
|
||||
elif command == 'restore':
|
||||
required_vars = [ 'instance_name', 'snapshot', 'instance_type' ]
|
||||
invalid_vars = [ 'db_engine', 'db_name', 'usernmae', 'password', 'engine_version', 'option_group', 'source_instance', 'apply_immediately', 'new_instance_name' ]
|
||||
|
||||
for v in required_vars:
|
||||
if not module.params.get(v):
|
||||
|
@ -466,74 +482,108 @@ def main():
|
|||
if new_instance_name:
|
||||
params["new_instance_id"] = new_instance_name
|
||||
|
||||
try:
|
||||
if command == 'create':
|
||||
db = conn.create_dbinstance(instance_name, size, instance_type, username, password, **params)
|
||||
elif command == 'replicate':
|
||||
if instance_type:
|
||||
params["instance_class"] = instance_type
|
||||
db = conn.create_dbinstance_read_replica(instance_name, source_instance, **params)
|
||||
elif command == 'delete':
|
||||
changed = True
|
||||
|
||||
if command in ['create', 'restore', 'facts']:
|
||||
try:
|
||||
result = conn.get_all_dbinstances(instance_name)[0]
|
||||
changed = False
|
||||
except boto.exception.BotoServerError, e:
|
||||
try:
|
||||
if command == 'create':
|
||||
result = conn.create_dbinstance(instance_name, size, instance_type, username, password, **params)
|
||||
if command == 'restore':
|
||||
result = conn.restore_dbinstance_from_dbsnapshot(snapshot, instance_name, instance_type, **params)
|
||||
if command == 'facts':
|
||||
module.fail_json(msg = "DB Instance %s does not exist" % instance_name)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = e.error_message)
|
||||
|
||||
if command == 'snapshot':
|
||||
try:
|
||||
result = conn.get_all_dbsnapshots(snapshot)[0]
|
||||
changed = False
|
||||
except boto.exception.BotoServerError, e:
|
||||
try:
|
||||
result = conn.create_dbsnapshot(snapshot, instance_name)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = e.error_message)
|
||||
|
||||
if command == 'delete':
|
||||
try:
|
||||
result = conn.get_all_dbinstances(instance_name)[0]
|
||||
if result.status == 'deleting':
|
||||
module.exit_json(changed=False)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.exit_json(changed=False)
|
||||
try:
|
||||
if snapshot:
|
||||
params["skip_final_snapshot"] = False
|
||||
params["final_snapshot_id"] = snapshot
|
||||
else:
|
||||
params["skip_final_snapshot"] = True
|
||||
result = conn.delete_dbinstance(instance_name, **params)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = e.error_message)
|
||||
|
||||
db = conn.delete_dbinstance(instance_name, **params)
|
||||
elif command == 'modify':
|
||||
if command == 'replicate':
|
||||
try:
|
||||
if instance_type:
|
||||
params["instance_class"] = instance_type
|
||||
result = conn.create_dbinstance_read_replica(instance_name, source_instance, **params)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = e.error_message)
|
||||
|
||||
if command == 'modify':
|
||||
try:
|
||||
params["apply_immediately"] = apply_immediately
|
||||
db = conn.modify_dbinstance(instance_name, **params)
|
||||
if apply_immediately:
|
||||
if new_instance_name:
|
||||
# Wait until the new instance name is valid
|
||||
found = 0
|
||||
while found == 0:
|
||||
instances = conn.get_all_dbinstances()
|
||||
for i in instances:
|
||||
if i.id == new_instance_name:
|
||||
instance_name = new_instance_name
|
||||
found = 1
|
||||
if found == 0:
|
||||
time.sleep(5)
|
||||
else:
|
||||
# Wait for a few seconds since it takes a while for AWS
|
||||
# to change the instance from 'available' to 'modifying'
|
||||
time.sleep(5)
|
||||
result = conn.modify_dbinstance(instance_name, **params)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = e.error_message)
|
||||
if apply_immediately:
|
||||
if new_instance_name:
|
||||
# Wait until the new instance name is valid
|
||||
found = 0
|
||||
while found == 0:
|
||||
instances = conn.get_all_dbinstances()
|
||||
for i in instances:
|
||||
if i.id == new_instance_name:
|
||||
instance_name = new_instance_name
|
||||
found = 1
|
||||
if found == 0:
|
||||
time.sleep(5)
|
||||
else:
|
||||
# Wait for a few seconds since it takes a while for AWS
|
||||
# to change the instance from 'available' to 'modifying'
|
||||
time.sleep(5)
|
||||
|
||||
elif command == 'promote':
|
||||
db = conn.promote_read_replica(instance_name, **params)
|
||||
|
||||
# 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:
|
||||
module.fail_json(msg = e.error_message)
|
||||
if command == 'promote':
|
||||
try:
|
||||
result = conn.promote_read_replica(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 command == 'delete' and not wait:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
try:
|
||||
instances = conn.get_all_dbinstances(instance_name)
|
||||
my_inst = instances[0]
|
||||
try:
|
||||
resource = get_current_resource(conn, result.id, command)
|
||||
except boto.exception.BotoServerError, e:
|
||||
module.fail_json(msg = e.error_message)
|
||||
|
||||
|
||||
# Wait for the instance to be available if requested
|
||||
# Wait for the resource 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':
|
||||
while wait_timeout > time.time() and resource.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]
|
||||
module.fail_json(msg = "Timeout waiting for resource %s" % resource.id)
|
||||
resource = get_current_resource(conn, result.id, command)
|
||||
except boto.exception.BotoServerError, e:
|
||||
# If we're waiting for an instance to be deleted then
|
||||
# get_all_dbinstances will eventually throw a
|
||||
|
@ -545,35 +595,52 @@ def main():
|
|||
|
||||
# If we got here then pack up all the instance details to send
|
||||
# back to ansible
|
||||
if command == 'snapshot':
|
||||
d = {
|
||||
'id' : resource.id,
|
||||
'create_time' : resource.snapshot_create_time,
|
||||
'status' : resource.status,
|
||||
'availability_zone' : resource.availability_zone,
|
||||
'instance_id' : resource.instance_id,
|
||||
'instance_created' : resource.instance_create_time,
|
||||
}
|
||||
try:
|
||||
d["snapshot_type"] = resource.snapshot_type
|
||||
d["iops"] = resource.iops
|
||||
except AttributeError, e:
|
||||
pass # needs boto >= 2.21.0
|
||||
|
||||
return module.exit_json(changed=changed, snapshot=d)
|
||||
|
||||
d = {
|
||||
'id' : my_inst.id,
|
||||
'create_time' : my_inst.create_time,
|
||||
'status' : my_inst.status,
|
||||
'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
|
||||
'id' : resource.id,
|
||||
'create_time' : resource.create_time,
|
||||
'status' : resource.status,
|
||||
'availability_zone' : resource.availability_zone,
|
||||
'backup_retention' : resource.backup_retention_period,
|
||||
'backup_window' : resource.preferred_backup_window,
|
||||
'maintenance_window' : resource.preferred_maintenance_window,
|
||||
'multi_zone' : resource.multi_az,
|
||||
'instance_type' : resource.instance_class,
|
||||
'username' : resource.master_username,
|
||||
'iops' : resource.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]
|
||||
if resource.status == 'available' and command != 'snapshot':
|
||||
d["endpoint"] = resource.endpoint[0]
|
||||
d["port"] = resource.endpoint[1]
|
||||
else:
|
||||
d["endpoint"] = None
|
||||
d["port"] = None
|
||||
|
||||
# ReadReplicaSourceDBInstanceIdentifier may or may not exist
|
||||
try:
|
||||
d["replication_source"] = my_inst.ReadReplicaSourceDBInstanceIdentifier
|
||||
d["replication_source"] = resource.ReadReplicaSourceDBInstanceIdentifier
|
||||
except Exception, e:
|
||||
d["replication_source"] = None
|
||||
|
||||
module.exit_json(changed=True, instance=d)
|
||||
module.exit_json(changed=changed, instance=d)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
|
Loading…
Reference in a new issue