Skip to content
This repository has been archived by the owner on Jun 5, 2020. It is now read-only.

(CLOUD-295) Add RDS resource support #165

Merged
merged 1 commit into from
Jun 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 159 additions & 27 deletions README.md

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions examples/postgres-rds-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# RDS

[Amazon Relational Database Service](http://aws.amazon.com/rds/) (Amazon RDS) is a web service that makes it easy to set up, operate, and scale a relational database in the cloud.

## How

This example creates a security group to allow access to a Postgres RDS instance, then creates that RDS instance with the security group assigned.

puppet apply rds_security.pp

Unfortunately, it's not possible to assign the EC2 group and the allowed IPs to the `db_securitygroup` through the API, so you have to do this manually though the console for now:

## Add the Security Group We Made with Puppet**
![Add EC2 Security Group](./images/add-rds-securitygroup.png?raw=true)

## Add an IP to allow access to the RDS instance
**Note: Enter `0.0.0.0/32` to allow all IPs**
![Add IP to allow](./images/add-ip-to-allow.png?raw=true)

## It should look something like this
![Final Look](./images/final-screen.png?raw=true)

You can now check your security group is correct by using Puppet resource commands:

puppet resource rds_db_securitygroup rds-postgres-db_securitygroup

It should return something like this:

~~~
rds_db_securitygroup { 'rds-postgres-db_securitygroup':
ensure => 'present',
ec2_security_groups => [{'ec2_security_group_id' => 'sg-83fb3z5', 'ec2_security_group_name' => 'rds-postgres-group', 'ec2_security_group_owner_id' => '4822239859', 'status' => 'authorized'}],
ip_ranges => [{'ip_range' => '0.0.0.0/32', 'status' => 'authorized'}],
owner_id => '239838031',
region => 'us-west-2',
}
~~~

When this is complete, create the RDS Postgres instance:

puppet apply rds_postgres.pp

This can take a while to setup, but when it's complete, you should be able to access it:

~~~
psql -d postgresql -h puppetlabs-aws-postgres.cwgutxb9fmx.us-west-2.rds.amazonaws.com -U root
Password for user root: pullZstringz345
psql (9.4.0, server 9.3.5)
SSL connection (protocol: TLSv1.2, cipher: DHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
postgresql=> exit
~~~
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions examples/postgres-rds-example/rds_postgres.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
rds_instance { 'puppetlabs-aws-postgres':
ensure => present,
allocated_storage => '5',
db_instance_class => 'db.m3.medium',
db_name => 'postgresql',
engine => 'postgres',
license_model => 'postgresql-license',
db_security_groups => 'rds-postgres-db_securitygroup',
master_username => 'root',
master_user_password=> 'pullZstringz345',
region => 'us-west-2',
skip_final_snapshot => 'true',
storage_type => 'gp2',
}
18 changes: 18 additions & 0 deletions examples/postgres-rds-example/rds_security.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ec2_securitygroup { 'rds-postgres-group':
ensure => present,
region => 'us-west-2',
description => 'Group for Allowing access to Postgres (Port 5432)',
ingress => [{
security_group => 'rds-postgres-group',
},{
protocol => 'tcp',
port => 5432,
cidr => '0.0.0.0/0',
}]
}

rds_db_securitygroup { 'rds-postgres-db_securitygroup':
ensure => present,
region => 'us-west-2',
description => 'An RDS Security group to allow Postgres',
}
31 changes: 31 additions & 0 deletions lib/puppet/provider/rds_db_parameter_group/v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative '../../../puppet_x/puppetlabs/aws.rb'

Puppet::Type.type(:rds_db_parameter_group).provide(:v2, :parent => PuppetX::Puppetlabs::Aws) do
confine feature: :aws

mk_resource_methods

def self.instances
regions.collect do |region|
instances = []
rds_client(region).describe_db_parameter_groups.each do |response|
response.data.db_parameter_groups.each do |db_parameter_group|
# There's always a default class
hash = db_parameter_group_to_hash(region, db_parameter_group)
instances << new(hash) if hash[:name]
end
end
instances
end.flatten
end

def self.db_parameter_group_to_hash(region, db_parameter_group)
{
:name => db_parameter_group.db_parameter_group_name,
:description => db_parameter_group.description,
:family => db_parameter_group.db_parameter_group_family,
:region => region,
}
end

end
93 changes: 93 additions & 0 deletions lib/puppet/provider/rds_db_securitygroup/v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
require_relative '../../../puppet_x/puppetlabs/aws.rb'

Puppet::Type.type(:rds_db_securitygroup).provide(:v2, :parent => PuppetX::Puppetlabs::Aws) do
confine feature: :aws

mk_resource_methods

def self.instances
regions.collect do |region|
instances = []
rds_client(region).describe_db_security_groups.each do |response|
response.data.db_security_groups.each do |db_security_group|
# There's always a default class
unless db_security_group.db_security_group_name =~ /^default$/
hash = db_security_group_to_hash(region, db_security_group)
instances << new(hash) if hash[:name]
end
end
end
instances
end.flatten
end

def self.prefetch(resources)
instances.each do |prov|
if resource = resources[prov.name] # rubocop:disable Lint/AssignmentInCondition
resource.provider = prov if resource[:region] == prov.region
end
end
end

read_only(:region, :description)

def self.db_security_group_to_hash(region, db_security_group)
{
:ensure => :present,
:region => region,
:name => db_security_group.db_security_group_name,
:description => db_security_group.db_security_group_description,
:owner_id => db_security_group.owner_id,
:security_groups => ec2_security_group_to_array_of_hashes(db_security_group.ec2_security_groups),
:ip_ranges => ip_ranges_to_array_of_hashes(db_security_group.ip_ranges),
}
end

def exists?
Puppet.info("Checking if DB Security Group #{name} exists")
[:present, :creating, :available].include? @property_hash[:ensure]
end

def create
Puppet.info("Creating DB Security Group #{name}")
config = {
:db_security_group_name => resource[:name],
:db_security_group_description => resource[:description],
}

rds_client(resource[:region]).create_db_security_group(config)

@property_hash[:ensure] = :present
end

def destroy
Puppet.info("Deleting DB Security Group #{name} in region #{resource[:region]}")
rds = rds_client(resource[:region])
config = {
db_security_group_name: name,
}
rds.delete_db_security_group(config)
@property_hash[:ensure] = :absent
end

def self.ec2_security_group_to_array_of_hashes(ec2_security_groups)
ec2_security_groups.collect do |group|
{
:status => group.status,
:ec2_security_group_name => group.ec2_security_group_name,
:ec2_security_group_owner_id => group.ec2_security_group_owner_id,
:ec2_security_group_id => group.ec2_security_group_id,
}
end
end

def self.ip_ranges_to_array_of_hashes(ip_ranges)
ip_ranges.collect do |group|
{
:status => group.status,
:ip_range => group.cidrip,
}
end
end

end
107 changes: 107 additions & 0 deletions lib/puppet/provider/rds_instance/v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
require_relative '../../../puppet_x/puppetlabs/aws.rb'

Puppet::Type.type(:rds_instance).provide(:v2, :parent => PuppetX::Puppetlabs::Aws) do
confine feature: :aws

mk_resource_methods

def self.instances
regions.collect do |region|
instances = []
rds_client(region).describe_db_instances.each do |response|
response.data.db_instances.each do |db|
unless db.db_instance_status =~ /^deleted$|^deleting$/
hash = db_instance_to_hash(region, db)
instances << new(hash) if hash[:name]
end
end
end
instances
end.flatten
end

read_only(:iops, :master_username, :multi_az, :license_model,
:db_name, :region, :db_instance_class, :availability_zone,
:engine, :engine_version, :allocated_storage, :storage_type,
:db_security_groups, :db_parameter_group)

def self.prefetch(resources)
instances.each do |prov|
if resource = resources[prov.name] # rubocop:disable Lint/AssignmentInCondition
resource.provider = prov if resource[:region] == prov.region
end
end
end

def self.db_instance_to_hash(region, instance)
config = {
ensure: :present,
name: instance.db_instance_identifier,
region: region,
engine: instance.engine,
engine_version: instance.engine_version,
db_instance_class: instance.db_instance_class,
master_username: instance.master_username,
db_name: instance.db_name,
allocated_storage: instance.allocated_storage,
storage_type: instance.storage_type,
license_model: instance.license_model,
multi_az: instance.multi_az,
iops: instance.iops,
db_parameter_group: instance.db_parameter_groups.collect(&:db_parameter_group_name).first,
db_security_groups: instance.db_security_groups.collect(&:db_security_group_name),
}
if instance.respond_to?('endpoint') && !instance.endpoint.nil?
config[:endpoint] = instance.endpoint.address
config[:port] = instance.endpoint.port
end
config
end

def exists?
dest_region = resource[:region] if resource
Puppet.info("Checking if instance #{name} exists in region #{dest_region || region}")
[:present, :creating, :available, :backing_up].include? @property_hash[:ensure]
end

def create
Puppet.info("Starting DB instance #{name}")
config = {
db_instance_identifier: resource[:name],
db_name: resource[:db_name],
db_instance_class: resource[:db_instance_class],
engine: resource[:engine],
engine_version: resource[:engine_version],
license_model: resource[:license_model],
storage_type: resource[:storage_type],
multi_az: resource[:multi_az].to_s,
allocated_storage: resource[:allocated_storage],
iops: resource[:iops],
master_username: resource[:master_username],
master_user_password: resource[:master_user_password],
subnet_group_name: resource[:db_subnet],
db_security_groups: resource[:db_security_groups],
db_parameter_group_name: resource[:db_parameter_group],
}

rds_client(resource[:region]).create_db_instance(config)

@property_hash[:ensure] = :present
end

def destroy
Puppet.info("Deleting database #{name} in region #{resource[:region]}")
rds = rds_client(resource[:region])
if resource[:skip_final_snapshot].to_s == 'true'
Puppet.info("A snapshot of the database on deletion will be available as #{resource[:final_db_snapshot_identifier]}")
end
config = {
db_instance_identifier: name,
skip_final_snapshot: resource[:skip_final_snapshot].to_s,
final_db_snapshot_identifier: resource[:final_db_snapshot_identifier],
}
rds.delete_db_instance(config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with aws api, but this looks as if I can set skip_final_snapshot to any value I like at destroy time
why is it marked as readonly then ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. On further investigation here these should be params not properties, they aren't set at creation and they aren't readable back from AWS. They only affect things at deletion time. I've pushed the changes and related test fixes. Cheers.

@property_hash[:ensure] = :absent
end

end
33 changes: 33 additions & 0 deletions lib/puppet/type/rds_db_parameter_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Puppet::Type.newtype(:rds_db_parameter_group) do
@doc = 'Type representing an RDS DB Parameter group.'

newparam(:name, namevar: true) do
desc 'The name of the DB Parameter Group (also known as the db_parameter_group_name).'
validate do |value|
fail 'name should be a String' unless value.is_a?(String)
end
end

newproperty(:description) do
desc 'The description of a DB parameter group.'
validate do |value|
fail 'description should be a String' unless value.is_a?(String)
end
end

newproperty(:family) do
desc 'The name of the DB family that this DB parameter group is compatible with (eg. mysql5.1).'
validate do |value|
fail 'family should be a String' unless value.is_a?(String)
end
end

newproperty(:region) do
desc 'The region in which to create the db_parameter_group.'
validate do |value|
fail 'region should be a String' unless value.is_a?(String)
fail 'region should not contain spaces' if value =~ /\s/
end
end

end
Loading