class Ridoku::Base
Constants
- POSTGRES_GROUP_NAME
Attributes
account[RW]
account_id[RW]
app[RW]
app_list[RW]
aws_client[RW]
config[RW]
custom_json[RW]
ec2_client[RW]
iam_client[RW]
instance_list[RW]
instances[RW]
layer_list[RW]
layers[RW]
permissions[RW]
stack[RW]
stack_list[RW]
Public Class Methods
base_command(app_id, instance_ids, comment)
click to toggle source
# File lib/ridoku/base.rb, line 571 def base_command(app_id, instance_ids, comment) fail ArgumentError.new('[ERROR] No instances selected.') if !instance_ids.is_a?(Array) || instance_ids.empty? {}.tap do |cmd| cmd[:instance_ids] = instance_ids cmd[:app_id] = app_id if app_id cmd[:comment] = comment if comment end end
color_code_logs(logs)
click to toggle source
# File lib/ridoku/base.rb, line 642 def color_code_logs(logs) $stderr.puts(logs.gsub(%r((?<color>\[[0-9]{1,2}m)),"\e\\k<color>")) end
configure_ec2_client()
click to toggle source
# File lib/ridoku/base.rb, line 236 def configure_ec2_client return if self.ec2_client self.ec2_client = AWS::EC2.new end
configure_iam_client()
click to toggle source
# File lib/ridoku/base.rb, line 229 def configure_iam_client return if self.iam_client iam = AWS::IAM.new self.iam_client = iam.client end
configure_instance_roles()
click to toggle source
# File lib/ridoku/base.rb, line 353 def configure_instance_roles return true if instance_role_configured? fetch_account instance_role = "%7B%22Version%22%3A%222008-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D" instance_resource = 'role/aws-opsworks-ec2-role' instance_role_arn = "arn:aws:iam::#{account_id}:#{instance_resource}" end
configure_opsworks_client()
click to toggle source
# File lib/ridoku/base.rb, line 58 def configure_opsworks_client opsworks = AWS::OpsWorks.new self.aws_client = opsworks.client end
configure_roles()
click to toggle source
# File lib/ridoku/base.rb, line 348 def configure_roles configure_service_roles configure_instance_roles end
configure_service_roles()
click to toggle source
# File lib/ridoku/base.rb, line 362 def configure_service_roles return true if service_role_configured? fetch_account opsworks_role = "%7B%22Version%22%3A%222008-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22opsworks.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D" opsworks_resource = 'role/aws-opsworks-service-role' opsworks_role_arn = "arn:aws:iam::#{account_id}:#{opsworks_resource}" end
create_app(conf)
click to toggle source
# File lib/ridoku/base.rb, line 379 def create_app(conf) conf[:stack_id] = stack[:stack_id] # Ensure key exists key_file = conf[:app_source][:ssh_key] fail ArgumentError.new('Key file doesn\'t exist.') unless File.exists?(key_file) File.open(key_file, 'r') { |f| conf[:app_source][:ssh_key] = f.read } # Config[:attributes] must be a hash of <string,string> type. conf[:attributes].tap do |opt| opt.keys.each do |k| opt[k.to_s.camelize] = opt.delete(k).to_s unless k.is_a?(String) end end # Ensure attribute 'rails_env' is specified fail ArgumentError.new('attribute:rails_env must be specified.') unless conf[:attributes]['RailsEnv'].length > 0 if config[:practice] $stdout.puts conf.to_s else aws_client.create_app(conf) initialize_app_environment(conf) end end
create_role(conf)
click to toggle source
# File lib/ridoku/base.rb, line 371 def create_role(conf) if config[:practice] puts conf.to_s else iam_client.create_role(conf) end end
deploy(app_id, instance_ids, comment, custom_json = nil)
click to toggle source
# File lib/ridoku/base.rb, line 600 def deploy(app_id, instance_ids, comment, custom_json = nil) base_command(app_id, instance_ids, comment).tap do |cmd| cmd[:command] = { name: 'deploy' } cmd[:custom_json] = custom_json if custom_json end end
execute_recipes(app_id, instance_ids, comment, recipes, custom_json = nil)
click to toggle source
# File lib/ridoku/base.rb, line 589 def execute_recipes(app_id, instance_ids, comment, recipes, custom_json = nil) base_command(app_id, instance_ids, comment).tap do |cmd| cmd[:command] = { name: 'execute_recipes', args: { 'recipes' => [recipes].flatten } } cmd[:custom_json] = custom_json if custom_json end end
extract_instance_ids(layers = nil)
click to toggle source
# File lib/ridoku/base.rb, line 554 def extract_instance_ids(layers = nil) Base.fetch_instance(layers || Base.config[:layers] || :all, force: true) names = Base.config[:instances] || [] instances = Base.instances.select do |inst| if names.length > 0 names.index(inst[:hostname]) != nil && inst[:status] != 'offline' else inst[:status] == 'online' end end instances.map do |inst| inst[:instance_id] end end
fetch_account(options = {})
click to toggle source
# File lib/ridoku/base.rb, line 285 def fetch_account(options = {}) return account if account && !options[:force] configure_iam_client self.account = iam_client.get_user self.account_id = nil account[:user][:arn].match(/.*:.*:.*:.*:([0-9]+)/) do |m| self.account_id = m[1] end fail StandardError.new('Failed to determine account ID from user info (it was me not you!)!') unless account_id account end
fetch_app(options = {})
click to toggle source
# File lib/ridoku/base.rb, line 96 def fetch_app(options = {}) return app if app && !options[:force] fetch_stack app_name = config[:app] fail InvalidConfig.new(:app, :none) unless app_name self.app_list = aws_client.describe_apps(stack_id: stack[:stack_id])[:apps] self.app = nil app_list.each do |sapp| self.app = sapp if app_name == sapp[:name] end fail InvalidConfig.new(:app, :invalid) unless app return app end
fetch_instance(shortname = :all, options = {})
click to toggle source
‘lb’ - load balancing layers ‘rails-app’ ‘custom’
# File lib/ridoku/base.rb, line 193 def fetch_instance(shortname = :all, options = {}) return instances if instances && !options[:force] fetch_stack unless instance_list self.instance_list = self.instances = aws_client.describe_instances(stack_id: stack[:stack_id])[:instances] end if shortname != :all fetch_layer(shortname, force: true) self.instances = [] layers.each do |layer| instance = aws_client.describe_instances( layer_id: layer[:layer_id]) self.instances << instance[:instances] end self.instances.flatten! end end
fetch_layer(shortname = :all, options = {})
click to toggle source
# File lib/ridoku/base.rb, line 137 def fetch_layer(shortname = :all, options = {}) return layers if layers && !options[:force] fetch_stack unless self.layer_list self.layers = self.layer_list = aws_client.describe_layers( stack_id: stack[:stack_id])[:layers] end if shortname != :all shortname = [shortname] unless shortname.is_a?(Array) self.layers = [] shortname.each do |short| self.layers << self.layer_list.select do |layer| layer[:shortname] == short end end self.layers.flatten! end end
fetch_permissions(options = {})
click to toggle source
# File lib/ridoku/base.rb, line 305 def fetch_permissions(options = {}) fetch_stack fetch_account return permissions if permissions && !options[:force] self.permissions = aws_client.describe_permissions( iam_user_arn: account[:user][:arn], stack_id: stack[:stack_id] ) end
fetch_roles()
click to toggle source
# File lib/ridoku/base.rb, line 317 def fetch_roles configure_iam_client service = 'aws-opsworks-service-role' instance = 'aws-opsworks-ec2-role' iam_client.list_roles[:roles].each do |role| config[:instance_arn] = role[:arn] if role[:role_name] == instance && !config.key?(:instance_arn) config[:service_arn] = role[:arn] if role[:role_name] == service && !config.key?(:service_arn) end end
fetch_stack(options = {})
click to toggle source
# File lib/ridoku/base.rb, line 63 def fetch_stack(options = {}) return stack if stack && !options[:force] configure_opsworks_client stack_name = config[:stack] fail InvalidConfig.new(:stack, :none) unless stack_name || !options[:force] self.stack_list = aws_client.describe_stacks[:stacks] self.stack = nil stack_list.each do |stck| self.stack = stck if stack_name == stck[:name] end fail InvalidConfig.new(:stack, :invalid) if !stack && !options[:force] self.custom_json = JSON.parse(stack[:custom_json]) if stack return stack end
get_instances_for_layer(layer)
click to toggle source
# File lib/ridoku/base.rb, line 216 def get_instances_for_layer(layer) layer_ids = get_layer_ids(layer) instances = aws_client .describe_instances(stack_id: stack[:stack_id])[:instances] ret = [] layer_ids.each do |id| instances.each do |inst| ret << inst if inst[:layer_ids].include?(id) end end ret end
get_layer_ids(shortname)
click to toggle source
# File lib/ridoku/base.rb, line 160 def get_layer_ids(shortname) fetch_stack layers = aws_client.describe_layers(stack_id: stack[:stack_id])[:layers] layers.select { |l| l[:shortname] == shortname } .map { |l| l[:layer_id] } end
if_debug?() { || ... }
click to toggle source
# File lib/ridoku/base.rb, line 329 def if_debug?(&block) yield if config[:debug] end
initialize_app_environment(conf)
click to toggle source
# File lib/ridoku/base.rb, line 409 def initialize_app_environment(conf) fetch_stack fetch_layer fetch_instance app_layer = layer_list.select do |lyr| lyr[:shortname] == 'rails-app' end.first db_layer = layer_list.select do |lyr| lyr[:shortname] == 'postgresql' end.first deploy_info = custom_json['deploy'] app = conf[:shortname] instance = instances.select do |inst| inst[:status] == 'online' && inst[:layer_ids].index(app_layer[:layer_id]) != nil end.first db_instance = instances.select do |inst| inst[:layer_ids].index(db_layer[:layer_id]) != nil end.first dbase_info = { database: app, username: SecureRandom.hex(12), user_password: SecureRandom.hex(12) } ((custom_json['postgresql'] ||= {})['databases'] ||= []) << dbase_info deploy_info[app] = { auto_assets_precompile_on_deploy: true, assetmaster: instance[:hostname], app_env: { 'RAILS_ENV' => conf[:attributes]['RailsEnv'] }, database: { adapter: 'postgresql', username: dbase_info[:username], database: dbase_info[:database], host: db_instance[:public_ip], password: dbase_info[:user_password], port: custom_json['postgresql']['config']['port'] } } save_stack # Update add our changes to the database. run_command({ instance_ids: [db_instance[:instance_id]], command: { name: 'execute_recipes', args: { 'recipes' => 'postgresql::create_databases' } } }) end
instance_by_id(id)
click to toggle source
# File lib/ridoku/base.rb, line 185 def instance_by_id(id) fetch_instance instance_list.select { |is| is[:instance_id] == id }.first end
instance_role_configured?()
click to toggle source
# File lib/ridoku/base.rb, line 343 def instance_role_configured? fetch_roles config.key?(:instance_arn) && config[:instance_arn] != nil end
load_config(path)
click to toggle source
# File lib/ridoku/base.rb, line 31 def load_config(path) if File.exists?(path) File.open(path, 'r') do |file| self.config = JSON.parse(file.read, symbolize_names: true) end end (self.config ||= {}).tap do |default| default[:wait] = true end end
monitor_deployment(dep_ids)
click to toggle source
# File lib/ridoku/base.rb, line 646 def monitor_deployment(dep_ids) cmds = aws_client.describe_commands(deployment_id: dep_ids) commands = cmds[:commands].map do |cmd| { command: cmd, instance: instance_by_id(cmd[:instance_id]) } end $stdout.puts "Command issued to #{commands.length} instances:" commands.each do |cmd| $stdout.puts " #{$stdout.colorize(cmd[:instance][:hostname], :green)}" end # Iterate a reasonable number of times... 100*5 => 500 seconds 20.times do |time| cmds = aws_client.describe_commands(deployment_id: dep_ids) success = cmds[:commands].select do |cmd| cmd[:status] == 'successful' end # Show we are still thinking... case time % 4 when 0 print "\\\r" when 1 print "|\r" when 2 print "/\r" when 3 print "-\r" end if cmds.length == success.length $stdout.puts 'Command executed successfully.' return end # Collect the non-[running,pending,successful] command entries not_ok = cmds[:commands].select do |cmd| ['running', 'pending', 'successful'].index(cmd[:status]) == nil end.map do |cmd| { command: cmd, instance: instance_by_id(cmd[:instance_id]) } end # Print each one that has failed. not_ok.each do |item| $stderr.puts "#{item[:instance][:hostname]}" $stderr.puts " Status: " + $stderr.colorize(item[:command][:status], :red) $stderr.puts " Url: " + item[:command][:log_url] color_code_logs(RestClient.get(item[:command][:log_url])) exit 1 end sleep 5 end end
postgresql_group_exists?(region = 'us-west-1')
click to toggle source
# File lib/ridoku/base.rb, line 242 def postgresql_group_exists?(region = 'us-west-1') configure_ec2_client ec2_client.security_groups.filter('group-name', POSTGRES_GROUP_NAME).length > 0 end
pretty_instances(io)
click to toggle source
# File lib/ridoku/base.rb, line 506 def pretty_instances(io) inststr = [] instances.each do |inst| val = "#{inst[:hostname]} [#{inst[:status]}]" inststr << io.colorize(val, [:bold, inst[:status] == 'online' ? :green : :red]) end inststr end
roles_configured?()
click to toggle source
# File lib/ridoku/base.rb, line 333 def roles_configured? fetch_roles service_role_configured? && instance_role_configured? end
rollback(app_id, instance_ids, comment, custom_json = nil)
click to toggle source
# File lib/ridoku/base.rb, line 609 def rollback(app_id, instance_ids, comment, custom_json = nil) dep = deploy(app_id, instance_ids, comment, custom_json) dep[:command] = { name: 'rollback' } dep end
run_command(deployment)
click to toggle source
# File lib/ridoku/base.rb, line 518 def run_command(deployment) fetch_stack fetch_app deployment[:stack_id] = stack[:stack_id] if config[:practice] $stdout.puts "Would run command: #{deployment[:command][:name]}" $stdout.puts 'On instances:' instances.each do |inst| next unless deployment[:instance_ids].index(inst[:instance_id]) != nil $stdout.puts " #{inst[:hostname]}: #{$stdout.colorize( inst[:status], inst[:status] == 'online' ? :green : :red)}" end if deployment.key?(:custom_json) $stdout.puts 'With custom_json:' $stdout.puts JSON.pretty_generate(deployment[:custom_json]) end else if deployment.key?(:custom_json) deployment[:custom_json] = JSON.generate(deployment[:custom_json]) end depid = aws_client.create_deployment(deployment)[:deployment_id] $stdout.puts $stdout.colorize('Command Sent', :green) if config[:verbose] monitor_deployment(depid) if config[:wait] end end
save_app(values)
click to toggle source
# File lib/ridoku/base.rb, line 116 def save_app(values) values = [values] unless values.is_a?(Array) unless app $stderr.puts "Unable to save information because no app is " + "specified." return end save_info = { app_id: app[:app_id] } save_info.tap do |info| values.each do |val| info[val] = app[val] end end aws_client.update_app(save_info) end
save_config(path, limit = [:app, :stack, :ssh_key, :local_init, :shell_user, :service_arn, :instance_arn, :backup_bucket])
click to toggle source
# File lib/ridoku/base.rb, line 43 def save_config(path, limit = [:app, :stack, :ssh_key, :local_init, :shell_user, :service_arn, :instance_arn, :backup_bucket]) save = {} if limit.length limit.each do |lc| save[lc] = config[lc] end else save = config end File.open(path, 'w') do |file| file.write(save.to_json) end end
save_layer(layer, values)
click to toggle source
# File lib/ridoku/base.rb, line 167 def save_layer(layer, values) values = [values] unless values.is_a?(Array) return unless values.length > 0 save_info = { layer_id: layer[:layer_id] } save_info.tap do |info| values.each do |val| info[val] = layer[val] end end aws_client.update_layer(save_info) end
save_stack()
click to toggle source
# File lib/ridoku/base.rb, line 88 def save_stack aws_client.update_stack( stack_id: stack[:stack_id], custom_json: custom_json.to_json, service_role_arn: stack[:service_role_arn] ) if stack end
select_instances(args)
click to toggle source
# File lib/ridoku/base.rb, line 494 def select_instances(args) fetch_instance return instance_list unless args args = [args] unless args.is_a?(Array) return nil if args.length == 0 self.instances = instance_list.select do |inst| args.index(inst[:hostname]) != nil end end
service_role_configured?()
click to toggle source
# File lib/ridoku/base.rb, line 338 def service_role_configured? fetch_roles config.key?(:service_arn) && config[:service_arn] != nil end
standard_deploy(layer = :all, custom_json = nil)
click to toggle source
# File lib/ridoku/base.rb, line 616 def standard_deploy(layer = :all, custom_json = nil) fetch_instance(layer, force: true) fetch_app instances.select! { |inst| inst[:status] == 'online' } instance_ids = instances.map { |inst| inst[:instance_id] } unless config[:quiet] $stdout.puts "Application:" $stdout.puts " #{$stdout.colorize(app[:name], :bold)}" $stdout.puts "#{instances.length} instance(s):" pretty_instances($stdout).each do |inst| $stdout.puts " #{inst}" end $stdout.puts "Repository:" $stdout.puts " #{$stdout.colorize(app[:app_source][:url], :bold)}"\ " @ #{$stdout.colorize(app[:app_source][:revision], :bold)}" end run_command(deploy(app[:app_id], instance_ids, config[:comment], custom_json)) end
update_cookbooks(instance_ids)
click to toggle source
# File lib/ridoku/base.rb, line 582 def update_cookbooks(instance_ids) command = Base.base_command(nil, instance_ids, Base.config[:comment]) command[:command] = { name: 'update_custom_cookbooks' } command end
update_pg_security_group(client = self.ec2_client)
click to toggle source
# File lib/ridoku/base.rb, line 255 def update_pg_security_group(client = self.ec2_client) fetch_stack port = 5432 if custom_json.key?('postgresql') && custom_json['postgresql'].key?('config') custom_json['postgresql']['config'].key?('port') port = custom_json['postgresql']['config']['port'] end perm_match = false group = client.security_groups.filter('group-name', POSTGRES_GROUP_NAME).first unless group $stdout.puts "Creating security group: #{POSTGRES_GROUP_NAME} in #{client.regions.first.name}" group = client.security_groups.create(POSTGRES_GROUP_NAME) else group.ingress_ip_permissions.each do |ipperm| if ipperm.protocol == :tcp && ipperm.port_range == port..port perm_match = true else ipperm.revoke end end end group.authorize_ingress(:tcp, port) unless perm_match end
update_pg_security_groups_in_all_regions()
click to toggle source
# File lib/ridoku/base.rb, line 248 def update_pg_security_groups_in_all_regions AWS.regions.each do |region| $stdout.puts "Checking region: #{region.name}" update_pg_security_group(region.ec2) end end
valid_instances?(args)
click to toggle source
# File lib/ridoku/base.rb, line 471 def valid_instances?(args) args = [args] unless args.is_a?(Array) return false if args.length == 0 fetch_instance inst_names = instances.map do |inst| # if requested is stop, its definitely invalid. return false if args.index(inst[:hostname]) != nil && inst[:status] == 'stopped' inst[:hostname] end # if a requested is not in the list, then its an invalid list. args.each do |arg| return false if inst_names.index(arg) == nil end true end