Skip to content

Instantly share code, notes, and snippets.

@excalq
Created February 9, 2017 04:47
Show Gist options
  • Save excalq/664c7577b0e244332c10ac5b822f1e24 to your computer and use it in GitHub Desktop.
Save excalq/664c7577b0e244332c10ac5b822f1e24 to your computer and use it in GitHub Desktop.
DevClusters - Build/Start/Stop an AWS cluster for a user
#!/usr/bin/env ruby
USER="arthur"
AWS_KEY="/Users/arthur/.ssh/DevClusters.pem"
CLUSTER_ROLES={ # Build in this order:
nfs: {image: 'ami-a0951573', name: 'devcluster-nfs', secgroups: ['sg-b37b7886','sg-aa7a697f'], count: 1},
redis: {image: 'ami-10201b69', name: 'devcluster-redis', secgroups: ['sg-b37b7886','sg-aa7a697f'], count: 1},
databrain: {image: 'ami-8a30206d', name: 'devcluster-databrain', secgroups: ['sg-b37b7886','sg-aa7a697f'], count: 1},
mysql: {image: 'ami-cc5b10fd', name: 'devcluster-mysql', secgroups: ['sg-b37b7886','sg-aa7a697f'], count: 2},
mongodb: {image: 'ami-56102071', name: 'devcluster-mongodb', secgroups: ['sg-b37b7886','sg-aa7a697f'], count: 2},
app: {image: 'ami-2b415b75', name: 'devcluster-app', secgroups: ['sg-b37b7886','sg-aa7a697f','sg-b57c68a0'], count: 1}
}
SEC_GROUPS=[]
DRY_RUN=false
require 'aws-sdk-core'
require 'json'
require 'awesome_print'
class InstanceManager
@ec2 = nil
def initialize(region)
@ec2 = Aws::EC2::Client.new(region: region)
end
def build_instance(config_params)
secgroups = config_params.delete(:secgroups) || []
config_params = {min_count: 1, max_count: 1, instance_type: 't2.micro',
dry_run: DRY_RUN,
key_name: 'DevClusters',
network_interfaces: [
device_index: 0,
subnet_id: 'subnet-9b97890a',
associate_public_ip_address: true,
groups: ['sg-941af7e1'] # The default sec-group. More are appended below
]
}.merge(config_params)
config_params[:network_interfaces].each{|ni| ni[:groups] += secgroups} unless secgroups.empty?
begin
return @ec2.run_instances(config_params)
rescue Aws::EC2::Errors::DryRunOperation
puts "Dry run succeeded for #{config_params}"
end
end
# Returns array of AwsInstances
def build_all_instances
started_instance_ids = []
cluster_instances = []
CLUSTER_ROLES.each do |role, cfg|
cfg[:count].times do |i|
suffix = ''
suffix = i.to_s if cfg[:count] > 1
cluster_instances << AwsInstance.new(cfg.merge({role: role, role_sequence: i, suffix: suffix}))
end
end
cluster_instances.each do |instance|
result = build_instance({image_id: instance.image, secgroups: instance.secgroups})
instance.id = result[:instances].first[:instance_id]
started_instance_ids << instance.id
end
wait_for_new_instances(started_instance_ids)
puts "New instances are now online."
ap @ec2.describe_instances(instance_ids: started_instance_ids)
cluster_instances.each do |instance|
unless DRY_RUN
# Set ip address info
instance_data = @ec2.describe_instances(instance_ids: [instance.id])
instance.public_ip_address = instance_data[:reservations].first[:instances].first[:public_ip_address]
instance.private_ip_address = instance_data[:reservations].first[:instances].first[:private_ip_address]
# Set tags (Name, Owner)
tag_instances([instance.id], "Name", "#{instance.name}#{instance.suffix}")
tag_instances([instance.id], "Owner", USER)
end
end
puts "Created new instances: \n\t#{cluster_instances.map(&:to_s).join("\n\t")}"
cluster_instances
end
def wait_for_new_instances(started_instance_ids)
# Wait until they're running...
puts "Waiting for new instances to become ready..."
@ec2.wait_until(:instance_running, {instance_ids: started_instance_ids})
end
def tag_instances(ids, tag_name, tag_value)
@ec2.create_tags(
resources: ids,
tags: [
{key: tag_name, value: tag_value}
])
end
def configure_instances(cluster)
# Writes internal IP addresses to specific configuration files
end
def get_instance_ips
ap instance[:network_interfaces][0][:association][:public_ip]
end
end
class AwsInstance
attr_accessor :name
attr_accessor :id
attr_accessor :role
attr_accessor :image
attr_accessor :secgroups
attr_accessor :role_sequence # when multiple instances exist per role
attr_accessor :suffix # number at end of multiple-instance names
attr_accessor :public_ip_address
attr_accessor :private_ip_address
attr_accessor :errors
def initialize(*attrs)
if attrs.length == 1 && attrs.first.kind_of?(Hash)
attrs.first.each { |k,v| send("#{k}=",v) if self.respond_to? k }
end
end
def to_s
{
name: name,
id: id,
role: role,
role_sequence: role_sequence,
public_ip_address: public_ip_address,
private_ip_address: private_ip_address
}
end
end
######################
# Read API Credentials for IAM user
unless File.exists?('secrets.json')
puts 'You must first create a secrets.json file, containing this data:
{
"region": "us-west-2",
"AccessKeyId": "your-aws-access-key",
"SecretAccessKey": "your-aws-secret-key"
}'
puts 'Exiting for now.'
exit(1)
end
creds = JSON.load(File.read('secrets.json'))
Aws.config[:credentials] = Aws::Credentials.new(creds['AccessKeyId'], creds['SecretAccessKey'])
im = InstanceManager.new(creds['region'])
######################
# build Cluster
if ARGV.nil? or ARGV.count < 1
puts "Usage: #{$0} [operation]
Where [operation] is one of:
build_cluster
start_cluster
stop_cluster"
exit 1
end
case ARGV[0]
when 'build_cluster'
cluster = im.build_all_instances
im.configure_instances(cluster)
else
puts "Sorry #{ARGV[0]} is not supported yet (or is invalid)."
end
@excalq
Copy link
Author

excalq commented Feb 9, 2017

Add a secrets.json file as follows:

{
  "region": "us-west-2",
  "AccessKeyId": "your-aws-access-key",
  "SecretAccessKey": "your-aws-secret-key"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment