opzworks 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ def opsworks_list_ips(options = {})
2
+ response = @client.describe_instances options
3
+ @ip_addrs = []
4
+ response[:instances].each { |instance| @ip_addrs << instance.private_ip if instance[:status] == 'online' }
5
+ rescue StandardError => e
6
+ abort "Exception raised: #{e}".foreground(:red)
7
+ end
8
+
9
+ def es_get_input(input, data = {}, *cmd)
10
+ match = {}
11
+ count = 0
12
+
13
+ data[:stacks].each do |stack|
14
+ next unless stack[:name].chomp =~ /#{input}/
15
+ count = count += 1
16
+ match = stack.to_hash
17
+ end
18
+
19
+ # break?
20
+ if count < 1
21
+ puts 'No matching stacks found for input '.foreground(:yellow) + input.foreground(:green) + ', skipping.'.foreground(:yellow)
22
+ @get_data_failure = true
23
+ elsif count > 1
24
+ puts 'Found more than one stack matching input '.foreground(:yellow) + input.foreground(:green) + ', skipping.'.foreground(:yellow)
25
+ @get_data_failure = true
26
+ else
27
+ puts 'Operating on stack '.foreground(:blue) + "#{match[:name]}".foreground(:green)
28
+ layers = @client.describe_layers(stack_id: match[:stack_id])
29
+ layers[:layers].each { |layer| printf("%-30s %-50s\n", layer[:name], layer[:layer_id]) }
30
+
31
+ STDOUT.print 'Specify a layer: '.foreground(:blue)
32
+ layer = STDIN.gets.chomp
33
+
34
+ unless cmd.include? 'start'
35
+ STDOUT.print 'Disable shard allocation before starting? (true/false, default is true): '.foreground(:blue)
36
+ disable_allocation = STDIN.gets.chomp
37
+ case disable_allocation
38
+ when 'false'
39
+ @disable_shard_allocation = false
40
+ else
41
+ @disable_shard_allocation = true
42
+ end
43
+ end
44
+
45
+ options = {}
46
+ if layer == ''
47
+ puts 'Must specify a layer.'.foreground(:red)
48
+ abort
49
+ else
50
+ options[:layer_id] = layer
51
+ end
52
+ opsworks_list_ips(options)
53
+ end
54
+ end
55
+
56
+ def es_enable_allocation(ip, type)
57
+ puts "Cluster routing.allocation is being set to #{type}".foreground(:blue)
58
+ conn = Faraday.new(url: "http://#{ip}:9200") do |f|
59
+ f.adapter :net_http
60
+ end
61
+
62
+ count = 0
63
+ loop do
64
+ begin
65
+ conn.put do |req|
66
+ req.url '/_cluster/settings'
67
+ req.body = "{\"transient\": {\"cluster.routing.allocation.enable\": \"#{type}\"}}"
68
+ req.options[:timeout] = 5
69
+ req.options[:open_timeout] = 2
70
+ end
71
+ break
72
+ rescue StandardError => e
73
+ puts 'Caught exception while trying to change allocation state: '.foreground(:yellow) + "#{e}".foreground(:red) + ', looping around...'.foreground(:yellow) if count == 0
74
+ count += 1
75
+ sleep 1
76
+ end
77
+ end
78
+ end
79
+
80
+ def es_service(command, ips = [])
81
+ puts "Operating on ES with command #{command}".foreground(:yellow)
82
+ user = ENV['USER']
83
+
84
+ Net::SSH::Multi.start do |session|
85
+ ips.each do |ip|
86
+ session.use "#{user}@#{ip}"
87
+ end
88
+
89
+ session.exec "sudo service elasticsearch #{command}"
90
+ session.loop
91
+ end
92
+ end
93
+
94
+ def es_wait_for_status(ip, color)
95
+ puts 'Waiting for cluster to go '.foreground(:blue) + "#{color}".foreground(:"#{color}")
96
+ conn = Faraday.new(url: "http://#{ip}:9200") do |f|
97
+ f.adapter :net_http
98
+ end
99
+
100
+ count = 0
101
+ rescue_count = 0
102
+ loop do
103
+ begin
104
+ response = conn.get do |req|
105
+ req.url '/_cluster/health'
106
+ req.options[:timeout] = 5
107
+ req.options[:open_timeout] = 2
108
+ end
109
+ json = JSON.parse response.body
110
+ rescue StandardError => e
111
+ puts 'Caught exception while trying to check cluster status: '.foreground(:yellow) + "#{e}".foreground(:red) + ', looping around...'.foreground(:yellow) if rescue_count == 0
112
+ rescue_count += 1
113
+ printf '.'
114
+ sleep 1
115
+ else
116
+ case json['status']
117
+ when color
118
+ puts "\nCluster is now ".foreground(:blue) + "#{color}".foreground(:"#{color}")
119
+ break
120
+ when 'green'
121
+ puts "\nCluster is green, proceeding without waiting for requested status of #{color}".foreground(:green)
122
+ break
123
+ else
124
+ count += 1
125
+ if count == 10
126
+ puts "\nStill waiting, cluster is currently ".foreground(:blue) + "#{json['status']}".foreground(:"#{json['status']}")
127
+ count = 0
128
+ end
129
+ printf '.'
130
+ sleep 1
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,24 @@
1
+ def manage_berks_repos
2
+ config = OpzWorks.config
3
+ @target_path = File.expand_path(config.berks_repository_path + "/opsworks-#{@project}", File.dirname(__FILE__))
4
+
5
+ if !File.directory?(@target_path)
6
+ if config.berks_github_org.nil?
7
+ puts "#{@target_path} does not exist, and 'berks-github-org' is not set in ~/.aws/config, skipping.".foreground(:yellow)
8
+ @berks_repo_failure = true
9
+ else
10
+ puts "#{@target_path} does not exist!".foreground(:red)
11
+ puts 'Attempting git clone of '.foreground(:blue) + "opsworks-#{@project}.".foreground(:green)
12
+ run_local <<-BASH
13
+ cd #{config.berks_repository_path}
14
+ git clone git@github.com:#{config.berks_github_org}/opsworks-#{@project}.git
15
+ BASH
16
+ end
17
+ else
18
+ puts "Git pull from #{@target_path}, branch: ".foreground(:blue) + @branch.foreground(:green)
19
+ run_local <<-BASH
20
+ cd #{@target_path}
21
+ git checkout #{@branch} && git pull origin #{@branch}
22
+ BASH
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ def populate_stack(input, data = {})
2
+ # loops over inputs
3
+ match = {}
4
+ count = 0
5
+
6
+ data[:stacks].each do |stack|
7
+ next unless stack[:name].chomp =~ /#{input}/
8
+ count = count += 1
9
+ match = stack.to_hash
10
+ end
11
+
12
+ # break?
13
+ if count < 1
14
+ puts 'No matching stacks found for input '.foreground(:yellow) + input.foreground(:green) + ', skipping.'.foreground(:yellow)
15
+ @populate_stack_failure = true
16
+ elsif count > 1
17
+ puts 'Found more than one stack matching input '.foreground(:yellow) + input.foreground(:green) + ', skipping.'.foreground(:yellow)
18
+ @populate_stack_failure = true
19
+ else
20
+ @stack_json = match[:custom_json]
21
+ @project = match[:name].split('::').first
22
+ @s3_path = match[:name].gsub('::', '-')
23
+ @stack_id = match[:stack_id]
24
+ @branch = (match[:name].split('::')[1] + '-' + match[:name].split('::')[2]).gsub('::', '-')
25
+
26
+ hash = {
27
+ 'PROJECT:' => @project,
28
+ 'STACK ID:' => @stack_id,
29
+ 'S3 PATH:' => @s3_path,
30
+ 'BRANCH:' => @branch
31
+ }
32
+ puts "\n"
33
+ hash.each { |k, v| printf("%-25s %-25s\n", k.foreground(:green), v.foreground(:red)) }
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ # wrap run_locally so we can catch failures
2
+ def run_local(cmd)
3
+ require 'English'
4
+
5
+ system cmd
6
+ return unless $CHILD_STATUS.exitstatus != 0
7
+ puts 'exit code: ' + $CHILD_STATUS.exitstatus.to_s
8
+ exit
9
+ end
@@ -0,0 +1,81 @@
1
+ require 'aws-sdk'
2
+ require 'trollop'
3
+ require 'diffy'
4
+ require 'opzworks'
5
+ require 'rainbow/ext/string'
6
+
7
+ require_relative 'include/run_local'
8
+ require_relative 'include/populate_stack'
9
+ require_relative 'include/manage_berks_repos'
10
+
11
+ module OpzWorks
12
+ class Commands
13
+ class JSON
14
+ def self.banner
15
+ 'Update stack json'
16
+ end
17
+
18
+ def self.run
19
+ options = Trollop.options do
20
+ banner <<-EOS.unindent
21
+ #{JSON.banner}
22
+
23
+ opzworks json stack1 stack2 ...
24
+
25
+ The stack name can be passed as any unique regex. If there is
26
+ more than one match, it will simply be skipped.
27
+
28
+ Options:
29
+ EOS
30
+ opt :quiet, 'Update the stack json without confirmation', short: 'q', default: false
31
+ opt :context, 'Change the number lines of diff context to show', short: 'c', default: 5
32
+ end
33
+ ARGV.empty? ? Trollop.die('no stacks specified') : false
34
+
35
+ config = OpzWorks.config
36
+ client = Aws::OpsWorks::Client.new(region: config.aws_region, profile: config.aws_profile)
37
+ response = client.describe_stacks
38
+
39
+ # loops over inputs
40
+ ARGV.each do |opt|
41
+ populate_stack(opt, response)
42
+ next if @populate_stack_failure == true
43
+
44
+ manage_berks_repos
45
+ next if @berks_repo_failure == true
46
+
47
+ json = File.read("#{@target_path}/stack.json")
48
+ diff = Diffy::Diff.new(@stack_json + "\n", json, context: options[:context])
49
+ diff_str = diff.to_s(:color).chomp
50
+
51
+ hash = {}
52
+ hash[:stack_id] = @stack_id
53
+ hash[:custom_json] = json
54
+
55
+ if diff_str.empty?
56
+ puts 'There are no differences between the existing stack json and the json you\'re asking to push.'.foreground(:yellow)
57
+ else
58
+ if options[:quiet]
59
+ puts 'Quiet mode detected. Pushing the following updated json:'.foreground(:yellow)
60
+ puts diff_str
61
+
62
+ client.update_stack(hash)
63
+ puts 'Done!'.color(:green)
64
+ else
65
+ puts "The following is a partial diff of the existing stack json and the json you're asking to push:".foreground(:yellow)
66
+ puts diff_str
67
+ STDOUT.print "\nType ".foreground(:yellow) + 'yes '.foreground(:blue) + 'to continue, any other key will abort: '.foreground(:yellow)
68
+ input = STDIN.gets.chomp
69
+ if input =~ /^y/i
70
+ client.update_stack(hash)
71
+ puts 'Done!'.color(:green)
72
+ else
73
+ puts 'Update skipped.'.foreground(:red)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,100 @@
1
+ require 'aws-sdk'
2
+ require 'trollop'
3
+ require 'opzworks'
4
+
5
+ SSH_PREFIX = '# --- OpzWorks ---'
6
+ SSH_POSTFIX = '# --- End of OpzWorks ---'
7
+
8
+ module OpzWorks
9
+ class Commands
10
+ class SSH
11
+ def self.banner
12
+ 'Generate and update SSH configuration files'
13
+ end
14
+
15
+ def self.run
16
+ options = Trollop.options do
17
+ banner <<-EOS.unindent
18
+ #{SSH.banner}
19
+
20
+ opzworks ssh {stack1} {stack2} {...}
21
+
22
+ The stack name can be passed as any unique regex. If no
23
+ arguments are passed, the command will iterate over all stacks.
24
+
25
+ Options:
26
+ EOS
27
+ opt :update, 'Update ~/.ssh/config directly'
28
+ opt :backup, 'Backup old SSH config before updating'
29
+ opt :quiet, 'Use SSH LogLevel quiet', default: true
30
+ opt :private, 'Use private ips to populate SSH config, rather than public', default: false
31
+ end
32
+
33
+ config = OpzWorks.config
34
+ client = Aws::OpsWorks::Client.new(region: config.aws_region, profile: config.aws_profile)
35
+
36
+ stacks = []
37
+ stack_data = client.describe_stacks
38
+
39
+ if ARGV.empty?
40
+ stack_data[:stacks].each { |stack| stacks.push(stack) }
41
+ else
42
+ ARGV.each do |arg|
43
+ stack_data[:stacks].each do |stack|
44
+ stacks.push(stack) if stack[:name] =~ /#{arg}/
45
+ end
46
+ end
47
+ end
48
+
49
+ stacks.each do |stack|
50
+ instances = []
51
+ stack_name = ''
52
+
53
+ stack_name = stack[:name].gsub('::', '-')
54
+
55
+ result = client.describe_instances(stack_id: stack[:stack_id])
56
+ instances += result.instances.select { |i| i[:status] != 'stopped' }
57
+
58
+ instances.map! do |instance|
59
+ if options[:private]
60
+ ip = instance[:private_ip]
61
+ else
62
+ instance[:elastic_ip].nil? ? ip = instance[:public_ip] : ip = instance[:elastic_ip]
63
+ end
64
+ parameters = {
65
+ 'Host' => "#{instance[:hostname]}-#{stack_name}",
66
+ 'HostName' => ip,
67
+ 'User' => config.ssh_user_name
68
+ }
69
+ parameters['LogLevel'] = 'quiet' if options[:quiet]
70
+ parameters.map { |param| param.join(' ') }.join("\n ")
71
+ end
72
+
73
+ new_contents = "#{instances.join("\n")}\n"
74
+
75
+ if options[:update]
76
+ ssh_config = "#{ENV['HOME']}/.ssh/config"
77
+ old_contents = File.read(ssh_config)
78
+
79
+ if options[:backup]
80
+ backup_name = ssh_config + '.backup'
81
+ File.open(backup_name, 'w') { |file| file.puts old_contents }
82
+ end
83
+
84
+ File.open(ssh_config, 'w') do |file|
85
+ file.puts old_contents.gsub(
86
+ /\n?\n?#{SSH_PREFIX}.*#{SSH_POSTFIX}\n?\n?/m,
87
+ ''
88
+ )
89
+ file.puts new_contents
90
+ end
91
+
92
+ puts "Successfully updated #{ssh_config} with #{instances.length} instances!"
93
+ else
94
+ puts new_contents.strip
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,35 @@
1
+ require 'inifile'
2
+
3
+ module OpzWorks
4
+ def self.config
5
+ @config ||= Config.new
6
+ end
7
+
8
+ class Config
9
+ attr_reader :ssh_user_name, :berks_repository_path, :aws_region, :aws_profile,
10
+ :berks_base_path, :berks_s3_bucket, :berks_tarball_name, :berks_github_org
11
+
12
+ def initialize
13
+ file = ENV['AWS_CONFIG_FILE'] || "#{ENV['HOME']}/.aws/config"
14
+
15
+ fail 'AWS config file not found' unless File.exist? file
16
+ ini = IniFile.load(file)
17
+
18
+ # set the region and the profile we want to pick up from ~/.aws/credentials
19
+ @aws_profile = ENV['AWS_PROFILE'] || 'default'
20
+ @aws_region = ENV['AWS_REGION'] || ini[@aws_profile]['region']
21
+
22
+ @ssh_user_name = ini['opzworks']['ssh-user-name'].strip
23
+ @berks_repository_path = ini['opzworks']['berks-repository-path'].strip
24
+
25
+ @berks_base_path =
26
+ ini['opzworks']['berks-base-path'].strip unless ini['opzworks']['berks-base-path'].nil?
27
+ @berks_s3_bucket =
28
+ ini['opzworks']['berks-s3-bucket'].strip unless ini['opzworks']['berks-s3-bucket'].nil?
29
+ @berks_tarball_name =
30
+ ini['opzworks']['berks-tarball-name'].strip unless ini['opzworks']['berks-tarball-name'].nil?
31
+ @berks_github_org =
32
+ ini['opzworks']['berks-github-org'].strip unless ini['opzworks']['berks-github-org'].nil?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ module OpzWorks
2
+ VERSION = '0.3.0'
3
+ AUTHORS = ['Grant Heffernan', 'Mapzen']
4
+ EMAIL = ['grant@mapzen.com']
5
+ DESCRIPTION = 'OpzWorks Utilities'
6
+ SUMMARY = 'Command line interface for Amazon OpsWorks'
7
+ end
data/lib/opzworks.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'opzworks/meta'
2
+ require 'opzworks/config'
3
+ require 'opzworks/commands/ssh'
4
+ require 'opzworks/commands/json'
5
+ require 'opzworks/commands/berks'
6
+ require 'opzworks/commands/elastic'
7
+
8
+ class String
9
+ def unindent
10
+ gsub(/^#{self[/\A\s*/]}/, '')
11
+ end
12
+ end
13
+
14
+ module OpsWorks
15
+ end
data/opzworks.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ root = File.expand_path('..', __FILE__)
4
+ lib = File.expand_path('lib', root)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'opzworks/meta'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'opzworks'
10
+ spec.version = OpzWorks::VERSION
11
+ spec.authors = OpzWorks::AUTHORS
12
+ spec.email = OpzWorks::EMAIL
13
+ spec.description = OpzWorks::DESCRIPTION
14
+ spec.summary = OpzWorks::SUMMARY
15
+ spec.homepage = 'https://github.com/mapzen/opzworks'
16
+ spec.license = 'MIT'
17
+
18
+ ignores = File.readlines('.gitignore').grep(/\S+/).map(&:chomp)
19
+ spec.files = Dir['**/*'].reject do |f|
20
+ File.directory?(f) || ignores.any? { |i| File.fnmatch(i, f) }
21
+ end
22
+ spec.files += ['.gitignore']
23
+
24
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
25
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_dependency 'aws-sdk', '~> 2.2.7'
29
+ spec.add_dependency 'trollop', '~> 2.0'
30
+ spec.add_dependency 'inifile', '~> 2.0.2'
31
+ spec.add_dependency 'rubocop', '~> 0.35.0'
32
+ spec.add_dependency 'diffy', '~> 3.1.0'
33
+ spec.add_dependency 'rainbow', '~> 2.0.0'
34
+ spec.add_dependency 'faraday', '~> 0.9.2'
35
+ spec.add_dependency 'net-ssh', '~> 3.0.1'
36
+ spec.add_dependency 'net-ssh-multi', '~> 1.2.1'
37
+
38
+ spec.add_development_dependency 'bundler', '~> 1.3'
39
+ spec.add_development_dependency 'rake'
40
+ spec.add_development_dependency 'awesome_print'
41
+ end