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
4c168abccc
commit
5d41934873
1 changed files with 134 additions and 67 deletions
201
cloud/rds
201
cloud/rds
|
@ -28,7 +28,7 @@ options:
|
||||||
required: true
|
required: true
|
||||||
default: null
|
default: null
|
||||||
aliases: []
|
aliases: []
|
||||||
choices: [ 'create', 'replicate', 'delete', 'facts', 'modify' , 'promote' ]
|
choices: [ 'create', 'replicate', 'delete', 'facts', 'modify' , 'promote', 'snapshot', 'restore' ]
|
||||||
instance_name:
|
instance_name:
|
||||||
description:
|
description:
|
||||||
- Database instance identifier.
|
- Database instance identifier.
|
||||||
|
@ -56,7 +56,7 @@ options:
|
||||||
aliases: []
|
aliases: []
|
||||||
instance_type:
|
instance_type:
|
||||||
description:
|
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
|
required: false
|
||||||
default: null
|
default: null
|
||||||
aliases: []
|
aliases: []
|
||||||
|
@ -99,7 +99,7 @@ options:
|
||||||
aliases: []
|
aliases: []
|
||||||
license_model:
|
license_model:
|
||||||
description:
|
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
|
required: false
|
||||||
default: null
|
default: null
|
||||||
aliases: []
|
aliases: []
|
||||||
|
@ -162,7 +162,7 @@ options:
|
||||||
aliases: []
|
aliases: []
|
||||||
zone:
|
zone:
|
||||||
description:
|
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
|
required: false
|
||||||
default: null
|
default: null
|
||||||
aliases: ['aws_zone', 'ec2_zone']
|
aliases: ['aws_zone', 'ec2_zone']
|
||||||
|
@ -174,7 +174,7 @@ options:
|
||||||
aliases: []
|
aliases: []
|
||||||
snapshot:
|
snapshot:
|
||||||
description:
|
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
|
required: false
|
||||||
default: null
|
default: null
|
||||||
aliases: []
|
aliases: []
|
||||||
|
@ -192,7 +192,7 @@ options:
|
||||||
aliases: [ 'ec2_access_key', 'access_key' ]
|
aliases: [ 'ec2_access_key', 'access_key' ]
|
||||||
wait:
|
wait:
|
||||||
description:
|
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
|
required: false
|
||||||
default: "no"
|
default: "no"
|
||||||
choices: [ "yes", "no" ]
|
choices: [ "yes", "no" ]
|
||||||
|
@ -277,10 +277,18 @@ except ImportError:
|
||||||
print "failed=True msg='boto required for this module'"
|
print "failed=True msg='boto required for this module'"
|
||||||
sys.exit(1)
|
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():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec = dict(
|
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),
|
instance_name = dict(required=True),
|
||||||
source_instance = dict(required=False),
|
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),
|
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':
|
elif command == 'promote':
|
||||||
required_vars = [ 'instance_name' ]
|
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' ]
|
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:
|
for v in required_vars:
|
||||||
if not module.params.get(v):
|
if not module.params.get(v):
|
||||||
|
@ -466,74 +482,108 @@ def main():
|
||||||
if new_instance_name:
|
if new_instance_name:
|
||||||
params["new_instance_id"] = new_instance_name
|
params["new_instance_id"] = new_instance_name
|
||||||
|
|
||||||
try:
|
changed = True
|
||||||
if command == 'create':
|
|
||||||
db = conn.create_dbinstance(instance_name, size, instance_type, username, password, **params)
|
if command in ['create', 'restore', 'facts']:
|
||||||
elif command == 'replicate':
|
try:
|
||||||
if instance_type:
|
result = conn.get_all_dbinstances(instance_name)[0]
|
||||||
params["instance_class"] = instance_type
|
changed = False
|
||||||
db = conn.create_dbinstance_read_replica(instance_name, source_instance, **params)
|
except boto.exception.BotoServerError, e:
|
||||||
elif command == 'delete':
|
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:
|
if snapshot:
|
||||||
params["skip_final_snapshot"] = False
|
params["skip_final_snapshot"] = False
|
||||||
params["final_snapshot_id"] = snapshot
|
params["final_snapshot_id"] = snapshot
|
||||||
else:
|
else:
|
||||||
params["skip_final_snapshot"] = True
|
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)
|
if command == 'replicate':
|
||||||
elif command == 'modify':
|
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
|
params["apply_immediately"] = apply_immediately
|
||||||
db = conn.modify_dbinstance(instance_name, **params)
|
result = conn.modify_dbinstance(instance_name, **params)
|
||||||
if apply_immediately:
|
except boto.exception.BotoServerError, e:
|
||||||
if new_instance_name:
|
module.fail_json(msg = e.error_message)
|
||||||
# Wait until the new instance name is valid
|
if apply_immediately:
|
||||||
found = 0
|
if new_instance_name:
|
||||||
while found == 0:
|
# Wait until the new instance name is valid
|
||||||
instances = conn.get_all_dbinstances()
|
found = 0
|
||||||
for i in instances:
|
while found == 0:
|
||||||
if i.id == new_instance_name:
|
instances = conn.get_all_dbinstances()
|
||||||
instance_name = new_instance_name
|
for i in instances:
|
||||||
found = 1
|
if i.id == new_instance_name:
|
||||||
if found == 0:
|
instance_name = new_instance_name
|
||||||
time.sleep(5)
|
found = 1
|
||||||
else:
|
if found == 0:
|
||||||
# Wait for a few seconds since it takes a while for AWS
|
time.sleep(5)
|
||||||
# to change the instance from 'available' to 'modifying'
|
else:
|
||||||
time.sleep(5)
|
# 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':
|
if command == 'promote':
|
||||||
db = conn.promote_read_replica(instance_name, **params)
|
try:
|
||||||
|
result = conn.promote_read_replica(instance_name, **params)
|
||||||
# Don't do anything for the 'facts' command since we'll just drop down
|
except boto.exception.BotoServerError, e:
|
||||||
# to get_all_dbinstances below to collect the facts
|
module.fail_json(msg = e.error_message)
|
||||||
|
|
||||||
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
|
# If we're not waiting for a delete to complete then we're all done
|
||||||
# so just return
|
# so just return
|
||||||
if command == 'delete' and not wait:
|
if command == 'delete' and not wait:
|
||||||
module.exit_json(changed=True)
|
module.exit_json(changed=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
instances = conn.get_all_dbinstances(instance_name)
|
resource = get_current_resource(conn, result.id, command)
|
||||||
my_inst = instances[0]
|
|
||||||
except boto.exception.BotoServerError, e:
|
except boto.exception.BotoServerError, e:
|
||||||
module.fail_json(msg = e.error_message)
|
module.fail_json(msg = e.error_message)
|
||||||
|
|
||||||
|
# Wait for the resource to be available if requested
|
||||||
# Wait for the instance to be available if requested
|
|
||||||
if wait:
|
if wait:
|
||||||
try:
|
try:
|
||||||
wait_timeout = time.time() + wait_timeout
|
wait_timeout = time.time() + wait_timeout
|
||||||
time.sleep(5)
|
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)
|
time.sleep(5)
|
||||||
if wait_timeout <= time.time():
|
if wait_timeout <= time.time():
|
||||||
module.fail_json(msg = "Timeout waiting for database instance %s" % instance_name)
|
module.fail_json(msg = "Timeout waiting for resource %s" % resource.id)
|
||||||
instances = conn.get_all_dbinstances(instance_name)
|
resource = get_current_resource(conn, result.id, command)
|
||||||
my_inst = instances[0]
|
|
||||||
except boto.exception.BotoServerError, e:
|
except boto.exception.BotoServerError, e:
|
||||||
# If we're waiting for an instance to be deleted then
|
# If we're waiting for an instance to be deleted then
|
||||||
# get_all_dbinstances will eventually throw a
|
# 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
|
# If we got here then pack up all the instance details to send
|
||||||
# back to ansible
|
# 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 = {
|
d = {
|
||||||
'id' : my_inst.id,
|
'id' : resource.id,
|
||||||
'create_time' : my_inst.create_time,
|
'create_time' : resource.create_time,
|
||||||
'status' : my_inst.status,
|
'status' : resource.status,
|
||||||
'availability_zone' : my_inst.availability_zone,
|
'availability_zone' : resource.availability_zone,
|
||||||
'backup_retention' : my_inst.backup_retention_period,
|
'backup_retention' : resource.backup_retention_period,
|
||||||
'backup_window' : my_inst.preferred_backup_window,
|
'backup_window' : resource.preferred_backup_window,
|
||||||
'maintenance_window' : my_inst.preferred_maintenance_window,
|
'maintenance_window' : resource.preferred_maintenance_window,
|
||||||
'multi_zone' : my_inst.multi_az,
|
'multi_zone' : resource.multi_az,
|
||||||
'instance_type' : my_inst.instance_class,
|
'instance_type' : resource.instance_class,
|
||||||
'username' : my_inst.master_username,
|
'username' : resource.master_username,
|
||||||
'iops' : my_inst.iops
|
'iops' : resource.iops
|
||||||
}
|
}
|
||||||
|
|
||||||
# Endpoint exists only if the instance is available
|
# Endpoint exists only if the instance is available
|
||||||
if my_inst.status == 'available':
|
if resource.status == 'available' and command != 'snapshot':
|
||||||
d["endpoint"] = my_inst.endpoint[0]
|
d["endpoint"] = resource.endpoint[0]
|
||||||
d["port"] = my_inst.endpoint[1]
|
d["port"] = resource.endpoint[1]
|
||||||
else:
|
else:
|
||||||
d["endpoint"] = None
|
d["endpoint"] = None
|
||||||
d["port"] = None
|
d["port"] = None
|
||||||
|
|
||||||
# ReadReplicaSourceDBInstanceIdentifier may or may not exist
|
# ReadReplicaSourceDBInstanceIdentifier may or may not exist
|
||||||
try:
|
try:
|
||||||
d["replication_source"] = my_inst.ReadReplicaSourceDBInstanceIdentifier
|
d["replication_source"] = resource.ReadReplicaSourceDBInstanceIdentifier
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
d["replication_source"] = None
|
d["replication_source"] = None
|
||||||
|
|
||||||
module.exit_json(changed=True, instance=d)
|
module.exit_json(changed=changed, instance=d)
|
||||||
|
|
||||||
# import module snippets
|
# import module snippets
|
||||||
from ansible.module_utils.basic import *
|
from ansible.module_utils.basic import *
|
||||||
|
|
Loading…
Reference in a new issue